use crate::getifaddrs::getifaddrs;
use crate::{Error, NetworkInterface, Result};
use libc::{AF_INET, AF_INET6, AF_PACKET, c_char, if_nametoindex, strlen};
#[cfg(feature = "mac")]
use mac_address::MacAddress;
use std::collections::HashMap;
#[cfg(feature = "ipv4")]
use std::net::Ipv4Addr;
#[cfg(feature = "ipv6")]
use std::net::Ipv6Addr;
#[cfg(feature = "ipv4")]
use std::ops::{BitOr, Not};
use std::slice::from_raw_parts;
pub fn get_interfaces() -> Result<HashMap<String, NetworkInterface>> {
let mut network_interfaces = HashMap::new();
for ifaddrs in getifaddrs()? {
let ifa_addr = ifaddrs.ifa_addr;
if ifa_addr.is_null() {
continue;
}
let family = unsafe { (*ifa_addr).sa_family as i32 };
match family {
AF_PACKET | AF_INET | AF_INET6 => {}
_ => continue,
};
let raw_name = ifaddrs.ifa_name as *const libc::c_char;
let name = get_name(raw_name)?;
let entry = network_interfaces.entry(name).or_insert_with_key(|name| {
let index = get_index_from_name(raw_name);
NetworkInterface::new(name.clone(), index)
});
match family {
#[cfg(feature = "mac")]
AF_PACKET => {
let mac = make_mac_addrs(&ifaddrs);
entry.mac_addr = Some(mac);
}
#[cfg(feature = "ipv4")]
AF_INET => {
let socket_addr = ifa_addr as *mut libc::sockaddr_in;
let internet_address = unsafe { (*socket_addr).sin_addr };
let ip = ipv4_from_in_addr(&internet_address);
let netmask = Netmask::new(&ifaddrs).map(|netmask| netmask.as_v4());
let broadcast = get_broadcast(ip, netmask);
entry.add_v4(ip, broadcast, netmask);
}
#[cfg(feature = "ipv6")]
AF_INET6 => {
let socket_addr = ifa_addr as *mut libc::sockaddr_in6;
let internet_address = unsafe { (*socket_addr).sin6_addr };
let ip = Ipv6Addr::from(internet_address.s6_addr);
let netmask = Netmask::new(&ifaddrs).map(|netmask| netmask.as_v6().unwrap());
let broadcast = make_ipv6_broadcast_addr(&ifaddrs);
entry.add_v6(ip, broadcast, netmask);
}
_ => {}
};
}
Ok(network_interfaces)
}
#[cfg(feature = "ipv4")]
fn get_broadcast(addr: Ipv4Addr, netmask: Option<Ipv4Addr>) -> Option<Ipv4Addr> {
Some(addr.bitor(netmask?.not()))
}
fn get_name(raw_name: *const c_char) -> Result<String> {
let len = unsafe { strlen(raw_name) };
let bytes_slice = unsafe { from_raw_parts(raw_name as *const u8, len) };
match String::from_utf8(bytes_slice.to_vec()) {
Ok(s) => Ok(s),
Err(e) => Err(Error::ParseUtf8Error(e)),
}
}
#[inline]
fn get_index_from_name(raw_name: *const c_char) -> u32 {
unsafe { if_nametoindex(raw_name) }
}
#[cfg(feature = "ipv6")]
fn make_ipv6_broadcast_addr(ifaddrs: &libc::ifaddrs) -> Option<Ipv6Addr> {
let ifa_dstaddr = ifaddrs.ifa_ifu;
if ifa_dstaddr.is_null() {
return None;
}
let socket_addr = ifa_dstaddr as *mut libc::sockaddr_in6;
let internet_address = unsafe { (*socket_addr).sin6_addr };
let addr = Ipv6Addr::from(internet_address.s6_addr);
Some(addr)
}
#[cfg(feature = "mac")]
fn make_mac_addrs(ifaddrs: &libc::ifaddrs) -> MacAddress {
let ifa_addr = ifaddrs.ifa_addr;
let socket_addr = ifa_addr as *mut libc::sockaddr_ll;
let raw_array = unsafe { (*socket_addr).sll_addr };
let addr_len = unsafe { (*socket_addr).sll_halen };
let real_addr_len = std::cmp::min(addr_len as usize, raw_array.len());
let mac_slice = unsafe { std::slice::from_raw_parts(raw_array.as_ptr(), real_addr_len) };
let mac_array = mac_slice.try_into().unwrap();
MacAddress::new(mac_array)
}
#[cfg(feature = "ipv4")]
pub fn ipv4_from_in_addr(internet_address: &libc::in_addr) -> Ipv4Addr {
if cfg!(target_endian = "little") {
Ipv4Addr::from(internet_address.s_addr.swap_bytes())
} else {
Ipv4Addr::from(internet_address.s_addr)
}
}
pub struct Netmask {
sockaddr: *mut libc::sockaddr,
}
impl Netmask {
pub fn new(ifaddrs: &libc::ifaddrs) -> Option<Self> {
let sockaddr = ifaddrs.ifa_netmask;
if sockaddr.is_null() {
return None;
}
Some(Self { sockaddr })
}
#[cfg(feature = "ipv4")]
pub fn as_v4(&self) -> Ipv4Addr {
let socket_addr = self.sockaddr as *mut libc::sockaddr_in;
let internet_address = unsafe { (*socket_addr).sin_addr };
ipv4_from_in_addr(&internet_address)
}
#[cfg(feature = "ipv6")]
pub fn as_v6(&self) -> Option<Ipv6Addr> {
let socket_addr = self.sockaddr as *mut libc::sockaddr_in6;
let internet_address = unsafe { (*socket_addr).sin6_addr };
if internet_address.s6_addr[0] == 0xfe && internet_address.s6_addr[1] == 0x80 {
return None;
}
let addr = Ipv6Addr::from(internet_address.s6_addr);
Some(addr)
}
}
#[test]
#[cfg(feature = "ipv4")]
fn test_get_broadcast() {
let broadcast = get_broadcast(
Ipv4Addr::new(172, 16, 0, 0),
Some(Ipv4Addr::new(255, 240, 0, 0)),
)
.unwrap();
assert_eq!(broadcast, Ipv4Addr::new(172, 31, 255, 255));
}
#[test]
#[cfg(feature = "ipv4")]
fn test_get_broadcast_without_netmask() {
let broadcast = get_broadcast(Ipv4Addr::new(172, 16, 0, 0), None);
assert!(broadcast.is_none());
}