use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use tracing::error; /// Network interface information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NetworkInterface { pub name: String, pub addresses: Vec, pub is_up: bool, pub is_loopback: bool, } /// IP address with type information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IpAddress { pub address: String, pub is_ipv4: bool, pub is_ipv6: bool, pub is_private: bool, } /// Network utilities pub struct NetworkUtils; impl NetworkUtils { /// Get the primary local IP address pub fn get_local_ip_address() -> Option { // Try to get network interfaces let interfaces = Self::get_all_interfaces(); // First, try to find a private network address (192.168.x.x, 10.x.x.x, 172.16-31.x.x) for interface in &interfaces { if interface.is_loopback || !interface.is_up { continue; } for addr in &interface.addresses { if addr.is_ipv4 && addr.is_private { return Some(addr.address.clone()); } } } // If no private address found, return any non-loopback IPv4 for interface in &interfaces { if interface.is_loopback || !interface.is_up { continue; } for addr in &interface.addresses { if addr.is_ipv4 { return Some(addr.address.clone()); } } } None } /// Get all IP addresses pub fn get_all_ip_addresses() -> Vec { let interfaces = Self::get_all_interfaces(); let mut addresses = Vec::new(); for interface in interfaces { if interface.is_loopback { continue; } for addr in interface.addresses { addresses.push(addr.address); } } addresses } /// Get all network interfaces pub fn get_all_interfaces() -> Vec { #[cfg(unix)] { Self::get_interfaces_unix() } #[cfg(windows)] { Self::get_interfaces_windows() } #[cfg(not(any(unix, windows)))] { Vec::new() } } #[cfg(unix)] fn get_interfaces_unix() -> Vec { use nix::ifaddrs::getifaddrs; let mut interfaces = std::collections::HashMap::new(); match getifaddrs() { Ok(addrs) => { for ifaddr in addrs { let name = ifaddr.interface_name.clone(); let flags = ifaddr.flags; let interface = interfaces .entry(name.clone()) .or_insert_with(|| NetworkInterface { name, addresses: Vec::new(), is_up: flags.contains(nix::net::if_::InterfaceFlags::IFF_UP), is_loopback: flags .contains(nix::net::if_::InterfaceFlags::IFF_LOOPBACK), }); if let Some(address) = ifaddr.address { if let Some(sockaddr) = address.as_sockaddr_in() { let ip = IpAddr::V4(Ipv4Addr::from(sockaddr.ip())); interface.addresses.push(IpAddress { address: ip.to_string(), is_ipv4: true, is_ipv6: false, is_private: Self::is_private_ip(&ip), }); } else if let Some(sockaddr) = address.as_sockaddr_in6() { let ip = IpAddr::V6(sockaddr.ip()); interface.addresses.push(IpAddress { address: ip.to_string(), is_ipv4: false, is_ipv6: true, is_private: Self::is_private_ip(&ip), }); } } } } Err(e) => { error!("Failed to get network interfaces: {}", e); } } interfaces.into_values().collect() } #[cfg(windows)] fn get_interfaces_windows() -> Vec { use ipconfig::get_adapters; let mut interfaces = Vec::new(); match get_adapters() { Ok(adapters) => { for adapter in adapters { let mut addresses = Vec::new(); // Get IPv4 addresses for addr in adapter.ipv4_addresses() { addresses.push(IpAddress { address: addr.to_string(), is_ipv4: true, is_ipv6: false, is_private: Self::is_private_ipv4(addr), }); } // Get IPv6 addresses for addr in adapter.ipv6_addresses() { addresses.push(IpAddress { address: addr.to_string(), is_ipv4: false, is_ipv6: true, is_private: Self::is_private_ipv6(addr), }); } interfaces.push(NetworkInterface { name: adapter.friendly_name().to_string(), addresses, is_up: adapter.oper_status() == ipconfig::OperStatus::IfOperStatusUp, is_loopback: adapter.if_type() == ipconfig::IfType::SoftwareLoopback, }); } } Err(e) => { error!("Failed to get network interfaces: {}", e); } } interfaces } /// Check if an IP address is private const fn is_private_ip(ip: &IpAddr) -> bool { match ip { IpAddr::V4(ipv4) => Self::is_private_ipv4(ipv4), IpAddr::V6(ipv6) => Self::is_private_ipv6(ipv6), } } /// Check if an IPv4 address is private const fn is_private_ipv4(ip: &Ipv4Addr) -> bool { let octets = ip.octets(); // 10.0.0.0/8 if octets[0] == 10 { return true; } // 172.16.0.0/12 if octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31) { return true; } // 192.168.0.0/16 if octets[0] == 192 && octets[1] == 168 { return true; } false } /// Check if an IPv6 address is private const fn is_private_ipv6(ip: &Ipv6Addr) -> bool { // Check for link-local addresses (fe80::/10) let segments = ip.segments(); if segments[0] & 0xffc0 == 0xfe80 { return true; } // Check for unique local addresses (fc00::/7) if segments[0] & 0xfe00 == 0xfc00 { return true; } false } /// Get hostname pub fn get_hostname() -> Option { hostname::get() .ok() .and_then(|name| name.into_string().ok()) } /// Test network connectivity to a host pub async fn test_connectivity(host: &str, port: u16) -> bool { use std::time::Duration; use tokio::net::TcpStream; use tokio::time::timeout; let addr = format!("{host}:{port}"); match timeout(Duration::from_secs(3), TcpStream::connect(&addr)).await { Ok(Ok(_)) => true, _ => false, } } /// Get network statistics pub fn get_network_stats() -> NetworkStats { NetworkStats { hostname: Self::get_hostname(), primary_ip: Self::get_local_ip_address(), all_ips: Self::get_all_ip_addresses(), interface_count: Self::get_all_interfaces().len(), } } } /// Network statistics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NetworkStats { pub hostname: Option, pub primary_ip: Option, pub all_ips: Vec, pub interface_count: usize, } #[cfg(test)] mod tests { use super::*; #[test] fn test_private_ipv4() { assert!(NetworkUtils::is_private_ipv4(&"10.0.0.1".parse().unwrap())); assert!(NetworkUtils::is_private_ipv4( &"172.16.0.1".parse().unwrap() )); assert!(NetworkUtils::is_private_ipv4( &"192.168.1.1".parse().unwrap() )); assert!(!NetworkUtils::is_private_ipv4(&"8.8.8.8".parse().unwrap())); } }