commit
7e89532b4b
@ -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()); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue