feat(serve): burst-gated body stall eviction
The body budget from the prior commit is a generous absolute cap; alone it just hands a body-phase slowloris a bigger window. Add a stall gate under that cap that distinguishes a slowloris trickle from a slow-but-legit client by requiring BURSTS, not a mere average rate: - BodyStallGate: each body read is bounded by min(body cap, mark + stall). The stall mark advances only when body_burst_bytes accumulate since the last advance, so a steady sub-burst trickle never moves it and is evicted at ~body_stall_timeout, while a bursty slow client keeps resetting it. - Two words of state; one add + one compare per read. Raw socket bytes are counted, so chunked framing counts and an MSS-fragmented burst still accumulates. Reuses the existing read_some deadline plumbing. - Wired into read_body (fixed CL) and read_chunked_body (via fill_to, the single choke point all chunked reads pass through). - New knobs body_burst_bytes (4 KiB) + body_stall_timeout (20s); effective floor ~205 B/s enforced in bursts. Tests: body_smooth_trickle_evicted_at_stall_timeout (chunked; active sub-burst trickle evicted at ~stall while the cap is far away) and bursty_slow_body_survives_stall_gate (fixed CL; real bursts with sub-stall gaps complete intact). 79 lib + 45 integration green; clippy --lib clean.
This commit is contained in:
@@ -45,6 +45,13 @@ pub struct Config {
|
||||
/// to full body). Sized for slow links, so much larger than
|
||||
/// `head_timeout`. See `ConnLimits::body_timeout`.
|
||||
pub body_timeout: Duration,
|
||||
/// Burst size that resets the body stall clock. A body dribbling fewer
|
||||
/// than this per `body_stall_timeout` window is evicted — the slowloris
|
||||
/// / slow-legit discriminator. See `ConnLimits::body_burst_bytes`.
|
||||
pub body_burst_bytes: usize,
|
||||
/// Max time since the last qualifying body burst before eviction;
|
||||
/// backstopped by `body_timeout`. See `ConnLimits::body_stall_timeout`.
|
||||
pub body_stall_timeout: Duration,
|
||||
/// Per-write budget for response bytes (the fixed head+body write, and
|
||||
/// each streamed chunk). See `ConnLimits::write_timeout`.
|
||||
pub write_timeout: Duration,
|
||||
@@ -97,6 +104,8 @@ impl Config {
|
||||
read_buf_size: 8 * 1024,
|
||||
head_timeout: Duration::from_secs(30),
|
||||
body_timeout: Duration::from_secs(300),
|
||||
body_burst_bytes: 4 * 1024,
|
||||
body_stall_timeout: Duration::from_secs(20),
|
||||
write_timeout: Duration::from_secs(30),
|
||||
max_body_bytes: 16 * 1024 * 1024,
|
||||
drain_timeout: Duration::from_secs(30),
|
||||
@@ -117,6 +126,8 @@ impl Config {
|
||||
keep_alive_timeout: self.keep_alive_timeout,
|
||||
head_timeout: self.head_timeout,
|
||||
body_timeout: self.body_timeout,
|
||||
body_burst_bytes: self.body_burst_bytes,
|
||||
body_stall_timeout: self.body_stall_timeout,
|
||||
write_timeout: self.write_timeout,
|
||||
max_frame_payload: self.max_frame_payload,
|
||||
max_message_bytes: self.max_message_bytes,
|
||||
|
||||
Reference in New Issue
Block a user