use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use smallvec_wrapper::SmallVec;
use super::{
super::{ipv4_filter_to_ip_filter, ipv6_filter_to_ip_filter, local_ip_filter},
interface_addresses, interface_ipv4_addresses, interface_ipv6_addresses, IfNet, Ifv4Net, Ifv6Net,
NO_ERROR,
};
use windows_sys::Win32::NetworkManagement::IpHelper::*;
use windows_sys::Win32::Networking::WinSock::*;
fn best_default_route_interface(family: u16) -> io::Result<SmallVec<u32>> {
unsafe {
let mut forward_ptr: *mut MIB_IPFORWARD_TABLE2 = std::ptr::null_mut();
let r = GetIpForwardTable2(family, &mut forward_ptr);
if r != NO_ERROR {
return classify_table_error(r);
}
struct ForwardGuard(*mut MIB_IPFORWARD_TABLE2);
impl Drop for ForwardGuard {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { FreeMibTable(self.0 as *mut _) };
}
}
}
let _g1 = ForwardGuard(forward_ptr);
let mut iface_ptr: *mut MIB_IPINTERFACE_TABLE = std::ptr::null_mut();
let r2 = GetIpInterfaceTable(family, &mut iface_ptr);
if r2 != NO_ERROR {
return classify_table_error(r2);
}
struct IfaceGuard(*mut MIB_IPINTERFACE_TABLE);
impl Drop for IfaceGuard {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { FreeMibTable(self.0 as *mut _) };
}
}
}
let _g2 = IfaceGuard(iface_ptr);
let mut iface_state: std::collections::HashMap<u32, (u32, bool)> =
std::collections::HashMap::new();
if !iface_ptr.is_null() {
let it = &*iface_ptr;
let rows = core::slice::from_raw_parts(
&it.Table as *const _ as *const MIB_IPINTERFACE_ROW,
it.NumEntries as usize,
);
for r in rows {
iface_state.insert(r.InterfaceIndex, (r.Metric, r.Connected));
}
}
let mut best_eff: u64 = u64::MAX;
let mut best_oifs: SmallVec<u32> = SmallVec::new();
if !forward_ptr.is_null() {
let ft = &*forward_ptr;
let rows = core::slice::from_raw_parts(
&ft.Table as *const _ as *const MIB_IPFORWARD_ROW2,
ft.NumEntries as usize,
);
for row in rows {
if row.DestinationPrefix.PrefixLength != 0 {
continue;
}
if row.ValidLifetime == 0 || row.Loopback {
continue;
}
let if_m = match iface_state.get(&row.InterfaceIndex) {
Some(&(metric, connected)) if connected => metric as u64,
_ => continue,
};
let eff = row.Metric as u64 + if_m;
if eff < best_eff {
best_eff = eff;
best_oifs.clear();
best_oifs.push(row.InterfaceIndex);
} else if eff == best_eff {
best_oifs.push(row.InterfaceIndex);
}
}
}
best_oifs.sort_unstable();
best_oifs.dedup();
Ok(best_oifs)
}
}
#[inline]
fn classify_table_error(code: u32) -> io::Result<SmallVec<u32>> {
const ERROR_NOT_SUPPORTED: u32 = 50;
const ERROR_NOT_FOUND: u32 = 1168;
const ERROR_NETWORK_UNREACHABLE: u32 = 1231;
match code {
ERROR_NOT_SUPPORTED | ERROR_NOT_FOUND | ERROR_NETWORK_UNREACHABLE => Ok(SmallVec::new()),
_ => Err(io::Error::from_raw_os_error(code as i32)),
}
}
pub(crate) fn best_local_ipv4_addrs() -> io::Result<SmallVec<Ifv4Net>> {
let mut out: SmallVec<Ifv4Net> = SmallVec::new();
for idx in best_default_route_interface(AF_INET)? {
let v4 = interface_ipv4_addresses(Some(idx), local_ip_filter)?;
for a in v4 {
out.push(a);
}
}
Ok(out)
}
pub(crate) fn best_local_ipv6_addrs() -> io::Result<SmallVec<Ifv6Net>> {
let mut out: SmallVec<Ifv6Net> = SmallVec::new();
for idx in best_default_route_interface(AF_INET6)? {
let v6 = interface_ipv6_addresses(Some(idx), local_ip_filter)?;
for a in v6 {
out.push(a);
}
}
Ok(out)
}
pub(crate) fn best_local_addrs() -> io::Result<SmallVec<IfNet>> {
let mut result: SmallVec<IfNet> = SmallVec::new();
for idx in best_default_route_interface(AF_INET)? {
let v4 = interface_ipv4_addresses(Some(idx), local_ip_filter)?;
for a in v4 {
result.push(a.into());
}
}
for idx in best_default_route_interface(AF_INET6)? {
let v6 = interface_ipv6_addresses(Some(idx), local_ip_filter)?;
for a in v6 {
result.push(a.into());
}
}
Ok(result)
}
pub(crate) fn local_ipv4_addrs() -> io::Result<SmallVec<Ifv4Net>> {
interface_ipv4_addresses(None, local_ip_filter)
}
pub(crate) fn local_ipv6_addrs() -> io::Result<SmallVec<Ifv6Net>> {
interface_ipv6_addresses(None, local_ip_filter)
}
pub(crate) fn local_addrs() -> io::Result<SmallVec<IfNet>> {
interface_addresses(None, 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(None, 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(None, 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(None, |addr| f(addr) && local_ip_filter(addr))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_table_error_whitelist_returns_empty() {
for code in [50u32, 1168u32, 1231u32] {
let r = classify_table_error(code).unwrap();
assert!(r.is_empty(), "expected empty result for whitelisted {code}");
}
}
#[test]
fn classify_table_error_unknown_propagates() {
let r = classify_table_error(0xDEAD);
assert!(r.is_err());
assert_eq!(r.unwrap_err().raw_os_error(), Some(0xDEAD));
}
}