changeset 8522:fc89d02bdca2 quic

QUIC: added version negotiation support. If a client attemtps to start a new connection with unsupported version, a version negotiation packet is sent that contains a list of supported versions (currently this is a single version, selected at compile time).
author Vladimir Homutov <vl@nginx.com>
date Thu, 20 Aug 2020 17:11:04 +0300
parents a748095bf94e
children 0609ea17ca23
files src/event/ngx_event_quic.c src/event/ngx_event_quic_transport.c src/event/ngx_event_quic_transport.h
diffstat 3 files changed, 102 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -169,6 +169,8 @@ static int ngx_quic_send_alert(ngx_ssl_c
 
 static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl,
     ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c,
+    ngx_quic_header_t *inpkt);
 static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid);
 static ngx_int_t ngx_quic_retry(ngx_connection_t *c);
 static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token);
@@ -659,6 +661,10 @@ ngx_quic_new_connection(ngx_connection_t
         return rc;
     }
 
+    if (pkt->version != NGX_QUIC_VERSION) {
+        return ngx_quic_negotiate_version(c, pkt);
+    }
+
     if (!ngx_quic_pkt_in(pkt->flags)) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "quic invalid initial packet: 0x%xd", pkt->flags);
@@ -824,6 +830,35 @@ ngx_quic_new_connection(ngx_connection_t
 
 
 static ngx_int_t
+ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt)
+{
+    size_t             len;
+    ngx_quic_header_t  pkt;
+
+    /* buffer size is calculated assuming a single supported version */
+    static u_char      buf[NGX_QUIC_MAX_LONG_HEADER + sizeof(uint32_t)];
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "sending version negotiation packet");
+
+    pkt.log = c->log;
+    pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT;
+    pkt.dcid = inpkt->scid;
+    pkt.scid = inpkt->dcid;
+
+    len = ngx_quic_create_version_negotiation(&pkt, buf);
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+    ngx_quic_hexdump(c->log, "quic vnego packet to send", buf, len);
+#endif
+
+    (void) c->send(c, buf, len);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
 ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid)
 {
     uint8_t                 len;
@@ -1628,6 +1663,12 @@ ngx_quic_retry_input(ngx_connection_t *c
         return rc;
     }
 
+    if (pkt->version != NGX_QUIC_VERSION) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "quic unsupported version: 0x%xD", pkt->version);
+        return NGX_ERROR;
+    }
+
     if (ngx_quic_pkt_zrtt(pkt->flags)) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "quic discard inflight 0-RTT packet");
@@ -1715,6 +1756,12 @@ ngx_quic_initial_input(ngx_connection_t 
         return rc;
     }
 
+    if (pkt->version != NGX_QUIC_VERSION) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "quic unsupported version: 0x%xD", pkt->version);
+        return NGX_ERROR;
+    }
+
     if (ngx_quic_parse_initial_header(pkt) != NGX_OK) {
         return NGX_ERROR;
     }
@@ -1765,6 +1812,12 @@ ngx_quic_handshake_input(ngx_connection_
         return rc;
     }
 
+    if (pkt->version != NGX_QUIC_VERSION) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "quic unsupported version: 0x%xD", pkt->version);
+        return NGX_ERROR;
+    }
+
     if (ngx_quic_check_peer(qc, pkt) != NGX_OK) {
         return NGX_ERROR;
     }
@@ -1825,6 +1878,12 @@ ngx_quic_early_input(ngx_connection_t *c
         return rc;
     }
 
+    if (pkt->version != NGX_QUIC_VERSION) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "quic unsupported version: 0x%xD", pkt->version);
+        return NGX_ERROR;
+    }
+
     if (ngx_quic_check_peer(qc, pkt) != NGX_OK) {
         return NGX_ERROR;
     }
--- a/src/event/ngx_event_quic_transport.c
+++ b/src/event/ngx_event_quic_transport.c
@@ -88,6 +88,15 @@ static ngx_int_t ngx_quic_parse_transpor
     uint16_t id, ngx_quic_tp_t *dst);
 
 
+/* currently only single version (selected at compile-time) is supported */
+uint32_t  ngx_quic_versions[] = {
+    NGX_QUIC_VERSION
+};
+
+#define NGX_QUIC_NVERSIONS \
+    (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0]))
+
+
 /* literal errors indexed by corresponding value */
 static char *ngx_quic_errors[] = {
     "NO_ERROR",
@@ -232,8 +241,8 @@ ngx_quic_error_text(uint64_t error_code)
 ngx_int_t
 ngx_quic_parse_long_header(ngx_quic_header_t *pkt)
 {
-    u_char  *p, *end;
-    uint8_t  idlen;
+    u_char   *p, *end;
+    uint8_t   idlen;
 
     p = pkt->data;
     end = pkt->data + pkt->len;
@@ -270,12 +279,6 @@ ngx_quic_parse_long_header(ngx_quic_head
         return NGX_DECLINED;
     }
 
-    if (pkt->version != NGX_QUIC_VERSION) {
-        ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
-                      "quic unsupported version: 0x%xD", pkt->version);
-        return NGX_ERROR;
-    }
-
     p = ngx_quic_read_uint8(p, end, &idlen);
     if (p == NULL) {
         ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
@@ -327,6 +330,36 @@ ngx_quic_parse_long_header(ngx_quic_head
 
 
 size_t
+ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out)
+{
+    u_char      *p, *start;
+    ngx_uint_t   i;
+
+    p = start = out;
+
+    *p++ = pkt->flags;
+
+    /*
+     * The Version field of a Version Negotiation packet
+     * MUST be set to 0x00000000
+     */
+    p = ngx_quic_write_uint32(p, 0);
+
+    *p++ = pkt->dcid.len;
+    p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
+
+    *p++ = pkt->scid.len;
+    p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
+
+    for (i = 0; i < NGX_QUIC_NVERSIONS; i++) {
+        p = ngx_quic_write_uint32(p, ngx_quic_versions[i]);
+    }
+
+    return p - start;
+}
+
+
+size_t
 ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
     size_t pkt_len, u_char **pnp)
 {
--- a/src/event/ngx_event_quic_transport.h
+++ b/src/event/ngx_event_quic_transport.h
@@ -317,6 +317,8 @@ typedef struct {
 
 u_char *ngx_quic_error_text(uint64_t error_code);
 
+size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out);
+
 ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt);
 size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
     size_t pkt_len, u_char **pnp);