- All Implemented Interfaces:
Runnable, EventDispatcher
- Direct Known Subclasses:
I2Ping, I2PSOCKSTunnel, I2PTunnelClient, I2PTunnelDCCClient, I2PTunnelHTTPClientBase, I2PTunnelIRCClient
Manages I2PSocketManager creation, local server socket setup, connection threading, and framework for shared and non-shared client tunnels. Handles SSL configuration, connection timeouts, and provides abstract methods for connection-specific implementations.
Extended by specific tunnel types like HTTP clients, SOCKS proxies, and standard TCP tunnels.
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate classBlocking runner, used during the connection establishment.private static enumThe state of the shared socket manager.Field Summary
Modifier and TypeFieldDescriptionprivate static final AtomicLongClient ID counter.private final AtomicIntegerLive reservation counter shared by the connections of this tunnel.private booleanWhether we are building tunnels.protected longThis client's ID.protected final I2PAppContextThe application context.protected ThreadPoolExecutorThread pool executor.private final StringHandler name.protected final LogThe logger.private intResolved concurrent connection cap for this tunnel.private booleanTrue when this tunnel's config explicitly set "i2ptunnel.maxConnections".private longTime the tunnel was opened, for timeout checks in close().protected booleanWhether we own our destination.private booleanTrue if we created _executor ourselves (TCG was null) and must shut it down on close.private final longSocket open timeout in ms; close() will proceed after this even with active sockets.private ThreadTunnel builder thread.private booleanTrue if we are chained from a server.(package private) static final longDefault connect timeout.static final intDefault cap on concurrently handled client connections.static final longDefault socket open timeout in ms, after whichclose(boolean)will proceed even with active sockets.protected DestinationThe destination.protected final LoggingThe logging instance.protected booleanProtected for I2Ping since 0.9.11.private intLocal port.private static final intMost tunnel rebuild attempts before giving up.(package private) static final intLegacy cap on consecutive connect timeouts before abandoning tunnel failover.(package private) static final longMinimum remaining budget for starting one connect leg.List of active sockets.(package private) static final longDeadline sentinel for callers that have not adopted the shared request budget (SOCKS, IRC, DCC, rawcreateI2PSocket): they keep their original per-call limits because this sentinel never reads as expired and never clamps.(package private) static final longOne-shot deadline extension granted while the outbound tunnel pool is still building (poolState() == 0) when the shared budget runs out.private StringPrivate key file path.static final StringProperty name: max concurrently active client connections handled by one client tunnel.static final StringProperty turning on SSL between the proxy and the client.(package private) static final longSingle wall-clock budget for establishing one proxied request.private static final intPause before retrying a failed tunnel: 15 seconds.private static I2PSocketManagerThis is ONLY for shared clients.private static I2PTunnelClientBase.SocketManagerStateWhether this router's shared socket manager has connected yet.protected final ObjectGuards sockMgr and mySockets.protected I2PSocketManagerThe socket manager.protected ServerSocketServer socket.private final ObjectStart lock.private booleanWhether start is running.Constructor Summary
ConstructorsConstructorDescriptionI2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName, I2PTunnel tunnel) The main constructor.I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName, I2PTunnel tunnel, String pkf) Use this to build a client with a persistent private key.I2PTunnelClientBase(int localPort, Logging l, I2PSocketManager sktMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) This constructor is used to add a client to an existing socket manager.Method Summary
Modifier and TypeMethodDescriptionprotected I2PSocketManagerFor NON-SHARED clients (ownDest = true).protected static I2PSocketManagerbuildSocketManager(I2PTunnel tunnel) Create a disconnected socket manager for the tunnel, with no private key file.protected static I2PSocketManagerbuildSocketManager(I2PTunnel tunnel, String pkf) Create a disconnected socket manager for the tunnel, loading the persistent client dest from pkf if it is non-null.protected static I2PSocketManagerbuildSocketManager(I2PTunnel tunnel, String pkf, Logging log) Create a disconnected socket manager for the tunnel, loading the persistent client dest from pkf if it is non-null, and logging any key file error to log.(package private) static longclampToDeadlineMs(long requestedMs, long deadlineMs, long nowMs) Clamp a requested timeout to the time left before a shared deadline.protected abstract voidManage a connection in a separate thread.booleanclose(boolean forced) Note that the tunnel can be reopened after this by calling startRunning().static voidClose a socket silently.(package private) static longconnectDeadlineFrom(long nowMs) Compute the shared connect deadline for a request starting now.private voidWarning, blocks while connecting to router and building tunnels; This may take a LONG time.createI2PSocket(Destination dest) Create a new I2PSocket towards to the specified destination, adding it to the list of connections actually managed by this tunnel.createI2PSocket(Destination dest, int port) Create a new I2PSocket towards to the specified destination, adding it to the list of connections actually managed by this tunnel.createI2PSocket(Destination dest, I2PSocketOptions opt) Create a new I2PSocket towards to the specified destination, adding it to the list of connections actually managed by this tunnel.(package private) I2PSocketcreateI2PSocket(Destination dest, I2PSocketOptions opt, long deadlineMs) Create a new I2PSocket bounded by a shared request deadline.private I2PSocketcreateI2PSocketWithFailover(Destination dest, I2PSocketOptions opt, long deadlineMs) Create a new I2PSocket with retry/failover across the session's tunnel pool.private ServerSocketcreateServerSocket(Properties opts, InetAddress addr) Create the local server socket, with SSL if enabled.booleandestroy()Note that the tunnel cannot be reopened after this by calling startRunning(), as it will destroy the underlying socket manager.(package private) static longextendDeadlineForBuildingPool(long deadlineMs, long nowMs, int poolState, boolean alreadyExtended) One-shot deadline extension for a request whose outbound tunnel pool is still building when the shared deadline runs out.protected I2PSocketOptionsCreate the default options (using the default timeout, etc).protected I2PSocketOptionsgetDefaultOptions(Properties overrides) Create the default options (using the default timeout, etc).(package private) intThe effective concurrent-connection cap for this tunnel at the moment the accept loop admits a connection.protected final InetAddressGet the listen host address.final intGet the local port.Returns the I2PSocketManager for this task.protected static I2PSocketManagergetSocketManager(I2PTunnel tunnel, String pkf) This is ONLY for shared clients.longReturns the socket open timeout in ms; close() will proceed after this even with active sockets.private voidGet this tunnel's private runner pool from the tunnel controller group (or create one locally if the group was never started).(package private) static booleanFor timeout wording.(package private) static booleanisDeadlineExpired(long deadlineMs, long nowMs) Whether a shared connect deadline has passed.(package private) static longlegConnectTimeoutMs(long requestedMs, long deadlineMs, long nowMs) Compute the connect timeout to apply to one failover leg under a shared deadline.protected voidManage the connection just opened on the specified socket.voidoptionsUpdated(I2PTunnel tunnel) Update the I2PSocketManager.protected booleanAnd none are being built, so further connect retries cannot succeed.protected intRouter-context only, best-effort check of the client outbound tunnel pool.(package private) static intresolveEffectiveMaxConnections(boolean customized, int ownCap, int globalDefault) Pure decision helper forgetEffectiveMaxConnections(): pick the per-tunnel override when one was explicitly configured, otherwise the Tuner-managed default cap.(package private) static intresolveMaxConnections(String configured) Resolve the max-concurrent-connections cap from the tunnel's configured value.(package private) static intresolveRunnerCeiling(boolean customized, int effectiveMax) Ceiling to pin on this tunnel's runner pool at creation time.voidrun()Non-final since 0.9.11.(package private) static booleanshouldContinueFailover(int tunnelCount, int attemptsMade, int timeoutFailures, boolean poolDown) Whether another tunnel-failover attempt is worthwhile after a connect failure.(package private) static booleanshouldContinueFailover(int tunnelCount, int attemptsMade, int timeoutFailures, boolean poolDown, boolean poolBuilding) Whether another tunnel-failover attempt is worthwhile after a connect failure.(package private) static booleanshouldOuterRetryConnect(int connectAttempts, int timeoutConnectAttempts, boolean timedOut, boolean poolDown, boolean poolBuilding, boolean browserClosed) Whether the outer HTTP connect loop should retry after a failure.(package private) static booleanshouldOuterRetryConnect(int connectAttempts, int timeoutConnectAttempts, boolean timedOut, int poolState, boolean deadlineExpired) Whether the outer HTTP connect loop should retry after a failure, given the single pool-state snapshot taken for that failure.voidActually open the local socket and start working on incoming connections.private voidstartup()Opens the tunnel and starts the socket manager.protected voidCreate the manager if it doesn't exist, AND connect it to the router and build tunnels.private voidWait until startRunning() authorizes connection processing.protected voidBest-effort shed response on the browser-facing socket, then close it.Methods inherited from class I2PTunnelTask
connected, disconnected, errorOccurred, getBooleanOption, getId, getTunnel, isOpen, reportAbuse, routerDisconnected, setId, setName, setTunnel, toStringModifier and TypeMethodDescriptionvoidconnected(I2PSession session) For tasks that don't callI2PTunnel.addSession(I2PSession)directly.voiddisconnected(I2PSession session) Called when a session is disconnected.voiderrorOccurred(I2PSession session, String message, Throwable error) Does nothing here.protected booleangetBooleanOption(String opt, boolean dflt) Read a boolean client option from the tunnel's properties.intgetId()The id of the client list entry for this task.The I2PTunnel this task runs inside.booleanisOpen()Whether the tunnel is running and accepting connections.voidreportAbuse(I2PSession session, int severity) Does nothing here.protected voidTell the tunnel that the router connection has dropped.voidsetId(int id) Record the client list entry id for this task.protected voidRecord the display name for this task.voidRebind this task to a different tunnel.toString()The display name given to this task when it was constructed.Methods inherited from class EventDispatcherImpl
attachEventDispatcher, detachEventDispatcher, getEventDispatcher, getEvents, getEventValue, ignoreEvents, notifyEvent, unIgnoreEvents, waitEventValueModifier and TypeMethodDescriptionvoidAttach an EventDispatcher object to the events dispatching chain.voidDetach the specified EventDispatcher object from the events dispatching chain.Object to be used to deliver events (usuallythis, but YMMV).Retrieve the names of all the events that have been received.getEventValue(String name) Retrieve the value currently associated with the specified event value.voidIgnore further event notifications.voidnotifyEvent(String eventName, Object args) Deliver an event.voidAlmost like the method above :-).waitEventValue(String name) Wait until the given event has received a value.
Field Details
_log
The logger._context
The application context.l
The logging instance.DEFAULT_CONNECT_TIMEOUT
MAX_TIMEOUT_FAILOVER
static final int MAX_TIMEOUT_FAILOVERLegacy cap on consecutive connect timeouts before abandoning tunnel failover. The leg walk no longer stops on a timeout count — every configured leg is tried while the pool has capacity — so the walk is now bounded byREQUEST_CONNECT_DEADLINE_MSinstead. Retained as a documented legacy bound.- See Also:
REQUEST_CONNECT_DEADLINE_MS
static final long REQUEST_CONNECT_DEADLINE_MSSingle wall-clock budget for establishing one proxied request. Naming lookups, every tunnel-failover leg, retry sleeps, and outer connect re-walks all draw from one deadline computed from this budget instead of each keeping an independent limit, so a quantity-4 leg walk plus outer retries can no longer stack well past two minutes before the request gives up.- See Also:
NO_DEADLINE
static final long NO_DEADLINEDeadline sentinel for callers that have not adopted the shared request budget (SOCKS, IRC, DCC, rawcreateI2PSocket): they keep their original per-call limits because this sentinel never reads as expired and never clamps.- See Also:
MIN_CONNECT_LEG_MS
static final long MIN_CONNECT_LEG_MSMinimum remaining budget for starting one connect leg. Streaming floors connect timeouts at 10s (CONNECT_TIMEOUT_FLOOR_MS), so a leg with less than this left is guaranteed to be cut short — it can only burn the shared deadline on a doomed attempt. The walk skips such a leg instead.- See Also:
POOL_BUILD_DEADLINE_GRACE_MS
static final long POOL_BUILD_DEADLINE_GRACE_MSOne-shot deadline extension granted while the outbound tunnel pool is still building (poolState() == 0) when the shared budget runs out. No connect leg can succeed before the pool's first tunnel lands; a healthy first build completes inside this window, so expiring the request at the 120s mark would discard a request whose pool was seconds from ready. At most one extension per request, keeping the worst case atREQUEST_CONNECT_DEADLINE_MS+ this value.- See Also:
__clientId
Client ID counter._clientId
protected long _clientIdThis client's ID.sockLock
Guards sockMgr and mySockets.sockMgr
The socket manager.mySockets
_ownDest
protected boolean _ownDestWhether we own our destination.dest
The destination.localPort
private volatile int localPortLocal port._handlerName
Handler name.listenerReady
protected boolean listenerReadyProtected for I2Ping since 0.9.11. Not for use outside package.ss
Server socket.startLock
Start lock.startRunning
private boolean startRunningWhether start is running._buildingTunnels
private volatile boolean _buildingTunnelsWhether we are building tunnels._tunnelBuilder
Tunnel builder thread.privKeyFile
Private key file path.chained
private boolean chainedTrue if we are chained from a server._executor
Thread pool executor._ownExecutor
private volatile boolean _ownExecutorTrue if we created _executor ourselves (TCG was null) and must shut it down on close.PROP_MAX_CONNECTIONS
Property name: max concurrently active client connections handled by one client tunnel.- See Also:
DEFAULT_SOCKET_OPEN_TIMEOUT
public static final long DEFAULT_SOCKET_OPEN_TIMEOUTDefault socket open timeout in ms, after whichclose(boolean)will proceed even with active sockets.- See Also:
DEFAULT_MAX_CONNECTIONS
public static final int DEFAULT_MAX_CONNECTIONSDefault cap on concurrently handled client connections.The accept/connect path runs on an unbounded
I2PTunnelClientBase.BlockingRunnerpool, so a flood of inbound peer connections (e.g. tracker announces/scrapes) can spawn unlimited parallel connect attempts - each with its own retry loop - starving legitimate browsing streams. A hard concurrent-process cap sheds excess inbound load instead of amplifying it.- See Also:
_maxConnections
private volatile int _maxConnectionsResolved concurrent connection cap for this tunnel. Guards #manageConnection._maxConnectionsCustomized
private volatile boolean _maxConnectionsCustomizedTrue when this tunnel's config explicitly set "i2ptunnel.maxConnections". When false the tunnel inherits the Tuner-managed default cap (seegetEffectiveMaxConnections()), so the Tuner can raise/lower the floor without a tunnel restart. An explicit override always wins over the Tuner default._activeConnections
Live reservation counter shared by the connections of this tunnel._socketOpenTimeout
private final long _socketOpenTimeoutSocket open timeout in ms; close() will proceed after this even with active sockets.- See Also:
_openStarted
private long _openStartedTime the tunnel was opened, for timeout checks in close().socketManager
This is ONLY for shared clients.socketManagerState
Whether this router's shared socket manager has connected yet.PROP_USE_SSL
Property turning on SSL between the proxy and the client.- See Also:
RETRY_DELAY
private static final int RETRY_DELAYPause before retrying a failed tunnel: 15 seconds.- See Also:
MAX_RETRIES
private static final int MAX_RETRIESMost tunnel rebuild attempts before giving up.- See Also:
Constructor Details
I2PTunnelClientBase
public I2PTunnelClientBase(int localPort, Logging l, I2PSocketManager sktMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) throws IllegalArgumentException This constructor is used to add a client to an existing socket manager.As of 0.9.21 this does NOT open the local socket. You MUST call
startRunningfor that. The local socket will be opened immediately (ignoring thei2cp.delayOpenoption).- Parameters:
localPort- if 0, use any port, get actual port selected withgetLocalPort()l- the logging instancesktMgr- the existing socket managertunnel- the I2PTunnel instancenotifyThis- the event dispatcher for notificationsclientId- the client identifier- Throws:
IllegalArgumentException
I2PTunnelClientBase
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName, I2PTunnel tunnel) throws IllegalArgumentException The main constructor.As of 0.9.21 this is fast, and does NOT connect the manager to the router, or open the local socket. You MUST call
startRunning()for that.(0.9.20 claimed to be fast, but due to a bug it DID connect the manager to the router. It did NOT open the local socket however, so it was still necessary to call startRunning() for that.).
- Parameters:
localPort- if 0, use any port, get actual port selected with getLocalPort()ownDest- whether to use an owned destinationl- the logging instancenotifyThis- the event dispatcher for notificationshandlerName- the handler nametunnel- the I2PTunnel instance- Throws:
IllegalArgumentException- if the I2CP configuration is b0rked so badly that we can't create a socketManager
I2PTunnelClientBase
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName, I2PTunnel tunnel, String pkf) throws IllegalArgumentException Use this to build a client with a persistent private key.As of 0.9.21 this is fast, and does NOT connect the manager to the router, or open the local socket. You MUST call startRunning() for that.
(0.9.20 claimed to be fast, but due to a bug it DID connect the manager to the router. It did NOT open the local socket however, so it was still necessary to call startRunning() for that.).
- Parameters:
localPort- if 0, use any port, get actual port selected with getLocalPort()ownDest- whether to use an owned destinationl- the logging instancenotifyThis- the event dispatcher for notificationshandlerName- the handler nametunnel- the I2PTunnel instancepkf- Path to the private key file, or null to generate a transient key- Throws:
IllegalArgumentException- if the I2CP configuration is b0rked so badly that we can't create a socketManager
Method Details
verifySocketManager
protected void verifySocketManager()Create the manager if it doesn't exist, AND connect it to the router and build tunnels.Sets the this.sockMgr field if it is null, or if we want a new one. This may take a LONG time if building a new manager.
We need a socket manager before
getDefaultOptions()and most other things.- Throws:
IllegalStateException- if the I2CP configuration is b0rked so badly that we cant create a socketManager
getSocketManager
Returns the I2PSocketManager for this task. For non-shared clients returns the instance socket manager. For shared clients returns the shared static socket manager.- Overrides:
getSocketManagerin classI2PTunnelTask- Returns:
- the socket manager
getSocketManager
This is ONLY for shared clients. As of 0.9.20 this is fast, and does NOT connect the manager to the router. CallverifySocketManager()for that.- Parameters:
tunnel- the I2PTunnel instancepkf- the private key file path, or null- Returns:
- non-null
- Throws:
IllegalArgumentException- if the I2CP configuration is b0rked so badly that we cant create a socketManager
buildSocketManager
For NON-SHARED clients (ownDest = true).As of 0.9.20 this is fast, and does NOT connect the manager to the router. Call verifySocketManager() for that.
- Returns:
- non-null
- Throws:
IllegalArgumentException- if the I2CP configuration is b0rked so badly that we cant create a socketManager
buildSocketManager
Create a disconnected socket manager for the tunnel, with no private key file.As of 0.9.20 this is fast, and does NOT connect the manager to the router. Call verifySocketManager() for that.
- Parameters:
tunnel- the I2PTunnel instance- Returns:
- non-null
- Throws:
IllegalArgumentException- if the I2CP configuration is b0rked so badly that we cant create a socketManager
buildSocketManager
Create a disconnected socket manager for the tunnel, loading the persistent client dest from pkf if it is non-null.As of 0.9.20 this is fast, and does NOT connect the manager to the router. Call verifySocketManager() for that.
- Parameters:
tunnel- the I2PTunnel instancepkf- absolute path or null- Returns:
- non-null
- Throws:
IllegalArgumentException- if the I2CP configuration is b0rked so badly that we cant create a socketManager
buildSocketManager
Create a disconnected socket manager for the tunnel, loading the persistent client dest from pkf if it is non-null, and logging any key file error to log.As of 0.9.20 this is fast, and does NOT connect the manager to the router. Call verifySocketManager() for that.
- Parameters:
tunnel- the I2PTunnel instancepkf- absolute path or nulllog- the logging instance- Returns:
- non-null
- Throws:
IllegalArgumentException- if the I2CP configuration is b0rked so badly that we cant create a socketManager
connectManager
private void connectManager()Warning, blocks while connecting to router and building tunnels; This may take a LONG time.- Throws:
IllegalArgumentException- if the I2CP configuration is b0rked so badly that we cant create a socketManager
getLocalPort
public final int getLocalPort()Get the local port.- Returns:
- the local port number
getListenHost
Get the listen host address.- Parameters:
l- the logging instance- Returns:
- the InetAddress for the listen host, or null on error
startRunning
public void startRunning()Actually open the local socket and start working on incoming connections. *Must* be called by derived classes after initialization.This will be fast if
i2cp.delayOpenis true, but could take a LONG TIME if it is false, as it connects to the router and builds tunnels.Extending classes must check the value of boolean open after calling super.startRunning(), if false then something went wrong.
startup
private void startup()Opens the tunnel and starts the socket manager.getDefaultOptions
Create the default options (using the default timeout, etc). Warning, this does not make a copy of I2PTunnel's client options, it modifies them directly.- Returns:
- the default socket options
getDefaultOptions
Create the default options (using the default timeout, etc). Warning, this does not make a copy of I2PTunnel's client options, it modifies them directly. Do not use overrides for per-socket options.- Parameters:
overrides- the properties to override- Returns:
- the default socket options with overrides
optionsUpdated
Update the I2PSocketManager.- Overrides:
optionsUpdatedin classI2PTunnelTask- Parameters:
tunnel- the tunnel whose client options were updated
createI2PSocket
public I2PSocket createI2PSocket(Destination dest) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException Create a new I2PSocket towards to the specified destination, adding it to the list of connections actually managed by this tunnel.- Parameters:
dest- The destination to connect to, non-null- Returns:
- a new I2PSocket
- Throws:
I2PException- if there is an I2P-related problemConnectException- if the peer refuses the connectionNoRouteToHostException- if the peer is not found or not reachableInterruptedIOException- if the connection times out
createI2PSocket
public I2PSocket createI2PSocket(Destination dest, int port) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException Create a new I2PSocket towards to the specified destination, adding it to the list of connections actually managed by this tunnel.- Parameters:
dest- The destination to connect to, non-nullport- The destination port to connect to 0 - 65535- Returns:
- a new I2PSocket
- Throws:
I2PException- if there is an I2P-related problemConnectException- if the peer refuses the connectionNoRouteToHostException- if the peer is not found or not reachableInterruptedIOException- if the connection times out
createI2PSocket
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException Create a new I2PSocket towards to the specified destination, adding it to the list of connections actually managed by this tunnel.With multiple inbound or outbound tunnels configured (
inbound.quantity > 1oroutbound.quantity > 1), the session's tunnel pool has multiple tunnels. If a connect attempt fails withNoRouteToHostException, the method retries, allowing the session's tunnel pool to select a different tunnel. This provides robust delivery and automatic tunnel failover.- Parameters:
dest- The destination to connect to, non-nullopt- Option to be used to open when opening the socket- Returns:
- a new I2PSocket
- Throws:
ConnectException- if the peer refuses the connectionNoRouteToHostException- if the peer is not found or not reachableInterruptedIOException- if the connection times outI2PException- if there is some other I2P-related problem
createI2PSocket
I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt, long deadlineMs) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException Create a new I2PSocket bounded by a shared request deadline.The HTTP proxy threads one request-scoped deadline through naming, the failover walk, retry sleeps, and outer retries (
REQUEST_CONNECT_DEADLINE_MS). The walk starts no leg once the deadline has passed and shortens each leg's connect timeout to the remaining budget, so the whole operation cannot exceed the deadline by a full connect timeout. Because a deadline may shortenoptin place, a caller passing one must not share the options object with other requests.- Parameters:
dest- The destination to connect to, non-nullopt- Option to be used to open the socketdeadlineMs- absolute wall-clock deadline in ms since the epoch, orNO_DEADLINEfor no shared budget- Returns:
- a new I2PSocket
- Throws:
I2PException- if there is some other I2P-related problemConnectException- if the peer refuses the connectionNoRouteToHostException- if the peer is not found or not reachable after retriesInterruptedIOException- if the connection times out
createI2PSocketWithFailover
private I2PSocket createI2PSocketWithFailover(Destination dest, I2PSocketOptions opt, long deadlineMs) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException Create a new I2PSocket with retry/failover across the session's tunnel pool.With multiple inbound or outbound tunnels configured, the session's tunnel pools have multiple tunnels available. If a connection attempt fails with
NoRouteToHostException, the method retries, allowing the session's tunnel pool to select a different tunnel. The walk is additionally bounded bydeadlineMs: no new leg is started after the deadline, each leg's connect timeout is clamped to the remaining budget (a default is substituted when the caller requested none, and legs with less thanMIN_CONNECT_LEG_MSleft are skipped rather than doomed), and one pool-state snapshot is taken per failure to decide whether another leg is worthwhile.- Parameters:
dest- The destination to connect to, non-nullopt- the socket options, passed to the connection manager for the attemptdeadlineMs- absolute wall-clock deadline in ms since the epoch, orNO_DEADLINEfor no shared budget- Returns:
- a new I2PSocket
- Throws:
I2PException- if there is some other I2P-related problemConnectException- if the peer refuses the connectionNoRouteToHostException- if the peer is not found or not reachable after retriesInterruptedIOException- if the connection times out
shouldContinueFailover
static boolean shouldContinueFailover(int tunnelCount, int attemptsMade, int timeoutFailures, boolean poolDown) Whether another tunnel-failover attempt is worthwhile after a connect failure.Stops immediately when the outbound pool is provably dead (no valid tunnels, none building). Further legs cannot succeed and each leg may burn the full connect timeout. When a timeout occurred, walks all configured legs (up to
tunnelCount) so a pool with several dead tunnels does not give up afterMAX_TIMEOUT_FAILOVERlegs while healthy legs remain untried. Non-timeout failures (NoRoute, refused) also walk all legs.- Parameters:
tunnelCount- configured max failover legs (max inbound/outbound quantity)attemptsMade- legs already attempted (1-based)timeoutFailures- consecutive timeout-style failures so farpoolDown- true ifpoolIsDefinitivelyDown()was true after the failure- Returns:
- true to try the next tunnel leg
shouldContinueFailover
static boolean shouldContinueFailover(int tunnelCount, int attemptsMade, int timeoutFailures, boolean poolDown, boolean poolBuilding) Whether another tunnel-failover attempt is worthwhile after a connect failure.Stops immediately when the outbound pool is provably dead. Otherwise walks all configured legs (
attemptsMade < tunnelCount) regardless of how many were timeouts — giving up afterMAX_TIMEOUT_FAILOVERwhile healthy legs remain wastes the failover budget and surfaces "giving up" at tunnel 1/4.poolBuildingno longer extends the timeout cap; it is retained for call-site compatibility and future tuning.- Parameters:
tunnelCount- configured max failover legs (max inbound/outbound quantity)attemptsMade- legs already attempted (1-based)timeoutFailures- consecutive timeout-style failures so farpoolDown- true ifpoolIsDefinitivelyDown()was true after the failurepoolBuilding- true whenpoolState()reports builds in flight- Returns:
- true to try the next tunnel leg
shouldOuterRetryConnect
static boolean shouldOuterRetryConnect(int connectAttempts, int timeoutConnectAttempts, boolean timedOut, boolean poolDown, boolean poolBuilding, boolean browserClosed) Whether the outer HTTP connect loop should retry after a failure. createI2PSocketWithFailover already walked every tunnel leg; this decides whether to re-enter that walk.Policy: always allow at least one timeout retry so a pool that was mid-build when the first walk started can finish its replacements. While
poolBuilding, allow a second timeout wait (the in-flight builds may still complete). Stop immediately when the pool is provably dead, or when the general connect budget is exhausted.- Parameters:
connectAttempts- total connect attempts so far (1-based after failure)timeoutConnectAttempts- timeout-style attempts so fartimedOut- true when the failure classified as a connect timeoutpoolDown- true ifpoolIsDefinitivelyDown()was truepoolBuilding- true whenpoolState()reports builds in flightbrowserClosed- true if the browser already hung up (abort now)- Returns:
- true to sleep and retry the outer connect loop
shouldOuterRetryConnect
static boolean shouldOuterRetryConnect(int connectAttempts, int timeoutConnectAttempts, boolean timedOut, int poolState, boolean deadlineExpired) Whether the outer HTTP connect loop should retry after a failure, given the single pool-state snapshot taken for that failure.Both derived flags (
poolDown,poolBuilding) come from onepoolState()reading so a failure never mixes two pool observations — the reflective pool query is relatively expensive and one failure must not repeat it. Stops immediately when the shared request deadline has passed, regardless of remaining attempt budget.- Parameters:
connectAttempts- total connect attempts so far (1-based after failure)timeoutConnectAttempts- timeout-style attempts so fartimedOut- true when the failure classified as a connect timeoutpoolState- the singlepoolState()reading for this failuredeadlineExpired- true when the shared request deadline has passed- Returns:
- true to sleep and retry the outer connect loop
connectDeadlineFrom
static long connectDeadlineFrom(long nowMs) Compute the shared connect deadline for a request starting now.- Parameters:
nowMs- current time in ms since the epoch- Returns:
- the absolute deadline (
nowMs + REQUEST_CONNECT_DEADLINE_MS)
isDeadlineExpired
static boolean isDeadlineExpired(long deadlineMs, long nowMs) Whether a shared connect deadline has passed. Pure decision — no clock access, safe for unit tests. WithNO_DEADLINEthis is always false, so callers that have not adopted the shared budget keep their original behavior.- Parameters:
deadlineMs- absolute deadline in ms since the epochnowMs- current time in ms since the epoch- Returns:
- true when
nowMshas reached or passed the deadline
clampToDeadlineMs
static long clampToDeadlineMs(long requestedMs, long deadlineMs, long nowMs) Clamp a requested timeout to the time left before a shared deadline. Used for naming lookups, retry sleeps, and per-leg connect timeouts so no single step can spend budget the rest of the request still needs. Pure decision. No clock access, safe for unit tests.- Parameters:
requestedMs- the timeout the step would use with no deadlinedeadlineMs- absolute deadline in ms since the epoch, orNO_DEADLINEnowMs- current time in ms since the epoch- Returns:
- the requested timeout reduced to the remaining budget; 0 when the deadline has passed (callers must skip the step)
legConnectTimeoutMs
static long legConnectTimeoutMs(long requestedMs, long deadlineMs, long nowMs) Compute the connect timeout to apply to one failover leg under a shared deadline. Pure decision. No clock access, safe for unit tests.Three behaviors the raw clamp lacked: a caller that requested no timeout at all (
requestedMs <= 0, which streaming can report when its effective connect window is not yet established) getsDEFAULT_CONNECT_TIMEOUTinstead of waiting forever against a wall-clock budget; the result is clamped to the remaining budget; and a budget smaller thanMIN_CONNECT_LEG_MSreturns-1so the walk skips the leg rather than starting an attempt streaming will cut short anyway. WithNO_DEADLINEthe requested value is passed through untouched, preserving legacy behavior for callers that have not adopted the shared budget.- Parameters:
requestedMs- the leg timeout the options carry;<= 0means no timeout requesteddeadlineMs- absolute deadline in ms since the epoch, orNO_DEADLINEnowMs- current time in ms since the epoch- Returns:
- the timeout to apply to the options, or
-1to skip this leg
extendDeadlineForBuildingPool
static long extendDeadlineForBuildingPool(long deadlineMs, long nowMs, int poolState, boolean alreadyExtended) One-shot deadline extension for a request whose outbound tunnel pool is still building when the shared deadline runs out. Pure decision. No clock access, safe for unit tests.Pool state 0 means the pool exists with no valid tunnel yet but builds are in progress: no connect leg can succeed until one lands, and a healthy first build finishes well inside the grace window. Without this, a request racing a cold pool died at the full budget a few seconds before its pool came up — the observed SYN-timeout failure mode. The extension is granted at most once per request so the worst case stays bounded by
REQUEST_CONNECT_DEADLINE_MS+POOL_BUILD_DEADLINE_GRACE_MS, and only when the deadline has actually expired (an expiring request with a healthy pool still fails on schedule).- Parameters:
deadlineMs- current deadline in ms since the epochnowMs- current time in ms since the epochpoolState- latestpoolState()reading (0 = still building)alreadyExtended- true if this request already consumed its grace- Returns:
- the extended deadline, or
deadlineMsunchanged when no extension applies
isConnectTimeout
For timeout wording.- Parameters:
e- the throwable whose cause chain, up to eight links deep, is scanned- Returns:
- true if the cause chain looks like a connect/read timeout
poolIsDefinitivelyDown
protected boolean poolIsDefinitivelyDown()And none are being built, so further connect retries cannot succeed.- Returns:
- true if the client outbound tunnel pool provably has no tunnels
poolState
protected int poolState()Router-context only, best-effort check of the client outbound tunnel pool. Uses reflection so i2ptunnel compiles against core alone. The router creates a per-client pool keyed by session hash; getValidTunnelCount() counts non-failed, non-expired tunnels, getInProgressCount() counts builds in progress.- Returns:
- 1 if the pool has valid tunnels, 0 if it exists but is still building, -1 if it exists but is dead (no valid, none building), -2 if unknown (standalone client, no router pool)
run
createServerSocket
Create the local server socket, with SSL if enabled.- Parameters:
opts- the client optionsaddr- the local bind address- Returns:
- the bound server socket
- Throws:
IOException- if the socket cannot be created
waitForStartRunning
private void waitForStartRunning()Wait until startRunning() authorizes connection processing.initializeExecutor
private void initializeExecutor()Get this tunnel's private runner pool from the tunnel controller group (or create one locally if the group was never started). The pool is a share of the global clientRunnerMax budget so another dest's flood cannot consume every runner thread.resolveMaxConnections
Resolve the max-concurrent-connections cap from the tunnel's configured value.Pure decision - no context access, safe for unit tests. Returns the configured value when it is a positive integer, otherwise the built-in default. A configured value of 0 or negative is rejected so a typo cannot disable the flood-shedding cap.
- Parameters:
configured- the raw integer string fromPROP_MAX_CONNECTIONS, may be null- Returns:
- the connection cap to use: the parsed value if positive, else
DEFAULT_MAX_CONNECTIONS
getEffectiveMaxConnections
int getEffectiveMaxConnections()The effective concurrent-connection cap for this tunnel at the moment the accept loop admits a connection. Reads the Tuner-managed default dynamically so the floor can move without a tunnel restart, unless this tunnel declared an explicit "i2ptunnel.maxConnections" override (which always wins).The Tuner raises this gate under sustained load so excess inbound connections are handed to executor threads instead of being shed (a shed close with zero bytes is what an HTTP proxy browser reports as an empty response), and lowers it back toward the floor when idle to bound memory/FD usage.
- Returns:
- the effective cap: >= 1 when gated, 0 meaning unlimited
resolveEffectiveMaxConnections
static int resolveEffectiveMaxConnections(boolean customized, int ownCap, int globalDefault) Pure decision helper forgetEffectiveMaxConnections(): pick the per-tunnel override when one was explicitly configured, otherwise the Tuner-managed default cap. Package-visible and context-free so unit tests can exercise it without a live tunnel.- Parameters:
customized- whether this tunnel declared an explicit "i2ptunnel.maxConnections" overrideownCap- the tunnel's resolved cap (resolveMaxConnections(String)result)globalDefault- the Tuner-managed default cap- Returns:
- the effective cap: >= 1 when gated, 0 meaning unlimited
resolveRunnerCeiling
static int resolveRunnerCeiling(boolean customized, int effectiveMax) Ceiling to pin on this tunnel's runner pool at creation time.Customized tunnels keep their explicit cap; un-customized tunnels pass 0 so
TunnelControllerGroupresolves the live Tuner default on each rebalance rather than freezing the start-time value (ceiling staleness).- Parameters:
customized- true when the tunnel set its own maxConnectionseffectiveMax- the tunnel's resolved effective maxConnections- Returns:
- the explicit ceiling, or 0 to track the live default
manageConnection
Manage the connection just opened on the specified socket.- Parameters:
s- Socket to take care of
writeShedResponse
Best-effort shed response on the browser-facing socket, then close it.The default is a silent close (non-HTTP tunnels). HTTP subclasses override this to emit a real status line. A cookie-capped meta-refresh page or a 503 — so browsers never see an empty response for a shed connection.
- Parameters:
s- the accepted socket being shed; never null
close
public boolean close(boolean forced) Note that the tunnel can be reopened after this by calling startRunning(). This may not release all resources. In particular, the I2PSocketManager remains and it may have timer threads that continue running.To release all resources permanently, call
destroy().Does nothing if open is already false. Sets open = false but does not notifyAll().
- Specified by:
closein classI2PTunnelTask- Parameters:
forced- true to interrupt an in-progress build, false to close gracefully- Returns:
- true if the tunnel is closed, or false if connections were still active and the close was refused
getSocketOpenTimeout
public long getSocketOpenTimeout()Returns the socket open timeout in ms; close() will proceed after this even with active sockets.- Returns:
- the socket open timeout in milliseconds
destroy
public boolean destroy()Note that the tunnel cannot be reopened after this by calling startRunning(), as it will destroy the underlying socket manager. This releases all resources if not a shared client. For shared client, the router will kill all the remaining streaming timers at shutdown.- Overrides:
destroyin classI2PTunnelTask- Returns:
- true if every resource was released
closeSocket
clientConnectionRun
Manage a connection in a separate thread. This only works if you do not overridemanageConnection(Socket).This is run in a thread from an unlimited-size thread pool, so it may block or run indefinitely.
- Parameters:
s- the socket to manage