From 80070fe51c47c40b829cbb2e5d7bb7797919b2d7 Mon Sep 17 00:00:00 2001 From: Frederic Lecaille Date: Mon, 11 Aug 2025 16:53:29 +0200 Subject: [PATCH] MEDIUM: quic-be: Parse, store and reuse tokens provided by NEW_TOKEN 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 | 3 ++ src/quic_rx.c | 44 +++++++++++++++++++++--- src/quic_tx.c | 68 ++++++++++++++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 12 deletions(-) diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index 668861908..f888de3c7 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -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 */ diff --git a/src/quic_rx.c b/src/quic_rx.c index 7b993b29f..ec4a0f4e7 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -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 token received from a NEW_TOKEN frame into + * 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 QUIC packet for QUIC connection and * 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 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: diff --git a/src/quic_tx.c b/src/quic_tx.c index f0cd143fe..421dba86c 100644 --- a/src/quic_tx.c +++ b/src/quic_tx.c @@ -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 token data field made of bytes at buffer + * address. 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 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 information (its type) * into a buffer with as position pointer and as QUIC TLS encryption * level for QUIC connection and 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; -- 2.47.3