git: 5f4f061728d8 - main - www/nginx-devel: update HTTPv3/QUIC patch to the recent commit

From: Sergey A. Osokin <osa_at_FreeBSD.org>
Date: Tue, 25 Jan 2022 13:59:58 UTC
The branch main has been updated by osa:

URL: https://cgit.FreeBSD.org/ports/commit/?id=5f4f061728d8515176cd51d569bec152a384ecdd

commit 5f4f061728d8515176cd51d569bec152a384ecdd
Author:     Sergey A. Osokin <osa@FreeBSD.org>
AuthorDate: 2022-01-25 13:59:22 +0000
Commit:     Sergey A. Osokin <osa@FreeBSD.org>
CommitDate: 2022-01-25 13:59:51 +0000

    www/nginx-devel: update HTTPv3/QUIC patch to the recent commit
    
    Bump PORTREVISION.
---
 www/nginx-devel/Makefile                 |   2 +-
 www/nginx-devel/files/extra-patch-httpv3 | 987 +++++++++++++------------------
 2 files changed, 407 insertions(+), 582 deletions(-)

diff --git a/www/nginx-devel/Makefile b/www/nginx-devel/Makefile
index 2ce4b8b4fce2..6d4f2874fa9a 100644
--- a/www/nginx-devel/Makefile
+++ b/www/nginx-devel/Makefile
@@ -2,7 +2,7 @@
 
 PORTNAME?=	nginx
 PORTVERSION=	1.21.5
-PORTREVISION=	10
+PORTREVISION=	11
 CATEGORIES=	www
 MASTER_SITES=	https://nginx.org/download/ \
 		LOCAL/osa
diff --git a/www/nginx-devel/files/extra-patch-httpv3 b/www/nginx-devel/files/extra-patch-httpv3
index 4c5a4cae03df..9f0ab11e7c7c 100644
--- a/www/nginx-devel/files/extra-patch-httpv3
+++ b/www/nginx-devel/files/extra-patch-httpv3
@@ -1929,7 +1929,7 @@ diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c
 new file mode 100644
 --- /dev/null
 +++ b/src/event/quic/ngx_event_quic.c
-@@ -0,0 +1,1489 @@
+@@ -0,0 +1,1491 @@
 +
 +/*
 + * Copyright (C) Nginx, Inc.
@@ -2063,8 +2063,8 @@ new file mode 100644
 +
 +    qc = ngx_quic_get_connection(c);
 +
-+    scid.data = qc->socket->cid->id;
-+    scid.len = qc->socket->cid->len;
++    scid.data = qc->path->cid->id;
++    scid.len = qc->path->cid->len;
 +
 +    if (scid.len != ctp->initial_scid.len
 +        || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0)
@@ -2305,7 +2305,7 @@ new file mode 100644
 +    {
 +        cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
 +
-+        if (cid->seqnum == 0 || cid->refcnt == 0) {
++        if (cid->seqnum == 0 || !cid->used) {
 +            /*
 +             * No stateless reset token in initial connection id.
 +             * Don't accept a token from an unused connection id.
@@ -2605,10 +2605,12 @@ new file mode 100644
 +    u_char                 *p, *start;
 +    ngx_int_t               rc;
 +    ngx_uint_t              good;
++    ngx_quic_path_t        *path;
 +    ngx_quic_header_t       pkt;
 +    ngx_quic_connection_t  *qc;
 +
 +    good = 0;
++    path = NULL;
 +
 +    size = b->last - b->pos;
 +
@@ -2622,6 +2624,7 @@ new file mode 100644
 +        pkt.len = b->last - p;
 +        pkt.log = c->log;
 +        pkt.first = (p == start) ? 1 : 0;
++        pkt.path = path;
 +        pkt.flags = p[0];
 +        pkt.raw->pos++;
 +
@@ -2652,6 +2655,8 @@ new file mode 100644
 +            good = 1;
 +        }
 +
++        path = pkt.path; /* preserve packet path from 1st packet */
++
 +        /* NGX_OK || NGX_DECLINED */
 +
 +        /*
@@ -2757,14 +2762,15 @@ new file mode 100644
 +            }
 +
 +            if (pkt->first) {
-+                if (ngx_quic_find_path(c, c->udp->dgram->sockaddr,
-+                                       c->udp->dgram->socklen)
-+                    == NULL)
++                if (ngx_cmp_sockaddr(c->udp->dgram->sockaddr,
++                                     c->udp->dgram->socklen,
++                                     qc->path->sockaddr, qc->path->socklen, 1)
++                    != NGX_OK)
 +                {
 +                    /* packet comes from unknown path, possibly migration */
 +                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
 +                                  "quic too early migration attempt");
-+                    return NGX_DECLINED;
++                    return NGX_DONE;
 +                }
 +            }
 +
@@ -2923,9 +2929,12 @@ new file mode 100644
 +
 +    pkt->decrypted = 1;
 +
-+    if (pkt->first) {
-+        if (ngx_quic_update_paths(c, pkt) != NGX_OK) {
-+            return NGX_ERROR;
++    c->log->action = "handling decrypted packet";
++
++    if (pkt->path == NULL) {
++        rc = ngx_quic_set_path(c, pkt);
++        if (rc != NGX_OK) {
++            return rc;
 +        }
 +    }
 +
@@ -2944,9 +2953,10 @@ new file mode 100644
 +         */
 +        ngx_quic_discard_ctx(c, ssl_encryption_initial);
 +
-+        if (qc->socket->path->state != NGX_QUIC_PATH_VALIDATED) {
-+            qc->socket->path->state = NGX_QUIC_PATH_VALIDATED;
-+            qc->socket->path->limited = 0;
++        if (!qc->path->validated) {
++            qc->path->validated = 1;
++            qc->path->limited = 0;
++            ngx_quic_path_dbg(c, "in handshake", qc->path);
 +            ngx_post_event(&qc->push, &ngx_posted_events);
 +        }
 +    }
@@ -3085,7 +3095,6 @@ new file mode 100644
 +    ngx_uint_t              do_close, nonprobing;
 +    ngx_chain_t             chain;
 +    ngx_quic_frame_t        frame;
-+    ngx_quic_socket_t      *qsock;
 +    ngx_quic_connection_t  *qc;
 +
 +    qc = ngx_quic_get_connection(c);
@@ -3267,7 +3276,8 @@ new file mode 100644
 +
 +        case NGX_QUIC_FT_PATH_CHALLENGE:
 +
-+            if (ngx_quic_handle_path_challenge_frame(c, &frame.u.path_challenge)
++            if (ngx_quic_handle_path_challenge_frame(c, pkt,
++                                                     &frame.u.path_challenge)
 +                != NGX_OK)
 +            {
 +                return NGX_ERROR;
@@ -3326,26 +3336,18 @@ new file mode 100644
 +        ngx_quic_close_connection(c, NGX_OK);
 +    }
 +
-+    qsock = ngx_quic_get_socket(c);
-+
-+    if (qsock != qc->socket) {
++    if (pkt->path != qc->path && nonprobing) {
 +
-+        if (qsock->path != qc->socket->path && nonprobing) {
-+            /*
-+             * RFC 9000, 9.2.  Initiating Connection Migration
-+             *
-+             * An endpoint can migrate a connection to a new local
-+             * address by sending packets containing non-probing frames
-+             * from that address.
-+             */
-+            if (ngx_quic_handle_migration(c, pkt) != NGX_OK) {
-+                return NGX_ERROR;
-+            }
-+        }
 +        /*
-+         * else: packet arrived via non-default socket;
-+         *       no reason to change active path
++         * RFC 9000, 9.2.  Initiating Connection Migration
++         *
++         * An endpoint can migrate a connection to a new local
++         * address by sending packets containing non-probing frames
++         * from that address.
 +         */
++        if (ngx_quic_handle_migration(c, pkt) != NGX_OK) {
++            return NGX_ERROR;
++        }
 +    }
 +
 +    if (ngx_quic_ack_packet(c, pkt) != NGX_OK) {
@@ -3423,7 +3425,7 @@ diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h
 new file mode 100644
 --- /dev/null
 +++ b/src/event/quic/ngx_event_quic.h
-@@ -0,0 +1,87 @@
+@@ -0,0 +1,88 @@
 +
 +/*
 + * Copyright (C) Nginx, Inc.
@@ -3466,6 +3468,7 @@ new file mode 100644
 +    size_t                     stream_buffer_size;
 +    ngx_uint_t                 max_concurrent_streams_bidi;
 +    ngx_uint_t                 max_concurrent_streams_uni;
++    ngx_uint_t                 active_connection_id_limit;
 +    ngx_int_t                  stream_close_code;
 +    ngx_int_t                  stream_reject_code_uni;
 +    ngx_int_t                  stream_reject_code_bidi;
@@ -5500,7 +5503,7 @@ diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_eve
 new file mode 100644
 --- /dev/null
 +++ b/src/event/quic/ngx_event_quic_connection.h
-@@ -0,0 +1,274 @@
+@@ -0,0 +1,272 @@
 +/*
 + * Copyright (C) Nginx, Inc.
 + */
@@ -5572,7 +5575,7 @@ new file mode 100644
 +    size_t                            len;
 +    u_char                            id[NGX_QUIC_CID_LEN_MAX];
 +    u_char                            sr_token[NGX_QUIC_SR_TOKEN_LEN];
-+    ngx_uint_t                        refcnt;
++    ngx_uint_t                        used;  /* unsigned  used:1; */
 +};
 +
 +
@@ -5586,20 +5589,22 @@ new file mode 100644
 +struct ngx_quic_path_s {
 +    ngx_queue_t                       queue;
 +    struct sockaddr                  *sockaddr;
++    ngx_sockaddr_t                    sa;
 +    socklen_t                         socklen;
-+    ngx_uint_t                        state;
-+    ngx_uint_t                        limited; /* unsigned  limited:1; */
++    ngx_quic_client_id_t             *cid;
 +    ngx_msec_t                        expires;
-+    ngx_msec_t                        last_seen;
 +    ngx_uint_t                        tries;
++    ngx_uint_t                        tag;
 +    off_t                             sent;
 +    off_t                             received;
 +    u_char                            challenge1[8];
 +    u_char                            challenge2[8];
-+    ngx_uint_t                        refcnt;
 +    uint64_t                          seqnum;
 +    ngx_str_t                         addr_text;
 +    u_char                            text[NGX_SOCKADDR_STRLEN];
++    unsigned                          validated:1;
++    unsigned                          validating:1;
++    unsigned                          limited:1;
 +};
 +
 +
@@ -5607,11 +5612,8 @@ new file mode 100644
 +    ngx_udp_connection_t              udp;
 +    ngx_quic_connection_t            *quic;
 +    ngx_queue_t                       queue;
-+
 +    ngx_quic_server_id_t              sid;
-+
-+    ngx_quic_path_t                  *path;
-+    ngx_quic_client_id_t             *cid;
++    ngx_uint_t                        used; /* unsigned  used:1; */
 +};
 +
 +
@@ -5687,8 +5689,7 @@ new file mode 100644
 +struct ngx_quic_connection_s {
 +    uint32_t                          version;
 +
-+    ngx_quic_socket_t                *socket;
-+    ngx_quic_socket_t                *backup;
++    ngx_quic_path_t                  *path;
 +
 +    ngx_queue_t                       sockets;
 +    ngx_queue_t                       paths;
@@ -5779,7 +5780,7 @@ diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_q
 new file mode 100644
 --- /dev/null
 +++ b/src/event/quic/ngx_event_quic_connid.c
-@@ -0,0 +1,613 @@
+@@ -0,0 +1,502 @@
 +
 +/*
 + * Copyright (C) Nginx, Inc.
@@ -5797,13 +5798,10 @@ new file mode 100644
 +#if (NGX_QUIC_BPF)
 +static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id);
 +#endif
-+static ngx_int_t ngx_quic_send_retire_connection_id(ngx_connection_t *c,
-+    uint64_t seqnum);
-+
++static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c,
++    ngx_quic_client_id_t *cid);
 +static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c,
 +    ngx_quic_connection_t *qc);
-+static ngx_int_t ngx_quic_replace_retired_client_id(ngx_connection_t *c,
-+    ngx_quic_client_id_t *retired_cid);
 +static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c,
 +    ngx_quic_server_id_t *sid);
 +
@@ -5859,9 +5857,9 @@ new file mode 100644
 +ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
 +    ngx_quic_new_conn_id_frame_t *f)
 +{
-+    uint64_t                seq;
 +    ngx_str_t               id;
 +    ngx_queue_t            *q;
++    ngx_quic_frame_t       *frame;
 +    ngx_quic_client_id_t   *cid, *item;
 +    ngx_quic_connection_t  *qc;
 +
@@ -5879,10 +5877,17 @@ new file mode 100644
 +         *  done so for that sequence number.
 +         */
 +
-+        if (ngx_quic_send_retire_connection_id(c, f->seqnum) != NGX_OK) {
++        frame = ngx_quic_alloc_frame(c);
++        if (frame == NULL) {
 +            return NGX_ERROR;
 +        }
 +
++        frame->level = ssl_encryption_application;
++        frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
++        frame->u.retire_cid.sequence_number = f->seqnum;
++
++        ngx_quic_queue_frame(qc, frame);
++
 +        goto retire;
 +    }
 +
@@ -5955,20 +5960,7 @@ new file mode 100644
 +            continue;
 +        }
 +
-+        /* this connection id must be retired */
-+        seq = cid->seqnum;
-+
-+        if (cid->refcnt) {
-+            /* we are going to retire client id which is in use */
-+            if (ngx_quic_replace_retired_client_id(c, cid) != NGX_OK) {
-+                return NGX_ERROR;
-+            }
-+
-+        } else {
-+            ngx_quic_unref_client_id(c, cid);
-+        }
-+
-+        if (ngx_quic_send_retire_connection_id(c, seq) != NGX_OK) {
++        if (ngx_quic_retire_client_id(c, cid) != NGX_OK) {
 +            return NGX_ERROR;
 +        }
 +    }
@@ -5995,25 +5987,47 @@ new file mode 100644
 +
 +
 +static ngx_int_t
-+ngx_quic_send_retire_connection_id(ngx_connection_t *c, uint64_t seqnum)
++ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
 +{
-+    ngx_quic_frame_t       *frame;
++    ngx_queue_t            *q;
++    ngx_quic_path_t        *path;
++    ngx_quic_client_id_t   *new_cid;
 +    ngx_quic_connection_t  *qc;
 +
 +    qc = ngx_quic_get_connection(c);
 +
-+    frame = ngx_quic_alloc_frame(c);
-+    if (frame == NULL) {
-+        return NGX_ERROR;
++    if (!cid->used) {
++        return ngx_quic_free_client_id(c, cid);
 +    }
 +
-+    frame->level = ssl_encryption_application;
-+    frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
-+    frame->u.retire_cid.sequence_number = seqnum;
++    /* we are going to retire client id which is in use */
 +
-+    ngx_quic_queue_frame(qc, frame);
++    q = ngx_queue_head(&qc->paths);
 +
-+    /* we are no longer going to use this client id */
++    while (q != ngx_queue_sentinel(&qc->paths)) {
++
++        path = ngx_queue_data(q, ngx_quic_path_t, queue);
++        q = ngx_queue_next(q);
++
++        if (path->cid != cid) {
++            continue;
++        }
++
++        if (path == qc->path) {
++            /* this is the active path: update it with new CID */
++            new_cid = ngx_quic_next_client_id(c);
++            if (new_cid == NULL) {
++                return NGX_ERROR;
++            }
++
++            qc->path->cid = new_cid;
++            new_cid->used = 1;
++
++            return ngx_quic_free_client_id(c, cid);
++        }
++
++        return ngx_quic_free_path(c, path);
++    }
 +
 +    return NGX_OK;
 +}
@@ -6100,7 +6114,7 @@ new file mode 100644
 +    {
 +        cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
 +
-+        if (cid->refcnt == 0) {
++        if (!cid->used) {
 +            return cid;
 +        }
 +    }
@@ -6109,42 +6123,11 @@ new file mode 100644
 +}
 +
 +
-+ngx_quic_client_id_t *
-+ngx_quic_used_client_id(ngx_connection_t *c, ngx_quic_path_t *path)
-+{
-+    ngx_queue_t            *q;
-+    ngx_quic_socket_t      *qsock;
-+    ngx_quic_connection_t  *qc;
-+
-+    qc = ngx_quic_get_connection(c);
-+
-+    /* best guess: cid used by active path is good for us */
-+    if (qc->socket->path == path) {
-+        return qc->socket->cid;
-+    }
-+
-+    for (q = ngx_queue_head(&qc->sockets);
-+         q != ngx_queue_sentinel(&qc->sockets);
-+         q = ngx_queue_next(q))
-+    {
-+        qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
-+
-+        if (qsock->path && qsock->path == path) {
-+            return qsock->cid;
-+        }
-+    }
-+
-+    return NULL;
-+}
-+
-+
 +ngx_int_t
 +ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
 +    ngx_quic_retire_cid_frame_t *f)
 +{
-+    ngx_quic_path_t        *path;
-+    ngx_quic_socket_t      *qsock, **tmp;
-+    ngx_quic_client_id_t   *cid;
++    ngx_quic_socket_t      *qsock;
 +    ngx_quic_connection_t  *qc;
 +
 +    qc = ngx_quic_get_connection(c);
@@ -6190,76 +6173,14 @@ new file mode 100644
 +    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
 +                   "quic socket #%uL is retired", qsock->sid.seqnum);
 +
-+    /* check if client is willing to retire sid we have in use */
-+    if (qsock->sid.seqnum == qc->socket->sid.seqnum) {
-+        tmp = &qc->socket;
-+
-+    } else if (qc->backup && qsock->sid.seqnum == qc->backup->sid.seqnum) {
-+        tmp = &qc->backup;
-+
-+    } else {
-+
-+        ngx_quic_close_socket(c, qsock);
-+
-+        /* restore socket count up to a limit after deletion */
-+        if (ngx_quic_create_sockets(c) != NGX_OK) {
-+            return NGX_ERROR;
-+        }
-+
-+        return NGX_OK;
-+    }
-+
-+    /* preserve path/cid from retired socket */
-+    path = qsock->path;
-+    cid = qsock->cid;
-+
-+    /* ensure that closing_socket will not drop path and cid */
-+    path->refcnt++;
-+    cid->refcnt++;
-+
 +    ngx_quic_close_socket(c, qsock);
 +
-+    /* restore original values */
-+    path->refcnt--;
-+    cid->refcnt--;
-+
 +    /* restore socket count up to a limit after deletion */
 +    if (ngx_quic_create_sockets(c) != NGX_OK) {
-+        goto failed;
-+    }
-+
-+    qsock = ngx_quic_get_unconnected_socket(c);
-+    if (qsock == NULL) {
-+        qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
-+        qc->error_reason = "not enough server IDs";
-+        goto failed;
++        return NGX_ERROR;
 +    }
 +
-+    ngx_quic_connect(c, qsock, path, cid);
-+
-+    ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+                   "quic %s socket is now #%uL:%uL:%uL (%s)",
-+                   (*tmp) == qc->socket ? "active" : "backup",
-+                   qsock->sid.seqnum, qsock->cid->seqnum,
-+                   qsock->path->seqnum,
-+                   ngx_quic_path_state_str(qsock->path));
-+
-+    /* restore active/backup pointer in quic connection */
-+    *tmp = qsock;
-+
 +    return NGX_OK;
-+
-+failed:
-+
-+    /*
-+     * socket was closed, path and cid were preserved artifically
-+     * to be reused, but it didn't happen, thus unref here
-+     */
-+
-+    ngx_quic_unref_path(c, path);
-+    ngx_quic_unref_client_id(c, cid);
-+
-+    return NGX_ERROR;
 +}
 +
 +
@@ -6334,70 +6255,39 @@ new file mode 100644
 +}
 +
 +
-+static ngx_int_t
-+ngx_quic_replace_retired_client_id(ngx_connection_t *c,
-+    ngx_quic_client_id_t *retired_cid)
++ngx_int_t
++ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
 +{
-+    ngx_queue_t            *q;
-+    ngx_quic_socket_t      *qsock;
-+    ngx_quic_client_id_t   *cid;
++    ngx_quic_frame_t       *frame;
 +    ngx_quic_connection_t  *qc;
 +
 +    qc = ngx_quic_get_connection(c);
 +
-+    for (q = ngx_queue_head(&qc->sockets);
-+         q != ngx_queue_sentinel(&qc->sockets);
-+         q = ngx_queue_next(q))
-+    {
-+        qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
-+
-+        if (qsock->cid == retired_cid) {
-+
-+            cid = ngx_quic_next_client_id(c);
-+            if (cid == NULL) {
-+                return NGX_ERROR;
-+            }
-+
-+            qsock->cid = cid;
-+            cid->refcnt++;
-+
-+            ngx_quic_unref_client_id(c, retired_cid);
-+
-+            if (retired_cid->refcnt == 0) {
-+                return NGX_OK;
-+            }
-+        }
++    frame = ngx_quic_alloc_frame(c);
++    if (frame == NULL) {
++        return NGX_ERROR;
 +    }
 +
-+    return NGX_OK;
-+}
-+
-+
-+void
-+ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
-+{
-+    ngx_quic_connection_t  *qc;
-+
-+    if (cid->refcnt) {
-+        cid->refcnt--;
-+    } /* else: unused client id */
++    frame->level = ssl_encryption_application;
++    frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
++    frame->u.retire_cid.sequence_number = cid->seqnum;
 +
-+    if (cid->refcnt) {
-+        return;
-+    }
++    ngx_quic_queue_frame(qc, frame);
 +
-+    qc = ngx_quic_get_connection(c);
++    /* we are no longer going to use this client id */
 +
 +    ngx_queue_remove(&cid->queue);
 +    ngx_queue_insert_head(&qc->free_client_ids, &cid->queue);
 +
 +    qc->nclient_ids--;
++
++    return NGX_OK;
 +}
 diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h
 new file mode 100644
 --- /dev/null
 +++ b/src/event/quic/ngx_event_quic_connid.h
-@@ -0,0 +1,30 @@
+@@ -0,0 +1,29 @@
 +
 +/*
 + * Copyright (C) Nginx, Inc.
@@ -6423,16 +6313,15 @@ new file mode 100644
 +ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c,
 +    ngx_str_t *id, uint64_t seqnum, u_char *token);
 +ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c);
-+ngx_quic_client_id_t *ngx_quic_used_client_id(ngx_connection_t *c,
-+    ngx_quic_path_t *path);
-+void ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid);
++ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c,
++    ngx_quic_client_id_t *cid);
 +
 +#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */
 diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c
 new file mode 100644
 --- /dev/null
 +++ b/src/event/quic/ngx_event_quic_frames.c
-@@ -0,0 +1,811 @@
+@@ -0,0 +1,813 @@
 +
 +/*
 + * Copyright (C) Nginx, Inc.
@@ -6971,14 +6860,16 @@ new file mode 100644
 +            continue;
 +        }
 +
-+        for (p = b->pos + offset; p != b->last && in; /* void */ ) {
++        p = b->pos + offset;
++
++        while (in) {
 +
 +            if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) {
 +                in = in->next;
 +                continue;
 +            }
 +
-+            if (limit == 0) {
++            if (p == b->last || limit == 0) {
 +                break;
 +            }
 +
@@ -7295,7 +7186,7 @@ diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_even
 new file mode 100644
 --- /dev/null
 +++ b/src/event/quic/ngx_event_quic_migration.c
-@@ -0,0 +1,689 @@
+@@ -0,0 +1,672 @@
 +
 +/*
 + * Copyright (C) Nginx, Inc.
@@ -7314,17 +7205,14 @@ new file mode 100644
 +    ngx_quic_path_t *path);
 +static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
 +    ngx_quic_path_t *path);
-+static ngx_int_t ngx_quic_path_restore(ngx_connection_t *c);
-+static ngx_quic_path_t *ngx_quic_alloc_path(ngx_connection_t *c);
++static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
 +
 +
 +ngx_int_t
 +ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
-+    ngx_quic_path_challenge_frame_t *f)
++    ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
 +{
-+    ngx_quic_path_t        *path;
 +    ngx_quic_frame_t        frame, *fp;
-+    ngx_quic_socket_t      *qsock;
 +    ngx_quic_connection_t  *qc;
 +
 +    qc = ngx_quic_get_connection(c);
@@ -7341,18 +7229,16 @@ new file mode 100644
 +     * A PATH_RESPONSE frame MUST be sent on the network path where the
 +     * PATH_CHALLENGE frame was received.
 +     */
-+    qsock = ngx_quic_get_socket(c);
-+    path = qsock->path;
 +
 +    /*
 +     * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame
 +     * to at least the smallest allowed maximum datagram size of 1200 bytes.
 +     */
-+    if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
++    if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) {
 +        return NGX_ERROR;
 +    }
 +
-+    if (qsock == qc->socket) {
++    if (pkt->path == qc->path) {
 +        /*
 +         * RFC 9000, 9.3.3.  Off-Path Packet Forwarding
 +         *
@@ -7399,7 +7285,7 @@ new file mode 100644
 +    {
 +        path = ngx_queue_data(q, ngx_quic_path_t, queue);
 +
-+        if (path->state != NGX_QUIC_PATH_VALIDATING) {
++        if (!path->validating) {
 +            continue;
 +        }
 +
@@ -7410,7 +7296,7 @@ new file mode 100644
 +        }
 +    }
 +
-+    ngx_log_error(NGX_LOG_INFO, c->log, 0,
++    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
 +                  "quic stale PATH_RESPONSE ignored");
 +
 +    return NGX_OK;
@@ -7428,8 +7314,9 @@ new file mode 100644
 +
 +    rst = 1;
 +
-+    if (qc->backup) {
-+        prev = qc->backup->path;
++    prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
++
++    if (prev != NULL) {
 +
 +        if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen,
 +                             path->sockaddr, path->socklen, 0)
@@ -7462,20 +7349,24 @@ new file mode 100644
 +    }
 +
 +    ngx_log_error(NGX_LOG_INFO, c->log, 0,
-+                   "quic path #%uL successfully validated", path->seqnum);
++                  "quic path #%uL addr:%V successfully validated",
++                  path->seqnum, &path->addr_text);
++
++    ngx_quic_path_dbg(c, "is validated", path);
 +
-+    path->state = NGX_QUIC_PATH_VALIDATED;
++    path->validated = 1;
++    path->validating = 0;
 +    path->limited = 0;
 +
 +    return NGX_OK;
 +}
 +
 +
-+static ngx_quic_path_t *
-+ngx_quic_alloc_path(ngx_connection_t *c)
++ngx_quic_path_t *
++ngx_quic_new_path(ngx_connection_t *c,
++    struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid)
 +{
 +    ngx_queue_t            *q;
-+    struct sockaddr        *sa;
 +    ngx_quic_path_t        *path;
 +    ngx_quic_connection_t  *qc;
 +
@@ -7488,9 +7379,7 @@ new file mode 100644
 +
 +        ngx_queue_remove(&path->queue);
 +
-+        sa = path->sockaddr;
 +        ngx_memzero(path, sizeof(ngx_quic_path_t));
-+        path->sockaddr = sa;
 +
 +    } else {
 +
@@ -7498,37 +7387,18 @@ new file mode 100644
 +        if (path == NULL) {
 +            return NULL;
 +        }
-+
-+        path->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN);
-+        if (path->sockaddr == NULL) {
-+            return NULL;
-+        }
 +    }
 +
-+    return path;
-+}
-+
-+
-+ngx_quic_path_t *
-+ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr,
-+    socklen_t socklen)
-+{
-+    ngx_quic_path_t        *path;
-+    ngx_quic_connection_t  *qc;
-+
-+    qc = ngx_quic_get_connection(c);
++    ngx_queue_insert_tail(&qc->paths, &path->queue);
 +
-+    path = ngx_quic_alloc_path(c);
-+    if (path == NULL) {
-+        return NULL;
-+    }
++    path->cid = cid;
++    cid->used = 1;
 +
-+    path->state = NGX_QUIC_PATH_NEW;
 +    path->limited = 1;
 +
 +    path->seqnum = qc->path_seqnum++;
-+    path->last_seen = ngx_current_msec;
 +
++    path->sockaddr = &path->sa.sockaddr;
 +    path->socklen = socklen;
 +    ngx_memcpy(path->sockaddr, sockaddr, socklen);
 +
@@ -7536,19 +7406,15 @@ new file mode 100644
 +    path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
 +                                        NGX_SOCKADDR_STRLEN, 1);
 +
-+    ngx_queue_insert_tail(&qc->paths, &path->queue);
-+
 +    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+                   "quic path #%uL created src:%V",
++                   "quic path #%uL created addr:%V",
 +                   path->seqnum, &path->addr_text);
-+
 +    return path;
 +}
 +
 +
-+ngx_quic_path_t *
-+ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr,
-+    socklen_t socklen)
++static ngx_quic_path_t *
++ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag)
 +{
 +    ngx_queue_t            *q;
 +    ngx_quic_path_t        *path;
@@ -7562,10 +7428,7 @@ new file mode 100644
 +    {
 +        path = ngx_queue_data(q, ngx_quic_path_t, queue);
 +
-+        if (ngx_cmp_sockaddr(sockaddr, socklen,
-+                             path->sockaddr, path->socklen, 1)
-+            == NGX_OK)
-+        {
++        if (path->tag == tag) {
 +            return path;
 +        }
 +    }
@@ -7575,83 +7438,92 @@ new file mode 100644
 +
 +
 +ngx_int_t
-+ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt)
++ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt)
 +{
 +    off_t                   len;
-+    ngx_quic_path_t        *path;
++    ngx_queue_t            *q;
++    ngx_quic_path_t        *path, *probe;
 +    ngx_quic_socket_t      *qsock;
++    ngx_quic_send_ctx_t    *ctx;
 +    ngx_quic_client_id_t   *cid;
 +    ngx_quic_connection_t  *qc;
 +
 +    qc = ngx_quic_get_connection(c);
 +    qsock = ngx_quic_get_socket(c);
 +
++    len = pkt->raw->last - pkt->raw->start;
++
 +    if (c->udp->dgram == NULL) {
-+        /* 1st ever packet in connection, path already exists */
-+        path = qsock->path;
++        /* first ever packet in connection, path already exists  */
++        path = qc->path;
 +        goto update;
 +    }
 +
-+    path = ngx_quic_find_path(c, c->udp->dgram->sockaddr,
-+                              c->udp->dgram->socklen);
-+
-+    if (path == NULL) {
-+        path = ngx_quic_add_path(c, c->udp->dgram->sockaddr,
-+                                 c->udp->dgram->socklen);
-+        if (path == NULL) {
-+            return NGX_ERROR;
-+        }
-+
-+        if (qsock->path) {
-+            /* NAT rebinding case: packet to same CID, but from new address */
++    probe = NULL;
 +
-+            ngx_quic_unref_path(c, qsock->path);
-+
-+            qsock->path = path;
-+            path->refcnt++;
++    for (q = ngx_queue_head(&qc->paths);
++         q != ngx_queue_sentinel(&qc->paths);
++         q = ngx_queue_next(q))
++    {
++        path = ngx_queue_data(q, ngx_quic_path_t, queue);
 +
++        if (ngx_cmp_sockaddr(c->udp->dgram->sockaddr, c->udp->dgram->socklen,
++                             path->sockaddr, path->socklen, 1)
++            == NGX_OK)
++        {
 +            goto update;
 +        }
 +
-+    } else if (qsock->path) {
-+        goto update;
++        if (path->tag == NGX_QUIC_PATH_PROBE) {
++            probe = path;
++        }
 +    }
 +
-+    /* prefer unused client IDs if available */
-+    cid = ngx_quic_next_client_id(c);
-+    if (cid == NULL) {
++    /* packet from new path, drop current probe, if any */
 +
-+        /* try to reuse connection ID used on the same path */
-+        cid = ngx_quic_used_client_id(c, path);
-+        if (cid == NULL) {
++    ctx = ngx_quic_get_send_ctx(qc, pkt->level);
 +
-+            qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
-+            qc->error_reason = "no available client ids for new path";
++    /*
++     * only accept highest-numbered packets to prevent connection id
++     * exhaustion by excessive probing packets from unknown paths
++     */
++    if (pkt->pn != ctx->largest_pn) {
++        return NGX_DONE;
++    }
 +
-+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
-+                          "no available client ids for new path");
++    if (probe && ngx_quic_free_path(c, probe) != NGX_OK) {
++        return NGX_ERROR;
++    }
 +
-+            return NGX_ERROR;
-+        }
++    /* new path requires new client id */
++    cid = ngx_quic_next_client_id(c);
++    if (cid == NULL) {
++        ngx_log_error(NGX_LOG_ERR, c->log, 0,
++                      "quic no available client ids for new path");
++        /* stop processing of this datagram */
++        return NGX_DONE;
 +    }
 +
-+    ngx_quic_connect(c, qsock, path, cid);
++    path = ngx_quic_new_path(c, c->udp->dgram->sockaddr,
++                             c->udp->dgram->socklen, cid);
++    if (path == NULL) {
++        return NGX_ERROR;
++    }
 +
-+update:
++    path->tag = NGX_QUIC_PATH_PROBE;
 +
-+    if (path->state != NGX_QUIC_PATH_NEW) {
-+        /* force limits/revalidation for paths that were not seen recently */
-+        if (ngx_current_msec - path->last_seen > qc->tp.max_idle_timeout) {
*** 878 LINES SKIPPED ***