diff --git a/easytier/src/common/global_ctx.rs b/easytier/src/common/global_ctx.rs index a35a9746..91fea7dc 100644 --- a/easytier/src/common/global_ctx.rs +++ b/easytier/src/common/global_ctx.rs @@ -303,6 +303,18 @@ impl GlobalCtx { } } + pub fn is_port_in_running_listeners(&self, port: u16, is_udp: bool) -> bool { + let check_proto = |listener_proto: &str| { + let listener_is_udp = matches!(listener_proto, "udp" | "wg"); + listener_is_udp == is_udp + }; + self.running_listeners + .lock() + .unwrap() + .iter() + .any(|x| x.port() == Some(port) && check_proto(x.scheme())) + } + pub fn get_vpn_portal_cidr(&self) -> Option { self.config.get_vpn_portal_config().map(|x| x.client_cidr) } diff --git a/easytier/src/gateway/kcp_proxy.rs b/easytier/src/gateway/kcp_proxy.rs index 52598313..2fb51056 100644 --- a/easytier/src/gateway/kcp_proxy.rs +++ b/easytier/src/gateway/kcp_proxy.rs @@ -507,6 +507,15 @@ impl KcpProxyDst { Some(dst_socket.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address())); if send_to_self && global_ctx.no_tun() { + if global_ctx.is_port_in_running_listeners(dst_socket.port(), false) + && global_ctx.is_ip_in_same_network(&src_ip) + { + return Err(anyhow::anyhow!( + "dst socket {:?} is in running listeners, ignore it", + dst_socket + ) + .into()); + } dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap(); } diff --git a/easytier/src/gateway/quic_proxy.rs b/easytier/src/gateway/quic_proxy.rs index d4edc402..7a383684 100644 --- a/easytier/src/gateway/quic_proxy.rs +++ b/easytier/src/gateway/quic_proxy.rs @@ -416,6 +416,15 @@ impl QUICProxyDst { let send_to_self = Some(*dst_socket.ip()) == ctx.get_ipv4().map(|ip| ip.address()); if send_to_self && ctx.no_tun() { + if ctx.is_port_in_running_listeners(dst_socket.port(), false) + && ctx.is_ip_in_same_network(&src_ip) + { + return Err(anyhow::anyhow!( + "dst socket {:?} is in running listeners, ignore it", + dst_socket + ) + .into()); + } dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap(); } diff --git a/easytier/src/gateway/tcp_proxy.rs b/easytier/src/gateway/tcp_proxy.rs index 74b2dd29..bcd4da3c 100644 --- a/easytier/src/gateway/tcp_proxy.rs +++ b/easytier/src/gateway/tcp_proxy.rs @@ -733,6 +733,18 @@ impl TcpProxy { let nat_dst = if Some(nat_entry.real_dst.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address())) { + if global_ctx.is_port_in_running_listeners(nat_entry.real_dst.port(), false) + && global_ctx.is_ip_in_same_network(&nat_entry.src.ip()) + { + tracing::error!( + ?nat_entry, + "nat dst port {} is in running listeners, ignore it", + nat_entry.real_dst.port() + ); + nat_entry.state.store(NatDstEntryState::Closed); + Self::remove_entry_from_all_conn_map(conn_map, addr_conn_map, nat_entry); + return; + } format!("127.0.0.1:{}", nat_entry.real_dst.port()) .parse() .unwrap() diff --git a/easytier/src/gateway/udp_proxy.rs b/easytier/src/gateway/udp_proxy.rs index e2d8046b..6bdce796 100644 --- a/easytier/src/gateway/udp_proxy.rs +++ b/easytier/src/gateway/udp_proxy.rs @@ -298,6 +298,30 @@ impl UdpProxy { udp::UdpPacket::new(ipv4.payload())? }; + // TODO: should it be async. + let dst_socket = if Some(ipv4.get_destination()) + == self.global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address) + { + if self + .global_ctx + .is_port_in_running_listeners(udp_packet.get_destination(), true) + && self + .global_ctx + .is_ip_in_same_network(&std::net::IpAddr::V4(ipv4.get_source())) + { + tracing::debug!( + dst_port = udp_packet.get_destination(), + "dst socket is in running listeners, ignore it" + ); + return Some(()); + } + format!("127.0.0.1:{}", udp_packet.get_destination()) + .parse() + .unwrap() + } else { + SocketAddr::new(real_dst_ip.into(), udp_packet.get_destination()) + }; + tracing::trace!( ?packet, ?ipv4, @@ -339,17 +363,6 @@ impl UdpProxy { nat_entry.mark_active(); - // TODO: should it be async. - let dst_socket = if Some(ipv4.get_destination()) - == self.global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address) - { - format!("127.0.0.1:{}", udp_packet.get_destination()) - .parse() - .unwrap() - } else { - SocketAddr::new(real_dst_ip.into(), udp_packet.get_destination()) - }; - let send_ret = { let _g = self.global_ctx.net_ns.guard(); nat_entry diff --git a/easytier/src/peers/peer_manager.rs b/easytier/src/peers/peer_manager.rs index 871b23eb..03f4056d 100644 --- a/easytier/src/peers/peer_manager.rs +++ b/easytier/src/peers/peer_manager.rs @@ -1,6 +1,6 @@ use std::{ fmt::Debug, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, sync::{atomic::AtomicBool, Arc, Weak}, time::{Instant, SystemTime}, }; @@ -416,47 +416,25 @@ impl PeerManager { if src.scheme() == "ring" { return Ok(()); } - let src_host = match src.socket_addrs(|| Some(1)) { - Ok(addrs) => addrs, - Err(_) => { - // if the tunnel is not rely on ip address, skip check - return Ok(()); - } + let Ok(Some(addr)) = src.socket_addrs(|| Some(1)).map(|x| x.first().cloned()) else { + // if the tunnel is not rely on ip address, skip check + return Ok(()); }; - let virtual_ipv4 = self.global_ctx.get_ipv4().map(|ip| ip.network()); - let virtual_ipv6 = self.global_ctx.get_ipv6().map(|ip| ip.network()); - tracing::info!( - ?virtual_ipv4, - ?virtual_ipv6, - "check remote addr not from virtual network" - ); - for addr in src_host { - // if no-tun is enabled, the src ip of packet in virtual network is converted to loopback address - if addr.ip().is_loopback() - && !self - .allow_loopback_tunnel - .load(std::sync::atomic::Ordering::Relaxed) - { - anyhow::bail!("tunnel src host is loopback address"); - } - match addr { - SocketAddr::V4(addr) => { - if let Some(virtual_ipv4) = virtual_ipv4 { - if virtual_ipv4.contains(addr.ip()) { - anyhow::bail!("tunnel src host is from the virtual network (ignore this error please)"); - } - } - } - SocketAddr::V6(addr) => { - if let Some(virtual_ipv6) = virtual_ipv6 { - if virtual_ipv6.contains(addr.ip()) { - anyhow::bail!("tunnel src host is from the virtual network (ignore this error please)"); - } - } - } - } + // if no-tun is enabled, the src ip of packet in virtual network is converted to loopback address + // we already filter out the connection in tcp/quic/kcp proxy so no need check here. + if addr.ip().is_loopback() { + // allow other loopback address, good for conn from cdn/l4 connection + return Ok(()); } + + if self.global_ctx.is_ip_in_same_network(&addr.ip()) { + anyhow::bail!( + "tunnel src {} is from the same network (ignore this error please)", + addr + ); + } + Ok(()) } diff --git a/easytier/src/tests/three_node.rs b/easytier/src/tests/three_node.rs index bb419dcd..27d0c04b 100644 --- a/easytier/src/tests/three_node.rs +++ b/easytier/src/tests/three_node.rs @@ -1481,9 +1481,23 @@ pub async fn relay_bps_limit_test(#[values(100, 200, 400, 800)] bps_limit: u64) drop_insts(insts).await; } +#[rstest::rstest] +#[serial_test::serial] #[tokio::test] -async fn avoid_tunnel_loop_back_to_virtual_network() { - let insts = init_three_node("udp").await; +async fn avoid_tunnel_loop_back_to_virtual_network(#[values(true, false)] no_tun: bool) { + let insts = init_three_node_ex( + "udp", + |cfg| { + if matches!(cfg.get_inst_name().as_str(), "inst2" | "inst3") { + let mut flags = cfg.get_flags(); + flags.no_tun = no_tun; + cfg.set_flags(flags); + } + cfg + }, + false, + ) + .await; let tcp_connector = TcpTunnelConnector::new("tcp://10.144.144.2:11010".parse().unwrap()); insts[0]