From e6ffe8384ebf1972faac9b031b9ff6182e79cfd6 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 25 Feb 2026 21:09:21 +0400 Subject: [PATCH] QUIC: Stateless Reset rate limiting. It uses a bloom filter to limit sending Stateless Reset packets no more than once per second in average for the given address. This allows to address resource asymmetry from precomputed packets, as well as to limit potential Stateless Reset exchange. --- src/event/quic/ngx_event_quic_output.c | 65 ++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 72119a8ea..f98c834a1 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -64,6 +64,7 @@ static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); +static ngx_int_t ngx_quic_stateless_reset_filter(ngx_connection_t *c); ngx_int_t @@ -823,10 +824,11 @@ ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { - u_char *token; - size_t len, max; - uint16_t rndbytes; - u_char buf[NGX_QUIC_MAX_SR_PACKET]; + u_char *token; + size_t len, max; + uint16_t rndbytes; + ngx_int_t rc; + u_char buf[NGX_QUIC_MAX_SR_PACKET]; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handle stateless reset output"); @@ -835,6 +837,11 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } + rc = ngx_quic_stateless_reset_filter(c); + if (rc != NGX_OK) { + return rc; + } + if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { len = pkt->len - 1; @@ -870,6 +877,56 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, } +static ngx_int_t +ngx_quic_stateless_reset_filter(ngx_connection_t *c) +{ + time_t now; + u_char salt; + ngx_uint_t i, n, m, hit; + u_char hash[20]; + + static time_t t; + static u_char rndbyte; + static uint8_t bitmap[65536]; + + now = ngx_time(); + + if (t != now) { + t = now; + + if (RAND_bytes(&rndbyte, 1) != 1) { + return NGX_ERROR; + } + + ngx_memzero(bitmap, sizeof(bitmap)); + } + + hit = 0; + + for (i = 0; i < 3; i++) { + salt = rndbyte + i; + + ngx_quic_address_hash(c->sockaddr, c->socklen, 0, &salt, 1, hash); + + n = hash[0] | hash[1] << 8; + m = 1 << hash[2] % 8; + + if (!(bitmap[n] & m)) { + bitmap[n] |= m; + + } else { + hit++; + } + } + + if (hit == 3) { + return NGX_DECLINED; + } + + return NGX_OK; +} + + ngx_int_t ngx_quic_send_cc(ngx_connection_t *c) {