-
Notifications
You must be signed in to change notification settings - Fork 30.2k
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
http2, tls: check content-length, fix RST and GOAWAY logic #53160
base: main
Are you sure you want to change the base?
Conversation
Review requested:
|
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.
Wow. congrats! It would really be better if the patch was landed, yes.
The linter and commit validator are failing, could you check? |
This comment was marked as resolved.
This comment was marked as resolved.
@jasnell could you reach out to the nghttp2 maintainers and ask about the status of that PR? I would sincerely hope we wouldn't have to maintain a floating patch. |
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.
Therefore the best change - although breaking - is to make it emit an error by default.
The reason why .destroy()
does not emit an error by default is that it's a Stream API. .destroy()
without error is supposed to be "safe" to call.
req.on('error', common.expectsError({ | ||
code: 'ERR_HTTP2_STREAM_ERROR', | ||
name: 'Error', | ||
message: 'Stream closed with error code NGHTTP2_PROTOCOL_ERROR' | ||
})); | ||
|
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 don't think this should be removed
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.
@mcollina This is a result of the fix for making .destroy()
not throw when sending RST. It throws only when receiving RST. This means that stream.close(1)
won't emit error. Are you okay with this change? By looking at the original code for close()
it looks like the author didn't mean to emit error on close()
.
Otherwise I would have to make stream.close()
set stream[kState].rstCode
and then call this.destroy(error)
.
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.
Actually my understanding was wrong. It shouldn't be removed indeed.
It should error via onStreamClose
(so pipeline fails etc.) but fails to do so because
node/lib/internal/http2/core.js
Line 2400 in 56dea47
this.destroy(); |
gets called first.
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'm not fluent in stream internals but setImmediate
seems to work here (in afterShutdown), so I went with that.
Looks like the main cause is that writable shutdown is delayed by single tick in order to attach the END_STREAM flag. It's a pity nghttp2 has no API to amend the last queued DATA frame.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as outdated.
This comment was marked as outdated.
Have you tried pinging nghttp2? |
This comment was marked as resolved.
This comment was marked as resolved.
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.
lgtm
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as off-topic.
This comment was marked as off-topic.
@mcollina Can you trigger CI again? Thanks 🙏 The lint errors and test failures on GitHub Actions seem to be unrelated to this PR. |
@@ -3326,7 +3324,7 @@ function socketOnClose() { | |||
|
|||
state.streams.forEach(closeStream); | |||
state.pendingStreams.forEach(closeStream); | |||
session.close(); |
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.
session.close()
(with this PR) calls session.destroy()
if state.streams
and state.pendingStreams
are empty. Therefore it could obstruct session[kMaybeDestroy](err)
. Now it just marks the session as closed, hopefully fixing Windows: test-http2-server-sessionerror.js
and test-http2-too-many-settings.js
that do not create any streams.
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.
Indeed it fixed that!
@mcollina Can you trigger CI again? Hopefully Windows passes this time 🙏🏼 |
Windows passes now.
IMO this PR is good to go. I'd prefer if this landed first in v23, then backport to v22 if necessary. |
@mhdawson can you help with this? |
Fixes #35209
Closes #35378
Time spent: 90h
Caution
These bug fixes are potentially
semver-major
!stream.close(NGHTTP2_CANCEL)
closing with0
akaNGHTTP2_NO_ERROR
.serverStream.destroy()
closed with0
, nowNGHTTP2_INTERNAL_ERROR
.clientStream.destroy()
closed with0
, nowNGHTTP2_CANCEL
.content-length
now throwsNGHTTP2_PROTOCOL_ERROR
as according to the spec.GOAWAY
(server -> client) being greeted withGOAWAY
(client -> server) as it's against the spec.NGHTTP2_CANCEL
on socket close, now respectsgoawayCode
anddestroyCode
. The default isNGHTTP2_INTERNAL_ERROR
for premature TCP FIN and TCP RST.stream.rstCode
being 0 while active - docs say it should be undefined.sessionCode
overrstCode
when destroying a stream with an error.NO_ERROR
if session receivedGOAWAY
withNO_ERROR
and remote closed connection.NO_ERROR
if session sentGOAWAY
withNO_ERROR
and destroyed.GOAWAY
preventingRST_STREAM
from being sent beforeGOAWAY
.nghttp2 correctly assumes that it should prevent
RST_STREAM
from being sent but incorretly applies it to all frames in a packet instead of frames defined afterGOAWAY
. This is not the only thing that nghttp2 does wrong:benchmark/http2/headers.js
callingclient.destroy()
(resulting in dropped requests).Now calls
client.close()
which closes gracefully.connectStreamSocket.destroy()
now closes withNGHTTP2_CONNECT_ERROR
.GOAWAY
onsession.close()
.Http2Session::Close()
and previous writes weren't finished yet.RST_STREAM
on idle streams (to reproduce 16. needs to be fixed).Sorry so many bugs are fixed in a single PR but it's impossible to fix one without fixing them all!
Best if nghttp2/nghttp2#1508 got merged before this, but it has been open for years 😢
Hence the patch for
nghttp2_session.c
/cc @jasnell