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.
This commit is contained in:
Amaury Denoyelle 2021-11-10 15:17:56 +01:00
parent 154bc7f864
commit a22d860406

View File

@ -52,6 +52,14 @@
#include <haproxy/trace.h> #include <haproxy/trace.h>
#include <haproxy/xprt_quic.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. /* This is the values of some QUIC transport parameters when absent.
* Should be used to initialize any transport parameters (local or remote) * Should be used to initialize any transport parameters (local or remote)
* before updating them with customized values. * 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)) if (!quic_read_uint32(&pkt->version, (const unsigned char **)buf, end))
return 0; return 0;
if (!pkt->version) { /* XXX TO DO XXX Version negotiation packet */ };
/* Destination Connection ID Length */ /* Destination Connection ID Length */
dcid_len = *(*buf)++; dcid_len = *(*buf)++;
/* We want to be sure we can read <dcid_len> bytes and one more for <scid_len> value */ /* 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)) __attribute__((unused))
static ssize_t qc_srv_pkt_rcv(unsigned char **buf, const unsigned char *end, static ssize_t qc_srv_pkt_rcv(unsigned char **buf, const unsigned char *end,
struct quic_rx_packet *pkt, 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)) if (!quic_packet_read_long_header(buf, end, pkt))
goto err; 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), /* For Initial packets, and for servers (QUIC clients connections),
* there is no Initial connection IDs storage. * 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; 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, static ssize_t qc_lstnr_pkt_rcv(unsigned char **buf, const unsigned char *end,
struct quic_rx_packet *pkt, struct quic_rx_packet *pkt,
struct quic_dgram_ctx *dgram_ctx, 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; 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; dcid_len = pkt->dcid.len;
/* For Initial packets, and for servers (QUIC clients connections), /* For Initial packets, and for servers (QUIC clients connections),
* there is no Initial connection IDs storage. * 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)) if (conn_ctx && HA_ATOMIC_LOAD(&conn_ctx->conn->ctx))
tasklet_wakeup(conn_ctx->wait_event.tasklet); tasklet_wakeup(conn_ctx->wait_event.tasklet);
out:
TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc->conn, pkt); TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc->conn, pkt);
return pkt->len; return pkt->len;