use std::alloc::{alloc, dealloc, Layout};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use libc::{
getifaddrs, strlen, c_char, ifaddrs, sockaddr_in, sockaddr_in6, AF_INET, AF_INET6, IFF_LOOPBACK,
};
use crate::Error;
type IfAddrsPtr = *mut *mut ifaddrs;
pub fn list_afinet_netifas() -> Result<Vec<(String, IpAddr)>, Error> {
match list_afinet_netifas_info() {
Ok(interfaces) => Ok(interfaces
.iter()
.map(|i| (i.iname.clone(), i.addr))
.collect()),
Err(e) => Err(e),
}
}
pub(crate) struct AfInetInfo {
pub addr: IpAddr,
pub iname: String,
pub is_loopback: bool,
}
impl AfInetInfo {
#[cfg(target_os = "android")]
pub(crate) fn is_mobile_data(&self) -> bool {
self.iname.contains("rmnet_data")
}
#[cfg(target_os = "ios")]
pub(crate) fn is_mobile_data(&self) -> bool {
self.iname.contains("pdp_ip")
}
#[cfg(any(
target_os = "linux",
target_os = "windows",
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
pub(crate) fn is_mobile_data(&self) -> bool {
false
}
#[allow(dead_code)]
pub(crate) fn is_local_network(&self) -> bool {
self.iname.contains("eth") || self.iname.contains("wlan") || self.iname.contains("en")
}
}
pub(crate) fn list_afinet_netifas_info() -> Result<Vec<AfInetInfo>, Error> {
unsafe {
let layout = Layout::new::<IfAddrsPtr>();
let ptr = alloc(layout);
let myaddr = ptr as IfAddrsPtr;
let getifaddrs_result = getifaddrs(myaddr);
if getifaddrs_result != 0 {
return Err(Error::StrategyError(format!(
"GetIfAddrs returned error: {}",
getifaddrs_result
)));
}
let mut interfaces: Vec<AfInetInfo> = Vec::new();
let ifa = myaddr;
loop {
let ifa_addr = (**ifa).ifa_addr;
if !ifa_addr.is_null() {
match (*ifa_addr).sa_family as i32 {
AF_INET => {
let interface_address = ifa_addr;
let socket_addr_v4: *mut sockaddr_in =
interface_address as *mut sockaddr_in;
let in_addr = (*socket_addr_v4).sin_addr;
let mut ip_addr = Ipv4Addr::from(in_addr.s_addr);
if cfg!(target_endian = "little") {
ip_addr = Ipv4Addr::from(in_addr.s_addr.swap_bytes());
}
interfaces.push(AfInetInfo {
addr: IpAddr::V4(ip_addr),
iname: get_ifa_name(ifa)?,
is_loopback: is_loopback_addr(ifa),
});
}
AF_INET6 => {
let interface_address = ifa_addr;
let socket_addr_v6: *mut sockaddr_in6 =
interface_address as *mut sockaddr_in6;
let in6_addr = (*socket_addr_v6).sin6_addr;
let ip_addr = Ipv6Addr::from(in6_addr.s6_addr);
interfaces.push(AfInetInfo {
addr: IpAddr::V6(ip_addr),
iname: get_ifa_name(ifa)?,
is_loopback: is_loopback_addr(ifa),
});
}
_ => {}
}
}
*ifa = (**ifa).ifa_next;
if (*ifa).is_null() {
break;
}
}
dealloc(ptr, layout);
Ok(interfaces)
}
}
unsafe fn get_ifa_name(ifa: *mut *mut ifaddrs) -> Result<String, Error> {
unsafe {
let str = (*(*ifa)).ifa_name;
let len = strlen(str as *const c_char);
let slice = std::slice::from_raw_parts(str as *mut u8, len);
match String::from_utf8(slice.to_vec()) {
Ok(s) => Ok(s),
Err(e) => Err(Error::StrategyError(format!(
"Failed to retrieve interface name. The name is not a valid UTF-8 string. {}",
e
))),
}
}
}
unsafe fn is_loopback_addr(ifa: *mut *mut ifaddrs) -> bool {
unsafe {
let iflags = (*(*ifa)).ifa_flags as i32;
(iflags & IFF_LOOPBACK) != 0
}
}