diff --git a/Cargo.toml b/Cargo.toml index 9a3ff28..32fe12d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,15 @@ license = "MIT" [dependencies] libc = "0.2" +[target.'cfg(target_os = "android")'.dependencies] +# DL Open +dlopen2 = "0.4" +once_cell = "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 new file mode 100644 index 0000000..4012f84 --- /dev/null +++ b/src/interface/android.rs @@ -0,0 +1,303 @@ +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"; + + match dlopen::raw::Library::open(LIB_NAME) { + Ok(lib) => match unsafe { lib.symbol::(sym) } { + Ok(val) => Some(val), + Err(err) => { + eprintln!("failed to load symbol {} from {}: {:?}", sym, LIB_NAME, err); + None + } + }, + Err(err) => { + eprintln!("failed to load {}: {:?}", LIB_NAME, err); + None + } + } +} + +fn get_getifaddrs() -> Option libc::c_int> { + static INSTANCE: OnceCell< + Option libc::c_int>, + > = OnceCell::new(); + + *INSTANCE.get_or_init(|| load_symbol("getifaddrs")) +} + +fn get_freeifaddrs() -> Option { + static INSTANCE: OnceCell> = OnceCell::new(); + + *INSTANCE.get_or_init(|| load_symbol("freeifaddrs")) +} + +pub mod netlink { + //! Netlink based getifaddrs. + //! + //! Based on the logic found in https://git.musl-libc.org/cgit/musl/tree/src/network/getifaddrs.c + + 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::io; + use std::net::{Ipv4Addr, Ipv6Addr}; + + use crate::interface::{Interface, InterfaceType, Ipv4Net, Ipv6Net, MacAddr}; + + pub fn unix_interfaces() -> Vec { + let mut ifaces = Vec::new(); + if let Ok(socket) = Socket::new(NETLINK_ROUTE) { + if let Err(err) = enumerate_netlink( + &socket, + RtnlMessage::GetLink(LinkMessage::default()), + &mut ifaces, + handle_new_link, + ) { + eprintln!("unable to list interfaces: {:?}", err); + }; + if let Err(err) = enumerate_netlink( + &socket, + RtnlMessage::GetAddress(AddressMessage::default()), + &mut ifaces, + handle_new_addr, + ) { + eprintln!("unable to list addresses: {:?}", err); + } + } + ifaces + } + + fn handle_new_link(ifaces: &mut Vec, msg: RtnlMessage) -> io::Result<()> { + 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); + } + _ => {} + } + + Ok(()) + } + + fn handle_new_addr(ifaces: &mut Vec, msg: RtnlMessage) -> io::Result<()> { + match msg { + RtnlMessage::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 + ); + } + } + _ => {} + } + + Ok(()) + } + + struct NetlinkIter<'a> { + socket: &'a Socket, + /// Buffer for received data. + buf: Vec, + /// Size of the data available in `buf`. + size: usize, + /// Offset into the data currently in `buf`. + offset: usize, + /// Are we don iterating? + done: bool, + } + + impl<'a> NetlinkIter<'a> { + fn new(socket: &'a Socket, msg: RtnlMessage) -> io::Result { + 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(); + + let mut buf = vec![0; packet.header.length as usize]; + assert_eq!(buf.len(), packet.buffer_len()); + packet.serialize(&mut buf[..]); + socket.send(&buf[..], 0)?; + + Ok(NetlinkIter { + socket, + offset: 0, + size: 0, + buf: vec![0u8; 4096], + done: false, + }) + } + } + + impl<'a> Iterator for NetlinkIter<'a> { + type Item = io::Result; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + + while !self.done { + // Outer loop + if self.size == 0 { + match self.socket.recv(&mut &mut self.buf[..], 0) { + Ok(size) => { + self.size = size; + self.offset = 0; + } + Err(err) => { + self.done = true; + return Some(Err(err)); + } + } + } + + let bytes = &self.buf[self.offset..]; + match NetlinkMessage::::deserialize(bytes) { + Ok(packet) => { + self.offset += packet.header.length as usize; + if packet.header.length == 0 || self.offset == self.size { + // mark this message as fully read + self.size = 0; + } + match packet.payload { + NetlinkPayload::Done => { + self.done = true; + return None; + } + NetlinkPayload::Error(err) => { + self.done = true; + return Some(Err(io::Error::new( + io::ErrorKind::Other, + err.to_string(), + ))); + } + NetlinkPayload::InnerMessage(msg) => return Some(Ok(msg)), + _ => { + continue; + } + } + } + Err(err) => { + self.done = true; + return Some(Err(io::Error::new(io::ErrorKind::Other, err.to_string()))); + } + } + } + + None + } + } + + fn enumerate_netlink( + socket: &Socket, + msg: RtnlMessage, + ifaces: &mut Vec, + cb: F, + ) -> io::Result<()> + where + F: Fn(&mut Vec, RtnlMessage) -> io::Result<()>, + { + let iter = NetlinkIter::new(socket, msg)?; + for msg in iter { + let msg = msg?; + cb(ifaces, msg)?; + } + + Ok(()) + } + + #[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/mod.rs b/src/interface/mod.rs index e649e4e..67e4f49 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -33,6 +33,9 @@ use self::windows::*; #[cfg(any(target_os = "linux", target_os = "android"))] mod linux; +#[cfg(target_os = "android")] +mod android; + #[cfg(any(target_os = "macos", target_os = "ios"))] mod macos; diff --git a/src/interface/unix.rs b/src/interface/unix.rs index 2887b60..6cf4ff6 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -1,6 +1,7 @@ use super::Interface; use super::MacAddr; use crate::gateway; +use crate::interface::InterfaceType; use crate::ip::{Ipv4Net, Ipv6Net}; use crate::sys; @@ -125,7 +126,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 { @@ -196,10 +199,29 @@ fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option, Opti } } +#[cfg(target_os = "android")] +pub fn unix_interfaces() -> Vec { + use super::android; + + if let Some((getifaddrs, freeifaddrs)) = android::get_libc_ifaddrs() { + return unix_interfaces_inner(getifaddrs, freeifaddrs); + } + + android::netlink::unix_interfaces() +} + +#[cfg(not(target_os = "android"))] pub fn unix_interfaces() -> Vec { + unix_interfaces_inner(libc::getifaddrs, libc::freeifaddrs) +} + +fn unix_interfaces_inner( + getifaddrs: unsafe extern "C" fn(*mut *mut libc::ifaddrs) -> libc::c_int, + freeifaddrs: unsafe extern "C" fn(*mut libc::ifaddrs), +) -> Vec { let mut ifaces: Vec = vec![]; let mut addrs: MaybeUninit<*mut libc::ifaddrs> = MaybeUninit::uninit(); - if unsafe { libc::getifaddrs(addrs.as_mut_ptr()) } != 0 { + if unsafe { getifaddrs(addrs.as_mut_ptr()) } != 0 { return ifaces; } let addrs = unsafe { addrs.assume_init() };