diff --git a/Cargo.lock b/Cargo.lock index 41a1d848..6c5f2f7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1425,6 +1425,7 @@ dependencies = [ "tun-easytier", "url", "uuid", + "wildmatch", "windows-sys 0.52.0", "winreg 0.11.0", "zerocopy", @@ -6801,6 +6802,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "wildmatch" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3928939971918220fed093266b809d1ee4ec6c1a2d72692ff6876898f3b16c19" + [[package]] name = "winapi" version = "0.3.9" diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 59194b06..5cdd95dd 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -166,6 +166,8 @@ smoltcp = { version = "0.11.0", optional = true, default-features = false, featu ] } parking_lot = { version = "0.12.0", optional = true } +wildmatch = "2.3.4" + [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52", features = [ "Win32_Networking_WinSock", diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index 758ad34f..9ff51dfa 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -169,6 +169,8 @@ pub struct Flags { pub no_tun: bool, #[derivative(Default(value = "false"))] pub use_smoltcp: bool, + #[derivative(Default(value = "\"*\".to_string()"))] + pub foreign_network_whitelist: String, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] diff --git a/easytier/src/connector/udp_hole_punch.rs b/easytier/src/connector/udp_hole_punch.rs index 4a353458..fa3c7a7f 100644 --- a/easytier/src/connector/udp_hole_punch.rs +++ b/easytier/src/connector/udp_hole_punch.rs @@ -1007,7 +1007,6 @@ pub mod tests { use tokio::net::UdpSocket; - use crate::connector::udp_hole_punch::UdpHolePunchListener; use crate::rpc::{NatType, StunInfo}; use crate::tunnel::common::tests::wait_for_condition; diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index 46808dc5..e975b13a 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -79,7 +79,10 @@ struct Cli { #[arg( short, long, - help = "automatically determine and set IP address by Easytier, and the IP address starts from 10.0.0.1 by default. Warning, if there is an IP conflict in the network when using DHCP, the IP will be automatically changed." + help = "automatically determine and set IP address by Easytier, and the +IP address starts from 10.0.0.1 by default. Warning, if there is an IP +conflict in the network when using DHCP, the IP will be automatically +changed." )] dhcp: bool, @@ -219,10 +222,19 @@ and the vpn client is in network of 10.14.14.0/24" #[arg( long, help = "assign routes cidr manually, will disable subnet proxy and - wireguard routes propogated from peers. e.g.: 192.168.0.0/16", +wireguard routes propogated from peers. e.g.: 192.168.0.0/16", num_args = 0.. )] manual_routes: Option>, + + #[arg( + long, + help = "only relay traffic of whitelisted networks, input is a wildcard +string, e.g.: '*' (all networks), 'def*' (network prefixed with def), can specify multiple networks +disable relay if arg is empty. default is allowing all networks", + num_args = 0.., + )] + relay_network_whitelist: Option>, } impl Cli { @@ -455,6 +467,9 @@ impl From for TomlConfigLoader { f.enable_exit_node = cli.enable_exit_node; f.no_tun = cli.no_tun || cfg!(not(feature = "tun")); f.use_smoltcp = cli.use_smoltcp; + if let Some(wl) = cli.relay_network_whitelist { + f.foreign_network_whitelist = wl.join(" "); + } cfg.set_flags(f); cfg.set_exit_nodes(cli.exit_nodes.clone()); diff --git a/easytier/src/peers/foreign_network_manager.rs b/easytier/src/peers/foreign_network_manager.rs index beecaa36..099feb25 100644 --- a/easytier/src/peers/foreign_network_manager.rs +++ b/easytier/src/peers/foreign_network_manager.rs @@ -399,6 +399,37 @@ mod tests { assert_eq!(2, rpc_resp.foreign_networks["net1"].peers.len()); } + async fn foreign_network_whitelist_helper(name: String) { + let pm_center = create_mock_peer_manager_with_mock_stun(crate::rpc::NatType::Unknown).await; + tracing::debug!("pm_center: {:?}", pm_center.my_peer_id()); + let mut flag = pm_center.get_global_ctx().get_flags(); + flag.foreign_network_whitelist = vec!["net1".to_string(), "net2*".to_string()].join(" "); + pm_center.get_global_ctx().config.set_flags(flag); + + let pma_net1 = create_mock_peer_manager_for_foreign_network(name.as_str()).await; + + let (a_ring, b_ring) = crate::tunnel::ring::create_ring_tunnel_pair(); + let b_mgr_copy = pm_center.clone(); + let s_ret = tokio::spawn(async move { b_mgr_copy.add_tunnel_as_server(b_ring).await }); + + pma_net1.add_client_tunnel(a_ring).await.unwrap(); + + s_ret.await.unwrap().unwrap(); + } + + #[tokio::test] + async fn foreign_network_whitelist() { + foreign_network_whitelist_helper("net1".to_string()).await; + foreign_network_whitelist_helper("net2".to_string()).await; + foreign_network_whitelist_helper("net2abc".to_string()).await; + } + + #[tokio::test] + #[should_panic] + async fn foreign_network_whitelist_fail() { + foreign_network_whitelist_helper("net3".to_string()).await; + } + #[tokio::test] async fn test_foreign_network_manager() { let pm_center = create_mock_peer_manager_with_mock_stun(crate::rpc::NatType::Unknown).await; diff --git a/easytier/src/peers/peer_manager.rs b/easytier/src/peers/peer_manager.rs index f10fba64..392bcdd4 100644 --- a/easytier/src/peers/peer_manager.rs +++ b/easytier/src/peers/peer_manager.rs @@ -309,6 +309,21 @@ impl PeerManager { self.add_client_tunnel(t).await } + fn check_network_in_whitelist(&self, network_name: &str) -> Result<(), Error> { + if self + .global_ctx + .get_flags() + .foreign_network_whitelist + .split(" ") + .map(wildmatch::WildMatch::new) + .any(|wl| wl.matches(network_name)) + { + Ok(()) + } else { + Err(anyhow::anyhow!("network {} not in whitelist", network_name).into()) + } + } + #[tracing::instrument] pub async fn add_tunnel_as_server(&self, tunnel: Box) -> Result<(), Error> { tracing::info!("add tunnel as server start"); @@ -319,6 +334,7 @@ impl PeerManager { { self.add_new_peer_conn(peer).await?; } else { + self.check_network_in_whitelist(&peer.get_network_identity().network_name)?; self.foreign_network_manager.add_peer_conn(peer).await?; } tracing::info!("add tunnel as server done");