diff --git a/easytier/src/instance/instance.rs b/easytier/src/instance/instance.rs index 87c400c..ee1d1a9 100644 --- a/easytier/src/instance/instance.rs +++ b/easytier/src/instance/instance.rs @@ -31,8 +31,9 @@ use crate::peers::{create_packet_recv_chan, recv_packet_from_chan, PacketRecvCha use crate::proto::cli::VpnPortalRpc; use crate::proto::cli::{GetVpnPortalInfoRequest, GetVpnPortalInfoResponse, VpnPortalInfo}; use crate::proto::cli::{ - MappedListenerManageRpc, MappedListener, ListMappedListenerRequest, ListMappedListenerResponse, - ManageMappedListenerRequest, MappedListenerManageAction, ManageMappedListenerResponse + ListMappedListenerRequest, ListMappedListenerResponse, ManageMappedListenerRequest, + ManageMappedListenerResponse, MappedListener, MappedListenerManageAction, + MappedListenerManageRpc, }; use crate::proto::common::TunnelInfo; use crate::proto::peer_rpc::PeerCenterRpcServer; @@ -271,6 +272,8 @@ impl Instance { peer_packet_sender.clone(), )); + peer_manager.set_allow_loopback_tunnel(false); + let listener_manager = Arc::new(Mutex::new(ListenerManager::new( global_ctx.clone(), peer_manager.clone(), @@ -719,7 +722,9 @@ impl Instance { } } - fn get_mapped_listener_manager_rpc_service(&self) -> impl MappedListenerManageRpc + Clone { + fn get_mapped_listener_manager_rpc_service( + &self, + ) -> impl MappedListenerManageRpc + Clone { #[derive(Clone)] pub struct MappedListenerManagerRpcService(Arc); @@ -736,7 +741,9 @@ impl Instance { let urls = self.0.config.get_mapped_listeners(); let mapped_listeners: Vec = urls .into_iter() - .map(|u|MappedListener{url: Some(u.into())}) + .map(|u| MappedListener { + url: Some(u.into()), + }) .collect(); ret.mappedlisteners = mapped_listeners; Ok(ret) @@ -793,8 +800,10 @@ impl Instance { .register(PeerCenterRpcServer::new(peer_center.get_rpc_service()), ""); s.registry() .register(VpnPortalRpcServer::new(vpn_portal_rpc), ""); - s.registry() - .register(MappedListenerManageRpcServer::new(mapped_listener_manager_rpc), ""); + s.registry().register( + MappedListenerManageRpcServer::new(mapped_listener_manager_rpc), + "", + ); if let Some(ip_proxy) = self.ip_proxy.as_ref() { s.registry().register( diff --git a/easytier/src/peers/peer_manager.rs b/easytier/src/peers/peer_manager.rs index 04ef501..0062761 100644 --- a/easytier/src/peers/peer_manager.rs +++ b/easytier/src/peers/peer_manager.rs @@ -1,7 +1,7 @@ use std::{ fmt::Debug, - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - sync::{Arc, Weak}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + sync::{atomic::AtomicBool, Arc, Weak}, time::{Instant, SystemTime}, }; @@ -143,6 +143,8 @@ pub struct PeerManager { exit_nodes: Vec, reserved_my_peer_id_map: DashMap, + + allow_loopback_tunnel: AtomicBool, } impl Debug for PeerManager { @@ -271,9 +273,16 @@ impl PeerManager { exit_nodes, reserved_my_peer_id_map: DashMap::new(), + + allow_loopback_tunnel: AtomicBool::new(true), } } + pub fn set_allow_loopback_tunnel(&self, allow_loopback_tunnel: bool) { + self.allow_loopback_tunnel + .store(allow_loopback_tunnel, std::sync::atomic::Ordering::Relaxed); + } + fn build_foreign_network_manager_accessor( peer_map: &Arc, ) -> Box { @@ -356,6 +365,65 @@ impl PeerManager { self.add_client_tunnel(t, true).await } + // avoid loop back to virtual network + fn check_remote_addr_not_from_virtual_network( + &self, + tunnel: &dyn Tunnel, + ) -> Result<(), anyhow::Error> { + tracing::info!("check remote addr not from virtual network"); + let Some(tunnel_info) = tunnel.info() else { + anyhow::bail!("tunnel info is not set"); + }; + let Some(src) = tunnel_info.remote_addr.map(url::Url::from) else { + anyhow::bail!("tunnel info remote addr is not set"); + }; + 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 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)"); + } + } + } + } + } + Ok(()) + } + #[tracing::instrument(ret)] pub async fn add_tunnel_as_server( &self, @@ -363,6 +431,8 @@ impl PeerManager { is_directly_connected: bool, ) -> Result<(), Error> { tracing::info!("add tunnel as server start"); + self.check_remote_addr_not_from_virtual_network(&tunnel)?; + let mut conn = PeerConn::new(self.my_peer_id, self.global_ctx.clone(), tunnel); conn.do_handshake_as_server_ext(|peer, msg| { if msg.network_name diff --git a/easytier/src/tests/three_node.rs b/easytier/src/tests/three_node.rs index d0e775c..901d67d 100644 --- a/easytier/src/tests/three_node.rs +++ b/easytier/src/tests/three_node.rs @@ -1307,3 +1307,24 @@ pub async fn relay_bps_limit_test(#[values(100, 200, 400, 800)] bps_limit: u64) drop_insts(insts).await; } + +#[tokio::test] +async fn avoid_tunnel_loop_back_to_virtual_network() { + let insts = init_three_node("udp").await; + + let tcp_connector = TcpTunnelConnector::new("tcp://10.144.144.2:11010".parse().unwrap()); + insts[0] + .get_peer_manager() + .try_direct_connect(tcp_connector) + .await + .unwrap_err(); + + let udp_connector = UdpTunnelConnector::new("udp://10.144.144.3:11010".parse().unwrap()); + insts[0] + .get_peer_manager() + .try_direct_connect(udp_connector) + .await + .unwrap_err(); + + drop_insts(insts).await; +}