use std::{ops::Deref, sync::Arc}; use crate::rpc::peer::GetIpListResponse; use pnet::datalink::NetworkInterface; use tokio::{ sync::{Mutex, RwLock}, task::JoinSet, }; use super::netns::NetNS; pub const CACHED_IP_LIST_TIMEOUT_SEC: u64 = 60; struct InterfaceFilter { iface: NetworkInterface, } #[cfg(target_os = "linux")] impl InterfaceFilter { async fn is_tun_tap_device(&self) -> bool { let path = format!("/sys/class/net/{}/tun_flags", self.iface.name); tokio::fs::metadata(&path).await.is_ok() } async fn has_valid_ip(&self) -> bool { self.iface .ips .iter() .map(|ip| ip.ip()) .any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast()) } async fn filter_iface(&self) -> bool { tracing::trace!( "filter linux iface: {:?}, is_point_to_point: {}, is_loopback: {}, is_up: {}, is_lower_up: {}, is_tun: {}, has_valid_ip: {}", self.iface, self.iface.is_point_to_point(), self.iface.is_loopback(), self.iface.is_up(), self.iface.is_lower_up(), self.is_tun_tap_device().await, self.has_valid_ip().await ); !self.iface.is_point_to_point() && !self.iface.is_loopback() && self.iface.is_up() && self.iface.is_lower_up() && !self.is_tun_tap_device().await && self.has_valid_ip().await } } #[cfg(target_os = "macos")] impl InterfaceFilter { async fn is_interface_physical(interface_name: &str) -> bool { let output = tokio::process::Command::new("networksetup") .args(&["-listallhardwareports"]) .output() .await .expect("Failed to execute command"); let stdout = std::str::from_utf8(&output.stdout).expect("Invalid UTF-8"); let lines: Vec<&str> = stdout.lines().collect(); for i in 0..lines.len() { let line = lines[i]; if line.contains("Device:") && line.contains(interface_name) { let next_line = lines[i + 1]; if next_line.contains("Virtual Interface") { return false; } else { return true; } } } false } async fn filter_iface(&self) -> bool { !self.iface.is_point_to_point() && !self.iface.is_loopback() && self.iface.is_up() && Self::is_interface_physical(&self.iface.name).await } } #[cfg(target_os = "windows")] impl InterfaceFilter { async fn filter_iface(&self) -> bool { tracing::debug!( "iface_name: {:?}, p2p: {:?}, is_up: {:?}, iface: {:?}", self.iface.name, self.iface.is_point_to_point(), self.iface.is_up(), self.iface ); !self.iface.is_point_to_point() && !self.iface.is_loopback() && self .iface .ips .iter() .map(|ip| ip.ip()) .any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast()) && self.iface.mac.map(|mac| !mac.is_zero()).unwrap_or(false) } } pub async fn local_ipv4() -> std::io::Result { let socket = tokio::net::UdpSocket::bind("0.0.0.0:0").await?; socket.connect("8.8.8.8:80").await?; let addr = socket.local_addr()?; match addr.ip() { std::net::IpAddr::V4(ip) => Ok(ip), std::net::IpAddr::V6(_) => Err(std::io::Error::new( std::io::ErrorKind::AddrNotAvailable, "no ipv4 address", )), } } pub async fn local_ipv6() -> std::io::Result { let socket = tokio::net::UdpSocket::bind("[::]:0").await?; socket .connect("[2001:4860:4860:0000:0000:0000:0000:8888]:80") .await?; let addr = socket.local_addr()?; match addr.ip() { std::net::IpAddr::V6(ip) => Ok(ip), std::net::IpAddr::V4(_) => Err(std::io::Error::new( std::io::ErrorKind::AddrNotAvailable, "no ipv4 address", )), } } pub struct IPCollector { cached_ip_list: Arc>, collect_ip_task: Mutex>, net_ns: NetNS, } impl IPCollector { pub fn new(net_ns: NetNS) -> Self { Self { cached_ip_list: Arc::new(RwLock::new(GetIpListResponse::new())), collect_ip_task: Mutex::new(JoinSet::new()), net_ns, } } pub async fn collect_ip_addrs(&self) -> GetIpListResponse { let mut task = self.collect_ip_task.lock().await; if task.is_empty() { let cached_ip_list = self.cached_ip_list.clone(); *cached_ip_list.write().await = Self::do_collect_ip_addrs(false, self.net_ns.clone()).await; let net_ns = self.net_ns.clone(); task.spawn(async move { loop { let ip_addrs = Self::do_collect_ip_addrs(true, net_ns.clone()).await; *cached_ip_list.write().await = ip_addrs; tokio::time::sleep(std::time::Duration::from_secs(CACHED_IP_LIST_TIMEOUT_SEC)) .await; } }); } return self.cached_ip_list.read().await.deref().clone(); } pub async fn collect_interfaces(net_ns: NetNS) -> Vec { let _g = net_ns.guard(); let ifaces = pnet::datalink::interfaces(); let mut ret = vec![]; for iface in ifaces { let f = InterfaceFilter { iface: iface.clone(), }; if !f.filter_iface().await { continue; } ret.push(iface); } ret } #[tracing::instrument(skip(net_ns))] async fn do_collect_ip_addrs(with_public: bool, net_ns: NetNS) -> GetIpListResponse { let mut ret = crate::rpc::peer::GetIpListResponse::new(); if with_public { if let Some(v4_addr) = public_ip::addr_with(public_ip::http::ALL, public_ip::Version::V4).await { ret.public_ipv4 = v4_addr.to_string(); } if let Some(v6_addr) = public_ip::addr_v6().await { ret.public_ipv6 = v6_addr.to_string(); } } let ifaces = Self::collect_interfaces(net_ns.clone()).await; let _g = net_ns.guard(); for iface in ifaces { for ip in iface.ips { let ip: std::net::IpAddr = ip.ip(); if ip.is_loopback() || ip.is_multicast() { continue; } if ip.is_ipv4() { ret.interface_ipv4s.push(ip.to_string()); } else if ip.is_ipv6() { ret.interface_ipv6s.push(ip.to_string()); } } } if let Ok(v4_addr) = local_ipv4().await { tracing::trace!("got local ipv4: {}", v4_addr); if !ret.interface_ipv4s.contains(&v4_addr.to_string()) { ret.interface_ipv4s.push(v4_addr.to_string()); } } if let Ok(v6_addr) = local_ipv6().await { tracing::trace!("got local ipv6: {}", v6_addr); if !ret.interface_ipv6s.contains(&v6_addr.to_string()) { ret.interface_ipv6s.push(v6_addr.to_string()); } } ret } }