Skip to content
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

Open
wants to merge 2 commits into
base: 4.1
Choose a base branch
from

Conversation

vietj
Copy link
Contributor

@vietj vietj commented Nov 10, 2022

… 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

… 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
@normanmaurer
Copy link
Member

@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:

...
In the absence of more specific guidance elsewhere in this document,
   implementations SHOULD treat the receipt of a frame that is not
   expressly permitted in the description of a state as a connection
   error ([Section 5.4.1](https://datatracker.ietf.org/doc/html/rfc7540#section-5.4.1)) of type PROTOCOL_ERROR.  Note that PRIORITY can
   be sent and received in any stream state.  Frames of unknown types
   are ignored.

@vietj
Copy link
Contributor Author

vietj commented Nov 11, 2022

The part I'm referring to in https://datatracker.ietf.org/doc/html/rfc7540#section-5.1.1 is:

An endpoint that receives an unexpected stream identifier MUST respond with a connection error ([Section 5.4.1](https://datatracker.ietf.org/doc/html/rfc7540#section-5.4.1)) of type PROTOCOL_ERROR.

@normanmaurer
Copy link
Member

@vietj got it... Can you add this as a comment to the code ?

@normanmaurer
Copy link
Member

@vietj I added the comment

@normanmaurer normanmaurer added this to the 4.1.86.Final milestone Nov 11, 2022
@normanmaurer
Copy link
Member

/cc @ejona86

@vietj
Copy link
Contributor Author

vietj commented Nov 11, 2022

@vietj I added the comment

thanks

Copy link
Contributor

@hyperxpro hyperxpro left a 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. :)

Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @vietj!

@normanmaurer
Copy link
Member

@vietj can you check the test failure. This seems to be related:

Http2ConnectionRoundtripTest.headersUsingHigherValuedStreamIdPreventsUsingLowerStreamId

@@ -146,7 +146,7 @@ public void teardown() throws Exception {
}

@Test
public void inflightFrameAfterStreamResetShouldNotMakeConnectionUnusable() throws Exception {
Copy link
Member

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).

Copy link
Member

@ejona86 ejona86 Nov 15, 2022

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vietj please check the comments of @ejona86

Copy link
Contributor Author

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

Copy link
Contributor Author

@vietj vietj Nov 18, 2022

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any update /cc @ejona86

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping @ejona86 :-)

Copy link
Member

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.

@rsvoboda
Copy link

rsvoboda commented Dec 9, 2022

Hi @vietj. Seems this PR fell through the cracks.

@vietj
Copy link
Contributor Author

vietj commented Dec 15, 2022

Hi @vietj. Seems this PR fell through the cracks.

it is not clear whether it can be fixed in Netty, I asked a clarification to @ejona86

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect behavior when an HTTP/2 server receives an unknown stream id
6 participants