use std::net::IpAddr;
use crate::error::IoError;
#[derive(Debug, Clone)]
pub struct Interface {
pub name: String,
pub friendly_name: Option<String>,
pub mac_address: Option<[u8; 6]>,
pub ips: Vec<IpAddr>,
pub is_loopback: bool,
pub is_up: bool,
}
pub fn list_interfaces() -> Result<Vec<Interface>, IoError> {
platform_list_interfaces()
}
#[cfg(target_os = "linux")]
fn platform_list_interfaces() -> Result<Vec<Interface>, IoError> {
use std::os::fd::AsRawFd;
use socket2::{Domain, Socket, Type};
let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
let fd = sock.as_raw_fd();
let mut ifc: libc::ifconf = unsafe { std::mem::zeroed() };
let buf_size = 4096usize;
let mut buf = vec![0u8; buf_size];
ifc.ifc_len = buf_size as i32;
ifc.ifc_ifcu.ifcu_buf = buf.as_mut_ptr() as *mut libc::c_char;
if unsafe { libc::ioctl(fd, libc::SIOCGIFCONF, &mut ifc) } < 0 {
return Err(IoError::Socket(std::io::Error::last_os_error()));
}
let mut interfaces = Vec::new();
let mut pos = 0usize;
while pos < ifc.ifc_len as usize {
let ifr: &libc::ifreq = unsafe { &*(ifc.ifc_ifcu.ifcu_buf.add(pos) as *const libc::ifreq) };
pos += std::mem::size_of::<libc::ifreq>();
let name = cstr_to_string(&ifr.ifr_name);
let mut ifr_flags = unsafe { std::mem::zeroed::<libc::ifreq>() };
copy_ifname(&mut ifr_flags, &name);
let is_up = unsafe { libc::ioctl(fd, libc::SIOCGIFFLAGS, &mut ifr_flags) } >= 0
&& unsafe { ifr_flags.ifr_ifru.ifru_flags & libc::IFF_UP as i16 } != 0;
let is_loopback = unsafe { libc::ioctl(fd, libc::SIOCGIFFLAGS, &mut ifr_flags) } >= 0
&& unsafe { ifr_flags.ifr_ifru.ifru_flags & libc::IFF_LOOPBACK as i16 } != 0;
let mut ifr_hw = unsafe { std::mem::zeroed::<libc::ifreq>() };
copy_ifname(&mut ifr_hw, &name);
let mac = if unsafe { libc::ioctl(fd, libc::SIOCGIFHWADDR, &mut ifr_hw) } >= 0 {
let sa = unsafe { &ifr_hw.ifr_ifru.ifru_hwaddr };
Some([sa.sa_data[0] as u8, sa.sa_data[1] as u8, sa.sa_data[2] as u8,
sa.sa_data[3] as u8, sa.sa_data[4] as u8, sa.sa_data[5] as u8])
} else { None };
let mut ifr_addr = unsafe { std::mem::zeroed::<libc::ifreq>() };
copy_ifname(&mut ifr_addr, &name);
let ips = if unsafe { libc::ioctl(fd, libc::SIOCGIFADDR, &mut ifr_addr) } >= 0 {
let sa = unsafe { &ifr_addr.ifr_ifru.ifru_addr };
if sa.sa_family as i32 == libc::AF_INET {
let addr_bytes = unsafe { (*(sa as *const libc::sockaddr as *const libc::sockaddr_in)).sin_addr.s_addr };
let ip = IpAddr::V4(std::net::Ipv4Addr::from(u32::from_be(addr_bytes)));
vec![ip]
} else { vec![] }
} else { vec![] };
interfaces.push(Interface { name, friendly_name: None, mac_address: mac, ips, is_loopback, is_up });
}
Ok(interfaces)
}
#[cfg(target_os = "linux")]
fn cstr_to_string(cstr: &[i8]) -> String {
let bytes: Vec<u8> = cstr.iter().take_while(|&&b| b != 0).map(|&b| b as u8).collect();
String::from_utf8_lossy(&bytes).into_owned()
}
#[cfg(target_os = "linux")]
fn copy_ifname(ifr: &mut libc::ifreq, name: &str) {
let bytes = name.as_bytes();
let len = bytes.len().min(ifr.ifr_name.len() - 1);
ifr.ifr_name[..len].copy_from_slice(unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const i8, len) });
}
#[cfg(target_os = "macos")]
fn platform_list_interfaces() -> Result<Vec<Interface>, IoError> {
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr};
let mut ifap: *mut libc::ifaddrs = std::ptr::null_mut();
if unsafe { libc::getifaddrs(&mut ifap) } != 0 {
return Err(IoError::Socket(std::io::Error::last_os_error()));
}
struct IfInfo {
name: String,
mac: Option<[u8; 6]>,
is_loopback: bool,
is_up: bool,
ips: Vec<IpAddr>,
}
let mut map: HashMap<String, IfInfo> = HashMap::new();
let mut cur = ifap;
while !cur.is_null() {
let ifa = unsafe { &*cur };
let name = cstr_to_string_osx(ifa.ifa_name);
let entry = map.entry(name.clone()).or_insert_with(|| IfInfo {
name,
mac: None,
is_loopback: (ifa.ifa_flags & libc::IFF_LOOPBACK as u32) != 0,
is_up: (ifa.ifa_flags & libc::IFF_UP as u32) != 0,
ips: vec![],
});
if !ifa.ifa_addr.is_null() {
let sa = unsafe { &*ifa.ifa_addr };
match sa.sa_family as i32 {
libc::AF_LINK => {
let sdl = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_dl) };
if sdl.sdl_alen == 6 {
let mac_addr = unsafe { *(sdl.sdl_data.as_ptr().offset(sdl.sdl_nlen as isize) as *const [u8; 6]) };
entry.mac = Some(mac_addr);
}
}
libc::AF_INET => {
let sin = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_in) };
let addr = Ipv4Addr::from(u32::from_be(sin.sin_addr.s_addr));
entry.ips.push(IpAddr::V4(addr));
}
libc::AF_INET6 => {
let sin6 = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_in6) };
let addr = Ipv6Addr::from(sin6.sin6_addr.s6_addr);
entry.ips.push(IpAddr::V6(addr));
}
_ => {}
}
}
cur = ifa.ifa_next;
}
unsafe { libc::freeifaddrs(ifap); }
Ok(map.into_values().map(|info| Interface {
name: info.name,
friendly_name: None,
mac_address: info.mac,
ips: info.ips,
is_loopback: info.is_loopback,
is_up: info.is_up,
}).collect())
}
#[cfg(target_os = "macos")]
fn cstr_to_string_osx(ptr: *const libc::c_char) -> String {
if ptr.is_null() { return String::new(); }
let bytes = unsafe { std::ffi::CStr::from_ptr(ptr).to_bytes() };
String::from_utf8_lossy(bytes).into_owned()
}
#[cfg(target_os = "windows")]
fn platform_list_interfaces() -> Result<Vec<Interface>, IoError> {
use std::net::{Ipv4Addr, Ipv6Addr};
const AF_UNSPEC: u32 = 0;
const GAA_FLAG_INCLUDE_PREFIX: u32 = 0x0010;
const IF_TYPE_SOFTWARE_LOOPBACK: u32 = 24;
const IF_OPER_STATUS_UP: u32 = 1;
#[repr(C)]
struct IpAdapterUnicastAddress {
length: u32,
_flags: u32,
next: *mut IpAdapterUnicastAddress,
sockaddr_ptr: *const u8, sockaddr_len: i32,
}
#[repr(C)]
struct IpAdapterAddresses {
length: u32,
if_index: u32,
next: *mut IpAdapterAddresses,
adapter_name: *const u8,
first_unicast_address: *mut IpAdapterUnicastAddress,
_pad1: [u8; 48],
physical_address: [u8; 8],
physical_address_length: u32,
flags: u32,
mtu: u32,
if_type: u32,
oper_status: u32,
}
extern "system" {
fn GetAdaptersAddresses(
family: u32,
flags: u32,
reserved: *const std::ffi::c_void,
addresses: *mut IpAdapterAddresses,
size: *mut u32,
) -> u32;
}
let mut size: u32 = 0;
let ret = unsafe { GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, std::ptr::null(), std::ptr::null_mut(), &mut size) };
if ret != 111 { return Ok(vec![]); }
let mut buf = vec![0u8; size as usize];
let ret = unsafe { GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, std::ptr::null(), buf.as_mut_ptr() as *mut IpAdapterAddresses, &mut size) };
if ret != 0 { return Ok(vec![]); }
let mut interfaces = Vec::new();
let mut cur = buf.as_ptr() as *const IpAdapterAddresses;
while !cur.is_null() {
let a = unsafe { &*cur };
let name = wide_to_string(a.adapter_name);
let friendly_name = None;
let mac = if a.physical_address_length == 6 {
Some([a.physical_address[0], a.physical_address[1], a.physical_address[2],
a.physical_address[3], a.physical_address[4], a.physical_address[5]])
} else { None };
let mut ips = Vec::new();
let mut ua = a.first_unicast_address;
while !ua.is_null() {
let u = unsafe { &*ua };
if !u.sockaddr_ptr.is_null() {
let family = unsafe { *(u.sockaddr_ptr as *const u16) };
match family {
2 => { let b = unsafe { std::slice::from_raw_parts(u.sockaddr_ptr.offset(4), 4) };
ips.push(IpAddr::V4(Ipv4Addr::new(b[0], b[1], b[2], b[3])));
}
23 => { let b = unsafe { std::slice::from_raw_parts(u.sockaddr_ptr.offset(8), 16) };
let mut arr = [0u8; 16];
arr.copy_from_slice(b);
ips.push(IpAddr::V6(Ipv6Addr::from(arr)));
}
_ => {}
}
}
ua = u.next;
}
interfaces.push(Interface {
name,
friendly_name,
mac_address: mac,
ips,
is_loopback: a.if_type == IF_TYPE_SOFTWARE_LOOPBACK,
is_up: a.oper_status == IF_OPER_STATUS_UP,
});
cur = a.next;
}
Ok(interfaces)
}
#[cfg(target_os = "windows")]
fn wide_to_string(ptr: *const u8) -> String {
if ptr.is_null() { return String::new(); }
let len = unsafe {
let mut end = ptr;
loop {
let w = u16::from_le_bytes([*end, *(end.offset(1))]);
if w == 0 { break; }
end = end.offset(2);
}
(end as usize - ptr as usize) / 2
};
let wide: Vec<u16> = (0..len).map(|i| unsafe {
u16::from_le_bytes([*ptr.add(i*2), *ptr.add(i*2+1)])
}).collect();
String::from_utf16_lossy(&wide)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_interfaces_returns_something() {
let ifaces = list_interfaces().expect("list_interfaces should succeed");
assert!(!ifaces.is_empty(), "should return at least one interface");
}
#[test]
fn list_interfaces_has_loopback() {
let ifaces = list_interfaces().expect("list_interfaces should succeed");
assert!(ifaces.iter().any(|i| i.is_loopback), "should contain loopback");
}
}