- All Implemented Interfaces:
Runnable, EventDispatcher
- Direct Known Subclasses:
I2PTunnelHTTPBidirServer
Extends I2PTunnelServer to filter HTTP headers from client to server, replacing the Host header with configured value. Compresses HTTP message body and sets Content-Encoding: x-i2p-gzip when browser requests Accept-Encoding: x-i2p-gzip.
Features include POST/PUT rate limiting, referer and user-agent rejection, inproxy detection, HTTP blocklist support, keepalive management, and response header security filtering.
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static classThrown when HTTP headers are malformed.private static final classWatchdog for an async (detached) body handoff.private static classCopies a gzipped request through to the web server.private static classGzips the response on its way to the browser.private static classThrown when a header line exceeds MAX_LINE_LENGTH, when there are too many headers, or when total header size exceeds MAX_TOTAL_HEADER_SIZE.private static classThrown when the request line exceeds MAX_LINE_LENGTH.(package private) static classPumps a client request body through to the I2P side and back.Field Summary
Modifier and TypeFieldDescriptionprivate BlocklistManagerHTTP blocklist manager.private static final AtomicLongBodies aborted because the I/O pool was saturated.private static final ExecutorServiceDedicated executor for non-blocking DNS lookups with bounded timeout.private longLive first-request header timeout (ms); updated by optionsUpdated.private static final ObjectLock for creating the shared IO executor.private static final ConcurrentHashMap<Object, ThreadPoolExecutor> Per-tunnel I/O pools for Server→Client data transfer, keyed by this server instance so a saturated dest cannot consume every IO thread.private booleanWhether HTTP persistent connections are accepted; updated by optionsUpdated.private longLive idle keepalive wait (ms); updated by optionsUpdated.Last time each client was logged for a request error, for rate limiting.private longWhen the last overload shed was logged.private ConnThrottlerPOST/PUT throttler for rate limiting.private StringThe hostname to inject as the Host header, or null for passthrough.(package private) static final AtomicLongMonotonically increasing counter of stall events detected by runOnIO or the Sender.private longTimestamp when the tunnel started, used for error-level escalation timing.private static final StringAge header, stripped for anonymity.private static final StringAlt-Svc header pointing elsewhere, stripped.private static final longProgress thresholds forcomputeStallWindowMs(long, long).private static final longBody size where the bulk-transfer stall is at full delay.private static final longBody size where the bulk-transfer stall begins.Headers stripped from the client's request.(package private) static final intConnection: close or unknown value.(package private) static final intConnection: keep-alive.(package private) static final intConnection header absent.(package private) static final intConnection: upgrade (websocket).private static final StringDate header, replaced by the proxy.static final longDefault first-request header timeout (ms).private static final longDefault read timeout for the server socket, -1 for none.private static final booleanDefault keepalive setting for HTTP persistent connections.static final longDefault idle keepalive wait (ms); short enough to free the handler thread, long enough for a browser to reuse the connection.static final intPost throttle window (seconds).static final intDefault max posts per window.static final intDefault total ban time for excessive posts.static final intDefault max total posts before ban.static final intDefault post rate-limit window (seconds).private static final StringHeader carrying the server's destination in base32.private static final StringHeader carrying the server's destination in base64.private static final intMax wait for DNS resolution of non-I2P hostnames in the HTTP proxy.private static final String400 Bad Request error response.private static final String429 Too Many Requests error response.(package private) static final String403 Forbidden error response.private static final String431 Request header fields too large error response.(package private) static final String404 Not Found error response.protected static final StringHTTP 408 Request Timeout error response.private static final String414 Request URI too long error response.private static final String503 Service Unavailable error response.private static final StringExpires header, stripped as it conflicts with cache-control.private static final StringClient request headers to remove.private static final longGrace period for remaining header lines after the first line, on top of the initial header timeout (slowloris bound).private static final intMax clients tracked by the HTTP blocklist.(package private) static final intMaximum global I/O budget (Tuner max must match).(package private) static final intAbsolute floor for the global I/O budget.private static final long[]Sample periods for the I/O transfer load stat.(package private) static longCurrent stall window, adjustable by the Tuner between the floor and max.(package private) static intDefault I/O transfer threads (global budget); Tuner adjusts viaTunnelControllerGroup.static final longCeiling forPROP_HEADER_TIMEOUT(ms).private static final intRidiculously long, just to prevent OOM DOS.static final longCeiling forPROP_KEEPALIVE_TIMEOUT(ms).private static final intLongest request header line accepted.(package private) static final longHard ceiling on a progress-scaled window, ms.private static final intIncludes request, just to prevent OOM DOS.(package private) static final intCap on distinct clients tracked for warning rate limiting, so a flood from many distinct peers cannot grow this map without bound.static final longBounds forPROP_HEADER_TIMEOUT(ms).static final longBounds forPROP_KEEPALIVE_TIMEOUT(ms).private static final intMinimum response size in bytes before gzip compression is applied.private static final PatternPattern splitting a request line at CRLF.static final StringConfig key to add Allow response header.static final StringConfig key to add Cache-Control response header.static final StringConfig key to add X-Content-Type-Options: nosniff.static final StringConfig key to add Referrer-Policy response header.static final StringConfig key to enable keepalive (legacy short form; see PROP_KEEPALIVE).static final StringConfig key for post ban time.static final StringConfig key for max posts.static final StringConfig key for post total ban time.static final StringConfig key for max total posts.static final StringConfig key for post check time window.static final StringConfig key to reject requests from inproxy.static final StringConfig key to reject requests with Referer header.static final StringConfig key to reject specific User-Agents.static final StringConfig key for User-Agent reject list.private static final StringObsolete Pragma header, stripped.private static final StringHeader carrying the browser's request priority.static final StringConfig key for the first-request header read timeout, in ms.static final StringConfig key to enable HTTP persistent connections; live via optionsUpdated.static final StringConfig key for the idle keepalive wait before closing a persistent connection, in ms.private static final StringThe Proxy-Connection header.private static final StringProxy header, checked against the httpoxy attack.private static final StringReferer header, stripped as it does not belong in responses.(package private) static final longMinimum interval between per-client "Request error" warnings.private static final StringHeader carrying the Global Privacy Control signal.private static final StringServer header naming the web server, stripped.private static final intRead timeout while reading a GET request, relatively short; POST and CONNECT get a long failsafe timeout since the user may be uploading a massive file.private static final intRead timeout for medium uploads.private static final intRead timeout while reading a POST body.Headers stripped from the server's response.private static final longMinimum interval between shed warnings for this listener.private static final PatternPattern splitting a path at slashes.private static final PatternPattern splitting a request line at spaces.(package private) static final StringStall side reported when the destination moved nothing.(package private) static final StringStall side reported when both stamps are equally stale.private static final longBelow this many bytes a body has moved too little for a "moved nothing, and only a read is blocked" claim, so equally stale stamps are reported as destination anyway.(package private) static final StringStall side reported when the source moved nothing and only a read blocked.(package private) static final longFloor for the configured stall window, ms.private static final longMin time before socket error is escalated to ERROR level.private static final StringHSTS header, useless through the proxy, stripped.private static final StringVia header listing proxies, stripped for anonymity.private static final StringX-Cache header revealing cache state, stripped.private static final StringX-Cache-Hits header revealing cache state, stripped.private static final StringGoogle Cloud trace header, stripped.private static final StringX-Contextid header, stripped.private static final StringGoogle Cloud Storage generation header, stripped.private static final StringGoogle Cloud Storage hash header, stripped.private static final StringGoogle upload id header, stripped.private static final StringWordpress X-Hacker header, stripped.private static final StringWordpress Batcache header, stripped.private static final StringPantheon hosting host name, stripped for anonymity.private static final StringX-Powered-By header naming the framework, stripped.private static final StringHeader a proxy uses to name the client address.private static final StringRails runtime header, stripped.private static final StringHost that served the response, stripped for anonymity.private static final StringRequest id on Pantheon responses, stripped.Fields inherited from class I2PTunnelServer
_clientExecutor, _log, _tcg, bidir, DEFAULT_HANDLE_MS, i2pss, l, localPort, MAX_QUEUE_DRAIN_MS, PROP_ALT_PKF, PROP_MAX_CONNECTIONS, PROP_READ_TIMEOUT, PROP_SERVER_THREADS, PROP_UNIQUE_LOCAL, PROP_USE_SSL, PROP_WRITE_TIMEOUT, readTimeout, remoteHost, remotePort, serverId, slock, sockMgr, sslLock, taskModifier and TypeFieldDescriptionprotected ThreadPoolExecutorPool running the per-connection client handlers, taken from the TunnelControllerGroup so server load stays inside its own thread budget.protected final LogLogger for this tunnel server.protected TunnelControllerGroupThe controller group that owns this tunnel's runner pool, cached fromTunnelControllerGroup.getInstance()once inI2PTunnelServer.run().protected booleanTrue for HTTP bidirectional server mode.(package private) static final longFallback average handler time (ms) when no recent blockingHandleTime sample is available for the drain estimate.protected I2PServerSocketThe listening server socket, null until the tunnel is started.protected final LoggingClient-visible log channel: every lifecycle notice and warning reported to the I2P client (bad option values, unresolvable hosts, lingering connections).protected intLocal port a HTTP bidir subclass hands to its proxy, replacing theI2PTunnelServer.DEFAULT_LOCAL_PORTplaceholder.(package private) static final longReject only when a fully busy pool would need longer than this to drain the queue.static final StringConfig key naming an alternate private key file.static final StringConfig key to cap concurrent inbound connections; 0 (default) = unlimited.static final StringConfig key to override the socket read timeout in ms; 0 (default) = shut down silent connections promptly (HTTP server falls back to SERVER_READ_TIMEOUT_GET).static final StringConfig key to cap this tunnel's private server handler pool in threads.static final StringConfig key to enable unique local address.static final StringConfig key to enable SSL.static final StringConfig key to override the outbound write timeout in ms (0 = use the streaming default, which can be the full 120s disconnect timeout with no ACK progress).protected longDefault timeout - override if desired.protected InetAddressRemote endpoint of the current connection, null for the listening server.protected intRemote port of the current connection, 0 for the listening server.protected static longServer ID for HTTP bidirectional proxies; not incremented, always 0.protected final ObjectPassed to each I2PTunnelRunner so connection teardown serializes on it.protected final I2PSocketManagerBuilds the outbound sockets used to reach each configured destination.protected final ObjectGuards lazy creation of _sslFactory.protected I2PTunnelTaskHTTP bidirectional server task, null for standard tunnel server.Constructor Summary
ConstructorsConstructorDescriptionI2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) Create an HTTP tunnel server from a private key file.I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) Create an HTTP tunnel server from a private key stream.I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) Create an HTTP tunnel server with a private key string.Method Summary
Modifier and TypeMethodDescription(package private) static voidAdd an entry to the multimap.private voidapplySpoofedHost(I2PSocket socket, Map<String, List<String>> headers, Properties opts) Apply port-specific spoofed host header.protected voidblockingHandle(I2PSocket socket) Called by the thread pool of I2PSocket handlers.(package private) static StringclassifyStalledSide(long readAgeMs, long writeAgeMs, long bytesTransferred) Which side of a stalled body transfer stopped making progress.booleanclose(boolean forced) Close the tunnel and stop the POST throttler.private static voidClose a stream, ignoring failures.(package private) static longcomputeStallWindowMs(long baseMs, long bytesTransferred) Stall window for a body that has already movedbytesTransferred.(package private) static ThreadPoolExecutorcreateIOExecutor(int threads) Create a fixed-size I/O transfer pool.private static voidevictOldestErrorClients(long now) Free space in the tracking map, guaranteeing it never exceedsMAX_TRACKED_ERROR_CLIENTS.(package private) static StringextractRequestUrl(String headers) Extract the request URL from the client's headers for logging.static longCount of bodies dropped because the I/O pool had no free worker.(package private) static intgetConnectionType(Map<String, List<String>> headers) Classify the request's Connection header value.private static StringGet the first matching entry in the multimap.longHow long the first request's headers may take to arrive.private intgetIntOption(String opt, int dflt) Get an integer option from the tunnel client options.(package private) ThreadPoolExecutorGet (creating on first use) this tunnel's private I/O pool for data transfer.static doubleThe busiest pool's utilization: its active plus queued transfers divided by its present maximum, as a rate that may exceed 1.0 when the queue is backing up past the pool's ceiling.static longGet the current I/O stall timeout in ms.static intGet the saturation pressure summed across all live I/O pools: running workers plus queued transfers.static intGet the current I/O transfer pool size.longHow long an idle keepalive connection is held before it is probed.static longGet the total number of stall events detected by Sender since router start.private StringgetTunnelLogId(Destination localDest) Short human-readable identifier for this HTTP server tunnel, e.g.private voidGrow this tunnel's runner pool if work is backing up, immediately before a submit.private booleanhandlePort443(I2PSocket socket) Handle port 443 (SSL passthrough).(package private) static booleanhandOffBody(ThreadPoolExecutor pool, I2PTunnelHTTPServer.Sender s, String desc) Hand off the response body to the I/O transfer pool, or run it inline when the pool is null or shut down.(package private) static booleanhandOffBody(ThreadPoolExecutor pool, I2PTunnelHTTPServer.Sender s, String desc, Runnable onCompletion) AshandOffBody(ThreadPoolExecutor, Sender, String)with a completion callback invoked after the Sender finishes (success, failure, or abort), on the pool worker when submitted and on the caller otherwise.(package private) static booleanhasGzipEncoding(String enc) Whether the Accept-Encoding value includes x-i2p-gzip.(package private) static intThe floor for one I/O pool, proportional to the budget and pool count.private voidDeregister this tunnel's I/O pool on stop so its budget share is freed.(package private) static intioQueueCap(int threads) Maximum queued body tasks for an I/O pool withthreadsworkers.(package private) static booleanisBodyWatchdogStalled(long lastReadNanos, long nowNanos, long stallNs) Decide whether an async body handoff has stalled.(package private) static booleanisGzipAllowed(Properties opts) Whether the tunnel allows gzip responses, from the tunnel options.(package private) booleanisInproxyRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts) Reject requests that contain forwarded-for headers (inproxy detection).booleanWhether a client may reuse one connection for several requests.(package private) static booleanisKeepAliveRequest(String cmd) Whether the request can keep the connection alive: GET or HEAD with HTTP/1.1.(package private) booleanisPostThrottled(StringBuilder command, Hash peerHash, I2PSocket socket, String peerB32) Check POST/PUT throttling for this peer.(package private) booleanisRefererRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts) Reject requests with absolute Referer URIs.(package private) booleanisUserAgentRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts) Reject requests with blacklisted User-Agent strings.static intHeap-derived ceiling on the I/O transfer budget.voidoptionsUpdated(I2PTunnel tunnel) Called when tunnel options are updated.(package private) static longparseHeaderTimeout(Properties opts) Parse and clamp the first-request header timeout (ms) from tunnel options.(package private) static booleanparseKeepAlive(Properties opts) Parse the keepalive enable flag from tunnel options.(package private) static longParse and clamp the idle keepalive timeout (ms) from tunnel options.private static longparseTimeoutMs(Properties opts, String key, long dflt, long min, long max) Shared timeout parser: default on missing/unparseable, clamp to [min, max].private voidprocessBlocklist(I2PSocket socket, StringBuilder command) Check the request command against the HTTP blocklist.readHeaders(I2PSocket socket, InputStream in, StringBuilder command, String[] skipHeaders, I2PAppContext ctx, long initialTimeout) Read headers from the input stream, using an array of skip headers.readHeaders(I2PSocket socket, InputStream in, StringBuilder command, Set<String> skipHeaders, I2PAppContext ctx, long initialTimeout) From I2P to server: socket non-null, in null.readHeadersInternal(I2PSocket socket, InputStream in, StringBuilder command, Set<String> skipHeaders, I2PAppContext ctx, long initialTimeout) Read and parse HTTP headers, filtering out specified headers and enforcing size limits.private static voidreadLine(I2PSocket socket, StringBuilder buf, long timeout) Read a line terminated by newline, with a total read timeout.readRequestHeaders(I2PSocket socket, StringBuilder command, int requestCount, String peerB32, I2PAppContext ctx, Log log, String tunnelId) Read the request headers with the default timeouts.readRequestHeaders(I2PSocket socket, StringBuilder command, int requestCount, String peerB32, I2PAppContext ctx, Log log, String tunnelId, long headerTimeout, long keepAliveTimeout) Read the request headers, handling the specific errors that can occur by sending the appropriate error response to the client.(package private) static voidTrigger a full I/O-pool rebalance after a global budget change (Tuner).private static voidRebalance every live I/O pool from the global ioTransferThreads budget.private voidrecordInitialTiming(long afterAccept, long afterHeaders, long afterSocket, long afterHandle, int requestCount, String peerB32) Record timing stats for the first request in a keepalive cycle.protected voidrejectConnection(I2PSocket socket) Refuse an inbound connection when the gate cap is exceeded: send a 503 (SSL port 443 resets instead) and close.protected voidrejectConnection(I2PSocket socket, boolean queueFull) Refuse an inbound connection (queue-full or connection-cap): send a 503 (SSL port 443 resets instead) and close, then log the accurate reason.private voidreportRunnerShed(String tunnelId, String peerB32) Report a runner-pool rejection to the Tuner and the log.(package private) static voidTest seam: clear the request-error rate limiting state.private static voidresizeIOExecutor(ThreadPoolExecutor ex, int newThreads) Resize an I/O pool, preserving core == max.private static voidsampleIOLoad(ThreadPoolExecutor ioPool) Publish a pool's load for the operator-facing view on /stats and the graphs page.private static voidSend an error response to the client.private static voidsendErrorAndClose(I2PSocket socket) Send a 503 and close the socket, ignoring any failures.(package private) static voidRemove the other matching entries and set this entry as the only one.voidsetHeaderTimeout(long ms) Set the first-request header timeout, clamped to the configured bounds.static voidsetIOStallTimeoutMs(long val) Set the I/O stall timeout in ms (called by Tuner via reflection).static voidsetIOTransferThreads(int val) Set the I/O transfer pool size (called by Tuner via reflection).voidsetKeepAlive(boolean on) Enable/disable HTTP persistent connections without a tunnel restart.voidsetKeepAliveTimeout(long ms) Set the idle keepalive wait, clamped to the configured bounds.private voidsetupI2PTunnelHTTPServer(String spoofHost) Common initialization for all constructors.private voidInitialize or update the POST/PUT throttler from tunnel options.private booleanshouldKeepalive(AtomicInteger waiter, long afterAccept, int requestCount) Check whether the keepalive waiter indicates we should stay alive.(package private) static booleanshouldLogRequestError(String peerB32, long now) Decide whether a request-error warning should be emitted for a client.voidStart the tunnel and initialize timing and throttling.(package private) static intTest seam: number of clients currently tracked.private voidValidate the request hostname against local/loopback/private addresses.Methods inherited from class I2PTunnelServer
acquireConnectionSlot, destroy, effectiveWriteTimeout, getReadTimeout, getSocket, getSocket, getSocketManager, getSocketString, parseServerThreadOverride, releaseConnection, releaseConnectionSlot, run, setReadTimeout, shouldRejectOnQueue, shouldRejectOnQueue, tryAcquireConnectionModifier and TypeMethodDescription(package private) static booleanacquireConnectionSlot(int maxConnections, AtomicInteger active) Decide whether a concurrent-connection slot is available and, if so, reserve it.booleandestroy()Note that the tunnel cannot be reopened after this by calling startRunning(), as it will destroy the underlying socket manager.(package private) static longeffectiveWriteTimeout(Properties props) Determine the write timeout to bind the outbound I2P stream to, in ms.longGet the read idle timeout for newly-created connections (in milliseconds).protected SocketCreates a TCP socket connection to the configured destination.protected SocketgetSocket(Hash from, InetAddress remoteHost, int remotePort) Creates a TCP socket connection to the specified destination.Return the socket manager, or null if destroyed.protected StringgetSocketString(int incomingPort) Gets a human-readable string representation of the target socket.(package private) static intParse the per-tunnel server handler pool override.protected voidRelease a reservation taken byI2PTunnelServer.tryAcquireConnection().(package private) static voidreleaseConnectionSlot(AtomicInteger active) Release a slot reserved byI2PTunnelServer.acquireConnectionSlot(int, AtomicInteger).voidrun()Runs the I2PTunnelServer to accept and handle incoming connections asynchronously.voidsetReadTimeout(long ms) Set the read idle timeout for newly-created connections (in milliseconds).(package private) static booleanshouldRejectOnQueue(int queueDepth, int capacity, int active, int threads) Back-compat overload when the caller has no handle-time sample yet.(package private) static booleanshouldRejectOnQueue(int queueDepth, int capacity, int active, int threads, long avgHandleMs) Decide whether a connection should be rejected because the handler queue is already drowning.protected booleanTry to reserve a slot in the concurrent-connection gate.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
DEFAULT_POST_BAN_TIME
public static final int DEFAULT_POST_BAN_TIMEPost throttle window (seconds).- See Also:
DEFAULT_POST_MAX
DEFAULT_POST_TOTAL_BAN_TIME
public static final int DEFAULT_POST_TOTAL_BAN_TIMEDefault total ban time for excessive posts.- See Also:
DEFAULT_POST_TOTAL_MAX
public static final int DEFAULT_POST_TOTAL_MAXDefault max total posts before ban.- See Also:
DEFAULT_POST_WINDOW
public static final int DEFAULT_POST_WINDOWDefault post rate-limit window (seconds).- See Also:
DEFAULT_KEEPALIVE
private static final boolean DEFAULT_KEEPALIVEDefault keepalive setting for HTTP persistent connections.- See Also:
OPT_POST_BAN_TIME
Config key for post ban time.- See Also:
OPT_POST_MAX
OPT_POST_TOTAL_BAN_TIME
Config key for post total ban time.- See Also:
OPT_POST_TOTAL_MAX
Config key for max total posts.- See Also:
OPT_POST_WINDOW
Config key for post check time window.- See Also:
DNS_TIMEOUT_MS
private static final int DNS_TIMEOUT_MSMax wait for DNS resolution of non-I2P hostnames in the HTTP proxy.- See Also:
REQUEST_ERROR_LOG_INTERVAL_MS
static final long REQUEST_ERROR_LOG_INTERVAL_MSMinimum interval between per-client "Request error" warnings. A single torrent client reconnecting without sending a request produced 1000+ read timeouts in one rotation, which buried every other warning.- See Also:
MAX_TRACKED_ERROR_CLIENTS
static final int MAX_TRACKED_ERROR_CLIENTSCap on distinct clients tracked for warning rate limiting, so a flood from many distinct peers cannot grow this map without bound.- See Also:
_lastErrorLogTime
Last time each client was logged for a request error, for rate limiting. Keyed on the base32 destination; bounded byMAX_TRACKED_ERROR_CLIENTS._dnsResolver
Dedicated executor for non-blocking DNS lookups with bounded timeout._ioPools
Per-tunnel I/O pools for Server→Client data transfer, keyed by this server instance so a saturated dest cannot consume every IO thread. Sized as shares ofTunnelControllerGroup.getIOTransferThreads(). Separate from the runner pool so pinned runner threads are never consumed by slow downloads._ioExecutorLock
Lock for creating the shared IO executor.ioTransferThreads
static volatile int ioTransferThreadsDefault I/O transfer threads (global budget); Tuner adjusts viaTunnelControllerGroup. Sized for concurrent body transfers across many server tunnels (e.g. 16 eepsites x floor 8 = 128 threads when fully loaded).IO_THREADS_MIN
static final int IO_THREADS_MINAbsolute floor for the global I/O budget. Two transfers minimum so a quiet router can still serve a page and its assets; the per-tunnel floor is derived from the budget inioPoolFloor().- See Also:
IO_THREADS_MAX
static final int IO_THREADS_MAXMaximum global I/O budget (Tuner max must match). @since 0.9.71+.IO_TRANSFER_RATES
private static final long[] IO_TRANSFER_RATESSample periods for the I/O transfer load stat. Five minutes is included because a body transfer holds its thread for the whole transfer, so the per-minute view is noisy while the five-minute one shows the trend the operator needs against the pool's ceiling.STALL_WINDOW_FLOOR_MS
static final long STALL_WINDOW_FLOOR_MSFloor for the configured stall window, ms. A window below this is shorter than any pause in a bulk transfer over a congested path and aborts healthy downloads; bodies that have moved megabytes scale up further viacomputeStallWindowMs(long, long).- See Also:
ioStallTimeoutMs
static volatile long ioStallTimeoutMsCurrent stall window, adjustable by the Tuner between the floor and max._stallEventCount
Monotonically increasing counter of stall events detected by runOnIO or the Sender. Used by the Tuner as the observed signal for stall timeout adjustment._bodyAbortCount
Bodies aborted because the I/O pool was saturated. A response aborted here reaches the client as a truncated body, which is otherwise indistinguishable from a socket close or a read timeout, so the counter is what lets the cause be told apart in the log and on the console.SHED_LOG_THROTTLE_MS
private static final long SHED_LOG_THROTTLE_MSMinimum interval between shed warnings for this listener.- See Also:
_lastShedLog
private volatile long _lastShedLogWhen the last overload shed was logged.BULK_STALL_1M
private static final long BULK_STALL_1MProgress thresholds forcomputeStallWindowMs(long, long). @since 0.9.71+.- See Also:
BULK_STALL_8M
private static final long BULK_STALL_8MBody size where the bulk-transfer stall begins.- See Also:
BULK_STALL_32M
private static final long BULK_STALL_32MBody size where the bulk-transfer stall is at full delay.- See Also:
MAX_STALL_WINDOW_MS
static final long MAX_STALL_WINDOW_MSHard ceiling on a progress-scaled window, ms. Above the top progress tier (8x) so a raised base window still cannot push a body past ten minutes without a stall.- See Also:
STALL_SIDE_INDETERMINATE
Stall side reported when both stamps are equally stale. @since 0.9.71+.- See Also:
STALL_SIDE_SOURCE
Stall side reported when the source moved nothing and only a read blocked.- See Also:
STALL_SIDE_DESTINATION
Stall side reported when the destination moved nothing.- See Also:
STALL_SIDE_INDETERMINATE_MAX_BYTES
private static final long STALL_SIDE_INDETERMINATE_MAX_BYTESBelow this many bytes a body has moved too little for a "moved nothing, and only a read is blocked" claim, so equally stale stamps are reported as destination anyway. @since 0.9.71+.- See Also:
OPT_REJECT_INPROXY
Config key to reject requests from inproxy.- See Also:
OPT_REJECT_REFERER
Config key to reject requests with Referer header.- See Also:
OPT_REJECT_USER_AGENTS
Config key to reject specific User-Agents.- See Also:
OPT_USER_AGENTS
Config key for User-Agent reject list.- See Also:
OPT_KEEPALIVE
Config key to enable keepalive (legacy short form; see PROP_KEEPALIVE).- See Also:
PROP_KEEPALIVE
Config key to enable HTTP persistent connections; live via optionsUpdated. Falls back to the legacyOPT_KEEPALIVEshort key when unset. Key:tunnel.N.option.i2ptunnel.server.keepalive.- See Also:
PROP_KEEPALIVE_TIMEOUT
Config key for the idle keepalive wait before closing a persistent connection, in ms. Replaces the hard-coded 130s wait so a browser that goes away does not pin a handler thread for over two minutes. Key:tunnel.N.option.i2ptunnel.server.keepAliveTimeout.- See Also:
PROP_HEADER_TIMEOUT
Config key for the first-request header read timeout, in ms. Key:tunnel.N.option.i2ptunnel.server.headerTimeout.- See Also:
DEFAULT_KEEPALIVE_TIMEOUT_MS
public static final long DEFAULT_KEEPALIVE_TIMEOUT_MSDefault idle keepalive wait (ms); short enough to free the handler thread, long enough for a browser to reuse the connection.- See Also:
DEFAULT_HEADER_TIMEOUT_MS
public static final long DEFAULT_HEADER_TIMEOUT_MSDefault first-request header timeout (ms). 30s matches the pre-config hard-coded value and gives slow eepsites time to start responding before the 408 kill fires (which aborts Range-resume downloads mid-body).- See Also:
MIN_KEEPALIVE_TIMEOUT_MS
MAX_KEEPALIVE_TIMEOUT_MS
MIN_HEADER_TIMEOUT_MS
MAX_HEADER_TIMEOUT_MS
OPT_ADD_RESPONSE_HEADER_ALLOW
Config key to add Allow response header.- See Also:
OPT_ADD_RESPONSE_HEADER_CACHE_CONTROL
Config key to add Cache-Control response header.- See Also:
OPT_ADD_RESPONSE_HEADER_NOSNIFF
Config key to add X-Content-Type-Options: nosniff.- See Also:
OPT_ADD_RESPONSE_HEADER_REFERRER_POLICY
Config key to add Referrer-Policy response header.- See Also:
_spoofHost
The hostname to inject as the Host header, or null for passthrough.HASH_HEADER
Client request headers to remove.- See Also:
DEST64_HEADER
Header carrying the server's destination in base64.- See Also:
DEST32_HEADER
Header carrying the server's destination in base32.- See Also:
PROXY_CONN_HEADER
The Proxy-Connection header.- See Also:
PRIORITY_HEADER
Header carrying the browser's request priority.- See Also:
SEC_GPC_HEADER
Header carrying the Global Privacy Control signal.- See Also:
X_REAL_IP_HEADER
Header a proxy uses to name the client address.- See Also:
CLIENT_SKIPHEADERS
SERVER_SKIPHEADERS
AGE_HEADER
Age header, stripped for anonymity.- See Also:
ALT_SVC_HEADER
Alt-Svc header pointing elsewhere, stripped.- See Also:
DATE_HEADER
Date header, replaced by the proxy.- See Also:
EXPIRES_HEADER
Expires header, stripped as it conflicts with cache-control.- See Also:
PRAGMA_HEADER
Obsolete Pragma header, stripped.- See Also:
REFERER_HEADER
Referer header, stripped as it does not belong in responses.- See Also:
SERVER_HEADER
Server header naming the web server, stripped.- See Also:
STRICT_TRANSPORT_SECURITY_HEADER
HSTS header, useless through the proxy, stripped.- See Also:
VIA_HEADER
Via header listing proxies, stripped for anonymity.- See Also:
X_CACHE_HEADER
X-Cache header revealing cache state, stripped.- See Also:
X_CACHE_HITS_HEADER
X-Cache-Hits header revealing cache state, stripped.- See Also:
X_CLOUD_TRACE_CONTEXT_HEADER
Google Cloud trace header, stripped.- See Also:
X_CONTEXTID_HEADER
X-Contextid header, stripped.- See Also:
X_GOOG_GENERATION_HEADER
Google Cloud Storage generation header, stripped.- See Also:
X_GOOG_HASH_HEADER
Google Cloud Storage hash header, stripped.- See Also:
X_GUPLOADER_UPLOADID_HEADER
Google upload id header, stripped.- See Also:
X_HACKER_HEADER
Wordpress X-Hacker header, stripped.- See Also:
X_NANANANA_HEADER
Wordpress Batcache header, stripped.- See Also:
X_PANTHEON_STYX_HOSTNAME_HEADER
Pantheon hosting host name, stripped for anonymity.- See Also:
X_POWERED_BY_HEADER
X-Powered-By header naming the framework, stripped.- See Also:
X_RUNTIME_HEADER
Rails runtime header, stripped.- See Also:
X_SERVED_BY_HEADER
Host that served the response, stripped for anonymity.- See Also:
X_STYX_REQ_ID_HEADER
Request id on Pantheon responses, stripped.- See Also:
PROXY_HEADER
Proxy header, checked against the httpoxy attack. See also: httpoxy- See Also:
NEWLINE_SPLIT
Pattern splitting a request line at CRLF.SPACE_SPLIT
Pattern splitting a request line at spaces.SLASH_SPLIT
Pattern splitting a path at slashes._headerTimeoutMs
private volatile long _headerTimeoutMsLive first-request header timeout (ms); updated by optionsUpdated. Was a hard-coded 30s constant._keepAliveTimeoutMs
private volatile long _keepAliveTimeoutMsLive idle keepalive wait (ms); updated by optionsUpdated. Was a hard-coded 130s wait._keepAlive
private volatile boolean _keepAliveWhether HTTP persistent connections are accepted; updated by optionsUpdated.HEADER_FINISH_TIMEOUT
private static final long HEADER_FINISH_TIMEOUTGrace period for remaining header lines after the first line, on top of the initial header timeout (slowloris bound).- See Also:
START_INTERVAL
private static final long START_INTERVALMin time before socket error is escalated to ERROR level.- See Also:
MAX_LINE_LENGTH
private static final int MAX_LINE_LENGTHLongest request header line accepted.- See Also:
MAX_HEADERS
private static final int MAX_HEADERSRidiculously long, just to prevent OOM DOS.- See Also:
MAX_TOTAL_HEADER_SIZE
private static final int MAX_TOTAL_HEADER_SIZEIncludes request, just to prevent OOM DOS.- See Also:
DEFAULT_HTTP_READ_TIMEOUT
private static final long DEFAULT_HTTP_READ_TIMEOUTDefault read timeout for the server socket, -1 for none. It does not apply to header reads; forever keeps a large response from timing out, and the web server presumably implements its own timeout for POST requests.- See Also:
SERVER_READ_TIMEOUT_GET
private static final int SERVER_READ_TIMEOUT_GETRead timeout while reading a GET request, relatively short; POST and CONNECT get a long failsafe timeout since the user may be uploading a massive file.- See Also:
SERVER_READ_TIMEOUT_MEDIUM
private static final int SERVER_READ_TIMEOUT_MEDIUMRead timeout for medium uploads.- See Also:
SERVER_READ_TIMEOUT_POST
private static final int SERVER_READ_TIMEOUT_POSTRead timeout while reading a POST body.- See Also:
_startedOn
private long _startedOnTimestamp when the tunnel started, used for error-level escalation timing._postThrottler
POST/PUT throttler for rate limiting.HTTP_BLOCKLIST_CLIENT_LIMIT
private static final int HTTP_BLOCKLIST_CLIENT_LIMITMax clients tracked by the HTTP blocklist.- See Also:
_blocklistManager
HTTP blocklist manager.ERR_NOT_FOUND
ERR_UNAVAILABLE
503 Service Unavailable error response.- See Also:
ERR_DENIED
429 Too Many Requests error response.- See Also:
ERR_FORBIDDEN
ERR_REQUEST_URI_TOO_LONG
414 Request URI too long error response.- See Also:
ERR_HEADERS_TOO_LARGE
431 Request header fields too large error response.- See Also:
ERR_REQUEST_TIMEOUT
HTTP 408 Request Timeout error response. Includes a meta refresh to retry after 5 seconds.- See Also:
ERR_BAD_REQUEST
400 Bad Request error response.- See Also:
CONN_NONE
CONN_KEEPALIVE
CONN_UPGRADE
CONN_CLOSE
MIN_TO_COMPRESS
private static final int MIN_TO_COMPRESSMinimum response size in bytes before gzip compression is applied.- See Also:
Constructor Details
I2PTunnelHTTPServer
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) Create an HTTP tunnel server with a private key string.- Parameters:
host- the local address to bind toport- the local portprivData- the private key data as a Base64 stringspoofHost- the hostname to spoof in the Host header, or nulll- the logging instancenotifyThis- the event dispatcher for notificationstunnel- the parent tunnel configuration
I2PTunnelHTTPServer
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) Create an HTTP tunnel server from a private key file.- Parameters:
host- the local address to bind toport- the local portprivkey- the private key fileprivkeyname- the name for the private keyspoofHost- the hostname to spoof in the Host header, or nulll- the logging instancenotifyThis- the event dispatcher for notificationstunnel- the parent tunnel configuration
I2PTunnelHTTPServer
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) Create an HTTP tunnel server from a private key stream.- Parameters:
host- the local address to bind toport- the local portprivData- the input stream containing the private key dataprivkeyname- the name for the private keyspoofHost- the hostname to spoof in the Host header, or nulll- the logging instancenotifyThis- the event dispatcher for notificationstunnel- the parent tunnel configuration
Method Details
shouldLogRequestError
Decide whether a request-error warning should be emitted for a client. First occurrence for a client is always logged; subsequent ones only afterREQUEST_ERROR_LOG_INTERVAL_MS. Evicts the oldest entries once the tracking map is full, so a burst of distinct peers cannot grow it without bound.- Parameters:
peerB32- client base32, may be nullnow- current time in ms- Returns:
- true if the caller should log at WARN
evictOldestErrorClients
private static void evictOldestErrorClients(long now) Free space in the tracking map, guaranteeing it never exceedsMAX_TRACKED_ERROR_CLIENTS. First drops clients whose last warning is older than the log interval. If that is not enough (a burst of distinct peers inside one interval leaves nothing stale), the single oldest entry is evicted so the bound always holds. This runs only on the overflow path, never on the normal per-request check.- Parameters:
now- current time in ms
resetRequestErrorLimiter
static void resetRequestErrorLimiter()Test seam: clear the request-error rate limiting state.trackedErrorClients
static int trackedErrorClients()Test seam: number of clients currently tracked.- Returns:
- tracked client count
maxIOThreads
public static int maxIOThreads()Heap-derived ceiling on the I/O transfer budget.Each I/O thread holds 16 KB of buffer plus a 16 KB BufferedInputStream and is blocked for the whole of a body transfer, so a thread costs roughly an order of magnitude more than a runner-pool task, which finishes in microseconds. Costing the divisor accordingly: 4 MB per thread rather than the 2 MB the runner pool uses. At 8 GB that is 2048 threads, adding roughly 190 MB to a 1.2 GB footprint — comfortable, and still large headroom over the few hundred concurrent transfers a 40 K-client tracker produces at a 30-minute announce interval.
The ceiling is burst headroom, not residency:
allowCoreThreadTimeOutwith a 30-second keepalive reaps threads half a minute after their last task, so an idle pool costs nothing whatever the ceiling says.- Returns:
- the memory-feasible I/O thread ceiling, in [1024, 16384]
getBodyAbortCount
public static long getBodyAbortCount()Count of bodies dropped because the I/O pool had no free worker.- Returns:
- bodies aborted due to I/O pool saturation since router start
getIOExecutor
ThreadPoolExecutor getIOExecutor()Get (creating on first use) this tunnel's private I/O pool for data transfer. The pool is a share of the global ioTransferThreads budget so one tunnel's bulk downloads cannot starve another's.- Returns:
- non-null, this tunnel's I/O executor
growRunnerPoolForSubmit
private void growRunnerPoolForSubmit()Grow this tunnel's runner pool if work is backing up, immediately before a submit.The fast path is one atomic load of the queue depth, so a request that finds a free pool pays nothing. Only when the queue holds work is
getActiveCount()consulted, because that takes the pool's mainLock and would otherwise be a per-request monitor acquisition on a busy tunnel.Growing here rather than in the accept loop is deliberate: the accept loop owns the server handler pool, and the runner pool is only reachable from a request that has already been parsed.
reportRunnerShed
Report a runner-pool rejection to the Tuner and the log.The shed stat is the point of this method. It is the unambiguous overload signal
I2PTunnelClientRunnerMaxParamgrows on, and the server paths that reject never wrote it:i2ptunnel.clientConnectionShedread "No lifetime events" on a live router that was rejecting about 1.7 requests a second. Without the write the Tuner reads such a tunnel as idle and decays its runner budget toward the floor while the tunnel rejects at its share.The active-thread sample is taken here because this is the only instant the pool is observably pinned; the accept-loop sample is taken before the work starts and reads low. Closing the connection is left to the caller, which knows what it opened.
- Parameters:
tunnelId- the tunnel identifier for the log linepeerB32- the client's base32 address for the log line
ioQueueCap
static int ioQueueCap(int threads) Maximum queued body tasks for an I/O pool withthreadsworkers. The queue is bounded so a saturated pool cannot park an unbounded number of sockets with their browser and I2P streams open; overflow runs the body on the handler thread instead (seehandOffBody(ThreadPoolExecutor, I2PTunnelHTTPServer.Sender, String)), which preserves a response whose headers were already sent. Package-visible for tests.- Parameters:
threads- pool worker count (already floored)- Returns:
- queue capacity, at least 32
createIOExecutor
Create a fixed-size I/O transfer pool. Package-visible for tests.- Parameters:
threads- fixed core/max worker count- Returns:
- non-null executor with a bounded queue (
ioQueueCap(int))
rebalanceIOPools
private static void rebalanceIOPools()Rebalance every live I/O pool from the global ioTransferThreads budget. Must run under_ioExecutorLock.resizeIOExecutor
Resize an I/O pool, preserving core == max.- Parameters:
ex- the pool; ignored if null or shut downnewThreads- the new fixed size
ioPoolStopped
private void ioPoolStopped()Deregister this tunnel's I/O pool on stop so its budget share is freed.rebalanceAllIOPools
static void rebalanceAllIOPools()Trigger a full I/O-pool rebalance after a global budget change (Tuner).getIOTransferActiveCount
public static int getIOTransferActiveCount()Get the saturation pressure summed across all live I/O pools: running workers plus queued transfers. Used by the Tuner as the reactive growth signal (queued bodies count as pressure so a backlog grows the budget on the next cycle, not only when workers are full).- Returns:
- active + queued transfers across pools, or 0 if none created
getIOMaxPoolUtilization
public static double getIOMaxPoolUtilization()The busiest pool's utilization: its active plus queued transfers divided by its present maximum, as a rate that may exceed 1.0 when the queue is backing up past the pool's ceiling.The growth signal must be per-pool, not a global sum. Ten pools sharing one budget means the summed pressure divided by the budget reads low even when one pool is saturated — a tracker under load can sit at its allocation while the global ratio stays below the growth step. This returns the busiest pool's own utilization against its own maximum, so a saturated pool is visible regardless of how quiet the others are.
- Returns:
- the busiest pool's utilization rate, or 0.0 when no pools exist
ioPoolFloor
static int ioPoolFloor()The floor for one I/O pool, proportional to the budget and pool count.A fixed floor scales linearly with tunnel count and can consume the whole budget before any load-based allocation — ten pools at a floor of 8 leave 16 of 96 threads for load. The floor is a quarter of one pool's fair share, so it shrinks as pools multiply and always leaves the majority of the budget for pressure-based distribution.
- Returns:
- the per-pool floor, never below
IO_THREADS_MIN
setIOTransferThreads
public static void setIOTransferThreads(int val) Set the I/O transfer pool size (called by Tuner via reflection). Clamped to the absolute bounds only: the per-pool floor is derived from the budget it would clamp against, so using it here would make the setter path-dependent. A set could never lower the budget below a quarter of its previous value. The per-pool floor is enforced where it is used, at allocation inrebalanceIOPools().- Parameters:
val- new pool size (clamped to [IO_THREADS_MIN,IO_THREADS_MAX])
getIOTransferThreads
public static int getIOTransferThreads()Get the current I/O transfer pool size.- Returns:
- current pool size
setIOStallTimeoutMs
public static void setIOStallTimeoutMs(long val) Set the I/O stall timeout in ms (called by Tuner via reflection).The floor matches the Tuner's own floor: a window below
STALL_WINDOW_FLOOR_MSis shorter than any pause in a bulk transfer, so it aborts healthy downloads rather than freeing threads. Bulk bodies get a larger window still viacomputeStallWindowMs(long, long).- Parameters:
val- new timeout in ms (clamped to [STALL_WINDOW_FLOOR_MS, 300000])
getIOStallTimeoutMs
public static long getIOStallTimeoutMs()Get the current I/O stall timeout in ms.- Returns:
- current stall timeout
getStallEventCount
public static long getStallEventCount()Get the total number of stall events detected by Sender since router start. Each stall event means a Server→Client transfer was interrupted because no data arrived forioStallTimeoutMs. Used by the Tuner as a real-time signal for stall timeout adjustment.- Returns:
- monotonically increasing stall event count
sampleIOLoad
Publish a pool's load for the operator-facing view on /stats and the graphs page.Explicitly *not* the Tuner's input, despite the name matching its param:
I2PTunnelServerIOTransferParam.getObservedStatreads the livegetIOTransferActiveCount()total instead, on the fast five-second cycle, so the ceiling reacts to load in seconds rather than to a rate window. An earlier revision of this javadoc claimed this stat unblocked that policy — it does not, and the frozen pool it was meant to explain was caused byrebalanceIOPools()splitting the budget equally across every pool, which left a busy tracker at the 8-thread floor alongside nine idle eepsites. Load-weighting that rebalance is the fix; this stat is how an operator sees the same pressure over time.- Parameters:
ioPool- the pool to sample; ignored if null or shut down
handOffBody
static boolean handOffBody(ThreadPoolExecutor pool, I2PTunnelHTTPServer.Sender s, String desc) throws IOException Hand off the response body to the I/O transfer pool, or run it inline when the pool is null or shut down.- Parameters:
pool- the I/O executor to submit to; null or shut down forces an inline runs- the Sender to run; null does nothing and reports falsedesc- descriptive name for error messages- Returns:
- true if the body was submitted asynchronously, false if it ran inline
- Throws:
IOException- part of the handoff API's signature; the body reports every failure through the Sender rather than throwing
handOffBody
static boolean handOffBody(ThreadPoolExecutor pool, I2PTunnelHTTPServer.Sender s, String desc, Runnable onCompletion) throws IOException AshandOffBody(ThreadPoolExecutor, Sender, String)with a completion callback invoked after the Sender finishes (success, failure, or abort), on the pool worker when submitted and on the caller otherwise. The callback always runs, so a caller can rely on it for connection teardown no matter which path was taken.- Parameters:
pool- the I/O executor to submit to; null or shut down forces an inline runs- the Sender to run; null runs the callback only and reports falsedesc- descriptive name for error messagesonCompletion- may be null; must not throw- Returns:
- true if the body was submitted asynchronously, false if it ran inline or was aborted
- Throws:
IOException- part of the handoff API's signature; the body reports every failure through the Sender rather than throwing
isBodyWatchdogStalled
static boolean isBodyWatchdogStalled(long lastReadNanos, long nowNanos, long stallNs) Decide whether an async body handoff has stalled. Pure decision helper for the body watchdog: true when the Sender's last-read stamp is at least the stall window old. A task that has not started yet (stamp 0) never stalls — queueing behind a healthy long transfer is normal — and a disabled window (<= 0) never stalls.- Parameters:
lastReadNanos- Sender#getLastReadNanos(); 0 or negative if the worker has not started the copy yetnowNanos- the currentSystem.nanoTime(), same base as lastReadNanosstallNs- stall window in nanoseconds; <= 0 disables- Returns:
- true if the watchdog should close the streams and finish
computeStallWindowMs
static long computeStallWindowMs(long baseMs, long bytesTransferred) Stall window for a body that has already movedbytesTransferred.The configured window is right for a small response but wrong for a bulk one: over a congested path a 45MB transfer routinely pauses for many seconds between copy steps, and a single fixed window either kills the transfer or, if sized for the bulk case, pins a worker for minutes behind a dead peer. The response
Content-Lengthis not available here (it is never parsed, and gzip and chunked bodies have no useful declared length anyway), so the window scales with progress instead: a body that has already moved megabytes has demonstrably earned patience.- Parameters:
baseMs- configured window,ioStallTimeoutMsbytesTransferred- bytes already moved by the body- Returns:
- the window to apply, ms
classifyStalledSide
Which side of a stalled body transfer stopped making progress.Read and write stamps are updated either side of the same copy step, so a body blocked mid-write ages both equally: the read that fed the blocked write is stamped, then the write never completes to stamp itself. The two therefore look identical to a body blocked mid-read, and only the progress counter separates them -- a body that has moved meaningful bytes and then gone quiet is sitting in a blocked write, because a blocked read would have had nothing to hand the previous write.
Reporting the ambiguous case as the source is actively misleading: a 45MB download that stalled after 3.2MB was logged as "local backend silent" while the same backend served the whole file in 4ms on a fresh connection, sending every diagnosis to the wrong layer.
- Parameters:
readAgeMs- age of the last-read stampwriteAgeMs- age of the last-write stampbytesTransferred- bytes already moved by the body- Returns:
- one of
STALL_SIDE_SOURCE,STALL_SIDE_DESTINATION, orSTALL_SIDE_INDETERMINATE
closeQuietly
Close a stream, ignoring failures. Used by stall/teardown paths where the close is best-effort (the stream may already be dead) and the goal is only to unblock a thread parked in read or write.- Parameters:
c- stream to close; ignored if null
setupI2PTunnelHTTPServer
Common initialization for all constructors. Sets the spoofed host, creates stats, and initializes the blocklist manager.- Parameters:
spoofHost- the hostname to spoof in the Host header, or null for no spoofing
startRunning
public void startRunning()Start the tunnel and initialize timing and throttling.- Overrides:
startRunningin classI2PTunnelServer
setupPostThrottle
private void setupPostThrottle()Initialize or update the POST/PUT throttler from tunnel options. Creates a new ConnThrottler if limits are configured, or updates existing.parseKeepAlive
Parse the keepalive enable flag from tunnel options. PrefersPROP_KEEPALIVE; falls back to the legacyOPT_KEEPALIVEshort key; defaults to true when unset.- Parameters:
opts- the client options; may be null- Returns:
- true if persistent connections are enabled
parseKeepAliveTimeout
Parse and clamp the idle keepalive timeout (ms) from tunnel options. Unparseable values fall back to the default; out-of-range values clamp.- Parameters:
opts- the client options; may be null- Returns:
- timeout in ms within [
MIN_KEEPALIVE_TIMEOUT_MS,MAX_KEEPALIVE_TIMEOUT_MS]
parseHeaderTimeout
Parse and clamp the first-request header timeout (ms) from tunnel options.- Parameters:
opts- the client options; may be null- Returns:
- timeout in ms within [
MIN_HEADER_TIMEOUT_MS,MAX_HEADER_TIMEOUT_MS]
parseTimeoutMs
Shared timeout parser: default on missing/unparseable, clamp to [min, max].- Parameters:
opts- the options; may be nullkey- the property keydflt- default when unset or not a numbermin- inclusive lower boundmax- inclusive upper bound- Returns:
- the parsed and clamped timeout in ms
isKeepAlive
public boolean isKeepAlive()Whether a client may reuse one connection for several requests.- Returns:
- whether HTTP persistent connections are currently enabled
setKeepAlive
public void setKeepAlive(boolean on) Enable/disable HTTP persistent connections without a tunnel restart.- Parameters:
on- true to accept persistent connections
getKeepAliveTimeout
public long getKeepAliveTimeout()How long an idle keepalive connection is held before it is probed.- Returns:
- idle keepalive wait in ms
setKeepAliveTimeout
public void setKeepAliveTimeout(long ms) Set the idle keepalive wait, clamped to the configured bounds.- Parameters:
ms- timeout in ms
getHeaderTimeout
public long getHeaderTimeout()How long the first request's headers may take to arrive.- Returns:
- first-request header timeout in ms
setHeaderTimeout
public void setHeaderTimeout(long ms) Set the first-request header timeout, clamped to the configured bounds.- Parameters:
ms- timeout in ms
getIntOption
Get an integer option from the tunnel client options.- Parameters:
opt- the option keydflt- the default value if not set or invalid- Returns:
- the option value, or dflt
close
public boolean close(boolean forced) Close the tunnel and stop the POST throttler.- Overrides:
closein classI2PTunnelServer- Parameters:
forced- true to force close- Returns:
- true if closed successfully
optionsUpdated
Called when tunnel options are updated. Re-initializes the POST throttler and updates the spoofed host.- Overrides:
optionsUpdatedin classI2PTunnelServer- Parameters:
tunnel- tunnel that was updated; ignored unless it is the tunnel we serve
getTunnelLogId
Short human-readable identifier for this HTTP server tunnel, e.g. "[skank / 8charB32]", matching the outbound tunnel log format "[nickname / b32prefix]" so related warnings can be correlated across the router and the i2ptunnel logs. A trailing ".i2p" on the configured nickname is stripped to match that format.- Parameters:
localDest- this tunnel's own destination, may be null- Returns:
- the "[nickname / prefix]" label, never null
blockingHandle
Called by the thread pool of I2PSocket handlers. Handles a single client connection through the HTTP proxy lifecycle: read headers, validate, apply spoofing, compress, and forward.- Overrides:
blockingHandlein classI2PTunnelServer- Parameters:
socket- the incoming I2P socket from the client- See Also:
readRequestHeaders
static Map<String, List<String>> readRequestHeaders(I2PSocket socket, StringBuilder command, int requestCount, String peerB32, I2PAppContext ctx, Log log, String tunnelId) throws IOException Read the request headers with the default timeouts. Delegates toreadRequestHeaders(I2PSocket, StringBuilder, int, String, I2PAppContext, Log, String, long, long).- Parameters:
socket- the client socketcommand- buffer for the request linerequestCount- the number of requests already handled on this connection (0 = first)peerB32- the client's base32 for loggingctx- the I2P app contextlog- the logging instancetunnelId- "[nickname / b32prefix]" label for this server tunnel- Returns:
- the parsed headers, or null if the request failed and the client was notified
- Throws:
IOException- on other I/O errors, propagated to the caller
readRequestHeaders
static Map<String, List<String>> readRequestHeaders(I2PSocket socket, StringBuilder command, int requestCount, String peerB32, I2PAppContext ctx, Log log, String tunnelId, long headerTimeout, long keepAliveTimeout) throws IOException Read the request headers, handling the specific errors that can occur by sending the appropriate error response to the client. The 5 error handlers share this shape: send error, log, close, return null.- Parameters:
socket- the client socketcommand- buffer for the request linerequestCount- the number of requests already handled on this connection (0 = first)peerB32- the client's base32 for loggingctx- the I2P app contextlog- the logging instancetunnelId- "[nickname / b32prefix]" label for this server tunnelheaderTimeout- first-request header timeout (ms)keepAliveTimeout- idle keepalive wait for subsequent requests (ms)- Returns:
- the parsed headers, or null if the request failed and the client was notified
- Throws:
IOException- on other I/O errors, propagated to the caller
getConnectionType
isKeepAliveRequest
Whether the request can keep the connection alive: GET or HEAD with HTTP/1.1.- Parameters:
cmd- the trimmed request line- Returns:
- true if keepalive may continue
hasGzipEncoding
Whether the Accept-Encoding value includes x-i2p-gzip.- Parameters:
enc- an Accept-Encoding header value, or null- Returns:
- true if it includes x-i2p-gzip
isGzipAllowed
Whether the tunnel allows gzip responses, from the tunnel options.- Parameters:
opts- the client options- Returns:
- true unless PROP_TUN_GZIP is set to false
handlePort443
Handle port 443 (SSL passthrough). If no target is configured for port 443, reset the socket. Otherwise, set a long timeout and forward directly to the server viaI2PTunnelRunner.- Parameters:
socket- the incoming I2P socket- Returns:
- true if the socket was handled (port 443), false otherwise
- Throws:
IOException- if socket operations fail
rejectConnection
Refuse an inbound connection when the gate cap is exceeded: send a 503 (SSL port 443 resets instead) and close. The base implementation then logs the rate-limited warning.- Overrides:
rejectConnectionin classI2PTunnelServer- Parameters:
socket- the accepted but undelivered I2PSocket to reject
rejectConnection
Refuse an inbound connection (queue-full or connection-cap): send a 503 (SSL port 443 resets instead) and close, then log the accurate reason.- Overrides:
rejectConnectionin classI2PTunnelServer- Parameters:
socket- the accepted but undelivered I2PSocket to rejectqueueFull- true if the handler queue gate rejected, false for the connection cap
validateRequestHost
private void validateRequestHost(Map<String, List<String>> headers, I2PSocket socket, String peerB32) throws IOException Validate the request hostname against local/loopback/private addresses. If the hostname resolves to a private or loopback address, the peer is blocklisted and the socket is closed. If DNS resolves to 0.0.0.0 (blocked DNS), a 403 is sent. DNS is resolved asynchronously with a bounded timeout to avoid blocking the shared client thread pool on slow or unreachable DNS servers.- Parameters:
headers- the request headers map (to extract Host header)socket- the incoming I2P socketpeerB32- the peer's base32 address for logging/blocklisting- Throws:
IOException- if blocklist writing fails
isInproxyRejection
boolean isInproxyRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts) Reject requests that contain forwarded-for headers (inproxy detection).- Parameters:
headers- the request headerssocket- the incoming I2P socketpeerB32- the peer's base32 address for loggingopts- the tunnel client options- Returns:
- true if the request was rejected and the socket closed
isRefererRejection
boolean isRefererRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts) Reject requests with absolute Referer URIs.- Parameters:
headers- the request headerssocket- the incoming I2P socketpeerB32- the peer's base32 address for loggingopts- the tunnel client options- Returns:
- true if the request was rejected and the socket closed
isUserAgentRejection
boolean isUserAgentRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts) Reject requests with blacklisted User-Agent strings.- Parameters:
headers- the request headerssocket- the incoming I2P socketpeerB32- the peer's base32 address for loggingopts- the tunnel client options- Returns:
- true if the request was rejected and the socket closed
isPostThrottled
Check POST/PUT throttling for this peer.- Parameters:
command- the request command string (first line)peerHash- the peer's hash for throttling lookupsocket- the incoming I2P socketpeerB32- the peer's base32 address for logging- Returns:
- true if the request was throttled and the socket closed
recordInitialTiming
private void recordInitialTiming(long afterAccept, long afterHeaders, long afterSocket, long afterHandle, int requestCount, String peerB32) Record timing stats for the first request in a keepalive cycle.- Parameters:
afterAccept- clock time right after acceptafterHeaders- clock time after reading headersafterSocket- clock time after creating the server socketafterHandle- clock time after dispatching the requestrequestCount- the request number (only logs for 0)peerB32- the peer's base32 address for logging
applySpoofedHost
Apply port-specific spoofed host header. If no port-specific spoof is configured for the socket's port, falls back to the default _spoofHost.- Parameters:
socket- the incoming I2P socketheaders- the request header map to modifyopts- the tunnel client options
shouldKeepalive
Check whether the keepalive waiter indicates we should stay alive.- Parameters:
waiter- the AtomicInteger set by CompressedRequestorafterAccept- clock time right after accept (for debug logging)requestCount- the request number (for debug logging)- Returns:
- true if the waiter returned keepalive-eligible (value 2)
sendError
Send an error response to the client. For port 443 (SSL), resets the socket instead since we can't send plaintext error messages.- Parameters:
socket- the client socketresp- the HTTP error response string- Throws:
IOException- if writing the response fails
sendErrorAndClose
Send a 503 and close the socket, ignoring any failures.- Parameters:
socket- the socket to respond on
extractRequestUrl
Extract the request URL from the client's headers for logging.Package-visible and static so a test can drive it with a hostile request line: the URL is entirely attacker-controlled, and this method used to call substring(0, 12) on any host merely containing "b32.i2p", so a request for "/b32.i2p" aborted the handler with StringIndexOutOfBoundsException.
- Parameters:
headers- the raw request headers, may be null- Returns:
- a human-readable request description, never null
addEntry
setEntry
getEntryOrNull
readHeaders
public static Map<String, List<String>> readHeaders(I2PSocket socket, InputStream in, StringBuilder command, Set<String> skipHeaders, I2PAppContext ctx, long initialTimeout) throws IOException From I2P to server: socket non-null, in null. From server to I2P: socket null, in non-null.Note: This does not handle RFC 2616 header line splitting, which is obsoleted in RFC 7230.
- Parameters:
socket- if null, use in as InputStreamin- if null, use socket.getInputStream() as InputStreamcommand- out parameter, first lineskipHeaders- MUST be lower casectx- I2P app context for clock and timeoutinitialTimeout- timeout for the first line read- Returns:
- the parsed header multimap
- Throws:
SocketTimeoutException- if timeout is reached before newlineEOFException- if EOF is reached before newlineI2PTunnelHTTPServer.LineTooLongException- if one header too long, or too many headers, or total size too bigI2PTunnelHTTPServer.RequestTooLongException- if too longI2PTunnelHTTPServer.BadRequestException- on bad headersIOException- on other errors in the underlying stream
readHeaders
public static Map<String, List<String>> readHeaders(I2PSocket socket, InputStream in, StringBuilder command, String[] skipHeaders, I2PAppContext ctx, long initialTimeout) throws IOException Read headers from the input stream, using an array of skip headers.- Parameters:
socket- if non-null, read from socket; otherwise read from inin- if non-null and socket is null, read from this streamcommand- output buffer for the first request lineskipHeaders- array of lower-case header names to filter outctx- I2P app context for clock and timeoutinitialTimeout- timeout for the first line read- Returns:
- the parsed header multimap
- Throws:
IOException- on I/O errors
readHeadersInternal
private static Map<String, List<String>> readHeadersInternal(I2PSocket socket, InputStream in, StringBuilder command, Set<String> skipHeaders, I2PAppContext ctx, long initialTimeout) throws IOException Read and parse HTTP headers, filtering out specified headers and enforcing size limits.- Parameters:
socket- if non-null, read from socket; otherwise read from inin- if non-null and socket is null, read from this streamcommand- output buffer for the first request lineskipHeaders- set of lower-case header names to filter outctx- I2P app context for clock and timeoutinitialTimeout- timeout for the first line read- Returns:
- the parsed header multimap
- Throws:
SocketTimeoutException- if the first line read exceeds initialTimeoutEOFException- on unexpected EOFI2PTunnelHTTPServer.LineTooLongException- if a line, total, or count exceeds limitsI2PTunnelHTTPServer.RequestTooLongException- if the request line is too longI2PTunnelHTTPServer.BadRequestException- on malformed headersIOException- on other I/O errors
readLine
Read a line terminated by newline, with a total read timeout.Warning - strips \n but not \r Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded Warning - not UTF-8.
- Parameters:
socket- the socket to read the line frombuf- StringBuilder the line is appended to, without its newlinetimeout- throws SocketTimeoutException immediately if zero or negative- Throws:
SocketTimeoutException- if timeout is reached before newlineEOFException- if EOF is reached before newlineI2PTunnelHTTPServer.LineTooLongException- if too longIOException- on other errors in the underlying stream
processBlocklist
Check the request command against the HTTP blocklist. If matched, logs the blocked destination, closes the socket, and throws BadRequestException.- Parameters:
socket- the incoming I2P socket to close if blockedcommand- the request command string to check- Throws:
I2PTunnelHTTPServer.BadRequestException- if the request matches a blocklist entryIOException- if closing the socket fails