]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: quic: derive first DCID from client ODCID
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 3 Apr 2023 16:49:39 +0000 (18:49 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 5 Apr 2023 09:06:04 +0000 (11:06 +0200)
Change the generation of the first CID of a connection. It is directly
derived from the client ODCID using a 64-bits hash function. Client
address is added to avoid collision between clients which could use the
same ODCID.

For the moment, this change as no functional impact. However, it will be
directly used for the next commit to be able to remove the ODCID tree.

This should be backported up to 2.7.

src/quic_conn.c

index fb791dd6d2a29e1683d67ab42ab63b73f24ba708..6fa187ace7cc8dd600894dd04c51f412f5cff7c6 100644 (file)
@@ -32,6 +32,7 @@
 #include <haproxy/debug.h>
 #include <haproxy/tools.h>
 #include <haproxy/ticks.h>
+#include <haproxy/xxhash.h>
 
 #include <haproxy/applet-t.h>
 #include <haproxy/cli.h>
@@ -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 <orig> CID and <addr> 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 <root> 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 <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.
  */
 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;