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.
diff --git a/include/haproxy/listener-t.h b/include/haproxy/listener-t.h
index fa1042e..ebdeb7c 100644
--- a/include/haproxy/listener-t.h
+++ b/include/haproxy/listener-t.h
@@ -170,6 +170,7 @@
#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 34ec729..4a94bb3 100644
--- a/src/cfgparse-quic.c
+++ b/src/cfgparse-quic.c
@@ -2,7 +2,14 @@
#include <haproxy/listener.h>
#include <haproxy/proxy-t.h>
+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 83379d0..514a951 100644
--- a/src/xprt_quic.c
+++ b/src/xprt_quic.c
@@ -3981,6 +3981,88 @@
return 0;
}
+/* Generate the token to be used in Retry packets. The token is written to
+ * <buf> which is expected to be <len> bytes.
+ *
+ * Various parameters are expected to be encoded in the token. For now, only
+ * the DCID from <pkt> 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 <fd> socket to <addr> in response to
+ * the Initial <pkt> 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 <pkt> 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 <pkt> DCID field. If the packet is of
* type INITIAL, the ODCID tree is first used. In this case, <saddr> is
* concatenated to the <pkt> DCID field.
@@ -4146,7 +4228,20 @@
* 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;
}