Merge pull request #22 from dignifiedquire/feat-android

feat: Android compatibility
main
shellrow 1 year ago committed by GitHub
commit 7e89532b4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      Cargo.toml
  2. 303
      src/interface/android.rs
  3. 3
      src/interface/mod.rs
  4. 26
      src/interface/unix.rs

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

@ -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());
}
}
}

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

@ -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<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 {
@ -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() };

Loading…
Cancel
Save