use ipnet::{ip_mask_to_prefix, Ipv4Net, Ipv6Net};
use libc::{
c_void, if_msghdr, size_t, sysctl, AF_INET, AF_INET6, AF_LINK, AF_ROUTE, AF_UNSPEC, CTL_NET,
NET_RT_IFLIST, RTAX_BRD, RTAX_IFA, RTAX_MAX, RTAX_NETMASK, RTM_IFINFO, RTM_NEWADDR, RTM_VERSION,
};
#[cfg(apple)]
use libc::NET_RT_IFLIST2;
use compat::IfaMsghdr as ifa_msghdr;
use smallvec_wrapper::{SmallVec, TinyVec};
use smol_str::SmolStr;
use std::{
io, mem,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
ptr::null_mut,
};
use super::{
IfNet, Ifv4Net, Ifv6Net, Interface, IpRoute, Ipv4Route, Ipv6Route, MacAddr, Net, MAC_ADDRESS_SIZE,
};
#[cfg(any(
target_vendor = "apple",
target_os = "freebsd",
target_os = "dragonfly"
))]
use super::{Address, IfAddr, Ifv4Addr, Ifv6Addr};
macro_rules! rt_generic_mod {
($($name:ident($rtf:ident, $rta:ident)), +$(,)?) => {
$(
paste::paste! {
pub(super) use [< rt_ $name >]::*;
mod [<rt_ $name>] {
use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use libc::{AF_INET, AF_INET6, AF_UNSPEC, $rta, $rtf};
use smallvec_wrapper::SmallVec;
use crate::{ipv4_filter_to_ip_filter, ipv6_filter_to_ip_filter};
use super::super::{Address, IfAddr, Ifv4Addr, Ifv6Addr};
pub(crate) fn [<$name _addrs >]() -> io::Result<SmallVec<IfAddr>> {
[< $name _addrs_in >](AF_UNSPEC, |_| true)
}
pub(crate) fn [<$name _ipv4_addrs >]() -> io::Result<SmallVec<Ifv4Addr>> {
[< $name _addrs_in >](AF_INET, |_| true)
}
pub(crate) fn [<$name _ipv6_addrs >]() -> io::Result<SmallVec<Ifv6Addr>> {
[< $name _addrs_in >](AF_INET6, |_| true)
}
pub(crate) fn [<$name _addrs_by_filter >]<F>(f: F) -> io::Result<SmallVec<IfAddr>>
where
F: FnMut(&IpAddr) -> bool,
{
[< $name _addrs_in >](AF_UNSPEC, f)
}
pub(crate) fn [<$name _ipv4_addrs_by_filter >]<F>(f: F) -> io::Result<SmallVec<Ifv4Addr>>
where
F: FnMut(&Ipv4Addr) -> bool,
{
[< $name _addrs_in >](AF_INET, ipv4_filter_to_ip_filter(f))
}
pub(crate) fn [<$name _ipv6_addrs_by_filter >]<F>(f: F) -> io::Result<SmallVec<Ifv6Addr>>
where
F: FnMut(&Ipv6Addr) -> bool,
{
[< $name _addrs_in >](AF_INET6, ipv6_filter_to_ip_filter(f))
}
fn [<$name _addrs_in >]<A, F>(family: i32, f: F) -> io::Result<SmallVec<A>>
where
A: Address + Eq,
F: FnMut(&IpAddr) -> bool,
{
super::rt_generic::rt_generic_addrs_in(family, $rtf, $rta, f)
}
}
}
)*
};
}
rt_generic_mod!(gateway(RTF_GATEWAY, RTA_GATEWAY),);
pub(super) use local_addr::*;
#[inline]
fn build_routev4(
index: u32,
rtm_flags: libc::c_int,
dst: IpAddr,
gateway: Option<IpAddr>,
netmask: Option<IpAddr>,
) -> Option<Ipv4Route> {
let dst_v4 = match dst {
IpAddr::V4(ip) => ip,
_ => return None,
};
if dst_v4.is_multicast() || dst_v4.is_broadcast() {
return None;
}
let prefix_len = match netmask {
Some(IpAddr::V4(m)) => ip_mask_to_prefix(IpAddr::V4(m)).ok()?,
_ if dst_v4.is_unspecified() => 0,
_ if (rtm_flags & libc::RTF_HOST) != 0 => 32,
_ => return None,
};
let net = Ipv4Net::new(dst_v4, prefix_len).ok()?;
let gw = match gateway {
Some(IpAddr::V4(g)) if g != Ipv4Addr::UNSPECIFIED => Some(g),
_ => None,
};
Some(Ipv4Route::new(index, net, gw))
}
#[inline]
fn build_routev6(
index: u32,
rtm_flags: libc::c_int,
dst: IpAddr,
gateway: Option<IpAddr>,
netmask: Option<IpAddr>,
) -> Option<Ipv6Route> {
let dst_v6 = match dst {
IpAddr::V6(ip) => ip,
_ => return None,
};
if dst_v6.is_multicast() {
return None;
}
let prefix_len = match netmask {
Some(IpAddr::V6(m)) => ip_mask_to_prefix(IpAddr::V6(m)).ok()?,
_ if dst_v6.is_unspecified() => 0,
_ if (rtm_flags & libc::RTF_HOST) != 0 => 128,
_ => return None,
};
let net = Ipv6Net::new(dst_v6, prefix_len).ok()?;
let gw = match gateway {
Some(IpAddr::V6(g)) if g != Ipv6Addr::UNSPECIFIED => Some(g),
_ => None,
};
Some(Ipv6Route::new(index, net, gw))
}
pub(super) fn family_unavailable_to_empty(result: io::Result<()>) -> io::Result<()> {
match result {
Ok(()) => Ok(()),
Err(e) => match e.raw_os_error() {
Some(c) if c == libc::EAFNOSUPPORT || c == libc::EPROTONOSUPPORT || c == libc::EOPNOTSUPP => {
Ok(())
}
_ => Err(e),
},
}
}
pub(super) fn route_table_by_filter<F>(mut f: F) -> io::Result<SmallVec<IpRoute>>
where
F: FnMut(&IpRoute) -> bool,
{
let mut out: SmallVec<IpRoute> = SmallVec::new();
family_unavailable_to_empty(route::walk_route_table(
AF_INET,
|index, flags, dst, gw, mask| {
let dst = dst.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
if let Some(r) = build_routev4(index, flags, dst, gw, mask) {
let r = IpRoute::V4(r);
if f(&r) {
out.push(r);
}
}
},
))?;
family_unavailable_to_empty(route::walk_route_table(
AF_INET6,
|index, flags, dst, gw, mask| {
let dst = dst.unwrap_or(IpAddr::V6(Ipv6Addr::UNSPECIFIED));
if let Some(r) = build_routev6(index, flags, dst, gw, mask) {
let r = IpRoute::V6(r);
if f(&r) {
out.push(r);
}
}
},
))?;
Ok(out)
}
pub(super) fn route_ipv4_table_by_filter<F>(mut f: F) -> io::Result<SmallVec<Ipv4Route>>
where
F: FnMut(&Ipv4Route) -> bool,
{
let mut out: SmallVec<Ipv4Route> = SmallVec::new();
route::walk_route_table(AF_INET, |index, flags, dst, gw, mask| {
let dst = dst.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
if let Some(r) = build_routev4(index, flags, dst, gw, mask) {
if f(&r) {
out.push(r);
}
}
})?;
Ok(out)
}
pub(super) fn route_ipv6_table_by_filter<F>(mut f: F) -> io::Result<SmallVec<Ipv6Route>>
where
F: FnMut(&Ipv6Route) -> bool,
{
let mut out: SmallVec<Ipv6Route> = SmallVec::new();
route::walk_route_table(AF_INET6, |index, flags, dst, gw, mask| {
let dst = dst.unwrap_or(IpAddr::V6(Ipv6Addr::UNSPECIFIED));
if let Some(r) = build_routev6(index, flags, dst, gw, mask) {
if f(&r) {
out.push(r);
}
}
})?;
Ok(out)
}
#[path = "bsd_like/compat.rs"]
mod compat;
#[path = "bsd_like/local_addr.rs"]
mod local_addr;
#[path = "bsd_like/route.rs"]
mod route;
#[path = "bsd_like/rt_generic.rs"]
mod rt_generic;
#[cfg(target_vendor = "apple")]
const KERNAL_ALIGN: usize = 4;
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd",))]
const KERNAL_ALIGN: usize = core::mem::size_of::<usize>();
#[cfg(target_os = "netbsd")]
const KERNAL_ALIGN: usize = 8;
fn invalid_address() -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, "invalid address")
}
fn invalid_message() -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, "invalid message")
}
fn message_too_short() -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, "message too short")
}
bitflags::bitflags! {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Flags: u32 {
const UP = 0x1;
const BROADCAST = 0x2;
const DEBUG = 0x4;
const LOOPBACK = 0x8;
const POINTOPOINT = 0x10;
const NOTRAILERS = 0x20;
const RUNNING = 0x40;
const NOARP = 0x80;
const PROMISC = 0x100;
const ALLMULTI = 0x200;
const OACTIVE = 0x400;
const SIMPLEX = 0x800;
const LINK0 = 0x1000;
const LINK1 = 0x2000;
const LINK2 = 0x4000;
const ALTPHYS = 0x4000;
const MULTICAST = 0x8000;
}
}
fn parse(mut b: &[u8]) -> io::Result<(SmolStr, Option<MacAddr>)> {
if b.len() < 8 {
return Err(invalid_address());
}
b = &b[4..];
let (mut nlen, mut alen, mut slen) = (b[1] as usize, b[2] as usize, b[3] as usize);
if nlen == 0xff {
nlen = 0
}
if alen == 0xff {
alen = 0
}
if slen == 0xff {
slen = 0
}
let l = 4 + nlen + alen + slen;
if b.len() < l {
return Err(invalid_address());
}
let mut data = &b[4..];
let name = if nlen > 0 {
let name = core::str::from_utf8(&data[..nlen])
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
data = &data[nlen..];
SmolStr::from(name)
} else {
SmolStr::default()
};
let addr = if alen == MAC_ADDRESS_SIZE {
Some(MacAddr::from_raw(data[..alen].try_into().unwrap()))
} else {
None
};
Ok((name, addr))
}
fn parse_kernel_inet_addr(b: &[u8]) -> io::Result<(usize, IpAddr)> {
#[cfg(any(target_os = "macos", target_os = "ios"))]
let l = {
let mut l = b[0] as usize;
if l == 0 || b.len() > roundup(l) {
l = roundup(l);
}
l
};
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
let l = roundup(b[0] as usize);
if b.len() < l {
return Err(invalid_address());
}
const OFF4: usize = 4; const OFF6: usize = 8;
match () {
() if b[0] as usize == size_of::<libc::sockaddr_in>() => {
let mut ip = [0u8; 4];
ip.copy_from_slice(&b[OFF4..OFF4 + 4]);
Ok((b[0] as usize, IpAddr::V4(ip.into())))
}
() if b[0] as usize == size_of::<libc::sockaddr_in6>() => {
let mut ip = [0u8; 16];
ip.copy_from_slice(&b[OFF6..OFF6 + 16]);
Ok((b[0] as usize, IpAddr::V6(ip.into())))
}
_ => {
let mut ip = [0u8; 4];
let remaining = l - 1;
if remaining < OFF4 {
ip[..remaining].copy_from_slice(&b[1..l]);
} else {
ip.copy_from_slice(&b[l - OFF4..l]);
}
Ok((b[0] as usize, IpAddr::V4(ip.into())))
}
}
}
#[inline]
const fn roundup(l: usize) -> usize {
if l == 0 {
return KERNAL_ALIGN;
}
(l + KERNAL_ALIGN - 1) & !(KERNAL_ALIGN - 1)
}
const SOCK4: usize = size_of::<libc::sockaddr_in>();
const SOCK6: usize = size_of::<libc::sockaddr_in6>();
fn parse_short_inet_addr(af: i32, sa: &[u8]) -> io::Result<IpAddr> {
match af {
AF_INET => {
const OFF: usize = 4;
let mut ip = [0u8; 4];
if sa.len() > OFF {
let n = (sa.len() - OFF).min(4);
ip[..n].copy_from_slice(&sa[OFF..OFF + n]);
}
Ok(IpAddr::V4(ip.into()))
}
AF_INET6 => {
const OFF: usize = 8;
let mut ip = [0u8; 16];
if sa.len() > OFF {
let n = (sa.len() - OFF).min(16);
ip[..n].copy_from_slice(&sa[OFF..OFF + n]);
}
Ok(IpAddr::V6(Ipv6Addr::from(ip)))
}
_ => Err(invalid_address()),
}
}
fn parse_inet_addr(af: i32, b: &[u8]) -> io::Result<(usize, IpAddr)> {
match af {
AF_INET => {
if b.len() < SOCK4 {
return Err(invalid_address());
}
let sockaddr: libc::sockaddr_in =
unsafe { core::ptr::read_unaligned(b.as_ptr() as *const libc::sockaddr_in) };
Ok((
SOCK4,
IpAddr::V4(sockaddr.sin_addr.s_addr.to_ne_bytes().into()),
))
}
AF_INET6 => {
if b.len() < SOCK6 {
return Err(invalid_address());
}
let sockaddr: libc::sockaddr_in6 =
unsafe { core::ptr::read_unaligned(b.as_ptr() as *const libc::sockaddr_in6) };
let mut ip = sockaddr.sin6_addr.s6_addr;
let _zone_id = sockaddr.sin6_scope_id;
let mut addr: Ipv6Addr = ip.into();
if ip[0] == 0xfe && ip[1] & 0xc0 == 0x80
|| ip[0] == 0xff && (ip[1] & 0x0f == 0x01 || ip[1] & 0x0f == 0x02)
{
let id = u16::from_be_bytes([ip[2], ip[3]]);
if id != 0 {
ip[2] = 0;
ip[3] = 0;
addr = ip.into();
}
}
Ok((SOCK6, addr.into()))
}
_ => Err(invalid_address()),
}
}
pub(super) fn parse_addrs(
addrs: u32,
mut b: &[u8],
) -> io::Result<[Option<IpAddr>; RTAX_MAX as usize]> {
let mut as_ = [None; RTAX_MAX as usize];
#[allow(clippy::needless_range_loop)]
for i in 0..RTAX_MAX as usize {
if addrs & (1 << i) == 0 {
continue;
}
if b.len() < KERNAL_ALIGN {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"message too short",
));
}
if i <= RTAX_BRD as usize {
match b[1] as i32 {
AF_LINK => {
let l = roundup(b[0] as usize);
if b.len() < l {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"message too short",
));
}
b = &b[l..];
}
AF_INET | AF_INET6 => {
let af = b[1] as i32;
let sa_len = b[0] as usize;
let needed = if af == AF_INET { SOCK4 } else { SOCK6 };
let l = roundup(sa_len);
if b.len() < l || b.len() < sa_len {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"message too short",
));
}
let addr = if sa_len >= needed {
let (_, a) = parse_inet_addr(af, b)?;
a
} else if i == RTAX_NETMASK as usize {
parse_short_inet_addr(af, &b[..sa_len])?
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"short sockaddr outside RTAX_NETMASK",
));
};
as_[i] = Some(addr);
b = &b[l..];
}
_ => {
let (l, addr) = parse_kernel_inet_addr(b)?;
as_[i] = Some(addr);
let ll = roundup(l);
if b.len() < ll {
b = &b[l..];
} else {
b = &b[ll..];
}
}
}
} else {
let l = roundup(b[0] as usize);
if b.len() < l {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"message too short",
));
}
b = &b[l..];
}
}
Ok(as_)
}
fn fetch(family: i32, rt: i32, flag: i32) -> io::Result<Vec<u8>> {
unsafe {
let mut mib = [CTL_NET, AF_ROUTE, 0, family, rt, flag];
let mut len: size_t = 0;
if sysctl(mib.as_mut_ptr(), 6, null_mut(), &mut len, null_mut(), 0) < 0 {
return Err(io::Error::last_os_error());
}
let mut buf = vec![0u8; len];
if sysctl(
mib.as_mut_ptr(),
6,
buf.as_mut_ptr() as *mut c_void,
&mut len,
null_mut(),
0,
) < 0
{
return Err(io::Error::last_os_error());
}
buf.truncate(len);
Ok(buf)
}
}
pub(super) fn interface_table(idx: u32) -> io::Result<TinyVec<Interface>> {
unsafe {
let buf = fetch(AF_UNSPEC, NET_RT_IFLIST, idx as i32)?;
let mut results = TinyVec::new();
let mut src = buf.as_slice();
while src.len() > 4 {
let l = u16::from_ne_bytes(src[..2].try_into().unwrap()) as usize;
if l == 0 {
return Err(invalid_message());
}
if src.len() < l {
return Err(message_too_short());
}
if src[2] as i32 != libc::RTM_VERSION {
src = &src[l..];
continue;
}
if src[3] as i32 == libc::RTM_IFINFO {
const HEADER_SIZE: usize = size_of::<if_msghdr>();
if l < HEADER_SIZE {
return Err(message_too_short());
}
let ifm: if_msghdr = core::ptr::read_unaligned(src.as_ptr() as *const if_msghdr);
if ifm.ifm_type as i32 == RTM_IFINFO {
let (name, mac) = parse(&src[HEADER_SIZE..l])?;
let interface = Interface {
index: ifm.ifm_index as u32,
mtu: ifm.ifm_data.ifi_mtu as u32,
name,
mac_addr: mac,
flags: Flags::from_bits_truncate(ifm.ifm_flags as u32),
};
results.push(interface);
}
}
src = &src[l..];
}
Ok(results)
}
}
pub(super) fn interface_ipv4_addresses<F>(idx: u32, f: F) -> io::Result<SmallVec<Ifv4Net>>
where
F: FnMut(&IpAddr) -> bool,
{
interface_addr_table(AF_INET, idx, f)
}
pub(super) fn interface_ipv6_addresses<F>(idx: u32, f: F) -> io::Result<SmallVec<Ifv6Net>>
where
F: FnMut(&IpAddr) -> bool,
{
interface_addr_table(AF_INET6, idx, f)
}
pub(super) fn interface_addresses<F>(idx: u32, f: F) -> io::Result<SmallVec<IfNet>>
where
F: FnMut(&IpAddr) -> bool,
{
interface_addr_table(AF_UNSPEC, idx, f)
}
pub(super) fn interface_addr_table<T, F>(family: i32, idx: u32, f: F) -> io::Result<SmallVec<T>>
where
T: Net,
F: FnMut(&IpAddr) -> bool,
{
let mut out = SmallVec::new();
interface_addr_table_into(family, idx, f, &mut out)?;
Ok(out)
}
pub(super) fn interface_addr_table_into<T, F>(
family: i32,
idx: u32,
mut f: F,
results: &mut SmallVec<T>,
) -> io::Result<()>
where
T: Net,
F: FnMut(&IpAddr) -> bool,
{
const HEADER_SIZE: usize = mem::size_of::<ifa_msghdr>();
unsafe {
let buf = fetch(family, NET_RT_IFLIST, idx as i32)?;
let mut b = buf.as_slice();
while b.len() > HEADER_SIZE {
let ifam: ifa_msghdr = core::ptr::read_unaligned(b.as_ptr() as *const ifa_msghdr);
let len = ifam.ifam_msglen as usize;
if len < HEADER_SIZE || len > b.len() {
return Err(message_too_short());
}
if (ifam.ifam_version as i32 != RTM_VERSION) || (ifam.ifam_index as u32 != idx && idx != 0) {
b = &b[len..];
continue;
}
if ifam.ifam_type as i32 == RTM_NEWADDR {
let addrs = parse_addrs(ifam.ifam_addrs as u32, &b[HEADER_SIZE..len])?;
let mask = addrs[RTAX_NETMASK as usize]
.as_ref()
.map(|ip| ip_mask_to_prefix(*ip));
let ip: Option<IpAddr> = addrs[RTAX_IFA as usize].as_ref().map(|ip| *ip);
if let (Some(ip), Some(Ok(prefix))) = (ip, mask) {
if let Some(ifa) =
T::try_from_with_filter(ifam.ifam_index as u32, ip, prefix, |addr| f(addr))
{
results.push(ifa);
}
}
}
b = &b[len..];
}
Ok(())
}
}
cfg_bsd_multicast!(
pub(super) fn interface_multicast_ipv4_addresses<F>(
idx: u32,
mut f: F,
) -> io::Result<SmallVec<Ifv4Addr>>
where
F: FnMut(&std::net::Ipv4Addr) -> bool,
{
interface_multiaddr_table(AF_INET, idx, |addr| match addr {
IpAddr::V4(ip) => f(ip),
_ => false,
})
}
pub(super) fn interface_multicast_ipv6_addresses<F>(
idx: u32,
mut f: F,
) -> io::Result<SmallVec<Ifv6Addr>>
where
F: FnMut(&Ipv6Addr) -> bool,
{
interface_multiaddr_table(AF_INET6, idx, |addr| match addr {
IpAddr::V6(ip) => f(ip),
_ => false,
})
}
pub(super) fn interface_multicast_addresses<F>(idx: u32, f: F) -> io::Result<SmallVec<IfAddr>>
where
F: FnMut(&IpAddr) -> bool,
{
interface_multiaddr_table(AF_UNSPEC, idx, f)
}
);
cfg_apple!(
pub(super) fn interface_multiaddr_table<T, F>(
family: i32,
idx: u32,
mut f: F,
) -> io::Result<SmallVec<T>>
where
T: Address,
F: FnMut(&IpAddr) -> bool,
{
const HEADER_SIZE: usize = mem::size_of::<libc::ifma_msghdr2>();
unsafe {
let buf = fetch(family, NET_RT_IFLIST2, idx as i32)?;
let mut results = SmallVec::new();
let mut b = buf.as_slice();
while b.len() > HEADER_SIZE {
let ifam: libc::ifma_msghdr2 =
core::ptr::read_unaligned(b.as_ptr() as *const libc::ifma_msghdr2);
let len = ifam.ifmam_msglen as usize;
if len < HEADER_SIZE || len > b.len() {
return Err(message_too_short());
}
if ifam.ifmam_version as i32 != RTM_VERSION {
b = &b[len..];
continue;
}
if ifam.ifmam_type as i32 == libc::RTM_NEWMADDR2 {
let addrs = parse_addrs(ifam.ifmam_addrs as u32, &b[HEADER_SIZE..len])?;
if let Some(ip) = addrs[RTAX_IFA as usize].as_ref() {
if let Some(ip) = T::try_from_with_filter(ifam.ifmam_index as u32, *ip, |addr| f(addr))
{
results.push(ip);
}
}
}
b = &b[len..];
}
Ok(results)
}
}
);
#[cfg(target_os = "freebsd")]
pub(super) fn interface_multiaddr_table<T, F>(
family: i32,
idx: u32,
mut f: F,
) -> io::Result<SmallVec<T>>
where
T: Address,
F: FnMut(&IpAddr) -> bool,
{
use compat::{IfmaMsghdr, NET_RT_IFMALIST};
const HEADER_SIZE: usize = mem::size_of::<IfmaMsghdr>();
unsafe {
let buf = fetch(family, NET_RT_IFMALIST, idx as i32)?;
let mut results = SmallVec::new();
let mut b = buf.as_slice();
while b.len() > HEADER_SIZE {
let ifam: IfmaMsghdr = core::ptr::read_unaligned(b.as_ptr() as *const IfmaMsghdr);
let len = ifam.ifmam_msglen as usize;
if len < HEADER_SIZE || len > b.len() {
return Err(message_too_short());
}
if ifam.ifmam_version as i32 != RTM_VERSION {
b = &b[len..];
continue;
}
if ifam.ifmam_type as i32 == libc::RTM_NEWMADDR {
let addrs = parse_addrs(ifam.ifmam_addrs as u32, &b[HEADER_SIZE..len])?;
if let Some(ip) = addrs[RTAX_IFA as usize].as_ref() {
if let Some(ip) = T::try_from_with_filter(ifam.ifmam_index as u32, *ip, |addr| f(addr)) {
results.push(ip);
}
}
}
b = &b[len..];
}
Ok(results)
}
}
#[cfg(target_os = "dragonfly")]
pub(super) fn interface_multiaddr_table<T, F>(
_family: i32,
_idx: u32,
_f: F,
) -> io::Result<SmallVec<T>>
where
T: Address,
F: FnMut(&IpAddr) -> bool,
{
Err(io::Error::new(
io::ErrorKind::Unsupported,
"multicast group enumeration is not supported on DragonFly \
(no NET_RT_IFMALIST sysctl selector)",
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn family_unavailable_collapses_known_errnos() {
for c in [libc::EAFNOSUPPORT, libc::EPROTONOSUPPORT, libc::EOPNOTSUPP] {
let e = io::Error::from_raw_os_error(c);
let r = family_unavailable_to_empty(Err(e));
assert!(r.is_ok(), "expected Ok for errno {c}");
}
}
#[test]
fn family_unavailable_propagates_other_errnos() {
let e = io::Error::from_raw_os_error(libc::EINVAL);
let r = family_unavailable_to_empty(Err(e));
assert!(r.is_err());
}
#[test]
fn family_unavailable_passthrough_ok() {
assert!(family_unavailable_to_empty(Ok(())).is_ok());
}
#[test]
fn roundup_matches_kernel_alignment() {
assert_eq!(roundup(0), KERNAL_ALIGN);
assert_eq!(roundup(KERNAL_ALIGN), KERNAL_ALIGN);
assert_eq!(roundup(2 * KERNAL_ALIGN), 2 * KERNAL_ALIGN);
assert_eq!(roundup(1), KERNAL_ALIGN);
assert_eq!(roundup(KERNAL_ALIGN + 1), 2 * KERNAL_ALIGN);
}
#[test]
fn parse_short_inet_addr_zero_extends_v4() {
let bytes = [7u8, libc::AF_INET as u8, 0, 0, 255, 255, 255];
let ip = parse_short_inet_addr(libc::AF_INET, &bytes).unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)));
}
#[test]
fn parse_short_inet_addr_zero_extends_v6() {
let mut bytes = [0u8; 9];
bytes[0] = 9;
bytes[1] = libc::AF_INET6 as u8;
bytes[8] = 0xff;
let ip = parse_short_inet_addr(libc::AF_INET6, &bytes).unwrap();
assert!(matches!(ip, IpAddr::V6(_)));
}
#[test]
fn parse_short_inet_addr_unknown_family_errors() {
let bytes = [4u8, 99, 0, 0]; assert!(parse_short_inet_addr(99, &bytes).is_err());
}
#[test]
fn parse_inet_addr_truncated_v4_errors() {
let buf = [0u8; 4];
assert!(parse_inet_addr(libc::AF_INET, &buf).is_err());
}
#[test]
fn parse_inet_addr_truncated_v6_errors() {
let buf = [0u8; 8];
assert!(parse_inet_addr(libc::AF_INET6, &buf).is_err());
}
#[test]
fn parse_inet_addr_unknown_family_errors() {
let buf = [0u8; 32];
assert!(parse_inet_addr(0xff, &buf).is_err());
}
}