[PATCH 1 of 3] QUIC: changed path validation timeout

Roman Arutyunyan arut at nginx.com
Tue May 9 15:29:12 UTC 2023


On Tue, May 09, 2023 at 07:07:09PM +0400, Sergey Kandaurov wrote:
> 
> > On 9 May 2023, at 16:58, Sergey Kandaurov <pluknet at nginx.com> wrote:
> > 
> >> 
> >> On 9 May 2023, at 14:11, Sergey Kandaurov <pluknet at nginx.com> wrote:
> >> 
> >>> 
> >>> On 3 May 2023, at 17:00, Roman Arutyunyan <arut at nginx.com> wrote:
> >>> 
> >>> Hi,
> >>> 
> >>> On Mon, Apr 24, 2023 at 04:15:21PM +0400, Sergey Kandaurov wrote:
> >>>> 
> >>>> 
> >>> 
> >>>> [..]
> >>>> # HG changeset patch
> >>>> # User Sergey Kandaurov <pluknet at nginx.com>
> >>>> # Date 1682338151 -14400
> >>>> #      Mon Apr 24 16:09:11 2023 +0400
> >>>> # Branch quic
> >>>> # Node ID 808fe808e276496a9b026690c141201720744ab3
> >>>> # Parent  f49aba6e3fb54843d3e3bd5df26dbb45f5d3d687
> >>>> QUIC: separated path validation retransmit backoff.
> >>>> 
> >>>> Path validation packets containing PATH_CHALLENGE frames are sent separately
> >>>> from regular frame queue, because of the need to use a decicated path and pad
> >>>> the packets.  The packets are sent periodically, separately from the regular
> >>>> probe/lost detection mechanism.  A path validation packet is resent up to 3
> >>>> times, each time after PTO expiration, with increasing per-path PTO backoff.
> >>>> 
> >>>> 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
> >>>> @@ -736,7 +736,8 @@ ngx_quic_set_lost_timer(ngx_connection_t
> >>>> 
> >>>>       q = ngx_queue_last(&ctx->sent);
> >>>>       f = ngx_queue_data(q, ngx_quic_frame_t, queue);
> >>>> -        w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now);
> >>>> +        w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count)
> >>>> +                              - now);
> >>>> 
> >>>>       if (w < 0) {
> >>>>           w = 0;
> >>>> @@ -785,10 +786,9 @@ ngx_quic_pto(ngx_connection_t *c, ngx_qu
> >>>> 
> >>>>   duration = qc->avg_rtt;
> >>>>   duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY);
> >>>> -    duration <<= qc->pto_count;
> >>>> 
> >>>>   if (ctx->level == ssl_encryption_application && c->ssl->handshaked) {
> >>>> -        duration += qc->ctp.max_ack_delay << qc->pto_count;
> >>>> +        duration += qc->ctp.max_ack_delay;
> >>>>   }
> >>>> 
> >>>>   return duration;
> >>>> @@ -846,7 +846,9 @@ ngx_quic_pto_handler(ngx_event_t *ev)
> >>>>           continue;
> >>>>       }
> >>>> 
> >>>> -        if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) {
> >>>> +        if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count)
> >>>> +                              - now) > 0)
> >>>> +        {
> >>>>           continue;
> >>>>       }
> >>>> 
> >>>> 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
> >>>> @@ -496,6 +496,7 @@ ngx_quic_validate_path(ngx_connection_t 
> >>>>                  "quic initiated validation of path seq:%uL", path->seqnum);
> >>>> 
> >>>>   path->validating = 1;
> >>>> +    path->tries = 0;
> >>>> 
> >>>>   if (RAND_bytes(path->challenge1, 8) != 1) {
> >>>>       return NGX_ERROR;
> >>>> @@ -513,7 +514,6 @@ ngx_quic_validate_path(ngx_connection_t 
> >>>>   pto = ngx_quic_pto(c, ctx);
> >>>> 
> >>>>   path->expires = ngx_current_msec + pto;
> >>>> -    path->tries = NGX_QUIC_PATH_RETRIES;
> >>>> 
> >>>>   if (!qc->path_validation.timer_set) {
> >>>>       ngx_add_timer(&qc->path_validation, pto);
> >>>> @@ -578,7 +578,6 @@ ngx_quic_path_validation_handler(ngx_eve
> >>>>   qc = ngx_quic_get_connection(c);
> >>>> 
> >>>>   ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
> >>>> -    pto = ngx_quic_pto(c, ctx);
> >>>> 
> >>>>   next = -1;
> >>>>   now = ngx_current_msec;
> >>>> @@ -605,7 +604,9 @@ ngx_quic_path_validation_handler(ngx_eve
> >>>>           continue;
> >>>>       }
> >>>> 
> >>>> -        if (--path->tries) {
> >>>> +        if (++path->tries < NGX_QUIC_PATH_RETRIES) {
> >>>> +            pto = ngx_quic_pto(c, ctx) << path->tries;
> >>> 
> >>> Here we schedule a timer for 2 * PTO or 4 * PTO.  Technically, our path
> >>> validation code allows several paths to be validated at the same time.  If that
> >>> happens, 2 * PTO and 4 * PTO will be too much for the first attempt for the new
> >>> path, where the timeout is just PTO.  As a result, the last path may wait 2x
> >>> or 4x longer than needed.
> >> 
> >> Agree it needs to be addressed.
> >> 
> >> Below is the first glance to set the next available pv timer:
> >> 1) in ngx_quic_validate_path(), a new path may own a shorter timer
> >> than already established, especially after several timeouts
> >> Previously, new path validation could be scheduled late.
> >> 2) in ngx_quic_handle_path_response_frame(), a validated path
> >> might left a spurious timer, while nothing more to validate
> >> or remaining paths have more distant timer
> >> This one is less severe, just extra work added.
> >> 
> >> Unlike the two above, pv handler has a more complex
> >> logic I decided not to touch it.
> >> 
> > 
> > Updated patch, with timers logic moved inside
> > similar to ngx_quic_set_lost_timer().
> > 
> > # HG changeset patch
> > # User Sergey Kandaurov <pluknet at nginx.com>
> > # Date 1683636833 -14400
> > #      Tue May 09 16:53:53 2023 +0400
> > # Branch quic
> > # Node ID a40ce69032547110b1a10e953ca55dfc684f131d
> > # Parent  c9a6960c548501b5234ca7f7ff5ae23fad75b023
> > QUIC: reschedule path validation on path insertion/removal.
> > 
> > Two issues fixed:
> > - new path validation could be scheduled late
> > - a validated path could leave a spurious timer
> > 
> > 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
> > @@ -16,6 +16,7 @@ static ngx_int_t ngx_quic_validate_path(
> >     ngx_quic_path_t *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_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
> > 
> > 
> > @@ -169,6 +170,8 @@ valid:
> >     path->validating = 0;
> >     path->limited = 0;
> > 
> > +    ngx_quic_set_path_timer(c);
> > +
> >     return NGX_OK;
> > }
> > 
> > @@ -515,9 +518,7 @@ ngx_quic_validate_path(ngx_connection_t 
> > 
> >     path->expires = ngx_current_msec + pto;
> > 
> > -    if (!qc->path_validation.timer_set) {
> > -        ngx_add_timer(&qc->path_validation, pto);
> > -    }
> > +    ngx_quic_set_path_timer(c);
> > 
> >     return NGX_OK;
> > }
> > @@ -563,6 +564,46 @@ ngx_quic_send_path_challenge(ngx_connect
> > }
> > 
> > 
> > +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_quic_path_t        *path;
> > +    ngx_quic_connection_t  *qc;
> > +
> > +    qc = ngx_quic_get_connection(c);
> > +
> > +    now = ngx_current_msec;
> > +    next = -1;
> > +
> > +    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->validating) {
> > +            continue;
> > +        }
> > +
> > +        left = path->expires - now;
> > +
> > +        if (next == -1 || left < next) {
> > +            next = left;
> > +        }
> > +    }
> > +
> > +    if (next == -1) {
> > +        ngx_del_timer(&qc->path_validation);
> > +
> > +    } else {
> > +        ngx_add_timer(&qc->path_validation, next);
> > +    }
> > +}
> > +
> > +
> > void
> > ngx_quic_path_validation_handler(ngx_event_t *ev)
> > {
> > 
> 
> Addendum to handle negation expiration time.
> 
> Previously, when updating path validation timer, another path being
> in the process of validation could already expire, notably when both
> were scheduled on apparent migration.
> This resulted in negative expiration time and could left a path
> without a timer.
> 
> 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
> @@ -586,17 +586,18 @@ ngx_quic_set_path_timer(ngx_connection_t
>          }
>  
>          left = path->expires - now;
> +        left = ngx_max(left, 1);
>  
>          if (next == -1 || left < next) {
>              next = left;
>          }
>      }
>  
> -    if (next == -1) {
> -        ngx_del_timer(&qc->path_validation);
> +    if (next != -1) {
> +        ngx_add_timer(&qc->path_validation, next);
>  
> -    } else {
> -        ngx_add_timer(&qc->path_validation, next);
> +    } else if (qc->path_validation.timer_set) {
> +        ngx_del_timer(&qc->path_validation);
>      }
>  }

Looks good

--
Roman Arutyunyan


More information about the nginx-devel mailing list