MEDIUM: quic: Send CONNECTION_CLOSE packets from a dedicated buffer.

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.
This commit is contained in:
Frédéric Lécaille 2023-08-04 17:59:28 +02:00
parent f7ab5918d1
commit dc9b8e1f27
5 changed files with 131 additions and 39 deletions

View 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 */

View 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)

View 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);

View 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;

View 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);