]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: quic: implement GSO fallback mechanism
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 10 Jul 2024 08:54:43 +0000 (10:54 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 11 Jul 2024 09:02:44 +0000 (11:02 +0200)
UDP GSO on Linux is not implemented in every network devices. For
example, this is not available for veth devices frequently used in
container environment. In such case, EIO is reported on send()
invocation.

It is impossible to test at startup for proper GSO support in this case
as a listener may be bound on multiple network interfaces. Furthermore,
network interfaces may change during haproxy lifetime.

As such, the only option is to react on send syscall error when GSO is
used. The purpose of this patch is to implement a fallback when
encountering such conditions. Emission can be retried immediately by
trying to send each prepared datagrams individually.

To support this, qc_send_ppkts() is able to iterate over each datagram
in a so-called non-GSO fallback mode. Between each emission, a datagram
header is rewritten in front of the buffer which allows the sending loop
to proceed until last datagram is emitted.

To complement this, quic_conn listener is flagged on first GSO send
error with value LI_F_UDP_GSO_NOTSUPP. This completely disables GSO for
all future emission with QUIC connections using this listener.

For the moment, non-GSO fallback mode is activated when EIO is reported
after GSO has been set. This is the error reported for the veth usage
described above.

include/haproxy/listener-t.h
src/quic_sock.c
src/quic_tx.c

index b9a8447cbaecd93d1b44778d854f9259a28f8892..becc83b01d5df6a9891b78a62005c4f272d94a0e 100644 (file)
@@ -264,6 +264,7 @@ struct listener {
 /* listener flags (16 bits) */
 #define LI_F_FINALIZED           0x0001  /* listener made it to the READY||LIMITED||FULL state at least once, may be suspended/resumed safely */
 #define LI_F_SUSPENDED           0x0002  /* listener has been suspended using suspend_listener(), it is either is LI_PAUSED or LI_ASSIGNED state */
+#define LI_F_UDP_GSO_NOTSUPP     0x0004  /* UDP GSO disabled after send error */
 
 /* Descriptor for a "bind" keyword. The ->parse() function returns 0 in case of
  * success, or a combination of ERR_* flags if an error is encountered. The
index b0153822ddb4e7752b3b7081caa5dcca2733b6f2..541bd6cbb4b11c99845e5bacc255a2fc6546a09f 100644 (file)
@@ -789,7 +789,7 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
                        qc->cntrs.sendto_err_unknown++;
                        TRACE_PRINTF(TRACE_LEVEL_USER, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0,
                                     "UDP send failure errno=%d (%s)", errno, strerror(errno));
-                       return -1;
+                       return -errno;
                }
        }
 
index ad912eb2a424fd17ce32ef7090dcaeda8b62829d..06c973a455fb574eb6cc312af7b222ef842c653e 100644 (file)
@@ -14,6 +14,8 @@
 
 #include <haproxy/quic_tx.h>
 
+#include <errno.h>
+
 #include <haproxy/pool.h>
 #include <haproxy/trace.h>
 #include <haproxy/quic_cid.h>
@@ -288,7 +290,7 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
                unsigned char *pos;
                struct buffer tmpbuf = { };
                struct quic_tx_packet *first_pkt, *pkt, *next_pkt;
-               uint16_t dglen, gso = 0;
+               uint16_t dglen, gso = 0, gso_fallback = 0;
                unsigned int time_sent;
 
                pos = (unsigned char *)b_head(buf);
@@ -297,8 +299,16 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
 
                /* If datagram bigger than MTU, several ones were encoded for GSO usage. */
                if (dglen > qc->path->mtu) {
-                       TRACE_PROTO("send multiple datagrams with GSO", QUIC_EV_CONN_SPPKTS, qc);
-                       gso = qc->path->mtu;
+                       if (likely(!(HA_ATOMIC_LOAD(&qc->li->flags) & LI_F_UDP_GSO_NOTSUPP))) {
+                               TRACE_PROTO("send multiple datagrams with GSO", QUIC_EV_CONN_SPPKTS, qc);
+                               gso = qc->path->mtu;
+                       }
+                       else {
+                               TRACE_PROTO("use non-GSO fallback emission mode", QUIC_EV_CONN_SPPKTS, qc);
+                               gso_fallback = dglen;
+                               /* Only send a single datagram now that GSO is disabled. */
+                               dglen = qc->path->mtu;
+                       }
                }
 
                first_pkt = read_ptr(pos + sizeof(dglen));
@@ -310,6 +320,15 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
                if (!skip_sendto) {
                        int ret = qc_snd_buf(qc, &tmpbuf, tmpbuf.data, 0, gso);
                        if (ret < 0) {
+                               if (gso && ret == -EIO) {
+                                       /* Disable permanently UDP GSO for this listener.
+                                        * Retry standard emission.
+                                        */
+                                       TRACE_ERROR("mark listener UDP GSO as unsupported", QUIC_EV_CONN_SPPKTS, qc, first_pkt);
+                                       HA_ATOMIC_OR(&qc->li->flags, LI_F_UDP_GSO_NOTSUPP);
+                                       continue;
+                               }
+
                                TRACE_ERROR("sendto fatal error", QUIC_EV_CONN_SPPKTS, qc, first_pkt);
                                qc_kill_conn(qc);
                                qc_free_tx_coalesced_pkts(qc, first_pkt);
@@ -336,6 +355,31 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
 
                for (pkt = first_pkt; pkt; pkt = next_pkt) {
                        struct quic_cc *cc = &qc->path->cc;
+
+                       /* Packets built with GSO from consecutive datagrams
+                        * are attached together but without COALESCED flag.
+                        * Unlink them to treat them separately on ACK Rx.
+                        */
+                       if (!(pkt->flags & QUIC_FL_TX_PACKET_COALESCED)) {
+                               if (pkt->prev) {
+                                       pkt->prev->next = NULL;
+                                       pkt->prev = NULL;
+                               }
+
+                               /* Packet from first dgram only were sent on non-GSO fallback. */
+                               if (gso_fallback) {
+                                       BUG_ON_HOT(gso_fallback < dglen);
+                                       gso_fallback -= dglen;
+
+                                       /* Built a new datagram header. */
+                                       buf->head -= QUIC_DGRAM_HEADLEN;
+                                       b_add(buf, QUIC_DGRAM_HEADLEN);
+                                       write_u16(b_head(buf), gso_fallback);
+                                       write_ptr(b_head(buf) + sizeof(gso_fallback), pkt);
+                                       break;
+                               }
+                       }
+
                        qc->cntrs.sent_pkt++;
 
                        pkt->time_sent = time_sent;
@@ -375,17 +419,6 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
                        next_pkt = pkt->next;
                        quic_tx_packet_refinc(pkt);
                        eb64_insert(&pkt->pktns->tx.pkts, &pkt->pn_node);
-
-                       /* Packets built with GSO from consecutive datagrams
-                        * are attached together but without COALESCED flag.
-                        * Unlink them to treat them separately on ACK Rx.
-                        */
-                       if (!(pkt->flags & QUIC_FL_TX_PACKET_COALESCED)) {
-                               if (pkt->prev) {
-                                       pkt->prev->next = NULL;
-                                       pkt->prev = NULL;
-                               }
-                       }
                }
        }
 
@@ -665,6 +698,7 @@ static int qc_prep_pkts(struct quic_conn *qc, struct buffer *buf,
                                prv_pkt = cur_pkt;
                        }
                        else if (!(global.tune.options & GTUNE_QUIC_NO_UDP_GSO) &&
+                                !(HA_ATOMIC_LOAD(&qc->li->flags) & LI_F_UDP_GSO_NOTSUPP) &&
                                 dglen == qc->path->mtu &&
                                 (char *)end < b_wrap(buf) &&
                                 gso_dgram_cnt < 64) {