From a5801e542da04f5c8dcca9214f31dfb940775b29 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Fri, 21 Nov 2025 17:21:26 +0100 Subject: [PATCH] MINOR: quic: split global CID tree between FE and BE sides QUIC CIDs are stored in a global tree. Prior to this patch, CIDs used on both frontend and backend sides were mixed together. This patch implement CID storage separation between FE and BE sides. The original tre quic_cid_trees is splitted as quic_fe_cid_trees/quic_be_cid_trees. This patch should reduce contention between frontend and backend usages. Also, it should reduce the risk of random CID collision. --- include/haproxy/quic_cid-t.h | 15 ++++++-- include/haproxy/quic_cid.h | 16 ++++++-- include/haproxy/quic_conn.h | 6 +++ src/quic_cid.c | 75 ++++++++++++++++++++++++++---------- src/quic_conn.c | 2 +- src/quic_rx.c | 4 +- src/xprt_quic.c | 2 +- 7 files changed, 87 insertions(+), 33 deletions(-) diff --git a/include/haproxy/quic_cid-t.h b/include/haproxy/quic_cid-t.h index f19ce2626..703fe6e76 100644 --- a/include/haproxy/quic_cid-t.h +++ b/include/haproxy/quic_cid-t.h @@ -24,6 +24,12 @@ struct quic_cid { unsigned char len; /* size of QUIC CID */ }; +/* Determines whether a CID is used for frontend or backend connections. */ +enum quic_cid_side { + QUIC_CID_SIDE_FE, + QUIC_CID_SIDE_BE +}; + /* QUIC connection id attached to a QUIC connection. * * This structure is used to match received packets DCIDs with the @@ -34,11 +40,12 @@ struct quic_connection_id { uint64_t retire_prior_to; unsigned char stateless_reset_token[QUIC_STATELESS_RESET_TOKEN_LEN]; - struct ebmb_node node; /* node for receiver tree, cid.data as key */ - struct quic_cid cid; /* CID data */ + struct ebmb_node node; /* node for receiver tree, cid.data as key */ + struct quic_cid cid; /* CID data */ - struct quic_conn *qc; /* QUIC connection using this CID */ - uint tid; /* Attached Thread ID for the connection. */ + struct quic_conn *qc; /* QUIC connection using this CID */ + uint tid; /* Attached Thread ID for the connection. */ + enum quic_cid_side side; /* side where this CID is used */ }; #endif /* _HAPROXY_QUIC_CID_T_H */ diff --git a/include/haproxy/quic_cid.h b/include/haproxy/quic_cid.h index 13a0669d1..05f3e5b80 100644 --- a/include/haproxy/quic_cid.h +++ b/include/haproxy/quic_cid.h @@ -15,9 +15,10 @@ #include #include -extern struct quic_cid_tree *quic_cid_trees; +extern struct quic_cid_tree *quic_fe_cid_trees; +extern struct quic_cid_tree *quic_be_cid_trees; -struct quic_connection_id *quic_cid_alloc(void); +struct quic_connection_id *quic_cid_alloc(enum quic_cid_side side); int quic_cid_generate_random(struct quic_connection_id *conn_id); int quic_cid_generate_from_hash(struct quic_connection_id *conn_id, uint64_t hash64); @@ -81,11 +82,18 @@ static inline uchar quic_cid_tree_idx(const struct quic_cid *cid) return _quic_cid_tree_idx(cid->data); } +/* Returns the tree instance responsible for storage. */ +static inline struct quic_cid_tree *quic_cid_get_tree(const struct quic_connection_id *conn_id) +{ + const int tree_idx = quic_cid_tree_idx(&conn_id->cid); + return conn_id->side == QUIC_CID_SIDE_FE ? + &quic_fe_cid_trees[tree_idx] : &quic_be_cid_trees[tree_idx]; +} + /* Remove from global CID tree as a thread-safe operation. */ static inline void quic_cid_delete(struct quic_connection_id *conn_id) { - const uchar idx = quic_cid_tree_idx(&conn_id->cid); - struct quic_cid_tree __maybe_unused *tree = &quic_cid_trees[idx]; + struct quic_cid_tree __maybe_unused *tree = quic_cid_get_tree(conn_id); HA_RWLOCK_WRLOCK(QC_CID_LOCK, &tree->lock); ebmb_delete(&conn_id->node); diff --git a/include/haproxy/quic_conn.h b/include/haproxy/quic_conn.h index 3c447c73d..84318c1bf 100644 --- a/include/haproxy/quic_conn.h +++ b/include/haproxy/quic_conn.h @@ -91,6 +91,12 @@ static inline int qc_is_back(const struct quic_conn *qc) return qc->flags & QUIC_FL_CONN_IS_BACK; } +static inline enum quic_cid_side qc_cid_side(const struct quic_conn *qc) +{ + return !(qc->flags & QUIC_FL_CONN_IS_BACK) ? + QUIC_CID_SIDE_FE : QUIC_CID_SIDE_BE; +} + /* Free the CIDs attached to QUIC connection. */ static inline void free_quic_conn_cids(struct quic_conn *conn) { diff --git a/src/quic_cid.c b/src/quic_cid.c index 166d9074d..28164c7d1 100644 --- a/src/quic_cid.c +++ b/src/quic_cid.c @@ -17,11 +17,12 @@ * * . CID global storage * CIDs generated by haproxy and reuse by the peer as DCID are stored in a - * global tree. Tree access must only be done under lock protection. + * global tree. Tree access must only be done under lock protection. Separate + * trees are used on frontend and backend sides. * * . CID global tree splitting - * To reduce thread contention, global CID tree is in reality split into 256 - * distinct tree instances. Each CID is assigned to a single tree instance + * To reduce the thread contention, a global CID tree is in reality splitted + * into 256 distinct instances. Each CID is assigned to a single tree instance * based on its content. Use quic_cid_tree_idx() to retrieve the expected tree * location for a CID. * @@ -35,7 +36,8 @@ */ #define QUIC_CID_TREES_CNT 256 -struct quic_cid_tree *quic_cid_trees; +struct quic_cid_tree *quic_fe_cid_trees; +struct quic_cid_tree *quic_be_cid_trees; /* Initialize the stateless reset token attached to connection ID. * Returns 1 if succeeded, 0 if not. @@ -120,7 +122,7 @@ static struct quic_cid quic_derive_cid(const struct quic_cid *orig, * * Returns the CID or NULL on allocation failure. */ -struct quic_connection_id *quic_cid_alloc(void) +struct quic_connection_id *quic_cid_alloc(enum quic_cid_side side) { struct quic_connection_id *conn_id; @@ -135,6 +137,7 @@ struct quic_connection_id *quic_cid_alloc(void) HA_ATOMIC_STORE(&conn_id->tid, tid); conn_id->qc = NULL; + conn_id->side = side; TRACE_LEAVE(QUIC_EV_CONN_TXPKT); return conn_id; @@ -280,8 +283,8 @@ int quic_cid_insert(struct quic_connection_id *conn_id, int *new_tid) if (new_tid) *new_tid = -1; - tree = &quic_cid_trees[quic_cid_tree_idx(&conn_id->cid)]; + tree = quic_cid_get_tree(conn_id); HA_RWLOCK_WRLOCK(QC_CID_LOCK, &tree->lock); node = ebmb_insert(&tree->root, &conn_id->node, conn_id->cid.len); if (node != &conn_id->node) { @@ -312,7 +315,8 @@ int quic_cmp_cid_conn(const unsigned char *cid, size_t cid_len, struct ebmb_node *node; int ret = 0; - tree = &quic_cid_trees[_quic_cid_tree_idx(cid)]; + /* This function is only used on frontend side. */ + tree = &quic_fe_cid_trees[_quic_cid_tree_idx(cid)]; HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); node = ebmb_lookup(&tree->root, cid, cid_len); if (node) { @@ -342,7 +346,8 @@ int quic_get_cid_tid(const unsigned char *cid, size_t cid_len, struct ebmb_node *node; int cid_tid = -1; - tree = &quic_cid_trees[_quic_cid_tree_idx(cid)]; + /* This function is only used on frontend side. */ + tree = &quic_fe_cid_trees[_quic_cid_tree_idx(cid)]; HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); node = ebmb_lookup(&tree->root, cid, cid_len); if (node) { @@ -371,7 +376,7 @@ int quic_get_cid_tid(const unsigned char *cid, size_t cid_len, orig.len = cid_len; derive_cid = quic_derive_cid(&orig, cli_addr); - tree = &quic_cid_trees[quic_cid_tree_idx(&derive_cid)]; + tree = &quic_fe_cid_trees[quic_cid_tree_idx(&derive_cid)]; HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); node = ebmb_lookup(&tree->root, cid, cid_len); if (node) { @@ -404,8 +409,10 @@ struct quic_conn *retrieve_qc_conn_from_cid(struct quic_rx_packet *pkt, 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)]; + /* First look into DCID tree. + * This function is only used on frontend side. + */ + tree = &quic_fe_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); @@ -419,7 +426,7 @@ struct quic_conn *retrieve_qc_conn_from_cid(struct quic_rx_packet *pkt, HA_RWLOCK_RDUNLOCK(QC_CID_LOCK, &tree->lock); - tree = &quic_cid_trees[quic_cid_tree_idx(&derive_cid)]; + tree = &quic_fe_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); } @@ -475,28 +482,54 @@ int qc_build_new_connection_id_frm(struct quic_conn *qc, return ret; } -static int quic_alloc_global_cid_tree(void) +static int quic_alloc_global_fe_cid_tree(void) { int i; - quic_cid_trees = calloc(QUIC_CID_TREES_CNT, sizeof(*quic_cid_trees)); - if (!quic_cid_trees) { + quic_fe_cid_trees = calloc(QUIC_CID_TREES_CNT, sizeof(*quic_fe_cid_trees)); + if (!quic_fe_cid_trees) { ha_alert("Failed to allocate global quic CIDs trees.\n"); return 0; } for (i = 0; i < QUIC_CID_TREES_CNT; ++i) { - HA_RWLOCK_INIT(&quic_cid_trees[i].lock); - quic_cid_trees[i].root = EB_ROOT_UNIQUE; + HA_RWLOCK_INIT(&quic_fe_cid_trees[i].lock); + quic_fe_cid_trees[i].root = EB_ROOT_UNIQUE; } return 1; } -REGISTER_POST_CHECK(quic_alloc_global_cid_tree); +REGISTER_POST_CHECK(quic_alloc_global_fe_cid_tree); -static int quic_deallocate_global_cid_tree(void) +static int quic_deallocate_global_fe_cid_tree(void) { - ha_free(&quic_cid_trees); + ha_free(&quic_fe_cid_trees); return 1; } -REGISTER_POST_DEINIT(quic_deallocate_global_cid_tree); +REGISTER_POST_DEINIT(quic_deallocate_global_fe_cid_tree); + +static int quic_alloc_global_be_cid_tree(void) +{ + int i; + + quic_be_cid_trees = calloc(QUIC_CID_TREES_CNT, sizeof(*quic_be_cid_trees)); + if (!quic_be_cid_trees) { + ha_alert("Failed to allocate global quic CIDs trees.\n"); + return 0; + } + + for (i = 0; i < QUIC_CID_TREES_CNT; ++i) { + HA_RWLOCK_INIT(&quic_be_cid_trees[i].lock); + quic_be_cid_trees[i].root = EB_ROOT_UNIQUE; + } + + return 1; +} +REGISTER_POST_CHECK(quic_alloc_global_be_cid_tree); + +static int quic_deallocate_global_be_cid_tree(void) +{ + ha_free(&quic_be_cid_trees); + return 1; +} +REGISTER_POST_DEINIT(quic_deallocate_global_be_cid_tree); diff --git a/src/quic_conn.c b/src/quic_conn.c index 249f7a616..2e15bcb29 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -550,7 +550,7 @@ int quic_build_post_handshake_frames(struct quic_conn *qc, goto err; } - conn_id = quic_cid_alloc(); + conn_id = quic_cid_alloc(qc_cid_side(qc)); if (!conn_id) { qc_frm_free(qc, &frm); TRACE_ERROR("CID allocation error", QUIC_EV_CONN_IO_CB, qc); diff --git a/src/quic_rx.c b/src/quic_rx.c index ca4f916f6..c01cc9d33 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -1052,7 +1052,7 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt, pool_free(pool_head_quic_connection_id, conn_id); TRACE_PROTO("CID retired", QUIC_EV_CONN_PSTRM, qc); - conn_id = quic_cid_alloc(); + conn_id = quic_cid_alloc(qc_cid_side(qc)); if (!conn_id) { TRACE_ERROR("CID allocation error", QUIC_EV_CONN_IO_CB, qc); quic_set_connection_close(qc, quic_err_transport(QC_ERR_INTERNAL_ERROR)); @@ -1798,7 +1798,7 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt, pkt->saddr = dgram->saddr; - conn_id = quic_cid_alloc(); + conn_id = quic_cid_alloc(QUIC_CID_SIDE_FE); if (!conn_id) { TRACE_ERROR("error on first CID allocation", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version); diff --git a/src/xprt_quic.c b/src/xprt_quic.c index e16274771..c21b1b551 100644 --- a/src/xprt_quic.c +++ b/src/xprt_quic.c @@ -138,7 +138,7 @@ static int qc_conn_init(struct connection *conn, void **xprt_ctx) int retry_rand_cid = 3; /* Number of random retries on CID collision. */ struct server *srv = objt_server(conn->target); - conn_id = quic_cid_alloc(); + conn_id = quic_cid_alloc(QUIC_CID_SIDE_BE); if (!conn_id) { TRACE_ERROR("error on CID allocation", QUIC_EV_CONN_NEW); goto out;