Skip to content

Commit d95080e

Browse files
committed
quic: add rate limiting docs
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode/Opus 4.6
1 parent 82546a4 commit d95080e

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

doc/api/quic.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,55 @@ Certificate compression ([RFC 8879][]) can also address this issue by
140140
compressing the certificate chain during the handshake. However, Node.js does
141141
not currently support TLS certificate compression.
142142

143+
### Rate limiting
144+
145+
QUIC endpoints include built-in rate limiting to protect against
146+
denial-of-service attacks. There are two layers of defense:
147+
148+
**Global rate limits** cap the total rate of stateless responses that the
149+
endpoint will send, regardless of the source address. These protect against
150+
floods from spoofed source IP addresses, where an attacker rotates through
151+
many fake source addresses to bypass per-host limits. Four types of stateless
152+
responses are independently rate-limited:
153+
154+
* **Retry packets** — sent to validate a client's address during connection
155+
setup. Configurable via [`endpointOptions.retryRate`][] and
156+
[`endpointOptions.retryBurst`][].
157+
* **Stateless reset packets** — sent when the endpoint receives a packet for an
158+
unknown session. Configurable via [`endpointOptions.statelessResetRate`][]
159+
and [`endpointOptions.statelessResetBurst`][].
160+
* **Version negotiation packets** — sent when a client uses an unsupported QUIC
161+
version. Configurable via [`endpointOptions.versionNegotiationRate`][] and
162+
[`endpointOptions.versionNegotiationBurst`][].
163+
* **Immediate connection close packets** — sent when the server is busy or a
164+
token is invalid. Configurable via [`endpointOptions.immediateCloseRate`][]
165+
and [`endpointOptions.immediateCloseBurst`][].
166+
167+
Each rate limit uses a token bucket: the endpoint can send up to the burst
168+
capacity instantly, and tokens refill at the configured rate per second. When
169+
the bucket is empty, additional responses of that type are silently dropped.
170+
The defaults (100 per second, burst of 200) are suitable for most deployments.
171+
172+
**Per-host session creation rate limits** cap how fast a single remote address
173+
can create new sessions. This is tracked per validated remote address and
174+
prevents a single client from churning through sessions (rapidly connecting and
175+
disconnecting) to consume server resources. Configurable via
176+
[`endpointOptions.sessionCreationRate`][] and
177+
[`endpointOptions.sessionCreationBurst`][]. The defaults (50 per second, burst
178+
of 100) are generous enough for legitimate traffic patterns. For benchmarking
179+
scenarios where traffic comes from a single source, increase these values.
180+
181+
In addition to rate limiting, the endpoint supports **concurrent connection
182+
limits** via `maxConnectionsPerHost` and `maxConnectionsTotal`, and a
183+
**busy mode** via [`endpoint.busy`][] that rejects all new connections.
184+
185+
Rate limiting activity can be monitored through the endpoint's statistics
186+
object. Each rate limiter has a corresponding counter
187+
(e.g., `endpoint.stats.retryRateLimited`,
188+
`endpoint.stats.sessionCreationRateLimited`) that tracks how many responses
189+
were dropped. A non-zero value indicates the rate limiter is actively
190+
protecting the endpoint.
191+
143192
### Applications
144193

145194
Every `QuicSession` is associated with a single application protocol, negotiated
@@ -4225,8 +4274,19 @@ throughput issues caused by flow control.
42254274
[`application.enableConnectProtocol`]: #sessionoptionsapplication
42264275
[`application.enableDatagrams`]: #sessionoptionsapplication
42274276
[`application.qpackMaxDTableCapacity`]: #sessionoptionsapplication
4277+
[`endpoint.busy`]: #endpointbusy
42284278
[`endpoint.maxConnectionsPerHost`]: #endpointmaxconnectionsperhost
42294279
[`endpoint.maxConnectionsTotal`]: #endpointmaxconnectionstotal
4280+
[`endpointOptions.immediateCloseBurst`]: #endpointoptionsimmediatecloseburst
4281+
[`endpointOptions.immediateCloseRate`]: #endpointoptionsimmediatecloserate
4282+
[`endpointOptions.retryBurst`]: #endpointoptionsretryburst
4283+
[`endpointOptions.retryRate`]: #endpointoptionsretryrate
4284+
[`endpointOptions.sessionCreationBurst`]: #endpointoptionssessioncreationburst
4285+
[`endpointOptions.sessionCreationRate`]: #endpointoptionssessioncreationrate
4286+
[`endpointOptions.statelessResetBurst`]: #endpointoptionsstatelessresetburst
4287+
[`endpointOptions.statelessResetRate`]: #endpointoptionsstatelessresetrate
4288+
[`endpointOptions.versionNegotiationBurst`]: #endpointoptionsversionnegotiationburst
4289+
[`endpointOptions.versionNegotiationRate`]: #endpointoptionsversionnegotiationrate
42304290
[`error.errorCode`]: #errorerrorcode
42314291
[`fs.promises.open(path, 'r')`]: fs.md#fspromisesopenpath-flags-mode
42324292
[`maxDatagramFrameSize`]: #transportparamsmaxdatagramframesize

0 commit comments

Comments
 (0)