From 2144168bb0ac78fedf52a9268223962746d0718a Mon Sep 17 00:00:00 2001 From: shellrow Date: Sat, 2 Aug 2025 18:22:38 +0900 Subject: [PATCH 01/10] Add support for interface operstate --- examples/default_interface.rs | 1 + src/interface/linux.rs | 10 ++++- src/interface/mod.rs | 11 ++++++ src/interface/state.rs | 63 ++++++++++++++++++++++++++++++ src/interface/unix.rs | 73 +++++++++++++++++++++++++++++++++++ src/interface/windows.rs | 54 +++++++++++++++++++++++++- src/sys/unix.rs | 8 ++++ 7 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/interface/state.rs diff --git a/examples/default_interface.rs b/examples/default_interface.rs index 2ff0ab2..6ccbf17 100644 --- a/examples/default_interface.rs +++ b/examples/default_interface.rs @@ -18,6 +18,7 @@ fn main() { println!("\t\tis TUN {}", interface.is_tun()); println!("\t\tis RUNNING {}", interface.is_running()); println!("\t\tis PHYSICAL {}", interface.is_physical()); + println!("\tOperational state: {:?}", interface.oper_state()); if let Some(mac_addr) = interface.mac_addr { println!("\tMAC Address: {}", mac_addr); } else { diff --git a/src/interface/linux.rs b/src/interface/linux.rs index 677713c..51853ac 100644 --- a/src/interface/linux.rs +++ b/src/interface/linux.rs @@ -1,4 +1,4 @@ -use crate::interface::InterfaceType; +use crate::interface::{InterfaceType, OperState}; use std::convert::TryFrom; use std::fs::{read_link, read_to_string}; @@ -71,3 +71,11 @@ pub fn get_interface_speed(if_name: &str) -> Option { } }; } + +pub fn operstate(if_name: &str) -> OperState { + let path = format!("/sys/class/net/{}/operstate", if_name); + match read_to_string(path) { + Ok(content) => content.trim().parse().unwrap_or(OperState::Unknown), + Err(_) => OperState::Unknown, + } +} diff --git a/src/interface/mod.rs b/src/interface/mod.rs index eca5a44..af8730a 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -6,6 +6,9 @@ pub use self::shared::*; mod types; pub use self::types::*; +mod state; +pub use self::state::*; + #[cfg(any( target_os = "linux", target_vendor = "apple", @@ -205,6 +208,14 @@ impl Interface { && !crate::db::oui::is_virtual_mac(&self.mac_addr.unwrap_or(MacAddr::zero())) && !crate::db::oui::is_known_loopback_mac(&self.mac_addr.unwrap_or(MacAddr::zero())) } + /// Get the operational state of the network interface + pub fn oper_state(&self) -> OperState { + operstate(&self.name) + } + /// Check if the operational state of the interface is up + pub fn is_oper_up(&self) -> bool { + self.oper_state() == OperState::Up + } /// Returns a list of IPv4 addresses assigned to this interface. pub fn ipv4_addrs(&self) -> Vec { self.ipv4.iter().map(|net| net.addr()).collect() diff --git a/src/interface/state.rs b/src/interface/state.rs new file mode 100644 index 0000000..9ee518b --- /dev/null +++ b/src/interface/state.rs @@ -0,0 +1,63 @@ +use std::fmt; +use std::str::FromStr; + +/// Operational state of a network interface. +/// +/// See also: +/// https://www.kernel.org/doc/Documentation/networking/operstates.txt +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum OperState { + /// Interface state is unknown + Unknown, + /// Interface is not present + NotPresent, + /// Interface is administratively or otherwise down + Down, + /// Interface is down because a lower layer is down + LowerLayerDown, + /// Interface is in testing state + Testing, + /// Interface is dormant + Dormant, + /// Interface is operational + Up, +} + +impl OperState { + /// Return lowercase string representation matching `/sys/class/net/*/operstate` + pub fn as_str(&self) -> &'static str { + match self { + OperState::Unknown => "unknown", + OperState::NotPresent => "notpresent", + OperState::Down => "down", + OperState::LowerLayerDown => "lowerlayerdown", + OperState::Testing => "testing", + OperState::Dormant => "dormant", + OperState::Up => "up", + } + } +} + +impl fmt::Display for OperState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl FromStr for OperState { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "unknown" => Ok(OperState::Unknown), + "notpresent" => Ok(OperState::NotPresent), + "down" => Ok(OperState::Down), + "lowerlayerdown" => Ok(OperState::LowerLayerDown), + "testing" => Ok(OperState::Testing), + "dormant" => Ok(OperState::Dormant), + "up" => Ok(OperState::Up), + _ => Err(()), + } + } +} diff --git a/src/interface/unix.rs b/src/interface/unix.rs index ac55dfe..bf27678 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -13,6 +13,9 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::os::raw::c_char; use std::str::from_utf8_unchecked; +#[cfg(not(any(target_os = "linux", target_os = "android")))] +use super::OperState; + #[cfg(feature = "gateway")] use std::net::ToSocketAddrs; #[cfg(feature = "gateway")] @@ -306,6 +309,76 @@ pub fn is_physical_interface(interface: &Interface) -> bool { || (!interface.is_loopback() && !linux::is_virtual_interface(&interface.name)) } +#[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd" +))] +pub fn get_interface_flags(if_name: &str) -> std::io::Result { + use libc::{c_char, ifreq, ioctl, socket, AF_INET, SOCK_DGRAM}; + use std::mem; + use std::os::unix::io::RawFd; + use std::ptr; + use sys::SIOCGIFFLAGS; + + let sock: RawFd = unsafe { socket(AF_INET, SOCK_DGRAM, 0) }; + if sock < 0 { + return Err(std::io::Error::last_os_error()); + } + + let mut ifr: ifreq = unsafe { mem::zeroed() }; + let ifname_c = std::ffi::CString::new(if_name).map_err(|_| std::io::ErrorKind::InvalidInput)?; + let bytes = ifname_c.as_bytes_with_nul(); + + if bytes.len() > ifr.ifr_name.len() { + unsafe { libc::close(sock) }; + return Err(std::io::ErrorKind::InvalidInput.into()); + } + + unsafe { + ptr::copy_nonoverlapping( + bytes.as_ptr() as *const c_char, + ifr.ifr_name.as_mut_ptr(), + bytes.len(), + ); + } + + let res = unsafe { ioctl(sock, SIOCGIFFLAGS, &mut ifr) }; + unsafe { libc::close(sock) }; + + if res < 0 { + Err(std::io::Error::last_os_error()) + } else { + #[cfg(target_vendor = "apple")] + { + Ok(unsafe { ifr.ifr_ifru.ifru_flags as u32 }) + } + + #[cfg(not(target_vendor = "apple"))] + { + Ok(unsafe { ifr.ifr_ifru.ifru_flags[0] as u32 }) + } + } +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub use super::linux::operstate; + +#[cfg(not(any(target_os = "linux", target_os = "android")))] +pub fn operstate(if_name: &str) -> OperState { + match get_interface_flags(if_name) { + Ok(flags) => { + if (flags & sys::IFF_UP as u32) != 0 && (flags & sys::IFF_RUNNING as u32) != 0 { + OperState::Up + } else { + OperState::Down + } + } + Err(_) => OperState::Unknown, + } +} + #[cfg(any( target_vendor = "apple", target_os = "openbsd", diff --git a/src/interface/windows.rs b/src/interface/windows.rs index a8d9cad..42aac7e 100644 --- a/src/interface/windows.rs +++ b/src/interface/windows.rs @@ -11,7 +11,7 @@ use windows_sys::Win32::Networking::WinSock::{ }; use crate::device::NetworkDevice; -use crate::interface::{Interface, InterfaceType}; +use crate::interface::{Interface, InterfaceType, OperState}; use crate::ipnet::{Ipv4Net, Ipv6Net}; use crate::mac::MacAddr; use crate::stats::InterfaceStats; @@ -75,6 +75,58 @@ pub fn is_running(interface: &Interface) -> bool { interface.is_up() } +/// Return the operational state of a given Windows interface by its adapter name (GUID string) +pub fn operstate(if_name: &str) -> OperState { + let mut mem = Vec::::with_capacity(15000); + let mut retries = 3; + loop { + let mut dwsize = mem.capacity() as u32; + let ret = unsafe { + GetAdaptersAddresses( + AF_UNSPEC as u32, + GAA_FLAG_INCLUDE_GATEWAYS, + std::ptr::null_mut(), + mem.as_mut_ptr().cast(), + &mut dwsize, + ) + }; + match ret { + 0 => { + unsafe { + mem.set_len(dwsize as usize); + } + break; + } + 111 if retries > 0 => { + mem.reserve(dwsize as usize); + retries -= 1; + } + _ => return OperState::Unknown, + } + } + + let ptr = mem.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH; + for cur in unsafe { crate::sys::linked_list_iter!(&ptr) } { + let adapter_name = unsafe { + CStr::from_ptr(cur.AdapterName.cast()).to_string_lossy().to_string() + }; + if adapter_name == if_name { + return match cur.OperStatus { + 1 => OperState::Up, + 2 => OperState::Down, + 3 => OperState::Testing, + 4 => OperState::Unknown, + 5 => OperState::Dormant, + 6 => OperState::NotPresent, + 7 => OperState::LowerLayerDown, + _ => OperState::Unknown, + }; + } + } + + OperState::Unknown +} + /// Check if a network interface has a connector present, indicating it is a physical interface. fn is_connector_present(if_index: u32) -> bool { // Initialize MIB_IF_ROW2 diff --git a/src/sys/unix.rs b/src/sys/unix.rs index bb30709..75f8d34 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -12,6 +12,14 @@ pub const AF_INET6: libc::c_int = libc::AF_INET6; pub use libc::{IFF_BROADCAST, IFF_LOOPBACK, IFF_MULTICAST, IFF_POINTOPOINT, IFF_RUNNING, IFF_UP}; +#[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd" +))] +pub const SIOCGIFFLAGS: libc::c_ulong = 0xc0206911; + fn ntohs(u: u16) -> u16 { u16::from_be(u) } From e6e8db639b71a17b8267c8eecf91fd5b168d629a Mon Sep 17 00:00:00 2001 From: shellrow <81893184+shellrow@users.noreply.github.com> Date: Sat, 2 Aug 2025 18:34:36 +0900 Subject: [PATCH 02/10] Update windows.rs --- src/interface/windows.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/interface/windows.rs b/src/interface/windows.rs index 42aac7e..dcddc02 100644 --- a/src/interface/windows.rs +++ b/src/interface/windows.rs @@ -28,6 +28,25 @@ const IFF_CONNECTOR_PRESENT: u8 = 0b0000_0100; //const IFF_LOW_POWER: u8 = 0b0100_0000; //const IFF_END_POINT_INTERFACE: u8 = 0b1000_0000; +// Note: We take `&*mut T` instead of just `*mut T` to tie the lifetime of all the returned items +// to the lifetime of the pointer for some extra safety. +unsafe fn linked_list_iter(ptr: &*mut T, next: fn(&T) -> *mut T) -> impl Iterator { + let mut ptr = ptr.cast_const(); + + std::iter::from_fn(move || { + let cur = ptr.as_ref()?; + ptr = next(cur); + Some(cur) + }) +} + +// The `Next` element is always the same, so use a macro to avoid the repetition. +macro_rules! linked_list_iter { + ($ptr:expr) => { + linked_list_iter($ptr, |cur| cur.Next) + }; +} + fn get_mac_through_arp(src_ip: Ipv4Addr, dst_ip: Ipv4Addr) -> MacAddr { let src_ip_int = u32::from_ne_bytes(src_ip.octets()); let dst_ip_int = u32::from_ne_bytes(dst_ip.octets()); @@ -106,7 +125,7 @@ pub fn operstate(if_name: &str) -> OperState { } let ptr = mem.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH; - for cur in unsafe { crate::sys::linked_list_iter!(&ptr) } { + for cur in unsafe { linked_list_iter!(&ptr) } { let adapter_name = unsafe { CStr::from_ptr(cur.AdapterName.cast()).to_string_lossy().to_string() }; @@ -160,25 +179,6 @@ unsafe fn from_wide_string(ptr: *const u16) -> String { String::from_utf16_lossy(std::slice::from_raw_parts(ptr, len)) } -// Note: We take `&*mut T` instead of just `*mut T` to tie the lifetime of all the returned items -// to the lifetime of the pointer for some extra safety. -unsafe fn linked_list_iter(ptr: &*mut T, next: fn(&T) -> *mut T) -> impl Iterator { - let mut ptr = ptr.cast_const(); - - std::iter::from_fn(move || { - let cur = ptr.as_ref()?; - ptr = next(cur); - Some(cur) - }) -} - -// The `Next` element is always the same, so use a macro to avoid the repetition. -macro_rules! linked_list_iter { - ($ptr:expr) => { - linked_list_iter($ptr, |cur| cur.Next) - }; -} - // Get network interfaces using the IP Helper API // Reference: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses pub fn interfaces() -> Vec { From 8e8de9548879a66dafeac73e134ced5359777d7a Mon Sep 17 00:00:00 2001 From: shellrow <81893184+shellrow@users.noreply.github.com> Date: Sat, 2 Aug 2025 18:34:53 +0900 Subject: [PATCH 03/10] serde support --- src/interface/state.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/interface/state.rs b/src/interface/state.rs index 9ee518b..efb48fb 100644 --- a/src/interface/state.rs +++ b/src/interface/state.rs @@ -1,6 +1,9 @@ use std::fmt; use std::str::FromStr; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// Operational state of a network interface. /// /// See also: From 63ef790a48ecfe2f171079239630defaa6b23c0a Mon Sep 17 00:00:00 2001 From: shellrow Date: Sat, 2 Aug 2025 18:51:09 +0900 Subject: [PATCH 04/10] Format code with cargo fmt --- src/interface/state.rs | 2 +- src/interface/windows.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/interface/state.rs b/src/interface/state.rs index efb48fb..cb14b1f 100644 --- a/src/interface/state.rs +++ b/src/interface/state.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; /// Operational state of a network interface. -/// +/// /// See also: /// https://www.kernel.org/doc/Documentation/networking/operstates.txt #[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] diff --git a/src/interface/windows.rs b/src/interface/windows.rs index dcddc02..f31c61d 100644 --- a/src/interface/windows.rs +++ b/src/interface/windows.rs @@ -127,7 +127,9 @@ pub fn operstate(if_name: &str) -> OperState { let ptr = mem.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH; for cur in unsafe { linked_list_iter!(&ptr) } { let adapter_name = unsafe { - CStr::from_ptr(cur.AdapterName.cast()).to_string_lossy().to_string() + CStr::from_ptr(cur.AdapterName.cast()) + .to_string_lossy() + .to_string() }; if adapter_name == if_name { return match cur.OperStatus { From 381cd5c680bc493343c2f63dfac7df4ab39c9f49 Mon Sep 17 00:00:00 2001 From: shellrow Date: Sun, 3 Aug 2025 12:19:53 +0900 Subject: [PATCH 05/10] Add oper_state field to Interface --- examples/default_interface.rs | 2 +- examples/list_interfaces.rs | 1 + src/interface/android.rs | 3 ++- src/interface/mod.rs | 11 +++++++++-- src/interface/state.rs | 14 ++++++++++++++ src/interface/unix.rs | 15 +++++++-------- src/interface/windows.rs | 13 +++++++++++++ 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/examples/default_interface.rs b/examples/default_interface.rs index 6ccbf17..994ecc6 100644 --- a/examples/default_interface.rs +++ b/examples/default_interface.rs @@ -18,7 +18,7 @@ fn main() { println!("\t\tis TUN {}", interface.is_tun()); println!("\t\tis RUNNING {}", interface.is_running()); println!("\t\tis PHYSICAL {}", interface.is_physical()); - println!("\tOperational state: {:?}", interface.oper_state()); + println!("\tOperational state: {:?}", interface.oper_state); if let Some(mac_addr) = interface.mac_addr { println!("\tMAC Address: {}", mac_addr); } else { diff --git a/examples/list_interfaces.rs b/examples/list_interfaces.rs index 57a18b4..6631188 100644 --- a/examples/list_interfaces.rs +++ b/examples/list_interfaces.rs @@ -18,6 +18,7 @@ fn main() { println!("\t\tis TUN {}", interface.is_tun()); println!("\t\tis RUNNING {}", interface.is_running()); println!("\t\tis PHYSICAL {}", interface.is_physical()); + println!("\tOperational state: {:?}", interface.oper_state); if let Some(mac_addr) = interface.mac_addr { println!("\tMAC Address: {}", mac_addr); } else { diff --git a/src/interface/android.rs b/src/interface/android.rs index c91965b..2a168f6 100644 --- a/src/interface/android.rs +++ b/src/interface/android.rs @@ -62,7 +62,7 @@ pub mod netlink { }; use crate::{ - interface::{Interface, InterfaceType, Ipv4Net, Ipv6Net}, + interface::{Interface, InterfaceType, OperState, Ipv4Net, Ipv6Net}, mac::MacAddr, }; @@ -109,6 +109,7 @@ pub mod netlink { ipv6: Vec::new(), ipv6_scope_ids: Vec::new(), flags: link_msg.header.flags.bits(), + oper_state: OperState::from_if_flags(link_msg.header.flags.bits()), transmit_speed: None, receive_speed: None, stats: None, diff --git a/src/interface/mod.rs b/src/interface/mod.rs index af8730a..d5e0ce6 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -88,6 +88,8 @@ pub struct Interface { pub ipv6_scope_ids: Vec, /// Flags for the network interface (OS Specific) pub flags: u32, + /// Operational state at the time of interface discovery + pub oper_state: OperState, /// Speed in bits per second of the transmit for the network interface, if known. /// Currently only supported on Linux, Android, and Windows. pub transmit_speed: Option, @@ -162,6 +164,7 @@ impl Interface { ipv6: Vec::new(), ipv6_scope_ids: Vec::new(), flags: 0, + oper_state: OperState::Unknown, transmit_speed: None, receive_speed: None, stats: None, @@ -210,11 +213,15 @@ impl Interface { } /// Get the operational state of the network interface pub fn oper_state(&self) -> OperState { - operstate(&self.name) + self.oper_state } /// Check if the operational state of the interface is up pub fn is_oper_up(&self) -> bool { - self.oper_state() == OperState::Up + self.oper_state == OperState::Up + } + /// Update the `oper_state` field by re-reading the current operstate from the system + pub fn update_oper_state(&mut self) { + self.oper_state = operstate(&self.name); } /// Returns a list of IPv4 addresses assigned to this interface. pub fn ipv4_addrs(&self) -> Vec { diff --git a/src/interface/state.rs b/src/interface/state.rs index cb14b1f..f8b872c 100644 --- a/src/interface/state.rs +++ b/src/interface/state.rs @@ -1,5 +1,6 @@ use std::fmt; use std::str::FromStr; +use crate::sys; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -40,6 +41,19 @@ impl OperState { OperState::Up => "up", } } + pub fn from_if_flags(if_flags: u32) -> Self { + // Determine the operational state based on interface flags. + // This is a fallback for when the operstate not available. + if if_flags & sys::IFF_UP as u32 != 0 { + if if_flags & sys::IFF_RUNNING as u32 != 0 { + OperState::Up + } else { + OperState::Dormant + } + } else { + OperState::Down + } + } } impl fmt::Display for OperState { diff --git a/src/interface/unix.rs b/src/interface/unix.rs index bf27678..9071b71 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -1,7 +1,10 @@ use super::Interface; use super::MacAddr; +use super::OperState; + #[cfg(feature = "gateway")] use crate::gateway; + use crate::interface::InterfaceType; use crate::ipnet::{Ipv4Net, Ipv6Net}; use crate::stats::{get_stats, InterfaceStats}; @@ -13,9 +16,6 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::os::raw::c_char; use std::str::from_utf8_unchecked; -#[cfg(not(any(target_os = "linux", target_os = "android")))] -use super::OperState; - #[cfg(feature = "gateway")] use std::net::ToSocketAddrs; #[cfg(feature = "gateway")] @@ -123,6 +123,8 @@ pub fn interfaces() -> Vec { iface.transmit_speed = if_speed; iface.receive_speed = if_speed; + iface.oper_state = linux::operstate(&iface.name); + #[cfg(feature = "gateway")] if let Some(gateway) = gateway_map.get(&iface.name) { iface.gateway = Some(gateway.clone()); @@ -369,11 +371,7 @@ pub use super::linux::operstate; pub fn operstate(if_name: &str) -> OperState { match get_interface_flags(if_name) { Ok(flags) => { - if (flags & sys::IFF_UP as u32) != 0 && (flags & sys::IFF_RUNNING as u32) != 0 { - OperState::Up - } else { - OperState::Down - } + OperState::from_if_flags(flags) } Err(_) => OperState::Unknown, } @@ -571,6 +569,7 @@ fn unix_interfaces_inner( None => vec![], }, flags: addr_ref.ifa_flags, + oper_state: OperState::from_if_flags(addr_ref.ifa_flags), transmit_speed: None, receive_speed: None, stats: stats, diff --git a/src/interface/windows.rs b/src/interface/windows.rs index f31c61d..bac2773 100644 --- a/src/interface/windows.rs +++ b/src/interface/windows.rs @@ -254,6 +254,18 @@ pub fn interfaces() -> Vec { } _ => {} } + + let oper_state: OperState = match cur.OperStatus { + 1 => OperState::Up, + 2 => OperState::Down, + 3 => OperState::Testing, + 4 => OperState::Unknown, + 5 => OperState::Dormant, + 6 => OperState::NotPresent, + 7 => OperState::LowerLayerDown, + _ => OperState::Unknown, + }; + // Name let adapter_name = unsafe { CStr::from_ptr(cur.AdapterName.cast()) } .to_string_lossy() @@ -332,6 +344,7 @@ pub fn interfaces() -> Vec { ipv6: ipv6_vec, ipv6_scope_ids: ipv6_scope_id_vec, flags, + oper_state, transmit_speed: sys::sanitize_u64(cur.TransmitLinkSpeed), receive_speed: sys::sanitize_u64(cur.ReceiveLinkSpeed), stats, From 85e2ac6e9ed75fedf721e4ccfc4d1793a03a324a Mon Sep 17 00:00:00 2001 From: shellrow <81893184+shellrow@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:36:51 +0900 Subject: [PATCH 06/10] Windows support: add OperState fallback --- src/interface/state.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/interface/state.rs b/src/interface/state.rs index f8b872c..faf1161 100644 --- a/src/interface/state.rs +++ b/src/interface/state.rs @@ -41,17 +41,36 @@ impl OperState { OperState::Up => "up", } } + + /// Determine the operational state based on interface flags. + /// + /// This is primarily a fallback mechanism for platforms where + /// `/sys/class/net/*/operstate` or native operstate APIs are not available. + /// + /// On Windows, this method is **not used** in practice, as the `OperState` is + /// obtained through native API calls. pub fn from_if_flags(if_flags: u32) -> Self { - // Determine the operational state based on interface flags. - // This is a fallback for when the operstate not available. - if if_flags & sys::IFF_UP as u32 != 0 { - if if_flags & sys::IFF_RUNNING as u32 != 0 { + + #[cfg(not(target_os = "windows"))] + { + if if_flags & sys::IFF_UP as u32 != 0 { + if if_flags & sys::IFF_RUNNING as u32 != 0 { + OperState::Up + } else { + OperState::Dormant + } + } else { + OperState::Down + } + } + + #[cfg(target_os = "windows")] + { + if if_flags & sys::IFF_UP as u32 != 0 { OperState::Up } else { - OperState::Dormant + OperState::Down } - } else { - OperState::Down } } } From 5fce91460476eadc4866fa583982dfac570aad40 Mon Sep 17 00:00:00 2001 From: shellrow Date: Sun, 3 Aug 2025 12:44:09 +0900 Subject: [PATCH 07/10] Format code with cargo fmt --- src/interface/android.rs | 2 +- src/interface/state.rs | 7 +++---- src/interface/unix.rs | 4 +--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/interface/android.rs b/src/interface/android.rs index 2a168f6..ea592c1 100644 --- a/src/interface/android.rs +++ b/src/interface/android.rs @@ -62,7 +62,7 @@ pub mod netlink { }; use crate::{ - interface::{Interface, InterfaceType, OperState, Ipv4Net, Ipv6Net}, + interface::{Interface, InterfaceType, Ipv4Net, Ipv6Net, OperState}, mac::MacAddr, }; diff --git a/src/interface/state.rs b/src/interface/state.rs index faf1161..cc73fad 100644 --- a/src/interface/state.rs +++ b/src/interface/state.rs @@ -1,6 +1,6 @@ +use crate::sys; use std::fmt; use std::str::FromStr; -use crate::sys; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -50,8 +50,7 @@ impl OperState { /// On Windows, this method is **not used** in practice, as the `OperState` is /// obtained through native API calls. pub fn from_if_flags(if_flags: u32) -> Self { - - #[cfg(not(target_os = "windows"))] + #[cfg(not(target_os = "windows"))] { if if_flags & sys::IFF_UP as u32 != 0 { if if_flags & sys::IFF_RUNNING as u32 != 0 { @@ -64,7 +63,7 @@ impl OperState { } } - #[cfg(target_os = "windows")] + #[cfg(target_os = "windows")] { if if_flags & sys::IFF_UP as u32 != 0 { OperState::Up diff --git a/src/interface/unix.rs b/src/interface/unix.rs index 9071b71..f247bab 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -370,9 +370,7 @@ pub use super::linux::operstate; #[cfg(not(any(target_os = "linux", target_os = "android")))] pub fn operstate(if_name: &str) -> OperState { match get_interface_flags(if_name) { - Ok(flags) => { - OperState::from_if_flags(flags) - } + Ok(flags) => OperState::from_if_flags(flags), Err(_) => OperState::Unknown, } } From 0839a4a5749195d33937c07aa040e7e6d47050de Mon Sep 17 00:00:00 2001 From: shellrow Date: Sun, 3 Aug 2025 13:18:27 +0900 Subject: [PATCH 08/10] fix: netbsd --- src/interface/unix.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/interface/unix.rs b/src/interface/unix.rs index f247bab..af1f27d 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -318,18 +318,30 @@ pub fn is_physical_interface(interface: &Interface) -> bool { target_os = "netbsd" ))] pub fn get_interface_flags(if_name: &str) -> std::io::Result { - use libc::{c_char, ifreq, ioctl, socket, AF_INET, SOCK_DGRAM}; + use libc::{c_char, ioctl, socket, AF_INET, SOCK_DGRAM}; use std::mem; use std::os::unix::io::RawFd; use std::ptr; use sys::SIOCGIFFLAGS; + #[cfg(target_os = "netbsd")] + #[repr(C)] + #[derive(Copy, Clone)] + struct IfReq { + ifr_name: [c_char; libc::IFNAMSIZ], + ifru_flags: [libc::c_short; 2], + } + + #[cfg(not(target_os = "netbsd"))] + use libc::ifreq as IfReq; + let sock: RawFd = unsafe { socket(AF_INET, SOCK_DGRAM, 0) }; if sock < 0 { return Err(std::io::Error::last_os_error()); } - let mut ifr: ifreq = unsafe { mem::zeroed() }; + let mut ifr: IfReq = unsafe { mem::zeroed() }; + let ifname_c = std::ffi::CString::new(if_name).map_err(|_| std::io::ErrorKind::InvalidInput)?; let bytes = ifname_c.as_bytes_with_nul(); From 7f365b706f82c16d17479e27e77f6d3a45aee0c3 Mon Sep 17 00:00:00 2001 From: shellrow Date: Sun, 3 Aug 2025 13:25:57 +0900 Subject: [PATCH 09/10] fix: netbsd --- src/interface/unix.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/interface/unix.rs b/src/interface/unix.rs index af1f27d..c85e31a 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -369,7 +369,15 @@ pub fn get_interface_flags(if_name: &str) -> std::io::Result { Ok(unsafe { ifr.ifr_ifru.ifru_flags as u32 }) } - #[cfg(not(target_vendor = "apple"))] + #[cfg(target_os = "netbsd")] + { + Ok(unsafe { ifr.ifru_flags[0] as u32 }) + } + + #[cfg(all( + not(target_vendor = "apple"), + not(target_os = "netbsd") + ))] { Ok(unsafe { ifr.ifr_ifru.ifru_flags[0] as u32 }) } From 847b5141f5f49121afb959861c8239d5936d3a95 Mon Sep 17 00:00:00 2001 From: shellrow Date: Sun, 3 Aug 2025 13:26:54 +0900 Subject: [PATCH 10/10] Format code with cargo fmt --- src/interface/unix.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/interface/unix.rs b/src/interface/unix.rs index c85e31a..8e49c0e 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -374,10 +374,7 @@ pub fn get_interface_flags(if_name: &str) -> std::io::Result { Ok(unsafe { ifr.ifru_flags[0] as u32 }) } - #[cfg(all( - not(target_vendor = "apple"), - not(target_os = "netbsd") - ))] + #[cfg(all(not(target_vendor = "apple"), not(target_os = "netbsd")))] { Ok(unsafe { ifr.ifr_ifru.ifru_flags[0] as u32 }) }