]> git.kaiwu.me - nginx.git/commitdiff
QUIC: Stateless Reset rate limiting.
authorSergey Kandaurov <pluknet@nginx.com>
Wed, 25 Feb 2026 17:09:21 +0000 (21:09 +0400)
committerRoman Arutyunyan <arutyunyan.roman@gmail.com>
Tue, 24 Mar 2026 18:33:23 +0000 (22:33 +0400)
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

index e872b803cf0027dd6d4fd74b93a12b53b045f2f7..393bf939fe770be5457b43d79359404ec129b218 100644 (file)
@@ -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)
 {