changeset 9192:efcdaa66df2e

QUIC: congestion control in ngx_quic_frame_sendto(). Previously ngx_quic_frame_sendto() ignored congestion control and did not contribute to in_flight counter. Now congestion control window is checked unless ignore_congestion flag is set. Also, in_flight counter is incremented and the frame is stored in ctx->sent queue if it's ack-eliciting. This behavior is now similar to ngx_quic_output_packet().
author Roman Arutyunyan <arut@nginx.com>
date Wed, 29 Nov 2023 21:41:29 +0400
parents 618132842e7c
children ce1ff81e9b92
files src/event/quic/ngx_event_quic_ack.c src/event/quic/ngx_event_quic_migration.c src/event/quic/ngx_event_quic_output.c src/event/quic/ngx_event_quic_transport.h
diffstat 4 files changed, 108 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/quic/ngx_event_quic_ack.c
+++ b/src/event/quic/ngx_event_quic_ack.c
@@ -593,6 +593,7 @@ ngx_quic_resend_frames(ngx_connection_t 
             break;
 
         case NGX_QUIC_FT_PING:
+        case NGX_QUIC_FT_PATH_CHALLENGE:
         case NGX_QUIC_FT_PATH_RESPONSE:
         case NGX_QUIC_FT_CONNECTION_CLOSE:
             ngx_quic_free_frame(c, f);
@@ -824,11 +825,11 @@ void ngx_quic_lost_handler(ngx_event_t *
 void
 ngx_quic_pto_handler(ngx_event_t *ev)
 {
-    ngx_uint_t              i;
+    ngx_uint_t              i, n;
     ngx_msec_t              now;
     ngx_queue_t            *q;
     ngx_connection_t       *c;
-    ngx_quic_frame_t       *f, frame;
+    ngx_quic_frame_t       *f;
     ngx_quic_send_ctx_t    *ctx;
     ngx_quic_connection_t  *qc;
 
@@ -865,16 +866,20 @@ ngx_quic_pto_handler(ngx_event_t *ev)
                        "quic pto %s pto_count:%ui",
                        ngx_quic_level_name(ctx->level), qc->pto_count);
 
-        ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
+        for (n = 0; n < 2; n++) {
 
-        frame.level = ctx->level;
-        frame.type = NGX_QUIC_FT_PING;
+            f = ngx_quic_alloc_frame(c);
+            if (f == NULL) {
+                goto failed;
+            }
 
-        if (ngx_quic_frame_sendto(c, &frame, 0, qc->path) != NGX_OK
-            || ngx_quic_frame_sendto(c, &frame, 0, qc->path) != NGX_OK)
-        {
-            ngx_quic_close_connection(c, NGX_ERROR);
-            return;
+            f->level = ctx->level;
+            f->type = NGX_QUIC_FT_PING;
+            f->ignore_congestion = 1;
+
+            if (ngx_quic_frame_sendto(c, f, 0, qc->path) == NGX_ERROR) {
+                goto failed;
+            }
         }
     }
 
@@ -883,6 +888,13 @@ ngx_quic_pto_handler(ngx_event_t *ev)
     ngx_quic_set_lost_timer(c);
 
     ngx_quic_connstate_dbg(c);
+
+    return;
+
+failed:
+
+    ngx_quic_close_connection(c, NGX_ERROR);
+    return;
 }
 
 
--- a/src/event/quic/ngx_event_quic_migration.c
+++ b/src/event/quic/ngx_event_quic_migration.c
@@ -37,7 +37,7 @@ ngx_quic_handle_path_challenge_frame(ngx
     ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
 {
     size_t                  min;
-    ngx_quic_frame_t        frame, *fp;
+    ngx_quic_frame_t       *fp;
     ngx_quic_connection_t  *qc;
 
     if (pkt->level != ssl_encryption_application || pkt->path_challenged) {
@@ -50,11 +50,14 @@ ngx_quic_handle_path_challenge_frame(ngx
 
     qc = ngx_quic_get_connection(c);
 
-    ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
+    fp = ngx_quic_alloc_frame(c);
+    if (fp == NULL) {
+        return NGX_ERROR;
+    }
 
-    frame.level = ssl_encryption_application;
-    frame.type = NGX_QUIC_FT_PATH_RESPONSE;
-    frame.u.path_response = *f;
+    fp->level = ssl_encryption_application;
+    fp->type = NGX_QUIC_FT_PATH_RESPONSE;
+    fp->u.path_response = *f;
 
     /*
      * RFC 9000, 8.2.2.  Path Validation Responses
@@ -73,7 +76,7 @@ ngx_quic_handle_path_challenge_frame(ngx
 
     min = (ngx_quic_path_limit(c, pkt->path, 1200) < 1200) ? 0 : 1200;
 
-    if (ngx_quic_frame_sendto(c, &frame, min, pkt->path) == NGX_ERROR) {
+    if (ngx_quic_frame_sendto(c, fp, min, pkt->path) == NGX_ERROR) {
         return NGX_ERROR;
     }
 
@@ -546,22 +549,25 @@ ngx_quic_validate_path(ngx_connection_t 
 static ngx_int_t
 ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path)
 {
-    size_t            min;
-    ngx_uint_t        n;
-    ngx_quic_frame_t  frame;
+    size_t             min;
+    ngx_uint_t         n;
+    ngx_quic_frame_t  *frame;
 
     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
                    "quic path seq:%uL send path_challenge tries:%ui",
                    path->seqnum, path->tries);
 
-    ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
-
-    frame.level = ssl_encryption_application;
-    frame.type = NGX_QUIC_FT_PATH_CHALLENGE;
-
     for (n = 0; n < 2; n++) {
 
-        ngx_memcpy(frame.u.path_challenge.data, path->challenge[n], 8);
+        frame = ngx_quic_alloc_frame(c);
+        if (frame == NULL) {
+            return NGX_ERROR;
+        }
+
+        frame->level = ssl_encryption_application;
+        frame->type = NGX_QUIC_FT_PATH_CHALLENGE;
+
+        ngx_memcpy(frame->u.path_challenge.data, path->challenge[n], 8);
 
         /*
          * RFC 9000, 8.2.1.  Initiating Path Validation
@@ -574,7 +580,7 @@ ngx_quic_send_path_challenge(ngx_connect
 
         min = (ngx_quic_path_limit(c, path, 1200) < 1200) ? 0 : 1200;
 
-        if (ngx_quic_frame_sendto(c, &frame, min, path) == NGX_ERROR) {
+        if (ngx_quic_frame_sendto(c, frame, min, path) == NGX_ERROR) {
             return NGX_ERROR;
         }
     }
@@ -883,14 +889,17 @@ ngx_quic_send_path_mtu_probe(ngx_connect
     size_t                  mtu;
     ngx_int_t               rc;
     ngx_uint_t              log_error;
-    ngx_quic_frame_t        frame;
+    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 = ngx_quic_alloc_frame(c);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
 
-    frame.level = ssl_encryption_application;
-    frame.type = NGX_QUIC_FT_PING;
+    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);
@@ -907,7 +916,7 @@ ngx_quic_send_path_mtu_probe(ngx_connect
     mtu = path->mtu;
     path->mtu = path->mtud;
 
-    rc = ngx_quic_frame_sendto(c, &frame, path->mtud, path);
+    rc = ngx_quic_frame_sendto(c, frame, path->mtud, path);
 
     path->mtu = mtu;
     c->log_error = log_error;
--- a/src/event/quic/ngx_event_quic_output.c
+++ b/src/event/quic/ngx_event_quic_output.c
@@ -844,7 +844,7 @@ ngx_quic_send_stateless_reset(ngx_connec
 ngx_int_t
 ngx_quic_send_cc(ngx_connection_t *c)
 {
-    ngx_quic_frame_t        frame;
+    ngx_quic_frame_t       *frame;
     ngx_quic_connection_t  *qc;
 
     qc = ngx_quic_get_connection(c);
@@ -860,22 +860,27 @@ ngx_quic_send_cc(ngx_connection_t *c)
         return NGX_OK;
     }
 
-    ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
+    frame = ngx_quic_alloc_frame(c);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
 
-    frame.level = qc->error_level;
-    frame.type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP
-                               : NGX_QUIC_FT_CONNECTION_CLOSE;
-    frame.u.close.error_code = qc->error;
-    frame.u.close.frame_type = qc->error_ftype;
+    frame->level = qc->error_level;
+    frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP
+                                : NGX_QUIC_FT_CONNECTION_CLOSE;
+    frame->u.close.error_code = qc->error;
+    frame->u.close.frame_type = qc->error_ftype;
 
     if (qc->error_reason) {
-        frame.u.close.reason.len = ngx_strlen(qc->error_reason);
-        frame.u.close.reason.data = (u_char *) qc->error_reason;
+        frame->u.close.reason.len = ngx_strlen(qc->error_reason);
+        frame->u.close.reason.data = (u_char *) qc->error_reason;
     }
 
+    frame->ignore_congestion = 1;
+
     qc->last_cc = ngx_current_msec;
 
-    return ngx_quic_frame_sendto(c, &frame, 0, qc->path);
+    return ngx_quic_frame_sendto(c, frame, 0, qc->path);
 }
 
 
@@ -1184,22 +1189,32 @@ ngx_quic_frame_sendto(ngx_connection_t *
     size_t                  max, max_payload, min_payload, pad;
     ssize_t                 len, sent;
     ngx_str_t               res;
+    ngx_msec_t              now;
     ngx_quic_header_t       pkt;
     ngx_quic_send_ctx_t    *ctx;
+    ngx_quic_congestion_t  *cg;
     ngx_quic_connection_t  *qc;
 
     static u_char           src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
     static u_char           dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
 
     qc = ngx_quic_get_connection(c);
+    cg = &qc->congestion;
     ctx = ngx_quic_get_send_ctx(qc, frame->level);
 
+    now = ngx_current_msec;
+
     max = ngx_quic_path_limit(c, path, path->mtu);
 
     ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
                    "quic sendto %s packet max:%uz min:%uz",
                    ngx_quic_level_name(ctx->level), max, min);
 
+    if (cg->in_flight >= cg->window && !frame->ignore_congestion) {
+        ngx_quic_free_frame(c, frame);
+        return NGX_AGAIN;
+    }
+
     ngx_quic_init_packet(c, ctx, &pkt, path);
 
     min_payload = ngx_quic_payload_size(&pkt, min);
@@ -1210,6 +1225,7 @@ ngx_quic_frame_sendto(ngx_connection_t *
     min_payload = ngx_max(min_payload, pad);
 
     if (min_payload > max_payload) {
+        ngx_quic_free_frame(c, frame);
         return NGX_AGAIN;
     }
 
@@ -1221,11 +1237,13 @@ ngx_quic_frame_sendto(ngx_connection_t *
 
     len = ngx_quic_create_frame(NULL, frame);
     if ((size_t) len > max_payload) {
+        ngx_quic_free_frame(c, frame);
         return NGX_AGAIN;
     }
 
     len = ngx_quic_create_frame(src, frame);
     if (len == -1) {
+        ngx_quic_free_frame(c, frame);
         return NGX_ERROR;
     }
 
@@ -1242,18 +1260,45 @@ ngx_quic_frame_sendto(ngx_connection_t *
     ngx_quic_log_packet(c->log, &pkt);
 
     if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
+        ngx_quic_free_frame(c, frame);
         return NGX_ERROR;
     }
 
+    frame->pnum = ctx->pnum;
+    frame->first = now;
+    frame->last = now;
+    frame->plen = res.len;
+
     ctx->pnum++;
 
     sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen);
     if (sent < 0) {
+        ngx_quic_free_frame(c, frame);
         return sent;
     }
 
     path->sent += sent;
 
+    if (frame->need_ack && !qc->closing) {
+        ngx_queue_insert_tail(&ctx->sent, &frame->queue);
+
+        cg->in_flight += frame->plen;
+
+    } else {
+        ngx_quic_free_frame(c, frame);
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic congestion send if:%uz", cg->in_flight);
+
+    if (!qc->send_timer_set) {
+        qc->send_timer_set = 1;
+        ngx_add_timer(c->read, qc->tp.max_idle_timeout);
+    }
+
+    ngx_quic_set_lost_timer(c);
+
     return NGX_OK;
 }
 
--- a/src/event/quic/ngx_event_quic_transport.h
+++ b/src/event/quic/ngx_event_quic_transport.h
@@ -271,6 +271,7 @@ struct ngx_quic_frame_s {
     ssize_t                                     len;
     unsigned                                    need_ack:1;
     unsigned                                    pkt_need_ack:1;
+    unsigned                                    ignore_congestion:1;
 
     ngx_chain_t                                *data;
     union {