From 286e0f4a0d801637178d1e58def19e508c9109c2 Mon Sep 17 00:00:00 2001 From: MRK <141974684+milk2715093695@users.noreply.github.com> Date: Tue, 15 Sep 2026 14:37:47 +0800 Subject: [PATCH] fix(proxy): do not wrap TCP packets destined to own virtual IP (#2572) The wrapped TCP proxy (KCP/QUIC) claims every SYN emitted by the local host, including packets addressed to the node's own virtual IP. Those packets were marked for the wrapped path and self-delivered, then the wrapped destination lookup failed ("no peer found for wrapped TCP dst"). This broke local access to the node's own virtual IP on systems that route own-address traffic through the tun (macOS), and, with the proxy enabled on both sides, also inbound wrapped flows to a macOS node, because the receiving side dials its own virtual IP locally. Skip the wrapped path when the SYN destination equals the local virtual IPv4. Feed the NIC filter context the instance virtual IPv4 (snapshot.virtual_ipv4) instead of the smoltcp fake inet so the check works with smoltcp enabled too; the net-to-net guard is unaffected because it only runs when smoltcp is off, where both values are identical. Add a regression test for the KCP and QUIC transports. --- .../src/gateway/proxy/wrapped_tcp_proxy.rs | 33 +++++++++++++++++++ .../proxy/wrapped_transport/packet_plane.rs | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/easytier-core/src/gateway/proxy/wrapped_tcp_proxy.rs b/easytier-core/src/gateway/proxy/wrapped_tcp_proxy.rs index e1ce8c5c..aa613c25 100644 --- a/easytier-core/src/gateway/proxy/wrapped_tcp_proxy.rs +++ b/easytier-core/src/gateway/proxy/wrapped_tcp_proxy.rs @@ -159,6 +159,10 @@ where let is_syn = tcp_packet.syn() && !tcp_packet.ack(); if is_syn { + // Own virtual IP traffic must stay local; it can never reach a peer. + if ctx.local_ipv4 == Some(dst_ip) { + return false; + } if !check_dst_allowed(dst_ip).await { tracing::warn!( ?ctx.transport, @@ -447,4 +451,33 @@ mod tests { .await ); } + + #[tokio::test] + async fn own_virtual_ip_syn_is_not_marked() { + let own_ip = "10.144.144.204".parse().unwrap(); + let src = SocketAddrV4::new(own_ip, 50000); + let dst = SocketAddrV4::new(own_ip, 80); + + for transport in [ + WrappedTcpProxyTransport::Kcp, + WrappedTcpProxyTransport::Quic, + ] { + let mut packet = build_tcp_packet(src, dst, true, false); + + assert!( + !try_process_wrapped_tcp_packet_from_nic( + &mut packet, + context(transport), + |_| false, + |_| async { true }, + ) + .await + ); + + assert_eq!( + packet.peer_manager_header().unwrap().packet_type, + PacketType::Data as u8 + ); + } + } } diff --git a/easytier-core/src/gateway/proxy/wrapped_transport/packet_plane.rs b/easytier-core/src/gateway/proxy/wrapped_transport/packet_plane.rs index 5432bd3a..c6664c28 100644 --- a/easytier-core/src/gateway/proxy/wrapped_transport/packet_plane.rs +++ b/easytier-core/src/gateway/proxy/wrapped_transport/packet_plane.rs @@ -257,7 +257,7 @@ where WrappedTransportKind::Quic => WrappedTcpProxyTransport::Quic, }, my_peer_id: self.peer_manager.my_peer_id(), - local_ipv4: snapshot.local_inet.map(|inet| inet.address()), + local_ipv4: snapshot.virtual_ipv4, smoltcp_enabled: snapshot.smoltcp_enabled, }, move |src| connection_engine.is_tcp_proxy_connection(src),