diff src/event/quic/ngx_event_quic_output.c @ 8763:4117aa7fa38e quic

QUIC: connection migration. The patch adds proper transitions between multiple networking addresses that can be used by a single quic connection. New networking paths are validated using PATH_CHALLENGE/PATH_RESPONSE frames.
author Vladimir Homutov <vl@nginx.com>
date Thu, 29 Apr 2021 15:35:02 +0300
parents bc910a5ec737
children 4715f3e669f1
line wrap: on
line diff
--- a/src/event/quic/ngx_event_quic_output.c
+++ b/src/event/quic/ngx_event_quic_output.c
@@ -35,10 +35,14 @@
 #define NGX_QUIC_CC_MIN_INTERVAL       1000 /* 1s */
 
 
+static ngx_int_t ngx_quic_socket_output(ngx_connection_t *c,
+    ngx_quic_socket_t *qsock);
 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_send_ctx_t *ctx, u_char *data, size_t max, size_t min,
+    ngx_quic_socket_t *qsock);
 static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c);
-static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len);
+static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len,
+    struct sockaddr *sockaddr, socklen_t socklen);
 static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt,
     ngx_quic_send_ctx_t *ctx);
 
@@ -61,11 +65,29 @@ ngx_quic_max_udp_payload(ngx_connection_
 ngx_int_t
 ngx_quic_output(ngx_connection_t *c)
 {
+    ngx_quic_connection_t  *qc;
+
+    qc = ngx_quic_get_connection(c);
+
+    if (ngx_quic_socket_output(c, qc->socket) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_quic_set_lost_timer(c);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock)
+{
     off_t                   max;
     size_t                  len, min, in_flight;
     ssize_t                 n;
     u_char                 *p;
     ngx_uint_t              i, pad;
+    ngx_quic_path_t        *path;
     ngx_quic_send_ctx_t    *ctx;
     ngx_quic_congestion_t  *cg;
     ngx_quic_connection_t  *qc;
@@ -78,15 +100,18 @@ ngx_quic_output(ngx_connection_t *c)
 
     in_flight = cg->in_flight;
 
+    path = qsock->path;
+
     for ( ;; ) {
         p = dst;
 
         len = ngx_min(qc->ctp.max_udp_payload_size,
                       NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
 
-        if (!qc->validated) {
-            max = qc->received * 3;
-            max = (c->sent >= max) ? 0 : max - c->sent;
+        if (path->state != NGX_QUIC_PATH_VALIDATED) {
+            max = path->received * 3;
+            max = (path->sent >= max) ? 0 : max - path->sent;
+
             len = ngx_min(len, (size_t) max);
         }
 
@@ -103,7 +128,7 @@ ngx_quic_output(ngx_connection_t *c)
             min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE)
                   ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0;
 
-            n = ngx_quic_output_packet(c, ctx, p, len, min);
+            n = ngx_quic_output_packet(c, ctx, p, len, min, qsock);
             if (n == NGX_ERROR) {
                 return NGX_ERROR;
             }
@@ -117,10 +142,13 @@ ngx_quic_output(ngx_connection_t *c)
             break;
         }
 
-        n = ngx_quic_send(c, dst, len);
+        n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen);
+
         if (n == NGX_ERROR) {
             return NGX_ERROR;
         }
+
+        path->sent += len;
     }
 
     if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) {
@@ -128,7 +156,6 @@ ngx_quic_output(ngx_connection_t *c)
         ngx_add_timer(c->read, qc->tp.max_idle_timeout);
     }
 
-    ngx_quic_set_lost_timer(c);
 
     return NGX_OK;
 }
@@ -176,14 +203,14 @@ ngx_quic_get_padding_level(ngx_connectio
 
 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)
+    u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock)
 {
     size_t                  len, hlen, pad_len;
     u_char                 *p;
     ssize_t                 flen;
     ngx_str_t               out, res;
     ngx_int_t               rc;
-    ngx_uint_t              nframes;
+    ngx_uint_t              nframes, has_pr;
     ngx_msec_t              now;
     ngx_queue_t            *q;
     ngx_quic_frame_t       *f;
@@ -196,9 +223,10 @@ ngx_quic_output_packet(ngx_connection_t 
         return 0;
     }
 
-    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic output %s packet max:%uz min:%uz",
-                   ngx_quic_level_name(ctx->level), max, min);
+    ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic output sock #%uL %s packet max:%uz min:%uz",
+                   qsock->sid.seqnum, ngx_quic_level_name(ctx->level),
+                   max, min);
 
     qc = ngx_quic_get_connection(c);
     cg = &qc->congestion;
@@ -208,7 +236,7 @@ ngx_quic_output_packet(ngx_connection_t 
            : NGX_QUIC_MAX_LONG_HEADER;
 
     hlen += EVP_GCM_TLS_TAG_LEN;
-    hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len;
+    hlen -= NGX_QUIC_MAX_CID_LEN - qsock->cid->len;
 
     ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
 
@@ -216,6 +244,7 @@ ngx_quic_output_packet(ngx_connection_t 
     nframes = 0;
     p = src;
     len = 0;
+    has_pr = 0;
 
     for (q = ngx_queue_head(&ctx->frames);
          q != ngx_queue_sentinel(&ctx->frames);
@@ -227,6 +256,12 @@ ngx_quic_output_packet(ngx_connection_t 
             max = cg->window;
         }
 
+        if (f->type == NGX_QUIC_FT_PATH_RESPONSE
+            || f->type == NGX_QUIC_FT_PATH_CHALLENGE)
+        {
+            has_pr = 1;
+        }
+
         if (hlen + len >= max) {
             break;
         }
@@ -296,15 +331,33 @@ ngx_quic_output_packet(ngx_connection_t 
     pkt.version = qc->version;
     pkt.log = c->log;
     pkt.level = ctx->level;
-    pkt.dcid = qc->scid;
-    pkt.scid = qc->dcid;
+
+    pkt.dcid.data = qsock->cid->id;
+    pkt.dcid.len = qsock->cid->len;
+
+    pkt.scid.data = qsock->sid.id;
+    pkt.scid.len = qsock->sid.len;
 
     pad_len = 4;
 
-    if (min) {
+    if (min || has_pr) {
         hlen = EVP_GCM_TLS_TAG_LEN
                + ngx_quic_create_header(&pkt, NULL, out.len, NULL);
 
+        /*
+         * 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;
         }
@@ -364,11 +417,14 @@ ngx_quic_output_packet(ngx_connection_t 
 }
 
 
-ssize_t
-ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len)
+static ssize_t
+ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len,
+    struct sockaddr *sockaddr, socklen_t socklen)
 {
-    ngx_buf_t    b;
-    ngx_chain_t  cl, *res;
+    ngx_buf_t         b;
+    socklen_t         orig_socklen;
+    ngx_chain_t       cl, *res;
+    struct sockaddr  *orig_sockaddr;
 
     ngx_memzero(&b, sizeof(ngx_buf_t));
 
@@ -380,7 +436,17 @@ ngx_quic_send(ngx_connection_t *c, u_cha
     cl.buf = &b;
     cl.next= NULL;
 
+    orig_socklen = c->socklen;
+    orig_sockaddr = c->sockaddr;
+
+    c->sockaddr = sockaddr;
+    c->socklen = socklen;
+
     res = c->send_chain(c, &cl, 0);
+
+    c->sockaddr = orig_sockaddr;
+    c->socklen = orig_socklen;
+
     if (res == NGX_CHAIN_ERROR) {
         return NGX_ERROR;
     }
@@ -441,7 +507,7 @@ ngx_quic_negotiate_version(ngx_connectio
                    "quic vnego packet to send len:%uz %*xs", len, len, buf);
 #endif
 
-    (void) ngx_quic_send(c, buf, len);
+    (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen);
 
     return NGX_ERROR;
 }
@@ -524,7 +590,7 @@ ngx_quic_send_stateless_reset(ngx_connec
         return NGX_ERROR;
     }
 
-    (void) ngx_quic_send(c, buf, len);
+    (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen);
 
     return NGX_DECLINED;
 }
@@ -642,7 +708,9 @@ ngx_quic_send_early_cc(ngx_connection_t 
         return NGX_ERROR;
     }
 
-    if (ngx_quic_send(c, res.data, res.len) == NGX_ERROR) {
+    if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen)
+        == NGX_ERROR)
+    {
         return NGX_ERROR;
     }
 
@@ -664,8 +732,8 @@ ngx_quic_send_retry(ngx_connection_t *c,
 
     expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME;
 
-    if (ngx_quic_new_token(c, conf->av_token_key, &token, &inpkt->dcid,
-                           expires, 1)
+    if (ngx_quic_new_token(c, c->sockaddr, c->socklen, conf->av_token_key,
+                           &token, &inpkt->dcid, expires, 1)
         != NGX_OK)
     {
         return NGX_ERROR;
@@ -700,7 +768,7 @@ ngx_quic_send_retry(ngx_connection_t *c,
                    "quic packet to send len:%uz %xV", res.len, &res);
 #endif
 
-    len = ngx_quic_send(c, res.data, res.len);
+    len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen);
     if (len == NGX_ERROR) {
         return NGX_ERROR;
     }
@@ -718,7 +786,7 @@ ngx_quic_send_retry(ngx_connection_t *c,
 
 
 ngx_int_t
-ngx_quic_send_new_token(ngx_connection_t *c)
+ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path)
 {
     time_t                  expires;
     ngx_str_t               token;
@@ -727,13 +795,10 @@ ngx_quic_send_new_token(ngx_connection_t
 
     qc = ngx_quic_get_connection(c);
 
-    if (!qc->conf->retry) {
-        return NGX_OK;
-    }
-
     expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME;
 
-    if (ngx_quic_new_token(c, qc->conf->av_token_key, &token, NULL, expires, 0)
+    if (ngx_quic_new_token(c, path->sockaddr, path->socklen,
+                           qc->conf->av_token_key, &token, NULL, expires, 0)
         != NGX_OK)
     {
         return NGX_ERROR;
@@ -849,3 +914,75 @@ ngx_quic_send_ack_range(ngx_connection_t
 
     return NGX_OK;
 }
+
+
+ssize_t
+ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame,
+    size_t min, struct sockaddr *sockaddr, socklen_t socklen)
+{
+    ssize_t                 len;
+    ngx_str_t               res;
+    ngx_quic_header_t       pkt;
+    ngx_quic_send_ctx_t    *ctx;
+    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);
+
+    ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
+
+    len = ngx_quic_create_frame(NULL, frame);
+    if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) {
+        return -1;
+    }
+
+    ngx_quic_log_frame(c->log, frame, 1);
+
+    len = ngx_quic_create_frame(src, frame);
+    if (len == -1) {
+        return -1;
+    }
+
+    if (len < (ssize_t) min) {
+        ngx_memset(src + len, NGX_QUIC_FT_PADDING, min - len);
+        len = min;
+    }
+
+    pkt.keys = qc->keys;
+    pkt.flags = NGX_QUIC_PKT_FIXED_BIT;
+
+    if (qc->key_phase) {
+        pkt.flags |= NGX_QUIC_PKT_KPHASE;
+    }
+
+    ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
+
+    ngx_quic_set_packet_number(&pkt, ctx);
+
+    pkt.version = qc->version;
+    pkt.log = c->log;
+    pkt.level = ctx->level;
+
+    pkt.dcid.data = qc->socket->cid->id;
+    pkt.dcid.len = qc->socket->cid->len;
+
+    pkt.scid.data = qc->socket->sid.id;
+    pkt.scid.len = qc->socket->sid.len;
+
+    pkt.payload.data = src;
+    pkt.payload.len = len;
+
+    res.data = dst;
+
+    if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
+        return -1;
+    }
+
+    ctx->pnum++;
+
+    len = ngx_quic_send(c, res.data, res.len, sockaddr, socklen);
+
+    return len;
+}