From 885e792aebeae11fbc6ddaae73de06987f9e04ef Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 20 Mar 2026 15:34:21 +0100 Subject: [PATCH] fix(proto): canonicalize off-path probe destinations to IPv4 Off-path NAT traversal probes had IPv4-mapped IPv6 destinations (e.g. [::ffff:10.0.0.4]) because map_to_local_socket_family maps to IPv6 when the connection has any IPv6 path. The transport layer routes V6 addresses to the IPv6 socket, which has a different port than the IPv4 socket. This breaks NAT hole punching because the probe arrives from the wrong source port. Canonicalize the destination in the Transmit so IPv4-mapped IPv6 addresses become plain IPv4, routing to the correct socket. --- noq-proto/src/connection/mod.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/noq-proto/src/connection/mod.rs b/noq-proto/src/connection/mod.rs index ca647c8a4..42d53e7b7 100644 --- a/noq-proto/src/connection/mod.rs +++ b/noq-proto/src/connection/mod.rs @@ -2034,7 +2034,10 @@ impl Connection { .udp_tx .on_sent(1, size); Some(Transmit { - destination: network_path.remote, + destination: SocketAddr::new( + network_path.remote.ip().to_canonical(), + network_path.remote.port(), + ), size, ecn: None, segment_size: None, @@ -2117,8 +2120,14 @@ impl Connection { .udp_tx .on_sent(1, size); + // Canonicalize the destination: IPv4-mapped IPv6 addresses (e.g. + // [::ffff:10.0.0.4]) must be converted to plain IPv4 so the transport + // layer routes them to the IPv4 socket. Without this, the packet goes + // out from the IPv6 socket with a different source port, breaking NAT + // mappings needed for hole punching. + let destination = SocketAddr::new(remote.ip().to_canonical(), remote.port()); Some(Transmit { - destination: remote, + destination, size, ecn: None, segment_size: None,