From ccca701dc6f3644b8d6d8d31d7094edaab703d7a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 29 Nov 2023 21:41:29 +0400 Subject: [PATCH] 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(). --- src/event/quic/ngx_event_quic_ack.c | 32 +++++++---- src/event/quic/ngx_event_quic_migration.c | 51 ++++++++++-------- src/event/quic/ngx_event_quic_output.c | 65 +++++++++++++++++++---- src/event/quic/ngx_event_quic_transport.h | 1 + 4 files changed, 108 insertions(+), 41 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index a7fd85d20..9572987e1 100644 --- 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 *c, ngx_quic_send_ctx_t *ctx) 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 *ev) 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; } diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index a6eaf4563..7819ae2f9 100644 --- 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_connection_t *c, 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_connection_t *c, 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_connection_t *c, 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 *c, ngx_quic_path_t *path) 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_connection_t *c, ngx_quic_path_t *path) 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_connection_t *c, ngx_quic_path_t *path) 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_connection_t *c, ngx_quic_path_t *path) 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; diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 338451e8b..1497b7831 100644 --- 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_connection_t *c, ngx_quic_conf_t *conf, 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 *c, ngx_quic_frame_t *frame, 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 *c, ngx_quic_frame_t *frame, 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 *c, ngx_quic_frame_t *frame, 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 *c, ngx_quic_frame_t *frame, 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; } diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 232ff18d3..9891ede44 100644 --- 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 {