On Initial packet parsing, a new quic_conn instance is allocated via
qc_new_conn(). Then a CID is allocated with its value derivated from
client ODCID. On CID tree insert, a collision can occur if another
thread was already parsing an Initial packet from the same client. In
this case, the connection is released and the packet will be requeued to
the other thread.
Originally, CID collision check was performed prior to quic_conn
allocation. This was changed by the commit below, as this could cause
issue on quic_conn alloc failure.
commit
4ae29be18c5b212dd2a1a8e9fa0ee2fcb9dbb4b3
BUG/MINOR: quic: Possible endless loop in quic_lstnr_dghdlr()
However, this procedure is less optimal. Indeed, qc_new_conn() performs
many steps, thus it could be better to skip it on Initial CID collision,
which can happen frequently. This patch restores the older order of
operations, with CID collision check prior to quic_conn allocation.
To ensure this does not cause again the same bug, the CID is removed in
case of quic_conn alloc failure. This should prevent any loop as it
ensures that a CID found in the global tree does not point to a NULL
quic_conn, unless if CID is attach to a foreign thread. When this thread
will parse a re-enqueued packet, either the quic_conn is already
allocated or the CID has been removed, triggering a fresh CID and
quic_conn allocation procedure.
extern struct quic_cid_tree *quic_cid_trees;
-struct quic_connection_id *quic_cid_alloc(struct quic_conn *qc);
+struct quic_connection_id *quic_cid_alloc(void);
-int quic_cid_generate(struct quic_connection_id *conn_id);
+int quic_cid_generate(struct quic_connection_id *conn_id, uint64_t hash);
int quic_cid_derive_from_odcid(struct quic_connection_id *conn_id,
const struct quic_cid *orig,
const struct sockaddr_storage *addr);
-void quic_cid_register_seq_num(struct quic_connection_id *conn_id);
+void quic_cid_register_seq_num(struct quic_connection_id *conn_id,
+ struct quic_conn *qc);
int quic_cid_insert(struct quic_connection_id *conn_id, int *new_tid);
int quic_cmp_cid_conn(const unsigned char *cid, size_t cid_len,
return cid;
}
-/* Allocate a quic_connection_id object and associate it with <qc> connection.
- * The CID object is not yet inserted in any tree storage.
+/* Allocate a quic_connection_id object and associate it to the current thread.
+ * The CID object is not yet associated to a connection or inserted in any tree
+ * storage.
*
* Returns the CID or NULL on allocation failure.
*/
-struct quic_connection_id *quic_cid_alloc(struct quic_conn *qc)
+struct quic_connection_id *quic_cid_alloc(void)
{
struct quic_connection_id *conn_id;
/* TODO use a better trace scope */
- TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc);
+ TRACE_ENTER(QUIC_EV_CONN_TXPKT);
conn_id = pool_alloc(pool_head_quic_connection_id);
if (!conn_id) {
- TRACE_ERROR("cid allocation failed", QUIC_EV_CONN_TXPKT, qc);
+ TRACE_ERROR("cid allocation failed", QUIC_EV_CONN_TXPKT);
goto err;
}
- conn_id->qc = qc;
HA_ATOMIC_STORE(&conn_id->tid, tid);
+ conn_id->qc = NULL;
- TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc);
+ TRACE_LEAVE(QUIC_EV_CONN_TXPKT);
return conn_id;
err:
- TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc);
+ TRACE_LEAVE(QUIC_EV_CONN_TXPKT);
return NULL;
}
/* Generate the value of <conn_id> and its associated stateless token. The CID
* value is calculated from a random generator or via quic_newcid_from_hash64()
- * external callback if defined with hash64 key from connection.
+ * external callback if defined with <hash64> key.
*
* Returns 0 on success else non-zero.
*/
-int quic_cid_generate(struct quic_connection_id *conn_id)
+int quic_cid_generate(struct quic_connection_id *conn_id, uint64_t hash64)
{
- const struct quic_conn *qc = conn_id->qc;
-
/* TODO use a better trace scope */
- TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc);
+ TRACE_ENTER(QUIC_EV_CONN_TXPKT);
conn_id->cid.len = QUIC_HAP_CID_LEN;
if (quic_newcid_from_hash64) {
- TRACE_DEVEL("calculate CID value from conn hash", QUIC_EV_CONN_TXPKT, qc);
- quic_newcid_from_hash64(conn_id->cid.data, conn_id->cid.len, qc->hash64,
+ TRACE_DEVEL("calculate CID value from conn hash", QUIC_EV_CONN_TXPKT);
+ quic_newcid_from_hash64(conn_id->cid.data, conn_id->cid.len, hash64,
global.cluster_secret, sizeof(global.cluster_secret));
}
else {
- TRACE_DEVEL("generate CID value from random generator", QUIC_EV_CONN_TXPKT, qc);
+ TRACE_DEVEL("generate CID value from random generator", QUIC_EV_CONN_TXPKT);
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);
+ TRACE_ERROR("RAND_bytes() failed", QUIC_EV_CONN_TXPKT);
goto err;
}
}
if (quic_stateless_reset_token_init(conn_id) != 1) {
- TRACE_ERROR("quic_stateless_reset_token_init() failed", QUIC_EV_CONN_TXPKT, qc);
+ TRACE_ERROR("quic_stateless_reset_token_init() failed", QUIC_EV_CONN_TXPKT);
goto err;
}
- TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc);
+ TRACE_LEAVE(QUIC_EV_CONN_TXPKT);
return 0;
err:
- TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc);
+ TRACE_LEAVE(QUIC_EV_CONN_TXPKT);
return 1;
}
* sequence number available. The CID should already be stored in the global
* tree to ensure there is no value collision.
*/
-void quic_cid_register_seq_num(struct quic_connection_id *conn_id)
+void quic_cid_register_seq_num(struct quic_connection_id *conn_id,
+ struct quic_conn *qc)
{
- struct quic_conn *qc = conn_id->qc;
-
/* TODO use a better trace scope */
TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc);
+ conn_id->qc = qc;
conn_id->seq_num.key = qc->next_cid_seq_num;
conn_id->retire_prior_to = 0;
/* insert the allocated CID in the quic_conn tree */
*new_tid = conn_id_tid;
goto end;
}
+
+ /* Ensures that conn is always set if CID is found on its thread.
+ * Necessary to prevent loop of re-enqueued Initial packets.
+ */
+ BUG_ON(!conn_id->qc);
qc = conn_id->qc;
TRACE_DEVEL("found connection", QUIC_EV_CONN_RXPKT, qc);
goto err;
}
- conn_id = quic_cid_alloc(qc);
+ conn_id = quic_cid_alloc();
if (!conn_id) {
qc_frm_free(qc, &frm);
TRACE_ERROR("CID allocation error", QUIC_EV_CONN_IO_CB, qc);
goto err;
}
- if (quic_cid_generate(conn_id)) {
+ if (quic_cid_generate(conn_id, qc->hash64)) {
qc_frm_free(qc, &frm);
pool_free(pool_head_quic_connection_id, conn_id);
TRACE_ERROR("error on CID generation", QUIC_EV_CONN_IO_CB, qc);
continue;
}
- quic_cid_register_seq_num(conn_id);
+ quic_cid_register_seq_num(conn_id, qc);
quic_connection_id_to_frm_cpy(frm, conn_id);
LIST_APPEND(&frm_list, &frm->list);
}
memcpy(&qc->odcid, qc->dcid.data, sizeof(qc->dcid.data));
qc->odcid.len = qc->dcid.len;
- conn_cid = quic_cid_alloc(qc);
+ conn_cid = quic_cid_alloc();
if (!conn_cid) {
TRACE_ERROR("error on CID allocation", QUIC_EV_CONN_INIT, qc);
goto err;
}
- if (quic_cid_generate(conn_cid)) {
+ if (quic_cid_generate(conn_cid, qc->hash64)) {
TRACE_ERROR("error on CID generation", QUIC_EV_CONN_INIT, qc);
pool_free(pool_head_quic_connection_id, conn_cid);
goto err;
goto err;
}
- quic_cid_register_seq_num(conn_cid);
+ quic_cid_register_seq_num(conn_cid, qc);
dcid = &qc->dcid;
conn_id = conn_cid;
}
pool_free(pool_head_quic_connection_id, conn_id);
TRACE_PROTO("CID retired", QUIC_EV_CONN_PSTRM, qc);
- conn_id = quic_cid_alloc(qc);
+ conn_id = quic_cid_alloc();
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));
}
while (retry_rand_cid--) {
- if (quic_cid_generate(conn_id)) {
+ if (quic_cid_generate(conn_id, qc->hash64)) {
TRACE_ERROR("error on CID generation", QUIC_EV_CONN_PSTRM, qc);
quic_set_connection_close(qc, quic_err_transport(QC_ERR_INTERNAL_ERROR));
pool_free(pool_head_quic_connection_id, conn_id);
goto err;
}
- quic_cid_register_seq_num(conn_id);
+ quic_cid_register_seq_num(conn_id, qc);
qc_build_new_connection_id_frm(qc, conn_id);
break;
pkt->saddr = dgram->saddr;
ipv4 = dgram->saddr.ss_family == AF_INET;
- conn_id = quic_cid_alloc(NULL);
+ conn_id = quic_cid_alloc();
if (!conn_id) {
TRACE_ERROR("error on first CID allocation",
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
goto err;
}
- qc = qc_new_conn(pkt->version, ipv4, &pkt->dcid, &pkt->scid, &token_odcid,
- conn_id, &dgram->daddr, &pkt->saddr,
- !!pkt->token_len, l);
- if (qc == NULL) {
- pool_free(pool_head_quic_connection_id, conn_id);
- goto err;
- }
-
- conn_id->qc = qc;
- /* Compute and store into the quic_conn the hash used to compute extra CIDs */
- if (quic_hash64_from_cid) {
- qc->hash64 = quic_hash64_from_cid(conn_id->cid.data, conn_id->cid.len,
- global.cluster_secret, sizeof(global.cluster_secret));
- }
-
if (quic_cid_insert(conn_id, new_tid)) {
/* Collision during CID insertion. This may
* happen when handling two Initial packets
*/
TRACE_STATE("duplicate CID on Initial", QUIC_EV_CONN_LPKT);
pool_free(pool_head_quic_connection_id, conn_id);
- quic_conn_release(qc);
- qc = NULL;
}
else {
- quic_cid_register_seq_num(conn_id);
+ qc = qc_new_conn(pkt->version, ipv4, &pkt->dcid, &pkt->scid, &token_odcid,
+ conn_id, &dgram->daddr, &pkt->saddr,
+ !!pkt->token_len, l);
+ if (qc == NULL) {
+ quic_cid_delete(conn_id); /* Removes CID from global tree as it points to a NULL qc. */
+ pool_free(pool_head_quic_connection_id, conn_id);
+ goto err;
+ }
+
+ /* Compute the hash used to derive extra CIDs. */
+ if (quic_hash64_from_cid) {
+ qc->hash64 = quic_hash64_from_cid(conn_id->cid.data, conn_id->cid.len,
+ global.cluster_secret, sizeof(global.cluster_secret));
+ }
+ quic_cid_register_seq_num(conn_id, qc);
if (dgram->flags & QUIC_DGRAM_FL_REJECT)
quic_set_connection_close(qc, quic_err_transport(QC_ERR_CONNECTION_REFUSED));