]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: quic: send version negotiation packet on unknown version
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 10 Nov 2021 14:17:56 +0000 (15:17 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 18 Nov 2021 09:50:58 +0000 (10:50 +0100)
If the client announced a QUIC version not supported by haproxy, emit a
Version Negotiation Packet, according to RFC9000 6. Version Negotiation.

This is required to be able to use the framework for QUIC interop
testing from https://github.com/marten-seemann/quic-interop-runner. The
simulator checks that the server is available by sending packets to
force the emission of a Version Negotiation Packet.

src/xprt_quic.c

index 35afeaaf0e8e6827a0422a6ae25b42718631aa81..ca1e44f6ba57598ac2aa4c835da2d25f839be49d 100644 (file)
 #include <haproxy/trace.h>
 #include <haproxy/xprt_quic.h>
 
+/* list of supported QUIC versions by this implementation */
+static int quic_supported_version[] = {
+       0x00000001,
+
+       /* placeholder, do not add entry after this */
+       0x0
+};
+
 /* This is the values of some QUIC transport parameters when absent.
  * Should be used to initialize any transport parameters (local or remote)
  * before updating them with customized values.
@@ -3136,8 +3144,6 @@ static inline int quic_packet_read_long_header(unsigned char **buf, const unsign
        if (!quic_read_uint32(&pkt->version, (const unsigned char **)buf, end))
                return 0;
 
-       if (!pkt->version) { /* XXX TO DO XXX Version negotiation packet */ };
-
        /* Destination Connection ID Length */
        dcid_len = *(*buf)++;
        /* We want to be sure we can read <dcid_len> bytes and one more for <scid_len> value */
@@ -3313,6 +3319,24 @@ static inline void qc_parse_hd_form(struct quic_rx_packet *pkt,
        }
 }
 
+/*
+ * Check if the QUIC version in packet <pkt> is supported. Returns a boolean.
+ */
+static inline int qc_pkt_is_supported_version(struct quic_rx_packet *pkt)
+{
+       int j = 0, version;
+
+       do {
+               version = quic_supported_version[j];
+               if (version == pkt->version)
+                       return 1;
+
+               version = quic_supported_version[++j];
+       } while(version);
+
+       return 0;
+}
+
 __attribute__((unused))
 static ssize_t qc_srv_pkt_rcv(unsigned char **buf, const unsigned char *end,
                               struct quic_rx_packet *pkt,
@@ -3351,6 +3375,12 @@ static ssize_t qc_srv_pkt_rcv(unsigned char **buf, const unsigned char *end,
                if (!quic_packet_read_long_header(buf, end, pkt))
                        goto err;
 
+               /* unsupported QUIC version */
+               if (!qc_pkt_is_supported_version(pkt)) {
+                       TRACE_PROTO("Null QUIC version, packet dropped", QUIC_EV_CONN_LPKT);
+                       goto err;
+               }
+
                /* For Initial packets, and for servers (QUIC clients connections),
                 * there is no Initial connection IDs storage.
                 */
@@ -3467,6 +3497,60 @@ static ssize_t qc_srv_pkt_rcv(unsigned char **buf, const unsigned char *end,
        return -1;
 }
 
+/*
+ * Send a Version Negotiation packet on response to <pkt> on socket <fd> to
+ * address <addr>.
+ * Implementation of RFC9000 6. Version Negotiation
+ *
+ * TODO implement a rate-limiting sending of Version Negotiation packets
+ *
+ * Returns 0 on success else non-zero
+ */
+static int qc_send_version_negotiation(int fd, struct sockaddr_storage *addr,
+                                       struct quic_rx_packet *pkt)
+{
+       char buf[256];
+       int i = 0, j, version;
+       const socklen_t addrlen = get_addr_len(addr);
+
+       /*
+        * header form
+        * long header, fixed bit to 0 for Version Negotiation
+        */
+       buf[i++] = '\x80';
+
+       /* null version for Version Negotiation */
+       buf[i++] = '\x00';
+       buf[i++] = '\x00';
+       buf[i++] = '\x00';
+       buf[i++] = '\x00';
+
+       /* source connection id */
+       buf[i++] = pkt->scid.len;
+       memcpy(buf, pkt->scid.data, pkt->scid.len);
+       i += pkt->scid.len;
+
+       /* destination connection id */
+       buf[i++] = pkt->dcid.len;
+       memcpy(buf, pkt->dcid.data, pkt->dcid.len);
+       i += pkt->dcid.len;
+
+       /* supported version */
+       j = 0;
+       do {
+               version = htonl(quic_supported_version[j]);
+               memcpy(&buf[i], &version, sizeof(version));
+               i += sizeof(version);
+
+               version = quic_supported_version[++j];
+       } while (version);
+
+       if (sendto(fd, buf, i, 0, (struct sockaddr *)addr, addrlen) < 0)
+               return 1;
+
+       return 0;
+}
+
 static ssize_t qc_lstnr_pkt_rcv(unsigned char **buf, const unsigned char *end,
                                 struct quic_rx_packet *pkt,
                                 struct quic_dgram_ctx *dgram_ctx,
@@ -3508,6 +3592,26 @@ static ssize_t qc_lstnr_pkt_rcv(unsigned char **buf, const unsigned char *end,
                        goto err;
                }
 
+               /* RFC9000 6. Version Negotiation */
+               if (!qc_pkt_is_supported_version(pkt)) {
+                       /* do not send Version Negotiation in response to a
+                        * Version Negotiation packet.
+                        */
+                       if (!pkt->version) {
+                               TRACE_PROTO("Null QUIC version, packet dropped", QUIC_EV_CONN_LPKT);
+                               goto err;
+                       }
+
+                        /* unsupported version, send Negotiation packet */
+                       if (qc_send_version_negotiation(l->rx.fd, saddr, pkt)) {
+                               TRACE_PROTO("Error on Version Negotiation sending", QUIC_EV_CONN_LPKT);
+                               goto err;
+                       }
+
+                       TRACE_PROTO("Unsupported QUIC version, send Version Negotiation packet", QUIC_EV_CONN_LPKT);
+                       goto out;
+               }
+
                dcid_len = pkt->dcid.len;
                /* For Initial packets, and for servers (QUIC clients connections),
                 * there is no Initial connection IDs storage.
@@ -3721,6 +3825,7 @@ static ssize_t qc_lstnr_pkt_rcv(unsigned char **buf, const unsigned char *end,
        if (conn_ctx && HA_ATOMIC_LOAD(&conn_ctx->conn->ctx))
                tasklet_wakeup(conn_ctx->wait_event.tasklet);
 
+ out:
        TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc->conn, pkt);
 
        return pkt->len;