changeset 8894:de7b9af30fc6 quic

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.
author Vladimir Homutov <vl@nginx.com>
date Thu, 07 Oct 2021 13:48:29 +0300
parents 126a15530136
children 4b2d259bdadd
files src/event/quic/ngx_event_quic_output.c src/event/quic/ngx_event_quic_protection.c src/event/quic/ngx_event_quic_transport.c src/event/quic/ngx_event_quic_transport.h
diffstat 4 files changed, 109 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/quic/ngx_event_quic_output.c
+++ b/src/event/quic/ngx_event_quic_output.c
@@ -10,10 +10,6 @@
 #include <ngx_event_quic_connection.h>
 
 
-#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;
--- 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
--- 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;
 
--- 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);