diff options
Diffstat (limited to '')
-rw-r--r-- | src/quic_cid.c | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/src/quic_cid.c b/src/quic_cid.c new file mode 100644 index 0000000..19c1f07 --- /dev/null +++ b/src/quic_cid.c @@ -0,0 +1,286 @@ +#include <import/eb64tree.h> +#include <import/ebmbtree.h> + +#include <haproxy/pool.h> +#include <haproxy/quic_cid.h> +#include <haproxy/quic_conn.h> +#include <haproxy/quic_rx-t.h> +#include <haproxy/quic_trace.h> +#include <haproxy/trace.h> +#include <haproxy/xxhash.h> + +/* Initialize the stateless reset token attached to <conn_id> connection ID. + * Returns 1 if succeeded, 0 if not. + */ +static int quic_stateless_reset_token_init(struct quic_connection_id *conn_id) +{ + /* Output secret */ + unsigned char *token = conn_id->stateless_reset_token; + size_t tokenlen = sizeof conn_id->stateless_reset_token; + /* Salt */ + const unsigned char *cid = conn_id->cid.data; + size_t cidlen = conn_id->cid.len; + + return quic_stateless_reset_token_cpy(token, tokenlen, cid, cidlen); +} + +/* Generate a CID directly derived from <orig> CID and <addr> address. + * + * Returns the derived CID. + */ +struct quic_cid quic_derive_cid(const struct quic_cid *orig, + const struct sockaddr_storage *addr) +{ + struct quic_cid cid; + const struct sockaddr_in *in; + const struct sockaddr_in6 *in6; + char *pos = trash.area; + size_t idx = 0; + uint64_t hash; + int i; + + /* Prepare buffer for hash using original CID first. */ + memcpy(pos, orig->data, orig->len); + idx += orig->len; + + /* Concatenate client address. */ + switch (addr->ss_family) { + case AF_INET: + in = (struct sockaddr_in *)addr; + + memcpy(&pos[idx], &in->sin_addr, sizeof(in->sin_addr)); + idx += sizeof(in->sin_addr); + memcpy(&pos[idx], &in->sin_port, sizeof(in->sin_port)); + idx += sizeof(in->sin_port); + break; + + case AF_INET6: + in6 = (struct sockaddr_in6 *)addr; + + memcpy(&pos[idx], &in6->sin6_addr, sizeof(in6->sin6_addr)); + idx += sizeof(in6->sin6_addr); + memcpy(&pos[idx], &in6->sin6_port, sizeof(in6->sin6_port)); + idx += sizeof(in6->sin6_port); + break; + + default: + /* TODO to implement */ + ABORT_NOW(); + } + + /* Avoid similar values between multiple haproxy process. */ + memcpy(&pos[idx], boot_seed, sizeof(boot_seed)); + idx += sizeof(boot_seed); + + /* Hash the final buffer content. */ + hash = XXH64(pos, idx, 0); + + for (i = 0; i < sizeof(hash); ++i) + cid.data[i] = hash >> ((sizeof(hash) * 7) - (8 * i)); + cid.len = sizeof(hash); + + return cid; +} + +/* Allocate a new CID and attach it to <root> ebtree. + * + * If <orig> and <addr> 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. + */ +struct quic_connection_id *new_quic_cid(struct eb_root *root, + struct quic_conn *qc, + const struct quic_cid *orig, + const struct sockaddr_storage *addr) +{ + struct quic_connection_id *conn_id; + + TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc); + + /* Caller must set either none or both values. */ + BUG_ON(!!orig != !!addr); + + conn_id = pool_alloc(pool_head_quic_connection_id); + if (!conn_id) { + TRACE_ERROR("cid allocation failed", QUIC_EV_CONN_TXPKT, qc); + goto err; + } + + conn_id->cid.len = QUIC_HAP_CID_LEN; + + if (!orig) { + if (quic_newcid_from_hash64) + quic_newcid_from_hash64(conn_id->cid.data, conn_id->cid.len, qc->hash64, + global.cluster_secret, sizeof(global.cluster_secret)); + else if (RAND_bytes(conn_id->cid.data, conn_id->cid.len) != 1) { + /* TODO: RAND_bytes() should be replaced */ + TRACE_ERROR("RAND_bytes() failed", QUIC_EV_CONN_TXPKT, qc); + goto err; + } + } + else { + /* Derive the new CID value from original CID. */ + conn_id->cid = quic_derive_cid(orig, addr); + } + + if (quic_stateless_reset_token_init(conn_id) != 1) { + TRACE_ERROR("quic_stateless_reset_token_init() failed", QUIC_EV_CONN_TXPKT, qc); + goto err; + } + + conn_id->qc = qc; + HA_ATOMIC_STORE(&conn_id->tid, tid); + + conn_id->seq_num.key = qc ? qc->next_cid_seq_num++ : 0; + conn_id->retire_prior_to = 0; + /* insert the allocated CID in the quic_conn tree */ + if (root) + eb64_insert(root, &conn_id->seq_num); + + TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc); + return conn_id; + + err: + pool_free(pool_head_quic_connection_id, conn_id); + TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc); + return NULL; +} + +/* Retrieve the thread ID associated to QUIC connection ID <cid> of length + * <cid_len>. CID may be not found on the CID tree because it is an ODCID. In + * this case, it will derived using client address <cli_addr> as hash + * parameter. However, this is done only if <pos> points to an INITIAL or 0RTT + * packet of length <len>. + * + * Returns the thread ID or a negative error code. + */ +int quic_get_cid_tid(const unsigned char *cid, size_t cid_len, + const struct sockaddr_storage *cli_addr, + unsigned char *pos, size_t len) +{ + struct quic_cid_tree *tree; + struct quic_connection_id *conn_id; + struct ebmb_node *node; + + tree = &quic_cid_trees[_quic_cid_tree_idx(cid)]; + HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); + node = ebmb_lookup(&tree->root, cid, cid_len); + HA_RWLOCK_RDUNLOCK(QC_CID_LOCK, &tree->lock); + + if (!node) { + struct quic_cid orig, derive_cid; + struct quic_rx_packet pkt; + + if (!qc_parse_hd_form(&pkt, &pos, pos + len)) + goto not_found; + + if (pkt.type != QUIC_PACKET_TYPE_INITIAL && + pkt.type != QUIC_PACKET_TYPE_0RTT) { + goto not_found; + } + + memcpy(orig.data, cid, cid_len); + orig.len = cid_len; + derive_cid = quic_derive_cid(&orig, cli_addr); + + tree = &quic_cid_trees[quic_cid_tree_idx(&derive_cid)]; + HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); + node = ebmb_lookup(&tree->root, cid, cid_len); + HA_RWLOCK_RDUNLOCK(QC_CID_LOCK, &tree->lock); + } + + if (!node) + goto not_found; + + conn_id = ebmb_entry(node, struct quic_connection_id, node); + return HA_ATOMIC_LOAD(&conn_id->tid); + + not_found: + return -1; +} + +/* Retrieve a quic_conn instance from the <pkt> DCID field. If the packet is an + * INITIAL or 0RTT type, we may have to use client address <saddr> if an ODCID + * is used. + * + * Returns the instance or NULL if not found. + */ +struct quic_conn *retrieve_qc_conn_from_cid(struct quic_rx_packet *pkt, + struct sockaddr_storage *saddr, + int *new_tid) +{ + struct quic_conn *qc = NULL; + struct ebmb_node *node; + struct quic_connection_id *conn_id; + struct quic_cid_tree *tree; + uint conn_id_tid; + + TRACE_ENTER(QUIC_EV_CONN_RXPKT); + *new_tid = -1; + + /* First look into DCID tree. */ + tree = &quic_cid_trees[_quic_cid_tree_idx(pkt->dcid.data)]; + HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); + node = ebmb_lookup(&tree->root, pkt->dcid.data, pkt->dcid.len); + + /* If not found on an Initial/0-RTT packet, it could be because an + * ODCID is reused by the client. Calculate the derived CID value to + * retrieve it from the DCID tree. + */ + if (!node && (pkt->type == QUIC_PACKET_TYPE_INITIAL || + pkt->type == QUIC_PACKET_TYPE_0RTT)) { + const struct quic_cid derive_cid = quic_derive_cid(&pkt->dcid, saddr); + + HA_RWLOCK_RDUNLOCK(QC_CID_LOCK, &tree->lock); + + tree = &quic_cid_trees[quic_cid_tree_idx(&derive_cid)]; + HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); + node = ebmb_lookup(&tree->root, derive_cid.data, derive_cid.len); + } + + if (!node) + goto end; + + conn_id = ebmb_entry(node, struct quic_connection_id, node); + conn_id_tid = HA_ATOMIC_LOAD(&conn_id->tid); + if (conn_id_tid != tid) { + *new_tid = conn_id_tid; + goto end; + } + qc = conn_id->qc; + + end: + HA_RWLOCK_RDUNLOCK(QC_CID_LOCK, &tree->lock); + TRACE_LEAVE(QUIC_EV_CONN_RXPKT, qc); + return qc; +} + +/* Build a NEW_CONNECTION_ID frame for <conn_id> CID of <qc> connection. + * + * Returns 1 on success else 0. + */ +int qc_build_new_connection_id_frm(struct quic_conn *qc, + struct quic_connection_id *conn_id) +{ + int ret = 0; + struct quic_frame *frm; + struct quic_enc_level *qel; + + TRACE_ENTER(QUIC_EV_CONN_PRSHPKT, qc); + + qel = qc->ael; + frm = qc_frm_alloc(QUIC_FT_NEW_CONNECTION_ID); + if (!frm) { + TRACE_ERROR("frame allocation error", QUIC_EV_CONN_IO_CB, qc); + goto leave; + } + + quic_connection_id_to_frm_cpy(frm, conn_id); + LIST_APPEND(&qel->pktns->tx.frms, &frm->list); + ret = 1; + leave: + TRACE_LEAVE(QUIC_EV_CONN_PRSHPKT, qc); + return ret; +} |