Skip to content

feat: Android compatibility #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 23, 2023
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
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
303 changes: 303 additions & 0 deletions src/interface/android.rs
Original file line number Diff line number Diff line change
@@ -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<T>(sym: &'static str) -> Option<T> {
const LIB_NAME: &str = "libc.so";

match dlopen::raw::Library::open(LIB_NAME) {
Ok(lib) => match unsafe { lib.symbol::<T>(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<unsafe extern "C" fn(*mut *mut libc::ifaddrs) -> libc::c_int> {
static INSTANCE: OnceCell<
Option<unsafe extern "C" fn(*mut *mut libc::ifaddrs) -> libc::c_int>,
> = OnceCell::new();

*INSTANCE.get_or_init(|| load_symbol("getifaddrs"))
}

fn get_freeifaddrs() -> Option<unsafe extern "C" fn(*mut libc::ifaddrs)> {
static INSTANCE: OnceCell<Option<unsafe extern "C" fn(*mut libc::ifaddrs)>> = 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<Interface> {
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<Interface>, 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<Interface>, 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<u8>,
/// 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<Self> {
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<RtnlMessage>;

fn next(&mut self) -> Option<Self::Item> {
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::<RtnlMessage>::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<F>(
socket: &Socket,
msg: RtnlMessage,
ifaces: &mut Vec<Interface>,
cb: F,
) -> io::Result<()>
where
F: Fn(&mut Vec<Interface>, 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());
}
}
}
3 changes: 3 additions & 0 deletions src/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
26 changes: 24 additions & 2 deletions src/interface/unix.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -125,7 +126,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 {
Expand Down Expand Up @@ -196,10 +199,29 @@ fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option<MacAddr>, Opti
}
}

#[cfg(target_os = "android")]
pub fn unix_interfaces() -> Vec<Interface> {
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<Interface> {
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<Interface> {
let mut ifaces: Vec<Interface> = 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() };
Expand Down