REORG: quic: Add a new module for retransmissions
authorFrédéric Lécaille <flecaille@haproxy.com>
Tue, 28 Nov 2023 13:27:33 +0000 (14:27 +0100)
committerFrédéric Lécaille <flecaille@haproxy.com>
Tue, 28 Nov 2023 14:47:18 +0000 (15:47 +0100)
Move several functions in relation with the retransmissions from TX part
(quic_tx.c) to quic_retransmit.c new C file.

Makefile
include/haproxy/quic_retransmit.h [new file with mode: 0644]
src/quic_retransmit.c [new file with mode: 0644]
src/quic_rx.c
src/quic_tx.c

index c446412..a66c9b6 100644 (file)
--- 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 (file)
index 0000000..403a53c
--- /dev/null
@@ -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 <haproxy/list-t.h>
+#include <haproxy/quic_conn-t.h>
+#include <haproxy/quic_tls-t.h>
+
+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 (file)
index 0000000..d06293f
--- /dev/null
@@ -0,0 +1,252 @@
+#include <import/eb64tree.h>
+
+#include <haproxy/quic_conn.h>
+#include <haproxy/quic_frame.h>
+#include <haproxy/quic_retransmit.h>
+#include <haproxy/quic_trace.h>
+#include <haproxy/quic_tx.h>
+#include <haproxy/trace.h>
+
+#define TRACE_SOURCE &trace_quic
+
+/* Duplicate all frames from <pkt_frm_list> list into <out_frm_list> list
+ * for <qc> 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 <frm> 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 {
+                       /* <origin> 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 <pkt> 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 <qel> 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);
+}
index 0f0c987..94593a6 100644 (file)
@@ -20,6 +20,7 @@
 #include <haproxy/proto_quic.h>
 #include <haproxy/quic_ack.h>
 #include <haproxy/quic_cid.h>
+#include <haproxy/quic_retransmit.h>
 #include <haproxy/quic_retry.h>
 #include <haproxy/quic_sock.h>
 #include <haproxy/quic_stream.h>
index 5ff9764..306b4c2 100644 (file)
@@ -18,6 +18,7 @@
 #include <haproxy/trace.h>
 #include <haproxy/quic_cid.h>
 #include <haproxy/quic_conn.h>
+#include <haproxy/quic_retransmit.h>
 #include <haproxy/quic_retry.h>
 #include <haproxy/quic_sock.h>
 #include <haproxy/quic_tls.h>
@@ -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 <pkt_frm_list> list into <out_frm_list> list
- * for <qc> 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 <frm> 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 {
-                       /* <origin> 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 <pkt> 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 <qel> 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 <qc> quic-conn if needed.
  *
  * Returns allocated buffer or NULL on error.