From 8c19a2293c5b9664e4baf29cef0bd7f71fd99fab Mon Sep 17 00:00:00 2001 From: KKRainbow <443152178@qq.com> Date: Sun, 29 Mar 2026 23:16:44 +0800 Subject: [PATCH] fix(windows): avoid pnet interface enumeration panic (#2031) --- easytier/src/common/network.rs | 89 +++++++++++++++++++ .../src/tunnel/fake_tcp/netfilter/pnet.rs | 3 +- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/easytier/src/common/network.rs b/easytier/src/common/network.rs index 3343b182..9097ad69 100644 --- a/easytier/src/common/network.rs +++ b/easytier/src/common/network.rs @@ -1,6 +1,12 @@ use std::{net::IpAddr, ops::Deref, sync::Arc}; +#[cfg(target_os = "windows")] +use network_interface::{ + Addr as SystemAddr, NetworkInterface as SystemNetworkInterface, NetworkInterfaceConfig, +}; use pnet::datalink::NetworkInterface; +#[cfg(target_os = "windows")] +use pnet::{ipnetwork::IpNetwork, util::MacAddr}; use tokio::{ sync::{Mutex, RwLock}, task::JoinSet, @@ -264,6 +270,9 @@ impl IPCollector { pub async fn collect_interfaces(net_ns: NetNS, filter: bool) -> Vec { let _g = net_ns.guard(); + #[cfg(target_os = "windows")] + let ifaces = Self::collect_interfaces_windows(); + #[cfg(not(target_os = "windows"))] let ifaces = pnet::datalink::interfaces(); let mut ret = vec![]; for iface in ifaces { @@ -281,6 +290,86 @@ impl IPCollector { ret } + #[cfg(target_os = "windows")] + fn collect_interfaces_windows() -> Vec { + match SystemNetworkInterface::show() { + Ok(ifaces) => ifaces + .into_iter() + .map(Self::convert_windows_interface) + .collect(), + Err(e) => { + tracing::warn!( + ?e, + "failed to enumerate interfaces via network-interface, falling back to pnet" + ); + match std::panic::catch_unwind(pnet::datalink::interfaces) { + Ok(ifaces) => ifaces, + Err(_) => { + tracing::error!( + "failed to enumerate interfaces via both network-interface and pnet" + ); + Vec::new() + } + } + } + } + } + + #[cfg(target_os = "windows")] + fn convert_windows_interface(iface: SystemNetworkInterface) -> NetworkInterface { + let mac = iface.mac_addr.as_deref().and_then(|mac| { + mac.parse::() + .map_err(|e| { + tracing::debug!(iface = %iface.name, mac, ?e, "failed to parse interface mac") + }) + .ok() + }); + + let ips = iface + .addr + .into_iter() + .filter_map(Self::convert_windows_interface_addr) + .collect(); + + NetworkInterface { + name: iface.name, + description: String::new(), + index: iface.index, + mac, + ips, + // pnet does not populate Windows flags either, so keep the existing semantics. + flags: 0, + } + } + + #[cfg(target_os = "windows")] + fn convert_windows_interface_addr(addr: SystemAddr) -> Option { + match addr { + SystemAddr::V4(addr) => { + let netmask = addr + .netmask + .map(IpAddr::V4) + .unwrap_or(IpAddr::V4(std::net::Ipv4Addr::new(255, 255, 255, 255))); + IpNetwork::with_netmask(IpAddr::V4(addr.ip), netmask) + .map_err(|e| { + tracing::debug!(ip = %addr.ip, ?addr.netmask, ?e, "failed to convert ipv4") + }) + .ok() + } + SystemAddr::V6(addr) => { + let netmask = addr + .netmask + .map(IpAddr::V6) + .unwrap_or(IpAddr::V6(std::net::Ipv6Addr::from(u128::MAX))); + IpNetwork::with_netmask(IpAddr::V6(addr.ip), netmask) + .map_err(|e| { + tracing::debug!(ip = %addr.ip, ?addr.netmask, ?e, "failed to convert ipv6") + }) + .ok() + } + } + } + #[tracing::instrument(skip(net_ns))] async fn do_collect_local_ip_addrs(net_ns: NetNS) -> GetIpListResponse { let mut ret = GetIpListResponse::default(); diff --git a/easytier/src/tunnel/fake_tcp/netfilter/pnet.rs b/easytier/src/tunnel/fake_tcp/netfilter/pnet.rs index 2e9d5c49..a7f4fd20 100644 --- a/easytier/src/tunnel/fake_tcp/netfilter/pnet.rs +++ b/easytier/src/tunnel/fake_tcp/netfilter/pnet.rs @@ -221,7 +221,8 @@ fn get_or_create_worker(interface_name: &str) -> io::Result // But creation is rare. // Let's find interface first. - let interfaces = datalink::interfaces(); + let interfaces = std::panic::catch_unwind(datalink::interfaces) + .map_err(|_| io::Error::other("failed to enumerate network interfaces: pnet panicked"))?; let interface = interfaces .into_iter() .find(|iface| iface.name == interface_name)