diff --git a/src/quic_conn.c b/src/quic_conn.c index fb791dd6d..6fa187ace 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -221,7 +222,9 @@ DECLARE_POOL(pool_head_quic_frame, "quic_frame", sizeof(struct quic_frame)); DECLARE_STATIC_POOL(pool_head_quic_arng, "quic_arng", sizeof(struct quic_arng_node)); static struct quic_connection_id *new_quic_cid(struct eb_root *root, - struct quic_conn *qc); + struct quic_conn *qc, + const struct quic_cid *odcid, + const struct sockaddr_storage *saddr); static struct quic_tx_packet *qc_build_pkt(unsigned char **pos, const unsigned char *buf_end, struct quic_enc_level *qel, struct quic_tls_ctx *ctx, struct list *frms, struct quic_conn *qc, @@ -3236,7 +3239,7 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt, pool_free(pool_head_quic_connection_id, cid); TRACE_PROTO("CID retired", QUIC_EV_CONN_PSTRM, qc); - cid = new_quic_cid(&qc->cids, qc); + cid = new_quic_cid(&qc->cids, qc, NULL, NULL); if (!cid) { TRACE_ERROR("CID allocation error", QUIC_EV_CONN_IO_CB, qc); } @@ -3883,21 +3886,83 @@ static int quic_stateless_reset_token_init(struct quic_conn *qc, return ret; } +/* Generate a CID directly derived from CID and address. The CID + * is then marked with the current thread ID. + * + * Returns a new 64-bits CID value. + */ +static uint64_t quic_derive_cid(const struct quic_cid *orig, + const struct sockaddr_storage *addr) +{ + const struct sockaddr_in *in; + const struct sockaddr_in6 *in6; + char *buf = trash.area; + size_t idx = 0; + uint64_t hash; + + /* Prepare buffer for hash using original CID first. */ + memcpy(buf, orig->data, orig->len); + idx += orig->len; + + /* Concatenate client address. */ + switch (addr->ss_family) { + case AF_INET: + in = (struct sockaddr_in *)addr; + + memcpy(&buf[idx], &in->sin_addr, sizeof(in->sin_addr)); + idx += sizeof(in->sin_addr); + memcpy(&buf[idx], &in->sin_port, sizeof(in->sin_port)); + idx += sizeof(in->sin_port); + break; + + case AF_INET6: + in6 = (struct sockaddr_in6 *)addr; + + memcpy(&buf[idx], &in6->sin6_addr, sizeof(in6->sin6_addr)); + idx += sizeof(in6->sin6_addr); + memcpy(&buf[idx], &in6->sin6_port, sizeof(in6->sin6_port)); + idx += sizeof(in6->sin6_port); + break; + + default: + /* TODO to implement */ + ABORT_NOW(); + return 0; + } + + /* Avoid similar values between multiple haproxy process. */ + memcpy(&buf[idx], boot_seed, sizeof(boot_seed)); + idx += sizeof(boot_seed); + + /* Hash the final buffer content. */ + hash = XXH64(buf, idx, 0); + + /* Mark the current thread id in the CID. */ + quic_pin_cid_to_tid((uchar *)&hash, tid); + + return hash; +} + /* Allocate a new CID and attach it to ebtree. * - * The CID is randomly generated in part with the result altered to be - * associated with the current thread ID. This means this function must only - * be called by the quic_conn thread. + * If and params are non null, the new CID value is directly + * derived from them. Else a random value is generated. The CID is then marked + * with the current thread ID. * * Returns the new CID if succeeded, NULL if not. */ static struct quic_connection_id *new_quic_cid(struct eb_root *root, - struct quic_conn *qc) + struct quic_conn *qc, + const struct quic_cid *orig, + const struct sockaddr_storage *addr) { struct quic_connection_id *cid; TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc); + /* Caller must set either none or both values. */ + BUG_ON(!!orig != !!addr); + cid = pool_alloc(pool_head_quic_connection_id); if (!cid) { TRACE_ERROR("cid allocation failed", QUIC_EV_CONN_TXPKT, qc); @@ -3905,13 +3970,21 @@ static struct quic_connection_id *new_quic_cid(struct eb_root *root, } cid->cid.len = QUIC_HAP_CID_LEN; - /* TODO: RAND_bytes() should be replaced */ - if (RAND_bytes(cid->cid.data, cid->cid.len) != 1) { - TRACE_ERROR("RAND_bytes() failed", QUIC_EV_CONN_TXPKT, qc); - goto err; + + if (!orig) { + /* TODO: RAND_bytes() should be replaced */ + if (RAND_bytes(cid->cid.data, cid->cid.len) != 1) { + TRACE_ERROR("RAND_bytes() failed", QUIC_EV_CONN_TXPKT, qc); + goto err; + } + quic_pin_cid_to_tid(cid->cid.data, tid); + } + else { + /* Derive the new CID value from original CID. */ + const uint64_t hash = quic_derive_cid(orig, addr); + memcpy(cid->cid.data, &hash, sizeof(hash)); } - quic_pin_cid_to_tid(cid->cid.data, tid); if (quic_stateless_reset_token_init(qc, cid) != 1) { TRACE_ERROR("quic_stateless_reset_token_init() failed", QUIC_EV_CONN_TXPKT, qc); goto err; @@ -3977,7 +4050,7 @@ static int quic_build_post_handshake_frames(struct quic_conn *qc) goto err; } - cid = new_quic_cid(&qc->cids, qc); + cid = new_quic_cid(&qc->cids, qc, NULL, NULL); if (!cid) { qc_frm_free(&frm); TRACE_ERROR("CID allocation error", QUIC_EV_CONN_IO_CB, qc); @@ -5372,8 +5445,13 @@ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, /* Initialize the next CID sequence number to be used for this connection. */ qc->next_cid_seq_num = 0; - /* Insert the CID for this connection with 0 as sequence number. */ - icid = new_quic_cid(&qc->cids, qc); + /* Generate the first connection CID. This is derived from the client + * ODCID and address. This allows to retrieve the connection from the + * ODCID without storing it in the CID tree. This is an interesting + * optimization as the client is expected to stop using its ODCID in + * favor of our generated value. + */ + icid = new_quic_cid(&qc->cids, qc, dcid, peer_addr); if (!icid) { TRACE_ERROR("Could not allocate a new connection ID", QUIC_EV_CONN_INIT, qc); goto err;