Expand description
Accept loop with client-silence reaping — the slowloris defense
axum::serve cannot provide.
axum::serve (0.8) builds hyper-util’s connection builder with no
timer and no header_read_timeout and exposes no way to set one, so
a client that connects and sends a partial request line — or full
headers and then dribbles a Content-Length body forever — holds its
fd and connection task indefinitely (verified empirically: 90 s+ holds
with zero reaping). On the shipped k8s/systemd exposure that is an
unauthenticated fd-exhaustion vector on the proxy front door.
This module owns the accept loop instead, mirroring axum’s semantics
(Router::call per connection, serve_connection_with_upgrades,
graceful drain on shutdown) while adding exactly two client-silence
bounds — deliberately not a whole-request deadline, because a
legitimate upstream first token can take minutes and SSE responses
stream far longer than any sane global timeout:
- Header phase: hyper’s
header_read_timeout(30 s), which also reaps pre-request idle keep-alive connections (hyper arms it between requests on an idle HTTP/1 connection). - Body phase: a per-frame gap timeout on the request body
(30 s between chunks), applied via
SilenceBoundedBody. A slow uploader making real progress never trips it; a stalled one is aborted with a hyper error that resets the stream. - HTTP/2: keep-alive pings (20 s interval / 30 s grace) reap
silent peers;
header_read_timeoutcovers the h1 preface path.
Shutdown mirrors axum::serve::with_graceful_shutdown: when the
caller’s shutdown future resolves, the listener is dropped (new
connects refused), every live connection gets
graceful_shutdown() via [hyper_util::server::graceful::GracefulShutdown],
and the returned future resolves once all connections finish — the
caller keeps its own drain-budget timeout around that future, so
the drain-timeout accounting in main.rs is unchanged.
Constants§
- BODY_
FRAME_ GAP_ TIMEOUT - Maximum gap between request-body frames from the client.
Production value; passed by
main.rsinto the serve loop. - HEADER_
READ_ TIMEOUT - Maximum client silence while reading request headers (also arms
between keep-alive requests, reaping pre-request idle connections).
Production value; passed by
main.rsinto the serve loop.
Functions§
- serve_
with_ client_ silence_ reaping - Serve
routeronlistenerwith client-silence reaping, resolving aftershutdownfires and every remaining connection drains. The caller wraps the post-shutdown tail in its own drain-budget timeout.on_acceptruns synchronously per accepted socket (socket-option tuning — the caller owns its failure/log policy). The two silence bounds are parameters so tests can exercise reaping in milliseconds; production callers passHEADER_READ_TIMEOUT/BODY_FRAME_GAP_TIMEOUT.