]> 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)
committerSergey Kandaurov <s.kandaurov@f5.com>
Thu, 26 Feb 2026 14:05:07 +0000 (18:05 +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 72119a8ea0902b6134483a26bb08b183a9295d49..f98c834a1959397e7fa00c54889ada364c716232 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)
 {