diff --git a/Cargo.toml b/Cargo.toml index ff1dcda..13f4421 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,13 @@ license = "MIT" libc = "0.2" [target.'cfg(target_os = "android")'.dependencies] +# DL Open dlopen = "0.1.8" once_cell = "1.17.1" +# netlink +netlink-packet-core = "0.5" +netlink-packet-route = "0.15" +netlink-sys = "0.8" [target.'cfg(windows)'.dependencies] memalloc = "0.1.0" diff --git a/src/interface/android.rs b/src/interface/android.rs index b4d6770..0798384 100644 --- a/src/interface/android.rs +++ b/src/interface/android.rs @@ -1,5 +1,15 @@ use once_cell::sync::OnceCell; +pub fn get_libc_ifaddrs() -> Option<( + unsafe extern "C" fn(*mut *mut libc::ifaddrs) -> libc::c_int, + unsafe extern "C" fn(*mut libc::ifaddrs), +)> { + match (get_getifaddrs(), get_freeifaddrs()) { + (Some(a), Some(b)) => Some((a, b)), + _ => None, + } +} + fn load_symbol(sym: &'static str) -> Option { const LIB_NAME: &str = "libc.so"; @@ -33,32 +43,191 @@ fn get_freeifaddrs() -> Option { *INSTANCE.get_or_init(|| load_symbol("freeifaddrs")) } -pub unsafe fn getifaddrs(ifap: *mut *mut libc::ifaddrs) -> libc::c_int { - // Android is complicated +mod netlink { + //! Netlink based getifaddrs. + //! + //! Based on the logic found in https://git.musl-libc.org/cgit/musl/tree/src/network/getifaddrs.c - // API 24+ contains the getifaddrs and freeifaddrs functions but the NDK doesn't - // expose those functions in ifaddrs.h when the minimum supported SDK is lower than 24 - // and therefore we need to load them manually. - if let Some(dyn_getifaddrs) = get_getifaddrs() { - return dyn_getifaddrs(ifap); - } + use netlink_packet_core::{ + NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, + }; + use netlink_packet_route::{ + rtnl::address::nlas::Nla as AddressNla, rtnl::link::nlas::Nla as LinkNla, AddressMessage, + LinkMessage, RtnlMessage, + }; + use netlink_sys::{protocols::NETLINK_ROUTE, Socket}; + use std::net::{Ipv4Addr, Ipv6Addr}; - // If API < 24 (or we can't load libc for some other reason), we fallback to using netlink - netlink_getifaddrs(ifap) -} + use crate::interface::{Interface, InterfaceType, Ipv4Net, Ipv6Net, MacAddr}; + + pub fn unix_interfaces() -> Vec { + let socket = Socket::new(NETLINK_ROUTE).unwrap(); -pub unsafe fn freeifaddrs(ifa: *mut libc::ifaddrs) { - if let Some(dyn_freeifaddrs) = get_freeifaddrs() { - return dyn_freeifaddrs(ifa); + let mut ifaces = Vec::new(); + enumerate_netlink( + &socket, + RtnlMessage::GetLink(LinkMessage::default()), + &mut ifaces, + ); + enumerate_netlink( + &socket, + RtnlMessage::GetAddress(AddressMessage::default()), + &mut ifaces, + ); + + ifaces } - netlink_freeifaddrs(ifa) -} + fn enumerate_netlink(socket: &Socket, msg: RtnlMessage, ifaces: &mut Vec) { + let mut packet = NetlinkMessage::new(NetlinkHeader::default(), NetlinkPayload::from(msg)); + packet.header.flags = NLM_F_DUMP | NLM_F_REQUEST; + packet.header.sequence_number = 1; + packet.finalize(); -unsafe fn netlink_getifaddrs(ifap: *mut *mut libc::ifaddrs) -> libc::c_int { - todo!() -} + let mut buf = vec![0; packet.header.length as usize]; + + // TODO: gracefully handle error + assert!(buf.len() == packet.buffer_len()); + packet.serialize(&mut buf[..]); + + socket.send(&buf[..], 0).unwrap(); -unsafe fn netlink_freeifaddrs(ifa: *mut libc::ifaddrs) { - todo!() + let mut receive_buffer = vec![0; 4096]; + let mut offset = 0; + + loop { + let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); + + loop { + let bytes = &receive_buffer[offset..]; + let rx_packet: NetlinkMessage = + NetlinkMessage::deserialize(bytes).unwrap(); + + match rx_packet.payload { + NetlinkPayload::Done => { + return; + } + NetlinkPayload::Error(err) => { + eprintln!("Error: {:?}", err); + return; + } + NetlinkPayload::InnerMessage(msg) => { + match msg { + RtnlMessage::NewLink(link_msg) => { + let mut interface: Interface = Interface { + index: link_msg.header.index, + name: String::new(), + friendly_name: None, + description: None, + if_type: InterfaceType::try_from( + link_msg.header.link_layer_type as u32, + ) + .unwrap_or(InterfaceType::Unknown), + mac_addr: None, + ipv4: Vec::new(), + ipv6: Vec::new(), + flags: link_msg.header.flags, + transmit_speed: None, + receive_speed: None, + gateway: None, + }; + + for nla in link_msg.nlas { + match nla { + LinkNla::IfName(name) => { + interface.name = name; + } + LinkNla::Address(addr) => { + match addr.len() { + 6 => { + interface.mac_addr = Some(MacAddr::new( + addr.try_into().unwrap(), + )); + } + 4 => { + let ip = Ipv4Addr::from( + <[u8; 4]>::try_from(addr).unwrap(), + ); + interface.ipv4.push(Ipv4Net::new_with_netmask( + ip, + Ipv4Addr::UNSPECIFIED, + )); + } + _ => { + // unclear what these would be + } + } + } + _ => {} + } + } + ifaces.push(interface); + } + RtnlMessage::NewAddress(addr_msg) => { + println!("NewAddress: {:?}", addr_msg); + if let Some(interface) = + ifaces.iter_mut().find(|i| i.index == addr_msg.header.index) + { + for nla in addr_msg.nlas { + match nla { + AddressNla::Address(addr) => match addr.len() { + 4 => { + let ip = Ipv4Addr::from( + <[u8; 4]>::try_from(addr).unwrap(), + ); + interface.ipv4.push(Ipv4Net::new( + ip, + addr_msg.header.prefix_len, + )); + } + 16 => { + let ip = Ipv6Addr::from( + <[u8; 16]>::try_from(addr).unwrap(), + ); + interface.ipv6.push(Ipv6Net::new( + ip, + addr_msg.header.prefix_len, + )); + } + _ => { + // what else? + } + }, + _ => {} + } + } + } else { + eprintln!( + "found unknown interface with index: {}", + addr_msg.header.index + ); + } + } + _ => { + // not expecting other messages + } + } + } + _ => {} + } + offset += rx_packet.header.length as usize; + if offset == size || rx_packet.header.length == 0 { + offset = 0; + break; + } + } + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_netlink_ifaddrs() { + let interfaces = unix_interfaces(); + dbg!(&interfaces); + assert!(!interfaces.is_empty()); + } + } } diff --git a/src/interface/unix.rs b/src/interface/unix.rs index e198b6d..6775286 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -1,10 +1,10 @@ use super::Interface; use super::MacAddr; use crate::gateway; +use crate::interface::InterfaceType; use crate::ip::{Ipv4Net, Ipv6Net}; use crate::sys; -use crate::interface::InterfaceType; use libc; use std::ffi::{CStr, CString}; use std::mem::{self, MaybeUninit}; @@ -12,9 +12,6 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::os::raw::c_char; use std::str::from_utf8_unchecked; -#[cfg(target_os = "android")] -use super::android::{freeifaddrs, getifaddrs}; - #[cfg(any(target_os = "openbsd", target_os = "freebsd", target_os = "netbsd"))] pub fn interfaces() -> Vec { let mut interfaces: Vec = unix_interfaces(); @@ -128,7 +125,9 @@ pub fn interfaces() -> Vec { } #[cfg(any(target_os = "linux", target_os = "android"))] -fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option, Option) { +pub(super) fn sockaddr_to_network_addr( + sa: *const libc::sockaddr, +) -> (Option, Option) { use std::net::SocketAddr; unsafe { @@ -150,7 +149,7 @@ fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option, Opti let addr = sys::sockaddr_to_addr(mem::transmute(sa), mem::size_of::()); - match addr { + match dbg!(addr) { Ok(SocketAddr::V4(sa)) => (None, Some(IpAddr::V4(*sa.ip()))), Ok(SocketAddr::V6(sa)) => (None, Some(IpAddr::V6(*sa.ip()))), Err(_) => (None, None), @@ -199,7 +198,30 @@ fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option, Opti } } +#[cfg(not(target_os = "android"))] +pub fn unix_interfaces() -> Vec { + unix_interfaces_inner(libc::getifaddrs, libc::freeifaddrs) +} + +#[cfg(target_os = "android")] pub fn unix_interfaces() -> Vec { + // Android is complicated + + // API 24+ contains the getifaddrs and freeifaddrs functions but the NDK doesn't + // expose those functions in ifaddrs.h when the minimum supported SDK is lower than 24 + // and therefore we need to load them manually. + if let Some((getifaddrs, freeeifaddrs)) = android::get_libc_ifaddrs() { + return unix_interfaces_inner(getifaddrs, freeifaddrs); + } + + // If API < 24 (or we can't load libc for some other reason), we fallback to using netlink + android::netlink::unix_interfaces() +} + +pub fn unix_interfaces_inner( + getifaddrs: unsafe extern "C" fn(ifap: *mut *mut libc::ifaddrs) -> libc::c_int, + freeifaddrs: unsafe extern "C" fn(ifa: *mut libc::ifaddrs), +) -> Vec { let mut ifaces: Vec = vec![]; let mut addrs: MaybeUninit<*mut libc::ifaddrs> = MaybeUninit::uninit(); if unsafe { getifaddrs(addrs.as_mut_ptr()) } != 0 { @@ -212,6 +234,7 @@ pub fn unix_interfaces() -> Vec { let c_str = addr_ref.ifa_name as *const c_char; let bytes = unsafe { CStr::from_ptr(c_str).to_bytes() }; let name = unsafe { from_utf8_unchecked(bytes).to_owned() }; + let (mac, ip) = sockaddr_to_network_addr(addr_ref.ifa_addr as *const libc::sockaddr); let (_, netmask) = sockaddr_to_network_addr(addr_ref.ifa_netmask as *const libc::sockaddr); let mut ini_ipv4: Vec = vec![]; @@ -308,17 +331,6 @@ pub fn unix_interfaces() -> Vec { ifaces } -#[cfg(not(target_os = "android"))] -unsafe fn getifaddrs(ifap: *mut *mut libc::ifaddrs) -> libc::c_int { - // Not android, everything is easy - libc::getifaddrs(ifap) -} - -#[cfg(not(target_os = "android"))] -unsafe fn freeifaddrs(ifa: *mut libc::ifaddrs) { - libc::freeifaddrs(ifa); -} - #[cfg(test)] mod tests { use super::*;