From: Yeonggi Kim <[email protected]>

When a QUIC listener is bound on a wildcard address, the source address
of emitted datagrams must be the local address targeted by the client,
else the kernel selects it from the route to the client. Both may differ
when several addresses are assigned to the host, for example VIPs
configured on a loopback interface behind a load balancer in direct
routing mode. In this case, QUIC clients relying on a connected UDP
socket silently drop the datagram.

Stateless reset packets are emitted without connection, via
send_stateless_reset() which used a bare sendto() on the listener socket
without source address. In the above conditions, the client never
receives the stateless reset. This typically happens after a restart of
haproxy with a configured cluster-secret, for clients which still use
connection IDs of the previous process: instead of being notified
immediately, they only give up after their idle timeout.

Fix this by adding a source address parameter to send_stateless_reset(),
similarly to the previous patch for Retry. Its caller sets it to the
datagram destination address, only when the listener is bound on a
wildcard address. Emission now relies on quic_sock_sendto(). Apart from
the source address, the emission is unchanged, except that the
MSG_DONTWAIT|MSG_NOSIGNAL flags are now set and EINTR is retried, as in
qc_snd_buf().

This should fix github issue #3503.

This should be backported up to 2.8. Note that it relies on patch
"MINOR: quic: define a function to emit a datagram without connection".
Prior to 2.9, send_stateless_reset() is a static function defined in
quic_conn.c.
---
 include/haproxy/quic_tx.h |  1 +
 src/quic_rx.c             |  3 ++-
 src/quic_tx.c             | 10 ++++++----
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/include/haproxy/quic_tx.h b/include/haproxy/quic_tx.h
index e85125470..4cbbe5b7a 100644
--- a/include/haproxy/quic_tx.h
+++ b/include/haproxy/quic_tx.h
@@ -51,6 +51,7 @@ int send_retry(int fd, struct sockaddr_storage *addr,
                struct sockaddr_storage *src,
                struct quic_rx_packet *pkt, const struct quic_version *qv);
 int send_stateless_reset(struct listener *l, struct sockaddr_storage *dstaddr,
+                         struct sockaddr_storage *srcaddr,
                          struct quic_rx_packet *rxpkt);
 int send_version_negotiation(int fd, struct sockaddr_storage *addr,
                              struct quic_rx_packet *pkt);
diff --git a/src/quic_rx.c b/src/quic_rx.c
index 4c665ab6d..08c845d91 100644
--- a/src/quic_rx.c
+++ b/src/quic_rx.c
@@ -1953,7 +1953,8 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct 
quic_rx_packet *pkt,
                 * emits stateless_reset_token in its TPs.
                 */
                TRACE_PROTO("RX non Initial pkt without connection", 
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
-               if (!send_stateless_reset(l, (struct sockaddr_storage 
*)&dgram->saddr, pkt))
+               if (!send_stateless_reset(l, (struct sockaddr_storage 
*)&dgram->saddr,
+                                         quic_dgram_reply_src(dgram, l, 
&laddr), pkt))
                        TRACE_ERROR("stateless reset not sent", 
QUIC_EV_CONN_LPKT, qc);
                goto err;
        }
diff --git a/src/quic_tx.c b/src/quic_tx.c
index 2023b7fdd..cb31df822 100644
--- a/src/quic_tx.c
+++ b/src/quic_tx.c
@@ -1203,16 +1203,18 @@ int send_version_negotiation(int fd, struct 
sockaddr_storage *addr,
 static THREAD_LOCAL struct freq_ctr quic_stateless_reset_freq_ctr;
 static THREAD_LOCAL uint quic_stateless_reset_rate;
 
-/* Send a stateless reset packet depending on <pkt> RX packet information
- * from <fd> UDP socket to <dst>
+/* Send a stateless reset packet depending on <rxpkt> RX packet information
+ * from <l> listener socket to <dstaddr>. <srcaddr> is the local address to use
+ * as datagram source, or NULL to let the kernel select it. It must be NULL 
when
+ * <l> is bound on a specific address (see quic_lstnr_may_set_src()).
  * Return 1 if succeeded, 0 if not.
  */
 int send_stateless_reset(struct listener *l, struct sockaddr_storage *dstaddr,
+                         struct sockaddr_storage *srcaddr,
                          struct quic_rx_packet *rxpkt)
 {
        int ret = 0, pktlen, rndlen;
        unsigned char pkt[64];
-       const socklen_t addrlen = get_addr_len(dstaddr);
        struct proxy *prx;
        struct quic_counters *prx_counters;
 
@@ -1271,7 +1273,7 @@ int send_stateless_reset(struct listener *l, struct 
sockaddr_storage *dstaddr,
                                            rxpkt->dcid.data, rxpkt->dcid.len))
                goto leave;
 
-       if (sendto(l->rx.fd, pkt, pktlen, 0, (struct sockaddr *)dstaddr, 
addrlen) < 0)
+       if (quic_sock_sendto(l->rx.fd, pkt, pktlen, dstaddr, srcaddr) < 0)
                goto leave;
 
     ret = 1;
-- 
2.50.1 (Apple Git-155)



Reply via email to