use std::{io, net::IpAddr};
use libc::{
NET_RT_DUMP, RTAX_DST, RTAX_GATEWAY, RTAX_NETMASK, RTF_BLACKHOLE, RTF_BROADCAST, RTF_REJECT,
RTF_UP, RTM_GET,
};
#[cfg(any(
apple,
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd"
))]
use libc::RTF_MULTICAST;
#[cfg(target_os = "netbsd")]
const RTF_MULTICAST: libc::c_int = 0;
use super::{compat::RtMsghdr, fetch, message_too_short, parse_addrs};
pub(super) fn walk_route_table<F>(family: i32, mut on_route: F) -> io::Result<()>
where
F: FnMut(u32, libc::c_int, Option<IpAddr>, Option<IpAddr>, Option<IpAddr>),
{
let buf = fetch(family, NET_RT_DUMP, 0)?;
unsafe {
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 {
break;
}
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 != RTM_GET {
src = &src[l..];
continue;
}
let header_size = std::mem::size_of::<RtMsghdr>();
if l < header_size {
return Err(message_too_short());
}
let rtm: RtMsghdr = std::ptr::read_unaligned(src.as_ptr() as *const RtMsghdr);
let unusable = RTF_REJECT | RTF_BLACKHOLE | RTF_BROADCAST | RTF_MULTICAST;
if (rtm.rtm_flags & RTF_UP) == 0 || (rtm.rtm_flags & unusable) != 0 {
src = &src[l..];
continue;
}
#[cfg(target_os = "netbsd")]
{
if (rtm.rtm_flags & libc::RTF_SRC) != 0 {
src = &src[l..];
continue;
}
}
#[cfg(target_os = "openbsd")]
{
let src_mask = (1u32 << libc::RTAX_SRC as u32) | (1u32 << libc::RTAX_SRCMASK as u32);
if (rtm.rtm_addrs as u32 & src_mask) != 0 {
src = &src[l..];
continue;
}
}
let addrs = parse_addrs(rtm.rtm_addrs as u32, &src[header_size..l])?;
let dst = addrs[RTAX_DST as usize];
let gateway = addrs[RTAX_GATEWAY as usize];
let netmask = addrs[RTAX_NETMASK as usize];
on_route(rtm.rtm_index as u32, rtm.rtm_flags, dst, gateway, netmask);
src = &src[l..];
}
}
Ok(())
}