]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: quic: Send CONNECTION_CLOSE packets from a dedicated buffer.
authorFrédéric Lécaille <flecaille@haproxy.com>
Fri, 4 Aug 2023 15:59:28 +0000 (17:59 +0200)
committerFrédéric Lécaille <flecaille@haproxy.com>
Tue, 8 Aug 2023 08:57:00 +0000 (10:57 +0200)
Add a new pool <pool_head_quic_cc_buf> for buffer used when building datagram
wich CONNECTION_CLOSE frames inside with QUIC_MIN_CC_PKTSIZE(128) as minimum
size.
Add ->cc_buf_area to quic_conn struct to store such buffers.
Add ->cc_dgram_len to store the size of the "connection close" datagrams
and ->cc_buf a buffer struct to be used with ->cc_buf_area as ->area member
value.
Implement qc_get_txb() to be called in place of qc_txb_alloc() to allocate
a struct "quic_cc_buf" buffer when the connection needs an immediate close
or a buffer struct if not.
Modify qc_prep_hptks() and qc_prep_app_pkts() to allow them to use such
"quic_cc_buf" buffer when an immediate close is required.

include/haproxy/quic_conn-t.h
include/haproxy/quic_tx-t.h
include/haproxy/quic_tx.h
src/quic_conn.c
src/quic_tx.c

index eff1a0d184b6ecaf4197fb9715fd413bf6d6aaa3..81829e9387b74e30792456ecd05816472cc2ea3c 100644 (file)
@@ -522,6 +522,11 @@ struct quic_conn {
                struct quic_transport_params params;
                /* Send buffer used to write datagrams. */
                struct buffer buf;
+               /* Send buffer used to send a "connection close" datagram . */
+               struct buffer cc_buf;
+               char *cc_buf_area;
+               /* Length of the "connection close" datagram. */
+               size_t cc_dgram_len;
        } tx;
        struct {
                /* Transport parameters the peer will receive */
index 8024abdbcf5d86aad2cdad9e987d5c474bf0b7fd..4653f04d04cfc41dce6b091bdf0b5c3d21342e6a 100644 (file)
@@ -1,7 +1,12 @@
 #ifndef _HAPROXY_TX_T_H
 #define _HAPROXY_TX_T_H
 
+#define QUIC_MIN_CC_PKTSIZE  128
+#define QUIC_DGRAM_HEADLEN  (sizeof(uint16_t) + sizeof(void *))
+#define QUIC_MAX_CC_BUFSIZE (2 * (QUIC_MIN_CC_PKTSIZE + QUIC_DGRAM_HEADLEN))
+
 extern struct pool_head *pool_head_quic_tx_packet;
+extern struct pool_head *pool_head_quic_cc_buf;
 
 /* Flag a sent packet as being an ack-eliciting packet. */
 #define QUIC_FL_TX_PACKET_ACK_ELICITING (1UL << 0)
index c97e7db87e2deab42f6294dc75c2d45b30e21ce3..b3702cc88b878d5b5d5ac4def4d0ec232fb60e84 100644 (file)
@@ -30,6 +30,7 @@
 struct buffer *qc_txb_alloc(struct quic_conn *qc);
 void qc_txb_release(struct quic_conn *qc);
 int qc_purge_txbuf(struct quic_conn *qc, struct buffer *buf);
+struct buffer *qc_get_txb(struct quic_conn *qc);
 
 int qc_need_sending(struct quic_conn *qc, struct quic_enc_level *qel);
 int qc_prep_hpkts(struct quic_conn *qc, struct buffer *buf, struct list *qels);
index 95f4f79b080446143932e71ec70c433a5e8f81a1..8cc5829033c7d55f9e1954a62ac3ca0408fd1c1c 100644 (file)
@@ -793,7 +793,7 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
                }
        }
 
-       buf = qc_txb_alloc(qc);
+       buf = qc_get_txb(qc);
        if (!buf)
                goto out;
 
@@ -1136,6 +1136,9 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
        qc->bytes.tx = qc->bytes.prep = 0;
        memset(&qc->tx.params, 0, sizeof(qc->tx.params));
        qc->tx.buf = BUF_NULL;
+       qc->tx.cc_buf = BUF_NULL;
+       qc->tx.cc_buf_area = NULL;
+       qc->tx.cc_dgram_len = 0;
        /* RX part. */
        qc->bytes.rx = 0;
        memset(&qc->rx.params, 0, sizeof(qc->rx.params));
@@ -1320,6 +1323,7 @@ void quic_conn_release(struct quic_conn *qc)
        quic_conn_prx_cntrs_update(qc);
        pool_free(pool_head_quic_conn_rxbuf, qc->rx.buf.area);
        qc->rx.buf.area = NULL;
+       pool_free(pool_head_quic_cc_buf, qc->tx.cc_buf_area);
        pool_free(pool_head_quic_conn, qc);
        qc = NULL;
 
index 09264860574211b0ab53a8bd53620ed8aa43f77b..814934fb395c5ab850743c54e2fc2af618b3fe1c 100644 (file)
@@ -24,6 +24,7 @@
 #define TRACE_SOURCE &trace_quic
 
 DECLARE_POOL(pool_head_quic_tx_packet, "quic_tx_packet", sizeof(struct quic_tx_packet));
+DECLARE_POOL(pool_head_quic_cc_buf, "quic_cc_buf", QUIC_MAX_CC_BUFSIZE);
 
 static struct quic_tx_packet *qc_build_pkt(unsigned char **pos, const unsigned char *buf_end,
                                            struct quic_enc_level *qel, struct quic_tls_ctx *ctx,
@@ -368,6 +369,37 @@ void qc_txb_release(struct quic_conn *qc)
        }
 }
 
+/* Return the TX buffer dedicated to the "connection close" datagram to be built
+ * if an immediate close is required after having allocated it or directly
+ * allocate a TX buffer if an immediate close is not required.
+ */
+struct buffer *qc_get_txb(struct quic_conn *qc)
+{
+       struct buffer *buf;
+
+       if (qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE) {
+               TRACE_PROTO("Immediate close required", QUIC_EV_CONN_PHPKTS, qc);
+               buf = &qc->tx.cc_buf;
+               if (b_is_null(buf)) {
+                       qc->tx.cc_buf_area = pool_alloc(pool_head_quic_cc_buf);
+                       if (!qc->tx.cc_buf_area)
+                               goto err;
+               }
+
+               /* In every case, initialize ->tx.cc_buf */
+               qc->tx.cc_buf = b_make(qc->tx.cc_buf_area, QUIC_MAX_CC_BUFSIZE, 0, 0);
+       }
+       else {
+               buf = qc_txb_alloc(qc);
+               if (!buf)
+                       goto err;
+       }
+
+       return buf;
+ err:
+       return NULL;
+}
+
 /* Commit a datagram payload written into <buf> of length <length>. <first_pkt>
  * must contains the address of the first packet stored in the payload.
  *
@@ -438,27 +470,27 @@ static int qc_may_build_pkt(struct quic_conn *qc, struct list *frms,
 static int qc_prep_app_pkts(struct quic_conn *qc, struct buffer *buf,
                             struct list *frms)
 {
-       int ret = -1;
+       int ret = -1, cc;
        struct quic_enc_level *qel;
        unsigned char *end, *pos;
        struct quic_tx_packet *pkt;
        size_t total;
-       /* Each datagram is prepended with its length followed by the address
-        * of the first packet in the datagram.
-        */
-       const size_t dg_headlen = sizeof(uint16_t) + sizeof(pkt);
 
        TRACE_ENTER(QUIC_EV_CONN_PHPKTS, qc);
 
        qel = qc->ael;
        total = 0;
        pos = (unsigned char *)b_tail(buf);
-       while (b_contig_space(buf) >= (int)qc->path->mtu + dg_headlen) {
-               int err, probe, cc, must_ack;
+       cc =  qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE;
+       /* Each datagram is prepended with its length followed by the address
+        * of the first packet in the datagram (QUIC_DGRAM_HEADLEN).
+        */
+       while ((!cc && b_contig_space(buf) >= (int)qc->path->mtu + QUIC_DGRAM_HEADLEN) ||
+               (cc && b_contig_space(buf) >= QUIC_MIN_CC_PKTSIZE + QUIC_DGRAM_HEADLEN)) {
+               int err, probe, must_ack;
 
                TRACE_PROTO("TX prep app pkts", QUIC_EV_CONN_PHPKTS, qc, qel, frms);
                probe = 0;
-               cc =  qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE;
                /* We do not probe if an immediate close was asked */
                if (!cc)
                        probe = qel->pktns->tx.pto_probe;
@@ -467,8 +499,11 @@ static int qc_prep_app_pkts(struct quic_conn *qc, struct buffer *buf,
                        break;
 
                /* Leave room for the datagram header */
-               pos += dg_headlen;
-               if (!quic_peer_validated_addr(qc) && qc_is_listener(qc)) {
+               pos += QUIC_DGRAM_HEADLEN;
+               if (cc) {
+                       end = pos + QUIC_MIN_CC_PKTSIZE;
+               }
+               else if (!quic_peer_validated_addr(qc) && qc_is_listener(qc)) {
                        end = pos + QUIC_MIN((uint64_t)qc->path->mtu, quic_may_send_bytes(qc));
                }
                else {
@@ -502,9 +537,16 @@ static int qc_prep_app_pkts(struct quic_conn *qc, struct buffer *buf,
 
                /* Write datagram header. */
                qc_txb_store(buf, pkt->len, pkt);
+               /* Build only one datagram when an immediate close is required. */
+               if (cc)
+                       break;
        }
 
  out:
+       if (total && cc) {
+               BUG_ON(buf != &qc->tx.cc_buf);
+               qc->tx.cc_dgram_len = total;
+       }
        ret = total;
  leave:
        TRACE_LEAVE(QUIC_EV_CONN_PHPKTS, qc);
@@ -825,9 +867,9 @@ int qc_send_app_pkts(struct quic_conn *qc, struct list *frms)
 
        TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc);
 
-       buf = qc_txb_alloc(qc);
+       buf = qc_get_txb(qc);
        if (!buf) {
-               TRACE_ERROR("buffer allocation failed", QUIC_EV_CONN_TXPKT, qc);
+               TRACE_ERROR("could not get a buffer", QUIC_EV_CONN_TXPKT, qc);
                goto err;
        }
 
@@ -947,6 +989,37 @@ static inline int qc_qel_is_head(struct quic_enc_level *qel, struct list *l,
        return !retrans ? &qel->list == l : &qel->retrans == l;
 }
 
+/* Select <*tls_ctx>, <*frms> and <*ver> for the encryption level <qel> of <qc> QUIC
+ * connection, depending on its state, especially the negotiated version and if
+ * retransmissions are required. If this the case <qels> is the list of encryption
+ * levels to used, or NULL if no retransmissions are required.
+ * Never fails.
+ */
+static inline void qc_select_tls_frms_ver(struct quic_conn *qc,
+                                          struct quic_enc_level *qel,
+                                          struct quic_tls_ctx **tls_ctx,
+                                          struct list **frms,
+                                          const struct quic_version **ver,
+                                          struct list *qels)
+{
+       if (qc->negotiated_version) {
+               *ver = qc->negotiated_version;
+               if (qel == qc->iel)
+                       *tls_ctx = qc->nictx;
+               else
+                       *tls_ctx = &qel->tls_ctx;
+       }
+       else {
+               *ver = qc->original_version;
+               *tls_ctx = &qel->tls_ctx;
+       }
+
+       if (!qels)
+               *frms = &qel->pktns->tx.frms;
+       else
+               *frms = qel->retrans_frms;
+}
+
 /* Prepare as much as possible QUIC datagrams/packets for sending from <qels>
  * list of encryption levels. Several packets can be coalesced into a single
  * datagram. The result is written into <buf>. Note that if <qels> is NULL,
@@ -961,10 +1034,9 @@ static inline int qc_qel_is_head(struct quic_enc_level *qel, struct list *l,
  */
 int qc_prep_hpkts(struct quic_conn *qc, struct buffer *buf, struct list *qels)
 {
-       int ret, retrans, padding;
+       int ret, cc, retrans, padding;
        struct quic_tx_packet *first_pkt, *prv_pkt;
        unsigned char *end, *pos;
-       const size_t dg_headlen = sizeof(uint16_t) + sizeof(first_pkt);
        uint16_t dglen;
        size_t total;
        struct list *qel_list;
@@ -977,6 +1049,7 @@ int qc_prep_hpkts(struct quic_conn *qc, struct buffer *buf, struct list *qels)
        BUG_ON_HOT(buf->head || buf->data);
 
        ret = -1;
+       cc =  qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE;
        retrans = !!qels;
        padding = 0;
        first_pkt = prv_pkt = NULL;
@@ -998,43 +1071,35 @@ int qc_prep_hpkts(struct quic_conn *qc, struct buffer *buf, struct list *qels)
                        continue;
                }
 
-               if (qc->negotiated_version) {
-                       ver = qc->negotiated_version;
-                       if (qel == qc->iel)
-                               tls_ctx = qc->nictx;
-                       else
-                               tls_ctx = &qel->tls_ctx;
-               }
-               else {
-                       ver = qc->original_version;
-                       tls_ctx = &qel->tls_ctx;
-               }
-
-               if (!qels)
-                       frms = &qel->pktns->tx.frms;
-               else
-                       frms = qel->retrans_frms;
+               qc_select_tls_frms_ver(qc, qel, &tls_ctx, &frms, &ver, qels);
 
                next_qel = qc_next_qel(qel, retrans);
                next_frms = qc_qel_is_head(next_qel, qel_list, retrans) ? NULL :
                        !qels ? &next_qel->pktns->tx.frms : next_qel->retrans_frms;
 
-               /* Build as much as datagrams at <qel> encryption level. */
-               while (b_contig_space(buf) >= (int)qc->path->mtu + dg_headlen || prv_pkt) {
-                       int err, probe, cc, must_ack;
+               /* Build as much as datagrams at <qel> encryption level.
+                * Each datagram is prepended with its length followed by the address
+                * of the first packet in the datagram (QUIC_DGRAM_HEADLEN).
+                */
+               while ((!cc && b_contig_space(buf) >= (int)qc->path->mtu + QUIC_DGRAM_HEADLEN) ||
+                       (cc && b_contig_space(buf) >= QUIC_MIN_CC_PKTSIZE + QUIC_DGRAM_HEADLEN) || prv_pkt) {
+                       int err, probe, must_ack;
                        enum quic_pkt_type pkt_type;
                        struct quic_tx_packet *cur_pkt;
 
                        TRACE_PROTO("TX prep pkts", QUIC_EV_CONN_PHPKTS, qc, qel);
                        probe = 0;
-                       cc =  qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE;
                        /* We do not probe if an immediate close was asked */
                        if (!cc)
                                probe = qel->pktns->tx.pto_probe;
 
                        if (!qc_may_build_pkt(qc, frms, qel, cc, probe, &must_ack)) {
-                               if (prv_pkt && qc_qel_is_head(next_qel, qel_list, retrans))
+                               if (prv_pkt && qc_qel_is_head(next_qel, qel_list, retrans)) {
                                        qc_txb_store(buf, dglen, first_pkt);
+                                       /* Build only one datagram when an immediate close is required. */
+                                       if (cc)
+                                               goto out;
+                               }
 
                                TRACE_DEVEL("next encryption level", QUIC_EV_CONN_PHPKTS, qc);
                                break;
@@ -1042,8 +1107,11 @@ int qc_prep_hpkts(struct quic_conn *qc, struct buffer *buf, struct list *qels)
 
                        if (!prv_pkt) {
                                /* Leave room for the datagram header */
-                               pos += dg_headlen;
-                               if (!quic_peer_validated_addr(qc) && qc_is_listener(qc)) {
+                               pos += QUIC_DGRAM_HEADLEN;
+                               if (cc) {
+                                       end = pos + QUIC_MIN_CC_PKTSIZE;
+                               }
+                               else if (!quic_peer_validated_addr(qc) && qc_is_listener(qc)) {
                                        end = pos + QUIC_MIN((uint64_t)qc->path->mtu, quic_may_send_bytes(qc));
                                }
                                else {
@@ -1129,6 +1197,9 @@ int qc_prep_hpkts(struct quic_conn *qc, struct buffer *buf, struct list *qels)
                        }
                        else {
                                qc_txb_store(buf, dglen, first_pkt);
+                               /* Build only one datagram when an immediate close is required. */
+                               if (cc)
+                                       goto out;
                                first_pkt = NULL;
                                dglen = 0;
                                padding = 0;
@@ -1141,6 +1212,12 @@ int qc_prep_hpkts(struct quic_conn *qc, struct buffer *buf, struct list *qels)
        }
 
  out:
+       if (cc && total) {
+               BUG_ON(buf != &qc->tx.cc_buf);
+               BUG_ON(dglen != total);
+               qc->tx.cc_dgram_len = dglen;
+       }
+
        ret = total;
  leave:
        TRACE_LEAVE(QUIC_EV_CONN_PHPKTS, qc);
@@ -1157,7 +1234,7 @@ int qc_send_hdshk_pkts(struct quic_conn *qc, int old_data,
                        struct quic_enc_level *qel1, struct quic_enc_level *qel2)
 {
        int ret, status = 0;
-       struct buffer *buf = qc_txb_alloc(qc);
+       struct buffer *buf = qc_get_txb(qc);
        struct list qels = LIST_HEAD_INIT(qels);
 
        TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc);