From: Amaury Denoyelle Date: Wed, 10 Nov 2021 14:17:56 +0000 (+0100) Subject: MEDIUM: quic: send version negotiation packet on unknown version X-Git-Tag: v2.5-dev15~39 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a22d860406cd4274fb54fce14d57ed864fb006cb;p=thirdparty%2Fhaproxy.git MEDIUM: quic: send version negotiation packet on unknown version 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. --- diff --git a/src/xprt_quic.c b/src/xprt_quic.c index 35afeaaf0e..ca1e44f6ba 100644 --- a/src/xprt_quic.c +++ b/src/xprt_quic.c @@ -52,6 +52,14 @@ #include #include +/* 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 bytes and one more for value */ @@ -3313,6 +3319,24 @@ static inline void qc_parse_hd_form(struct quic_rx_packet *pkt, } } +/* + * Check if the QUIC version in packet 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 on socket to + * address . + * 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;