From 95e9033fd216b5250987ecb958adc6f1dd3861c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= Date: Tue, 28 Nov 2023 14:27:33 +0100 Subject: [PATCH] REORG: quic: Add a new module for retransmissions Move several functions in relation with the retransmissions from TX part (quic_tx.c) to quic_retransmit.c new C file. --- Makefile | 3 +- include/haproxy/quic_retransmit.h | 20 +++ src/quic_retransmit.c | 252 ++++++++++++++++++++++++++++++ src/quic_rx.c | 1 + src/quic_tx.c | 243 +--------------------------- 5 files changed, 276 insertions(+), 243 deletions(-) create mode 100644 include/haproxy/quic_retransmit.h create mode 100644 src/quic_retransmit.c diff --git a/Makefile b/Makefile index c4464122e..a66c9b69c 100644 --- a/Makefile +++ b/Makefile @@ -620,7 +620,8 @@ OPTIONS_OBJS += src/quic_conn.o src/mux_quic.o src/h3.o src/xprt_quic.o \ src/h3_stats.o src/qmux_http.o src/cfgparse-quic.o \ src/cbuf.o src/quic_cc.o src/quic_cc_nocc.o src/quic_ack.o \ src/quic_trace.o src/quic_cli.o src/quic_ssl.o \ - src/quic_rx.o src/quic_tx.o src/quic_cid.o src/quic_retry.o + src/quic_rx.o src/quic_tx.o src/quic_cid.o src/quic_retry.o\ + src/quic_retransmit.o endif ifneq ($(USE_QUIC_OPENSSL_COMPAT),) diff --git a/include/haproxy/quic_retransmit.h b/include/haproxy/quic_retransmit.h new file mode 100644 index 000000000..403a53c06 --- /dev/null +++ b/include/haproxy/quic_retransmit.h @@ -0,0 +1,20 @@ +#ifndef _HAPROXY_QUIC_RETRANSMIT_H +#define _HAPROXY_QUIC_RETRANSMIT_H + +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include +#include +#include + +void qc_prep_fast_retrans(struct quic_conn *qc, + struct quic_pktns *pktns, + struct list *frms1, struct list *frms2); +void qc_prep_hdshk_fast_retrans(struct quic_conn *qc, + struct list *ifrms, struct list *hfrms); + +#endif /* USE_QUIC */ +#endif /* _HAPROXY_QUIC_RETRANSMIT_H */ diff --git a/src/quic_retransmit.c b/src/quic_retransmit.c new file mode 100644 index 000000000..d06293f5d --- /dev/null +++ b/src/quic_retransmit.c @@ -0,0 +1,252 @@ +#include + +#include +#include +#include +#include +#include +#include + +#define TRACE_SOURCE &trace_quic + +/* Duplicate all frames from list into list + * for QUIC connection. + * This is a best effort function which never fails even if no memory could be + * allocated to duplicate these frames. + */ +static void qc_dup_pkt_frms(struct quic_conn *qc, + struct list *pkt_frm_list, struct list *out_frm_list) +{ + struct quic_frame *frm, *frmbak; + struct list tmp = LIST_HEAD_INIT(tmp); + + TRACE_ENTER(QUIC_EV_CONN_PRSAFRM, qc); + + list_for_each_entry_safe(frm, frmbak, pkt_frm_list, list) { + struct quic_frame *dup_frm, *origin; + + if (frm->flags & QUIC_FL_TX_FRAME_ACKED) { + TRACE_DEVEL("already acknowledged frame", QUIC_EV_CONN_PRSAFRM, qc, frm); + continue; + } + + switch (frm->type) { + case QUIC_FT_STREAM_8 ... QUIC_FT_STREAM_F: + { + struct qf_stream *strm_frm = &frm->stream; + struct eb64_node *node = NULL; + struct qc_stream_desc *stream_desc; + + node = eb64_lookup(&qc->streams_by_id, strm_frm->id); + if (!node) { + TRACE_DEVEL("ignored frame for a released stream", QUIC_EV_CONN_PRSAFRM, qc, frm); + continue; + } + + stream_desc = eb64_entry(node, struct qc_stream_desc, by_id); + /* Do not resend this frame if in the "already acked range" */ + if (strm_frm->offset.key + strm_frm->len <= stream_desc->ack_offset) { + TRACE_DEVEL("ignored frame in already acked range", + QUIC_EV_CONN_PRSAFRM, qc, frm); + continue; + } + else if (strm_frm->offset.key < stream_desc->ack_offset) { + uint64_t diff = stream_desc->ack_offset - strm_frm->offset.key; + + qc_stream_frm_mv_fwd(frm, diff); + TRACE_DEVEL("updated partially acked frame", + QUIC_EV_CONN_PRSAFRM, qc, frm); + } + + strm_frm->dup = 1; + break; + } + + default: + break; + } + + /* If is already a copy of another frame, we must take + * its original frame as source for the copy. + */ + origin = frm->origin ? frm->origin : frm; + dup_frm = qc_frm_dup(origin); + if (!dup_frm) { + TRACE_ERROR("could not duplicate frame", QUIC_EV_CONN_PRSAFRM, qc, frm); + break; + } + + TRACE_DEVEL("built probing frame", QUIC_EV_CONN_PRSAFRM, qc, origin); + if (origin->pkt) { + TRACE_DEVEL("duplicated from packet", QUIC_EV_CONN_PRSAFRM, + qc, dup_frm, &origin->pkt->pn_node.key); + } + else { + /* is a frame which was sent from a packet detected as lost. */ + TRACE_DEVEL("duplicated from lost packet", QUIC_EV_CONN_PRSAFRM, qc); + } + + LIST_APPEND(&tmp, &dup_frm->list); + } + + LIST_SPLICE(out_frm_list, &tmp); + + TRACE_LEAVE(QUIC_EV_CONN_PRSAFRM, qc); +} + +/* Boolean function which return 1 if TX packet is only made of + * already acknowledged frame. + */ +static inline int qc_pkt_with_only_acked_frms(struct quic_tx_packet *pkt) +{ + struct quic_frame *frm; + + list_for_each_entry(frm, &pkt->frms, list) + if (!(frm->flags & QUIC_FL_TX_FRAME_ACKED)) + return 0; + + return 1; +} + +/* Prepare a fast retransmission from encryption level */ +void qc_prep_fast_retrans(struct quic_conn *qc, + struct quic_pktns *pktns, + struct list *frms1, struct list *frms2) +{ + struct eb_root *pkts = &pktns->tx.pkts; + struct list *frms = frms1; + struct eb64_node *node; + struct quic_tx_packet *pkt; + + TRACE_ENTER(QUIC_EV_CONN_SPPKTS, qc); + + BUG_ON(frms1 == frms2); + + pkt = NULL; + node = eb64_first(pkts); + start: + while (node) { + struct quic_tx_packet *p; + + p = eb64_entry(node, struct quic_tx_packet, pn_node); + node = eb64_next(node); + /* Skip the empty and coalesced packets */ + TRACE_PRINTF(TRACE_LEVEL_PROTO, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0, + "--> pn=%llu (%d %d %d)", (ull)p->pn_node.key, + LIST_ISEMPTY(&p->frms), !!(p->flags & QUIC_FL_TX_PACKET_COALESCED), + qc_pkt_with_only_acked_frms(p)); + if (!LIST_ISEMPTY(&p->frms) && !qc_pkt_with_only_acked_frms(p)) { + pkt = p; + break; + } + } + + if (!pkt) + goto leave; + + /* When building a packet from another one, the field which may increase the + * packet size is the packet number. And the maximum increase is 4 bytes. + */ + if (!quic_peer_validated_addr(qc) && qc_is_listener(qc) && + pkt->len + 4 > quic_may_send_bytes(qc)) { + qc->flags |= QUIC_FL_CONN_ANTI_AMPLIFICATION_REACHED; + TRACE_PROTO("anti-amplification limit would be reached", QUIC_EV_CONN_SPPKTS, qc, pkt); + goto leave; + } + + TRACE_PROTO("duplicating packet", QUIC_EV_CONN_SPPKTS, qc, pkt); + qc_dup_pkt_frms(qc, &pkt->frms, frms); + if (frms == frms1 && frms2) { + frms = frms2; + goto start; + } + leave: + TRACE_LEAVE(QUIC_EV_CONN_SPPKTS, qc); +} + +/* Prepare a fast retransmission during a handshake after a client + * has resent Initial packets. According to the RFC a server may retransmit + * Initial packets send them coalescing with others (Handshake here). + * (Listener only function). + */ +void qc_prep_hdshk_fast_retrans(struct quic_conn *qc, + struct list *ifrms, struct list *hfrms) +{ + struct list itmp = LIST_HEAD_INIT(itmp); + struct list htmp = LIST_HEAD_INIT(htmp); + + struct quic_enc_level *iqel = qc->iel; + struct quic_enc_level *hqel = qc->hel; + struct quic_enc_level *qel = iqel; + struct eb_root *pkts; + struct eb64_node *node; + struct quic_tx_packet *pkt; + struct list *tmp = &itmp; + + TRACE_ENTER(QUIC_EV_CONN_SPPKTS, qc); + start: + pkt = NULL; + pkts = &qel->pktns->tx.pkts; + node = eb64_first(pkts); + /* Skip the empty packet (they have already been retransmitted) */ + while (node) { + struct quic_tx_packet *p; + + p = eb64_entry(node, struct quic_tx_packet, pn_node); + TRACE_PRINTF(TRACE_LEVEL_PROTO, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0, + "--> pn=%llu (%d %d)", (ull)p->pn_node.key, + LIST_ISEMPTY(&p->frms), !!(p->flags & QUIC_FL_TX_PACKET_COALESCED)); + if (!LIST_ISEMPTY(&p->frms) && !(p->flags & QUIC_FL_TX_PACKET_COALESCED) && + !qc_pkt_with_only_acked_frms(p)) { + pkt = p; + break; + } + + node = eb64_next(node); + } + + if (!pkt) + goto end; + + /* When building a packet from another one, the field which may increase the + * packet size is the packet number. And the maximum increase is 4 bytes. + */ + if (!quic_peer_validated_addr(qc) && qc_is_listener(qc)) { + size_t dglen = pkt->len + 4; + size_t may_send; + + may_send = quic_may_send_bytes(qc); + dglen += pkt->next ? pkt->next->len + 4 : 0; + if (dglen > may_send) { + qc->flags |= QUIC_FL_CONN_ANTI_AMPLIFICATION_REACHED; + TRACE_PROTO("anti-amplification limit would be reached", QUIC_EV_CONN_SPPKTS, qc, pkt); + if (pkt->next) + TRACE_PROTO("anti-amplification limit would be reached", QUIC_EV_CONN_SPPKTS, qc, pkt->next); + if (qel == iqel && may_send >= QUIC_INITIAL_PACKET_MINLEN) + TRACE_PROTO("will probe Initial packet number space", QUIC_EV_CONN_SPPKTS, qc); + goto end; + } + } + + qel->pktns->tx.pto_probe += 1; + + /* No risk to loop here, #packet per datagram is bounded */ + requeue: + TRACE_PROTO("duplicating packet", QUIC_EV_CONN_PRSAFRM, qc, NULL, &pkt->pn_node.key); + qc_dup_pkt_frms(qc, &pkt->frms, tmp); + if (qel == iqel) { + if (pkt->next && pkt->next->type == QUIC_PACKET_TYPE_HANDSHAKE) { + pkt = pkt->next; + tmp = &htmp; + hqel->pktns->tx.pto_probe += 1; + TRACE_DEVEL("looping for next packet", QUIC_EV_CONN_SPPKTS, qc); + goto requeue; + } + } + + end: + LIST_SPLICE(ifrms, &itmp); + LIST_SPLICE(hfrms, &htmp); + + TRACE_LEAVE(QUIC_EV_CONN_SPPKTS, qc); +} diff --git a/src/quic_rx.c b/src/quic_rx.c index 0f0c9879a..94593a622 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/src/quic_tx.c b/src/quic_tx.c index 5ff9764a1..306b4c268 100644 --- a/src/quic_tx.c +++ b/src/quic_tx.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -80,248 +81,6 @@ static inline void free_quic_tx_packet(struct quic_conn *qc, TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc); } -/* Duplicate all frames from list into list - * for QUIC connection. - * This is a best effort function which never fails even if no memory could be - * allocated to duplicate these frames. - */ -static void qc_dup_pkt_frms(struct quic_conn *qc, - struct list *pkt_frm_list, struct list *out_frm_list) -{ - struct quic_frame *frm, *frmbak; - struct list tmp = LIST_HEAD_INIT(tmp); - - TRACE_ENTER(QUIC_EV_CONN_PRSAFRM, qc); - - list_for_each_entry_safe(frm, frmbak, pkt_frm_list, list) { - struct quic_frame *dup_frm, *origin; - - if (frm->flags & QUIC_FL_TX_FRAME_ACKED) { - TRACE_DEVEL("already acknowledged frame", QUIC_EV_CONN_PRSAFRM, qc, frm); - continue; - } - - switch (frm->type) { - case QUIC_FT_STREAM_8 ... QUIC_FT_STREAM_F: - { - struct qf_stream *strm_frm = &frm->stream; - struct eb64_node *node = NULL; - struct qc_stream_desc *stream_desc; - - node = eb64_lookup(&qc->streams_by_id, strm_frm->id); - if (!node) { - TRACE_DEVEL("ignored frame for a released stream", QUIC_EV_CONN_PRSAFRM, qc, frm); - continue; - } - - stream_desc = eb64_entry(node, struct qc_stream_desc, by_id); - /* Do not resend this frame if in the "already acked range" */ - if (strm_frm->offset.key + strm_frm->len <= stream_desc->ack_offset) { - TRACE_DEVEL("ignored frame in already acked range", - QUIC_EV_CONN_PRSAFRM, qc, frm); - continue; - } - else if (strm_frm->offset.key < stream_desc->ack_offset) { - uint64_t diff = stream_desc->ack_offset - strm_frm->offset.key; - - qc_stream_frm_mv_fwd(frm, diff); - TRACE_DEVEL("updated partially acked frame", - QUIC_EV_CONN_PRSAFRM, qc, frm); - } - - strm_frm->dup = 1; - break; - } - - default: - break; - } - - /* If is already a copy of another frame, we must take - * its original frame as source for the copy. - */ - origin = frm->origin ? frm->origin : frm; - dup_frm = qc_frm_dup(origin); - if (!dup_frm) { - TRACE_ERROR("could not duplicate frame", QUIC_EV_CONN_PRSAFRM, qc, frm); - break; - } - - TRACE_DEVEL("built probing frame", QUIC_EV_CONN_PRSAFRM, qc, origin); - if (origin->pkt) { - TRACE_DEVEL("duplicated from packet", QUIC_EV_CONN_PRSAFRM, - qc, dup_frm, &origin->pkt->pn_node.key); - } - else { - /* is a frame which was sent from a packet detected as lost. */ - TRACE_DEVEL("duplicated from lost packet", QUIC_EV_CONN_PRSAFRM, qc); - } - - LIST_APPEND(&tmp, &dup_frm->list); - } - - LIST_SPLICE(out_frm_list, &tmp); - - TRACE_LEAVE(QUIC_EV_CONN_PRSAFRM, qc); -} - -/* Boolean function which return 1 if TX packet is only made of - * already acknowledged frame. - */ -static inline int qc_pkt_with_only_acked_frms(struct quic_tx_packet *pkt) -{ - struct quic_frame *frm; - - list_for_each_entry(frm, &pkt->frms, list) - if (!(frm->flags & QUIC_FL_TX_FRAME_ACKED)) - return 0; - - return 1; -} - -/* Prepare a fast retransmission from encryption level */ -static void qc_prep_fast_retrans(struct quic_conn *qc, - struct quic_pktns *pktns, - struct list *frms1, struct list *frms2) -{ - struct eb_root *pkts = &pktns->tx.pkts; - struct list *frms = frms1; - struct eb64_node *node; - struct quic_tx_packet *pkt; - - TRACE_ENTER(QUIC_EV_CONN_SPPKTS, qc); - - BUG_ON(frms1 == frms2); - - pkt = NULL; - node = eb64_first(pkts); - start: - while (node) { - struct quic_tx_packet *p; - - p = eb64_entry(node, struct quic_tx_packet, pn_node); - node = eb64_next(node); - /* Skip the empty and coalesced packets */ - TRACE_PRINTF(TRACE_LEVEL_PROTO, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0, - "--> pn=%llu (%d %d %d)", (ull)p->pn_node.key, - LIST_ISEMPTY(&p->frms), !!(p->flags & QUIC_FL_TX_PACKET_COALESCED), - qc_pkt_with_only_acked_frms(p)); - if (!LIST_ISEMPTY(&p->frms) && !qc_pkt_with_only_acked_frms(p)) { - pkt = p; - break; - } - } - - if (!pkt) - goto leave; - - /* When building a packet from another one, the field which may increase the - * packet size is the packet number. And the maximum increase is 4 bytes. - */ - if (!quic_peer_validated_addr(qc) && qc_is_listener(qc) && - pkt->len + 4 > quic_may_send_bytes(qc)) { - qc->flags |= QUIC_FL_CONN_ANTI_AMPLIFICATION_REACHED; - TRACE_PROTO("anti-amplification limit would be reached", QUIC_EV_CONN_SPPKTS, qc, pkt); - goto leave; - } - - TRACE_PROTO("duplicating packet", QUIC_EV_CONN_SPPKTS, qc, pkt); - qc_dup_pkt_frms(qc, &pkt->frms, frms); - if (frms == frms1 && frms2) { - frms = frms2; - goto start; - } - leave: - TRACE_LEAVE(QUIC_EV_CONN_SPPKTS, qc); -} - -/* Prepare a fast retransmission during a handshake after a client - * has resent Initial packets. According to the RFC a server may retransmit - * Initial packets send them coalescing with others (Handshake here). - * (Listener only function). - */ -void qc_prep_hdshk_fast_retrans(struct quic_conn *qc, - struct list *ifrms, struct list *hfrms) -{ - struct list itmp = LIST_HEAD_INIT(itmp); - struct list htmp = LIST_HEAD_INIT(htmp); - - struct quic_enc_level *iqel = qc->iel; - struct quic_enc_level *hqel = qc->hel; - struct quic_enc_level *qel = iqel; - struct eb_root *pkts; - struct eb64_node *node; - struct quic_tx_packet *pkt; - struct list *tmp = &itmp; - - TRACE_ENTER(QUIC_EV_CONN_SPPKTS, qc); - start: - pkt = NULL; - pkts = &qel->pktns->tx.pkts; - node = eb64_first(pkts); - /* Skip the empty packet (they have already been retransmitted) */ - while (node) { - struct quic_tx_packet *p; - - p = eb64_entry(node, struct quic_tx_packet, pn_node); - TRACE_PRINTF(TRACE_LEVEL_PROTO, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0, - "--> pn=%llu (%d %d)", (ull)p->pn_node.key, - LIST_ISEMPTY(&p->frms), !!(p->flags & QUIC_FL_TX_PACKET_COALESCED)); - if (!LIST_ISEMPTY(&p->frms) && !(p->flags & QUIC_FL_TX_PACKET_COALESCED) && - !qc_pkt_with_only_acked_frms(p)) { - pkt = p; - break; - } - - node = eb64_next(node); - } - - if (!pkt) - goto end; - - /* When building a packet from another one, the field which may increase the - * packet size is the packet number. And the maximum increase is 4 bytes. - */ - if (!quic_peer_validated_addr(qc) && qc_is_listener(qc)) { - size_t dglen = pkt->len + 4; - size_t may_send; - - may_send = quic_may_send_bytes(qc); - dglen += pkt->next ? pkt->next->len + 4 : 0; - if (dglen > may_send) { - qc->flags |= QUIC_FL_CONN_ANTI_AMPLIFICATION_REACHED; - TRACE_PROTO("anti-amplification limit would be reached", QUIC_EV_CONN_SPPKTS, qc, pkt); - if (pkt->next) - TRACE_PROTO("anti-amplification limit would be reached", QUIC_EV_CONN_SPPKTS, qc, pkt->next); - if (qel == iqel && may_send >= QUIC_INITIAL_PACKET_MINLEN) - TRACE_PROTO("will probe Initial packet number space", QUIC_EV_CONN_SPPKTS, qc); - goto end; - } - } - - qel->pktns->tx.pto_probe += 1; - - /* No risk to loop here, #packet per datagram is bounded */ - requeue: - TRACE_PROTO("duplicating packet", QUIC_EV_CONN_PRSAFRM, qc, NULL, &pkt->pn_node.key); - qc_dup_pkt_frms(qc, &pkt->frms, tmp); - if (qel == iqel) { - if (pkt->next && pkt->next->type == QUIC_PACKET_TYPE_HANDSHAKE) { - pkt = pkt->next; - tmp = &htmp; - hqel->pktns->tx.pto_probe += 1; - TRACE_DEVEL("looping for next packet", QUIC_EV_CONN_SPPKTS, qc); - goto requeue; - } - } - - end: - LIST_SPLICE(ifrms, &itmp); - LIST_SPLICE(hfrms, &htmp); - - TRACE_LEAVE(QUIC_EV_CONN_SPPKTS, qc); -} - /* Allocate Tx buffer from quic-conn if needed. * * Returns allocated buffer or NULL on error.