diff --git a/easytier/src/instance/dns_server/runner.rs b/easytier/src/instance/dns_server/runner.rs index e777ca04..08ff256b 100644 --- a/easytier/src/instance/dns_server/runner.rs +++ b/easytier/src/instance/dns_server/runner.rs @@ -13,6 +13,7 @@ pub struct DnsRunner { server: Option, peer_mgr: Arc, tun_dev: Option, + netns: Option, tun_inet: Ipv4Inet, fake_ip: Ipv4Addr, } @@ -29,11 +30,24 @@ impl DnsRunner { server: None, peer_mgr, tun_dev, + netns: None, tun_inet, fake_ip, } } + pub fn new_with_netns( + peer_mgr: Arc, + tun_dev: Option, + tun_inet: Ipv4Inet, + fake_ip: Ipv4Addr, + netns: Option, + ) -> Self { + let mut runner = Self::new(peer_mgr, tun_dev, tun_inet, fake_ip); + runner.netns = netns; + runner + } + async fn clean_env(&mut self) { if let Some(server) = self.server.take() { server.clean_env().await; @@ -43,11 +57,12 @@ impl DnsRunner { async fn run_once(&mut self) -> anyhow::Result<()> { // try server first - match MagicDnsServerInstance::new( + match MagicDnsServerInstance::new_with_netns( self.peer_mgr.clone(), self.tun_dev.clone(), self.tun_inet, self.fake_ip, + self.netns.clone(), ) .await { diff --git a/easytier/src/instance/dns_server/server_instance.rs b/easytier/src/instance/dns_server/server_instance.rs index 91d21514..602efc32 100644 --- a/easytier/src/instance/dns_server/server_instance.rs +++ b/easytier/src/instance/dns_server/server_instance.rs @@ -15,7 +15,9 @@ use super::{ use crate::{ common::{ PeerId, + error::Error as EtError, ifcfg::{IfConfiger, IfConfiguerTrait}, + netns::NetNS, }, instance::dns_server::{ config::{Record, RecordBuilder, RecordType}, @@ -61,6 +63,7 @@ static NIC_PIPELINE_NAME: &str = "magic_dns_server"; pub(super) struct MagicDnsServerInstanceData { dns_server: Server, tun_dev: Option, + netns: Option, tun_ip: Ipv4Addr, fake_ip: Ipv4Addr, my_peer_id: PeerId, @@ -510,11 +513,54 @@ fn get_system_config( } impl MagicDnsServerInstance { + async fn add_fake_ip_route( + tun_dev_name: &str, + fake_ip: Ipv4Addr, + netns: Option, + cost: Option, + ) -> Result<(), anyhow::Error> { + let ifcfg = IfConfiger {}; + let _guard = NetNS::new(netns).guard(); + match ifcfg.add_ipv4_route(tun_dev_name, fake_ip, 32, cost).await { + Err(EtError::IOError(err)) if err.kind() == io::ErrorKind::AlreadyExists => { + ifcfg.remove_ipv4_route(tun_dev_name, fake_ip, 32).await?; + ifcfg + .add_ipv4_route(tun_dev_name, fake_ip, 32, cost) + .await?; + Ok(()) + } + ret => ret.map_err(Into::into), + } + } + + async fn remove_fake_ip_route(tun_dev_name: &str, fake_ip: Ipv4Addr, netns: Option) { + let ifcfg = IfConfiger {}; + let _guard = NetNS::new(netns).guard(); + if let Err(err) = ifcfg.remove_ipv4_route(tun_dev_name, fake_ip, 32).await { + tracing::warn!( + ?err, + ?tun_dev_name, + ?fake_ip, + "remove magic dns route failed" + ); + } + } + pub async fn new( peer_mgr: Arc, tun_dev: Option, tun_inet: Ipv4Inet, fake_ip: Ipv4Addr, + ) -> Result { + Self::new_with_netns(peer_mgr, tun_dev, tun_inet, fake_ip, None).await + } + + pub async fn new_with_netns( + peer_mgr: Arc, + tun_dev: Option, + tun_inet: Ipv4Inet, + fake_ip: Ipv4Addr, + netns: Option, ) -> Result { let tcp_listener = TcpTunnelListener::new(MAGIC_DNS_INSTANCE_ADDR.parse()?); let mut rpc_server = StandAloneServer::new(tcp_listener); @@ -535,15 +581,13 @@ impl MagicDnsServerInstance { } else { None }; - let ifcfg = IfConfiger {}; - ifcfg - .add_ipv4_route(tun_dev_name, fake_ip, 32, cost) - .await?; + Self::add_fake_ip_route(tun_dev_name, fake_ip, netns.clone(), cost).await?; } let data = Arc::new(MagicDnsServerInstanceData { dns_server, tun_dev: tun_dev.clone(), + netns, tun_ip: tun_inet.address(), fake_ip, my_peer_id: peer_mgr.my_peer_id(), @@ -586,14 +630,13 @@ impl MagicDnsServerInstance { if let Err(e) = ret { tracing::error!("Failed to close system config: {:?}", e); } - if !self.tun_inet.contains(&self.data.fake_ip) - && let Some(tun_dev_name) = &self.data.tun_dev - { - let ifcfg = IfConfiger {}; - let _ = ifcfg - .remove_ipv4_route(tun_dev_name, self.data.fake_ip, 32) - .await; - } + } + + if !self.tun_inet.contains(&self.data.fake_ip) + && let Some(tun_dev_name) = &self.data.tun_dev + { + Self::remove_fake_ip_route(tun_dev_name, self.data.fake_ip, self.data.netns.clone()) + .await; } let _ = self diff --git a/easytier/src/instance/instance.rs b/easytier/src/instance/instance.rs index 8fca813d..dacdd4ba 100644 --- a/easytier/src/instance/instance.rs +++ b/easytier/src/instance/instance.rs @@ -817,11 +817,12 @@ impl Instance { return None; } - let runner = DnsRunner::new( + let runner = DnsRunner::new_with_netns( peer_mgr, tun_dev, tun_ip, MAGIC_DNS_FAKE_IP.parse().unwrap(), + ctx.net_ns.name(), ); Some(runner) } @@ -1695,9 +1696,9 @@ impl Instance { } pub async fn clear_resources(&mut self) { - self.peer_manager.clear_resources().await; #[cfg(feature = "tun")] - let _ = self.nic_ctx.lock().await.take(); + Self::clear_nic_ctx(self.nic_ctx.clone(), self.peer_packet_receiver.clone()).await; + self.peer_manager.clear_resources().await; } } @@ -1707,9 +1708,11 @@ impl Drop for Instance { let pm = Arc::downgrade(&self.peer_manager); #[cfg(feature = "tun")] let nic_ctx = self.nic_ctx.clone(); + #[cfg(feature = "tun")] + let peer_packet_receiver = self.peer_packet_receiver.clone(); tokio::spawn(async move { #[cfg(feature = "tun")] - nic_ctx.lock().await.take(); + Self::clear_nic_ctx(nic_ctx, peer_packet_receiver).await; if let Some(pm) = pm.upgrade() { pm.clear_resources().await; }; diff --git a/easytier/src/tests/three_node.rs b/easytier/src/tests/three_node.rs index c577f08d..fb7a08c7 100644 --- a/easytier/src/tests/three_node.rs +++ b/easytier/src/tests/three_node.rs @@ -13,6 +13,9 @@ use x25519_dalek::StaticSecret; use super::*; +#[cfg(all(feature = "tun", feature = "magic-dns"))] +use crate::instance::dns_server::{DEFAULT_ET_DNS_ZONE, MAGIC_DNS_FAKE_IP}; + // TODO: 需要加一个单测,确保 socks5 + exit node == self || proxy_cidr == 0.0.0.0/0 时,可以实现出口节点的能力。 use crate::{ @@ -608,6 +611,146 @@ pub async fn shared_tun_proxy_cidr_reaches_member_network() { drop_insts(vec![center, shared_1, shared_2, remote]).await; } +#[cfg(all(feature = "tun", feature = "magic-dns"))] +#[tokio::test] +#[serial_test::serial] +pub async fn shared_tun_magic_dns_same_namespace_real_tun() { + prepare_linux_namespaces(); + + let dev_name = shared_tun_test_dev_name(); + let network_name = "shared_tun_magic_dns_network"; + let network_secret = "shared_tun_magic_dns_secret"; + + let center_cfg = shared_tun_test_config( + "shared_dns_center", + network_name, + network_secret, + Some("net_a"), + None, + "10.144.255.1/24", + false, + ); + let mut center = Instance::new(center_cfg); + + let shared_cfg_1 = shared_tun_test_config( + "shared_dns_first", + network_name, + network_secret, + Some("net_b"), + Some(&dev_name), + "10.144.255.2/24", + false, + ); + shared_cfg_1.set_hostname(Some("shared-dns-1".to_string())); + let mut shared_flags = shared_cfg_1.get_flags(); + shared_flags.accept_dns = true; + shared_cfg_1.set_flags(shared_flags.clone()); + let mut shared_1 = Instance::new(shared_cfg_1); + + let shared_cfg_2 = shared_tun_test_config( + "shared_dns_second", + network_name, + network_secret, + Some("net_b"), + Some(&dev_name), + "10.144.255.3/24", + false, + ); + shared_cfg_2.set_hostname(Some("shared-dns-2".to_string())); + shared_cfg_2.set_flags(shared_flags); + let mut shared_2 = Instance::new(shared_cfg_2); + + let remote_cfg = shared_tun_test_config( + "shared_dns_remote", + network_name, + network_secret, + Some("net_c"), + None, + "10.144.255.4/24", + false, + ); + remote_cfg.set_hostname(Some("shared-dns-remote".to_string())); + let mut remote = Instance::new(remote_cfg); + + let mut shared_1_events = shared_1.get_global_ctx().subscribe(); + let mut shared_2_events = shared_2.get_global_ctx().subscribe(); + + center.run().await.unwrap(); + shared_1.run().await.unwrap(); + shared_2.run().await.unwrap(); + remote.run().await.unwrap(); + + let shared_1_ifname = wait_tun_ready(&mut shared_1_events).await; + let shared_2_ifname = wait_tun_ready(&mut shared_2_events).await; + assert_eq!(shared_1_ifname, dev_name); + assert_eq!(shared_2_ifname, dev_name); + + shared_1 + .get_conn_manager() + .add_connector(RingTunnelConnector::new( + format!("ring://{}", center.id()).parse().unwrap(), + )); + shared_2 + .get_conn_manager() + .add_connector(RingTunnelConnector::new( + format!("ring://{}", center.id()).parse().unwrap(), + )); + remote + .get_conn_manager() + .add_connector(RingTunnelConnector::new( + format!("ring://{}", center.id()).parse().unwrap(), + )); + + wait_for_condition( + || async { + center.get_peer_manager().list_routes().await.len() == 3 + && shared_1.get_peer_manager().list_routes().await.len() == 3 + && shared_2.get_peer_manager().list_routes().await.len() == 3 + && remote.get_peer_manager().list_routes().await.len() == 3 + }, + Duration::from_secs(8), + ) + .await; + + wait_for_condition( + || async { ping_test("net_c", "10.144.255.2", None).await }, + Duration::from_secs(8), + ) + .await; + wait_for_condition( + || async { ping_test("net_c", "10.144.255.3", None).await }, + Duration::from_secs(8), + ) + .await; + wait_for_condition( + || async { ping_test("net_b", "10.144.255.4", None).await }, + Duration::from_secs(8), + ) + .await; + + wait_for_condition( + || async { magic_dns_record_matches("net_b", "shared-dns-1", "10.144.255.2").await }, + Duration::from_secs(8), + ) + .await; + wait_for_condition( + || async { magic_dns_record_matches("net_b", "shared-dns-2", "10.144.255.3").await }, + Duration::from_secs(8), + ) + .await; + wait_for_condition( + || async { magic_dns_record_matches("net_b", "shared-dns-remote", "10.144.255.4").await }, + Duration::from_secs(8), + ) + .await; + + drop_insts(vec![center, shared_1, shared_2, remote]).await; + assert!( + !ipv4_route_exists_in_ns("net_b", &format!("{MAGIC_DNS_FAKE_IP} dev {dev_name}")), + "magic dns fake-ip route should be removed with the shared tun member" + ); +} + mod direct_connector_mapped_listener_tests { use std::sync::Arc; @@ -752,6 +895,35 @@ async fn ping6_test(from_netns: &str, target_ip: &str, payload_size: Option bool { + let _g = NetNS::new(Some(ROOT_NETNS_NAME.to_owned())).guard(); + let server = format!("@{}", MAGIC_DNS_FAKE_IP); + let domain = format!("{}.{DEFAULT_ET_DNS_ZONE}", hostname); + let output = tokio::process::Command::new("ip") + .arg("netns") + .arg("exec") + .arg(from_netns) + .arg("dig") + .arg(server) + .arg(domain) + .arg("A") + .arg("+short") + .arg("+tries=1") + .arg("+time=1") + .output() + .await + .unwrap(); + + if !output.status.success() { + return false; + } + + String::from_utf8_lossy(&output.stdout) + .lines() + .any(|line| line.trim() == expected_ip) +} + fn run_cmd(program: &str, args: &[&str]) { let output = std::process::Command::new(program) .args(args) @@ -1102,6 +1274,13 @@ fn route_exists_in_ns(ns: &str, needle: &str) -> bool { .any(|line| line.contains(needle)) } +#[cfg(all(feature = "tun", feature = "magic-dns"))] +fn ipv4_route_exists_in_ns(ns: &str, needle: &str) -> bool { + run_ip_in_ns_output(ns, &["route", "show"]) + .lines() + .any(|line| line.contains(needle)) +} + fn addr_exists_in_ns(ns: &str, dev: &str, needle: &str) -> bool { run_ip_in_ns_output(ns, &["-6", "addr", "show", "dev", dev]).contains(needle) }