use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, UdpSocket};
use std::process::Command;
use std::str::FromStr;
use std::thread;
use std::time::Duration;
use pnet::datalink;
use crate::model::EthTable;
const DEFAULT_DNS_PROBE_TARGETS: &[&str] = &[
"223.5.5.5",
"223.6.6.6",
"119.29.29.29",
"114.114.114.114",
"8.8.8.8",
];
#[derive(Debug, Clone)]
struct RouteResolution {
interface: Option<String>,
gateway: Option<Ipv4Addr>,
}
#[derive(Debug, Clone)]
pub struct NetworkDevice {
pub name: String,
pub description: Option<String>,
pub mac: Option<pnet::util::MacAddr>,
pub ips: Vec<std::net::IpAddr>,
pub is_up: bool,
pub is_loopback: bool,
}
pub fn list_network_devices() -> Vec<NetworkDevice> {
datalink::interfaces()
.into_iter()
.map(|interface| NetworkDevice {
name: interface.name.clone(),
description: Some(interface.description.clone()),
mac: interface.mac,
ips: interface.ips.iter().map(|ip| ip.ip()).collect(),
is_up: interface.is_up(),
is_loopback: interface.is_loopback(),
})
.collect()
}
pub fn print_network_devices() {
let devices = list_network_devices();
println!(
"\n{:<20} {:<18} {:<15} {:<8} {:<10} {:<30}",
"设备名称", "MAC地址", "IP地址", "状态", "类型", "描述"
);
println!("{}", "-".repeat(100));
for device in devices {
let mac_str = device.mac.map_or("N/A".to_string(), |mac| mac.to_string());
let status = if device.is_up { "UP" } else { "DOWN" };
let device_type = if device.is_loopback {
"LOOPBACK"
} else {
"ETHERNET"
};
let description = device.description.as_deref().unwrap_or("N/A");
if device.ips.is_empty() {
println!(
"{:<20} {:<18} {:<15} {:<8} {:<10} {:<30}",
device.name, mac_str, "N/A", status, device_type, description
);
continue;
}
for (index, ip) in device.ips.iter().enumerate() {
if index == 0 {
println!(
"{:<20} {:<18} {:<15} {:<8} {:<10} {:<30}",
device.name,
mac_str,
ip.to_string(),
status,
device_type,
description
);
} else {
println!(
"{:<20} {:<18} {:<15} {:<8} {:<10} {:<30}",
"",
"",
ip.to_string(),
"",
"",
""
);
}
}
}
}
pub fn get_device_by_name(device_name: &str) -> Result<EthTable, String> {
get_device_by_name_for_dns(device_name, &[])
}
pub fn get_device_by_name_for_dns(
device_name: &str,
dns_servers: &[String],
) -> Result<EthTable, String> {
let probe_target = choose_probe_target(dns_servers)
.ok_or_else(|| "未找到可用于原始发包的DNS探测目标".to_string())?;
let route = resolve_route_to_target(probe_target)?;
let interface = datalink::interfaces()
.into_iter()
.find(|iface| iface.name == device_name && !iface.is_loopback())
.ok_or_else(|| format!("未找到指定网络接口 {}", device_name))?;
let src_ip = first_ipv4_on_interface(&interface)
.ok_or_else(|| format!("接口 {} 没有可用的IPv4地址", interface.name))?;
let src_mac = interface
.mac
.ok_or_else(|| format!("接口 {} 缺少MAC地址", interface.name))?;
if let Some(route_interface) = route.interface.as_ref() {
if route_interface != &interface.name {
return Err(format!(
"指定接口 {} 与系统路由接口 {} 不一致,拒绝使用错误网卡",
interface.name, route_interface
));
}
}
let next_hop_ip = route.gateway.unwrap_or(probe_target);
let dst_mac = resolve_next_hop_mac(src_ip, probe_target, next_hop_ip, &interface.name)?;
Ok(EthTable {
src_ip,
device: interface.name,
src_mac,
dst_mac,
})
}
#[allow(dead_code)]
fn get_gateway_mac(interface_name: &str) -> Option<pnet::util::MacAddr> {
let probe_target = choose_probe_target(&[])?;
let route = resolve_route_to_target(probe_target).ok()?;
let next_hop_ip = route.gateway.unwrap_or(probe_target);
lookup_arp_cache(next_hop_ip, interface_name)
}
pub async fn auto_get_devices() -> Result<EthTable, String> {
auto_get_devices_for_dns(&[]).await
}
pub async fn auto_get_devices_for_dns(dns_servers: &[String]) -> Result<EthTable, String> {
let probe_target = choose_probe_target(dns_servers).unwrap_or_else(|| {
Ipv4Addr::from_str(DEFAULT_DNS_PROBE_TARGETS[0]).unwrap_or(Ipv4Addr::new(223, 5, 5, 5))
});
resolve_best_device(probe_target)
}
fn resolve_best_device(probe_target: Ipv4Addr) -> Result<EthTable, String> {
let (interface_name, src_ip, src_mac) = detect_egress_interface(probe_target)?;
let route = resolve_route_to_target(probe_target)?;
if let Some(route_interface) = route.interface.as_ref() {
if route_interface != &interface_name {
return Err(format!(
"系统路由接口 {} 与本地出接口 {} 不一致",
route_interface, interface_name
));
}
}
let next_hop_ip = route.gateway.unwrap_or(probe_target);
let dst_mac = resolve_next_hop_mac(src_ip, probe_target, next_hop_ip, &interface_name)?;
println!(
"自动检测网络设备成功: interface={} src_ip={} next_hop={} dst_mac={}",
interface_name, src_ip, next_hop_ip, dst_mac
);
Ok(EthTable {
src_ip,
device: interface_name,
src_mac,
dst_mac,
})
}
fn detect_egress_interface(
probe_target: Ipv4Addr,
) -> Result<(String, Ipv4Addr, pnet::util::MacAddr), String> {
let socket = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))
.map_err(|error| format!("绑定UDP探测socket失败: {}", error))?;
socket
.connect(SocketAddrV4::new(probe_target, 53))
.map_err(|error| format!("连接探测DNS服务器失败: {}", error))?;
let local_ip = match socket
.local_addr()
.map_err(|error| format!("读取本地探测地址失败: {}", error))?
.ip()
{
IpAddr::V4(ip) => ip,
IpAddr::V6(ip) => {
return Err(format!("探测得到IPv6地址 {},当前仅支持IPv4原始发包", ip));
}
};
for interface in datalink::interfaces() {
if interface.is_loopback() {
continue;
}
let has_local_ip = interface
.ips
.iter()
.any(|network| network.ip() == IpAddr::V4(local_ip));
if !has_local_ip {
continue;
}
let src_mac = interface
.mac
.ok_or_else(|| format!("接口 {} 缺少MAC地址", interface.name))?;
return Ok((interface.name, local_ip, src_mac));
}
Err(format!("未找到承载本地地址 {} 的有效网络接口", local_ip))
}
fn resolve_route_to_target(probe_target: Ipv4Addr) -> Result<RouteResolution, String> {
let output = Command::new("route")
.arg("-n")
.arg("get")
.arg(probe_target.to_string())
.output()
.map_err(|error| format!("执行 route -n get 失败: {}", error))?;
if !output.status.success() {
return Err(format!(
"route -n get 返回失败: {}",
String::from_utf8_lossy(&output.stderr).trim()
));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let interface = parse_route_field(&stdout, "interface");
let gateway = parse_route_field(&stdout, "gateway")
.and_then(|value| parse_gateway_field(&value, probe_target));
Ok(RouteResolution { interface, gateway })
}
fn parse_route_field(output: &str, key: &str) -> Option<String> {
for line in output.lines() {
let trimmed = line.trim();
let prefix = format!("{}:", key);
if let Some(rest) = trimmed.strip_prefix(&prefix) {
return Some(rest.trim().to_string());
}
}
None
}
fn parse_gateway_field(value: &str, probe_target: Ipv4Addr) -> Option<Ipv4Addr> {
if value.starts_with("link#") {
return Some(probe_target);
}
Ipv4Addr::from_str(value).ok()
}
fn resolve_next_hop_mac(
src_ip: Ipv4Addr,
probe_target: Ipv4Addr,
next_hop_ip: Ipv4Addr,
interface_name: &str,
) -> Result<pnet::util::MacAddr, String> {
if let Some(mac) = lookup_arp_cache(next_hop_ip, interface_name) {
return Ok(mac);
}
for attempt in 0..5 {
warm_up_neighbor_cache(src_ip, probe_target)
.map_err(|error| format!("预热ARP缓存失败: {}", error))?;
thread::sleep(Duration::from_millis(200 * (attempt + 1) as u64));
if let Some(mac) = lookup_arp_cache(next_hop_ip, interface_name) {
return Ok(mac);
}
}
Err(format!(
"无法解析下一跳 {} 在接口 {} 上的MAC地址",
next_hop_ip, interface_name
))
}
fn warm_up_neighbor_cache(src_ip: Ipv4Addr, probe_target: Ipv4Addr) -> Result<(), std::io::Error> {
let socket = UdpSocket::bind(SocketAddrV4::new(src_ip, 0))?;
socket.set_write_timeout(Some(Duration::from_millis(500)))?;
let _ = socket.send_to(&[0u8], SocketAddrV4::new(probe_target, 53));
Ok(())
}
fn lookup_arp_cache(target_ip: Ipv4Addr, interface_name: &str) -> Option<pnet::util::MacAddr> {
let output = Command::new("arp")
.arg("-n")
.arg(target_ip.to_string())
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
parse_arp_cache_output(&stdout, interface_name)
}
fn parse_arp_cache_output(output: &str, interface_name: &str) -> Option<pnet::util::MacAddr> {
for line in output.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.contains("no entry") || trimmed.contains("incomplete") {
continue;
}
if !trimmed.contains(&format!(" on {}", interface_name)) {
continue;
}
let marker = " at ";
let start = trimmed.find(marker)?;
let rest = &trimmed[start + marker.len()..];
let mac_text = rest.split_whitespace().next()?;
if mac_text == "(incomplete)" {
continue;
}
if let Some(mac) = parse_mac_addr(mac_text) {
return Some(mac);
}
}
None
}
fn parse_mac_addr(value: &str) -> Option<pnet::util::MacAddr> {
let normalized = value.trim().replace('-', ":");
let parts: Vec<&str> = normalized.split(':').collect();
if parts.len() != 6 {
return None;
}
let mut octets = [0u8; 6];
for (index, part) in parts.iter().enumerate() {
octets[index] = u8::from_str_radix(part, 16).ok()?;
}
Some(pnet::util::MacAddr::new(
octets[0], octets[1], octets[2], octets[3], octets[4], octets[5],
))
}
fn choose_probe_target(dns_servers: &[String]) -> Option<Ipv4Addr> {
for server in dns_servers {
if let Ok(ip) = Ipv4Addr::from_str(server.trim()) {
return Some(ip);
}
}
DEFAULT_DNS_PROBE_TARGETS
.iter()
.find_map(|value| Ipv4Addr::from_str(value).ok())
}
fn first_ipv4_on_interface(interface: &datalink::NetworkInterface) -> Option<Ipv4Addr> {
interface.ips.iter().find_map(|network| match network.ip() {
IpAddr::V4(ip) => Some(ip),
IpAddr::V6(_) => None,
})
}