| // Copyright 2018 MaidSafe.net limited. |
| // |
| // This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT |
| // http://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD |
| // https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied, |
| // modified, or distributed except according to those terms. Please review the Licences for the |
| // specific language governing permissions and limitations relating to use of the SAFE Network |
| // Software. |
| #![cfg_attr(docsrs, feature(doc_auto_cfg))] |
| #![cfg_attr(docsrs, feature(doc_cfg))] |
| |
| #[cfg(not(windows))] |
| mod posix; |
| #[cfg(all( |
| not(windows), |
| not(all( |
| target_vendor = "apple", |
| any( |
| target_os = "macos", |
| target_os = "ios", |
| target_os = "tvos", |
| target_os = "watchos", |
| target_os = "visionos" |
| ) |
| )), |
| not(target_os = "freebsd"), |
| not(target_os = "netbsd"), |
| not(target_os = "openbsd"), |
| not(target_os = "illumos") |
| ))] |
| mod posix_not_apple; |
| mod sockaddr; |
| #[cfg(windows)] |
| mod windows; |
| |
| use std::io; |
| use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; |
| |
| /// The current operational state of the interface, as defined in RFC 2863 section 6. |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub enum IfOperStatus { |
| /// The interface is up and running. |
| Up = 1, |
| |
| /// The interface is down. |
| Down = 2, |
| |
| /// The interface is testing. |
| Testing = 3, |
| |
| /// The interface is unknown. |
| Unknown = 4, |
| |
| /// The interface is in a "pending" state, waiting for some external event. |
| Dormant = 5, |
| |
| /// A refinement on the down state which indicates that the relevant |
| /// interface is down specifically because some component (typically, |
| /// a hardware component) is not present in the managed system. |
| NotPresent = 6, |
| |
| /// A refinement on the down state. This new state indicates |
| /// that this interface runs on top of one or more other interfaces and |
| /// that this interface is down specifically because one or more of these |
| /// lower-layer interfaces are down. |
| LowerLayerDown = 7, |
| } |
| |
| impl From<i32> for IfOperStatus { |
| fn from(value: i32) -> Self { |
| match value { |
| 1 => Self::Up, |
| 2 => Self::Down, |
| 3 => Self::Testing, |
| 4 => Self::Unknown, |
| 5 => Self::Dormant, |
| 6 => Self::NotPresent, |
| 7 => Self::LowerLayerDown, |
| _ => Self::Unknown, |
| } |
| } |
| } |
| |
| /// Details about an interface on this host. |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub struct Interface { |
| /// The name of the interface. |
| pub name: String, |
| /// The address details of the interface. |
| pub addr: IfAddr, |
| /// The index of the interface. |
| pub index: Option<u32>, |
| |
| /// Whether the interface is operational up. |
| pub oper_status: IfOperStatus, |
| |
| /// (Windows only) A permanent and unique identifier for the interface. It |
| /// cannot be modified by the user. It is typically a GUID string of the |
| /// form: "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}", but this is not |
| /// guaranteed by the Windows API. |
| #[cfg(windows)] |
| pub adapter_name: String, |
| } |
| |
| impl Interface { |
| /// Check whether this is a loopback interface. |
| #[must_use] |
| pub const fn is_loopback(&self) -> bool { |
| self.addr.is_loopback() |
| } |
| |
| /// Check whether this is a link local interface. |
| #[must_use] |
| pub const fn is_link_local(&self) -> bool { |
| self.addr.is_link_local() |
| } |
| |
| /// Get the IP address of this interface. |
| #[must_use] |
| pub const fn ip(&self) -> IpAddr { |
| self.addr.ip() |
| } |
| |
| /// Check whether this interface is operationally up. |
| #[must_use] |
| pub fn is_oper_up(&self) -> bool { |
| self.oper_status == IfOperStatus::Up |
| } |
| } |
| |
| /// Details about the address of an interface on this host. |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub enum IfAddr { |
| /// This is an Ipv4 interface. |
| V4(Ifv4Addr), |
| /// This is an Ipv6 interface. |
| V6(Ifv6Addr), |
| } |
| |
| impl IfAddr { |
| /// Check whether this is a loopback address. |
| #[must_use] |
| pub const fn is_loopback(&self) -> bool { |
| match *self { |
| IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_loopback(), |
| IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_loopback(), |
| } |
| } |
| |
| /// Check whether this is a link local interface. |
| #[must_use] |
| pub const fn is_link_local(&self) -> bool { |
| match *self { |
| IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_link_local(), |
| IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_link_local(), |
| } |
| } |
| |
| /// Get the IP address of this interface address. |
| #[must_use] |
| pub const fn ip(&self) -> IpAddr { |
| match *self { |
| IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.ip), |
| IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.ip), |
| } |
| } |
| } |
| |
| /// Details about the ipv4 address of an interface on this host. |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub struct Ifv4Addr { |
| /// The IP address of the interface. |
| pub ip: Ipv4Addr, |
| /// The netmask of the interface. |
| pub netmask: Ipv4Addr, |
| /// The CIDR prefix of the interface. |
| pub prefixlen: u8, |
| /// The broadcast address of the interface. |
| pub broadcast: Option<Ipv4Addr>, |
| } |
| |
| impl Ifv4Addr { |
| /// Check whether this is a loopback address. |
| #[must_use] |
| pub const fn is_loopback(&self) -> bool { |
| self.ip.is_loopback() |
| } |
| |
| /// Check whether this is a link local address. |
| #[must_use] |
| pub const fn is_link_local(&self) -> bool { |
| self.ip.is_link_local() |
| } |
| } |
| |
| /// Details about the ipv6 address of an interface on this host. |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub struct Ifv6Addr { |
| /// The IP address of the interface. |
| pub ip: Ipv6Addr, |
| /// The netmask of the interface. |
| pub netmask: Ipv6Addr, |
| /// The CIDR prefix of the interface. |
| pub prefixlen: u8, |
| /// The broadcast address of the interface. |
| pub broadcast: Option<Ipv6Addr>, |
| } |
| |
| impl Ifv6Addr { |
| /// Check whether this is a loopback address. |
| #[must_use] |
| pub const fn is_loopback(&self) -> bool { |
| self.ip.is_loopback() |
| } |
| |
| /// Check whether this is a link local address. |
| #[must_use] |
| pub const fn is_link_local(&self) -> bool { |
| let bytes = self.ip.octets(); |
| |
| bytes[0] == 0xfe && bytes[1] == 0x80 |
| } |
| } |
| |
| #[cfg(not(windows))] |
| mod getifaddrs_posix { |
| use libc::if_nametoindex; |
| |
| use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface}; |
| use crate::posix::{self as ifaddrs, IfAddrs}; |
| use crate::sockaddr; |
| use crate::IfOperStatus; |
| use std::ffi::CStr; |
| use std::io; |
| use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; |
| |
| /// Defined in `<net/if.h>` on POSIX systems. |
| /// https://github.com/torvalds/linux/blob/18531f4d1c8c47c4796289dbbc1ab657ffa063d2/include/uapi/linux/if.h#L85 |
| #[cfg(not(target_os = "illumos"))] |
| const POSIX_IFF_RUNNING: u32 = 0x40; // 1<<6 |
| #[cfg(target_os = "illumos")] |
| const POSIX_IFF_RUNNING: u64 = 0x40; // 1<<6 |
| |
| /// Return a vector of IP details for all the valid interfaces on this host. |
| #[allow(unsafe_code)] |
| pub fn get_if_addrs() -> io::Result<Vec<Interface>> { |
| let mut ret = Vec::<Interface>::new(); |
| let ifaddrs = IfAddrs::new()?; |
| |
| for ifaddr in ifaddrs.iter() { |
| let addr = match sockaddr::to_ipaddr(ifaddr.ifa_addr) { |
| None => continue, |
| Some(IpAddr::V4(ipv4_addr)) => { |
| let netmask = match sockaddr::to_ipaddr(ifaddr.ifa_netmask) { |
| Some(IpAddr::V4(netmask)) => netmask, |
| _ => Ipv4Addr::new(0, 0, 0, 0), |
| }; |
| let broadcast = if (ifaddr.ifa_flags & 2) != 0 { |
| match ifaddrs::do_broadcast(&ifaddr) { |
| Some(IpAddr::V4(broadcast)) => Some(broadcast), |
| _ => None, |
| } |
| } else { |
| None |
| }; |
| let prefixlen = if cfg!(target_endian = "little") { |
| u32::from_le_bytes(netmask.octets()).count_ones() as u8 |
| } else { |
| u32::from_be_bytes(netmask.octets()).count_ones() as u8 |
| }; |
| IfAddr::V4(Ifv4Addr { |
| ip: ipv4_addr, |
| netmask, |
| prefixlen, |
| broadcast, |
| }) |
| } |
| Some(IpAddr::V6(ipv6_addr)) => { |
| let netmask = match sockaddr::to_ipaddr(ifaddr.ifa_netmask) { |
| Some(IpAddr::V6(netmask)) => netmask, |
| _ => Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), |
| }; |
| let broadcast = if (ifaddr.ifa_flags & 2) != 0 { |
| match ifaddrs::do_broadcast(&ifaddr) { |
| Some(IpAddr::V6(broadcast)) => Some(broadcast), |
| _ => None, |
| } |
| } else { |
| None |
| }; |
| let prefixlen = if cfg!(target_endian = "little") { |
| u128::from_le_bytes(netmask.octets()).count_ones() as u8 |
| } else { |
| u128::from_be_bytes(netmask.octets()).count_ones() as u8 |
| }; |
| IfAddr::V6(Ifv6Addr { |
| ip: ipv6_addr, |
| netmask, |
| prefixlen, |
| broadcast, |
| }) |
| } |
| }; |
| |
| let name = unsafe { CStr::from_ptr(ifaddr.ifa_name) } |
| .to_string_lossy() |
| .into_owned(); |
| let index = { |
| let index = unsafe { if_nametoindex(ifaddr.ifa_name) }; |
| |
| // From `man if_nametoindex 3`: |
| // The if_nametoindex() function maps the interface name specified in ifname to its |
| // corresponding index. If the specified interface does not exist, it returns 0. |
| if index == 0 { |
| None |
| } else { |
| Some(index) |
| } |
| }; |
| |
| let oper_status = if ifaddr.ifa_flags & POSIX_IFF_RUNNING != 0 { |
| IfOperStatus::Up |
| } else { |
| IfOperStatus::Unknown |
| }; |
| |
| ret.push(Interface { |
| name, |
| addr, |
| index, |
| oper_status, |
| }); |
| } |
| |
| Ok(ret) |
| } |
| } |
| |
| /// Get a list of all the network interfaces on this machine along with their IP info. |
| #[cfg(not(windows))] |
| pub fn get_if_addrs() -> io::Result<Vec<Interface>> { |
| getifaddrs_posix::get_if_addrs() |
| } |
| |
| #[cfg(windows)] |
| mod getifaddrs_windows { |
| use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface}; |
| use crate::sockaddr; |
| use crate::windows::IfAddrs; |
| use std::io; |
| use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; |
| use windows_sys::Win32::Networking::WinSock::IpDadStatePreferred; |
| |
| /// Return a vector of IP details for all the valid interfaces on this host. |
| pub fn get_if_addrs() -> io::Result<Vec<Interface>> { |
| let mut ret = Vec::<Interface>::new(); |
| let ifaddrs = IfAddrs::new()?; |
| |
| for ifaddr in ifaddrs.iter() { |
| for addr in ifaddr.unicast_addresses() { |
| if addr.DadState != IpDadStatePreferred { |
| continue; |
| } |
| let addr = match sockaddr::to_ipaddr(addr.Address.lpSockaddr) { |
| None => continue, |
| Some(IpAddr::V4(ipv4_addr)) => { |
| let mut item_netmask = Ipv4Addr::new(0, 0, 0, 0); |
| let mut item_broadcast = None; |
| let item_prefix = addr.OnLinkPrefixLength; |
| |
| // Search prefixes for a prefix matching addr |
| 'prefixloopv4: for prefix in ifaddr.prefixes() { |
| let ipprefix = sockaddr::to_ipaddr(prefix.Address.lpSockaddr); |
| match ipprefix { |
| Some(IpAddr::V4(ref a)) => { |
| let mut netmask: [u8; 4] = [0; 4]; |
| for (n, netmask_elt) in netmask |
| .iter_mut() |
| .enumerate() |
| .take((prefix.PrefixLength as usize + 7) / 8) |
| { |
| let x_byte = ipv4_addr.octets()[n]; |
| let y_byte = a.octets()[n]; |
| for m in 0..8 { |
| if (n * 8) + m >= prefix.PrefixLength as usize { |
| break; |
| } |
| let bit = 1 << (7 - m); |
| if (x_byte & bit) == (y_byte & bit) { |
| *netmask_elt |= bit; |
| } else { |
| continue 'prefixloopv4; |
| } |
| } |
| } |
| item_netmask = Ipv4Addr::new( |
| netmask[0], netmask[1], netmask[2], netmask[3], |
| ); |
| let mut broadcast: [u8; 4] = ipv4_addr.octets(); |
| for n in 0..4 { |
| broadcast[n] |= !netmask[n]; |
| } |
| item_broadcast = Some(Ipv4Addr::new( |
| broadcast[0], |
| broadcast[1], |
| broadcast[2], |
| broadcast[3], |
| )); |
| break 'prefixloopv4; |
| } |
| _ => continue, |
| }; |
| } |
| IfAddr::V4(Ifv4Addr { |
| ip: ipv4_addr, |
| netmask: item_netmask, |
| prefixlen: item_prefix, |
| broadcast: item_broadcast, |
| }) |
| } |
| Some(IpAddr::V6(ipv6_addr)) => { |
| let mut item_netmask = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); |
| let item_prefix = addr.OnLinkPrefixLength; |
| // Search prefixes for a prefix matching addr |
| 'prefixloopv6: for prefix in ifaddr.prefixes() { |
| let ipprefix = sockaddr::to_ipaddr(prefix.Address.lpSockaddr); |
| match ipprefix { |
| Some(IpAddr::V6(ref a)) => { |
| // Iterate the bits in the prefix, if they all match this prefix |
| // is the right one, else try the next prefix |
| let mut netmask: [u16; 8] = [0; 8]; |
| for (n, netmask_elt) in netmask |
| .iter_mut() |
| .enumerate() |
| .take((prefix.PrefixLength as usize + 15) / 16) |
| { |
| let x_word = ipv6_addr.segments()[n]; |
| let y_word = a.segments()[n]; |
| for m in 0..16 { |
| if (n * 16) + m >= prefix.PrefixLength as usize { |
| break; |
| } |
| let bit = 1 << (15 - m); |
| if (x_word & bit) == (y_word & bit) { |
| *netmask_elt |= bit; |
| } else { |
| continue 'prefixloopv6; |
| } |
| } |
| } |
| item_netmask = Ipv6Addr::new( |
| netmask[0], netmask[1], netmask[2], netmask[3], netmask[4], |
| netmask[5], netmask[6], netmask[7], |
| ); |
| break 'prefixloopv6; |
| } |
| _ => continue, |
| }; |
| } |
| IfAddr::V6(Ifv6Addr { |
| ip: ipv6_addr, |
| netmask: item_netmask, |
| prefixlen: item_prefix, |
| broadcast: None, |
| }) |
| } |
| }; |
| |
| let index = match addr { |
| IfAddr::V4(_) => ifaddr.ipv4_index(), |
| IfAddr::V6(_) => ifaddr.ipv6_index(), |
| }; |
| let oper_status = ifaddr.oper_status(); |
| |
| ret.push(Interface { |
| name: ifaddr.name(), |
| addr, |
| index, |
| oper_status, |
| adapter_name: ifaddr.adapter_name(), |
| }); |
| } |
| } |
| |
| Ok(ret) |
| } |
| } |
| |
| /// Get a list of all the network interfaces on this machine along with their IP info. |
| #[cfg(windows)] |
| pub fn get_if_addrs() -> io::Result<Vec<Interface>> { |
| getifaddrs_windows::get_if_addrs() |
| } |
| |
| #[cfg(not(any( |
| all( |
| target_vendor = "apple", |
| any( |
| target_os = "macos", |
| target_os = "ios", |
| target_os = "tvos", |
| target_os = "watchos", |
| target_os = "visionos" |
| ) |
| ), |
| target_os = "freebsd", |
| target_os = "netbsd", |
| target_os = "openbsd", |
| target_os = "illumos" |
| )))] |
| #[cfg_attr( |
| docsrs, |
| doc(cfg(any( |
| not(target_vendor = "apple"), |
| not(target_os = "freebsd"), |
| not(target_os = "netbsd"), |
| not(target_os = "openbsd"), |
| not(target_os = "illumos") |
| ))) |
| )] |
| mod if_change_notifier { |
| use super::Interface; |
| use std::collections::HashSet; |
| use std::io; |
| use std::time::{Duration, Instant}; |
| |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub enum IfChangeType { |
| Added(Interface), |
| Removed(Interface), |
| } |
| |
| #[cfg(windows)] |
| type InternalIfChangeNotifier = crate::windows::WindowsIfChangeNotifier; |
| #[cfg(not(windows))] |
| type InternalIfChangeNotifier = crate::posix_not_apple::PosixIfChangeNotifier; |
| |
| /// (Not available on iOS/macOS) A utility to monitor for interface changes |
| /// and report them, so you can handle events such as WiFi |
| /// disconnection/flight mode/route changes |
| pub struct IfChangeNotifier { |
| inner: InternalIfChangeNotifier, |
| last_ifs: HashSet<Interface>, |
| } |
| |
| impl IfChangeNotifier { |
| /// Create a new interface change notifier. Returns an OS specific error |
| /// if the network notifier could not be set up. |
| pub fn new() -> io::Result<Self> { |
| Ok(Self { |
| inner: InternalIfChangeNotifier::new()?, |
| last_ifs: HashSet::from_iter(super::get_if_addrs()?), |
| }) |
| } |
| |
| /// (Not available on iOS/macOS) Block until the OS reports that the |
| /// network interface list has changed, or until an optional timeout. |
| /// |
| /// For example, if an ethernet connector is plugged/unplugged, or a |
| /// WiFi network is connected to. |
| /// |
| /// The changed interfaces are returned. If an interface has both IPv4 |
| /// and IPv6 addresses, you can expect both of them to be returned from |
| /// a single call to `wait`. |
| /// |
| /// Returns an [`io::ErrorKind::WouldBlock`] error on timeout, or |
| /// another error if the network notifier could not be read from. |
| pub fn wait(&mut self, timeout: Option<Duration>) -> io::Result<Vec<IfChangeType>> { |
| let start = Instant::now(); |
| loop { |
| self.inner |
| .wait(timeout.map(|t| t.saturating_sub(start.elapsed())))?; |
| |
| // something has changed - now we find out what (or whether it was spurious) |
| let new_ifs = HashSet::from_iter(super::get_if_addrs()?); |
| let mut changes: Vec<IfChangeType> = new_ifs |
| .difference(&self.last_ifs) |
| .cloned() |
| .map(IfChangeType::Added) |
| .collect(); |
| changes.extend( |
| self.last_ifs |
| .difference(&new_ifs) |
| .cloned() |
| .map(IfChangeType::Removed), |
| ); |
| self.last_ifs = new_ifs; |
| |
| if !changes.is_empty() { |
| return Ok(changes); |
| } |
| } |
| } |
| } |
| } |
| |
| #[cfg(not(any( |
| all( |
| target_vendor = "apple", |
| any( |
| target_os = "macos", |
| target_os = "ios", |
| target_os = "tvos", |
| target_os = "watchos", |
| target_os = "visionos" |
| ) |
| ), |
| target_os = "freebsd", |
| target_os = "netbsd", |
| target_os = "openbsd", |
| target_os = "illumos" |
| )))] |
| #[cfg_attr( |
| docsrs, |
| doc(cfg(any( |
| not(target_vendor = "apple"), |
| not(target_os = "freebsd"), |
| not(target_os = "netbsd"), |
| not(target_os = "openbsd"), |
| not(target_os = "illumos") |
| ))) |
| )] |
| pub use if_change_notifier::{IfChangeNotifier, IfChangeType}; |
| |
| #[cfg(test)] |
| mod tests { |
| use super::{get_if_addrs, Interface}; |
| use std::io::Read; |
| use std::net::{IpAddr, Ipv4Addr}; |
| use std::process::{Command, Stdio}; |
| use std::str::FromStr; |
| use std::thread; |
| use std::time::Duration; |
| |
| fn list_system_interfaces(cmd: &str, args: &[&str]) -> String { |
| let start_cmd = if args.is_empty() { |
| Command::new(cmd).stdout(Stdio::piped()).spawn() |
| } else if args.len() == 1 { |
| let arg1 = args[0]; |
| if arg1.is_empty() { |
| Command::new(cmd).stdout(Stdio::piped()).spawn() |
| } else { |
| Command::new(cmd).arg(arg1).stdout(Stdio::piped()).spawn() |
| } |
| } else { |
| Command::new(cmd).args(args).stdout(Stdio::piped()).spawn() |
| }; |
| let mut process = match start_cmd { |
| Err(why) => { |
| println!("couldn't start cmd {} : {}", cmd, why); |
| return String::new(); |
| } |
| Ok(process) => process, |
| }; |
| thread::sleep(Duration::from_millis(1000)); |
| let _ = process.kill(); |
| let result: Vec<u8> = process |
| .stdout |
| .unwrap() |
| .bytes() |
| .map(|x| x.unwrap()) |
| .collect(); |
| String::from_utf8(result).unwrap() |
| } |
| |
| #[cfg(windows)] |
| /// Returns (IP-addr-list, IPv4-interface-status-list) |
| fn list_system_addrs() -> (Vec<IpAddr>, Vec<(String, bool)>) { |
| use std::net::Ipv6Addr; |
| let intf_list = list_system_interfaces("ipconfig", &[""]); |
| let ipaddr_list = intf_list |
| .lines() |
| .filter_map(|line| { |
| println!("{}", line); |
| if line.contains("Address") && !line.contains("Link-local") { |
| let addr_s: Vec<&str> = line.split(" : ").collect(); |
| if line.contains("IPv6") { |
| return Some(IpAddr::V6(Ipv6Addr::from_str(addr_s[1]).unwrap())); |
| } else if line.contains("IPv4") { |
| return Some(IpAddr::V4(Ipv4Addr::from_str(addr_s[1]).unwrap())); |
| } |
| } |
| None |
| }) |
| .collect(); |
| |
| /* An example on Windows: |
| > netsh interface ipv4 show interfaces |
| |
| Idx Met MTU State Name |
| --- ---------- ---------- ------------ --------------------------- |
| 1 75 4294967295 connected Loopback Pseudo-Interface 1 |
| 17 35 1500 connected Wi-Fi |
| 13 25 1500 disconnected Local Area Connection* 1 |
| 14 25 1500 disconnected Local Area Connection* 2 |
| 12 65 1500 disconnected Bluetooth Network Connection |
| */ |
| let netsh_list = |
| list_system_interfaces("netsh", &["interface", "ipv4", "show", "interfaces"]); |
| |
| let ipv4_status_vec: Vec<_> = netsh_list |
| .lines() |
| .filter_map(|line| { |
| if !line.contains("Idx") && !line.contains("---") && !line.is_empty() { |
| let columns: Vec<&str> = line.split_whitespace().collect(); |
| let status = columns[3].trim().to_string(); |
| let is_up = status == "connected"; |
| |
| // concat the strings in columns[4] and beyond |
| let intf_name = columns[4..].join(" "); |
| return Some((intf_name, is_up)); |
| } |
| None |
| }) |
| .collect(); |
| |
| (ipaddr_list, ipv4_status_vec) |
| } |
| |
| #[cfg(any(target_os = "linux", target_os = "android"))] |
| /// Returns (IP-addr-list, interface-status-list) |
| fn list_system_addrs() -> (Vec<IpAddr>, Vec<(String, bool)>) { |
| let intf_list = list_system_interfaces("ip", &["addr"]); |
| let ipaddr_list = intf_list |
| .lines() |
| .filter_map(|line| { |
| println!("{}", line); |
| if line.contains("inet ") { |
| let addr_s: Vec<&str> = line.split_whitespace().collect(); |
| let addr: Vec<&str> = addr_s[1].split('/').collect(); |
| return Some(IpAddr::V4(Ipv4Addr::from_str(addr[0]).unwrap())); |
| } |
| None |
| }) |
| .collect(); |
| let mut intf_status_vec = Vec::new(); |
| for line in intf_list.lines() { |
| if !line.starts_with(' ') && !line.is_empty() { |
| let name_s: Vec<&str> = line.split(':').collect(); |
| let is_up = !line.contains("state DOWN"); |
| intf_status_vec.push((name_s[1].trim().to_string(), is_up)); |
| } |
| } |
| |
| (ipaddr_list, intf_status_vec) |
| } |
| |
| #[cfg(any( |
| target_os = "freebsd", |
| target_os = "netbsd", |
| target_os = "openbsd", |
| target_os = "illumos", |
| all( |
| target_vendor = "apple", |
| any( |
| target_os = "macos", |
| target_os = "ios", |
| target_os = "tvos", |
| target_os = "watchos", |
| target_os = "visionos" |
| ) |
| ) |
| ))] |
| /// Returns (IP-addr-list, interface-status-list) |
| fn list_system_addrs() -> (Vec<IpAddr>, Vec<(String, bool)>) { |
| let intf_list = list_system_interfaces("ifconfig", &[""]); |
| let ipaddr_list = intf_list |
| .lines() |
| .filter_map(|line| { |
| println!("{}", line); |
| if line.contains("inet ") { |
| let addr_s: Vec<&str> = line.split_whitespace().collect(); |
| return Some(IpAddr::V4(Ipv4Addr::from_str(addr_s[1]).unwrap())); |
| } |
| None |
| }) |
| .collect(); |
| |
| let mut intf_status_vec = Vec::new(); |
| for line in intf_list.lines() { |
| // One example on macOS: |
| /* |
| en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 |
| options=6460<TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM> |
| ether c6:0e:5e:f8:5d:f4 |
| inet6 fe80::c02:ea58:92f4:be76%en0 prefixlen 64 secured scopeid 0xb |
| inet 192.168.0.112 netmask 0xffffff00 broadcast 192.168.0.255 |
| nd6 options=201<PERFORMNUD,DAD> |
| media: autoselect |
| status: active |
| */ |
| if !line.starts_with('\t') && !line.is_empty() { |
| let name_s: Vec<&str> = line.split(':').collect(); |
| let is_admin_up = line.contains("<UP"); |
| intf_status_vec.push((name_s[0].to_string(), is_admin_up)); |
| } else if line.contains("status: inactive") { |
| if let Some(current_intf) = intf_status_vec.last_mut() { |
| current_intf.1 = false; // overwrite the admin up |
| } |
| } |
| } |
| |
| (ipaddr_list, intf_status_vec) |
| } |
| |
| #[test] |
| fn test_get_if_addrs() { |
| let ifaces = get_if_addrs().unwrap(); |
| println!("Local interfaces:"); |
| println!("{:#?}", ifaces); |
| // at least one loop back address |
| assert!( |
| 1 <= ifaces |
| .iter() |
| .filter(|interface| interface.is_loopback()) |
| .count() |
| ); |
| // if index is set, it is non-zero |
| for interface in &ifaces { |
| if let Some(idx) = interface.index { |
| assert!(idx > 0); |
| } |
| } |
| |
| // one address of IpV4(127.0.0.1) |
| let is_loopback = |
| |interface: &&Interface| interface.addr.ip() == IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); |
| assert_eq!(1, ifaces.iter().filter(is_loopback).count()); |
| |
| // each system address shall be listed |
| let (system_addrs, intf_status_list) = list_system_addrs(); |
| assert!(!system_addrs.is_empty()); |
| for addr in system_addrs { |
| let mut listed = false; |
| println!("\n checking whether {:?} has been properly listed \n", addr); |
| for interface in &ifaces { |
| if interface.addr.ip() == addr { |
| listed = true; |
| } |
| |
| assert!(interface.index.is_some()); |
| } |
| assert!(listed); |
| } |
| |
| println!("Interface status list: {:#?}", intf_status_list); |
| for (intf_name, is_up) in intf_status_list { |
| for interface in &ifaces { |
| if interface.name == intf_name { |
| if interface.is_oper_up() != is_up { |
| println!( |
| "Interface {} status mismatch: listed {}, detected {:?}", |
| intf_name, is_up, interface.oper_status |
| ); |
| } |
| assert_eq!(interface.is_oper_up(), is_up); |
| } |
| } |
| } |
| } |
| |
| #[cfg(not(any( |
| all( |
| target_vendor = "apple", |
| any( |
| target_os = "macos", |
| target_os = "ios", |
| target_os = "tvos", |
| target_os = "watchos", |
| target_os = "visionos" |
| ) |
| ), |
| target_os = "freebsd", |
| target_os = "netbsd", |
| target_os = "openbsd", |
| target_os = "illumos" |
| )))] |
| #[test] |
| fn test_if_notifier() { |
| // Check that the interface notifier can start up and time out. No easy |
| // way to programmatically add/remove interfaces, so set a timeout of 0. |
| // Will cover a potential case of inadequate setup leading to an |
| // immediate change notification. |
| // |
| // There is a small race condition from creation -> check that an |
| // interface change *actually* occurs, so this test may spuriously fail |
| // extremely rarely. |
| |
| let notifier = crate::IfChangeNotifier::new(); |
| assert!(notifier.is_ok()); |
| let mut notifier = notifier.unwrap(); |
| |
| assert!(notifier.wait(Some(Duration::ZERO)).is_err()); |
| } |
| } |