]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: quic-be: Parse, store and reuse tokens provided by NEW_TOKEN
authorFrederic Lecaille <flecaille@haproxy.com>
Mon, 11 Aug 2025 14:53:29 +0000 (16:53 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Thu, 13 Nov 2025 13:04:31 +0000 (14:04 +0100)
Add a per thread ist struct to srv_per_thread struct to store the QUIC token to
be reused for subsequent sessions.

Parse at packet level (from qc_parse_ptk_frms()) these tokens and store
them calling qc_try_store_new_token() newly implemented function. This is
this new function which does its best (may fail) to update the tokens.

Modify qc_do_build_pkt() to resend these tokens calling quic_enc_token()
implemented by this patch.

include/haproxy/server-t.h
src/quic_rx.c
src/quic_tx.c

index 66886190829abcae27fd1a7f8268ae7af8c41efb..f888de3c7aca060387fe9eb4d284c11358b39df8 100644 (file)
@@ -276,6 +276,9 @@ struct srv_per_thread {
        struct ceb_root *idle_conns;            /* Shareable idle connections */
        struct ceb_root *safe_conns;            /* Safe idle connections */
        struct ceb_root *avail_conns;           /* Connections in use, but with still new streams available */
+#ifdef USE_QUIC
+       struct ist quic_retry_token;
+#endif
 };
 
 /* Each server will have one occurrence of this structure per thread group */
index 7b993b29f6d64ad87c4f06779f7956307df68593..ec4a0f4e7426f73ad76de9c1916e184e506e5abc 100644 (file)
@@ -802,6 +802,35 @@ static inline unsigned int quic_ack_delay_ms(struct qf_ack *ack_frm,
        return (ack_frm->ack_delay << conn->tx.params.ack_delay_exponent) / 1000;
 }
 
+/* Client only.
+ * Do its best to store <tok> token received from a NEW_TOKEN frame into <s>
+ * server cache for tokens to reuse.
+ */
+static inline void qc_try_store_new_token(struct server *s,
+                                          const unsigned char *tok,
+                                          size_t len)
+{
+       struct ist *stok;
+       char *stok_ptr;
+
+       stok = &s->per_thr[tid].quic_retry_token;
+       stok_ptr = istptr(*stok);
+       if (len > istlen(*stok)) {
+               stok_ptr = realloc(stok_ptr, len);
+               if (stok_ptr)
+                       s->per_thr[tid].quic_retry_token.ptr = stok_ptr;
+               else {
+                       memset(istptr(*stok), 0, istlen(*stok));
+                       istfree(stok);
+               }
+       }
+
+       if (stok_ptr) {
+               memcpy(stok_ptr, tok, len);
+               stok->len = len;
+       }
+}
+
 /* Parse all the frames of <pkt> QUIC packet for QUIC connection <qc> and <qel>
  * as encryption level.
  * Returns 1 if succeeded, 0 if failed.
@@ -906,11 +935,16 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
                                goto err;
                        }
                        else {
-                               /* TODO NEW_TOKEN not implemented on client side.
-                                * Note that for now token is not copied into <data> field
-                                * of qf_new_token frame. See quic_parse_new_token_frame()
-                                * for further explanations.
-                                */
+                               struct qf_new_token *new_tok_frm = &frm->new_token;
+
+                               if (!qc->conn) {
+                                       TRACE_ERROR("reject NEW_TOKEN frame (connection closed",
+                                                   QUIC_EV_CONN_PRSHPKT, qc);
+                                       goto err;
+                               }
+
+                               qc_try_store_new_token(__objt_server(qc->conn->target),
+                                                      new_tok_frm->r_data, new_tok_frm->len);
                        }
                        break;
                case QUIC_FT_STREAM_8 ... QUIC_FT_STREAM_F:
index f0cd143fe93648a01e3178b6a79c27362298b844..421dba86c66a1b741ad334f5a2a871277013b915 100644 (file)
@@ -1745,6 +1745,66 @@ static inline uint64_t quic_compute_ack_delay_us(unsigned int time_received,
        return ((now_ms - time_received) * 1000) >> conn->tx.params.ack_delay_exponent;
 }
 
+
+/* Encode the <tok> token data field made of <toklen> bytes at <pos> buffer
+ * address. <end> points to the byte following the end of <*pos> buffer.
+ * Note that the type of the frame which embed this token is not encoded.
+ * Return 1 if succeeded, 0 if not.
+ */
+static inline int quic_do_enc_token(unsigned char **pos, const unsigned char *end,
+                                    const unsigned char *tok, size_t toklen)
+{
+       if (!quic_enc_int(pos, end, toklen) || end - *pos <= toklen)
+               return 0;
+
+       if (toklen) {
+               memcpy(*pos, tok, toklen);
+               *pos += toklen;
+       }
+
+       return 1;
+}
+
+/* Encode a token depending on <qc> connection type (listener or not).
+ * For listeners, ony a null byte is encoded (no token).
+ * For clients, if a RETRY token has been received, it is encoded, if not, if a
+ * new token has been received (from NEW_TOKEN frame) and could be retrieved
+ * from cache, it is encoded, if not a null byte is encoded (no token).
+ */
+static inline int quic_enc_token(struct quic_conn *qc,
+                                 unsigned char **pos, const unsigned char *end)
+{
+       int ret = 0;
+       const unsigned char *tok;
+       size_t toklen;
+
+       if (!qc_is_back(qc)) {
+               ret = quic_do_enc_token(pos, end, NULL, 0);
+       }
+       else if (qc->retry_token) {
+               tok = qc->retry_token;
+               toklen = qc->retry_token_len;
+               ret = quic_do_enc_token(pos, end, tok, toklen);
+       }
+       else if (!qc->conn) {
+               TRACE_ERROR("connection closed", QUIC_EV_CONN_TXPKT, qc);
+               goto out;
+       }
+       else {
+               struct server *s = __objt_server(qc->conn->target);
+               struct ist *stok;
+
+               stok = &s->per_thr[tid].quic_retry_token;
+               if (isttest(*stok))
+                       ret = quic_do_enc_token(pos, end, (unsigned char *)istptr(*stok), istlen(*stok));
+               else
+                       ret = quic_do_enc_token(pos, end, NULL, 0);
+       }
+
+ out:
+       return ret;
+}
+
 /* This function builds a clear packet from <pkt> information (its type)
  * into a buffer with <pos> as position pointer and <qel> as QUIC TLS encryption
  * level for <conn> QUIC connection and <qel> as QUIC TLS encryption level,
@@ -1828,14 +1888,8 @@ static int qc_do_build_pkt(unsigned char *pos, const unsigned char *end,
 
        /* Encode the token length (0) for an Initial packet. */
        if (pkt->type == QUIC_PACKET_TYPE_INITIAL) {
-               if (!quic_enc_int(&pos, end, qc->retry_token_len) ||
-                   end - pos <= qc->retry_token_len)
+               if (!quic_enc_token(qc, &pos, end))
                        goto no_room;
-
-               if (qc->retry_token_len) {
-                       memcpy(pos, qc->retry_token, qc->retry_token_len);
-                       pos += qc->retry_token_len;
-               }
        }
 
        head_len = pos - beg;