changeset 8628:45db1b5c1706 quic

QUIC: connection multiplexing per port. Also, connection migration within a single worker is implemented.
author Roman Arutyunyan <arut@nginx.com>
date Wed, 11 Nov 2020 11:57:50 +0000
parents 405b6e8eb523
children feec2cc762f6
files src/event/ngx_event.h src/event/ngx_event_quic.c src/event/ngx_event_quic.h src/event/ngx_event_quic_transport.c src/event/ngx_event_udp.c src/http/modules/ngx_http_quic_module.c src/stream/ngx_stream_quic_module.c
diffstat 7 files changed, 550 insertions(+), 101 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event.h
+++ b/src/event/ngx_event.h
@@ -167,6 +167,18 @@ struct ngx_event_aio_s {
 #endif
 
 
+#if !(NGX_WIN32)
+
+struct ngx_udp_connection_s {
+    ngx_rbtree_node_t          node;
+    ngx_connection_t          *connection;
+    ngx_str_t                  key;
+    ngx_buf_t                 *buffer;
+};
+
+#endif
+
+
 typedef struct {
     ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
     ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
@@ -501,6 +513,8 @@ void ngx_event_accept(ngx_event_t *ev);
 void ngx_event_recvmsg(ngx_event_t *ev);
 void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp,
     ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+void ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp,
+    ngx_str_t *key);
 #endif
 void ngx_delete_udp_connection(void *data);
 ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle);
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -121,11 +121,18 @@ struct ngx_quic_connection_s {
     ngx_str_t                         odcid; /* original server ID */
     ngx_str_t                         token;
 
+    struct sockaddr                  *sockaddr;
+    socklen_t                         socklen;
+
     ngx_queue_t                       client_ids;
+    ngx_queue_t                       server_ids;
     ngx_queue_t                       free_client_ids;
+    ngx_queue_t                       free_server_ids;
     ngx_uint_t                        nclient_ids;
+    ngx_uint_t                        nserver_ids;
     uint64_t                          max_retired_seqnum;
     uint64_t                          client_seqnum;
+    uint64_t                          server_seqnum;
 
     ngx_uint_t                        client_tp_done;
     ngx_quic_tp_t                     tp;
@@ -185,6 +192,15 @@ typedef struct {
 } ngx_quic_client_id_t;
 
 
+typedef struct {
+    ngx_udp_connection_t              udp;
+    ngx_queue_t                       queue;
+    uint64_t                          seqnum;
+    size_t                            len;
+    u_char                            id[NGX_QUIC_CID_LEN_MAX];
+} ngx_quic_server_id_t;
+
+
 typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c,
     ngx_quic_frame_t *frame, void *data);
 
@@ -217,8 +233,7 @@ static ngx_int_t ngx_quic_process_statel
     ngx_quic_header_t *pkt);
 static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c,
     ngx_quic_header_t *inpkt);
-static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c,
-    ngx_quic_connection_t *qc, ngx_str_t *odcid);
+static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id);
 static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c);
 static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token);
 static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c,
@@ -304,8 +319,16 @@ static ngx_int_t ngx_quic_handle_new_con
     ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f);
 static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c,
     enum ssl_encryption_level_t level, uint64_t seqnum);
+static ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
+    ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f);
+static ngx_int_t ngx_quic_issue_server_ids(ngx_connection_t *c);
+static void ngx_quic_clear_temp_server_ids(ngx_connection_t *c);
+static ngx_quic_server_id_t *ngx_quic_insert_server_id(ngx_connection_t *c,
+    ngx_str_t *id);
 static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c,
     ngx_quic_connection_t *qc);
+static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c,
+    ngx_quic_connection_t *qc);
 
 static void ngx_quic_queue_frame(ngx_quic_connection_t *qc,
     ngx_quic_frame_t *frame);
@@ -439,7 +462,8 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_q
         break;
 
     case NGX_QUIC_FT_NEW_CONNECTION_ID:
-        p = ngx_slprintf(p, last, "NCID seq:%uL retire:%uL len:%ud",
+        p = ngx_slprintf(p, last,
+                         "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud",
                          f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len);
         break;
 
@@ -983,7 +1007,9 @@ ngx_quic_new_connection(ngx_connection_t
 
     ngx_queue_init(&qc->free_frames);
     ngx_queue_init(&qc->client_ids);
+    ngx_queue_init(&qc->server_ids);
     ngx_queue_init(&qc->free_client_ids);
+    ngx_queue_init(&qc->free_server_ids);
 
     qc->avg_rtt = NGX_QUIC_INITIAL_RTT;
     qc->rttvar = NGX_QUIC_INITIAL_RTT / 2;
@@ -992,6 +1018,7 @@ ngx_quic_new_connection(ngx_connection_t
     /*
      * qc->latest_rtt = 0
      * qc->nclient_ids = 0
+     * qc->nserver_ids = 0
      * qc->max_retired_seqnum = 0
      */
 
@@ -1010,6 +1037,16 @@ ngx_quic_new_connection(ngx_connection_t
     qc->conf = conf;
     qc->tp = conf->tp;
 
+    if (qc->tp.disable_active_migration) {
+        qc->sockaddr = ngx_palloc(c->pool, c->socklen);
+        if (qc->sockaddr == NULL) {
+            return NULL;
+        }
+
+        ngx_memcpy(qc->sockaddr, c->sockaddr, c->socklen);
+        qc->socklen = c->socklen;
+    }
+
     ctp = &qc->ctp;
     ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
     ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
@@ -1026,7 +1063,19 @@ ngx_quic_new_connection(ngx_connection_t
     qc->congestion.ssthresh = (size_t) -1;
     qc->congestion.recovery_start = ngx_current_msec;
 
-    if (ngx_quic_new_dcid(c, qc, &pkt->dcid) != NGX_OK) {
+    qc->odcid.len = pkt->dcid.len;
+    qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid);
+    if (qc->odcid.data == NULL) {
+        return NULL;
+    }
+
+    qc->dcid.len = NGX_QUIC_SERVER_CID_LEN;
+    qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len);
+    if (qc->dcid.data == NULL) {
+        return NULL;
+    }
+
+    if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) {
         return NULL;
     }
 
@@ -1055,6 +1104,8 @@ ngx_quic_new_connection(ngx_connection_t
     qc->nclient_ids++;
     qc->client_seqnum = 0;
 
+    qc->server_seqnum = NGX_QUIC_UNSET_PN;
+
     return qc;
 }
 
@@ -1186,26 +1237,14 @@ ngx_quic_negotiate_version(ngx_connectio
 
 
 static ngx_int_t
-ngx_quic_new_dcid(ngx_connection_t *c, ngx_quic_connection_t *qc,
-    ngx_str_t *odcid)
+ngx_quic_create_server_id(ngx_connection_t *c, u_char *id)
 {
-    qc->dcid.len = NGX_QUIC_SERVER_CID_LEN;
-    qc->dcid.data = ngx_pnalloc(c->pool, NGX_QUIC_SERVER_CID_LEN);
-    if (qc->dcid.data == NULL) {
+    if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) {
         return NGX_ERROR;
     }
 
-    if (RAND_bytes(qc->dcid.data, NGX_QUIC_SERVER_CID_LEN) != 1) {
-        return NGX_ERROR;
-    }
-
-    ngx_quic_hexdump(c->log, "quic server CID", qc->dcid.data, qc->dcid.len);
-
-    qc->odcid.len = odcid->len;
-    qc->odcid.data = ngx_pstrdup(c->pool, odcid);
-    if (qc->odcid.data == NULL) {
-        return NGX_ERROR;
-    }
+    ngx_quic_hexdump(c->log, "quic create server id",
+                     id, NGX_QUIC_SERVER_CID_LEN);
 
     return NGX_OK;
 }
@@ -1254,6 +1293,10 @@ ngx_quic_send_retry(ngx_connection_t *c)
     c->quic->tp.retry_scid = c->quic->dcid;
     c->quic->in_retry = 1;
 
+    if (ngx_quic_insert_server_id(c, &c->quic->dcid) == NULL) {
+        return NGX_ERROR;
+    }
+
     return NGX_OK;
 }
 
@@ -1629,6 +1672,16 @@ ngx_quic_input_handler(ngx_event_t *rev)
         return;
     }
 
+    if (qc->tp.disable_active_migration) {
+        if (c->socklen != qc->socklen
+            || ngx_memcmp(c->sockaddr, qc->sockaddr, c->socklen) != 0)
+        {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "quic dropping packet from new address");
+            return;
+        }
+    }
+
     b.last += n;
     qc->received += n;
 
@@ -1694,7 +1747,9 @@ static ngx_int_t
 ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc)
 {
     ngx_uint_t              i;
+    ngx_queue_t            *q;
     ngx_quic_send_ctx_t    *ctx;
+    ngx_quic_server_id_t   *sid;
     ngx_quic_connection_t  *qc;
 
     qc = c->quic;
@@ -1801,6 +1856,15 @@ ngx_quic_close_quic(ngx_connection_t *c,
         ngx_quic_free_frames(c, &qc->send_ctx[i].sent);
     }
 
+    while (!ngx_queue_empty(&qc->server_ids)) {
+        q = ngx_queue_head(&qc->server_ids);
+        sid = ngx_queue_data(q, ngx_quic_server_id_t, queue);
+
+        ngx_queue_remove(q);
+        ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node);
+        qc->nserver_ids--;
+    }
+
     if (qc->close.timer_set) {
         return NGX_AGAIN;
     }
@@ -2065,7 +2129,21 @@ ngx_quic_process_packet(ngx_connection_t
                 return NGX_DECLINED;
             }
 
-            if (ngx_quic_new_dcid(c, qc, &pkt->dcid) != NGX_OK) {
+            qc->odcid.len = pkt->dcid.len;
+            qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid);
+            if (qc->odcid.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_quic_clear_temp_server_ids(c);
+
+            if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            qc->server_seqnum = 0;
+
+            if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) {
                 return NGX_ERROR;
             }
 
@@ -2137,6 +2215,16 @@ ngx_quic_process_packet(ngx_connection_t
                 return NGX_ERROR;
             }
 
+            if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) {
+                return NGX_ERROR;
+            }
+
+            qc->server_seqnum = 0;
+
+            if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) {
+                return NGX_ERROR;
+            }
+
         } else if (pkt->level == ssl_encryption_application) {
             return ngx_quic_send_stateless_reset(c, conf, pkt);
 
@@ -2270,6 +2358,10 @@ ngx_quic_discard_ctx(ngx_connection_t *c
         ngx_quic_free_frame(c, f);
     }
 
+    if (level == ssl_encryption_initial) {
+        ngx_quic_clear_temp_server_ids(c);
+    }
+
     ctx->send_ack = 0;
 }
 
@@ -2277,43 +2369,12 @@ ngx_quic_discard_ctx(ngx_connection_t *c
 static ngx_int_t
 ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt)
 {
-    ngx_str_t             *dcid;
     ngx_queue_t           *q;
-    ngx_quic_send_ctx_t   *ctx;
     ngx_quic_client_id_t  *cid;
 
-    dcid = (pkt->level == ssl_encryption_early_data) ? &qc->odcid : &qc->dcid;
-
-    if (pkt->dcid.len == dcid->len
-        && ngx_memcmp(pkt->dcid.data, dcid->data, dcid->len) == 0)
-    {
-        if (pkt->level == ssl_encryption_application) {
-            return NGX_OK;
-        }
-
-        goto found;
-    }
-
-    /*
-     * a packet sent in response to an initial client packet might be lost,
-     * thus check also for old dcid
-     */
-    ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial);
-
-    if (pkt->level == ssl_encryption_initial
-        && ctx->largest_ack == NGX_QUIC_UNSET_PN)
-    {
-        if (pkt->dcid.len == qc->odcid.len
-            && ngx_memcmp(pkt->dcid.data, qc->odcid.data, qc->odcid.len) == 0)
-        {
-            goto found;
-        }
-    }
-
-    ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcid");
-    return NGX_ERROR;
-
-found:
+    if (pkt->level == ssl_encryption_application) {
+        return NGX_OK;
+    }
 
     for (q = ngx_queue_head(&qc->client_ids);
          q != ngx_queue_sentinel(&qc->client_ids);
@@ -2533,6 +2594,16 @@ ngx_quic_payload_handler(ngx_connection_
             break;
 
         case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
+
+            if (ngx_quic_handle_retire_connection_id_frame(c, pkt,
+                                                           &frame.u.retire_cid)
+                != NGX_OK)
+            {
+                return NGX_ERROR;
+            }
+
+            break;
+
         case NGX_QUIC_FT_PATH_RESPONSE:
 
             /* TODO: handle */
@@ -3638,6 +3709,10 @@ ngx_quic_crypto_input(ngx_connection_t *
      */
     ngx_quic_discard_ctx(c, ssl_encryption_handshake);
 
+    if (ngx_quic_issue_server_ids(c) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
     return NGX_OK;
 }
 
@@ -4265,6 +4340,173 @@ ngx_quic_retire_connection_id(ngx_connec
 }
 
 
+static ngx_int_t
+ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
+    ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f)
+{
+    ngx_queue_t            *q;
+    ngx_quic_server_id_t   *sid;
+    ngx_quic_connection_t  *qc;
+
+    qc = c->quic;
+
+    for (q = ngx_queue_head(&qc->server_ids);
+         q != ngx_queue_sentinel(&qc->server_ids);
+         q = ngx_queue_next(q))
+    {
+        sid = ngx_queue_data(q, ngx_quic_server_id_t, queue);
+
+        if (sid->seqnum == f->sequence_number) {
+            ngx_queue_remove(q);
+            ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node);
+            qc->nserver_ids--;
+
+            if (c->udp != &sid->udp) {
+                ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue);
+            }
+
+            break;
+        }
+    }
+
+    return ngx_quic_issue_server_ids(c);
+}
+
+
+static ngx_int_t
+ngx_quic_issue_server_ids(ngx_connection_t *c)
+{
+    ngx_str_t               dcid;
+    ngx_uint_t              n;
+    ngx_quic_frame_t       *frame;
+    ngx_quic_server_id_t   *sid;
+    ngx_quic_connection_t  *qc;
+    u_char                  id[NGX_QUIC_SERVER_CID_LEN];
+
+    qc = c->quic;
+
+    n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic issue server ids has:%ui max:%ui", qc->nserver_ids, n);
+
+    while (qc->nserver_ids < n) {
+        if (ngx_quic_create_server_id(c, id) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        dcid.len = NGX_QUIC_SERVER_CID_LEN;
+        dcid.data = id;
+
+        sid = ngx_quic_insert_server_id(c, &dcid);
+        if (sid == NULL) {
+            return NGX_ERROR;
+        }
+
+        frame = ngx_quic_alloc_frame(c, 0);
+        if (frame == NULL) {
+            return NGX_ERROR;
+        }
+
+        frame->level = ssl_encryption_application;
+        frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID;
+        frame->u.ncid.seqnum = sid->seqnum;
+        frame->u.ncid.retire = 0;
+        frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN;
+        ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN);
+
+        if (ngx_quic_new_sr_token(c, &dcid, &qc->conf->sr_token_key,
+                                  frame->u.ncid.srt)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        ngx_quic_queue_frame(c->quic, frame);
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_quic_clear_temp_server_ids(ngx_connection_t *c)
+{
+    ngx_queue_t            *q, *next;
+    ngx_quic_server_id_t   *sid;
+    ngx_quic_connection_t  *qc;
+
+    qc = c->quic;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic clear temp server ids");
+
+    for (q = ngx_queue_head(&qc->server_ids);
+         q != ngx_queue_sentinel(&qc->server_ids);
+         q = next)
+    {
+        next = ngx_queue_next(q);
+        sid = ngx_queue_data(q, ngx_quic_server_id_t, queue);
+
+        if (sid->seqnum != NGX_QUIC_UNSET_PN) {
+            continue;
+        }
+
+        ngx_queue_remove(q);
+        ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node);
+        qc->nserver_ids--;
+
+        if (c->udp != &sid->udp) {
+            ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue);
+        }
+    }
+}
+
+
+static ngx_quic_server_id_t *
+ngx_quic_insert_server_id(ngx_connection_t *c, ngx_str_t *id)
+{
+    ngx_str_t               dcid;
+    ngx_quic_server_id_t   *sid;
+    ngx_quic_connection_t  *qc;
+
+    qc = c->quic;
+
+    sid = ngx_quic_alloc_server_id(c, qc);
+    if (sid == NULL) {
+        return NULL;
+    }
+
+    sid->seqnum = qc->server_seqnum;
+
+    if (qc->server_seqnum != NGX_QUIC_UNSET_PN) {
+        qc->server_seqnum++;
+    }
+
+    sid->len = id->len;
+    ngx_memcpy(sid->id, id->data, id->len);
+
+    ngx_queue_insert_tail(&qc->server_ids, &sid->queue);
+    qc->nserver_ids++;
+
+    dcid.data = sid->id;
+    dcid.len = sid->len;
+
+    ngx_insert_udp_connection(c, &sid->udp, &dcid);
+
+    if (c->udp == NULL) {
+        c->udp = &sid->udp;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic insert server id seqnum:%uL", sid->seqnum);
+
+    ngx_quic_hexdump(c->log, "quic server id", id->data, id->len);
+
+    return sid;
+}
+
+
 static ngx_quic_client_id_t *
 ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc)
 {
@@ -4292,6 +4534,33 @@ ngx_quic_alloc_client_id(ngx_connection_
 }
 
 
+static ngx_quic_server_id_t *
+ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc)
+{
+    ngx_queue_t           *q;
+    ngx_quic_server_id_t  *sid;
+
+    if (!ngx_queue_empty(&qc->free_server_ids)) {
+
+        q = ngx_queue_head(&qc->free_server_ids);
+        sid = ngx_queue_data(q, ngx_quic_server_id_t, queue);
+
+        ngx_queue_remove(&sid->queue);
+
+        ngx_memzero(sid, sizeof(ngx_quic_server_id_t));
+
+    } else {
+
+        sid = ngx_pcalloc(c->pool, sizeof(ngx_quic_server_id_t));
+        if (sid == NULL) {
+            return NULL;
+        }
+    }
+
+    return sid;
+}
+
+
 static void
 ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame)
 {
--- a/src/event/ngx_event_quic.h
+++ b/src/event/ngx_event_quic.h
@@ -58,6 +58,8 @@
 
 #define NGX_QUIC_SR_TOKEN_LEN                16
 
+#define NGX_QUIC_MAX_SERVER_IDS              8
+
 
 typedef struct {
     /* configurable */
@@ -72,8 +74,8 @@ typedef struct {
     ngx_uint_t                 initial_max_streams_bidi;
     ngx_uint_t                 initial_max_streams_uni;
     ngx_uint_t                 ack_delay_exponent;
-    ngx_uint_t                 disable_active_migration;
     ngx_uint_t                 active_connection_id_limit;
+    ngx_flag_t                 disable_active_migration;
     ngx_str_t                  original_dcid;
     ngx_str_t                  initial_scid;
     ngx_str_t                  retry_scid;
@@ -123,6 +125,8 @@ ngx_connection_t *ngx_quic_open_stream(n
 void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
     const char *reason);
 uint32_t ngx_quic_version(ngx_connection_t *c);
+ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len,
+    ngx_str_t *dcid);
 
 
 /********************************* DEBUG *************************************/
--- a/src/event/ngx_event_quic_transport.c
+++ b/src/event/ngx_event_quic_transport.c
@@ -10,6 +10,11 @@
 #include <ngx_event_quic_transport.h>
 
 
+#define NGX_QUIC_LONG_DCID_LEN_OFFSET  5
+#define NGX_QUIC_LONG_DCID_OFFSET      6
+#define NGX_QUIC_SHORT_DCID_OFFSET     1
+
+
 #if (NGX_HAVE_NONALIGNED)
 
 #define ngx_quic_parse_uint16(p)  ntohs(*(uint16_t *) (p))
@@ -95,6 +100,8 @@ static size_t ngx_quic_create_max_data(u
     ngx_quic_max_data_frame_t *md);
 static size_t ngx_quic_create_path_response(u_char *p,
     ngx_quic_path_challenge_frame_t *pc);
+static size_t ngx_quic_create_new_connection_id(u_char *p,
+    ngx_quic_new_conn_id_frame_t *rcid);
 static size_t ngx_quic_create_retire_connection_id(u_char *p,
     ngx_quic_retire_cid_frame_t *rcid);
 static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl);
@@ -321,6 +328,46 @@ ngx_quic_parse_packet(ngx_quic_header_t 
 }
 
 
+ngx_int_t
+ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n,
+    ngx_str_t *dcid)
+{
+    size_t  len, offset;
+
+    if (n == 0) {
+        goto failed;
+    }
+
+    if (ngx_quic_long_pkt(*data)) {
+        if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) {
+            goto failed;
+        }
+
+        len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET];
+        offset = NGX_QUIC_LONG_DCID_OFFSET;
+
+    } else {
+        len = NGX_QUIC_SERVER_CID_LEN;
+        offset = NGX_QUIC_SHORT_DCID_OFFSET;
+    }
+
+    if (n < len + offset) {
+        goto failed;
+    }
+
+    dcid->len = len;
+    dcid->data = &data[offset];
+
+    return NGX_OK;
+
+failed:
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet");
+
+    return NGX_ERROR;
+}
+
+
 static ngx_int_t
 ngx_quic_parse_long_header(ngx_quic_header_t *pkt)
 {
@@ -1222,6 +1269,9 @@ ngx_quic_create_frame(u_char *p, ngx_qui
     case NGX_QUIC_FT_PATH_RESPONSE:
         return ngx_quic_create_path_response(p, &f->u.path_response);
 
+    case NGX_QUIC_FT_NEW_CONNECTION_ID:
+        return ngx_quic_create_new_connection_id(p, &f->u.ncid);
+
     case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
         return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid);
 
@@ -1705,6 +1755,35 @@ ngx_quic_create_path_response(u_char *p,
 
 
 static size_t
+ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid)
+{
+    size_t   len;
+    u_char  *start;
+
+    if (p == NULL) {
+        len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID);
+        len += ngx_quic_varint_len(ncid->seqnum);
+        len += ngx_quic_varint_len(ncid->retire);
+        len++;
+        len += ncid->len;
+        len += NGX_QUIC_SR_TOKEN_LEN;
+        return len;
+    }
+
+    start = p;
+
+    ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID);
+    ngx_quic_build_int(&p, ncid->seqnum);
+    ngx_quic_build_int(&p, ncid->retire);
+    *p++ = ncid->len;
+    p = ngx_cpymem(p, ncid->cid, ncid->len);
+    p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN);
+
+    return p - start;
+}
+
+
+static size_t
 ngx_quic_create_retire_connection_id(u_char *p,
     ngx_quic_retire_cid_frame_t *rcid)
 {
@@ -1783,6 +1862,11 @@ ngx_quic_create_transport_params(u_char 
         *clen = len;
     }
 
+    if (tp->disable_active_migration) {
+        len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION);
+        len += ngx_quic_varint_len(0);
+    }
+
     len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
                            tp->active_connection_id_limit);
 
@@ -1830,6 +1914,11 @@ ngx_quic_create_transport_params(u_char 
     ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
                      tp->max_idle_timeout);
 
+    if (tp->disable_active_migration) {
+        ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION);
+        ngx_quic_build_int(&p, 0);
+    }
+
     ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
                      tp->active_connection_id_limit);
 
--- a/src/event/ngx_event_udp.c
+++ b/src/event/ngx_event_udp.c
@@ -12,19 +12,12 @@
 
 #if !(NGX_WIN32)
 
-struct ngx_udp_connection_s {
-    ngx_rbtree_node_t   node;
-    ngx_connection_t   *connection;
-    ngx_buf_t          *buffer;
-};
-
-
 static void ngx_close_accepted_udp_connection(ngx_connection_t *c);
 static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf,
     size_t size);
-static ngx_int_t ngx_insert_udp_connection(ngx_connection_t *c);
+static ngx_int_t ngx_create_udp_connection(ngx_connection_t *c);
 static ngx_connection_t *ngx_lookup_udp_connection(ngx_listening_t *ls,
-    struct sockaddr *sockaddr, socklen_t socklen,
+    ngx_str_t *key, struct sockaddr *sockaddr, socklen_t socklen,
     struct sockaddr *local_sockaddr, socklen_t local_socklen);
 
 
@@ -32,6 +25,7 @@ void
 ngx_event_recvmsg(ngx_event_t *ev)
 {
     ssize_t            n;
+    ngx_str_t          key;
     ngx_buf_t          buf;
     ngx_log_t         *log;
     ngx_err_t          err;
@@ -229,8 +223,18 @@ ngx_event_recvmsg(ngx_event_t *ev)
 
 #endif
 
-        c = ngx_lookup_udp_connection(ls, sockaddr, socklen, local_sockaddr,
-                                      local_socklen);
+        ngx_str_null(&key);
+
+#if (NGX_QUIC)
+        if (ls->quic) {
+            if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) {
+                goto next;
+            }
+        }
+#endif
+
+        c = ngx_lookup_udp_connection(ls, &key, sockaddr, socklen,
+                                      local_sockaddr, local_socklen);
 
         if (c) {
 
@@ -403,7 +407,7 @@ ngx_event_recvmsg(ngx_event_t *ev)
         }
 #endif
 
-        if (ngx_insert_udp_connection(c) != NGX_OK) {
+        if (ngx_create_udp_connection(c) != NGX_OK) {
             ngx_close_accepted_udp_connection(c);
             return;
         }
@@ -492,8 +496,13 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_n
             udpt = (ngx_udp_connection_t *) temp;
             ct = udpt->connection;
 
-            rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen,
-                                  ct->sockaddr, ct->socklen, 1);
+            rc = ngx_memn2cmp(udp->key.data, udpt->key.data,
+                              udp->key.len, udpt->key.len);
+
+            if (rc == 0 && udp->key.len == 0) {
+                rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen,
+                                      ct->sockaddr, ct->socklen, 1);
+            }
 
             if (rc == 0 && c->listening->wildcard) {
                 rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen,
@@ -519,12 +528,18 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_n
 
 
 static ngx_int_t
-ngx_insert_udp_connection(ngx_connection_t *c)
+ngx_create_udp_connection(ngx_connection_t *c)
 {
-    uint32_t               hash;
+    ngx_str_t              key;
     ngx_pool_cleanup_t    *cln;
     ngx_udp_connection_t  *udp;
 
+#if (NGX_QUIC)
+    if (c->listening->quic) {
+        return NGX_OK;
+    }
+#endif
+
     if (c->udp) {
         return NGX_OK;
     }
@@ -534,19 +549,6 @@ ngx_insert_udp_connection(ngx_connection
         return NGX_ERROR;
     }
 
-    udp->connection = c;
-
-    ngx_crc32_init(hash);
-    ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen);
-
-    if (c->listening->wildcard) {
-        ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen);
-    }
-
-    ngx_crc32_final(hash);
-
-    udp->node.key = hash;
-
     cln = ngx_pool_cleanup_add(c->pool, 0);
     if (cln == NULL) {
         return NGX_ERROR;
@@ -555,7 +557,9 @@ ngx_insert_udp_connection(ngx_connection
     cln->data = c;
     cln->handler = ngx_delete_udp_connection;
 
-    ngx_rbtree_insert(&c->listening->rbtree, &udp->node);
+    key.len = 0;
+
+    ngx_insert_udp_connection(c, udp, &key);
 
     c->udp = udp;
 
@@ -564,6 +568,34 @@ ngx_insert_udp_connection(ngx_connection
 
 
 void
+ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp,
+    ngx_str_t *key)
+{
+    uint32_t  hash;
+
+    ngx_crc32_init(hash);
+
+    ngx_crc32_update(&hash, key->data, key->len);
+
+    if (key->len == 0) {
+        ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen);
+    }
+
+    if (c->listening->wildcard) {
+        ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen);
+    }
+
+    ngx_crc32_final(hash);
+
+    udp->connection = c;
+    udp->key = *key;
+    udp->node.key = hash;
+
+    ngx_rbtree_insert(&c->listening->rbtree, &udp->node);
+}
+
+
+void
 ngx_delete_udp_connection(void *data)
 {
     ngx_connection_t  *c = data;
@@ -579,8 +611,9 @@ ngx_delete_udp_connection(void *data)
 
 
 static ngx_connection_t *
-ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr,
-    socklen_t socklen, struct sockaddr *local_sockaddr, socklen_t local_socklen)
+ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key,
+    struct sockaddr *sockaddr, socklen_t socklen,
+    struct sockaddr *local_sockaddr, socklen_t local_socklen)
 {
     uint32_t               hash;
     ngx_int_t              rc;
@@ -608,7 +641,12 @@ ngx_lookup_udp_connection(ngx_listening_
     sentinel = ls->rbtree.sentinel;
 
     ngx_crc32_init(hash);
-    ngx_crc32_update(&hash, (u_char *) sockaddr, socklen);
+
+    ngx_crc32_update(&hash, key->data, key->len);
+
+    if (key->len == 0) {
+        ngx_crc32_update(&hash, (u_char *) sockaddr, socklen);
+    }
 
     if (ls->wildcard) {
         ngx_crc32_update(&hash, (u_char *) local_sockaddr, local_socklen);
@@ -634,8 +672,12 @@ ngx_lookup_udp_connection(ngx_listening_
 
         c = udp->connection;
 
-        rc = ngx_cmp_sockaddr(sockaddr, socklen,
-                              c->sockaddr, c->socklen, 1);
+        rc = ngx_memn2cmp(key->data, udp->key.data, key->len, udp->key.len);
+
+        if (rc == 0 && key->len == 0) {
+            rc = ngx_cmp_sockaddr(sockaddr, socklen,
+                                  c->sockaddr, c->socklen, 1);
+        }
 
         if (rc == 0 && ls->wildcard) {
             rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen,
@@ -643,6 +685,37 @@ ngx_lookup_udp_connection(ngx_listening_
         }
 
         if (rc == 0) {
+            if (key->len) {
+                rc = ngx_cmp_sockaddr(sockaddr, socklen,
+                                      c->sockaddr, c->socklen, 1);
+
+                if (rc) {
+#if (NGX_DEBUG)
+                    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+                        ngx_str_t  addr;
+                        u_char     text[NGX_SOCKADDR_STRLEN];
+
+                        addr.data = text;
+                        addr.len = ngx_sock_ntop(sockaddr, socklen, text,
+                                                 NGX_SOCKADDR_STRLEN, 1);
+
+                        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                                       "client migrated to %V", &addr);
+                    }
+#endif
+
+                    if (c->socklen < socklen) {
+                        c->sockaddr = ngx_palloc(c->pool, socklen);
+                        if (c->sockaddr == NULL) {
+                            return c;
+                        }
+                    }
+
+                    ngx_memcpy(c->sockaddr, sockaddr, socklen);
+                    c->socklen = socklen;
+                }
+            }
+
             return c;
         }
 
--- a/src/http/modules/ngx_http_quic_module.c
+++ b/src/http/modules/ngx_http_quic_module.c
@@ -104,9 +104,9 @@ static ngx_command_t  ngx_http_quic_comm
       offsetof(ngx_quic_conf_t, tp.ack_delay_exponent),
       &ngx_http_quic_ack_delay_exponent_bounds },
 
-    { ngx_string("quic_active_migration"),
+    { ngx_string("quic_disable_active_migration"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_num_slot,
+      ngx_conf_set_flag_slot,
       NGX_HTTP_SRV_CONF_OFFSET,
       offsetof(ngx_quic_conf_t, tp.disable_active_migration),
       NULL },
@@ -246,7 +246,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t
     conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT;
     conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT;
     conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT;
-    conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT;
+    conf->tp.disable_active_migration = NGX_CONF_UNSET;
     conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT;
 
     conf->retry = NGX_CONF_UNSET;
@@ -301,8 +301,8 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t 
                               prev->tp.ack_delay_exponent,
                               NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT);
 
-    ngx_conf_merge_uint_value(conf->tp.disable_active_migration,
-                              prev->tp.disable_active_migration, 1);
+    ngx_conf_merge_value(conf->tp.disable_active_migration,
+                              prev->tp.disable_active_migration, 0);
 
     ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit,
                               prev->tp.active_connection_id_limit, 2);
--- a/src/stream/ngx_stream_quic_module.c
+++ b/src/stream/ngx_stream_quic_module.c
@@ -105,9 +105,9 @@ static ngx_command_t  ngx_stream_quic_co
       offsetof(ngx_quic_conf_t, tp.ack_delay_exponent),
       &ngx_stream_quic_ack_delay_exponent_bounds },
 
-    { ngx_string("quic_active_migration"),
+    { ngx_string("quic_disable_active_migration"),
       NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_num_slot,
+      ngx_conf_set_flag_slot,
       NGX_STREAM_SRV_CONF_OFFSET,
       offsetof(ngx_quic_conf_t, tp.disable_active_migration),
       NULL },
@@ -236,7 +236,7 @@ ngx_stream_quic_create_srv_conf(ngx_conf
     conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT;
     conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT;
     conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT;
-    conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT;
+    conf->tp.disable_active_migration = NGX_CONF_UNSET;
     conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT;
 
     conf->retry = NGX_CONF_UNSET;
@@ -290,8 +290,8 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_
                               prev->tp.ack_delay_exponent,
                               NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT);
 
-    ngx_conf_merge_uint_value(conf->tp.disable_active_migration,
-                              prev->tp.disable_active_migration, 1);
+    ngx_conf_merge_value(conf->tp.disable_active_migration,
+                              prev->tp.disable_active_migration, 0);
 
     ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit,
                               prev->tp.active_connection_id_limit, 2);