# HG changeset patch # User Vladimir Homutov # Date 1633603709 -10800 # Node ID de7b9af30fc60c767a6942d0e314ca267f240f2e # Parent 126a15530136302f9b6c208fa3b67aa7eb1dd786 QUIC: refactored packet creation. The "min" and "max" arguments refer to UDP datagram size. Generating payload requires to account properly for header size, which is variable and depends on payload size and packet number. 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,10 +10,6 @@ #include -#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ -#define NGX_QUIC_MAX_LONG_HEADER 56 - /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ - #define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 #define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 @@ -532,12 +528,12 @@ static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock) { - size_t len, hlen, pad_len; + size_t len, pad, min_payload, max_payload; u_char *p; ssize_t flen; - ngx_str_t out, res; + ngx_str_t res; ngx_int_t rc; - ngx_uint_t nframes, has_pr; + ngx_uint_t nframes, expand; ngx_msec_t now; ngx_queue_t *q; ngx_quic_frame_t *f; @@ -555,18 +551,22 @@ ngx_quic_output_packet(ngx_connection_t ngx_quic_init_packet(c, ctx, qsock, &pkt); - hlen = (ctx->level == ssl_encryption_application) - ? NGX_QUIC_MAX_SHORT_HEADER - : NGX_QUIC_MAX_LONG_HEADER; + min_payload = ngx_quic_payload_size(&pkt, min); + max_payload = ngx_quic_payload_size(&pkt, max); - hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qsock->cid->len; + /* RFC 9001, 5.4.2. Header Protection Sample */ + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + + if (min_payload > max_payload) { + return 0; + } now = ngx_current_msec; nframes = 0; p = src; len = 0; - has_pr = 0; + expand = 0; for (q = ngx_queue_head(&ctx->frames); q != ngx_queue_sentinel(&ctx->frames); @@ -574,18 +574,39 @@ ngx_quic_output_packet(ngx_connection_t { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (f->type == NGX_QUIC_FT_PATH_RESPONSE - || f->type == NGX_QUIC_FT_PATH_CHALLENGE) + if (!expand && (f->type == NGX_QUIC_FT_PATH_RESPONSE + || f->type == NGX_QUIC_FT_PATH_CHALLENGE)) { - has_pr = 1; + /* + * RFC 9000, 8.2.1. Initiating Path Validation + * + * An endpoint MUST expand datagrams that contain a + * PATH_CHALLENGE frame to at least the smallest allowed + * maximum datagram size of 1200 bytes... + * + * (same applies to PATH_RESPONSE frames) + */ + + if (max < 1200) { + /* expanded packet will not fit */ + break; + } + + if (min < 1200) { + min = 1200; + + min_payload = ngx_quic_payload_size(&pkt, min); + } + + expand = 1; } - if (hlen + len >= max) { + if (len >= max_payload) { break; } - if (hlen + len + f->len > max) { - rc = ngx_quic_split_frame(c, f, max - hlen - len); + if (len + f->len > max_payload) { + rc = ngx_quic_split_frame(c, f, max_payload - len); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -626,53 +647,21 @@ ngx_quic_output_packet(ngx_connection_t return 0; } - out.data = src; - out.len = len; - - pad_len = 4; - - if (min || has_pr) { - hlen = EVP_GCM_TLS_TAG_LEN - + ngx_quic_create_header(&pkt, NULL, out.len, NULL); + if (len < min_payload) { + ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } - /* - * RFC 9000, 8.2.1. Initiating Path Validation - * - * An endpoint MUST expand datagrams that contain a - * PATH_CHALLENGE frame to at least the smallest allowed - * maximum datagram size of 1200 bytes, unless the - * anti-amplification limit for the path does not permit - * sending a datagram of this size. - * - * (same applies to PATH_RESPONSE frames) - */ - - if (has_pr) { - min = ngx_max(1200, min); - } - - if (min > hlen + pad_len) { - pad_len = min - hlen; - } - } - - if (out.len < pad_len) { - /* compensate for potentially enlarged header in Length bytes */ - pad_len -= ngx_quic_create_header(&pkt, NULL, pad_len, NULL) - - ngx_quic_create_header(&pkt, NULL, out.len, NULL); - ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); - out.len = pad_len; - } - - pkt.payload = out; + pkt.payload.data = src; + pkt.payload.len = len; res.data = data; ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet tx %s bytes:%ui" " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", - ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, - pkt.number, pkt.num_len, pkt.trunc); + ngx_quic_level_name(ctx->level), pkt.payload.len, + pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { return NGX_ERROR; @@ -1253,6 +1242,7 @@ ssize_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, size_t min, struct sockaddr *sockaddr, socklen_t socklen) { + size_t min_payload, pad; ssize_t len; ngx_str_t res; ngx_quic_header_t pkt; @@ -1267,6 +1257,11 @@ ngx_quic_frame_sendto(ngx_connection_t * ngx_quic_init_packet(c, ctx, qc->socket, &pkt); + min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0; + + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + len = ngx_quic_create_frame(NULL, frame); if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { return -1; @@ -1279,10 +1274,10 @@ ngx_quic_frame_sendto(ngx_connection_t * return -1; } - if (len < (ssize_t) min) { - ngx_memset(src + len, NGX_QUIC_FT_PADDING, min - len); - len = min; - } + if (len < (ssize_t) min_payload) { + ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } pkt.payload.data = src; pkt.payload.len = len; diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -836,11 +836,10 @@ ngx_quic_create_packet(ngx_quic_header_t ngx_quic_ciphers_t ciphers; u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; - out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; + ad.data = res->data; + ad.len = ngx_quic_create_header(pkt, ad.data, &pnp); - ad.data = res->data; - ad.len = ngx_quic_create_header(pkt, ad.data, out.len, &pnp); - + out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; out.data = res->data + ad.len; #ifdef NGX_QUIC_DEBUG_CRYPTO diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -94,7 +94,7 @@ static ngx_int_t ngx_quic_supported_vers static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); + u_char **pnp); static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp); @@ -613,25 +613,63 @@ ngx_quic_create_version_negotiation(ngx_ } +/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */ size_t -ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, - u_char **pnp) +ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) +{ + size_t len; + + if ngx_quic_short_pkt(pkt->flags) { + + len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN; + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; + } + + /* flags, version, dcid and scid with lengths and zero-length token */ + len = 5 + 2 + pkt->dcid.len + pkt->scid.len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + + if (len > pkt_len) { + return 0; + } + + /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */ + len += ngx_quic_varint_len(pkt_len - len) + + pkt->num_len + EVP_GCM_TLS_TAG_LEN; + + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; +} + + +size_t +ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp) { return ngx_quic_short_pkt(pkt->flags) ? ngx_quic_create_short_header(pkt, out, pnp) - : ngx_quic_create_long_header(pkt, out, pkt_len, pnp); + : ngx_quic_create_long_header(pkt, out, pnp); } static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp) + u_char **pnp) { + size_t rem_len; u_char *p, *start; + rem_len = pkt->num_len + pkt->payload.len + EVP_GCM_TLS_TAG_LEN; + if (out == NULL) { return 5 + 2 + pkt->dcid.len + pkt->scid.len - + ngx_quic_varint_len(pkt_len + pkt->num_len) + pkt->num_len + + ngx_quic_varint_len(rem_len) + pkt->num_len + (pkt->level == ssl_encryption_initial ? 1 : 0); } @@ -651,7 +689,7 @@ ngx_quic_create_long_header(ngx_quic_hea ngx_quic_build_int(&p, 0); } - ngx_quic_build_int(&p, pkt_len + pkt->num_len); + ngx_quic_build_int(&p, rem_len); *pnp = p; diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -345,8 +345,10 @@ ngx_int_t ngx_quic_parse_packet(ngx_quic size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); +size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len); + size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); + u_char **pnp); size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, u_char **start);