]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: quic-be: Parse, store and reuse tokens provided by NEW_TOKEN 20250911-quic-be-0rtt
authorFrederic Lecaille <flecaille@haproxy.com>
Mon, 11 Aug 2025 14:53:29 +0000 (16:53 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Thu, 11 Sep 2025 16:26:21 +0000 (18:26 +0200)
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
src/quic_rx.c
src/quic_tx.c

index e1f4195348f5cb47e2cb6ee66753dcf8fc4ac117..0a28053ded55e44fc5310b99e99b9225a2ec82a3 100644 (file)
@@ -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;
index 07a0481e7403dd84be55cdfb5663e6df4d23a911..98363f26cc77a5d136c2fb0c51ab837bdbd8dedc 100644 (file)
@@ -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 <tok> token received from a NEW_TOKEN frame into <s>
+ * 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 <pkt> QUIC packet for QUIC connection <qc> and <qel>
  * 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 <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 b31daf48160445d6f93c956fed3a7cbdfd6899cf..da442804ca23535c964833b7e51db0c929bb833e 100644 (file)
@@ -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 <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);
+
+               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 <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,
@@ -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;