From: Frédéric Lécaille Date: Fri, 4 Aug 2023 15:59:28 +0000 (+0200) Subject: MEDIUM: quic: Send CONNECTION_CLOSE packets from a dedicated buffer. X-Git-Tag: v2.9-dev3~62 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=dc9b8e1f278ed5584c7f9d7ff8133290515ed3dc;p=thirdparty%2Fhaproxy.git MEDIUM: quic: Send CONNECTION_CLOSE packets from a dedicated buffer. Add a new pool 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. --- diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h index eff1a0d184..81829e9387 100644 --- a/include/haproxy/quic_conn-t.h +++ b/include/haproxy/quic_conn-t.h @@ -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 */ diff --git a/include/haproxy/quic_tx-t.h b/include/haproxy/quic_tx-t.h index 8024abdbcf..4653f04d04 100644 --- a/include/haproxy/quic_tx-t.h +++ b/include/haproxy/quic_tx-t.h @@ -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) diff --git a/include/haproxy/quic_tx.h b/include/haproxy/quic_tx.h index c97e7db87e..b3702cc88b 100644 --- a/include/haproxy/quic_tx.h +++ b/include/haproxy/quic_tx.h @@ -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); diff --git a/src/quic_conn.c b/src/quic_conn.c index 95f4f79b08..8cc5829033 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -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; diff --git a/src/quic_tx.c b/src/quic_tx.c index 0926486057..814934fb39 100644 --- a/src/quic_tx.c +++ b/src/quic_tx.c @@ -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 of length . * 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 of QUIC + * connection, depending on its state, especially the negotiated version and if + * retransmissions are required. If this the case 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 * list of encryption levels. Several packets can be coalesced into a single * datagram. The result is written into . Note that if 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 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 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);