[PATCH 4 of 4] QUIC: path MTU discovery
Roman Arutyunyan
arut at nginx.com
Thu Aug 10 10:21:04 UTC 2023
On Tue, Aug 08, 2023 at 01:49:46PM +0400, Sergey Kandaurov wrote:
Hi,
>
> > On 31 Jul 2023, at 21:34, Roman Arutyunyan <arut at nginx.com> wrote:
> >
> > Hi,
> >
> > On Fri, Jul 28, 2023 at 07:51:22PM +0400, Sergey Kandaurov wrote:
> >>
> >>> On 6 Jul 2023, at 17:57, Roman Arutyunyan <arut at nginx.com> wrote:
> >>>
> >>> # HG changeset patch
> >>> # User Roman Arutyunyan <arut at nginx.com>
> >>> # Date 1688481216 -14400
> >>> # Tue Jul 04 18:33:36 2023 +0400
> >>> # Node ID 1a49fd5d2013b6c8d50263499e6ced440927e949
> >>> # Parent a1ea543d009311765144351b2557c00c8e6445bf
> >>> QUIC: path MTU discovery.
> >>>
> >>> MTU selection starts by stepping up until the first failure. Then binary
> >>> search is used to find the path MTU.
> >>>
> >>> diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c
> >>> --- a/src/core/ngx_connection.c
> >>> +++ b/src/core/ngx_connection.c
> >>> @@ -1583,6 +1583,10 @@ ngx_connection_error(ngx_connection_t *c
> >>> }
> >>> #endif
> >>>
> >>> + if (err == NGX_EMSGSIZE && c->log_error == NGX_ERROR_IGNORE_EMSGSIZE) {
> >>> + return 0;
> >>> + }
> >>> +
> >>> if (err == 0
> >>> || err == NGX_ECONNRESET
> >>> #if (NGX_WIN32)
> >>> @@ -1600,6 +1604,7 @@ ngx_connection_error(ngx_connection_t *c
> >>> {
> >>> switch (c->log_error) {
> >>>
> >>> + case NGX_ERROR_IGNORE_EMSGSIZE:
> >>> case NGX_ERROR_IGNORE_EINVAL:
> >>> case NGX_ERROR_IGNORE_ECONNRESET:
> >>> case NGX_ERROR_INFO:
> >>> diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h
> >>> --- a/src/core/ngx_connection.h
> >>> +++ b/src/core/ngx_connection.h
> >>> @@ -97,7 +97,8 @@ typedef enum {
> >>> NGX_ERROR_ERR,
> >>> NGX_ERROR_INFO,
> >>> NGX_ERROR_IGNORE_ECONNRESET,
> >>> - NGX_ERROR_IGNORE_EINVAL
> >>> + NGX_ERROR_IGNORE_EINVAL,
> >>> + NGX_ERROR_IGNORE_EMSGSIZE
> >>> } ngx_connection_log_error_e;
> >>>
> >>>
> >>> diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c
> >>> --- a/src/event/quic/ngx_event_quic.c
> >>> +++ b/src/event/quic/ngx_event_quic.c
> >>> @@ -149,11 +149,6 @@ ngx_quic_apply_transport_params(ngx_conn
> >>> ngx_log_error(NGX_LOG_INFO, c->log, 0,
> >>> "quic maximum packet size is invalid");
> >>> return NGX_ERROR;
> >>> -
> >>> - } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) {
> >>> - ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
> >>> - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> - "quic client maximum packet size truncated");
> >>> }
> >>>
> >>> if (ctp->active_connection_id_limit < 2) {
> >>> @@ -286,7 +281,7 @@ ngx_quic_new_connection(ngx_connection_t
> >>>
> >>> qc->path_validation.log = c->log;
> >>> qc->path_validation.data = c;
> >>> - qc->path_validation.handler = ngx_quic_path_validation_handler;
> >>> + qc->path_validation.handler = ngx_quic_path_handler;
> >>>
> >>> qc->conf = conf;
> >>>
> >>> @@ -297,7 +292,7 @@ ngx_quic_new_connection(ngx_connection_t
> >>> ctp = &qc->ctp;
> >>>
> >>> /* defaults to be used before actual client parameters are received */
> >>> - ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
> >>> + ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE;
> >>> ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
> >>> ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
> >>> ctp->active_connection_id_limit = 2;
> >>> diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c
> >>> --- a/src/event/quic/ngx_event_quic_ack.c
> >>> +++ b/src/event/quic/ngx_event_quic_ack.c
> >>> @@ -229,6 +229,12 @@ ngx_quic_handle_ack_frame_range(ngx_conn
> >>>
> >>> qc = ngx_quic_get_connection(c);
> >>>
> >>> + if (ctx->level == ssl_encryption_application) {
> >>> + if (ngx_quic_handle_path_mtu_ack(c, qc->path, min, max) != NGX_OK) {
> >>> + return NGX_ERROR;
> >>> + }
> >>> + }
> >>> +
> >>> st->max_pn = NGX_TIMER_INFINITE;
> >>> found = 0;
> >>>
> >>> diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h
> >>> --- a/src/event/quic/ngx_event_quic_connection.h
> >>> +++ b/src/event/quic/ngx_event_quic_connection.h
> >>> @@ -66,6 +66,14 @@ typedef struct ngx_quic_keys_s ng
> >>> #define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp))
> >>>
> >>>
> >>> +typedef enum {
> >>> + NGX_QUIC_PATH_IDLE = 0,
> >>> + NGX_QUIC_PATH_VALIDATING,
> >>> + NGX_QUIC_PATH_WAITING,
> >>> + NGX_QUIC_PATH_DISCOVERING_MTU
> >>
> >> Nitpicking on too long name.
> >> What about NGX_QUIC_PATH_MTUD or something.
> >
> > OK, renamed.
> >
> >>> +} ngx_quic_path_state_e;
> >>> +
> >>> +
> >>> struct ngx_quic_client_id_s {
> >>> ngx_queue_t queue;
> >>> uint64_t seqnum;
> >>> @@ -89,18 +97,22 @@ struct ngx_quic_path_s {
> >>> ngx_sockaddr_t sa;
> >>> socklen_t socklen;
> >>> ngx_quic_client_id_t *cid;
> >>> + ngx_quic_path_state_e state;
> >>> ngx_msec_t expires;
> >>> ngx_uint_t tries;
> >>> ngx_uint_t tag;
> >>> + size_t mtu;
> >>> + size_t mtud;
> >>> + size_t max_mtu;
> >>> off_t sent;
> >>> off_t received;
> >>> u_char challenge1[8];
> >>> u_char challenge2[8];
> >>> uint64_t seqnum;
> >>> + uint64_t mtu_pnum[NGX_QUIC_PATH_RETRIES];
> >>> ngx_str_t addr_text;
> >>> u_char text[NGX_SOCKADDR_STRLEN];
> >>> - unsigned validated:1;
> >>> - unsigned validating:1;
> >>> + ngx_uint_t validated; /* unsigned validated:1; */
> >>> };
> >>>
> >>>
> >>> diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c
> >>> --- a/src/event/quic/ngx_event_quic_migration.c
> >>> +++ b/src/event/quic/ngx_event_quic_migration.c
> >>> @@ -10,6 +10,11 @@
> >>> #include <ngx_event_quic_connection.h>
> >>>
> >>>
> >>> +#define NGX_QUIC_PATH_MTU_DELAY 1000
> >>
> >> The value looks too much,
> >> it would delay each subsequent probe (which is not retry).
> >
> > Probably the right value should be determined in real-life scenarios.
>
> I'm quite skeptical about delayed probes, it has little to no benefit
> for short connections with bulky transfers, as implemented, especially
> with short MTU steps. OTOH, immediate probes can lead to congestion
> window starvation resulting in request processing slow down.
>
> For the record,
> https://www.ietf.org/proceedings/62/pmtud.html provides a discussion
> about PMTUD, in particular, on search strategy and when to probe.
> For the latter, it seems to articulate an opposite view, i.e. to send
> probes ASAP. Also, it raises a question how short lived connections
> could benefit from PMTUD, e.g. by caching path MTU, which seems to
> correspond to a shared PLPMTU state in RFC 8899.
>
> > Let's decrease the value by a factor of 10 for now.
>
> For a simple implementation, 100ms is a good enough trade-off, IMHO.
>
> >
> >>> +#define NGX_QUIC_PATH_MTU_STEP 256
> >>> +#define NGX_QUIC_PATH_MTU_PRECISION 16
> >>> +
> >>> +
> >>> static void ngx_quic_set_connection_path(ngx_connection_t *c,
> >>> ngx_quic_path_t *path);
> >>> static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c,
> >>> @@ -17,7 +22,15 @@ static ngx_int_t ngx_quic_validate_path(
> >>> static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
> >>> ngx_quic_path_t *path);
> >>> static void ngx_quic_set_path_timer(ngx_connection_t *c);
> >>> +static ngx_int_t ngx_quic_expire_path_validation(ngx_connection_t *c,
> >>> + ngx_quic_path_t *path);
> >>> +static ngx_int_t ngx_quic_expire_path_mtu_delay(ngx_connection_t *c,
> >>> + ngx_quic_path_t *path);
> >>> +static ngx_int_t ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c,
> >>> + ngx_quic_path_t *path);
> >>> static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
> >>> +static ngx_int_t ngx_quic_send_path_mtu_probe(ngx_connection_t *c,
> >>> + ngx_quic_path_t *path);
> >>>
> >>>
> >>> ngx_int_t
> >>> @@ -97,7 +110,7 @@ ngx_quic_handle_path_response_frame(ngx_
> >>> {
> >>> path = ngx_queue_data(q, ngx_quic_path_t, queue);
> >>>
> >>> - if (!path->validating) {
> >>> + if (path->state != NGX_QUIC_PATH_VALIDATING) {
> >>> continue;
> >>> }
> >>>
> >>> @@ -136,6 +149,9 @@ valid:
> >>> {
> >>> /* address did not change */
> >>> rst = 0;
> >>> +
> >>> + path->mtu = prev->mtu;
> >>> + path->max_mtu = prev->max_mtu;
> >>> }
> >>> }
> >>>
> >>> @@ -167,9 +183,8 @@ valid:
> >>> ngx_quic_path_dbg(c, "is validated", path);
> >>>
> >>> path->validated = 1;
> >>> - path->validating = 0;
> >>>
> >>> - ngx_quic_set_path_timer(c);
> >>> + ngx_quic_discover_path_mtu(c, path);
> >>>
> >>> return NGX_OK;
> >>> }
> >>> @@ -217,6 +232,8 @@ ngx_quic_new_path(ngx_connection_t *c,
> >>> path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
> >>> NGX_SOCKADDR_STRLEN, 1);
> >>>
> >>> + path->mtu = NGX_QUIC_MIN_INITIAL_SIZE;
> >>> +
> >>> ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> "quic path seq:%uL created addr:%V",
> >>> path->seqnum, &path->addr_text);
> >>> @@ -464,7 +481,7 @@ ngx_quic_handle_migration(ngx_connection
> >>>
> >>> ngx_quic_set_connection_path(c, next);
> >>>
> >>> - if (!next->validated && !next->validating) {
> >>> + if (!next->validated && next->state != NGX_QUIC_PATH_VALIDATING) {
> >>> if (ngx_quic_validate_path(c, next) != NGX_OK) {
> >>> return NGX_ERROR;
> >>> }
> >>> @@ -492,7 +509,6 @@ ngx_quic_validate_path(ngx_connection_t
> >>> ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> "quic initiated validation of path seq:%uL", path->seqnum);
> >>>
> >>> - path->validating = 1;
> >>> path->tries = 0;
> >>>
> >>> if (RAND_bytes(path->challenge1, 8) != 1) {
> >>> @@ -511,6 +527,7 @@ ngx_quic_validate_path(ngx_connection_t
> >>> pto = ngx_max(ngx_quic_pto(c, ctx), 1000);
> >>>
> >>> path->expires = ngx_current_msec + pto;
> >>> + path->state = NGX_QUIC_PATH_VALIDATING;
> >>>
> >>> ngx_quic_set_path_timer(c);
> >>>
> >>> @@ -558,6 +575,42 @@ ngx_quic_send_path_challenge(ngx_connect
> >>> }
> >>>
> >>>
> >>> +void
> >>> +ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path)
> >>> +{
> >>> + ngx_quic_connection_t *qc;
> >>> +
> >>> + qc = ngx_quic_get_connection(c);
> >>> +
> >>> + if (path->max_mtu) {
> >>> + if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) {
> >>> + path->state = NGX_QUIC_PATH_IDLE;
> >>> + ngx_quic_set_path_timer(c);
> >>> + return;
> >>> + }
> >>> +
> >>> + path->mtud = (path->mtu + path->max_mtu) / 2;
> >>> +
> >>> + } else {
> >>> + path->mtud = path->mtu + NGX_QUIC_PATH_MTU_STEP;
> >>
> >> I like this approach, compared to multiplying probe steps.
> >> This better fits to the pattern to keep around a well-known
> >> initial value, which is typically 1500 bytes.
> >> Another one could be to let user specify its own initial
> >> value for cases when he knows well its network environment.
> >>
> >>> +
> >>> + if (path->mtud >= qc->ctp.max_udp_payload_size) {
> >>> + path->mtud = qc->ctp.max_udp_payload_size;
> >>> + path->max_mtu = qc->ctp.max_udp_payload_size;
> >>> + }
> >>> + }
> >>> +
> >>> + path->state = NGX_QUIC_PATH_WAITING;
> >>> + path->expires = ngx_current_msec + NGX_QUIC_PATH_MTU_DELAY;
> >>> +
> >>> + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> + "quic path seq:%uL schedule mtu:%uz",
> >>> + path->seqnum, path->mtud);
> >>> +
> >>> + ngx_quic_set_path_timer(c);
> >>> +}
> >>> +
> >>> +
> >>> static void
> >>> ngx_quic_set_path_timer(ngx_connection_t *c)
> >>> {
> >>> @@ -578,7 +631,7 @@ ngx_quic_set_path_timer(ngx_connection_t
> >>> {
> >>> path = ngx_queue_data(q, ngx_quic_path_t, queue);
> >>>
> >>> - if (!path->validating) {
> >>> + if (path->state == NGX_QUIC_PATH_IDLE) {
> >>> continue;
> >>> }
> >>>
> >>> @@ -600,22 +653,18 @@ ngx_quic_set_path_timer(ngx_connection_t
> >>>
> >>>
> >>> void
> >>> -ngx_quic_path_validation_handler(ngx_event_t *ev)
> >>> +ngx_quic_path_handler(ngx_event_t *ev)
> >>> {
> >>> ngx_msec_t now;
> >>> ngx_queue_t *q;
> >>> - ngx_msec_int_t left, next, pto;
> >>> - ngx_quic_path_t *path, *bkp;
> >>> + ngx_msec_int_t left;
> >>> + ngx_quic_path_t *path;
> >>> ngx_connection_t *c;
> >>> - ngx_quic_send_ctx_t *ctx;
> >>> ngx_quic_connection_t *qc;
> >>>
> >>> c = ev->data;
> >>> qc = ngx_quic_get_connection(c);
> >>>
> >>> - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
> >>> -
> >>> - next = -1;
> >>> now = ngx_current_msec;
> >>>
> >>> q = ngx_queue_head(&qc->paths);
> >>> @@ -625,83 +674,274 @@ ngx_quic_path_validation_handler(ngx_eve
> >>> path = ngx_queue_data(q, ngx_quic_path_t, queue);
> >>> q = ngx_queue_next(q);
> >>>
> >>> - if (!path->validating) {
> >>> + if (path->state == NGX_QUIC_PATH_IDLE) {
> >>> continue;
> >>> }
> >>>
> >>> left = path->expires - now;
> >>>
> >>> if (left > 0) {
> >>> -
> >>> - if (next == -1 || left < next) {
> >>> - next = left;
> >>> - }
> >>> -
> >>> - continue;
> >>> - }
> >>> -
> >>> - if (++path->tries < NGX_QUIC_PATH_RETRIES) {
> >>> - pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries;
> >>> -
> >>> - path->expires = ngx_current_msec + pto;
> >>> -
> >>> - if (next == -1 || pto < next) {
> >>> - next = pto;
> >>> - }
> >>> -
> >>> - /* retransmit */
> >>> - (void) ngx_quic_send_path_challenge(c, path);
> >>> -
> >>> continue;
> >>> }
> >>>
> >>> - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
> >>> - "quic path seq:%uL validation failed", path->seqnum);
> >>> + switch (path->state) {
> >>> + case NGX_QUIC_PATH_VALIDATING:
> >>> + if (ngx_quic_expire_path_validation(c, path) != NGX_OK) {
> >>> + goto failed;
> >>> + }
> >>> +
> >>> + break;
> >>> +
> >>> + case NGX_QUIC_PATH_WAITING:
> >>> + if (ngx_quic_expire_path_mtu_delay(c, path) != NGX_OK) {
> >>> + goto failed;
> >>> + }
> >>> +
> >>> + break;
> >>> +
> >>> + case NGX_QUIC_PATH_DISCOVERING_MTU:
> >>> + if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) {
> >>> + goto failed;
> >>> + }
> >>> +
> >>> + break;
> >>> +
> >>
> >> indentation in several places here
> >
> > Fixed, thanks.
> >
> >>> + default:
> >>> + break;
> >>> + }
> >>> + }
> >>> +
> >>> + ngx_quic_set_path_timer(c);
> >>> +
> >>> + return;
> >>>
> >>> - /* found expired path */
> >>> +failed:
> >>> +
> >>> + ngx_quic_close_connection(c, NGX_ERROR);
> >>> +}
> >>> +
> >>> +
> >>> +static ngx_int_t
> >>> +ngx_quic_expire_path_validation(ngx_connection_t *c, ngx_quic_path_t *path)
> >>> +{
> >>> + ngx_msec_int_t pto;
> >>> + ngx_quic_path_t *bkp;
> >>> + ngx_quic_send_ctx_t *ctx;
> >>> + ngx_quic_connection_t *qc;
> >>>
> >>> - path->validated = 0;
> >>> - path->validating = 0;
> >>> + qc = ngx_quic_get_connection(c);
> >>> + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
> >>> +
> >>> + if (++path->tries < NGX_QUIC_PATH_RETRIES) {
> >>> + pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries;
> >>> + path->expires = ngx_current_msec + pto;
> >>> +
> >>> + (void) ngx_quic_send_path_challenge(c, path);
> >>> +
> >>> + return NGX_OK;
> >>> + }
> >>> +
> >>> + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> + "quic path seq:%uL validation failed", path->seqnum);
> >>> +
> >>> + /* found expired path */
> >>> +
> >>> + path->validated = 0;
> >>>
> >>>
> >>> - /* RFC 9000, 9.3.2. On-Path Address Spoofing
> >>> - *
> >>> - * To protect the connection from failing due to such a spurious
> >>> - * migration, an endpoint MUST revert to using the last validated
> >>> - * peer address when validation of a new peer address fails.
> >>> - */
> >>> -
> >>> - if (qc->path == path) {
> >>> - /* active path validation failed */
> >>> -
> >>> - bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
> >>> + /* RFC 9000, 9.3.2. On-Path Address Spoofing
> >>> + *
> >>> + * To protect the connection from failing due to such a spurious
> >>> + * migration, an endpoint MUST revert to using the last validated
> >>> + * peer address when validation of a new peer address fails.
> >>> + */
> >>>
> >>> - if (bkp == NULL) {
> >>> - qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
> >>> - qc->error_reason = "no viable path";
> >>> - ngx_quic_close_connection(c, NGX_ERROR);
> >>> - return;
> >>> - }
> >>> + if (qc->path == path) {
> >>> + /* active path validation failed */
> >>> +
> >>> + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
> >>>
> >>> - qc->path = bkp;
> >>> - qc->path->tag = NGX_QUIC_PATH_ACTIVE;
> >>> -
> >>> - ngx_quic_set_connection_path(c, qc->path);
> >>> -
> >>> - ngx_log_error(NGX_LOG_INFO, c->log, 0,
> >>> - "quic path seq:%uL addr:%V is restored from backup",
> >>> - qc->path->seqnum, &qc->path->addr_text);
> >>> -
> >>> - ngx_quic_path_dbg(c, "is active", qc->path);
> >>> + if (bkp == NULL) {
> >>> + qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
> >>> + qc->error_reason = "no viable path";
> >>> + return NGX_ERROR;
> >>> }
> >>>
> >>> - if (ngx_quic_free_path(c, path) != NGX_OK) {
> >>> - ngx_quic_close_connection(c, NGX_ERROR);
> >>> - return;
> >>> + qc->path = bkp;
> >>> + qc->path->tag = NGX_QUIC_PATH_ACTIVE;
> >>> +
> >>> + ngx_quic_set_connection_path(c, qc->path);
> >>> +
> >>> + ngx_log_error(NGX_LOG_INFO, c->log, 0,
> >>> + "quic path seq:%uL addr:%V is restored from backup",
> >>> + qc->path->seqnum, &qc->path->addr_text);
> >>> +
> >>> + ngx_quic_path_dbg(c, "is active", qc->path);
> >>> + }
> >>> +
> >>> + return ngx_quic_free_path(c, path);
> >>> +}
> >>> +
> >>> +
> >>> +static ngx_int_t
> >>> +ngx_quic_expire_path_mtu_delay(ngx_connection_t *c, ngx_quic_path_t *path)
> >>> +{
> >>> + ngx_int_t rc;
> >>> + ngx_uint_t i;
> >>> + ngx_msec_t pto;
> >>> + ngx_quic_send_ctx_t *ctx;
> >>> + ngx_quic_connection_t *qc;
> >>> +
> >>> + qc = ngx_quic_get_connection(c);
> >>> + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
> >>> +
> >>> + path->tries = 0;
> >>> +
> >>> + for ( ;; ) {
> >>> +
> >>> + for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) {
> >>> + path->mtu_pnum[i] = NGX_QUIC_UNSET_PN;
> >>> + }
> >>> +
> >>> + rc = ngx_quic_send_path_mtu_probe(c, path);
> >>> +
> >>> + if (rc == NGX_ERROR) {
> >>> + return NGX_ERROR;
> >>> + }
> >>> +
> >>> + if (rc == NGX_OK) {
> >>> + pto = ngx_quic_pto(c, ctx);
> >>> + path->expires = ngx_current_msec + pto;
> >>> + path->state = NGX_QUIC_PATH_DISCOVERING_MTU;
> >>> + return NGX_OK;
> >>> + }
> >>> +
> >>> + /* rc == NGX_DECLINED */
> >>> +
> >>> + path->max_mtu = path->mtud;
> >>> +
> >>> + if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) {
> >>> + path->state = NGX_QUIC_PATH_IDLE;
> >>> + return NGX_OK;
> >>> + }
> >>> +
> >>> + path->mtud = (path->mtu + path->max_mtu) / 2;
> >>> + }
> >>> +}
> >>> +
> >>> +
> >>> +static ngx_int_t
> >>> +ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, ngx_quic_path_t *path)
> >>> +{
> >>> + ngx_int_t rc;
> >>> + ngx_msec_int_t pto;
> >>> + ngx_quic_send_ctx_t *ctx;
> >>> + ngx_quic_connection_t *qc;
> >>> +
> >>> + qc = ngx_quic_get_connection(c);
> >>> + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
> >>> +
> >>> + if (++path->tries < NGX_QUIC_PATH_RETRIES) {
> >>> + pto = ngx_quic_pto(c, ctx) << path->tries;
> >>> + path->expires = ngx_current_msec + pto;
> >>> +
> >>
> >> Setting path->expires there seems useless.
> >> It don't seem to be used in ngx_quic_send_path_mtu_probe(),
> >> and ngx_quic_discover_path_mtu() used to reset path->expires.
> >
> > If ngx_quic_send_path_mtu_probe() below returns NGX_OK, the path should stay
> > in the same state, but expiration time should be advanced. To make it
> > more obvious, I changed this code.
>
> Updated code makes it clear, thanks.
>
> >
> >>> + rc = ngx_quic_send_path_mtu_probe(c, path);
> >>> + if (rc != NGX_DECLINED) {
> >>> + return rc;
> >>> }
> >>> }
> >>>
> >>> - if (next != -1) {
> >>> - ngx_add_timer(&qc->path_validation, next);
> >>> + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> + "quic path seq:%uL expired mtu:%uz",
> >>> + path->seqnum, path->mtud);
> >>> +
> >>> + path->max_mtu = path->mtud;
> >>> +
> >>> + ngx_quic_discover_path_mtu(c, path);
> >>> +
> >>> + return NGX_OK;
> >>> +}
> >>> +
> >>> +
> >>> +static ngx_int_t
> >>> +ngx_quic_send_path_mtu_probe(ngx_connection_t *c, ngx_quic_path_t *path)
> >>> +{
> >>> + ngx_int_t rc;
> >>> + ngx_uint_t log_error;
> >>> + ngx_quic_frame_t frame;
> >>> + ngx_quic_send_ctx_t *ctx;
> >>> + ngx_quic_connection_t *qc;
> >>> +
> >>> + ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
> >>> +
> >>> + frame.level = ssl_encryption_application;
> >>> + frame.type = NGX_QUIC_FT_PING;
> >>> +
> >>> + qc = ngx_quic_get_connection(c);
> >>> + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
> >>> + path->mtu_pnum[path->tries] = ctx->pnum;
> >>> +
> >>> + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> + "quic path seq:%uL send probe "
> >>> + "mtu:%uz pnum:%uL tries:%ui",
> >>> + path->seqnum, path->mtud, ctx->pnum, path->tries);
> >>> +
> >>> + log_error = c->log_error;
> >>> + c->log_error = NGX_ERROR_IGNORE_EMSGSIZE;
> >>> +
> >>> + rc = ngx_quic_frame_sendto(c, &frame, path->mtud, path);
> >>> + c->log_error = log_error;
> >>> +
> >>> + if (rc == NGX_ERROR) {
> >>> + if (c->write->error) {
> >>> + c->write->error = 0;
> >>> +
> >>> + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> + "quic path seq:%uL rejected mtu:%uz",
> >>> + path->seqnum, path->mtud);
> >>> +
> >>> + return NGX_DECLINED;
> >>
> >> The error handling looks awkward to decline on write error.
> >> ngx_sendmsg() sets write error on fatal sendmsg errors,
> >> and keeps it unset NGX_EAGAIN and NGX_EINTR, which
> >> are typically non-fatal transient errors.
> >> I understand this is done mostly to ignore EMSGSIZE,
> >> maybe it makes sense to ignore the rest sendmsg() errors.
> >
> > The problem is, the error doesn't make it to this point. All be have here is
> > NGX_ERROR and c->write->error. I think, for PMTUD purposes we can safely
> > ignore the errors. It's hinghly likely, the error will repeat while doing
> > connection I/O and we'll shortly close the connection.
>
> Ok, I re-read ngx_sendmsg() to clarify the following:
> - NGX_EINTR is handled internally and never returned up to here;
> - NGX_EAGAIN (NGX_AGAIN) seems to be returned, at least on Linux,
> when send buffer is low for the MTU probe, converted to NGX_OK;
> - FTR, looking at sosend_dgram() on FreeBSD, sendmsg seems to
> return EMSGSIZE there, instead, on low socket buffer space;
> - other sendmsg errors are returned as NGX_ERROR, and
> c->write->error is set; this is converted to NGX_DECLINED;
> - non-sendmsg NGX_ERRORs appeared in ngx_quic_frame_sendto()
> are passed as is without conversion.
>
> So, the error handling as implemented to decline on any sendmsg
> "fatal" errors actually makes sense, if we'd like to avoid rewriting
> ngx_sendmsg() to further distinguish sendmsg errors, notably EMSGSIZE.
> The downside is that connection may ignore actually fatal errors,
> though the error will likely repeat on sending non-probing frames.
>
> NGX_AGAIN conversion to NGX_OK is questionable, because this
> makes a probe considered successfully sent while it is not.
> I wonder though what we can do about that other than to simply
> ignore, similar to other ngx_quic_frame_sendto() callers,
> because next time on a probe retry sendmsg can return success.
Ignoring certain send error for probes is not a big deal since timeout
expiration will lead to the same result eventually.
> >>> + }
> >>> +
> >>> + return NGX_ERROR;
> >>> }
> >>> +
> >>> + return NGX_OK;
> >>> }
> >>> +
> >>> +
> >>> +ngx_int_t
> >>> +ngx_quic_handle_path_mtu_ack(ngx_connection_t *c, ngx_quic_path_t *path,
> >>> + uint64_t min, uint64_t max)
> >>> +{
> >>> + uint64_t pnum;
> >>> + ngx_uint_t i;
> >>> +
> >>> + if (path->state != NGX_QUIC_PATH_DISCOVERING_MTU) {
> >>> + return NGX_OK;
> >>> + }
> >>> +
> >>> + for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) {
> >>> + pnum = path->mtu_pnum[i];
> >>> +
> >>> + if (pnum == NGX_QUIC_UNSET_PN) {
> >>> + break;
> >>> + }
> >>> +
> >>> + if (pnum < min || pnum > max) {
> >>> + continue;
> >>> + }
> >>> +
> >>> + path->mtu = path->mtud;
> >>> +
> >>> + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
> >>> + "quic path seq:%uL ack mtu:%uz",
> >>> + path->seqnum, path->mtu);
> >>> +
> >>> + ngx_quic_discover_path_mtu(c, path);
> >>> +
> >>> + break;
> >>
> >> What if two probes appeared ACKed in one range.
> >> Though that seems to be safe to break there, as two
> >> probes barely have different probe sizes, rather retries.
> >
> > No matter how many probes are acked, the next step is still the same.
> > So there's no reason to find all of them.
> >
>
> Ok, let's leave it as is.
>
> >>> + }
> >>> +
> >>> + return NGX_OK;
> >>> +}
> >>> diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h
> >>> --- a/src/event/quic/ngx_event_quic_migration.h
> >>> +++ b/src/event/quic/ngx_event_quic_migration.h
> >>> @@ -18,10 +18,10 @@
> >>> #define NGX_QUIC_PATH_BACKUP 2
> >>>
> >>> #define ngx_quic_path_dbg(c, msg, path) \
> >>> - ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, \
> >>> - "quic path seq:%uL %s sent:%O recvd:%O state:%s%s", \
> >>> + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \
> >>> + "quic path seq:%uL %s tx:%O rx:%O valid:%d st:%d mtu:%uz", \
> >>> path->seqnum, msg, path->sent, path->received, \
> >>> - path->validated ? "V": "N", path->validating ? "R": "");
> >>> + path->validated, path->state, path->mtu);
> >>>
> >>> ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
> >>> ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f);
> >>> @@ -36,6 +36,10 @@ ngx_int_t ngx_quic_set_path(ngx_connecti
> >>> ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c,
> >>> ngx_quic_header_t *pkt);
> >>>
> >>> -void ngx_quic_path_validation_handler(ngx_event_t *ev);
> >>> +void ngx_quic_path_handler(ngx_event_t *ev);
> >>> +
> >>> +void ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path);
> >>> +ngx_int_t ngx_quic_handle_path_mtu_ack(ngx_connection_t *c,
> >>> + ngx_quic_path_t *path, uint64_t min, uint64_t max);
> >>
> >> Nitpicking:
> >> maybe just ngx_quic_handle_path_mtu() ?
> >> This would match ngx_quic_discover_path_mtu() naming.
> >
> > OK, renamed.
> >
> >>>
> >>> #endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */
> >>> diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c
> >>> --- a/src/event/quic/ngx_event_quic_output.c
> >>> +++ b/src/event/quic/ngx_event_quic_output.c
> >>> @@ -10,9 +10,6 @@
> >>> #include <ngx_event_quic_connection.h>
> >>>
> >>>
> >>> -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252
> >>> -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232
> >>> -
> >>> #define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */
> >>> #define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */
> >>>
> >>> @@ -61,21 +58,6 @@ static size_t ngx_quic_path_limit(ngx_co
> >>> size_t size);
> >>>
> >>>
> >>> -size_t
> >>> -ngx_quic_max_udp_payload(ngx_connection_t *c)
> >>> -{
> >>> - /* TODO: path MTU discovery */
> >>> -
> >>> -#if (NGX_HAVE_INET6)
> >>> - if (c->sockaddr->sa_family == AF_INET6) {
> >>> - return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6;
> >>> - }
> >>> -#endif
> >>> -
> >>> - return NGX_QUIC_MAX_UDP_PAYLOAD_OUT;
> >>> -}
> >>> -
> >>> -
> >>> ngx_int_t
> >>> ngx_quic_output(ngx_connection_t *c)
> >>> {
> >>> @@ -142,10 +124,7 @@ ngx_quic_create_datagrams(ngx_connection
> >>>
> >>> p = dst;
> >>>
> >>> - len = ngx_min(qc->ctp.max_udp_payload_size,
> >>> - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
> >>> -
> >>> - len = ngx_quic_path_limit(c, path, len);
> >>> + len = ngx_quic_path_limit(c, path, path->mtu);
> >>>
> >>> pad = ngx_quic_get_padding_level(c);
> >>>
> >>> @@ -299,9 +278,7 @@ ngx_quic_allow_segmentation(ngx_connecti
> >>> ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
> >>>
> >>> bytes = 0;
> >>> -
> >>> - len = ngx_min(qc->ctp.max_udp_payload_size,
> >>> - NGX_QUIC_MAX_UDP_SEGMENT_BUF);
> >>> + len = ngx_min(qc->path->mtu, NGX_QUIC_MAX_UDP_SEGMENT_BUF);
> >>>
> >>> for (q = ngx_queue_head(&ctx->frames);
> >>> q != ngx_queue_sentinel(&ctx->frames);
> >>> @@ -345,8 +322,7 @@ ngx_quic_create_segments(ngx_connection_
> >>> return NGX_ERROR;
> >>> }
> >>>
> >>> - segsize = ngx_min(qc->ctp.max_udp_payload_size,
> >>> - NGX_QUIC_MAX_UDP_SEGMENT_BUF);
> >>> + segsize = ngx_min(path->mtu, NGX_QUIC_MAX_UDP_SEGMENT_BUF);
> >>> p = dst;
> >>> end = dst + sizeof(dst);
> >>>
> >>> diff --git a/src/event/quic/ngx_event_quic_output.h b/src/event/quic/ngx_event_quic_output.h
> >>> --- a/src/event/quic/ngx_event_quic_output.h
> >>> +++ b/src/event/quic/ngx_event_quic_output.h
> >>> @@ -12,8 +12,6 @@
> >>> #include <ngx_core.h>
> >>>
> >>>
> >>> -size_t ngx_quic_max_udp_payload(ngx_connection_t *c);
> >>> -
> >>> ngx_int_t ngx_quic_output(ngx_connection_t *c);
> >>>
> >>> ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c,
> >>> diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c
> >>> --- a/src/event/quic/ngx_event_quic_ssl.c
> >>> +++ b/src/event/quic/ngx_event_quic_ssl.c
> >>> @@ -494,6 +494,8 @@ ngx_quic_crypto_input(ngx_connection_t *
> >>> */
> >>> ngx_quic_discard_ctx(c, ssl_encryption_handshake);
> >>>
> >>> + ngx_quic_discover_path_mtu(c, qc->path);
> >>> +
> >>> /* start accepting clients on negotiated number of server ids */
> >>> if (ngx_quic_create_sockets(c) != NGX_OK) {
> >>> return NGX_ERROR;
> >>> diff --git a/src/os/unix/ngx_errno.h b/src/os/unix/ngx_errno.h
> >>> --- a/src/os/unix/ngx_errno.h
> >>> +++ b/src/os/unix/ngx_errno.h
> >>> @@ -54,6 +54,7 @@ typedef int ngx_err_t;
> >>> #define NGX_ENOMOREFILES 0
> >>> #define NGX_ELOOP ELOOP
> >>> #define NGX_EBADF EBADF
> >>> +#define NGX_EMSGSIZE EMSGSIZE
> >>>
> >>> #if (NGX_HAVE_OPENAT)
> >>> #define NGX_EMLINK EMLINK
>
> This will break on win32, an NGX_EMSGSIZE definition needs to be
> provided there, which is WSAEMSGSIZE.
Thanks, fixed.
> >
> > The diff is attached.
> >
>
> The diff looks good.
Also, with step-based mtu step updates, QUIC flood detection is triggered with
the h3_keepalive.t on interfaces with large mtu. I think, doubling mtu
is a safer choice.
Also, while looking in migration test logs, I thought it might be a good idea to
only handle validation/mtu events for the currently active path.
Including 3 patch updates:
- the last one + win32 fix + format fix in ngx_quic_path_dbg()
- x2 mtu
- single-path event handling
--
Roman Arutyunyan
-------------- next part --------------
# HG changeset patch
# User Roman Arutyunyan <arut at nginx.com>
# Date 1691659587 -14400
# Thu Aug 10 13:26:27 2023 +0400
# Node ID f32413c9248be3c14dae07453e95161b975066bd
# Parent 1f0ec6bca996556d100338b83de439ac702e5e44
[mq]: quic-pmtud-fix
diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c
--- a/src/event/quic/ngx_event_quic_ack.c
+++ b/src/event/quic/ngx_event_quic_ack.c
@@ -230,7 +230,7 @@ ngx_quic_handle_ack_frame_range(ngx_conn
qc = ngx_quic_get_connection(c);
if (ctx->level == ssl_encryption_application) {
- if (ngx_quic_handle_path_mtu_ack(c, qc->path, min, max) != NGX_OK) {
+ if (ngx_quic_handle_path_mtu(c, qc->path, min, max) != NGX_OK) {
return NGX_ERROR;
}
}
diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h
--- a/src/event/quic/ngx_event_quic_connection.h
+++ b/src/event/quic/ngx_event_quic_connection.h
@@ -70,7 +70,7 @@ typedef enum {
NGX_QUIC_PATH_IDLE = 0,
NGX_QUIC_PATH_VALIDATING,
NGX_QUIC_PATH_WAITING,
- NGX_QUIC_PATH_DISCOVERING_MTU
+ NGX_QUIC_PATH_MTUD
} ngx_quic_path_state_e;
diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c
--- a/src/event/quic/ngx_event_quic_migration.c
+++ b/src/event/quic/ngx_event_quic_migration.c
@@ -10,7 +10,7 @@
#include <ngx_event_quic_connection.h>
-#define NGX_QUIC_PATH_MTU_DELAY 1000
+#define NGX_QUIC_PATH_MTU_DELAY 100
#define NGX_QUIC_PATH_MTU_STEP 256
#define NGX_QUIC_PATH_MTU_PRECISION 16
@@ -686,7 +686,7 @@ ngx_quic_path_handler(ngx_event_t *ev)
switch (path->state) {
case NGX_QUIC_PATH_VALIDATING:
- if (ngx_quic_expire_path_validation(c, path) != NGX_OK) {
+ if (ngx_quic_expire_path_validation(c, path) != NGX_OK) {
goto failed;
}
@@ -699,17 +699,17 @@ ngx_quic_path_handler(ngx_event_t *ev)
break;
- case NGX_QUIC_PATH_DISCOVERING_MTU:
+ case NGX_QUIC_PATH_MTUD:
if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) {
goto failed;
- }
+ }
break;
default:
break;
- }
- }
+ }
+ }
ngx_quic_set_path_timer(c);
@@ -812,7 +812,7 @@ ngx_quic_expire_path_mtu_delay(ngx_conne
if (rc == NGX_OK) {
pto = ngx_quic_pto(c, ctx);
path->expires = ngx_current_msec + pto;
- path->state = NGX_QUIC_PATH_DISCOVERING_MTU;
+ path->state = NGX_QUIC_PATH_MTUD;
return NGX_OK;
}
@@ -842,13 +842,19 @@ ngx_quic_expire_path_mtu_discovery(ngx_c
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
if (++path->tries < NGX_QUIC_PATH_RETRIES) {
- pto = ngx_quic_pto(c, ctx) << path->tries;
- path->expires = ngx_current_msec + pto;
+ rc = ngx_quic_send_path_mtu_probe(c, path);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
- rc = ngx_quic_send_path_mtu_probe(c, path);
- if (rc != NGX_DECLINED) {
- return rc;
+ if (rc == NGX_OK) {
+ pto = ngx_quic_pto(c, ctx) << path->tries;
+ path->expires = ngx_current_msec + pto;
+ return NGX_OK;
}
+
+ /* rc == NGX_DECLINED */
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
@@ -911,13 +917,13 @@ ngx_quic_send_path_mtu_probe(ngx_connect
ngx_int_t
-ngx_quic_handle_path_mtu_ack(ngx_connection_t *c, ngx_quic_path_t *path,
+ngx_quic_handle_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path,
uint64_t min, uint64_t max)
{
uint64_t pnum;
ngx_uint_t i;
- if (path->state != NGX_QUIC_PATH_DISCOVERING_MTU) {
+ if (path->state != NGX_QUIC_PATH_MTUD) {
return NGX_OK;
}
diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h
--- a/src/event/quic/ngx_event_quic_migration.h
+++ b/src/event/quic/ngx_event_quic_migration.h
@@ -19,7 +19,7 @@
#define ngx_quic_path_dbg(c, msg, path) \
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \
- "quic path seq:%uL %s tx:%O rx:%O valid:%d st:%d mtu:%uz", \
+ "quic path seq:%uL %s tx:%O rx:%O valid:%ui st:%d mtu:%uz",\
path->seqnum, msg, path->sent, path->received, \
path->validated, path->state, path->mtu);
@@ -39,7 +39,7 @@ ngx_int_t ngx_quic_handle_migration(ngx_
void ngx_quic_path_handler(ngx_event_t *ev);
void ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path);
-ngx_int_t ngx_quic_handle_path_mtu_ack(ngx_connection_t *c,
+ngx_int_t ngx_quic_handle_path_mtu(ngx_connection_t *c,
ngx_quic_path_t *path, uint64_t min, uint64_t max);
#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */
diff --git a/src/os/win32/ngx_errno.h b/src/os/win32/ngx_errno.h
--- a/src/os/win32/ngx_errno.h
+++ b/src/os/win32/ngx_errno.h
@@ -57,6 +57,7 @@ typedef DWORD ngx_e
#define NGX_EILSEQ ERROR_NO_UNICODE_TRANSLATION
#define NGX_ELOOP 0
#define NGX_EBADF WSAEBADF
+#define NGX_EMSGSIZE WSAEMSGSIZE
#define NGX_EALREADY WSAEALREADY
#define NGX_EINVAL WSAEINVAL
-------------- next part --------------
# HG changeset patch
# User Roman Arutyunyan <arut at nginx.com>
# Date 1691658222 -14400
# Thu Aug 10 13:03:42 2023 +0400
# Node ID c224ba25ab49481f3e045e4f348a644ac965bf37
# Parent 02241bd4010398d1b5afa3a18bb4510ae54ea4a9
[mq]: quic-pmtud-scale-x2
diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c
--- a/src/event/quic/ngx_event_quic_migration.c
+++ b/src/event/quic/ngx_event_quic_migration.c
@@ -11,7 +11,6 @@
#define NGX_QUIC_PATH_MTU_DELAY 100
-#define NGX_QUIC_PATH_MTU_STEP 256
#define NGX_QUIC_PATH_MTU_PRECISION 16
@@ -592,7 +591,7 @@ ngx_quic_discover_path_mtu(ngx_connectio
path->mtud = (path->mtu + path->max_mtu) / 2;
} else {
- path->mtud = path->mtu + NGX_QUIC_PATH_MTU_STEP;
+ path->mtud = path->mtu * 2;
if (path->mtud >= qc->ctp.max_udp_payload_size) {
path->mtud = qc->ctp.max_udp_payload_size;
-------------- next part --------------
# HG changeset patch
# User Roman Arutyunyan <arut at nginx.com>
# Date 1691653593 -14400
# Thu Aug 10 11:46:33 2023 +0400
# Node ID daa3dbcfb366894dbd02e122fb6cefb2e13543b1
# Parent c224ba25ab49481f3e045e4f348a644ac965bf37
imported patch quic-pmtud-fix-2
diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c
--- a/src/event/quic/ngx_event_quic_migration.c
+++ b/src/event/quic/ngx_event_quic_migration.c
@@ -478,6 +478,8 @@ ngx_quic_handle_migration(ngx_connection
qc->path = next;
qc->path->tag = NGX_QUIC_PATH_ACTIVE;
+ ngx_quic_set_path_timer(c);
+
ngx_quic_set_connection_path(c, next);
if (!next->validated && next->state != NGX_QUIC_PATH_VALIDATING) {
@@ -614,38 +616,25 @@ static void
ngx_quic_set_path_timer(ngx_connection_t *c)
{
ngx_msec_t now;
- ngx_queue_t *q;
- ngx_msec_int_t left, next;
+ ngx_msec_int_t left;
ngx_quic_path_t *path;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
- now = ngx_current_msec;
- next = -1;
+ path = qc->path;
- 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 (path->state == NGX_QUIC_PATH_IDLE) {
- continue;
- }
+ if (path->state != NGX_QUIC_PATH_IDLE) {
+ now = ngx_current_msec;
left = path->expires - now;
left = ngx_max(left, 1);
- if (next == -1 || left < next) {
- next = left;
- }
+ ngx_add_timer(&qc->path_validation, left);
+ return;
}
- if (next != -1) {
- ngx_add_timer(&qc->path_validation, next);
-
- } else if (qc->path_validation.timer_set) {
+ if (qc->path_validation.timer_set) {
ngx_del_timer(&qc->path_validation);
}
}
@@ -655,7 +644,6 @@ void
ngx_quic_path_handler(ngx_event_t *ev)
{
ngx_msec_t now;
- ngx_queue_t *q;
ngx_msec_int_t left;
ngx_quic_path_t *path;
ngx_connection_t *c;
@@ -666,50 +654,46 @@ ngx_quic_path_handler(ngx_event_t *ev)
now = ngx_current_msec;
- q = ngx_queue_head(&qc->paths);
+ path = qc->path;
- while (q != ngx_queue_sentinel(&qc->paths)) {
+ if (path->state == NGX_QUIC_PATH_IDLE) {
+ goto done;
+ }
- path = ngx_queue_data(q, ngx_quic_path_t, queue);
- q = ngx_queue_next(q);
+ left = path->expires - now;
- if (path->state == NGX_QUIC_PATH_IDLE) {
- continue;
- }
+ if (left > 0) {
+ goto done;
+ }
- left = path->expires - now;
-
- if (left > 0) {
- continue;
+ switch (path->state) {
+ case NGX_QUIC_PATH_VALIDATING:
+ if (ngx_quic_expire_path_validation(c, path) != NGX_OK) {
+ goto failed;
}
- switch (path->state) {
- case NGX_QUIC_PATH_VALIDATING:
- if (ngx_quic_expire_path_validation(c, path) != NGX_OK) {
- goto failed;
- }
+ break;
- break;
+ case NGX_QUIC_PATH_WAITING:
+ if (ngx_quic_expire_path_mtu_delay(c, path) != NGX_OK) {
+ goto failed;
+ }
- case NGX_QUIC_PATH_WAITING:
- if (ngx_quic_expire_path_mtu_delay(c, path) != NGX_OK) {
- goto failed;
- }
+ break;
- break;
-
- case NGX_QUIC_PATH_MTUD:
- if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) {
- goto failed;
- }
+ case NGX_QUIC_PATH_MTUD:
+ if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) {
+ goto failed;
+ }
- break;
+ break;
- default:
- break;
- }
+ default:
+ break;
}
+done:
+
ngx_quic_set_path_timer(c);
return;
@@ -769,6 +753,8 @@ ngx_quic_expire_path_validation(ngx_conn
qc->path = bkp;
qc->path->tag = NGX_QUIC_PATH_ACTIVE;
+ ngx_quic_set_path_timer(c);
+
ngx_quic_set_connection_path(c, qc->path);
ngx_log_error(NGX_LOG_INFO, c->log, 0,
More information about the nginx-devel
mailing list