Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/default_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions examples/list_interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion src/interface/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub mod netlink {
};

use crate::{
interface::{Interface, InterfaceType, Ipv4Net, Ipv6Net},
interface::{Interface, InterfaceType, Ipv4Net, Ipv6Net, OperState},
mac::MacAddr,
};

Expand Down Expand Up @@ -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,
Expand Down
10 changes: 9 additions & 1 deletion src/interface/linux.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -71,3 +71,11 @@ pub fn get_interface_speed(if_name: &str) -> Option<u64> {
}
};
}

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,
}
}
18 changes: 18 additions & 0 deletions src/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -85,6 +88,8 @@ pub struct Interface {
pub ipv6_scope_ids: Vec<u32>,
/// 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<u64>,
Expand Down Expand Up @@ -159,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,
Expand Down Expand Up @@ -205,6 +211,18 @@ 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 {
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
}
/// 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<Ipv4Addr> {
self.ipv4.iter().map(|net| net.addr()).collect()
Expand Down
98 changes: 98 additions & 0 deletions src/interface/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::sys;
use std::fmt;
use std::str::FromStr;

#[cfg(feature = "serde")]
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)]
#[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",
}
}

/// 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 {
#[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::Down
}
}
}
}

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<Self, Self::Err> {
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(()),
}
}
}
87 changes: 87 additions & 0 deletions src/interface/unix.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -120,6 +123,8 @@ pub fn interfaces() -> Vec<Interface> {
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());
Expand Down Expand Up @@ -306,6 +311,87 @@ 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<u32> {
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 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(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 })
}
}
}

#[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) => OperState::from_if_flags(flags),
Err(_) => OperState::Unknown,
}
}

#[cfg(any(
target_vendor = "apple",
target_os = "openbsd",
Expand Down Expand Up @@ -498,6 +584,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,
Expand Down
Loading
Loading