Skip to content

Commit 988034b

Browse files
lundibundiaddaleax
authored andcommitted
http2: make maximum tolerated rejected streams configurable
PR-URL: #30534 Fixes: #30505 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: David Carlier <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 74f6bc7 commit 988034b

File tree

6 files changed

+70
-2
lines changed

6 files changed

+70
-2
lines changed

doc/api/http2.md

+18
Original file line numberDiff line numberDiff line change
@@ -1941,6 +1941,9 @@ error will be thrown.
19411941
<!-- YAML
19421942
added: v8.4.0
19431943
changes:
1944+
- version: REPLACEME
1945+
pr-url: https://github.com/nodejs/node/pull/30534
1946+
description: Added `maxSessionRejectedStreams` option with a default of 100.
19441947
- version: REPLACEME
19451948
pr-url: https://github.com/nodejs/node/pull/30534
19461949
description: Added `maxSessionInvalidFrames` option with a default of 1000.
@@ -2007,6 +2010,12 @@ changes:
20072010
* `maxSessionInvalidFrames` {integer} Sets the maximum number of invalid
20082011
frames that will be tolerated before the session is closed.
20092012
**Default:** `1000`.
2013+
* `maxSessionRejectedStreams` {integer} Sets the maximum number of rejected
2014+
upon creation streams that will be tolerated before the session is closed.
2015+
Each rejection is associated with an `NGHTTP2_ENHANCE_YOUR_CALM`
2016+
error that should tell the peer to not open any more streams, continuing
2017+
to open streams is therefore regarded as a sign of a misbehaving peer.
2018+
**Default:** `100`.
20102019
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
20112020
remote peer upon connection.
20122021
* `Http1IncomingMessage` {http.IncomingMessage} Specifies the
@@ -2059,6 +2068,9 @@ server.listen(80);
20592068
<!-- YAML
20602069
added: v8.4.0
20612070
changes:
2071+
- version: REPLACEME
2072+
pr-url: https://github.com/nodejs/node/pull/30534
2073+
description: Added `maxSessionRejectedStreams` option with a default of 100.
20622074
- version: REPLACEME
20632075
pr-url: https://github.com/nodejs/node/pull/30534
20642076
description: Added `maxSessionInvalidFrames` option with a default of 1000.
@@ -2125,6 +2137,12 @@ changes:
21252137
* `maxSessionInvalidFrames` {integer} Sets the maximum number of invalid
21262138
frames that will be tolerated before the session is closed.
21272139
**Default:** `1000`.
2140+
* `maxSessionRejectedStreams` {integer} Sets the maximum number of rejected
2141+
upon creation streams that will be tolerated before the session is closed.
2142+
Each rejection is associated with an `NGHTTP2_ENHANCE_YOUR_CALM`
2143+
error that should tell the peer to not open any more streams, continuing
2144+
to open streams is therefore regarded as a sign of a misbehaving peer.
2145+
**Default:** `100`.
21282146
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
21292147
remote peer upon connection.
21302148
* ...: Any [`tls.createServer()`][] options can be provided. For

lib/internal/http2/core.js

+14
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ const {
211211
kSessionPriorityListenerCount,
212212
kSessionFrameErrorListenerCount,
213213
kSessionMaxInvalidFrames,
214+
kSessionMaxRejectedStreams,
214215
kSessionUint8FieldCount,
215216
kSessionHasRemoteSettingsListeners,
216217
kSessionRemoteSettingsIsUpToDate,
@@ -955,6 +956,12 @@ function setupHandle(socket, type, options) {
955956
uint32[0] = options.maxSessionInvalidFrames;
956957
}
957958

959+
if (isUint32(options.maxSessionRejectedStreams)) {
960+
const uint32 = new Uint32Array(
961+
this[kNativeFields].buffer, kSessionMaxRejectedStreams, 1);
962+
uint32[0] = options.maxSessionRejectedStreams;
963+
}
964+
958965
const settings = typeof options.settings === 'object' ?
959966
options.settings : {};
960967

@@ -2788,6 +2795,13 @@ function initializeOptions(options) {
27882795
if (options.maxSessionInvalidFrames !== undefined)
27892796
validateUint32(options.maxSessionInvalidFrames, 'maxSessionInvalidFrames');
27902797

2798+
if (options.maxSessionRejectedStreams !== undefined) {
2799+
validateUint32(
2800+
options.maxSessionRejectedStreams,
2801+
'maxSessionRejectedStreams'
2802+
);
2803+
}
2804+
27912805
// Used only with allowHTTP1
27922806
options.Http1IncomingMessage = options.Http1IncomingMessage ||
27932807
http.IncomingMessage;

src/node_http2.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,8 @@ int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
920920
if (UNLIKELY(!session->CanAddStream() ||
921921
Http2Stream::New(session, id, frame->headers.cat) ==
922922
nullptr)) {
923-
if (session->rejected_stream_count_++ > 100)
923+
if (session->rejected_stream_count_++ >
924+
session->js_fields_.max_rejected_streams)
924925
return NGHTTP2_ERR_CALLBACK_FAILURE;
925926
// Too many concurrent streams being opened
926927
nghttp2_submit_rst_stream(**session, NGHTTP2_FLAG_NONE, id,
@@ -3062,6 +3063,7 @@ void Initialize(Local<Object> target,
30623063
NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
30633064
NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
30643065
NODE_DEFINE_CONSTANT(target, kSessionMaxInvalidFrames);
3066+
NODE_DEFINE_CONSTANT(target, kSessionMaxRejectedStreams);
30653067
NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);
30663068

30673069
NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);

src/node_http2.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ typedef struct {
678678
uint8_t priority_listener_count;
679679
uint8_t frame_error_listener_count;
680680
uint32_t max_invalid_frames = 1000;
681+
uint32_t max_rejected_streams = 100;
681682
} SessionJSFields;
682683

683684
// Indices for js_fields_, which serves as a way to communicate data with JS
@@ -691,6 +692,7 @@ enum SessionUint8Fields {
691692
kSessionFrameErrorListenerCount =
692693
offsetof(SessionJSFields, frame_error_listener_count),
693694
kSessionMaxInvalidFrames = offsetof(SessionJSFields, max_invalid_frames),
695+
kSessionMaxRejectedStreams = offsetof(SessionJSFields, max_rejected_streams),
694696
kSessionUint8FieldCount = sizeof(SessionJSFields)
695697
};
696698

@@ -1024,7 +1026,7 @@ class Http2Session : public AsyncWrap, public StreamListener {
10241026
// limit will result in the session being destroyed, as an indication of a
10251027
// misbehaving peer. This counter is reset once new streams are being
10261028
// accepted again.
1027-
int32_t rejected_stream_count_ = 0;
1029+
uint32_t rejected_stream_count_ = 0;
10281030
// Also use the invalid frame count as a measure for rejecting input frames.
10291031
uint32_t invalid_frame_count_ = 0;
10301032

test/parallel/test-http2-createsecureserver-options.js

+16
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ Object.entries({
5252
},
5353
},
5454
],
55+
maxSessionRejectedStreams: [
56+
{
57+
val: -1,
58+
err: {
59+
name: 'RangeError',
60+
code: 'ERR_OUT_OF_RANGE',
61+
},
62+
},
63+
{
64+
val: Number.NEGATIVE_INFINITY,
65+
err: {
66+
name: 'RangeError',
67+
code: 'ERR_OUT_OF_RANGE',
68+
},
69+
},
70+
],
5571
}).forEach(([opt, tests]) => {
5672
tests.forEach(({ val, err }) => {
5773
assert.throws(

test/parallel/test-http2-createserver-options.js

+16
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ Object.entries({
5252
},
5353
},
5454
],
55+
maxSessionRejectedStreams: [
56+
{
57+
val: -1,
58+
err: {
59+
name: 'RangeError',
60+
code: 'ERR_OUT_OF_RANGE',
61+
},
62+
},
63+
{
64+
val: Number.NEGATIVE_INFINITY,
65+
err: {
66+
name: 'RangeError',
67+
code: 'ERR_OUT_OF_RANGE',
68+
},
69+
},
70+
]
5571
}).forEach(([opt, tests]) => {
5672
tests.forEach(({ val, err }) => {
5773
assert.throws(

0 commit comments

Comments
 (0)