From: Amaury Denoyelle Date: Wed, 10 Jul 2024 08:54:43 +0000 (+0200) Subject: MEDIUM: quic: implement GSO fallback mechanism X-Git-Tag: v3.1-dev4~100 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d0ea173e35342cabc806ba4c2597acc195e86eb2;p=thirdparty%2Fhaproxy.git MEDIUM: quic: implement GSO fallback mechanism 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. --- diff --git a/include/haproxy/listener-t.h b/include/haproxy/listener-t.h index b9a8447cba..becc83b01d 100644 --- a/include/haproxy/listener-t.h +++ b/include/haproxy/listener-t.h @@ -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 diff --git a/src/quic_sock.c b/src/quic_sock.c index b0153822dd..541bd6cbb4 100644 --- a/src/quic_sock.c +++ b/src/quic_sock.c @@ -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; } } diff --git a/src/quic_tx.c b/src/quic_tx.c index ad912eb2a4..06c973a455 100644 --- a/src/quic_tx.c +++ b/src/quic_tx.c @@ -14,6 +14,8 @@ #include +#include + #include #include #include @@ -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) {