]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: quic-be: Parse, store and reuse tokens provided by NEW_TOKEN 20250802-quic-be-0rtt
authorFrederic Lecaille <flecaille@haproxy.com>
Mon, 11 Aug 2025 14:53:29 +0000 (16:53 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Wed, 13 Aug 2025 08:25:49 +0000 (10:25 +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 ec618bd88c3aed0864b52a660037ee03aaf31365..1c39c414924afbf295efa809a0689cb927a6755d 100644 (file)
@@ -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;
index 9fe7b465f028e6dccc41451f6b3eccd33e4c1c05..40361c9b880ac801475b3959bc7350a7fd97b663 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.
@@ -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 <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;
+
+                               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:
index d788382b5674607330d7c5ef003f4a9bc6b9a170..1c0d71dae9cc435eb62159501dcaab2a77ff3307 100644 (file)
@@ -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 <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 (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 <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,
@@ -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;