src/event/quic/ngx_event_quic_migration.c - nginx

Functions defined

Macros defined

Source code


  1. /*
  2. * Copyright (C) Nginx, Inc.
  3. */


  4. #include <ngx_config.h>
  5. #include <ngx_core.h>
  6. #include <ngx_event.h>
  7. #include <ngx_event_quic_connection.h>


  8. #define NGX_QUIC_PATH_MTU_DELAY       100
  9. #define NGX_QUIC_PATH_MTU_PRECISION   16


  10. static void ngx_quic_set_connection_path(ngx_connection_t *c,
  11.     ngx_quic_path_t *path);
  12. static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c,
  13.     ngx_quic_path_t *path);
  14. static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
  15.     ngx_quic_path_t *path);
  16. static void ngx_quic_set_path_timer(ngx_connection_t *c);
  17. static ngx_int_t ngx_quic_expire_path_validation(ngx_connection_t *c,
  18.     ngx_quic_path_t *path);
  19. static ngx_int_t ngx_quic_expire_path_mtu_delay(ngx_connection_t *c,
  20.     ngx_quic_path_t *path);
  21. static ngx_int_t ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c,
  22.     ngx_quic_path_t *path);
  23. static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
  24. static ngx_int_t ngx_quic_send_path_mtu_probe(ngx_connection_t *c,
  25.     ngx_quic_path_t *path);


  26. ngx_int_t
  27. ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
  28.     ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
  29. {
  30.     size_t                  min;
  31.     ngx_quic_frame_t       *fp;
  32.     ngx_quic_connection_t  *qc;

  33.     if (pkt->level != NGX_QUIC_ENCRYPTION_APPLICATION || pkt->path_challenged) {
  34.         ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
  35.                        "quic ignoring PATH_CHALLENGE");
  36.         return NGX_OK;
  37.     }

  38.     pkt->path_challenged = 1;

  39.     qc = ngx_quic_get_connection(c);

  40.     fp = ngx_quic_alloc_frame(c);
  41.     if (fp == NULL) {
  42.         return NGX_ERROR;
  43.     }

  44.     fp->level = NGX_QUIC_ENCRYPTION_APPLICATION;
  45.     fp->type = NGX_QUIC_FT_PATH_RESPONSE;
  46.     fp->u.path_response = *f;

  47.     /*
  48.      * RFC 9000, 8.2.2.  Path Validation Responses
  49.      *
  50.      * A PATH_RESPONSE frame MUST be sent on the network path where the
  51.      * PATH_CHALLENGE frame was received.
  52.      */

  53.     /*
  54.      * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame
  55.      * to at least the smallest allowed maximum datagram size of 1200 bytes.
  56.      * ...
  57.      * However, an endpoint MUST NOT expand the datagram containing the
  58.      * PATH_RESPONSE if the resulting data exceeds the anti-amplification limit.
  59.      */

  60.     min = (ngx_quic_path_limit(c, pkt->path, 1200) < 1200) ? 0 : 1200;

  61.     if (ngx_quic_frame_sendto(c, fp, min, pkt->path) == NGX_ERROR) {
  62.         return NGX_ERROR;
  63.     }

  64.     if (pkt->path == qc->path) {
  65.         /*
  66.          * RFC 9000, 9.3.3.  Off-Path Packet Forwarding
  67.          *
  68.          * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD
  69.          * send a non-probing packet in response.
  70.          */

  71.         fp = ngx_quic_alloc_frame(c);
  72.         if (fp == NULL) {
  73.             return NGX_ERROR;
  74.         }

  75.         fp->level = NGX_QUIC_ENCRYPTION_APPLICATION;
  76.         fp->type = NGX_QUIC_FT_PING;

  77.         ngx_quic_queue_frame(qc, fp);
  78.     }

  79.     return NGX_OK;
  80. }


  81. ngx_int_t
  82. ngx_quic_handle_path_response_frame(ngx_connection_t *c,
  83.     ngx_quic_path_challenge_frame_t *f)
  84. {
  85.     ngx_uint_t              rst;
  86.     ngx_queue_t            *q;
  87.     ngx_quic_path_t        *path, *prev;
  88.     ngx_quic_send_ctx_t    *ctx;
  89.     ngx_quic_connection_t  *qc;

  90.     qc = ngx_quic_get_connection(c);

  91.     /*
  92.      * RFC 9000, 8.2.3.  Successful Path Validation
  93.      *
  94.      * A PATH_RESPONSE frame received on any network path validates the path
  95.      * on which the PATH_CHALLENGE was sent.
  96.      */

  97.     for (q = ngx_queue_head(&qc->paths);
  98.          q != ngx_queue_sentinel(&qc->paths);
  99.          q = ngx_queue_next(q))
  100.     {
  101.         path = ngx_queue_data(q, ngx_quic_path_t, queue);

  102.         if (path->state != NGX_QUIC_PATH_VALIDATING) {
  103.             continue;
  104.         }

  105.         if (ngx_memcmp(path->challenge[0], f->data, sizeof(f->data)) == 0
  106.             || ngx_memcmp(path->challenge[1], f->data, sizeof(f->data)) == 0)
  107.         {
  108.             goto valid;
  109.         }
  110.     }

  111.     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
  112.                    "quic stale PATH_RESPONSE ignored");

  113.     return NGX_OK;

  114. valid:

  115.     /*
  116.      * RFC 9000, 9.4.  Loss Detection and Congestion Control
  117.      *
  118.      * On confirming a peer's ownership of its new address,
  119.      * an endpoint MUST immediately reset the congestion controller
  120.      * and round-trip time estimator for the new path to initial values
  121.      * unless the only change in the peer's address is its port number.
  122.      */

  123.     rst = 1;

  124.     prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);

  125.     if (prev != NULL) {

  126.         if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen,
  127.                              path->sockaddr, path->socklen, 0)
  128.             == NGX_OK)
  129.         {
  130.             /* address did not change */
  131.             rst = 0;

  132.             path->mtu = prev->mtu;
  133.             path->max_mtu = prev->max_mtu;
  134.             path->mtu_unvalidated = 0;
  135.         }
  136.     }

  137.     if (rst) {
  138.         /* prevent old path packets contribution to congestion control */

  139.         ctx = ngx_quic_get_send_ctx(qc, NGX_QUIC_ENCRYPTION_APPLICATION);
  140.         qc->rst_pnum = ctx->pnum;

  141.         ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t));

  142.         qc->congestion.window = ngx_min(10 * NGX_QUIC_MIN_INITIAL_SIZE,
  143.                                    ngx_max(2 * NGX_QUIC_MIN_INITIAL_SIZE,
  144.                                            14720));
  145.         qc->congestion.ssthresh = (size_t) -1;
  146.         qc->congestion.mtu = NGX_QUIC_MIN_INITIAL_SIZE;
  147.         qc->congestion.recovery_start = ngx_current_msec - 1;

  148.         ngx_quic_init_rtt(qc);
  149.     }

  150.     path->validated = 1;

  151.     ngx_quic_set_connection_path(c, path);

  152.     if (path->mtu_unvalidated) {
  153.         path->mtu_unvalidated = 0;
  154.         return ngx_quic_validate_path(c, path);
  155.     }

  156.     /*
  157.      * RFC 9000, 9.3.  Responding to Connection Migration
  158.      *
  159.      *  After verifying a new client address, the server SHOULD
  160.      *  send new address validation tokens (Section 8) to the client.
  161.      */

  162.     if (ngx_quic_send_new_token(c, path) != NGX_OK) {
  163.         return NGX_ERROR;
  164.     }

  165.     ngx_log_error(NGX_LOG_INFO, c->log, 0,
  166.                   "quic path seq:%uL addr:%V successfully validated",
  167.                   path->seqnum, &path->addr_text);

  168.     ngx_quic_path_dbg(c, "is validated", path);

  169.     ngx_quic_discover_path_mtu(c, path);

  170.     return NGX_OK;
  171. }


  172. ngx_quic_path_t *
  173. ngx_quic_new_path(ngx_connection_t *c,
  174.     struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid)
  175. {
  176.     ngx_queue_t            *q;
  177.     ngx_quic_path_t        *path;
  178.     ngx_quic_connection_t  *qc;

  179.     qc = ngx_quic_get_connection(c);

  180.     if (!ngx_queue_empty(&qc->free_paths)) {

  181.         q = ngx_queue_head(&qc->free_paths);
  182.         path = ngx_queue_data(q, ngx_quic_path_t, queue);

  183.         ngx_queue_remove(&path->queue);

  184.         ngx_memzero(path, sizeof(ngx_quic_path_t));

  185.     } else {

  186.         path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t));
  187.         if (path == NULL) {
  188.             return NULL;
  189.         }
  190.     }

  191.     ngx_queue_insert_tail(&qc->paths, &path->queue);

  192.     path->cid = cid;
  193.     cid->used = 1;

  194.     path->seqnum = qc->path_seqnum++;

  195.     path->sockaddr = &path->sa.sockaddr;
  196.     path->socklen = socklen;
  197.     ngx_memcpy(path->sockaddr, sockaddr, socklen);

  198.     path->addr_text.data = path->text;
  199.     path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
  200.                                         NGX_SOCKADDR_STRLEN, 1);

  201.     path->mtu = NGX_QUIC_MIN_INITIAL_SIZE;

  202.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  203.                    "quic path seq:%uL created addr:%V",
  204.                    path->seqnum, &path->addr_text);
  205.     return path;
  206. }


  207. static ngx_quic_path_t *
  208. ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag)
  209. {
  210.     ngx_queue_t            *q;
  211.     ngx_quic_path_t        *path;
  212.     ngx_quic_connection_t  *qc;

  213.     qc = ngx_quic_get_connection(c);

  214.     for (q = ngx_queue_head(&qc->paths);
  215.          q != ngx_queue_sentinel(&qc->paths);
  216.          q = ngx_queue_next(q))
  217.     {
  218.         path = ngx_queue_data(q, ngx_quic_path_t, queue);

  219.         if (path->tag == tag) {
  220.             return path;
  221.         }
  222.     }

  223.     return NULL;
  224. }


  225. ngx_int_t
  226. ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt)
  227. {
  228.     off_t                   len;
  229.     ngx_queue_t            *q;
  230.     ngx_quic_path_t        *path, *probe;
  231.     ngx_quic_socket_t      *qsock;
  232.     ngx_quic_send_ctx_t    *ctx;
  233.     ngx_quic_client_id_t   *cid;
  234.     ngx_quic_connection_t  *qc;

  235.     qc = ngx_quic_get_connection(c);
  236.     qsock = ngx_quic_get_socket(c);

  237.     len = pkt->raw->last - pkt->raw->start;

  238.     if (c->udp->buffer == NULL) {
  239.         /* first ever packet in connection, path already exists  */
  240.         path = qc->path;
  241.         goto update;
  242.     }

  243.     probe = NULL;

  244.     for (q = ngx_queue_head(&qc->paths);
  245.          q != ngx_queue_sentinel(&qc->paths);
  246.          q = ngx_queue_next(q))
  247.     {
  248.         path = ngx_queue_data(q, ngx_quic_path_t, queue);

  249.         if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen,
  250.                              path->sockaddr, path->socklen, 1)
  251.             == NGX_OK)
  252.         {
  253.             goto update;
  254.         }

  255.         if (path->tag == NGX_QUIC_PATH_PROBE) {
  256.             probe = path;
  257.         }
  258.     }

  259.     /* packet from new path, drop current probe, if any */

  260.     ctx = ngx_quic_get_send_ctx(qc, pkt->level);

  261.     /*
  262.      * only accept highest-numbered packets to prevent connection id
  263.      * exhaustion by excessive probing packets from unknown paths
  264.      */
  265.     if (pkt->pn != ctx->largest_pn) {
  266.         return NGX_DONE;
  267.     }

  268.     if (probe && ngx_quic_free_path(c, probe) != NGX_OK) {
  269.         return NGX_ERROR;
  270.     }

  271.     /* new path requires new client id */
  272.     cid = ngx_quic_next_client_id(c);
  273.     if (cid == NULL) {
  274.         ngx_log_error(NGX_LOG_INFO, c->log, 0,
  275.                       "quic no available client ids for new path");
  276.         /* stop processing of this datagram */
  277.         return NGX_DONE;
  278.     }

  279.     path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid);
  280.     if (path == NULL) {
  281.         return NGX_ERROR;
  282.     }

  283.     path->tag = NGX_QUIC_PATH_PROBE;

  284.     /*
  285.      * client arrived using new path and previously seen DCID,
  286.      * this indicates NAT rebinding (or bad client)
  287.      */
  288.     if (qsock->used) {
  289.         pkt->rebound = 1;
  290.     }

  291. update:

  292.     qsock->used = 1;
  293.     pkt->path = path;

  294.     /* TODO: this may be too late in some cases;
  295.      *       for example, if error happens during decrypt(), we cannot
  296.      *       send CC, if error happens in 1st packet, due to amplification
  297.      *       limit, because path->received = 0
  298.      *
  299.      *       should we account garbage as received or only decrypting packets?
  300.      */
  301.     path->received += len;

  302.     ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
  303.                    "quic packet len:%O via sock seq:%L path seq:%uL",
  304.                    len, (int64_t) qsock->sid.seqnum, path->seqnum);
  305.     ngx_quic_path_dbg(c, "status", path);

  306.     return NGX_OK;
  307. }


  308. ngx_int_t
  309. ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path)
  310. {
  311.     ngx_quic_connection_t  *qc;

  312.     qc = ngx_quic_get_connection(c);

  313.     ngx_queue_remove(&path->queue);
  314.     ngx_queue_insert_head(&qc->free_paths, &path->queue);

  315.     /*
  316.      * invalidate CID that is no longer usable for any other path;
  317.      * this also requests new CIDs from client
  318.      */
  319.     if (path->cid) {
  320.         if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) {
  321.             return NGX_ERROR;
  322.         }
  323.     }

  324.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  325.                    "quic path seq:%uL addr:%V retired",
  326.                    path->seqnum, &path->addr_text);

  327.     return NGX_OK;
  328. }


  329. static void
  330. ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path)
  331. {
  332.     ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen);
  333.     c->socklen = path->socklen;

  334.     if (c->addr_text.data) {
  335.         c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
  336.                                          c->addr_text.data,
  337.                                          c->listening->addr_text_max_len, 0);
  338.     }

  339.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  340.                    "quic send path set to seq:%uL addr:%V",
  341.                    path->seqnum, &path->addr_text);
  342. }


  343. ngx_int_t
  344. ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt)
  345. {
  346.     ngx_quic_path_t        *next, *bkp;
  347.     ngx_quic_send_ctx_t    *ctx;
  348.     ngx_quic_connection_t  *qc;

  349.     /* got non-probing packet via non-active path */

  350.     qc = ngx_quic_get_connection(c);

  351.     ctx = ngx_quic_get_send_ctx(qc, pkt->level);

  352.     /*
  353.      * RFC 9000, 9.3.  Responding to Connection Migration
  354.      *
  355.      * An endpoint only changes the address to which it sends packets in
  356.      * response to the highest-numbered non-probing packet.
  357.      */
  358.     if (pkt->pn != ctx->largest_pn) {
  359.         return NGX_OK;
  360.     }

  361.     next = pkt->path;

  362.     /*
  363.      * RFC 9000, 9.3.3:
  364.      *
  365.      * In response to an apparent migration, endpoints MUST validate the
  366.      * previously active path using a PATH_CHALLENGE frame.
  367.      */
  368.     if (pkt->rebound) {

  369.         /* NAT rebinding: client uses new path with old SID */
  370.         if (ngx_quic_validate_path(c, qc->path) != NGX_OK) {
  371.             return NGX_ERROR;
  372.         }
  373.     }

  374.     if (qc->path->validated) {

  375.         if (next->tag != NGX_QUIC_PATH_BACKUP) {
  376.             /* can delete backup path, if any */
  377.             bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);

  378.             if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) {
  379.                 return NGX_ERROR;
  380.             }
  381.         }

  382.         qc->path->tag = NGX_QUIC_PATH_BACKUP;
  383.         ngx_quic_path_dbg(c, "is now backup", qc->path);

  384.     } else {
  385.         if (ngx_quic_free_path(c, qc->path) != NGX_OK) {
  386.             return NGX_ERROR;
  387.         }
  388.     }

  389.     /* switch active path to migrated */
  390.     qc->path = next;
  391.     qc->path->tag = NGX_QUIC_PATH_ACTIVE;

  392.     if (next->validated) {
  393.         ngx_quic_set_connection_path(c, next);

  394.     } else if (next->state != NGX_QUIC_PATH_VALIDATING) {
  395.         if (ngx_quic_validate_path(c, next) != NGX_OK) {
  396.             return NGX_ERROR;
  397.         }
  398.     }

  399.     ngx_log_error(NGX_LOG_INFO, c->log, 0,
  400.                   "quic migrated to path seq:%uL addr:%V",
  401.                   qc->path->seqnum, &qc->path->addr_text);

  402.     ngx_quic_path_dbg(c, "is now active", qc->path);

  403.     return NGX_OK;
  404. }


  405. static ngx_int_t
  406. ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path)
  407. {
  408.     ngx_msec_t              pto;
  409.     ngx_quic_send_ctx_t    *ctx;
  410.     ngx_quic_connection_t  *qc;

  411.     qc = ngx_quic_get_connection(c);

  412.     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
  413.                    "quic initiated validation of path seq:%uL", path->seqnum);

  414.     path->tries = 0;

  415.     if (RAND_bytes((u_char *) path->challenge, sizeof(path->challenge)) != 1) {
  416.         return NGX_ERROR;
  417.     }

  418.     (void) ngx_quic_send_path_challenge(c, path);

  419.     ctx = ngx_quic_get_send_ctx(qc, NGX_QUIC_ENCRYPTION_APPLICATION);
  420.     pto = ngx_max(ngx_quic_pto(c, ctx), 1000);

  421.     path->expires = ngx_current_msec + pto;
  422.     path->state = NGX_QUIC_PATH_VALIDATING;

  423.     ngx_quic_set_path_timer(c);

  424.     return NGX_OK;
  425. }


  426. static ngx_int_t
  427. ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path)
  428. {
  429.     size_t             min;
  430.     ngx_uint_t         n;
  431.     ngx_quic_frame_t  *frame;

  432.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  433.                    "quic path seq:%uL send path_challenge tries:%ui",
  434.                    path->seqnum, path->tries);

  435.     for (n = 0; n < 2; n++) {

  436.         frame = ngx_quic_alloc_frame(c);
  437.         if (frame == NULL) {
  438.             return NGX_ERROR;
  439.         }

  440.         frame->level = NGX_QUIC_ENCRYPTION_APPLICATION;
  441.         frame->type = NGX_QUIC_FT_PATH_CHALLENGE;

  442.         ngx_memcpy(frame->u.path_challenge.data, path->challenge[n], 8);

  443.         /*
  444.          * RFC 9000, 8.2.1.  Initiating Path Validation
  445.          *
  446.          * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame
  447.          * to at least the smallest allowed maximum datagram size of 1200 bytes,
  448.          * unless the anti-amplification limit for the path does not permit
  449.          * sending a datagram of this size.
  450.          */

  451.         if (path->mtu_unvalidated
  452.             || ngx_quic_path_limit(c, path, 1200) < 1200)
  453.         {
  454.             min = 0;
  455.             path->mtu_unvalidated = 1;

  456.         } else {
  457.             min = 1200;
  458.         }

  459.         if (ngx_quic_frame_sendto(c, frame, min, path) == NGX_ERROR) {
  460.             return NGX_ERROR;
  461.         }
  462.     }

  463.     return NGX_OK;
  464. }


  465. void
  466. ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path)
  467. {
  468.     ngx_quic_connection_t  *qc;

  469.     qc = ngx_quic_get_connection(c);

  470.     if (path->max_mtu) {
  471.         if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) {
  472.             path->state = NGX_QUIC_PATH_IDLE;
  473.             ngx_quic_set_path_timer(c);
  474.             return;
  475.         }

  476.         path->mtud = (path->mtu + path->max_mtu) / 2;

  477.     } else {
  478.         path->mtud = path->mtu * 2;

  479.         if (path->mtud >= qc->ctp.max_udp_payload_size) {
  480.             path->mtud = qc->ctp.max_udp_payload_size;
  481.             path->max_mtu = qc->ctp.max_udp_payload_size;
  482.         }
  483.     }

  484.     path->state = NGX_QUIC_PATH_WAITING;
  485.     path->expires = ngx_current_msec + NGX_QUIC_PATH_MTU_DELAY;

  486.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  487.                    "quic path seq:%uL schedule mtu:%uz",
  488.                    path->seqnum, path->mtud);

  489.     ngx_quic_set_path_timer(c);
  490. }


  491. static void
  492. ngx_quic_set_path_timer(ngx_connection_t *c)
  493. {
  494.     ngx_msec_t              now;
  495.     ngx_queue_t            *q;
  496.     ngx_msec_int_t          left, next;
  497.     ngx_quic_path_t        *path;
  498.     ngx_quic_connection_t  *qc;

  499.     qc = ngx_quic_get_connection(c);

  500.     now = ngx_current_msec;
  501.     next = -1;

  502.     for (q = ngx_queue_head(&qc->paths);
  503.          q != ngx_queue_sentinel(&qc->paths);
  504.          q = ngx_queue_next(q))
  505.     {
  506.         path = ngx_queue_data(q, ngx_quic_path_t, queue);

  507.         if (path->state == NGX_QUIC_PATH_IDLE) {
  508.             continue;
  509.         }

  510.         left = path->expires - now;
  511.         left = ngx_max(left, 1);

  512.         if (next == -1 || left < next) {
  513.             next = left;
  514.         }
  515.     }

  516.     if (next != -1) {
  517.         ngx_add_timer(&qc->path_validation, next);

  518.     } else if (qc->path_validation.timer_set) {
  519.         ngx_del_timer(&qc->path_validation);
  520.     }
  521. }


  522. void
  523. ngx_quic_path_handler(ngx_event_t *ev)
  524. {
  525.     ngx_msec_t              now;
  526.     ngx_queue_t            *q;
  527.     ngx_msec_int_t          left;
  528.     ngx_quic_path_t        *path;
  529.     ngx_connection_t       *c;
  530.     ngx_quic_connection_t  *qc;

  531.     c = ev->data;
  532.     qc = ngx_quic_get_connection(c);

  533.     now = ngx_current_msec;

  534.     q = ngx_queue_head(&qc->paths);

  535.     while (q != ngx_queue_sentinel(&qc->paths)) {

  536.         path = ngx_queue_data(q, ngx_quic_path_t, queue);
  537.         q = ngx_queue_next(q);

  538.         if (path->state == NGX_QUIC_PATH_IDLE) {
  539.             continue;
  540.         }

  541.         left = path->expires - now;

  542.         if (left > 0) {
  543.             continue;
  544.         }

  545.         switch (path->state) {
  546.         case NGX_QUIC_PATH_VALIDATING:
  547.             if (ngx_quic_expire_path_validation(c, path) != NGX_OK) {
  548.                 goto failed;
  549.             }

  550.             break;

  551.         case NGX_QUIC_PATH_WAITING:
  552.             if (ngx_quic_expire_path_mtu_delay(c, path) != NGX_OK) {
  553.                 goto failed;
  554.             }

  555.             break;

  556.         case NGX_QUIC_PATH_MTUD:
  557.             if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) {
  558.                 goto failed;
  559.             }

  560.             break;

  561.         default:
  562.             break;
  563.         }
  564.     }

  565.     ngx_quic_set_path_timer(c);

  566.     return;

  567. failed:

  568.     ngx_quic_close_connection(c, NGX_ERROR);
  569. }


  570. static ngx_int_t
  571. ngx_quic_expire_path_validation(ngx_connection_t *c, ngx_quic_path_t *path)
  572. {
  573.     ngx_msec_int_t          pto;
  574.     ngx_quic_path_t        *bkp;
  575.     ngx_quic_send_ctx_t    *ctx;
  576.     ngx_quic_connection_t  *qc;

  577.     qc = ngx_quic_get_connection(c);
  578.     ctx = ngx_quic_get_send_ctx(qc, NGX_QUIC_ENCRYPTION_APPLICATION);

  579.     if (++path->tries < NGX_QUIC_PATH_RETRIES) {
  580.         pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries;
  581.         path->expires = ngx_current_msec + pto;

  582.         (void) ngx_quic_send_path_challenge(c, path);

  583.         return NGX_OK;
  584.     }

  585.     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
  586.                    "quic path seq:%uL validation failed", path->seqnum);

  587.     /* found expired path */

  588.     path->validated = 0;


  589.     /* RFC 9000, 9.3.2.  On-Path Address Spoofing
  590.      *
  591.      * To protect the connection from failing due to such a spurious
  592.      * migration, an endpoint MUST revert to using the last validated
  593.      * peer address when validation of a new peer address fails.
  594.      */

  595.     if (qc->path == path) {
  596.         /* active path validation failed */

  597.         bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);

  598.         if (bkp == NULL) {
  599.             qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
  600.             qc->error_reason = "no viable path";
  601.             return NGX_ERROR;
  602.         }

  603.         qc->path = bkp;
  604.         qc->path->tag = NGX_QUIC_PATH_ACTIVE;

  605.         ngx_log_error(NGX_LOG_INFO, c->log, 0,
  606.                       "quic path seq:%uL addr:%V is restored from backup",
  607.                       qc->path->seqnum, &qc->path->addr_text);

  608.         ngx_quic_path_dbg(c, "is active", qc->path);
  609.     }

  610.     return ngx_quic_free_path(c, path);
  611. }


  612. static ngx_int_t
  613. ngx_quic_expire_path_mtu_delay(ngx_connection_t *c, ngx_quic_path_t *path)
  614. {
  615.     ngx_int_t               rc;
  616.     ngx_uint_t              i;
  617.     ngx_msec_t              pto;
  618.     ngx_quic_send_ctx_t    *ctx;
  619.     ngx_quic_connection_t  *qc;

  620.     qc = ngx_quic_get_connection(c);
  621.     ctx = ngx_quic_get_send_ctx(qc, NGX_QUIC_ENCRYPTION_APPLICATION);

  622.     path->tries = 0;

  623.     for ( ;; ) {

  624.         for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) {
  625.             path->mtu_pnum[i] = NGX_QUIC_UNSET_PN;
  626.         }

  627.         rc = ngx_quic_send_path_mtu_probe(c, path);

  628.         if (rc == NGX_ERROR) {
  629.             return NGX_ERROR;
  630.         }

  631.         if (rc == NGX_OK) {
  632.             pto = ngx_quic_pto(c, ctx);
  633.             path->expires = ngx_current_msec + pto;
  634.             path->state = NGX_QUIC_PATH_MTUD;
  635.             return NGX_OK;
  636.         }

  637.         /* rc == NGX_DECLINED */

  638.         path->max_mtu = path->mtud;

  639.         if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) {
  640.             path->state = NGX_QUIC_PATH_IDLE;
  641.             return NGX_OK;
  642.         }

  643.         path->mtud = (path->mtu + path->max_mtu) / 2;
  644.     }
  645. }


  646. static ngx_int_t
  647. ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, ngx_quic_path_t *path)
  648. {
  649.     ngx_int_t               rc;
  650.     ngx_msec_int_t          pto;
  651.     ngx_quic_send_ctx_t    *ctx;
  652.     ngx_quic_connection_t  *qc;

  653.     qc = ngx_quic_get_connection(c);
  654.     ctx = ngx_quic_get_send_ctx(qc, NGX_QUIC_ENCRYPTION_APPLICATION);

  655.     if (++path->tries < NGX_QUIC_PATH_RETRIES) {
  656.         rc = ngx_quic_send_path_mtu_probe(c, path);

  657.         if (rc == NGX_ERROR) {
  658.             return NGX_ERROR;
  659.         }

  660.         if (rc == NGX_OK) {
  661.             pto = ngx_quic_pto(c, ctx) << path->tries;
  662.             path->expires = ngx_current_msec + pto;
  663.             return NGX_OK;
  664.         }

  665.         /* rc == NGX_DECLINED */
  666.     }

  667.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  668.                    "quic path seq:%uL expired mtu:%uz",
  669.                    path->seqnum, path->mtud);

  670.     path->max_mtu = path->mtud;

  671.     ngx_quic_discover_path_mtu(c, path);

  672.     return NGX_OK;
  673. }


  674. static ngx_int_t
  675. ngx_quic_send_path_mtu_probe(ngx_connection_t *c, ngx_quic_path_t *path)
  676. {
  677.     size_t                  mtu;
  678.     uint64_t                pnum;
  679.     ngx_int_t               rc;
  680.     ngx_uint_t              log_error;
  681.     ngx_quic_frame_t       *frame;
  682.     ngx_quic_send_ctx_t    *ctx;
  683.     ngx_quic_connection_t  *qc;

  684.     frame = ngx_quic_alloc_frame(c);
  685.     if (frame == NULL) {
  686.         return NGX_ERROR;
  687.     }

  688.     frame->level = NGX_QUIC_ENCRYPTION_APPLICATION;
  689.     frame->type = NGX_QUIC_FT_PING;
  690.     frame->ignore_loss = 1;
  691.     frame->ignore_congestion = 1;

  692.     qc = ngx_quic_get_connection(c);
  693.     ctx = ngx_quic_get_send_ctx(qc, NGX_QUIC_ENCRYPTION_APPLICATION);
  694.     pnum = ctx->pnum;

  695.     ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
  696.                    "quic path seq:%uL send probe "
  697.                    "mtu:%uz pnum:%uL tries:%ui",
  698.                    path->seqnum, path->mtud, ctx->pnum, path->tries);

  699.     log_error = c->log_error;
  700.     c->log_error = NGX_ERROR_IGNORE_EMSGSIZE;

  701.     mtu = path->mtu;
  702.     path->mtu = path->mtud;

  703.     rc = ngx_quic_frame_sendto(c, frame, path->mtud, path);

  704.     path->mtu = mtu;
  705.     c->log_error = log_error;

  706.     if (rc == NGX_OK) {
  707.         path->mtu_pnum[path->tries] = pnum;
  708.         return NGX_OK;
  709.     }

  710.     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  711.                    "quic path seq:%uL rejected mtu:%uz",
  712.                    path->seqnum, path->mtud);

  713.     if (rc == NGX_ERROR) {
  714.         if (c->write->error) {
  715.             c->write->error = 0;
  716.             return NGX_DECLINED;
  717.         }

  718.         return NGX_ERROR;
  719.     }

  720.     return NGX_OK;
  721. }


  722. ngx_int_t
  723. ngx_quic_handle_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path,
  724.     uint64_t min, uint64_t max)
  725. {
  726.     uint64_t    pnum;
  727.     ngx_uint_t  i;

  728.     if (path->state != NGX_QUIC_PATH_MTUD) {
  729.         return NGX_OK;
  730.     }

  731.     for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) {
  732.         pnum = path->mtu_pnum[i];

  733.         if (pnum == NGX_QUIC_UNSET_PN) {
  734.             continue;
  735.         }

  736.         if (pnum < min || pnum > max) {
  737.             continue;
  738.         }

  739.         path->mtu = path->mtud;

  740.         ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
  741.                        "quic path seq:%uL ack mtu:%uz",
  742.                        path->seqnum, path->mtu);

  743.         ngx_quic_discover_path_mtu(c, path);

  744.         break;
  745.     }

  746.     return NGX_OK;
  747. }