diff --git a/src/interface/flags.rs b/src/interface/flags.rs index cc63d5c..0f9b83f 100644 --- a/src/interface/flags.rs +++ b/src/interface/flags.rs @@ -1,9 +1,12 @@ #[cfg(target_family = "unix")] pub use crate::os::unix::flags::*; -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(target_os = "linux")] pub use crate::os::linux::flags::*; +#[cfg(target_os = "android")] +pub use crate::os::android::flags::*; + #[cfg(target_vendor = "apple")] pub use crate::os::darwin::flags::*; diff --git a/src/interface/state.rs b/src/interface/state.rs index 366e9f8..7e14848 100644 --- a/src/interface/state.rs +++ b/src/interface/state.rs @@ -97,10 +97,14 @@ impl FromStr for OperState { } pub fn operstate(if_name: &str) -> OperState { - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(target_os = "linux")] { crate::os::linux::state::operstate(if_name) } + #[cfg(target_os = "android")] + { + crate::os::android::state::operstate(if_name) + } #[cfg(target_vendor = "apple")] { crate::os::darwin::state::operstate(if_name) diff --git a/src/os/android/flags.rs b/src/os/android/flags.rs new file mode 100644 index 0000000..dfb9d19 --- /dev/null +++ b/src/os/android/flags.rs @@ -0,0 +1,5 @@ +use crate::interface::interface::Interface; + +pub fn is_physical_interface(interface: &Interface) -> bool { + interface.is_up() && interface.is_running() && !interface.is_tun() && !interface.is_loopback() +} diff --git a/src/os/android/interface.rs b/src/os/android/interface.rs index bf91abc..4f0f131 100644 --- a/src/os/android/interface.rs +++ b/src/os/android/interface.rs @@ -3,20 +3,17 @@ use crate::interface::interface::Interface; use crate::interface::state::OperState; use crate::ipnet::{Ipv4Net, Ipv6Net}; use crate::net::mac::MacAddr; -use crate::os::linux::mtu; -use crate::os::linux::sysfs; -use crate::os::unix::interface::unix_interfaces; use std::net::{Ipv4Addr, Ipv6Addr}; #[cfg(feature = "gateway")] use crate::net::device::NetworkDevice; #[cfg(feature = "gateway")] -use crate::os::linux::procfs; -#[cfg(feature = "gateway")] use crate::os::unix::dns::get_system_dns_conf; #[cfg(feature = "gateway")] use std::collections::HashMap; +use crate::os::unix::interface::unix_interfaces; + fn push_ipv4(v: &mut Vec, add: (Ipv4Addr, u8)) { if v.iter() .any(|n| n.addr() == add.0 && n.prefix_len() == add.1) @@ -50,9 +47,8 @@ fn calc_v6_scope_id(addr: &Ipv6Addr, ifindex: u32) -> u32 { } pub fn interfaces() -> Vec { - let mut ifaces = Vec::new(); - // Fill ifaces via netlink first - // If netlink fails, fallback to unix_interfaces + let mut ifaces: Vec = Vec::new(); + match netlink::collect_interfaces() { Ok(rows) => { for r in rows { @@ -62,7 +58,7 @@ pub fn interfaces() -> Vec { name: name.clone(), friendly_name: None, description: None, - if_type: sysfs::get_interface_type(&name), + if_type: super::types::guess_type_by_name(&name).unwrap_or(r.if_type), mac_addr: r.mac.map(MacAddr::from_octets), ipv4: Vec::new(), ipv6: Vec::new(), @@ -71,7 +67,7 @@ pub fn interfaces() -> Vec { oper_state: OperState::from_if_flags(r.flags), transmit_speed: None, receive_speed: None, - stats: None, + stats: r.stats.clone(), #[cfg(feature = "gateway")] gateway: None, #[cfg(feature = "gateway")] @@ -93,27 +89,31 @@ pub fn interfaces() -> Vec { } } Err(_) => { - // Fallback: unix ifaddrs + // fallback: unix ifaddrs ifaces = unix_interfaces(); + + for iface in &mut ifaces { + if let Some(t) = super::types::guess_type_by_name(&iface.name) { + iface.if_type = t; + } + } } } - // Fill gateway info if feature enabled + // Fill gateway info #[cfg(feature = "gateway")] - match netlink::collect_routes() { - Ok(gmap) => { + { + if let Ok(gmap) = netlink::collect_routes() { let by_index: HashMap = gmap.iter().map(|(k, v)| (*k, v)).collect(); + for iface in &mut ifaces { if iface.index == 0 { continue; } if let Some(row) = by_index.get(&iface.index) { let dev = NetworkDevice { - mac_addr: row - .mac - .map(|m| MacAddr::from_octets(m)) - .unwrap_or(MacAddr::zero()), + mac_addr: row.mac.map(MacAddr::from_octets).unwrap_or(MacAddr::zero()), ipv4: row.gw_v4.clone(), ipv6: row.gw_v6.clone(), }; @@ -121,35 +121,7 @@ pub fn interfaces() -> Vec { } } } - Err(_) => { - // Fallback: procfs - let gateway_map: HashMap = procfs::get_gateway_map(); - for iface in &mut ifaces { - if let Some(gateway) = gateway_map.get(&iface.name) { - iface.gateway = Some(gateway.clone()); - } - } - } - } - - // Fill other info - for iface in &mut ifaces { - iface.if_type = sysfs::get_interface_type(&iface.name); - let if_speed = sysfs::get_interface_speed(&iface.name); - iface.transmit_speed = if_speed; - iface.receive_speed = if_speed; - iface.oper_state = sysfs::operstate(&iface.name); - - if iface.stats.is_none() { - iface.stats = crate::stats::counters::get_stats_from_name(&iface.name); - } - if iface.mtu.is_none() { - iface.mtu = mtu::get_mtu(&iface.name); - } - } - #[cfg(feature = "gateway")] - { if let Some(local_ip) = crate::net::ip::get_local_ipaddr() { if let Some(idx) = crate::interface::pick_default_iface_index(&ifaces, local_ip) { if let Some(iface) = ifaces.iter_mut().find(|it| it.index == idx) { @@ -159,5 +131,6 @@ pub fn interfaces() -> Vec { } } } + ifaces } diff --git a/src/os/android/mod.rs b/src/os/android/mod.rs index af1c94c..3f4855b 100644 --- a/src/os/android/mod.rs +++ b/src/os/android/mod.rs @@ -1,5 +1,8 @@ +pub mod flags; pub mod interface; pub mod netlink; +pub mod state; +pub mod types; use once_cell::sync::OnceCell; diff --git a/src/os/android/netlink.rs b/src/os/android/netlink.rs index 6cb5fd4..6d79d74 100644 --- a/src/os/android/netlink.rs +++ b/src/os/android/netlink.rs @@ -1,3 +1,5 @@ +use crate::interface::types::InterfaceType; +use crate::stats::counters::InterfaceStats; use netlink_packet_core::{NLM_F_DUMP, NLM_F_REQUEST, NetlinkMessage, NetlinkPayload}; use netlink_packet_route::{ RouteNetlinkMessage, @@ -7,6 +9,7 @@ use netlink_packet_route::{ use netlink_sys::{Socket, SocketAddr, protocols::NETLINK_ROUTE}; use std::io::ErrorKind; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::time::SystemTime; use std::{ collections::HashMap, io, thread, @@ -327,6 +330,54 @@ fn mtu_from_link(link: &LinkMessage) -> Option { None } +fn if_type_from_link(link: &LinkMessage, name: &str) -> InterfaceType { + let arphrd = link.header.link_layer_type as u32; + let mut t = InterfaceType::try_from(arphrd).unwrap_or(InterfaceType::UnknownWithValue(arphrd)); + + // override by name guess + // ARPHRD may be unreliable on some devices + if let Some(guess) = super::types::guess_type_by_name(name) { + t = guess; + } + + t +} + +fn stats_from_link(link: &LinkMessage) -> Option { + for nla in &link.attributes { + match nla { + LinkAttribute::Stats64(s) => { + return Some(InterfaceStats { + rx_bytes: s.rx_bytes, + tx_bytes: s.tx_bytes, + timestamp: Some(SystemTime::now()), + }); + } + LinkAttribute::Stats(s) => { + return Some(InterfaceStats { + rx_bytes: s.rx_bytes as u64, + tx_bytes: s.tx_bytes as u64, + timestamp: Some(SystemTime::now()), + }); + } + _ => {} + } + } + None +} + +pub fn get_flags_by_name(name: &str) -> io::Result> { + let links = dump_links()?; + for l in links { + if let Some(ifname) = name_from_link(&l) { + if ifname == name { + return Ok(Some(l.header.flags.bits())); + } + } + } + Ok(None) +} + #[derive(Debug, Clone)] pub struct IfRow { pub index: u32, @@ -336,6 +387,8 @@ pub struct IfRow { pub ipv6: Vec<(Ipv6Addr, u8)>, pub flags: u32, pub mtu: Option, + pub if_type: InterfaceType, + pub stats: Option, } pub fn collect_interfaces() -> io::Result> { @@ -349,6 +402,9 @@ pub fn collect_interfaces() -> io::Result> { let mac = mac_from_link(&l); let flags = l.header.flags.bits(); let mtu_nl = mtu_from_link(&l); + let if_type = if_type_from_link(&l, &name); + let stats = stats_from_link(&l); + base.insert( idx, IfRow { @@ -359,6 +415,8 @@ pub fn collect_interfaces() -> io::Result> { ipv6: vec![], flags, mtu: mtu_nl, + if_type, + stats, }, ); } diff --git a/src/os/android/state.rs b/src/os/android/state.rs new file mode 100644 index 0000000..b384143 --- /dev/null +++ b/src/os/android/state.rs @@ -0,0 +1,9 @@ +use crate::interface::state::OperState; + +pub fn operstate(if_name: &str) -> OperState { + match super::netlink::get_flags_by_name(if_name) { + Ok(Some(flags)) => OperState::from_if_flags(flags), + Ok(None) => OperState::Unknown, + Err(_) => OperState::Unknown, + } +} diff --git a/src/os/android/types.rs b/src/os/android/types.rs new file mode 100644 index 0000000..4ad2802 --- /dev/null +++ b/src/os/android/types.rs @@ -0,0 +1,39 @@ +use crate::interface::types::InterfaceType; + +pub fn guess_type_by_name(name: &str) -> Option { + let n = name.as_bytes(); + + // Loopback: lo + if n == b"lo" { + return Some(InterfaceType::Loopback); + } + // Wi-Fi: wlan0 / wlan1 / wifi0 + if n.starts_with(b"wlan") || n.starts_with(b"wifi") { + return Some(InterfaceType::Wireless80211); + } + // Cellular: rmnet_data0 / rmnet0 / ccmni0 / pdp0 + if n.starts_with(b"rmnet") || n.starts_with(b"ccmni") || n.starts_with(b"pdp") { + return Some(InterfaceType::Wwanpp); + } + // Wi-Fi Direct: p2p0 + if n.starts_with(b"p2p") { + return Some(InterfaceType::PeerToPeerWireless); + } + // Tunnel: tun0 / tap0 / ipsec0 / clat4 + if n.starts_with(b"tun") + || n.starts_with(b"tap") + || n.starts_with(b"ipsec") + || n.starts_with(b"clat") + { + return Some(InterfaceType::Tunnel); + } + // Bridge / veth + if n.starts_with(b"br-") || n.starts_with(b"bridge") { + return Some(InterfaceType::Bridge); + } + if n.starts_with(b"veth") { + return Some(InterfaceType::ProprietaryVirtual); + } + + None +} diff --git a/src/os/linux/mod.rs b/src/os/linux/mod.rs index e1b0499..2cf86ce 100644 --- a/src/os/linux/mod.rs +++ b/src/os/linux/mod.rs @@ -1,11 +1,15 @@ pub mod arp; +#[cfg(not(target_os = "android"))] pub mod flags; #[cfg(not(target_os = "android"))] pub mod interface; pub mod mtu; #[cfg(not(target_os = "android"))] pub mod netlink; +#[cfg(not(target_os = "android"))] #[cfg(feature = "gateway")] pub mod procfs; +#[cfg(not(target_os = "android"))] pub mod state; +#[cfg(not(target_os = "android"))] pub mod sysfs; diff --git a/src/os/unix/types.rs b/src/os/unix/types.rs index 8729910..8e0a40e 100644 --- a/src/os/unix/types.rs +++ b/src/os/unix/types.rs @@ -10,7 +10,14 @@ pub fn get_interface_type(addr_ref: &libc::ifaddrs) -> InterfaceType { let c_str = addr_ref.ifa_name as *const c_char; let bytes = unsafe { CStr::from_ptr(c_str).to_bytes() }; let name: String = unsafe { from_utf8_unchecked(bytes).to_owned() }; - crate::os::linux::sysfs::get_interface_type(&name) + #[cfg(target_os = "linux")] + { + crate::os::linux::sysfs::get_interface_type(&name) + } + #[cfg(target_os = "android")] + { + crate::os::android::types::guess_type_by_name(&name).unwrap_or(InterfaceType::Unknown) + } } #[cfg(target_vendor = "apple")]