feat: use netlink as a fallback on android

main
dignifiedquire 1 year ago
parent a8bed97314
commit f629ab3ccb
  1. 5
      Cargo.toml
  2. 211
      src/interface/android.rs
  3. 46
      src/interface/unix.rs

@ -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"

@ -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<T>(sym: &'static str) -> Option<T> {
const LIB_NAME: &str = "libc.so";
@ -33,32 +43,191 @@ fn get_freeifaddrs() -> Option<unsafe extern "C" fn(*mut libc::ifaddrs)> {
*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<Interface> {
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<Interface>) {
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<RtnlMessage> =
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());
}
}
}

@ -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<Interface> {
let mut interfaces: Vec<Interface> = unix_interfaces();
@ -128,7 +125,9 @@ pub fn interfaces() -> Vec<Interface> {
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option<MacAddr>, Option<IpAddr>) {
pub(super) fn sockaddr_to_network_addr(
sa: *const libc::sockaddr,
) -> (Option<MacAddr>, Option<IpAddr>) {
use std::net::SocketAddr;
unsafe {
@ -150,7 +149,7 @@ fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option<MacAddr>, Opti
let addr =
sys::sockaddr_to_addr(mem::transmute(sa), mem::size_of::<libc::sockaddr_storage>());
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<MacAddr>, Opti
}
}
#[cfg(not(target_os = "android"))]
pub fn unix_interfaces() -> Vec<Interface> {
unix_interfaces_inner(libc::getifaddrs, libc::freeifaddrs)
}
#[cfg(target_os = "android")]
pub fn unix_interfaces() -> Vec<Interface> {
// 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<Interface> {
let mut ifaces: Vec<Interface> = 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<Interface> {
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<Ipv4Net> = vec![];
@ -308,17 +331,6 @@ pub fn unix_interfaces() -> Vec<Interface> {
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::*;

Loading…
Cancel
Save