From 5f1a4f750d852509924f0a34f7389611aec9b966 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 | 45 +++++++++++++++++++--- src/quic_tx.c | 76 ++++++++++++++++++++++++++++++++++---- 3 files changed, 111 insertions(+), 12 deletions(-) diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index ec618bd88..1c39c4149 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -465,6 +465,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 9fe7b465f..40361c9b8 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. @@ -949,11 +985,10 @@ 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; + + qc_try_store_new_token(__objt_server(qc->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 d788382b5..1c0d71dae 100644 --- a/src/quic_tx.c +++ b/src/quic_tx.c @@ -1731,6 +1731,74 @@ 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 (objt_listener(qc->target)) { + 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 { + struct server *s = __objt_server(qc->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); + } + + 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, @@ -1819,14 +1887,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.2