use ipnet::ip_mask_to_prefix;
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, Ipv6Addr},
ptr::null_mut,
};
use super::{IfNet, Ifv4Net, Ifv6Net, Interface, MacAddr, Net, MAC_ADDRESS_SIZE};
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
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::*;
#[path = "bsd_like/compat.rs"]
mod compat;
#[path = "bsd_like/local_addr.rs"]
mod local_addr;
#[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")
}
#[inline]
fn invalid_mask(e: ipnet::PrefixLenError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, e)
}
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)
}
fn parse_inet_addr(af: i32, b: &[u8]) -> io::Result<(usize, IpAddr)> {
const SOCK4: usize = size_of::<libc::sockaddr_in>();
const SOCK6: usize = size_of::<libc::sockaddr_in6>();
match af {
AF_INET => {
if b.len() < SOCK4 {
return Err(invalid_address());
}
let sockaddr = unsafe { &*(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 = unsafe { &*(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()),
}
}
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 b.len() < KERNAL_ALIGN {
break;
}
if addrs & (1 << i) == 0 {
continue;
}
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 (_, addr) = parse_inet_addr(b[1] as i32, b)?;
as_[i] = Some(addr);
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..];
}
_ => {
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());
}
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 {
let ifm = &*(src.as_ptr() as *const if_msghdr);
if ifm.ifm_type as i32 == RTM_IFINFO {
let (name, mac) = parse(&src[size_of::<if_msghdr>()..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, mut f: F) -> io::Result<SmallVec<T>>
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 results = SmallVec::new();
let mut b = buf.as_slice();
while b.len() > HEADER_SIZE {
let ifam = &*(b.as_ptr() as *const ifa_msghdr);
let len = ifam.ifam_msglen as usize;
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(mask)) = (ip, mask) {
if let Some(ifa) = T::try_from_with_filter(
ifam.ifam_index as u32,
ip,
mask.map_err(invalid_mask)?,
|addr| f(addr),
) {
results.push(ifa);
}
}
}
b = &b[len..];
}
Ok(results)
}
}
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 = &*(b.as_ptr() as *const libc::ifma_msghdr2);
let len = ifam.ifmam_msglen as usize;
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,
{
const HEADER_SIZE: usize = mem::size_of::<libc::ifma_msghdr>();
unsafe {
let buf = fetch(family, libc::NET_RT_IFMALIST, idx as i32)?;
let mut results = SmallVec::new();
let mut b = buf.as_slice();
while b.len() > HEADER_SIZE {
let ifam = &*(b.as_ptr() as *const libc::ifma_msghdr);
let len = ifam.ifmam_msglen as usize;
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)
}
}