From c4857f35e88617d09008bd53b52b4a38e03efb38 Mon Sep 17 00:00:00 2001 From: Frederic Lecaille Date: Mon, 11 Aug 2025 16:53:29 +0200 Subject: [PATCH] MINOR: quic-be: Parse, store and reuse tokens provided by NEW_TOKEN Add ->tok and ->toklen to store these tokens into the server SSL ctx cache. Parse at packet level (from qc_parse_ptk_frms()) these tokens and store them calling qc_try_store_new_token() newly implemented function. Modify qc_do_build_pkt() to resend these token calling quic_enc_token() implemented by this patch. --- include/haproxy/server-t.h | 2 + src/quic_rx.c | 51 +++++++++++++++++++++--- src/quic_tx.c | 81 ++++++++++++++++++++++++++++++++++---- 3 files changed, 122 insertions(+), 12 deletions(-) diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index e1f419534..0a28053de 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -474,6 +474,8 @@ struct server { #ifdef USE_QUIC struct quic_early_transport_params tps; char *alpn; + unsigned char *tok; + size_t toklen; #endif __decl_thread(HA_RWLOCK_T sess_lock); } * reused_sess; diff --git a/src/quic_rx.c b/src/quic_rx.c index 07a0481e7..98363f26c 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -813,6 +813,42 @@ 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 SSL cache for sessions to reuse. + */ +static inline void qc_try_store_new_token(struct server *s, + const unsigned char *tok, + size_t len) +{ + /* Cached token */ + unsigned char *stok; + + HA_RWLOCK_RDLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.lock); + + stok = s->ssl_ctx.reused_sess[tid].tok; + + HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.reused_sess[tid].sess_lock); + if (len > s->ssl_ctx.reused_sess[tid].toklen) { + stok = realloc(stok, len); + if (stok) { + s->ssl_ctx.reused_sess[tid].tok = stok; + } + else { + free(s->ssl_ctx.reused_sess[tid].tok); + s->ssl_ctx.reused_sess[tid].toklen = 0; + } + } + + if (stok) { + memcpy(stok, tok, len); + s->ssl_ctx.reused_sess[tid].toklen = len; + } + + HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.reused_sess[tid].sess_lock); + HA_RWLOCK_RDUNLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.lock); +} + /* Parse all the frames of QUIC packet for QUIC connection and * as encryption level. * Returns 1 if succeeded, 0 if failed. @@ -920,11 +956,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 b31daf481..da442804c 100644 --- a/src/quic_tx.c +++ b/src/quic_tx.c @@ -1752,6 +1752,79 @@ 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); + + HA_RWLOCK_RDLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.lock); + if (s->ssl_ctx.reused_sess[tid].tok) { + tok = s->ssl_ctx.reused_sess[tid].tok; + toklen = s->ssl_ctx.reused_sess[tid].toklen; + ret = quic_do_enc_token(pos, end, tok, toklen); + } + else { + uint old_tid = HA_ATOMIC_LOAD(&s->ssl_ctx.last_ssl_sess_tid); + if (old_tid) { + HA_RWLOCK_RDLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.reused_sess[old_tid-1].sess_lock); + tok = s->ssl_ctx.reused_sess[old_tid-1].tok; + toklen = s->ssl_ctx.reused_sess[old_tid-1].toklen; + ret = quic_do_enc_token(pos, end, tok, toklen); + HA_RWLOCK_RDUNLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.reused_sess[old_tid-1].sess_lock); + } + else + ret = quic_do_enc_token(pos, end, NULL, 0); + } + HA_RWLOCK_RDUNLOCK(SSL_SERVER_LOCK, &s->ssl_ctx.lock); + } + + 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, @@ -1835,14 +1908,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