You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Proposal: enable an HTTP/2 header block read timeout
Currently there is a read timeout for http1 in Hyper: http1::Builder::header_read_timeout,
and no such http/2 equivalent exists. This proposal aims on adding this equivalent to hyper enabled via h2 and is based on the following issues:
Hyper definitely cares about DOS attacks AFAIK. I don't know why this one seems low(er) priority. But anyways, the more I think about it the less I see why axum would try to do anything here, since it can be solved more easily in hyper.
Because of that, I'll close this. I think it would be best for the discussion to continue on the hyper issue.
One of the main concerns that this proposal and the issues highlighted above bring to light is that a server cannot authenticate a request until its headers are decoded. So a client can retain an unauthenticated connection by sending a valid header frame or header block indefinitely slowly which becomes the HTTP/2 form of a Slowloris attack.
The LengthDelimitedCodec usage in h2 buffers input until a complete physical frame is available. FramedRead::decode then assembles a logical header/field block (in h2 referred as header block, see https://github.com/hyperium/h2/blob/master/src/frame/headers.rs#L19-L35) across HEADERS and CONTINUATION frames until END_HEADERS. The existing limits bound: frame size, header-list size, and continuation count and not how long the peer may take to send them. Because a continuation sequence cannot be interleaved with frames from other streams, a single incomplete header block stalls parsing for the entire connection!
The HeaderBlockId token has the following semantics:
Some(id) means h2 may be reading an inbound header block. The ID starts provisionally with the frame prefix, remains active across CONTINUATION frames, and clears once END_HEADERS is fully decoded or the frame is identified as non-header.
Each new header block receives a different opaque ID, allowing Hyper to distinguish the blocks.
An ID is used instead of a boolean because h2 may complete one block and begin another. a changed ID tells Hyper to start a new deadline. FramedRead tracks the ID as it buffers input and partial header block. server::Connection exposes it to Hyper, which manages and enforces the timer/deadline.
Store the current header block ID and its deadline in Hyper’s serving state. The current loop waits directly on poll_accept. instead, after each call:
No ID: cancel the deadline
Same ID: keep the existing deadline
New ID: start a new deadline
Previous deadline
h2 returns
Hyper action
None
Some(A)
Start A’s deadline
A
Some(A)
Keep A’s deadline
A
None
A completed cancel the deadline
A
Some(B)
A completed and B started, start B’s deadline
The deadline is fixed i.e. receiving more header bytes does not reset it. If the header block completes during the poll, cancel its deadline before checking expiry. On expiry we terminate the entire connection and return an error for which Error::is_timeout() is true. The timeout closes the entire connection because an unfinished header block prevents h2 from processing any later frames on it. Hyper should make a best effort attempt to notify the client with GOAWAY(ENHANCE_YOUR_CALM) and then close immediately.
Notes
This timeout covers inbound http/2 request headers. Unlike http/1’s timeout, it does not cover idle connections, TLS or http/2 setup etc - those require separate timeouts. Thus, this does not cover the no request case discussed in How to avoid Slowloris DoS Attack? tokio-rs/axum#2741.
Other interesting implementations I looked into: NGINX, Envoy, Apache Traffic Server, haproxy and nghttpx which all provide a form of timeout for the case described.
I used GPT 5.6 to help me understand the code and debate/brainstorm my thought process through adversarial review and Cursor Grok 4.5 to help me navigate and mind map the codebase.
Thanks for all the work on h2! Please correct me if I went wrong somewhere in my understanding, I would be happy to make any changes/collaborate and drive this myself inclduing relevant PRs for hyper and h2.
Proposal: enable an HTTP/2 header block read timeout
Currently there is a read timeout for http1 in Hyper:
http1::Builder::header_read_timeout,and no such http/2 equivalent exists. This proposal aims on adding this equivalent to hyper enabled via h2 and is based on the following issues:
Relevant: How to avoid Slowloris DoS Attack? tokio-rs/axum#2741
Relevant: Server idle/read/write timeout support hyper#1628
Direct: Is there a way to configure h2 client_header_timeout? #823 (This is the intended proposal for this issue, as asked here)
One of the main concerns that this proposal and the issues highlighted above bring to light is that a server cannot authenticate a request until its headers are decoded. So a client can retain an unauthenticated connection by sending a valid header frame or header block indefinitely slowly which becomes the HTTP/2 form of a Slowloris attack.
Refer:
The
LengthDelimitedCodecusage in h2 buffers input until a complete physical frame is available.FramedRead::decodethen assembles a logical header/field block (in h2 referred as header block, see https://github.com/hyperium/h2/blob/master/src/frame/headers.rs#L19-L35) acrossHEADERSandCONTINUATIONframes untilEND_HEADERS. The existing limits bound: frame size, header-list size, and continuation count and not how long the peer may take to send them. Because a continuation sequence cannot be interleaved with frames from other streams, a single incomplete header block stalls parsing for the entire connection!Proposal
sleep()and check on active streams, and exposing relevant state for it in h2 as done in Try to support Keepalive Timeout, expose 'streams check' forConnection#838initial_max_send_streamsto 100 #731 (comment), the defaults are better selected by Hyper than by the protocol crate.Given the above, I propose to have h2 expose the protocol progress needed to enforce a timeout. This can be something like:
The
HeaderBlockIdtoken has the following semantics:Some(id)means h2 may be reading an inbound header block. The ID starts provisionally with the frame prefix, remains active acrossCONTINUATIONframes, and clears onceEND_HEADERSis fully decoded or the frame is identified as non-header.Each new header block receives a different opaque ID, allowing Hyper to distinguish the blocks.
An ID is used instead of a boolean because h2 may complete one block and begin another. a changed ID tells Hyper to start a new deadline.
FramedReadtracks the ID as it buffers input and partial header block.server::Connectionexposes it to Hyper, which manages and enforces the timer/deadline.Hyper integration
Hyper exposes a
http2::Builder::timer. And we can add an HTTP/1 style option:Store the current header block ID and its deadline in Hyper’s serving state. The current loop waits directly on poll_accept. instead, after each call:
NoneSome(A)ASome(A)ANoneASome(B)The deadline is fixed i.e. receiving more header bytes does not reset it. If the header block completes during the poll, cancel its deadline before checking expiry. On expiry we terminate the entire connection and return an error for which
Error::is_timeout()is true. The timeout closes the entire connection because an unfinished header block prevents h2 from processing any later frames on it. Hyper should make a best effort attempt to notify the client withGOAWAY(ENHANCE_YOUR_CALM)and then close immediately.Notes
This timeout covers inbound http/2 request headers. Unlike http/1’s timeout, it does not cover idle connections, TLS or http/2 setup etc - those require separate timeouts. Thus, this does not cover the no request case discussed in How to avoid Slowloris DoS Attack? tokio-rs/axum#2741.
Other interesting implementations I looked into: NGINX, Envoy, Apache Traffic Server, haproxy and nghttpx which all provide a form of timeout for the case described.
I used GPT 5.6 to help me understand the code and debate/brainstorm my thought process through adversarial review and Cursor Grok 4.5 to help me navigate and mind map the codebase.
Thanks for all the work on h2! Please correct me if I went wrong somewhere in my understanding, I would be happy to make any changes/collaborate and drive this myself inclduing relevant PRs for hyper and h2.