Use a bounded queue to limit the damage from SYN floods, router overload, or a slow client.
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate classWorker that pulls SYNs from the accept queue, processes them, and puts resulting connections into the result queue.private static classSimple end-of-queue marker.(package private) static classAccept-queue bookkeeping for one enqueued SYN.private classPeriodic reaper that removes SYN packets from the accept queue after their accept timeout has elapsed, sending a reset if it was a SYN.Field Summary
FieldsModifier and TypeFieldDescriptionprivate intExplicit per-manager override, or -1 to use the live configuredgetAcceptTimeout().The threads accepting connections.private booleanTrue while the handler accepts work.private final AtomicIntegerWorkers currently handling a connection.private final ByteCacheCache supplying the packet buffers.private final I2PAppContextThe app context.private longRouter-clock time of the last tunnel-stress sample.private longRouter-clock time of the last SYN expire-rate sample.private final LogLogger for the connection handler.private final ConnectionManagerThe manager owning this handler.private booleanTrue to restart the handler after the current pass.private final LinkedBlockingQueue<Connection> The connections waiting for a worker.private final ConcurrentHashMap<Packet, ConnectionHandler.SynEntry> Enqueue records for packets currently in (or just polled from) the accept queue, keyed by packet identity.private final LinkedBlockingDeque<Packet> Packets waiting to be sent.private final LongAdderSYNs that expired un-accepted (removed bySynReaper) in the current window.private final LongAdderSYNs added to the acceptance queue within the current sample window.private intMost recently observed SYN accept-queue residence time (ms), i.e.private final SimpleTimer2Timer running the reaper tasks.private doubleCached tunnel build success fraction; NaN when unavailable.private final AtomicIntegerSequence number of the worker threads.private static final intDefault number of accept worker threads.private static final intDefault max SYN queue size — large enough to absorb bursts.private static final intCeiling for scale-up under SYN queue pressure, seeConnectionHandler.SynReaper.(package private) static final intBaseline recent-SYN expire rate (percent) that must be exceeded, along with low build success, before the clamp is armed.(package private) static final intDefault RTT scale factor: the adaptive SYN floor isscale * recentRTT(capped at the configured timeout) whenever a positive stall is detected.(package private) static final intMinimum SYN accept-queue timeout (ms) kept even on a genuinely stressed, fast fabric.private static final longMinimum interval between re-sampling tunnel build success (ms).(package private) static final doubleBuild success fraction below which the tunnel system is considered stressed.(package private) static final longInterval between SYN accept-queue sweeps (ms).Constructor Summary
ConstructorsConstructorDescriptionConnectionHandler(I2PAppContext context, ConnectionManager mgr, SimpleTimer2 timer) Creates a new instance of ConnectionHandler.Method Summary
Modifier and TypeMethodDescriptionaccept(long timeoutMs) Receive an incoming connection.private intLive configured accept timeout; re-read each SYN so config changes apply without a restart.private intNumber of accept worker threads.booleanCheck if this handler is actively accepting new connections.(package private) static intgetAdaptiveSynTimeout(int configuredTimeoutMs, double buildSuccess, int recentExpireRatePct, int rttMs) Compute the effective SYN accept-queue timeout for an inbound SYN.private intThe effective SYN accept-queue timeout: the configured timeout, clamped -- only while the tunnel system shows positive stall evidence -- to an RTT-aware floor never belowSYN_STRESS_MIN_TIMEOUTand never above the configured timeout.private intThis is both SYNs and subsequent packets, and with an initial window size of 12, this is a backlog of 5 to 64 Syns, which seems like plenty for now Don't make this too big: removal of an expired entry is O(queue size), so a sweep that reaps the whole queue is O(n**2) - sortof.(package private) intReturns the max queue size for Tuner integration.(package private) intReturns the current SYN accept queue depth for Tuner integration.private intgetRttMs()A recent round-trip time sample in milliseconds, or 0 when unavailable.private intRecent SYN expire rate, sampled on the same interval as build success.private doubleTunnel build success fraction, sampled at most once perSYN_STRESS_SAMPLE_INTERVALto avoid per-packet StatManager lookups (eachSystemVersionprobe is six RateStat reads).private voidpublishSynExpireRate(int ratePct) Publish the SYN accept-queue expire rate as a router-wide RateStat so the Tuner can distinguish latency-bound accept stalls (high expire rate: more handler threads will not help.voidreceiveNewSyn(Packet packet) Receive new connection attemptsprivate voidreReceivePacket(Packet packet) We found a non-SYN packet that was queued in the syn queue, check to see if it has a home now, else drop it ...private voidresendSynAck(Connection con, Packet syn) Re-send a SYN-ACK in response to a retransmitted SYN for an existing connection.(package private) static intresolveAcceptTimeout(int overrideMs, int configDefaultMs) Resolve the effective SYN accept-queue timeout from an explicit override and the live configured value.private voidsampleSynResidence(Packet syn) Record how long a freshly accepted SYN spent in the accept queue.private voidSend a reset in response to this packet, but only if it contains a FROM field and Signature that can be verified.(package private) voidsetAcceptTimeout(int ms) Max time a received SYN may sit in the accept queue beforeaccept(long)is expected to pull it.voidsetActive(boolean active) Whether this handler is actively accepting new connections.voidThe router told us it's going to restart.private voidStop all accept worker threads.private voidStart one accept worker, named uniquely.private voidStart the accept worker threads.(package private) static intsynExpireRatePct(int processed, int expired) The expire rate of one sample window: percent of queued SYNs that were removed by SynReaper without ever being accepted.(package private) static StringsynExpiryMessage(Packet syn, int queueDepth, int maxQueueSize, int timeoutMs) Full single-line DEBUG detail for a timed-out SYN, folding in both the packet detail and the queue/timeout pressure that caused the expiry.(package private) static voidsynExpirySummary(Packet syn, StringBuilder out) Build a diagnostic message for a SYN that expired on the accept queue without being accepted.
Field Details
_context
The app context._log
Logger for the connection handler._cache
Cache supplying the packet buffers._manager
The manager owning this handler._synQueue
Packets waiting to be sent._resultQueue
The connections waiting for a worker._acceptWorkers
_activeWorkerCount
Workers currently handling a connection._workerId
Sequence number of the worker threads._timer
Timer running the reaper tasks._active
private volatile boolean _activeTrue while the handler accepts work._acceptTimeout
private volatile int _acceptTimeoutExplicit per-manager override, or -1 to use the live configuredgetAcceptTimeout()._restartPending
private boolean _restartPendingTrue to restart the handler after the current pass.SYN_STRESS_THRESHOLD
static final double SYN_STRESS_THRESHOLDBuild success fraction below which the tunnel system is considered stressed.- See Also:
SYN_STRESS_MIN_TIMEOUT
static final int SYN_STRESS_MIN_TIMEOUTMinimum SYN accept-queue timeout (ms) kept even on a genuinely stressed, fast fabric.- See Also:
SYN_RTT_SCALE_DEFAULT
static final int SYN_RTT_SCALE_DEFAULTDefault RTT scale factor: the adaptive SYN floor isscale * recentRTT(capped at the configured timeout) whenever a positive stall is detected. 4 keeps a handshake alive through roughly four round-trips of server/queue latency, matching live 3-10s-RTT fabrics while still failing fast on genuinely dead fast tunnels. Tunable viaI2PSocketManagerFull.setRttSynTimeoutScale(int).- See Also:
SYN_EXPIRE_THRESHOLD_DEFAULT
static final int SYN_EXPIRE_THRESHOLD_DEFAULTBaseline recent-SYN expire rate (percent) that must be exceeded, along with low build success, before the clamp is armed. 100 disables the clamp.- See Also:
SYN_STRESS_SAMPLE_INTERVAL
private static final long SYN_STRESS_SAMPLE_INTERVALMinimum interval between re-sampling tunnel build success (ms).- See Also:
SYN_SWEEP_INTERVAL
static final long SYN_SWEEP_INTERVALInterval between SYN accept-queue sweeps (ms). Far shorter than any accept timeout, so expiry latency is dominated by the timeout itself.- See Also:
DEFAULT_MAX_QUEUE_SIZE
private static final int DEFAULT_MAX_QUEUE_SIZEDefault max SYN queue size — large enough to absorb bursts.- See Also:
DEFAULT_ACCEPT_WORKERS
private static final int DEFAULT_ACCEPT_WORKERSDefault number of accept worker threads. One worker cannot drain a queue thousands deep on a busy public server (tracker, mail), so every SYN behind it waits the full accept timeout.- See Also:
MAX_ACCEPT_WORKERS
private static final int MAX_ACCEPT_WORKERSCeiling for scale-up under SYN queue pressure, seeConnectionHandler.SynReaper.- See Also:
_lastStressSampleAt
private volatile long _lastStressSampleAtRouter-clock time of the last tunnel-stress sample._lastSynExpireSampleAt
private volatile long _lastSynExpireSampleAtRouter-clock time of the last SYN expire-rate sample. Kept separate from_lastStressSampleAtbecause each sampler has its own window and one must not refresh the other's (that would starve this window's rollover:getSynExpireRatePct()would never publish)._tunnelBuildSuccess
private volatile double _tunnelBuildSuccessCached tunnel build success fraction; NaN when unavailable._synQueueProcessed
SYNs added to the acceptance queue within the current sample window. ALongAdderbecause the accept workers (4-16 of them) and theSynReaperall increment it; a volatile int with++is not atomic and the window silently lost updates, which is what the adaptive clamp below is built on._synQueueExpired
SYNs that expired un-accepted (removed bySynReaper) in the current window. Same multi-writer reasoning as_synQueueProcessed._synQueueResidenceMs
private volatile int _synQueueResidenceMsMost recently observed SYN accept-queue residence time (ms), i.e. how long a fresh SYN waited in the queue before being accepted. 0 until the first acceptance._synEnqueueTimes
Enqueue records for packets currently in (or just polled from) the accept queue, keyed by packet identity. Value holds the enqueue clock-time and the accept timeout snapshot taken at that moment; a worker that polls a SYN refreshes the entry so the expiry matches the timeout the client was quoted. Swept bySynReaper; re-arming is O(1) per SYN instead of the two per-packet timer events of the oldTimeoutSyndesign.
Constructor Details
ConnectionHandler
Creates a new instance of ConnectionHandler.- Parameters:
context- the application context supplying the clock and loggingmgr- the connection manager owning this handler and its queuestimer- the shared timer used to schedule SYN expiry sweeps
Method Details
setAcceptTimeout
void setAcceptTimeout(int ms) Max time a received SYN may sit in the accept queue beforeaccept(long)is expected to pull it. If accept() is delayed (busy or slow hosted server), the queued SYN is reset after this window, so it must be long enough to absorb brief stalls without refusing inbound connections. Tunable viai2p.streaming.acceptTimeout(default: 60000 / 60 seconds). On a congested fabric RTT is commonly 3-10s, so 30s was too short once a SYN actually queued; 60s gives the RTT-aware floor (seegetAdaptiveSynTimeout(int, double, int, int)) headroom to extend on slow-but-alive tunnels without refusing them. The configured value is re-read on every SYN, so changes apply without a restart unless this override is set.- Parameters:
ms- an explicit override, or < 0 to return to the live configured value
getAcceptTimeout
private int getAcceptTimeout()Live configured accept timeout; re-read each SYN so config changes apply without a restart.- Returns:
- the i2p.streaming.acceptTimeout property in ms, 60000 if unset
getMaxQueueSize
private int getMaxQueueSize()This is both SYNs and subsequent packets, and with an initial window size of 12, this is a backlog of 5 to 64 Syns, which seems like plenty for now Don't make this too big: removal of an expired entry is O(queue size), so a sweep that reaps the whole queue is O(n**2) - sortof. Read dynamically from config or Tuner — no restart required.- Returns:
- the max queue size
getAcceptWorkerCount
private int getAcceptWorkerCount()Number of accept worker threads. Dynamically adjustable via Tuner.- Returns:
- the number of accept worker threads
getAdaptiveSynTimeout
static int getAdaptiveSynTimeout(int configuredTimeoutMs, double buildSuccess, int recentExpireRatePct, int rttMs) Compute the effective SYN accept-queue timeout for an inbound SYN.A queued SYN is reset via
SynReaperafter this window, so it bounds how long a client waits for a connection the server never got around to accepting. A fixed low clamp (e.g. the historical 10s) fails fast on genuinely dead tunnels but also expires slow-but-alive handshakes on a high-latency / congested fabric, which surfaces as empty pages to every client. So the clamp is made evidence-gated and RTT-aware:Gate (#4). The clamp fires only when there is positive evidence of a real stall, not mere latency:
buildSuccessbelowSYN_STRESS_THRESHOLDand the recent SYN expire rate (recentExpireRatePct) at or above the Tuner threshold (I2PSocketManagerFull.getSynExprExpireThresh(), default 60). A server that is draining its SYN queue — even on a slow tunnel — is treated as healthy and keeps the full configured window.RTT-aware floor (#2). When the clamp does fire, the window is not a flat constant: the floor rises with the sampled round-trip time
rttMs. On a fast fabric the floor isSYN_STRESS_MIN_TIMEOUT(true fast-fail); on a slow fabric it isscale * rttMs, so a slow-but-alive tunnel keeps substantially longer than the historical 10s and its handshake has room to complete. The result is always capped at the configured timeout and never exceeds it.No data never triggers the clamp: a NaN build success (stand-alone streaming or early startup) returns the configured timeout unchanged, an unknown expire rate (
< 0) is treated as "no evidence" (no clamp), and a non-positive configured timeout is returned unchanged. A fractional 0.0 build success is genuine failure, not "no data".- Parameters:
configuredTimeoutMs- the configured accept timeout (i2p.streaming.acceptTimeout)buildSuccess- tunnel build success as a fraction [0.0, 1.0]; NaN when unavailablerecentExpireRatePct- percent of recent SYN-queue entries that expired un-accepted, or a negative value when the rate is not known yetrttMs- a recent round-trip time sample in milliseconds, or <=0 when unavailable- Returns:
- the timeout (ms) to arm the SYN reap with
getTunnelBuildSuccess
private double getTunnelBuildSuccess()Tunnel build success fraction, sampled at most once perSYN_STRESS_SAMPLE_INTERVALto avoid per-packet StatManager lookups (eachSystemVersionprobe is six RateStat reads).Outside a router context (stand-alone streaming) there are no tunnel statistics at all. Report NaN so the configured timeout is kept.
SystemVersionreports 0% both for "no tunnel events yet" and for real failure, so 0 is treated as unknown (NaN) rather than stress.- Returns:
- the build success fraction [0.0, 1.0], or NaN when unavailable
getSynExpireRatePct
private int getSynExpireRatePct()Recent SYN expire rate, sampled on the same interval as build success.Counts SYNs added to the accept queue (
_synQueueProcessed) and SYNs later removed bySynReaperwithout being accepted (_synQueueExpired). The window resets whenever a fullSYN_STRESS_SAMPLE_INTERVALelapses, so the rate reflects the most recent tunnel-health window rather than the ever since startup.Until the first window completes there is no evidence, which is reported as
-1so the adaptive clamp never fires on startup.- Returns:
- percent of queued SYNs that expired un-accepted [0,100], or -1 when not enough history has accumulated yet
synExpireRatePct
static int synExpireRatePct(int processed, int expired) The expire rate of one sample window: percent of queued SYNs that were removed by SynReaper without ever being accepted.Pure and static so the input to the adaptive stress clamp can be tested without a running streaming handler.
- Parameters:
processed- SYNs added to the accept queue during the windowexpired- of those, SYNs that expired un-accepted- Returns:
- percent [0,100] of queued SYNs that expired, or -1 when the window holds no evidence yet, i.e. nothing was processed. Clamped at 100: a SYN enqueued in an earlier window and reaped in this one is counted as expired here without having been processed here, so the raw ratio can exceed 1.
publishSynExpireRate
private void publishSynExpireRate(int ratePct) Publish the SYN accept-queue expire rate as a router-wide RateStat so the Tuner can distinguish latency-bound accept stalls (high expire rate: more handler threads will not help. The transport is the bottleneck) from a genuinely parallelizable load. Only meaningful in a router context, and only when a real rate is available.- Parameters:
ratePct- the expire rate percent, or -1 when there is no evidence
getEffectiveAcceptTimeout
private int getEffectiveAcceptTimeout()The effective SYN accept-queue timeout: the configured timeout, clamped -- only while the tunnel system shows positive stall evidence -- to an RTT-aware floor never belowSYN_STRESS_MIN_TIMEOUTand never above the configured timeout. The configured value is re-read on every call, so arouter.configchange or Tuner override applies without a restart.- Returns:
- timeout in ms to arm the SYN reap with
resolveAcceptTimeout
static int resolveAcceptTimeout(int overrideMs, int configDefaultMs) Resolve the effective SYN accept-queue timeout from an explicit override and the live configured value. A non-negative override (set viasetAcceptTimeout(int), e.g. byI2PSocketManagerFull.setAcceptTimeout(long)) wins; otherwise the configured value — re-read on each call so arouter.configchange or Tuner override applies without a restart — is used. A negative override clears any earlier explicit value and falls back to the configuration.Pure decision helper, extracted so the override-vs-config precedence is unit-testable without a router context.
- Parameters:
overrideMs- the explicit override, or < 0 for "not set"configDefaultMs- the live configured accept timeout- Returns:
- the timeout in ms to arm the SYN reap with
getRttMs
private int getRttMs()A recent round-trip time sample in milliseconds, or 0 when unavailable.On a hosted (server) connection there is no handshake RTT to read before the connection exists, so the adaptive floor uses the SYN accept-queue residence time as a direct proxy for how long the server takes to reach a freshly queued SYN: the window that must exceed this residence to avoid expiring slow handshakes. An unavailable/zero sample keeps the fixed
SYN_STRESS_MIN_TIMEOUTfloor and is always safe.- Returns:
- a recent queue-residence RTT in milliseconds, or 0 when not known yet
sampleSynResidence
Record how long a freshly accepted SYN spent in the accept queue.Called from the accept loop immediately before a fresh SYN is handed to
receiveConnection(). The enqueue timestamp (stamped inreceiveNewSyn) is removed and the residence time becomes the sampledgetRttMs()input to the adaptive SYN timeout. The sample is lightly smoothed toward the previous value so a single slow acceptance does not swing the floor all the way to configured on its own.Retransmitted and poison SYNs never reach this point, so only genuinely fresh handshakes are measured.
- Parameters:
syn- the freshly de-queued SYN about to be accepted
setRestartPending
public void setRestartPending()The router told us it's going to restart. Call instead of setActive(false).setActive
public void setActive(boolean active) Whether this handler is actively accepting new connections. When set to false, a poison packet is offered to wake any threads blocked in accept().- Parameters:
active- true to accept connections, false to stop
getActive
public boolean getActive()Check if this handler is actively accepting new connections.- Returns:
- true if accepting connections
startAcceptWorkers
private void startAcceptWorkers()Start the accept worker threads. Called when setActive(true).startAcceptWorker
private void startAcceptWorker()Start one accept worker, named uniquely. Synchronized because theConnectionHandler.SynReapermay scale the pool up while the app activates or deactivates the handler.shutdownAcceptWorkers
private void shutdownAcceptWorkers()Stop all accept worker threads. Called when setActive(false).getQueueDepth
int getQueueDepth()Returns the current SYN accept queue depth for Tuner integration.- Returns:
- queue size / max queue size
getQueueCapacity
int getQueueCapacity()Returns the max queue size for Tuner integration.- Returns:
- the configured max queue size
receiveNewSyn
Receive new connection attemptsUse a bounded queue to limit the damage from SYN floods, router overload, or a slow client.
- Parameters:
packet- the incoming connection attempt, null to do nothing
accept
public Connection accept(long timeoutMs) throws RouterRestartException, ConnectException, SocketTimeoutException Receive an incoming connection. Polls the result queue populated by accept worker threads.- Parameters:
timeoutMs- max amount of time to wait for a connection (if less than 1ms, wait indefinitely)- Returns:
- the connection received, never null; a timeout or a shut-down handler is reported by throwing instead
- Throws:
RouterRestartException- (extends I2PException) if the router is apparently restartingConnectException- if the I2PServerSocket is closed, or if interrupted.SocketTimeoutException- if a timeout was previously set with setSoTimeout and the timeout has been reached.
reReceivePacket
We found a non-SYN packet that was queued in the syn queue, check to see if it has a home now, else drop it ...- Parameters:
packet- the queued packet to re-route, logged and ignored if null
sendReset
Send a reset in response to this packet, but only if it contains a FROM field and Signature that can be verified.- Parameters:
packet- the incoming packet we're responding to
resendSynAck
Re-send a SYN-ACK in response to a retransmitted SYN for an existing connection. This prevents the race condition where: 1. Client sends SYN, server creates connection, sends SYN-ACK 2. Client retransmits SYN before receiving SYN-ACK (RTT > RTO) 3. Server destroys the old connection (which the client already completed handshake on) 4. Client's data arrives on dead stream IDs - dropped foreverInstead, we just re-send the SYN-ACK for the existing connection, which is the standard TCP behavior for retransmitted SYNs.
- Parameters:
con- the existing connection to re-send the SYN-ACK forsyn- the retransmitted SYN packet
synExpirySummary
Build a diagnostic message for a SYN that expired on the accept queue without being accepted. The historical message ("Expired on the SYN queue: " + packet) printed only stream IDs and flags — no peer hash, no queue pressure, no timeout — so a flood of these read as identical trailing-colon lines that could not be attributed or rate-assessed.This surfaces the data needed to tell "one flooding dest" apart from "many legit clients the app never accepted":
- the source dest hash (short base32 prefix), which is the peer that sent the SYN and what the per-dest flood gate keys on;
- current queue depth vs max, so a spike is visible as near-full;
- the effective accept timeout that expired it (evidence-gated clamp).
Idempotent and side-effect free so it can be unit-tested; the appends to
outlet the same formatting be reused for other SYN expiry reports (e.g. DEBUG-level summaries) without duplicating the format.- Parameters:
syn- the expired SYN, described as "null SYN" when nullout- the buffer receiving the packet description
synExpiryMessage
Full single-line DEBUG detail for a timed-out SYN, folding in both the packet detail and the queue/timeout pressure that caused the expiry. The WARN-level report is one aggregate per sweep inConnectionHandler.SynReaper.- Parameters:
syn- the expired SYN, described as "null SYN" when nullqueueDepth- the accept queue depth at expiry, as the count of unaccepted SYNsmaxQueueSize- the configured queue cap, 0 when uncappedtimeoutMs- the accept window in ms that elapsed before this SYN expired- Returns:
- the complete single-line WARN message