use std::{
collections::HashMap,
ffi::CStr,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
ptr,
time::Instant,
};
use crate::{
error::{Error, Result},
network::{traffic::TrafficTracker, NetworkMetrics},
utils::bindings::{
address_family, freeifaddrs, getifaddrs, if_flags, ifaddrs, sockaddr_dl, sockaddr_in,
sockaddr_in6,
},
};
type NetworkAddressMap = HashMap<String, (u32, Option<String>, Vec<IpAddr>)>;
type TrafficStatsMap = HashMap<String, (u64, u64, u64, u64, u64, u64, u64)>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InterfaceType {
Ethernet,
WiFi,
Loopback,
Virtual,
Other,
}
impl std::fmt::Display for InterfaceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InterfaceType::Ethernet => write!(f, "Ethernet"),
InterfaceType::WiFi => write!(f, "WiFi"),
InterfaceType::Loopback => write!(f, "Loopback"),
InterfaceType::Virtual => write!(f, "Virtual"),
InterfaceType::Other => write!(f, "Other"),
}
}
}
#[derive(Debug, Clone)]
pub struct Interface {
name: String,
interface_type: InterfaceType,
flags: u32,
mac_address: Option<String>,
addresses: Vec<IpAddr>,
traffic: TrafficTracker,
last_update: Instant,
}
impl Interface {
#[allow(clippy::too_many_arguments)]
pub fn new(
name: String,
interface_type: InterfaceType,
flags: u32,
mac_address: Option<String>,
addresses: Vec<IpAddr>,
bytes_received: u64,
bytes_sent: u64,
packets_received: u64,
packets_sent: u64,
receive_errors: u64,
send_errors: u64,
collisions: u64,
) -> Self {
Self {
name,
interface_type,
flags,
mac_address,
addresses,
traffic: TrafficTracker::new(
bytes_received,
bytes_sent,
packets_received,
packets_sent,
receive_errors,
send_errors,
collisions,
),
last_update: Instant::now(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn interface_type(&self) -> &InterfaceType {
&self.interface_type
}
pub fn mac_address(&self) -> Option<&str> {
self.mac_address.as_deref()
}
pub fn addresses(&self) -> Option<&[IpAddr]> {
if self.addresses.is_empty() {
None
} else {
Some(&self.addresses)
}
}
#[allow(clippy::too_many_arguments)]
pub fn update_traffic(
&mut self,
bytes_received: u64,
bytes_sent: u64,
packets_received: u64,
packets_sent: u64,
receive_errors: u64,
send_errors: u64,
collisions: u64,
) {
self.traffic.update(
bytes_received,
bytes_sent,
packets_received,
packets_sent,
receive_errors,
send_errors,
collisions,
);
self.last_update = Instant::now();
}
fn is_flag_set(&self, flag: u32) -> bool {
(self.flags & flag) == flag
}
pub fn packet_receive_rate(&self) -> f64 {
self.traffic.packet_receive_rate()
}
pub fn packet_send_rate(&self) -> f64 {
self.traffic.packet_send_rate()
}
pub fn receive_error_rate(&self) -> f64 {
self.traffic.receive_error_rate()
}
pub fn send_error_rate(&self) -> f64 {
self.traffic.send_error_rate()
}
pub fn is_loopback(&self) -> bool {
self.is_flag_set(if_flags::IFF_LOOPBACK)
}
pub fn supports_broadcast(&self) -> bool {
self.is_flag_set(if_flags::IFF_BROADCAST)
}
pub fn supports_multicast(&self) -> bool {
self.is_flag_set(if_flags::IFF_MULTICAST)
}
pub fn is_point_to_point(&self) -> bool {
self.is_flag_set(if_flags::IFF_POINTOPOINT)
}
pub fn is_wireless(&self) -> bool {
self.is_flag_set(if_flags::IFF_WIRELESS)
|| self.interface_type == InterfaceType::WiFi
|| self.name.starts_with("wl")
}
}
impl NetworkMetrics for Interface {
fn bytes_received(&self) -> u64 {
self.traffic.bytes_received()
}
fn bytes_sent(&self) -> u64 {
self.traffic.bytes_sent()
}
fn packets_received(&self) -> u64 {
self.traffic.packets_received()
}
fn packets_sent(&self) -> u64 {
self.traffic.packets_sent()
}
fn receive_errors(&self) -> u64 {
self.traffic.receive_errors()
}
fn send_errors(&self) -> u64 {
self.traffic.send_errors()
}
fn collisions(&self) -> u64 {
self.traffic.collisions()
}
fn download_speed(&self) -> f64 {
self.traffic.download_speed()
}
fn upload_speed(&self) -> f64 {
self.traffic.upload_speed()
}
fn is_active(&self) -> bool {
self.is_flag_set(if_flags::IFF_UP) && self.is_flag_set(if_flags::IFF_RUNNING)
}
}
#[derive(Debug)]
pub struct NetworkManager {
pub(crate) interfaces: HashMap<String, Interface>,
}
impl NetworkManager {
pub fn new() -> Result<Self> {
let mut manager = Self { interfaces: HashMap::new() };
if let Err(e) = manager.update() {
log::warn!("Failed to initialize network interfaces: {}", e);
}
Ok(manager)
}
pub fn update(&mut self) -> Result<()> {
let interfaces = self.get_interfaces()?;
for interface in interfaces {
let name = interface.name().to_string();
self.interfaces.insert(name, interface);
}
Ok(())
}
pub fn interfaces(&self) -> Vec<&Interface> {
self.interfaces.values().collect()
}
pub fn get_interface(&self, name: &str) -> Option<&Interface> {
self.interfaces.get(name)
}
pub fn total_download_speed(&self) -> f64 {
self.interfaces.values().map(|i| i.download_speed()).sum()
}
pub fn total_upload_speed(&self) -> f64 {
self.interfaces.values().map(|i| i.upload_speed()).sum()
}
fn get_interfaces(&self) -> Result<Vec<Interface>> {
let mut interfaces = Vec::new();
let addresses = self.get_network_addresses()?;
let mut interface_map: HashMap<String, Interface> = HashMap::new();
for (name, data) in addresses {
let (flags, mac_addr, ip_addrs) = data;
if let Some(existing) = interface_map.get_mut(&name) {
if existing.mac_address.is_none() && mac_addr.is_some() {
existing.mac_address = mac_addr;
}
for addr in ip_addrs {
if !existing.addresses.contains(&addr) {
existing.addresses.push(addr);
}
}
} else {
let interface_type = Self::determine_interface_type(&name, flags);
let interface = Interface::new(
name.clone(),
interface_type,
flags,
mac_addr,
ip_addrs,
0,
0,
0,
0,
0,
0,
0, );
interface_map.insert(name, interface);
}
}
let existing_traffic = self.update_traffic_stats();
if let Some(traffic_data) = existing_traffic {
for (
name,
(rx_bytes, tx_bytes, rx_packets, tx_packets, rx_errors, tx_errors, collisions),
) in traffic_data
{
if let Some(interface) = interface_map.get_mut(&name) {
interface.update_traffic(
rx_bytes, tx_bytes, rx_packets, tx_packets, rx_errors, tx_errors,
collisions,
);
} else if !name.is_empty() {
let interface = Interface::new(
name.clone(),
InterfaceType::Other,
0, None,
Vec::new(),
rx_bytes,
tx_bytes,
rx_packets,
tx_packets,
rx_errors,
tx_errors,
collisions,
);
interface_map.insert(name, interface);
}
}
}
for (_, interface) in interface_map {
interfaces.push(interface);
}
Ok(interfaces)
}
fn get_network_addresses(&self) -> Result<NetworkAddressMap> {
let mut result = HashMap::new();
let mut ifap: *mut ifaddrs = ptr::null_mut();
unsafe {
if getifaddrs(&mut ifap) != 0 {
return Err(Error::Network("Failed to get network interfaces".to_string()));
}
let _guard = scopeguard::guard(ifap, |ifap| {
freeifaddrs(ifap);
});
let mut current = ifap;
while !current.is_null() {
let ifa = &*current;
if ifa.ifa_name.is_null() {
current = ifa.ifa_next;
continue;
}
let name = match CStr::from_ptr(ifa.ifa_name).to_str() {
Ok(s) => s.to_string(),
Err(_) => {
current = ifa.ifa_next;
continue;
},
};
if name.is_empty() {
current = ifa.ifa_next;
continue;
}
let entry = result.entry(name).or_insert_with(|| (ifa.ifa_flags, None, Vec::new()));
if !ifa.ifa_addr.is_null() {
let addr = &*ifa.ifa_addr;
match addr.sa_family {
family if family == address_family::AF_INET => {
let addr_in = &*(ifa.ifa_addr as *mut sockaddr_in);
let ip = Ipv4Addr::from(u32::from_be(addr_in.sin_addr.s_addr));
entry.2.push(IpAddr::V4(ip));
},
family if family == address_family::AF_INET6 => {
let addr_in6 = &*(ifa.ifa_addr as *mut sockaddr_in6);
let ip = Ipv6Addr::from(addr_in6.sin6_addr.s6_addr);
entry.2.push(IpAddr::V6(ip));
},
family if family == address_family::AF_LINK => {
let addr_dl = &*(ifa.ifa_addr as *mut sockaddr_dl);
let mac_len = addr_dl.sdl_alen as usize;
if mac_len == 6 {
let offset = addr_dl.sdl_nlen as usize;
if offset + mac_len <= addr_dl.sdl_data.len() {
let mac_bytes = &addr_dl.sdl_data[offset..offset + mac_len];
let mac = format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac_bytes[0] as u8,
mac_bytes[1] as u8,
mac_bytes[2] as u8,
mac_bytes[3] as u8,
mac_bytes[4] as u8,
mac_bytes[5] as u8
);
entry.1 = Some(mac);
}
}
},
_ => {}, }
}
current = ifa.ifa_next;
}
}
Ok(result)
}
fn update_traffic_stats(&self) -> Option<TrafficStatsMap> {
let output = std::process::Command::new("netstat").args(["-ib"]).output().ok()?;
if !output.status.success() {
return None;
}
let output_str = String::from_utf8_lossy(&output.stdout);
let mut result = HashMap::new();
let lines = output_str.lines().skip(1);
for line in lines {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 10 {
continue;
}
let name = parts[0].to_string();
let rx_packets = parts[4].parse::<u64>().unwrap_or(0);
let rx_errors = parts[5].parse::<u64>().unwrap_or(0);
let rx_bytes = parts[6].parse::<u64>().unwrap_or(0);
let tx_packets = parts[7].parse::<u64>().unwrap_or(0);
let tx_errors = parts[8].parse::<u64>().unwrap_or(0);
let tx_bytes = parts[9].parse::<u64>().unwrap_or(0);
let collisions =
if parts.len() > 10 { parts[10].parse::<u64>().unwrap_or(0) } else { 0 };
result.insert(
name,
(rx_bytes, tx_bytes, rx_packets, tx_packets, rx_errors, tx_errors, collisions),
);
}
Some(result)
}
fn determine_interface_type(name: &str, flags: u32) -> InterfaceType {
if (flags & if_flags::IFF_LOOPBACK) != 0 {
InterfaceType::Loopback
} else if name.starts_with("en") {
if name == "en0" {
InterfaceType::WiFi
} else {
InterfaceType::Ethernet
}
} else if name.starts_with("wl") || name.starts_with("ath") {
InterfaceType::WiFi
} else if name.starts_with("vnic") || name.starts_with("bridge") || name.starts_with("utun")
{
InterfaceType::Virtual
} else {
InterfaceType::Other
}
}
pub async fn new_async() -> Result<Self> {
let mut manager = Self { interfaces: HashMap::new() };
if let Err(e) = manager.update_async().await {
log::warn!("Failed to initialize network interfaces asynchronously: {}", e);
}
Ok(manager)
}
pub async fn update_async(&mut self) -> Result<()> {
let interfaces = tokio::task::spawn_blocking(move || {
let temp_manager = NetworkManager { interfaces: HashMap::new() };
temp_manager.get_interfaces()
})
.await
.map_err(|e| Error::Network(format!("Task join error: {}", e)))??;
for interface in interfaces {
let name = interface.name().to_string();
self.interfaces.insert(name, interface);
}
Ok(())
}
pub async fn active_interfaces_async(&self) -> Vec<&Interface> {
self.interfaces.values().filter(|i| i.is_active()).collect()
}
pub async fn get_throughput_async(&self) -> Result<(f64, f64)> {
let total_download = self.total_download_speed();
let total_upload = self.total_upload_speed();
Ok((total_download, total_upload))
}
pub async fn get_interface_async(&self, name: &str) -> Option<&Interface> {
self.get_interface(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_interface_type_display() {
assert_eq!(InterfaceType::Ethernet.to_string(), "Ethernet");
assert_eq!(InterfaceType::WiFi.to_string(), "WiFi");
assert_eq!(InterfaceType::Loopback.to_string(), "Loopback");
assert_eq!(InterfaceType::Virtual.to_string(), "Virtual");
assert_eq!(InterfaceType::Other.to_string(), "Other");
}
#[test]
fn test_interface_creation() {
let interface = Interface::new(
"test0".to_string(),
InterfaceType::Ethernet,
if_flags::IFF_UP | if_flags::IFF_RUNNING | if_flags::IFF_BROADCAST,
Some("00:11:22:33:44:55".to_string()),
vec![IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100))],
1000, 2000, 10, 20, 1, 2, 0, );
assert_eq!(interface.name(), "test0");
assert_eq!(interface.interface_type(), &InterfaceType::Ethernet);
assert_eq!(interface.mac_address(), Some("00:11:22:33:44:55"));
assert_eq!(interface.addresses().unwrap()[0], IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)));
assert_eq!(interface.bytes_received(), 1000);
assert_eq!(interface.bytes_sent(), 2000);
assert_eq!(interface.packets_received(), 10);
assert_eq!(interface.packets_sent(), 20);
assert_eq!(interface.receive_errors(), 1);
assert_eq!(interface.send_errors(), 2);
assert_eq!(interface.collisions(), 0);
assert!(interface.is_active());
assert!(interface.supports_broadcast());
assert!(!interface.is_loopback());
assert!(!interface.is_point_to_point());
assert!(!interface.is_wireless());
}
#[test]
fn test_interface_update_traffic() {
let mut interface = Interface::new(
"test0".to_string(),
InterfaceType::Ethernet,
if_flags::IFF_UP | if_flags::IFF_RUNNING,
None,
vec![],
1000, 2000, 10, 20, 1, 2, 0, );
assert_eq!(interface.bytes_received(), 1000);
assert_eq!(interface.bytes_sent(), 2000);
interface.update_traffic(
2000, 3000, 20, 30, 2, 3, 1, );
assert_eq!(interface.bytes_received(), 2000);
assert_eq!(interface.bytes_sent(), 3000);
assert_eq!(interface.packets_received(), 20);
assert_eq!(interface.packets_sent(), 30);
assert_eq!(interface.receive_errors(), 2);
assert_eq!(interface.send_errors(), 3);
assert_eq!(interface.collisions(), 1);
}
#[test]
fn test_interface_wireless_detection() {
let wifi_interface = Interface::new(
"en0".to_string(),
InterfaceType::WiFi,
0,
None,
vec![],
0,
0,
0,
0,
0,
0,
0,
);
assert!(wifi_interface.is_wireless());
let wireless_interface = Interface::new(
"test0".to_string(),
InterfaceType::Ethernet,
if_flags::IFF_WIRELESS,
None,
vec![],
0,
0,
0,
0,
0,
0,
0,
);
assert!(wireless_interface.is_wireless());
let wlan_interface = Interface::new(
"wlan0".to_string(),
InterfaceType::Other,
0,
None,
vec![],
0,
0,
0,
0,
0,
0,
0,
);
assert!(wlan_interface.is_wireless());
}
#[test]
fn test_network_manager_creation() {
let manager = NetworkManager::new();
assert!(manager.is_ok());
}
#[test]
fn test_network_manager_interface_access() {
let mut manager = NetworkManager { interfaces: HashMap::new() };
let interface = Interface::new(
"test0".to_string(),
InterfaceType::Ethernet,
if_flags::IFF_UP | if_flags::IFF_RUNNING,
None,
vec![],
1000,
2000,
10,
20,
1,
2,
0,
);
manager.interfaces.insert("test0".to_string(), interface);
let interfaces = manager.interfaces();
assert_eq!(interfaces.len(), 1);
assert_eq!(interfaces[0].name(), "test0");
let found = manager.get_interface("test0");
assert!(found.is_some());
assert_eq!(found.unwrap().name(), "test0");
let not_found = manager.get_interface("nonexistent");
assert!(not_found.is_none());
}
#[test]
fn test_determine_interface_type() {
assert_eq!(
NetworkManager::determine_interface_type("lo0", if_flags::IFF_LOOPBACK),
InterfaceType::Loopback
);
assert_eq!(NetworkManager::determine_interface_type("en1", 0), InterfaceType::Ethernet);
assert_eq!(NetworkManager::determine_interface_type("en0", 0), InterfaceType::WiFi);
assert_eq!(NetworkManager::determine_interface_type("wlan0", 0), InterfaceType::WiFi);
assert_eq!(NetworkManager::determine_interface_type("vnic0", 0), InterfaceType::Virtual);
assert_eq!(NetworkManager::determine_interface_type("bridge0", 0), InterfaceType::Virtual);
assert_eq!(NetworkManager::determine_interface_type("utun0", 0), InterfaceType::Virtual);
assert_eq!(NetworkManager::determine_interface_type("unknown0", 0), InterfaceType::Other);
}
#[test]
fn test_network_speed_calculation() {
let mut interface1 = Interface::new(
"test1".to_string(),
InterfaceType::Ethernet,
if_flags::IFF_UP | if_flags::IFF_RUNNING,
None,
vec![],
1000, 2000, 10,
20,
0,
0,
0,
);
let mut interface2 = Interface::new(
"test2".to_string(),
InterfaceType::WiFi,
if_flags::IFF_UP | if_flags::IFF_RUNNING,
None,
vec![],
3000, 4000, 30,
40,
0,
0,
0,
);
std::thread::sleep(Duration::from_millis(100));
interface1.update_traffic(2000, 3000, 20, 30, 0, 0, 0);
interface2.update_traffic(5000, 7000, 50, 70, 0, 0, 0);
let mut manager = NetworkManager { interfaces: HashMap::new() };
manager.interfaces.insert("test1".to_string(), interface1);
manager.interfaces.insert("test2".to_string(), interface2);
let download = manager.total_download_speed();
let upload = manager.total_upload_speed();
assert!(download > 0.0);
assert!(upload > 0.0);
}
}