#![doc=include_str!("../README.md")]
use std::collections::BTreeMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use bitflags::bitflags;
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
pub type InterfaceIndex = u32;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InterfaceFlags: u32 {
const UP = 0x1;
const RUNNING = 0x2;
const BROADCAST = 0x4;
const LOOPBACK = 0x8;
const POINTTOPOINT = 0x10;
const MULTICAST = 0x20;
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Interface<A: sealed::Addressable = Address> {
pub name: String,
#[cfg(windows)]
pub description: String,
pub address: A,
pub flags: InterfaceFlags,
pub index: Option<InterfaceIndex>,
}
pub type Interfaces = BTreeMap<InterfaceIndex, Interface<Addresses>>;
impl FromIterator<Interface> for Interfaces {
fn from_iter<T: IntoIterator<Item = Interface>>(iter: T) -> Self {
let mut map = BTreeMap::new();
for interface in iter {
if let Some(index) = interface.index {
map.entry(index)
.or_insert_with(|| Interface {
name: interface.name,
#[cfg(windows)]
description: interface.description,
address: Addresses::default(),
flags: interface.flags,
index: Some(index),
})
.address
.insert(interface.address.family(), interface.address);
}
}
map
}
}
#[derive(Default, Debug)]
pub struct Addresses {
addresses: BTreeMap<AddressFamily, Vec<Address>>,
}
impl Addresses {
pub fn get(&self, family: AddressFamily) -> Option<&Address> {
self.addresses.get(&family).map(|addresses| &addresses[0])
}
pub fn get_all(&self, family: AddressFamily) -> Option<&[Address]> {
self.addresses.get(&family).map(|v| &**v)
}
pub fn has(&self, family: AddressFamily) -> bool {
self.addresses.contains_key(&family)
}
pub fn is_empty(&self) -> bool {
self.addresses.is_empty()
}
pub fn len(&self) -> usize {
self.addresses.len()
}
pub fn iter(&self) -> AddressesIter<'_> {
IntoIterator::into_iter(self)
}
fn insert(&mut self, family: AddressFamily, address: Address) {
let entry = self.addresses.entry(family).or_default();
match entry.binary_search(&address) {
Ok(_) => {
}
Err(pos) => {
entry.insert(pos, address);
}
}
}
}
pub struct AddressesIter<'a> {
iter: std::collections::btree_map::Values<'a, AddressFamily, Vec<Address>>,
}
impl<'a> Iterator for AddressesIter<'a> {
type Item = &'a [Address];
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|v| &**v)
}
}
impl<'a> IntoIterator for &'a Addresses {
type Item = &'a [Address];
type IntoIter = AddressesIter<'a>;
fn into_iter(self) -> Self::IntoIter {
AddressesIter {
iter: self.addresses.values(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AddressFamily {
V4,
V6,
Mac,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Address {
V4(NetworkAddress<Ipv4Addr>),
V6(NetworkAddress<Ipv6Addr>),
Mac([u8; 6]),
}
impl Address {
pub fn is_ipv4(&self) -> bool {
matches!(self, Address::V4(_))
}
pub fn is_ipv6(&self) -> bool {
matches!(self, Address::V6(_))
}
pub fn is_mac(&self) -> bool {
matches!(self, Address::Mac(_))
}
pub fn family(&self) -> AddressFamily {
match self {
Address::V4(_) => AddressFamily::V4,
Address::V6(_) => AddressFamily::V6,
Address::Mac(_) => AddressFamily::Mac,
}
}
pub fn mac_addr(&self) -> Option<[u8; 6]> {
match self {
Address::Mac(addr) => Some(*addr),
_ => None,
}
}
pub fn ip_addr(&self) -> Option<IpAddr> {
match self {
Address::V4(addr) => Some(IpAddr::V4(addr.address)),
Address::V6(addr) => Some(IpAddr::V6(addr.address)),
Address::Mac(_) => None,
}
}
pub fn netmask(&self) -> Option<IpAddr> {
match self {
Address::V4(addr) => addr.netmask.map(IpAddr::V4),
Address::V6(addr) => addr.netmask.map(IpAddr::V6),
Address::Mac(_) => None,
}
}
pub fn associated_address(&self) -> Option<IpAddr> {
match self {
Address::V4(addr) => addr.associated_address.map(IpAddr::V4),
Address::V6(addr) => addr.associated_address.map(IpAddr::V6),
Address::Mac(_) => None,
}
}
}
impl PartialEq<IpAddr> for Address {
fn eq(&self, other: &IpAddr) -> bool {
match self {
Address::V4(addr) => addr.address == *other,
Address::V6(addr) => addr.address == *other,
Address::Mac(_) => false,
}
}
}
mod sealed {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub trait NetworkAddressable: Into<IpAddr> {}
impl NetworkAddressable for Ipv4Addr {}
impl NetworkAddressable for Ipv6Addr {}
pub trait Addressable {}
impl Addressable for super::Address {}
impl Addressable for super::Addresses {}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NetworkAddress<T: sealed::NetworkAddressable> {
pub address: T,
pub netmask: Option<T>,
pub associated_address: Option<T>,
}
enum InterfaceFilterCriteria {
Loopback,
Index(InterfaceIndex),
Name(String),
}
#[derive(Default)]
pub struct InterfaceFilter {
criteria: Option<InterfaceFilterCriteria>,
address: Option<[bool; 3]>,
}
impl InterfaceFilter {
pub fn new() -> Self {
InterfaceFilter::default()
}
pub fn loopback(mut self) -> Self {
self.criteria = Some(InterfaceFilterCriteria::Loopback);
self
}
pub fn index(mut self, index: InterfaceIndex) -> Self {
self.criteria = Some(InterfaceFilterCriteria::Index(index));
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.criteria = Some(InterfaceFilterCriteria::Name(name.into()));
self
}
pub fn family(mut self, family: AddressFamily) -> Self {
let address = self.address.get_or_insert([false; 3]);
match family {
AddressFamily::V4 => address[0] = true,
AddressFamily::V6 => address[1] = true,
AddressFamily::Mac => address[2] = true,
}
self
}
pub fn v4(self) -> Self {
self.family(AddressFamily::V4)
}
pub fn v6(self) -> Self {
self.family(AddressFamily::V6)
}
pub fn mac(self) -> Self {
self.family(AddressFamily::Mac)
}
fn family_filter(&self, family: AddressFamily) -> bool {
self.address
.map(|address| {
address[match family {
AddressFamily::V4 => 0,
AddressFamily::V6 => 1,
AddressFamily::Mac => 2,
}]
})
.unwrap_or(true)
}
pub fn get(self) -> std::io::Result<impl Iterator<Item = Interface>> {
#[cfg(unix)]
{
unix::InterfaceIterator::new(self)
}
#[cfg(windows)]
{
windows::InterfaceIterator::new(self)
}
}
pub fn collect(self) -> std::io::Result<Interfaces> {
Ok(self.get()?.collect())
}
}
pub fn getifaddrs() -> std::io::Result<impl Iterator<Item = Interface>> {
InterfaceFilter::new().get()
}
pub fn if_indextoname(index: InterfaceIndex) -> std::io::Result<String> {
#[cfg(unix)]
{
unix::_if_indextoname(index)
}
#[cfg(windows)]
{
windows::_if_indextoname(index)
}
}
pub fn if_nametoindex(name: impl AsRef<str>) -> std::io::Result<InterfaceIndex> {
if let Ok(num) = name.as_ref().parse::<InterfaceIndex>() {
return Ok(num as _);
}
#[cfg(unix)]
{
unix::_if_nametoindex(name).map(|idx| idx as _)
}
#[cfg(windows)]
{
windows::_if_nametoindex(name).map(|idx| idx as _)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr};
#[test]
fn test_interfaces() {
let interfaces: Vec<Interface> = getifaddrs().unwrap().collect();
for interface in &interfaces {
eprintln!("{interface:#?}");
}
let localhost = interfaces.iter().find(|i| {
i.address == IpAddr::V4(Ipv4Addr::LOCALHOST)
&& i.flags.contains(InterfaceFlags::LOOPBACK)
});
assert!(localhost.is_some(), "No localhost interface found");
let non_localhost = interfaces.iter().find(|i| {
i.address != IpAddr::V4(Ipv4Addr::LOCALHOST)
&& !i.flags.contains(InterfaceFlags::LOOPBACK)
});
assert!(non_localhost.is_some(), "No non-localhost interface found");
for interface in &interfaces {
if let Some(index) = interface.index {
let name_from_index = if_indextoname(index as _).unwrap_or_default();
assert_eq!(
interface.name, name_from_index,
"Interface name mismatch for index {index}"
);
let index_from_name = if_nametoindex(&interface.name).unwrap_or_default();
assert_eq!(
index, index_from_name,
"Interface index mismatch for name {}",
interface.name
);
}
}
}
#[test]
fn test_collect() {
let interfaces: Interfaces = getifaddrs().unwrap().collect();
assert!(!interfaces.is_empty());
eprintln!("{interfaces:#?}");
}
#[test]
fn test_filter_address_type() {
let total = getifaddrs().unwrap().count();
let v4 = InterfaceFilter::new()
.v4()
.get()
.unwrap()
.collect::<Vec<_>>();
for interface in &v4 {
assert!(
interface.address.is_ipv4(),
"Expected v4 only: {interface:#?}"
);
}
let v6 = InterfaceFilter::new()
.v6()
.get()
.unwrap()
.collect::<Vec<_>>();
for interface in &v6 {
assert!(
interface.address.is_ipv6(),
"Expected v6 only: {interface:#?}"
);
}
let mac = InterfaceFilter::new()
.mac()
.get()
.unwrap()
.collect::<Vec<_>>();
for interface in &mac {
assert!(
interface.address.is_mac(),
"Expected mac only: {interface:#?}"
);
}
assert_eq!(
v4.len() + v6.len() + mac.len(),
total,
"v4 = {:?} v6 = {:?} mac = {:?} all = {:?}",
v4,
v6,
mac,
getifaddrs().unwrap().collect::<Vec<_>>()
);
}
#[test]
fn test_filter_address_type_with_mac() {
let total = getifaddrs().unwrap().count();
let v4_mac = InterfaceFilter::new()
.v4()
.mac()
.get()
.unwrap()
.collect::<Vec<_>>();
let v4 = InterfaceFilter::new()
.v4()
.get()
.unwrap()
.collect::<Vec<_>>();
let v6_mac = InterfaceFilter::new()
.v6()
.mac()
.get()
.unwrap()
.collect::<Vec<_>>();
let v6 = InterfaceFilter::new()
.v6()
.get()
.unwrap()
.collect::<Vec<_>>();
let v4_v6 = InterfaceFilter::new()
.v4()
.v6()
.get()
.unwrap()
.collect::<Vec<_>>();
let mac = InterfaceFilter::new()
.mac()
.get()
.unwrap()
.collect::<Vec<_>>();
assert_eq!(
v4_mac.len(),
v4.len() + mac.len(),
"v4_mac = {:?} != v4 = {:?} + mac = {:?}",
v4_mac,
v4,
mac
);
assert_eq!(
v6_mac.len(),
v6.len() + mac.len(),
"v6_mac = {:?} != v6 = {:?} + mac = {:?}",
v6_mac,
v6,
mac
);
assert_eq!(
v4_v6.len(),
v4.len() + v6.len(),
"v4_v6 = {:?} != v4 = {:?} + v6 = {:?}",
v4_v6,
v4,
v6
);
assert_eq!(
v4_mac.len() + v6.len(),
total,
"v4_mac = {:?} + v6 = {:?} != total = {:?}",
v4_mac,
v6,
total
);
assert_eq!(
v6_mac.len() + v4.len(),
total,
"v6_mac = {:?} + v4 = {:?} != total = {:?}",
v6_mac,
v4,
total
);
assert_eq!(
v4_v6.len() + mac.len(),
total,
"v4_v6 = {:?} + mac = {:?} != total = {:?}",
v4_v6,
mac,
total
);
}
#[test]
fn test_filter_name_and_index() {
for interface in getifaddrs().unwrap() {
let name = interface.name.clone();
let v: Vec<_> = InterfaceFilter::new()
.name(interface.name.clone())
.get()
.unwrap()
.collect();
eprintln!("Name filter {name}: {v:?}");
assert!(!v.is_empty());
for interface in v {
assert_eq!(name, interface.name);
}
if let Some(index) = interface.index {
let v: Vec<_> = InterfaceFilter::new().index(index).get().unwrap().collect();
eprintln!("Index filter {index}: {v:?}");
assert!(!v.is_empty());
for interface in v {
assert_eq!(Some(index), interface.index);
}
}
}
}
#[test]
fn test_filter_loopback() {
let loopback_interfaces: Vec<_> =
InterfaceFilter::new().loopback().get().unwrap().collect();
assert!(
!loopback_interfaces.is_empty(),
"No loopback interfaces found"
);
for interface in loopback_interfaces.clone() {
assert!(
interface.flags.contains(InterfaceFlags::LOOPBACK),
"Interface {:?} is not marked as loopback",
interface.name
);
}
let all_interfaces: Vec<_> = InterfaceFilter::new().get().unwrap().collect();
let non_loopback_count = all_interfaces
.iter()
.filter(|i| !i.flags.contains(InterfaceFlags::LOOPBACK))
.count();
assert_eq!(
all_interfaces.len() - loopback_interfaces.len(),
non_loopback_count,
"Loopback filter included non-loopback interfaces"
);
}
#[test]
fn test_associated_address() {
let interfaces: Vec<_> = getifaddrs().unwrap().collect();
for interface in &interfaces {
let associated = interface.address.associated_address();
if interface.flags.contains(InterfaceFlags::BROADCAST) && interface.address.is_ipv4() {
assert!(
associated.is_some(),
"Broadcast interface {} should have an associated address",
interface.name
);
if let Some(associated_addr) = associated {
assert_ne!(
interface.address.ip_addr().unwrap(),
associated_addr,
"Associated address should be different from interface address for broadcast interface {}",
interface.name
);
}
}
if interface.flags.contains(InterfaceFlags::LOOPBACK) {
}
}
}
}