New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HttpConnectionHandler should send a GO_AWAY frame with PROTOCOL_ERROR… #12985
base: 4.1
Are you sure you want to change the base?
Conversation
… when it receives an unknown stream instead of resetting the stream. Motivation: When an HTTP/2 connection process an unnkown stream it should throw a GO_AWAY frame and close the connection as per RFC 7540. The current implementation instead sends a RST frame. Modifications: Change the HttpConnectionHandler behavior to throw an error instead of writing a rst frame, the error will trigger a GO_AWAY frame to be sent and then close the connection appropriately. Result: HttpConnectionHandler should send a GO_AWAY frame with PROTOCOL_ERROR when it receives an unknown stream and fix netty#12974
@vietj can you link the relevant section in the RFC ? Is this your interpretation of https://datatracker.ietf.org/doc/html/rfc7540#section-5.1:
|
The part I'm referring to in https://datatracker.ietf.org/doc/html/rfc7540#section-5.1.1 is:
|
@vietj got it... Can you add this as a comment to the code ? |
codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java
Show resolved
Hide resolved
…nnectionHandler.java
@vietj I added the comment |
/cc @ejona86 |
thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the fix. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @vietj!
@vietj can you check the test failure. This seems to be related:
|
@@ -146,7 +146,7 @@ public void teardown() throws Exception { | |||
} | |||
|
|||
@Test | |||
public void inflightFrameAfterStreamResetShouldNotMakeConnectionUnusable() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhh.... this is a really important case. It seems like this change is overzealous and confusing what the spec is saying.
From RFC 7540§5.1.1 (Note that the language is the same in RFC 9113§5.1.1):
The identifier of a newly established stream MUST be numerically
greater than all streams that the initiating endpoint has opened or
reserved. This governs streams that are opened using a HEADERS frame
and streams that are reserved using PUSH_PROMISE. An endpoint that
receives an unexpected stream identifier MUST respond with a
connection error (Section 5.4.1) of type PROTOCOL_ERROR.
That is talking about newly-established streams. The error checking above is not limiting itself to HEADERS and PUSH_PROMISES.
Also, Netty forgets about streams after it sends RST_STREAM, so there is a problem of accidentally considering trailers as creating a "new stream" even though they were racing.
So I'm arguing that this change is ignoring the RFC, specifically RFC 7540§5.4.2 (same language in RFC 9113):
A RST_STREAM is the last frame that an endpoint can send on a stream.
The peer that sends the RST_STREAM frame MUST be prepared to receive
any frames that were sent or enqueued for sending by the remote peer.
These frames can be ignored, except where they modify connection
state (such as the state maintained for header compression
(Section 4.3) or flow control).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, what I'm saying is that with the current "memory" of Netty, it is difficult to do both things at once, and we are currently handling it the better way. Handling the RST_STREAM race is something that happens in practice with conforming implementations. Handling the "creating new stream with lower ID" only happens with a broken remote implementation. To be fully compliant would increase memory usage, and that is an option, but is complicated and helps almost nobody.
We could get partially compliant by just checking this case for PUSH_PROMISE. HEADERS are hard because of HTTP 1xx responses and trailers. I don't see a way to reliably detect if HEADERS are for a now-closed stream or are creating a new stream, without remembering detailed closed stream states. But in any case, any checks along those lines should be in DefaultHttp2ConnectionHandler, not in onStreamError()
, because onStreamError()
clearly needs to handle cases like racy DATA frames received after a RST_STREAM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have a look today @normanmaurer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if I understand correctly @ejona86 you are saying that when a stream id is received and not actually known by the state machine (because it does not retain such information and because the client is allowed to increase the stream id sequence as its will), we don't know whether that stream actually ever existed or it existed but is now closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any update /cc @ejona86
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ping @ejona86 :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are saying that when a stream id is received and not actually known by the state machine (because it does not retain such information and because the client is allowed to increase the stream id sequence as its will), we don't know whether that stream actually ever existed or it existed but is now closed
Correct. And the code assumes it is the more likely case today, that the stream did exist.
Hi @vietj. Seems this PR fell through the cracks. |
… when it receives an unknown stream instead of resetting the stream.
Motivation:
When an HTTP/2 connection process an unnkown stream it should throw a GO_AWAY frame and close the connection as per RFC 7540. The current implementation instead sends a RST frame.
Modifications:
Change the HttpConnectionHandler behavior to throw an error instead of writing a rst frame, the error will trigger a GO_AWAY frame to be sent and then close the connection appropriately.
Result:
HttpConnectionHandler should send a GO_AWAY frame with PROTOCOL_ERROR when it receives an unknown stream and fix #12974