From b76ae6951347a9e072c03ac54a047901ab19f25c Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Tue, 11 Jan 2022 14:16:37 +0100 Subject: [PATCH] MEDIUM: quic: implement Retry emission Implement the emission of Retry packets. These packets are emitted in response to Initial from clients without token. The token from the Retry packet contains the ODCID from the Initial packet. By default, Retry packet emission is disabled and the handshake can continue without address validation. To enable Retry, a new bind option has been defined named "quic-force-retry". If set, the handshake must be conducted only after receiving a token in the Initial packet. --- include/haproxy/listener-t.h | 1 + src/cfgparse-quic.c | 7 +++ src/xprt_quic.c | 97 +++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/include/haproxy/listener-t.h b/include/haproxy/listener-t.h index fa1042e8b..ebdeb7ca6 100644 --- a/include/haproxy/listener-t.h +++ b/include/haproxy/listener-t.h @@ -170,6 +170,7 @@ struct bind_conf { #endif #ifdef USE_QUIC struct quic_transport_params quic_params; /* QUIC transport parameters. */ + unsigned int quic_force_retry:1; /* always send Retry on reception of Initial without token */ #endif struct proxy *frontend; /* the frontend all these listeners belong to, or NULL */ const struct mux_proto_list *mux_proto; /* the mux to use for all incoming connections (specified by the "proto" keyword) */ diff --git a/src/cfgparse-quic.c b/src/cfgparse-quic.c index 34ec72916..4a94bb31d 100644 --- a/src/cfgparse-quic.c +++ b/src/cfgparse-quic.c @@ -2,7 +2,14 @@ #include #include +static int bind_parse_quic_force_retry(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->quic_force_retry = 1; + return 0; +} + static struct bind_kw_list bind_kws = { "QUIC", { }, { + { "quic-force-retry", bind_parse_quic_force_retry, 0 }, { NULL, NULL, 0 }, }}; diff --git a/src/xprt_quic.c b/src/xprt_quic.c index 83379d097..514a9517e 100644 --- a/src/xprt_quic.c +++ b/src/xprt_quic.c @@ -3981,6 +3981,88 @@ static int send_version_negotiation(int fd, struct sockaddr_storage *addr, return 0; } +/* Generate the token to be used in Retry packets. The token is written to + * which is expected to be bytes. + * + * Various parameters are expected to be encoded in the token. For now, only + * the DCID from is stored. This is useful to implement a stateless Retry + * as this CID must be repeated by the server in the transport parameters. + * + * TODO add the client address to validate the token origin. + * + * Returns the length of the encoded token or 0 on error. + */ +static int generate_retry_token(unsigned char *buf, unsigned char len, + struct quic_rx_packet *pkt) +{ + const size_t token_len = 1 + pkt->dcid.len; + unsigned char i = 0; + + if (token_len > len) + return 0; + + buf[i++] = pkt->dcid.len; + memcpy(&buf[i], pkt->dcid.data, pkt->dcid.len); + i += pkt->dcid.len; + + return i; +} + +/* Generate a Retry packet and send it on socket to in response to + * the Initial packet. + * + * Returns 0 on success else non-zero. + */ +static int send_retry(int fd, struct sockaddr_storage *addr, + struct quic_rx_packet *pkt) +{ + unsigned char buf[128]; + int i = 0, token_len; + const socklen_t addrlen = get_addr_len(addr); + struct quic_cid scid; + + /* long header + fixed bit + packet type 0x3 */ + buf[i++] = 0xf0; + /* version */ + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0x00; + buf[i++] = 0x01; + + /* Use the SCID from for Retry DCID. */ + buf[i++] = pkt->scid.len; + memcpy(&buf[i], pkt->scid.data, pkt->scid.len); + i += pkt->scid.len; + + /* Generate a new CID to be used as SCID for the Retry packet. */ + scid.len = QUIC_HAP_CID_LEN; + if (RAND_bytes(scid.data, scid.len) != 1) + return 1; + + buf[i++] = scid.len; + memcpy(&buf[i], scid.data, scid.len); + i += scid.len; + + /* token */ + if (!(token_len = generate_retry_token(&buf[i], &buf[i] - buf, pkt))) + return 1; + i += token_len; + + /* token integrity tag */ + if ((&buf[i] - buf < QUIC_TLS_TAG_LEN) || + !quic_tls_generate_retry_integrity_tag(pkt->dcid.data, + pkt->dcid.len, buf, i)) { + return 1; + } + + i += QUIC_TLS_TAG_LEN; + + if (sendto(fd, buf, i, 0, (struct sockaddr *)addr, addrlen) < 0) + return 1; + + return 0; +} + /* Retrieve a quic_conn instance from the DCID field. If the packet is of * type INITIAL, the ODCID tree is first used. In this case, is * concatenated to the DCID field. @@ -4146,7 +4228,20 @@ static ssize_t qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, * The token must be provided in a Retry packet or NEW_TOKEN frame. */ pkt->token_len = token_len; - if (pkt->token_len) { + + /* TODO Retry should be automatically activated if + * suspect network usage is detected. + */ + if (!token_len && l->bind_conf->quic_force_retry) { + TRACE_PROTO("Initial without token, sending retry", QUIC_EV_CONN_LPKT); + if (send_retry(l->rx.fd, saddr, pkt)) { + TRACE_PROTO("Error during Retry generation", QUIC_EV_CONN_LPKT); + goto err; + } + + goto err; + } + else { pkt->token = buf; buf += pkt->token_len; }