fix(windows): avoid pnet interface enumeration panic (#2031)

This commit is contained in:
KKRainbow
2026-03-29 23:16:44 +08:00
committed by GitHub
parent a1bec48dc9
commit 8c19a2293c
2 changed files with 91 additions and 1 deletions
+89
View File
@@ -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<NetworkInterface> {
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<NetworkInterface> {
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::<MacAddr>()
.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<IpNetwork> {
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();
@@ -221,7 +221,8 @@ fn get_or_create_worker(interface_name: &str) -> io::Result<Arc<InterfaceWorker>
// 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)