use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use libc::{
AF_INET, AF_INET6, NET_RT_DUMP, RTAX_DST, RTF_BLACKHOLE, RTF_BROADCAST, RTF_REJECT, RTF_UP,
};
#[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 smallvec_wrapper::SmallVec;
use super::{
super::{ipv4_filter_to_ip_filter, ipv6_filter_to_ip_filter, local_ip_filter},
compat::RtMsghdr,
fetch, interface_addr_table_into, interface_addresses, interface_ipv4_addresses,
interface_ipv6_addresses, message_too_short, parse_addrs, IfNet, Ifv4Net, Ifv6Net, Net,
};
pub(crate) fn best_local_ipv4_addrs() -> io::Result<SmallVec<Ifv4Net>> {
let mut out = SmallVec::new();
best_local_addrs_in(AF_INET, &mut out)?;
Ok(out)
}
pub(crate) fn best_local_ipv6_addrs() -> io::Result<SmallVec<Ifv6Net>> {
let mut out = SmallVec::new();
best_local_addrs_in(AF_INET6, &mut out)?;
Ok(out)
}
pub(crate) fn best_local_addrs() -> io::Result<SmallVec<IfNet>> {
let mut out: SmallVec<IfNet> = SmallVec::new();
super::family_unavailable_to_empty(best_local_addrs_in(AF_INET, &mut out))?;
super::family_unavailable_to_empty(best_local_addrs_in(AF_INET6, &mut out))?;
Ok(out)
}
#[cfg(target_os = "openbsd")]
#[inline]
fn route_priority(rtm: &RtMsghdr) -> u8 {
rtm.rtm_priority
}
#[cfg(not(target_os = "openbsd"))]
#[inline]
fn route_priority(_rtm: &RtMsghdr) -> u8 {
0
}
fn best_local_addrs_in<T: Net>(family: i32, out: &mut SmallVec<T>) -> io::Result<()> {
let routes = fetch(family, NET_RT_DUMP, 0)?;
let mut best_oifs: SmallVec<u16> = SmallVec::new();
let mut best_priority: u8 = u8::MAX;
unsafe {
let mut src = routes.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;
}
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 dst_present = (rtm.rtm_addrs as u32 & libc::RTA_DST as u32) != 0;
let is_default = match (family, dst) {
(AF_INET, None) | (AF_INET6, None) if !dst_present => true,
(_, Some(IpAddr::V4(v4))) if v4.is_unspecified() => true,
(_, Some(IpAddr::V6(v6))) if v6.is_unspecified() => true,
_ => false,
};
if is_default {
let prio = route_priority(&rtm);
if prio < best_priority {
best_priority = prio;
best_oifs.clear();
best_oifs.push(rtm.rtm_index);
} else if prio == best_priority {
best_oifs.push(rtm.rtm_index);
}
}
src = &src[l..];
}
}
best_oifs.sort_unstable();
best_oifs.dedup();
for idx in best_oifs {
interface_addr_table_into(family, idx as u32, local_ip_filter, out)?;
}
Ok(())
}
pub(crate) fn local_ipv4_addrs() -> io::Result<SmallVec<Ifv4Net>> {
interface_ipv4_addresses(0, local_ip_filter)
}
pub(crate) fn local_ipv6_addrs() -> io::Result<SmallVec<Ifv6Net>> {
interface_ipv6_addresses(0, local_ip_filter)
}
pub(crate) fn local_addrs() -> io::Result<SmallVec<IfNet>> {
interface_addresses(0, local_ip_filter)
}
pub(crate) fn local_ipv4_addrs_by_filter<F>(f: F) -> io::Result<SmallVec<Ifv4Net>>
where
F: FnMut(&Ipv4Addr) -> bool,
{
let mut f = ipv4_filter_to_ip_filter(f);
interface_ipv4_addresses(0, move |addr| f(addr) && local_ip_filter(addr))
}
pub(crate) fn local_ipv6_addrs_by_filter<F>(f: F) -> io::Result<SmallVec<Ifv6Net>>
where
F: FnMut(&Ipv6Addr) -> bool,
{
let mut f = ipv6_filter_to_ip_filter(f);
interface_ipv6_addresses(0, move |addr| f(addr) && local_ip_filter(addr))
}
pub(crate) fn local_addrs_by_filter<F>(mut f: F) -> io::Result<SmallVec<IfNet>>
where
F: FnMut(&IpAddr) -> bool,
{
interface_addresses(0, |addr| f(addr) && local_ip_filter(addr))
}