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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ features = [
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
objc2-core-foundation = "0.3"
objc2-system-configuration = { version = "0.3", default-features = false, features = ["SCNetworkConfiguration"] }
plist = "1.8"
plist = "1.10"

[target.'cfg(target_os = "macos")'.dependencies]
objc2 = { version = "0.6", optional = true }
Expand Down
5 changes: 5 additions & 0 deletions src/interface/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ impl Interface {
/// Returns `true` when the interface appears to be backed by physical hardware.
pub fn is_physical(&self) -> bool {
use crate::net::db::oui;

if self.if_type.is_known_virtual() {
return false;
}

super::flags::is_physical_interface(&self)
&& !oui::is_virtual_mac(&self.mac_addr.unwrap_or(MacAddr::zero()))
&& !oui::is_known_loopback_mac(&self.mac_addr.unwrap_or(MacAddr::zero()))
Expand Down
77 changes: 77 additions & 0 deletions src/interface/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,52 @@ pub enum InterfaceType {
}

impl InterfaceType {
#[cfg(any(target_vendor = "apple", target_os = "android", test))]
pub(crate) fn should_replace_with(self, candidate: InterfaceType) -> bool {
if candidate == self {
return false;
}

match (self, candidate) {
(InterfaceType::Unknown, candidate) => candidate != InterfaceType::Unknown,
(InterfaceType::UnknownWithValue(_), candidate) => !matches!(
candidate,
InterfaceType::Unknown | InterfaceType::UnknownWithValue(_)
),
(InterfaceType::Ethernet, candidate) => {
matches!(
candidate,
InterfaceType::Loopback
| InterfaceType::Wireless80211
| InterfaceType::Tunnel
| InterfaceType::Wwan
| InterfaceType::Wwanpp
| InterfaceType::Wwanpp2
| InterfaceType::Bridge
| InterfaceType::PeerToPeerWireless
| InterfaceType::ProprietaryVirtual
)
}
(InterfaceType::Wwan, candidate) => {
matches!(candidate, InterfaceType::Wwanpp | InterfaceType::Wwanpp2)
}
_ => false,
}
}

pub(crate) fn is_known_virtual(self) -> bool {
matches!(
self,
InterfaceType::Loopback
| InterfaceType::Ppp
| InterfaceType::Slip
| InterfaceType::ProprietaryVirtual
| InterfaceType::Tunnel
| InterfaceType::Bridge
| InterfaceType::PeerToPeerWireless
)
}

/// Returns the native numeric type identifier for the current target platform.
///
/// For variants that have no direct mapping on the current platform, this method returns
Expand Down Expand Up @@ -268,3 +314,34 @@ impl TryFrom<u32> for InterfaceType {
}
}
}

#[cfg(test)]
mod tests {
use super::InterfaceType;

#[test]
fn replaces_ambiguous_types_with_more_specific_types() {
assert!(InterfaceType::Unknown.should_replace_with(InterfaceType::Ethernet));
assert!(InterfaceType::Ethernet.should_replace_with(InterfaceType::Wireless80211));
assert!(InterfaceType::Ethernet.should_replace_with(InterfaceType::Bridge));
assert!(InterfaceType::Wwan.should_replace_with(InterfaceType::Wwanpp));
}

#[test]
fn preserves_specific_types_from_generic_candidates() {
assert!(!InterfaceType::Bridge.should_replace_with(InterfaceType::Ethernet));
assert!(
!InterfaceType::PeerToPeerWireless.should_replace_with(InterfaceType::Wireless80211)
);
assert!(!InterfaceType::Tunnel.should_replace_with(InterfaceType::Unknown));
}

#[test]
fn identifies_known_virtual_types() {
assert!(InterfaceType::Bridge.is_known_virtual());
assert!(InterfaceType::Tunnel.is_known_virtual());
assert!(InterfaceType::PeerToPeerWireless.is_known_virtual());
assert!(!InterfaceType::Ethernet.is_known_virtual());
assert!(!InterfaceType::Wireless80211.is_known_virtual());
}
}
50 changes: 3 additions & 47 deletions src/os/android/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,38 +63,10 @@ fn type_is_ambiguous(if_type: InterfaceType) -> bool {
)
}

fn type_is_more_specific(current: InterfaceType, candidate: InterfaceType) -> bool {
if candidate == current {
return false;
}

match (current, candidate) {
(InterfaceType::Unknown, _) | (InterfaceType::UnknownWithValue(_), _) => true,
(InterfaceType::Ethernet, candidate) => {
matches!(
candidate,
InterfaceType::Loopback
| InterfaceType::Wireless80211
| InterfaceType::Tunnel
| InterfaceType::Wwan
| InterfaceType::Wwanpp
| InterfaceType::Wwanpp2
| InterfaceType::Bridge
| InterfaceType::PeerToPeerWireless
| InterfaceType::ProprietaryVirtual
)
}
(InterfaceType::Wwan, candidate) => {
matches!(candidate, InterfaceType::Wwanpp | InterfaceType::Wwanpp2)
}
_ => false,
}
}

#[cfg(feature = "android-extra")]
fn finalize_interface(iface: &mut Interface, extras: Option<&super::api::InterfaceExtras>) {
if let Some(sysfs_type) = super::sysfs::get_interface_type(&iface.name) {
if type_is_more_specific(iface.if_type, sysfs_type) {
if iface.if_type.should_replace_with(sysfs_type) {
iface.if_type = sysfs_type;
}
}
Expand Down Expand Up @@ -152,7 +124,7 @@ fn finalize_interface(iface: &mut Interface, extras: Option<&super::api::Interfa
#[cfg(not(feature = "android-extra"))]
fn finalize_interface(iface: &mut Interface) {
if let Some(sysfs_type) = super::sysfs::get_interface_type(&iface.name) {
if type_is_more_specific(iface.if_type, sysfs_type) {
if iface.if_type.should_replace_with(sysfs_type) {
iface.if_type = sysfs_type;
}
}
Expand Down Expand Up @@ -295,7 +267,7 @@ pub fn interfaces() -> Vec<Interface> {

#[cfg(test)]
mod tests {
use super::{calc_v6_scope_id, push_ipv4, push_ipv6, type_is_ambiguous, type_is_more_specific};
use super::{calc_v6_scope_id, push_ipv4, push_ipv6, type_is_ambiguous};
use crate::interface::ipv6_addr_flags::Ipv6AddrFlags;
use crate::interface::types::InterfaceType;
use crate::ipnet::{Ipv4Net, Ipv6Net};
Expand Down Expand Up @@ -371,22 +343,6 @@ mod tests {
assert!(addr_flags[1].permanent);
}

#[test]
fn prefers_more_specific_sysfs_types() {
assert!(type_is_more_specific(
InterfaceType::Ethernet,
InterfaceType::Wireless80211
));
assert!(type_is_more_specific(
InterfaceType::Wwan,
InterfaceType::Wwanpp
));
assert!(!type_is_more_specific(
InterfaceType::Tunnel,
InterfaceType::Wireless80211
));
}

#[test]
fn marks_ambiguous_types() {
assert!(type_is_ambiguous(InterfaceType::Unknown));
Expand Down
137 changes: 12 additions & 125 deletions src/os/android/netlink.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
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_core::NetlinkPayload;
use netlink_packet_route::{
RouteNetlinkMessage,
address::{AddressAttribute, AddressFlags, AddressMessage},
link::{LinkAttribute, LinkMessage},
};
use netlink_sys::{Socket, SocketAddr, protocols::NETLINK_ROUTE};
use std::io::ErrorKind;
use netlink_sys::{Socket, protocols::NETLINK_ROUTE};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::time::SystemTime;
use std::{
collections::HashMap,
io, thread,
time::{Duration, Instant},
};
use std::{collections::HashMap, io};

#[cfg(feature = "gateway")]
use netlink_packet_route::AddressFamily;
Expand All @@ -24,133 +19,25 @@ use netlink_packet_route::neighbour::{NeighbourAddress, NeighbourAttribute, Neig
use netlink_packet_route::route::{RouteAddress, RouteAttribute, RouteMessage};

const SEQ_BASE: u32 = 0x6E_64_65_76; // "ndev"
const RECV_BUFSZ: usize = 1 << 20; // 1MB
const RECV_TIMEOUT: Duration = Duration::from_secs(2);
const NLMSG_ALIGNTO: usize = 4;
const MIN_NLMSG_HEADER_LEN: usize = 16;

#[inline]
fn nlmsg_align(n: usize) -> usize {
(n + NLMSG_ALIGNTO - 1) & !(NLMSG_ALIGNTO - 1)
}

fn open_route_socket() -> io::Result<Socket> {
let sock = Socket::new(NETLINK_ROUTE)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("netlink open: {e}")))?;
// On Android 11+, bind is denied by SELinux
//sock.bind_auto().map_err(|e| io::Error::new(io::ErrorKind::Other, format!("bind_auto: {e}")))?;
sock.set_non_blocking(true).ok();
crate::os::linux::netlink_io::set_non_blocking(&sock)?;
Ok(sock)
}

fn send_dump(sock: &mut Socket, msg: RouteNetlinkMessage, seq: u32) -> io::Result<()> {
let mut nl = NetlinkMessage::from(msg);
nl.header.flags = NLM_F_REQUEST | NLM_F_DUMP;
nl.header.sequence_number = seq;
nl.header.port_number = 0;

// Finalize to set length
nl.finalize();

let blen = nl.buffer_len();
if blen < MIN_NLMSG_HEADER_LEN {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("netlink message too short: buffer_len={}", blen),
));
}

let mut buf = vec![0; blen];
nl.serialize(&mut buf);

let kernel = SocketAddr::new(0, 0);
sock.send_to(&buf, &kernel, 0)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("netlink send: {e}")))?;
Ok(())
}

fn recv_multi(
sock: &mut Socket,
expect_seq: u32,
) -> io::Result<Vec<NetlinkMessage<RouteNetlinkMessage>>> {
let mut out = Vec::new();
let mut buf = vec![0u8; RECV_BUFSZ];
let kernel = SocketAddr::new(0, 0);
let deadline = Instant::now() + RECV_TIMEOUT;

loop {
match sock.recv_from(&mut &mut buf[..], 0) {
Ok((size, from)) => {
let _ = from == kernel;
let mut offset = 0usize;

while offset < size {
if size - offset < MIN_NLMSG_HEADER_LEN {
break;
}

let bytes = &buf[offset..size];

let msg =
NetlinkMessage::<RouteNetlinkMessage>::deserialize(bytes).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("deserialize: {e:?}"),
)
})?;

let consumed = msg.header.length as usize;
if consumed < MIN_NLMSG_HEADER_LEN || offset + consumed > size {
break;
}

if msg.header.sequence_number != expect_seq {
offset += nlmsg_align(consumed);
continue;
}

match &msg.payload {
NetlinkPayload::Done(_) => {
return Ok(out);
}
NetlinkPayload::Error(e) => {
if let Some(code) = e.code {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("netlink error: code={}", code),
));
}
// code==None: possibly ACK ... ignore
}
NetlinkPayload::Noop | NetlinkPayload::Overrun(_) => { /* skip */ }
_ => out.push(msg),
}

// Align to 4-byte boundary
offset += nlmsg_align(consumed);
}
}
Err(e) if e.kind() == ErrorKind::WouldBlock => {
if Instant::now() >= deadline {
// timeout
return Ok(out);
}
thread::sleep(Duration::from_millis(5));
}
Err(e) => return Err(e),
}
}
}

pub fn dump_links() -> io::Result<Vec<LinkMessage>> {
let mut sock = open_route_socket()?;
let seq = SEQ_BASE ^ 0x01;
send_dump(
crate::os::linux::netlink_io::send_dump(
&mut sock,
RouteNetlinkMessage::GetLink(LinkMessage::default()),
seq,
)?;
let msgs = recv_multi(&mut sock, seq)?;
let msgs = crate::os::linux::netlink_io::recv_multi(&mut sock, seq)?;
let mut out = Vec::new();
for m in msgs {
if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewLink(link)) = m.payload {
Expand All @@ -163,12 +50,12 @@ pub fn dump_links() -> io::Result<Vec<LinkMessage>> {
pub fn dump_addrs() -> io::Result<Vec<AddressMessage>> {
let mut sock = open_route_socket()?;
let seq = SEQ_BASE ^ 0x02;
send_dump(
crate::os::linux::netlink_io::send_dump(
&mut sock,
RouteNetlinkMessage::GetAddress(AddressMessage::default()),
seq,
)?;
let msgs = recv_multi(&mut sock, seq)?;
let msgs = crate::os::linux::netlink_io::recv_multi(&mut sock, seq)?;
let mut out = Vec::new();
for m in msgs {
if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(addr)) = m.payload {
Expand All @@ -182,12 +69,12 @@ pub fn dump_addrs() -> io::Result<Vec<AddressMessage>> {
pub fn dump_routes() -> io::Result<Vec<RouteMessage>> {
let mut sock = open_route_socket()?;
let seq = SEQ_BASE ^ 0x03;
send_dump(
crate::os::linux::netlink_io::send_dump(
&mut sock,
RouteNetlinkMessage::GetRoute(RouteMessage::default()),
seq,
)?;
let msgs = recv_multi(&mut sock, seq)?;
let msgs = crate::os::linux::netlink_io::recv_multi(&mut sock, seq)?;
let mut out = Vec::new();
for m in msgs {
if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(rt)) = m.payload {
Expand All @@ -201,12 +88,12 @@ pub fn dump_routes() -> io::Result<Vec<RouteMessage>> {
pub fn dump_neigh() -> io::Result<Vec<NeighbourMessage>> {
let mut sock = open_route_socket()?;
let seq = SEQ_BASE ^ 0x04;
send_dump(
crate::os::linux::netlink_io::send_dump(
&mut sock,
RouteNetlinkMessage::GetNeighbour(NeighbourMessage::default()),
seq,
)?;
let msgs = recv_multi(&mut sock, seq)?;
let msgs = crate::os::linux::netlink_io::recv_multi(&mut sock, seq)?;
let mut out = Vec::new();
for m in msgs {
if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewNeighbour(n)) = m.payload {
Expand Down
Loading
Loading