]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: quic: use datagram destination as source for version negotiation
authorYeonggi Kim <yg.kim@navercorp.com>
Thu, 15 Oct 2026 23:45:56 +0000 (08:45 +0900)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 16 Oct 2026 07:31:06 +0000 (09:31 +0200)
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.

Version Negotiation packets are emitted without connection, via
send_version_negotiation() which used a bare sendto() on the listener
socket without source address. In the above conditions, a client which
offers an unsupported QUIC version never receives the list of supported
versions, so it cannot switch to one of them.

Fix this by adding a source address parameter to
send_version_negotiation(), before the destination one, similarly to the
previous patches for Retry and stateless reset. Its caller sets it to
the datagram destination address, and emission now relies on
quic_lstnr_sock_send(), which only uses it if the listener is bound on a
wildcard address. Apart from the source address, the emission is
unchanged.

This should fix github issue #3503.

This should be backported up to 2.8. Note that it relies on the first
two patches of the series:
  - MINOR: quic: use the listener for Retry and Version Negotiation emission
  - MINOR: quic: define a function to emit a datagram on a listener socket
Prior to 3.4, quic_dgram addresses are of sockaddr_storage type, so the
casts are not necessary. Prior to 2.9, send_version_negotiation() is a
static function defined in quic_conn.c.

include/haproxy/quic_tx.h
src/quic_rx.c
src/quic_tx.c

index 97ed7b4718dc29cac8a9653f3cab3c4bc08c067e..4f58c21db50d4eedac8ea32c0092aa1ed6738723 100644 (file)
@@ -53,7 +53,8 @@ int send_retry(struct listener *l, struct sockaddr_storage *src,
 int send_stateless_reset(struct listener *l, struct sockaddr_storage *srcaddr,
                          struct sockaddr_storage *dstaddr,
                          struct quic_rx_packet *rxpkt);
-int send_version_negotiation(struct listener *l, struct sockaddr_storage *dst,
+int send_version_negotiation(struct listener *l, struct sockaddr_storage *src,
+                             struct sockaddr_storage *dst,
                              struct quic_rx_packet *pkt);
 
 /* The TX packets sent in the same datagram are linked to each others in
index 705f32928bed92cddbcea726c8d8a66a5ee6393a..d6acb229ec43b6a085c669ac73518d8ed71c4078 100644 (file)
@@ -2059,7 +2059,8 @@ static int quic_rx_pkt_parse(struct quic_conn *qc, struct quic_rx_packet *pkt,
                 */
                if (l && !pkt->version) {
                         /* unsupported version, send Negotiation packet */
-                       if (send_version_negotiation(l, (struct sockaddr_storage *)&dgram->saddr, pkt)) {
+                       if (send_version_negotiation(l, (struct sockaddr_storage *)&dgram->daddr,
+                                                    (struct sockaddr_storage *)&dgram->saddr, pkt)) {
                                TRACE_ERROR("VN packet not sent", QUIC_EV_CONN_LPKT);
                                goto drop_silent;
                        }
index b966c032a48d4eec612f57956a2305d9beee0ae6..696b337c3d0e668a71b3a0dd6ec084aea6eb3881 100644 (file)
@@ -1137,20 +1137,21 @@ int qc_dgrams_retransmit(struct quic_conn *qc)
 
 /*
  * Send a Version Negotiation packet on response to <pkt> on <l> listener
- * socket to <dst> address.
+ * socket to <dst> address. <src> is the local address targeted by the peer,
+ * used as datagram source if possible (see quic_lstnr_sock_send()).
  * Implementation of RFC9000 6. Version Negotiation
  *
  * TODO implement a rate-limiting sending of Version Negotiation packets
  *
  * Returns 0 on success else non-zero
  */
-int send_version_negotiation(struct listener *l, struct sockaddr_storage *dst,
+int send_version_negotiation(struct listener *l, struct sockaddr_storage *src,
+                             struct sockaddr_storage *dst,
                              struct quic_rx_packet *pkt)
 {
        char buf[256];
        int ret = 0, i = 0, j;
        uint32_t version;
-       const socklen_t addrlen = get_addr_len(dst);
 
        TRACE_ENTER(QUIC_EV_CONN_TXPKT);
        /*
@@ -1187,7 +1188,7 @@ int send_version_negotiation(struct listener *l, struct sockaddr_storage *dst,
                i += sizeof(version);
        }
 
-       if (sendto(l->rx.fd, buf, i, 0, (struct sockaddr *)dst, addrlen) < 0)
+       if (quic_lstnr_sock_send(l, buf, i, src, dst) < 0)
                goto out;
 
        ret = 1;