windows upnp work
This commit is contained in:
@@ -40,6 +40,7 @@ pub mod log_thru;
|
||||
pub mod must_join_handle;
|
||||
pub mod must_join_single_future;
|
||||
pub mod mutable_future;
|
||||
pub mod network_interfaces;
|
||||
pub mod network_result;
|
||||
pub mod random;
|
||||
pub mod single_shot_eventual;
|
||||
@@ -182,6 +183,8 @@ pub use must_join_single_future::*;
|
||||
#[doc(inline)]
|
||||
pub use mutable_future::*;
|
||||
#[doc(inline)]
|
||||
pub use network_interfaces::*;
|
||||
#[doc(inline)]
|
||||
pub use network_result::*;
|
||||
#[doc(inline)]
|
||||
pub use random::*;
|
||||
|
||||
516
veilid-tools/src/network_interfaces/apple.rs
Normal file
516
veilid-tools/src/network_interfaces/apple.rs
Normal file
@@ -0,0 +1,516 @@
|
||||
#![cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#![allow(non_camel_case_types)]
|
||||
use super::*;
|
||||
|
||||
use libc::{
|
||||
close, freeifaddrs, getifaddrs, if_nametoindex, ifaddrs, ioctl, pid_t, sockaddr, sockaddr_in6,
|
||||
socket, sysctl, time_t, AF_INET6, CTL_NET, IFF_BROADCAST, IFF_LOOPBACK, IFF_POINTOPOINT,
|
||||
IFF_RUNNING, IFNAMSIZ, NET_RT_FLAGS, PF_ROUTE, RTAX_DST, RTAX_GATEWAY, RTAX_MAX, RTA_DST,
|
||||
RTA_GATEWAY, RTF_GATEWAY, SOCK_DGRAM,
|
||||
};
|
||||
use sockaddr_tools::SockAddr;
|
||||
use std::ffi::CStr;
|
||||
use std::io;
|
||||
use std::os::raw::{c_int, c_uchar, c_ulong, c_ushort, c_void};
|
||||
|
||||
const SIOCGIFAFLAG_IN6: c_ulong = 0xC1206949;
|
||||
const SIOCGIFALIFETIME_IN6: c_ulong = 0xC1206951;
|
||||
const IN6_IFF_TENTATIVE: c_ushort = 0x0002;
|
||||
const IN6_IFF_DUPLICATED: c_ushort = 0x0004;
|
||||
const IN6_IFF_DETACHED: c_ushort = 0x0008;
|
||||
const IN6_IFF_AUTOCONF: c_ushort = 0x0040;
|
||||
const IN6_IFF_TEMPORARY: c_ushort = 0x0080;
|
||||
const IN6_IFF_DEPRECATED: c_ushort = 0x0010;
|
||||
const IN6_IFF_DYNAMIC: c_ushort = 0x0100;
|
||||
const IN6_IFF_SECURED: c_ushort = 0x0400;
|
||||
|
||||
macro_rules! set_name {
|
||||
($name_field:expr, $name_str:expr) => {{
|
||||
let name_c = &::std::ffi::CString::new($name_str.to_owned()).map_err(|_| {
|
||||
::std::io::Error::new(
|
||||
::std::io::ErrorKind::InvalidInput,
|
||||
"malformed interface name",
|
||||
)
|
||||
})?;
|
||||
let name_slice = name_c.as_bytes_with_nul();
|
||||
if name_slice.len() > IFNAMSIZ {
|
||||
return Err(io::Error::new(::std::io::ErrorKind::InvalidInput, ""));
|
||||
}
|
||||
$name_field[..name_slice.len()].clone_from_slice(name_slice);
|
||||
|
||||
Ok(())
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! round_up {
|
||||
($a:expr) => {
|
||||
if $a > 0 {
|
||||
1 + (($a - 1) | 3)
|
||||
} else {
|
||||
4
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
struct rt_msghdr {
|
||||
rtm_msglen: c_ushort,
|
||||
rtm_version: c_uchar,
|
||||
rtm_type: c_uchar,
|
||||
rtm_index: c_ushort,
|
||||
rtm_flags: c_int,
|
||||
rtm_addrs: c_int,
|
||||
rtm_pid: pid_t,
|
||||
rtm_seq: c_int,
|
||||
rtm_errno: c_int,
|
||||
rtm_use: c_int,
|
||||
rtm_inits: u32,
|
||||
rtm_rmx: rt_metrics,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
struct rt_metrics {
|
||||
rmx_locks: u32,
|
||||
rmx_mtu: u32,
|
||||
rmx_hopcount: u32,
|
||||
rmx_expire: i32,
|
||||
rmx_recvpipe: u32,
|
||||
rmx_sendpipe: u32,
|
||||
rmx_ssthresh: u32,
|
||||
rmx_rtt: u32,
|
||||
rmx_rttvar: u32,
|
||||
rmx_pksent: u32,
|
||||
rmx_state: u32,
|
||||
rmx_filler: [u32; 3],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
struct in6_addrlifetime {
|
||||
ia6t_expire: time_t,
|
||||
ia6t_preferred: time_t,
|
||||
ia6t_vltime: u32,
|
||||
ia6t_pltime: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
struct in6_ifstat {
|
||||
ifs6_in_receive: u64,
|
||||
ifs6_in_hdrerr: u64,
|
||||
ifs6_in_toobig: u64,
|
||||
ifs6_in_noroute: u64,
|
||||
ifs6_in_addrerr: u64,
|
||||
ifs6_in_protounknown: u64,
|
||||
ifs6_in_truncated: u64,
|
||||
ifs6_in_discard: u64,
|
||||
ifs6_in_deliver: u64,
|
||||
ifs6_out_forward: u64,
|
||||
ifs6_out_request: u64,
|
||||
ifs6_out_discard: u64,
|
||||
ifs6_out_fragok: u64,
|
||||
ifs6_out_fragfail: u64,
|
||||
ifs6_out_fragcreat: u64,
|
||||
ifs6_reass_reqd: u64,
|
||||
ifs6_reass_ok: u64,
|
||||
ifs6_atmfrag_rcvd: u64,
|
||||
ifs6_reass_fail: u64,
|
||||
ifs6_in_mcast: u64,
|
||||
ifs6_out_mcast: u64,
|
||||
ifs6_cantfoward_icmp6: u64,
|
||||
ifs6_addr_expiry_cnt: u64,
|
||||
ifs6_pfx_expiry_cnt: u64,
|
||||
ifs6_defrtr_expiry_cnt: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
struct icmp6_ifstat {
|
||||
ifs6_in_msg: u64,
|
||||
ifs6_in_error: u64,
|
||||
ifs6_in_dstunreach: u64,
|
||||
ifs6_in_adminprohib: u64,
|
||||
ifs6_in_timeexceed: u64,
|
||||
ifs6_in_paramprob: u64,
|
||||
ifs6_in_pkttoobig: u64,
|
||||
ifs6_in_echo: u64,
|
||||
ifs6_in_echoreply: u64,
|
||||
ifs6_in_routersolicit: u64,
|
||||
ifs6_in_routeradvert: u64,
|
||||
ifs6_in_neighborsolicit: u64,
|
||||
ifs6_in_neighboradvert: u64,
|
||||
ifs6_in_redirect: u64,
|
||||
ifs6_in_mldquery: u64,
|
||||
ifs6_in_mldreport: u64,
|
||||
ifs6_in_mlddone: u64,
|
||||
ifs6_out_msg: u64,
|
||||
ifs6_out_error: u64,
|
||||
ifs6_out_dstunreach: u64,
|
||||
ifs6_out_adminprohib: u64,
|
||||
ifs6_out_timeexceed: u64,
|
||||
ifs6_out_paramprob: u64,
|
||||
ifs6_out_pkttoobig: u64,
|
||||
ifs6_out_echo: u64,
|
||||
ifs6_out_echoreply: u64,
|
||||
ifs6_out_routersolicit: u64,
|
||||
ifs6_out_routeradvert: u64,
|
||||
ifs6_out_neighborsolicit: u64,
|
||||
ifs6_out_neighboradvert: u64,
|
||||
ifs6_out_redirect: u64,
|
||||
ifs6_out_mldquery: u64,
|
||||
ifs6_out_mldreport: u64,
|
||||
ifs6_out_mlddone: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
union IfrIfru {
|
||||
ifru_addr: sockaddr_in6,
|
||||
ifru_dstaddr: sockaddr_in6,
|
||||
ifru_flags: c_int,
|
||||
ifru_flags6: c_int,
|
||||
ifru_metric: c_int,
|
||||
ifru_intval: c_int,
|
||||
ifru_data: *mut c_uchar, // caddr_t
|
||||
ifru_lifetime: in6_addrlifetime,
|
||||
ifru_stat: in6_ifstat,
|
||||
ifru_icmp6stat: icmp6_ifstat,
|
||||
ifru_scope_id: [u32; 16],
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(C)]
|
||||
struct in6_ifreq {
|
||||
ifr_name: [c_uchar; IFNAMSIZ],
|
||||
ifr_ifru: IfrIfru,
|
||||
}
|
||||
|
||||
impl in6_ifreq {
|
||||
pub fn from_name(name: &str) -> io::Result<Self> {
|
||||
let mut req: in6_ifreq = unsafe { mem::zeroed() };
|
||||
req.set_name(name)?;
|
||||
Ok(req)
|
||||
}
|
||||
pub fn set_name(&mut self, name: &str) -> io::Result<()> {
|
||||
set_name!(self.ifr_name, name)
|
||||
}
|
||||
pub fn set_addr(&mut self, addr: sockaddr_in6) {
|
||||
self.ifr_ifru.ifru_addr = addr;
|
||||
}
|
||||
pub fn get_flags6(&self) -> c_ushort {
|
||||
unsafe { self.ifr_ifru.ifru_flags6 as c_ushort }
|
||||
}
|
||||
pub fn get_ia6t_expire(&self) -> time_t {
|
||||
unsafe { self.ifr_ifru.ifru_lifetime.ia6t_expire as time_t }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_broadcast(ifaddr: &ifaddrs) -> Option<IpAddr> {
|
||||
sockaddr_tools::to_ipaddr(ifaddr.ifa_dstaddr)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
pub struct IfAddrs {
|
||||
inner: *mut ifaddrs,
|
||||
}
|
||||
|
||||
impl IfAddrs {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let mut ifaddrs = mem::MaybeUninit::uninit();
|
||||
|
||||
unsafe {
|
||||
if -1 == getifaddrs(ifaddrs.as_mut_ptr()) {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(Self {
|
||||
inner: ifaddrs.assume_init(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> IfAddrsIterator {
|
||||
IfAddrsIterator { next: self.inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IfAddrs {
|
||||
#[allow(unsafe_code)]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
freeifaddrs(self.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IfAddrsIterator {
|
||||
next: *mut ifaddrs,
|
||||
}
|
||||
|
||||
impl Iterator for IfAddrsIterator {
|
||||
type Item = ifaddrs;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.next.is_null() {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(unsafe {
|
||||
let result = *self.next;
|
||||
self.next = (*self.next).ifa_next;
|
||||
|
||||
result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
pub struct PlatformSupportApple {
|
||||
default_route_interfaces: BTreeSet<u32>,
|
||||
}
|
||||
|
||||
impl PlatformSupportApple {
|
||||
pub fn new() -> Self {
|
||||
PlatformSupportApple {
|
||||
default_route_interfaces: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn refresh_default_route_interfaces(&mut self) -> EyreResult<()> {
|
||||
self.default_route_interfaces.clear();
|
||||
|
||||
let mut mib = [CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY];
|
||||
let mut sa_tab: [*const sockaddr; RTAX_MAX as usize] =
|
||||
[std::ptr::null(); RTAX_MAX as usize];
|
||||
let mut rt_buf_len = 0usize;
|
||||
|
||||
// Get memory size for mib result
|
||||
if unsafe {
|
||||
sysctl(
|
||||
mib.as_mut_ptr(),
|
||||
mib.len() as u32,
|
||||
std::ptr::null_mut(),
|
||||
&mut rt_buf_len as *mut usize,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
} < 0
|
||||
{
|
||||
bail!("Unable to get memory size for routing table");
|
||||
}
|
||||
|
||||
// Allocate a buffer
|
||||
let mut rt_buf = vec![0u8; rt_buf_len];
|
||||
|
||||
// Get mib result
|
||||
if unsafe {
|
||||
sysctl(
|
||||
mib.as_mut_ptr(),
|
||||
mib.len() as u32,
|
||||
rt_buf.as_mut_ptr() as *mut c_void,
|
||||
&mut rt_buf_len as *mut usize,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
} < 0
|
||||
{
|
||||
bail!("Unable to get memory size for routing table");
|
||||
}
|
||||
|
||||
// Process each routing message
|
||||
let mut mib_ptr = rt_buf.as_ptr();
|
||||
let mib_end = unsafe { mib_ptr.add(rt_buf_len) };
|
||||
while mib_ptr < mib_end {
|
||||
let rt = mib_ptr as *const rt_msghdr;
|
||||
let mut sa = unsafe { rt.add(1) } as *const sockaddr;
|
||||
let rtm_addrs = unsafe { (*rt).rtm_addrs };
|
||||
let intf_index = unsafe { (*rt).rtm_index } as u32;
|
||||
|
||||
// Fill in sockaddr table
|
||||
(0..(RTAX_MAX as usize)).for_each(|i| {
|
||||
if rtm_addrs & (1 << i) != 0 {
|
||||
sa_tab[i] = sa;
|
||||
sa = unsafe {
|
||||
let sa_len = (*sa).sa_len;
|
||||
sa = ((sa as *const u8).add(round_up!(sa_len as usize))) as *const sockaddr;
|
||||
sa
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Look for gateways
|
||||
if rtm_addrs & (RTA_DST | RTA_GATEWAY) == (RTA_DST | RTA_GATEWAY) {
|
||||
// Only interested in AF_INET and AF_INET6 address families
|
||||
// SockAddr::new() takes care of this for us
|
||||
let saddr_dst = match SockAddr::new(sa_tab[RTAX_DST as usize]) {
|
||||
Some(a) => a,
|
||||
None => continue,
|
||||
};
|
||||
let _saddr_gateway = match SockAddr::new(sa_tab[RTAX_GATEWAY as usize]) {
|
||||
Some(a) => a,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// Look for default gateways
|
||||
let dst_ipaddr = match saddr_dst.as_ipaddr() {
|
||||
Some(a) => a,
|
||||
None => continue,
|
||||
};
|
||||
if dst_ipaddr.is_unspecified() {
|
||||
self.default_route_interfaces.insert(intf_index);
|
||||
}
|
||||
}
|
||||
|
||||
mib_ptr = unsafe { mib_ptr.add((*rt).rtm_msglen.into()) };
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_interface_flags(&self, index: u32, flags: c_int) -> EyreResult<InterfaceFlags> {
|
||||
Ok(InterfaceFlags {
|
||||
is_loopback: (flags & IFF_LOOPBACK) != 0,
|
||||
is_running: (flags & IFF_RUNNING) != 0,
|
||||
is_point_to_point: (flags & IFF_POINTOPOINT) != 0,
|
||||
has_default_route: self.default_route_interfaces.contains(&index),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_address_flags(ifname: &str, addr: sockaddr_in6) -> EyreResult<AddressFlags> {
|
||||
let sock = unsafe { socket(AF_INET6, SOCK_DGRAM, 0) };
|
||||
if sock < 0 {
|
||||
bail!("Socket error {:?}", io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let mut req = in6_ifreq::from_name(ifname).unwrap();
|
||||
req.set_addr(addr);
|
||||
|
||||
let res = unsafe { ioctl(sock, SIOCGIFAFLAG_IN6, &mut req) };
|
||||
if res < 0 {
|
||||
unsafe { close(sock) };
|
||||
bail!(
|
||||
"SIOCGIFAFLAG_IN6 failed with error on device '{}': {:?}",
|
||||
ifname,
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
let flags = req.get_flags6();
|
||||
|
||||
let mut req = in6_ifreq::from_name(ifname).unwrap();
|
||||
req.set_addr(addr);
|
||||
|
||||
let res = unsafe { ioctl(sock, SIOCGIFALIFETIME_IN6, &mut req) };
|
||||
unsafe { close(sock) };
|
||||
if res < 0 {
|
||||
bail!(
|
||||
"SIOCGIFALIFETIME_IN6 failed with error on device '{}': {:?}",
|
||||
ifname,
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
let expire = req.get_ia6t_expire();
|
||||
|
||||
let is_auto_generated_random_address =
|
||||
flags & (IN6_IFF_SECURED | IN6_IFF_AUTOCONF) == (IN6_IFF_SECURED | IN6_IFF_AUTOCONF);
|
||||
|
||||
let is_temporary =
|
||||
(flags & IN6_IFF_TEMPORARY) != 0 || (expire != 0 && is_auto_generated_random_address);
|
||||
let is_dynamic = (flags & (IN6_IFF_DYNAMIC | IN6_IFF_AUTOCONF)) != 0;
|
||||
let is_preferred = (flags
|
||||
& (IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED | IN6_IFF_DETACHED | IN6_IFF_DEPRECATED))
|
||||
== 0;
|
||||
|
||||
Ok(AddressFlags {
|
||||
is_temporary,
|
||||
is_dynamic,
|
||||
is_preferred,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_interfaces(
|
||||
&mut self,
|
||||
interfaces: &mut BTreeMap<String, NetworkInterface>,
|
||||
) -> io::Result<()> {
|
||||
self.refresh_default_route_interfaces().await?;
|
||||
|
||||
// Ask for all the addresses we have
|
||||
let ifaddrs = IfAddrs::new()?;
|
||||
for ifaddr in ifaddrs.iter() {
|
||||
// Get the interface name
|
||||
let ifname = unsafe { CStr::from_ptr(ifaddr.ifa_name) }
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
// Get the interface index
|
||||
let ifindex = unsafe { if_nametoindex(ifaddr.ifa_name) };
|
||||
|
||||
// Map the name to a NetworkInterface
|
||||
if !interfaces.contains_key(&ifname) {
|
||||
// If we have no NetworkInterface yet, make one
|
||||
let flags = self.get_interface_flags(ifindex, ifaddr.ifa_flags as c_int)?;
|
||||
interfaces.insert(ifname.clone(), NetworkInterface::new(ifname.clone(), flags));
|
||||
}
|
||||
let intf = interfaces.get_mut(&ifname).unwrap();
|
||||
|
||||
let mut address_flags = AddressFlags::default();
|
||||
|
||||
let intf_addr = match sockaddr_tools::to_ipaddr(ifaddr.ifa_addr) {
|
||||
None => continue,
|
||||
Some(IpAddr::V4(ipv4_addr)) => {
|
||||
let netmask = match sockaddr_tools::to_ipaddr(ifaddr.ifa_netmask) {
|
||||
Some(IpAddr::V4(netmask)) => netmask,
|
||||
_ => Ipv4Addr::new(0, 0, 0, 0),
|
||||
};
|
||||
let broadcast = if (ifaddr.ifa_flags & (IFF_BROADCAST as u32)) != 0 {
|
||||
match do_broadcast(&ifaddr) {
|
||||
Some(IpAddr::V4(broadcast)) => Some(broadcast),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
IfAddr::V4(Ifv4Addr {
|
||||
ip: ipv4_addr,
|
||||
netmask,
|
||||
broadcast,
|
||||
})
|
||||
}
|
||||
Some(IpAddr::V6(ipv6_addr)) => {
|
||||
let netmask = match sockaddr_tools::to_ipaddr(ifaddr.ifa_netmask) {
|
||||
Some(IpAddr::V6(netmask)) => netmask,
|
||||
_ => Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0),
|
||||
};
|
||||
|
||||
// Get address flags for ipv6
|
||||
address_flags = match Self::get_address_flags(
|
||||
&ifname,
|
||||
SockAddr::new(ifaddr.ifa_addr).unwrap().sa_in6(),
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log_net!(error "failed to get address flags: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
IfAddr::V6(Ifv6Addr {
|
||||
ip: ipv6_addr,
|
||||
netmask,
|
||||
broadcast: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// Add to the list
|
||||
intf.addrs
|
||||
.push(InterfaceAddress::new(intf_addr, address_flags));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
434
veilid-tools/src/network_interfaces/mod.rs
Normal file
434
veilid-tools/src/network_interfaces/mod.rs
Normal file
@@ -0,0 +1,434 @@
|
||||
mod apple;
|
||||
mod netlink;
|
||||
mod sockaddr_tools;
|
||||
mod tools;
|
||||
mod windows;
|
||||
|
||||
use crate::*;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "android"))] {
|
||||
use self::netlink::PlatformSupportNetlink as PlatformSupport;
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
use self::windows::PlatformSupportWindows as PlatformSupport;
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "ios"))] {
|
||||
use self::apple::PlatformSupportApple as PlatformSupport;
|
||||
} else {
|
||||
compile_error!("No network interfaces support for this platform!");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)]
|
||||
pub enum IfAddr {
|
||||
V4(Ifv4Addr),
|
||||
V6(Ifv6Addr),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl IfAddr {
|
||||
pub fn ip(&self) -> IpAddr {
|
||||
match *self {
|
||||
IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.ip),
|
||||
IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.ip),
|
||||
}
|
||||
}
|
||||
pub fn netmask(&self) -> IpAddr {
|
||||
match *self {
|
||||
IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.netmask),
|
||||
IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.netmask),
|
||||
}
|
||||
}
|
||||
pub fn broadcast(&self) -> Option<IpAddr> {
|
||||
match *self {
|
||||
IfAddr::V4(ref ifv4_addr) => ifv4_addr.broadcast.map(IpAddr::V4),
|
||||
IfAddr::V6(ref ifv6_addr) => ifv6_addr.broadcast.map(IpAddr::V6),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Details about the ipv4 address of an interface on this host.
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)]
|
||||
pub struct Ifv4Addr {
|
||||
/// The IP address of the interface.
|
||||
pub ip: Ipv4Addr,
|
||||
/// The netmask of the interface.
|
||||
pub netmask: Ipv4Addr,
|
||||
/// The broadcast address of the interface.
|
||||
pub broadcast: Option<Ipv4Addr>,
|
||||
}
|
||||
|
||||
/// Details about the ipv6 address of an interface on this host.
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)]
|
||||
pub struct Ifv6Addr {
|
||||
/// The IP address of the interface.
|
||||
pub ip: Ipv6Addr,
|
||||
/// The netmask of the interface.
|
||||
pub netmask: Ipv6Addr,
|
||||
/// The broadcast address of the interface.
|
||||
pub broadcast: Option<Ipv6Addr>,
|
||||
}
|
||||
|
||||
/// Some of the flags associated with an interface.
|
||||
#[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
pub struct InterfaceFlags {
|
||||
pub is_loopback: bool,
|
||||
pub is_running: bool,
|
||||
pub is_point_to_point: bool,
|
||||
pub has_default_route: bool,
|
||||
}
|
||||
|
||||
/// Some of the flags associated with an address.
|
||||
#[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
pub struct AddressFlags {
|
||||
// common flags
|
||||
pub is_dynamic: bool,
|
||||
// ipv6 flags
|
||||
pub is_temporary: bool,
|
||||
pub is_preferred: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct InterfaceAddress {
|
||||
if_addr: IfAddr,
|
||||
flags: AddressFlags,
|
||||
}
|
||||
|
||||
use core::cmp::Ordering;
|
||||
|
||||
// less is less preferable, greater is more preferable
|
||||
impl Ord for InterfaceAddress {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (&self.if_addr, &other.if_addr) {
|
||||
(IfAddr::V4(a), IfAddr::V4(b)) => {
|
||||
// global scope addresses are better
|
||||
let ret = ipv4addr_is_global(&a.ip).cmp(&ipv4addr_is_global(&b.ip));
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
// local scope addresses are better
|
||||
let ret = ipv4addr_is_private(&a.ip).cmp(&ipv4addr_is_private(&b.ip));
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
// non-dynamic addresses are better
|
||||
let ret = (!self.flags.is_dynamic).cmp(&!other.flags.is_dynamic);
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
(IfAddr::V6(a), IfAddr::V6(b)) => {
|
||||
// preferred addresses are better
|
||||
let ret = self.flags.is_preferred.cmp(&other.flags.is_preferred);
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
// non-temporary address are better
|
||||
let ret = (!self.flags.is_temporary).cmp(&!other.flags.is_temporary);
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
// global scope addresses are better
|
||||
let ret = ipv6addr_is_global(&a.ip).cmp(&ipv6addr_is_global(&b.ip));
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
// unique local unicast addresses are better
|
||||
let ret = ipv6addr_is_unique_local(&a.ip).cmp(&ipv6addr_is_unique_local(&b.ip));
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
// unicast site local addresses are better
|
||||
let ret = ipv6addr_is_unicast_site_local(&a.ip)
|
||||
.cmp(&ipv6addr_is_unicast_site_local(&b.ip));
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
// unicast link local addresses are better
|
||||
let ret = ipv6addr_is_unicast_link_local(&a.ip)
|
||||
.cmp(&ipv6addr_is_unicast_link_local(&b.ip));
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
// non-dynamic addresses are better
|
||||
let ret = (!self.flags.is_dynamic).cmp(&!other.flags.is_dynamic);
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
(IfAddr::V4(a), IfAddr::V6(b)) => {
|
||||
// If the IPv6 address is preferred and not temporary, compare if it is global scope
|
||||
if other.flags.is_preferred && !other.flags.is_temporary {
|
||||
let ret = ipv4addr_is_global(&a.ip).cmp(&ipv6addr_is_global(&b.ip));
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Default, prefer IPv4 because many IPv6 addresses are not actually routed
|
||||
return Ordering::Greater;
|
||||
}
|
||||
(IfAddr::V6(a), IfAddr::V4(b)) => {
|
||||
// If the IPv6 address is preferred and not temporary, compare if it is global scope
|
||||
if self.flags.is_preferred && !self.flags.is_temporary {
|
||||
let ret = ipv6addr_is_global(&a.ip).cmp(&ipv4addr_is_global(&b.ip));
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Default, prefer IPv4 because many IPv6 addresses are not actually routed
|
||||
return Ordering::Less;
|
||||
}
|
||||
}
|
||||
// stable sort
|
||||
let ret = self.if_addr.cmp(&other.if_addr);
|
||||
if ret != Ordering::Equal {
|
||||
return ret;
|
||||
}
|
||||
self.flags.cmp(&other.flags)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for InterfaceAddress {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl InterfaceAddress {
|
||||
pub fn new(if_addr: IfAddr, flags: AddressFlags) -> Self {
|
||||
Self { if_addr, flags }
|
||||
}
|
||||
|
||||
pub fn if_addr(&self) -> &IfAddr {
|
||||
&self.if_addr
|
||||
}
|
||||
|
||||
pub fn is_temporary(&self) -> bool {
|
||||
self.flags.is_temporary
|
||||
}
|
||||
pub fn is_dynamic(&self) -> bool {
|
||||
self.flags.is_dynamic
|
||||
}
|
||||
pub fn is_preferred(&self) -> bool {
|
||||
self.flags.is_preferred
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
// enum NetworkInterfaceType {
|
||||
// Mobile, // Least preferable, usually metered and slow
|
||||
// Unknown, // Everything else if we can't detect the type
|
||||
// Wireless, // Wifi is usually free or cheap and medium speed
|
||||
// Wired, // Wired is usually free or cheap and high speed
|
||||
// }
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct NetworkInterface {
|
||||
pub name: String,
|
||||
pub flags: InterfaceFlags,
|
||||
pub addrs: Vec<InterfaceAddress>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for NetworkInterface {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("NetworkInterface")
|
||||
.field("name", &self.name)
|
||||
.field("flags", &self.flags)
|
||||
.field("addrs", &self.addrs)
|
||||
.finish()?;
|
||||
if f.alternate() {
|
||||
writeln!(f)?;
|
||||
writeln!(f, "// primary_ipv4: {:?}", self.primary_ipv4())?;
|
||||
writeln!(f, "// primary_ipv6: {:?}", self.primary_ipv6())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
impl NetworkInterface {
|
||||
pub fn new(name: String, flags: InterfaceFlags) -> Self {
|
||||
Self {
|
||||
name,
|
||||
flags,
|
||||
addrs: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
pub fn is_loopback(&self) -> bool {
|
||||
self.flags.is_loopback
|
||||
}
|
||||
|
||||
pub fn is_point_to_point(&self) -> bool {
|
||||
self.flags.is_point_to_point
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.flags.is_running
|
||||
}
|
||||
|
||||
pub fn has_default_route(&self) -> bool {
|
||||
self.flags.has_default_route
|
||||
}
|
||||
|
||||
pub fn primary_ipv4(&self) -> Option<InterfaceAddress> {
|
||||
let mut ipv4addrs: Vec<&InterfaceAddress> = self
|
||||
.addrs
|
||||
.iter()
|
||||
.filter(|a| matches!(a.if_addr(), IfAddr::V4(_)))
|
||||
.collect();
|
||||
ipv4addrs.sort();
|
||||
ipv4addrs.last().cloned().cloned()
|
||||
}
|
||||
|
||||
pub fn primary_ipv6(&self) -> Option<InterfaceAddress> {
|
||||
let mut ipv6addrs: Vec<&InterfaceAddress> = self
|
||||
.addrs
|
||||
.iter()
|
||||
.filter(|a| matches!(a.if_addr(), IfAddr::V6(_)))
|
||||
.collect();
|
||||
ipv6addrs.sort();
|
||||
ipv6addrs.last().cloned().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetworkInterfacesInner {
|
||||
valid: bool,
|
||||
interfaces: BTreeMap<String, NetworkInterface>,
|
||||
interface_address_cache: Vec<IpAddr>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NetworkInterfaces {
|
||||
inner: Arc<Mutex<NetworkInterfacesInner>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for NetworkInterfaces {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let inner = self.inner.lock();
|
||||
f.debug_struct("NetworkInterfaces")
|
||||
.field("valid", &inner.valid)
|
||||
.field("interfaces", &inner.interfaces)
|
||||
.finish()?;
|
||||
if f.alternate() {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"// stable_addresses: {:?}",
|
||||
inner.interface_address_cache
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NetworkInterfaces {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkInterfaces {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(NetworkInterfacesInner {
|
||||
valid: false,
|
||||
interfaces: BTreeMap::new(),
|
||||
interface_address_cache: Vec::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
let inner = self.inner.lock();
|
||||
inner.valid
|
||||
}
|
||||
pub fn clear(&self) {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
inner.interfaces.clear();
|
||||
inner.interface_address_cache.clear();
|
||||
inner.valid = false;
|
||||
}
|
||||
// returns false if refresh had no changes, true if changes were present
|
||||
pub async fn refresh(&self) -> std::io::Result<bool> {
|
||||
let mut last_interfaces = {
|
||||
let mut last_interfaces = BTreeMap::<String, NetworkInterface>::new();
|
||||
let mut platform_support = PlatformSupport::new();
|
||||
platform_support
|
||||
.get_interfaces(&mut last_interfaces)
|
||||
.await?;
|
||||
last_interfaces
|
||||
};
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
core::mem::swap(&mut inner.interfaces, &mut last_interfaces);
|
||||
inner.valid = true;
|
||||
|
||||
if last_interfaces != inner.interfaces {
|
||||
// get last address cache
|
||||
let old_stable_addresses = inner.interface_address_cache.clone();
|
||||
|
||||
// redo the address cache
|
||||
Self::cache_stable_addresses(&mut inner);
|
||||
|
||||
// See if our best addresses have changed
|
||||
if old_stable_addresses != inner.interface_address_cache {
|
||||
debug!(
|
||||
"Network interface addresses changed: \nFrom: {:?}\n To: {:?}\n",
|
||||
old_stable_addresses, inner.interface_address_cache
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
pub fn with_interfaces<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&BTreeMap<String, NetworkInterface>) -> R,
|
||||
{
|
||||
let inner = self.inner.lock();
|
||||
f(&inner.interfaces)
|
||||
}
|
||||
|
||||
pub fn stable_addresses(&self) -> Vec<IpAddr> {
|
||||
let inner = self.inner.lock();
|
||||
inner.interface_address_cache.clone()
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
|
||||
fn cache_stable_addresses(inner: &mut NetworkInterfacesInner) {
|
||||
// Reduce interfaces to their best routable ip addresses
|
||||
let mut intf_addrs = Vec::new();
|
||||
for intf in inner.interfaces.values() {
|
||||
if !intf.is_running()
|
||||
|| !intf.has_default_route()
|
||||
|| intf.is_loopback()
|
||||
|| intf.is_point_to_point()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(pipv4) = intf.primary_ipv4() {
|
||||
// Skip temporary addresses because they're going to change
|
||||
if !pipv4.is_temporary() {
|
||||
intf_addrs.push(pipv4);
|
||||
}
|
||||
}
|
||||
if let Some(pipv6) = intf.primary_ipv6() {
|
||||
// Skip temporary addresses because they're going to change
|
||||
if !pipv6.is_temporary() {
|
||||
intf_addrs.push(pipv6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort one more time to get the best interface addresses overall
|
||||
intf_addrs.sort();
|
||||
|
||||
// Now export just the addresses
|
||||
inner.interface_address_cache = intf_addrs.iter().map(|x| x.if_addr().ip()).collect()
|
||||
}
|
||||
}
|
||||
335
veilid-tools/src/network_interfaces/netlink.rs
Normal file
335
veilid-tools/src/network_interfaces/netlink.rs
Normal file
@@ -0,0 +1,335 @@
|
||||
#![cfg(any(target_os = "linux", target_os = "android"))]
|
||||
|
||||
use super::*;
|
||||
|
||||
use alloc::collections::btree_map::Entry;
|
||||
use futures_util::stream::TryStreamExt;
|
||||
use ifstructs::ifreq;
|
||||
use libc::{
|
||||
close, if_indextoname, ioctl, socket, IFF_LOOPBACK, IFF_POINTOPOINT, IFF_RUNNING, IF_NAMESIZE,
|
||||
SIOCGIFFLAGS, SOCK_DGRAM,
|
||||
};
|
||||
use netlink_packet_route::{
|
||||
nlas::address::Nla, AddressMessage, AF_INET, AF_INET6, IFA_F_DADFAILED, IFA_F_DEPRECATED,
|
||||
IFA_F_OPTIMISTIC, IFA_F_PERMANENT, IFA_F_TEMPORARY, IFA_F_TENTATIVE,
|
||||
};
|
||||
use rtnetlink::{new_connection_with_socket, Handle, IpVersion};
|
||||
cfg_if! {
|
||||
if #[cfg(feature="rt-async-std")] {
|
||||
use netlink_sys::{SmolSocket as RTNetLinkSocket};
|
||||
} else if #[cfg(feature="rt-tokio")] {
|
||||
use netlink_sys::{TokioSocket as RTNetLinkSocket};
|
||||
} else {
|
||||
compile_error!("needs executor implementation")
|
||||
}
|
||||
}
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CStr;
|
||||
use std::io;
|
||||
use std::os::raw::c_int;
|
||||
use tools::*;
|
||||
|
||||
fn get_interface_name(index: u32) -> io::Result<String> {
|
||||
let mut ifnamebuf = [0u8; (IF_NAMESIZE + 1)];
|
||||
cfg_if! {
|
||||
if #[cfg(all(any(target_os = "android", target_os="linux"), any(target_arch = "arm", target_arch = "aarch64")))] {
|
||||
if unsafe { if_indextoname(index, ifnamebuf.as_mut_ptr()) }.is_null() {
|
||||
bail!("if_indextoname returned null");
|
||||
}
|
||||
} else {
|
||||
if unsafe { if_indextoname(index, ifnamebuf.as_mut_ptr() as *mut i8) }.is_null() {
|
||||
bail!("if_indextoname returned null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ifnamebuflen = ifnamebuf
|
||||
.iter()
|
||||
.position(|c| *c == 0u8)
|
||||
.ok_or_else(|| eyre!("null not found in interface name"))?;
|
||||
let ifname_str = CStr::from_bytes_with_nul(&ifnamebuf[0..=ifnamebuflen])
|
||||
.wrap_err("failed to convert interface name")?
|
||||
.to_str()
|
||||
.wrap_err("invalid characters in interface name")?;
|
||||
Ok(ifname_str.to_owned())
|
||||
}
|
||||
|
||||
fn flags_to_address_flags(flags: u32) -> AddressFlags {
|
||||
AddressFlags {
|
||||
is_temporary: (flags & IFA_F_TEMPORARY) != 0,
|
||||
is_dynamic: (flags & IFA_F_PERMANENT) == 0,
|
||||
is_preferred: (flags
|
||||
& (IFA_F_TENTATIVE | IFA_F_DADFAILED | IFA_F_DEPRECATED | IFA_F_OPTIMISTIC))
|
||||
== 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlatformSupportNetlink {
|
||||
connection_jh: Option<MustJoinHandle<()>>,
|
||||
handle: Option<Handle>,
|
||||
default_route_interfaces: BTreeSet<u32>,
|
||||
}
|
||||
|
||||
impl PlatformSupportNetlink {
|
||||
pub fn new() -> Self {
|
||||
PlatformSupportNetlink {
|
||||
connection_jh: None,
|
||||
handle: None,
|
||||
default_route_interfaces: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out which interfaces have default routes
|
||||
async fn refresh_default_route_interfaces(&mut self) -> EyreResult<()> {
|
||||
self.default_route_interfaces.clear();
|
||||
let mut routesv4 = self
|
||||
.handle
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.route()
|
||||
.get(IpVersion::V4)
|
||||
.execute();
|
||||
while let Some(routev4) = routesv4.try_next().await.unwrap_or_default() {
|
||||
if let Some(index) = routev4.output_interface() {
|
||||
//println!("*** ipv4 route: {:#?}", routev4);
|
||||
if routev4.header.destination_prefix_length == 0 {
|
||||
self.default_route_interfaces.insert(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut routesv6 = self
|
||||
.handle
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.route()
|
||||
.get(IpVersion::V6)
|
||||
.execute();
|
||||
while let Some(routev6) = routesv6.try_next().await.unwrap_or_default() {
|
||||
if let Some(index) = routev6.output_interface() {
|
||||
//println!("*** ipv6 route: {:#?}", routev6);
|
||||
if routev6.header.destination_prefix_length == 0 {
|
||||
self.default_route_interfaces.insert(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_interface_flags(&self, index: u32, ifname: &str) -> EyreResult<InterfaceFlags> {
|
||||
let mut req = ifreq::from_name(ifname).wrap_err("failed to convert interface name")?;
|
||||
|
||||
let sock = unsafe { socket(AF_INET as i32, SOCK_DGRAM, 0) };
|
||||
if sock < 0 {
|
||||
return Err(io::Error::last_os_error()).wrap_err("failed to create socket");
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(target_os = "android", target_env = "musl"))] {
|
||||
let res = unsafe { ioctl(sock, SIOCGIFFLAGS as i32, &mut req) };
|
||||
} else {
|
||||
let res = unsafe { ioctl(sock, SIOCGIFFLAGS, &mut req) };
|
||||
}
|
||||
}
|
||||
unsafe { close(sock) };
|
||||
if res < 0 {
|
||||
return Err(io::Error::last_os_error()).wrap_err("failed to close socket");
|
||||
}
|
||||
|
||||
let flags = req.get_flags() as c_int;
|
||||
|
||||
Ok(InterfaceFlags {
|
||||
is_loopback: (flags & IFF_LOOPBACK) != 0,
|
||||
is_running: (flags & IFF_RUNNING) != 0,
|
||||
is_point_to_point: (flags & IFF_POINTOPOINT) != 0,
|
||||
has_default_route: self.default_route_interfaces.contains(&index),
|
||||
})
|
||||
}
|
||||
|
||||
fn process_address_message_v4(msg: AddressMessage) -> Option<InterfaceAddress> {
|
||||
// Get ip address
|
||||
let ip = msg.nlas.iter().find_map(|nla| {
|
||||
if let Nla::Address(a) = nla {
|
||||
let a: Option<[u8; 4]> = a.clone().try_into().ok();
|
||||
a.map(Ipv4Addr::from)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
// get netmask
|
||||
let plen = msg.header.prefix_len as i16;
|
||||
let mut netmask = [0u8; 4];
|
||||
get_netmask_from_prefix_length_v4(&mut netmask, plen);
|
||||
let netmask = Ipv4Addr::from(netmask);
|
||||
|
||||
// get broadcast address
|
||||
let broadcast = msg.nlas.iter().find_map(|nla| {
|
||||
if let Nla::Broadcast(b) = nla {
|
||||
let b: Option<[u8; 4]> = b.clone().try_into().ok();
|
||||
b.map(Ipv4Addr::from)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// get address flags
|
||||
let flags = msg
|
||||
.nlas
|
||||
.iter()
|
||||
.find_map(|nla| {
|
||||
if let Nla::Flags(f) = nla {
|
||||
Some(*f)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(msg.header.flags as u32);
|
||||
|
||||
Some(InterfaceAddress::new(
|
||||
IfAddr::V4(Ifv4Addr {
|
||||
ip,
|
||||
/// The netmask of the interface.
|
||||
netmask,
|
||||
/// The broadcast address of the interface.
|
||||
broadcast,
|
||||
}),
|
||||
flags_to_address_flags(flags),
|
||||
))
|
||||
}
|
||||
|
||||
fn process_address_message_v6(msg: AddressMessage) -> Option<InterfaceAddress> {
|
||||
// Get ip address
|
||||
let ip = msg.nlas.iter().find_map(|nla| {
|
||||
if let Nla::Address(a) = nla {
|
||||
let a: Option<[u8; 16]> = a.clone().try_into().ok();
|
||||
a.map(Ipv6Addr::from)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
// get netmask
|
||||
let plen = msg.header.prefix_len as i16;
|
||||
let mut netmask = [0u8; 16];
|
||||
get_netmask_from_prefix_length_v6(&mut netmask, plen);
|
||||
let netmask = Ipv6Addr::from(netmask);
|
||||
|
||||
// get address flags
|
||||
let flags = msg
|
||||
.nlas
|
||||
.iter()
|
||||
.find_map(|nla| {
|
||||
if let Nla::Flags(f) = nla {
|
||||
Some(*f)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(msg.header.flags as u32);
|
||||
|
||||
// Skip addresses going through duplicate address detection, or ones that have failed it
|
||||
if ((flags & IFA_F_TENTATIVE) != 0) || ((flags & IFA_F_DADFAILED) != 0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(InterfaceAddress::new(
|
||||
IfAddr::V6(Ifv6Addr {
|
||||
ip,
|
||||
/// The netmask of the interface.
|
||||
netmask,
|
||||
/// The broadcast address of the interface.
|
||||
broadcast: None,
|
||||
}),
|
||||
flags_to_address_flags(flags),
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_interfaces_internal(
|
||||
&mut self,
|
||||
interfaces: &mut BTreeMap<String, NetworkInterface>,
|
||||
) -> io::Result<()> {
|
||||
// Refresh the routes
|
||||
self.refresh_default_route_interfaces().await?;
|
||||
|
||||
// Ask for all the addresses we have
|
||||
let mut names = BTreeMap::<u32, String>::new();
|
||||
let mut addresses = self.handle.as_ref().unwrap().address().get().execute();
|
||||
while let Some(msg) = addresses.try_next().await? {
|
||||
// Have we seen this interface index yet?
|
||||
// Get the name from the index, cached, if we can
|
||||
let ifname = match names.entry(msg.header.index) {
|
||||
Entry::Vacant(v) => {
|
||||
// If not, get the name for the index if we can
|
||||
let ifname = match get_interface_name(msg.header.index) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log_net!(warn
|
||||
"couldn't get interface name for index {}: {}",
|
||||
msg.header.index,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
v.insert(ifname).clone()
|
||||
}
|
||||
Entry::Occupied(o) => o.get().clone(),
|
||||
};
|
||||
|
||||
// Map the name to a NetworkInterface
|
||||
if !interfaces.contains_key(&ifname) {
|
||||
// If we have no NetworkInterface yet, make one
|
||||
let flags = self.get_interface_flags(msg.header.index, &ifname)?;
|
||||
interfaces.insert(ifname.clone(), NetworkInterface::new(ifname.clone(), flags));
|
||||
}
|
||||
let intf = interfaces.get_mut(&ifname).unwrap();
|
||||
|
||||
// Process the address
|
||||
let intf_addr = match msg.header.family as u16 {
|
||||
AF_INET => match Self::process_address_message_v4(msg) {
|
||||
Some(ia) => ia,
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
AF_INET6 => match Self::process_address_message_v6(msg) {
|
||||
Some(ia) => ia,
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
intf.addrs.push(intf_addr);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_interfaces(
|
||||
&mut self,
|
||||
interfaces: &mut BTreeMap<String, NetworkInterface>,
|
||||
) -> io::Result<()> {
|
||||
// Get the netlink connection
|
||||
let (connection, handle, _) = new_connection_with_socket::<RTNetLinkSocket>()?;
|
||||
|
||||
// Spawn a connection handler
|
||||
let connection_jh = spawn(connection);
|
||||
|
||||
// Save the connection
|
||||
self.connection_jh = Some(connection_jh);
|
||||
self.handle = Some(handle);
|
||||
|
||||
// Do the work
|
||||
let out = self.get_interfaces_internal(interfaces).await;
|
||||
|
||||
// Clean up connection
|
||||
drop(self.handle.take());
|
||||
self.connection_jh.take().unwrap().abort().await;
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
103
veilid-tools/src/network_interfaces/sockaddr_tools.rs
Normal file
103
veilid-tools/src/network_interfaces/sockaddr_tools.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2018 MaidSafe.net limited.
|
||||
//
|
||||
// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
|
||||
// http://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD
|
||||
// https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied,
|
||||
// modified, or distributed except according to those terms. Please review the Licences for the
|
||||
// specific language governing permissions and limitations relating to use of the SAFE Network
|
||||
// Software.
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use libc::{sockaddr, sockaddr_in, sockaddr_in6, AF_INET, AF_INET6};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::ptr::NonNull;
|
||||
#[cfg(windows)]
|
||||
use winapi::{
|
||||
shared::ws2def::{AF_INET, AF_INET6, SOCKADDR as sockaddr, SOCKADDR_IN as sockaddr_in},
|
||||
shared::ws2ipdef::SOCKADDR_IN6 as sockaddr_in6,
|
||||
};
|
||||
|
||||
pub fn to_ipaddr(sockaddr: *const sockaddr) -> Option<IpAddr> {
|
||||
if sockaddr.is_null() {
|
||||
return None;
|
||||
}
|
||||
SockAddr::new(sockaddr)?.as_ipaddr()
|
||||
}
|
||||
pub enum SockAddrIn {
|
||||
In(sockaddr_in),
|
||||
In6(sockaddr_in6),
|
||||
}
|
||||
|
||||
// Wrapper around a sockaddr pointer. Guaranteed to not be null.
|
||||
pub struct SockAddr {
|
||||
inner: NonNull<sockaddr>,
|
||||
}
|
||||
|
||||
impl SockAddr {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(sockaddr: *const sockaddr) -> Option<Self> {
|
||||
NonNull::new(sockaddr as *mut _).map(|inner| Self { inner })
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn as_ipaddr(&self) -> Option<IpAddr> {
|
||||
match self.sockaddr_in() {
|
||||
Some(SockAddrIn::In(sa)) => Some(IpAddr::V4(Ipv4Addr::new(
|
||||
((sa.sin_addr.s_addr) & 255) as u8,
|
||||
((sa.sin_addr.s_addr >> 8) & 255) as u8,
|
||||
((sa.sin_addr.s_addr >> 16) & 255) as u8,
|
||||
((sa.sin_addr.s_addr >> 24) & 255) as u8,
|
||||
))),
|
||||
Some(SockAddrIn::In6(sa)) => Some(IpAddr::V6(Ipv6Addr::from(sa.sin6_addr.s6_addr))),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn as_ipaddr(&self) -> Option<IpAddr> {
|
||||
match self.sockaddr_in() {
|
||||
Some(SockAddrIn::In(sa)) => {
|
||||
let s_addr = unsafe { sa.sin_addr.S_un.S_addr() };
|
||||
Some(IpAddr::V4(Ipv4Addr::new(
|
||||
(s_addr & 255u32) as u8,
|
||||
((s_addr >> 8) & 255u32) as u8,
|
||||
((s_addr >> 16) & 255u32) as u8,
|
||||
((s_addr >> 24) & 255u32) as u8,
|
||||
)))
|
||||
}
|
||||
Some(SockAddrIn::In6(sa)) => {
|
||||
let s6_addr = unsafe { sa.sin6_addr.u.Byte() };
|
||||
Some(IpAddr::V6(Ipv6Addr::from(*s6_addr)))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sockaddr_in(&self) -> Option<SockAddrIn> {
|
||||
const AF_INET_U32: u32 = AF_INET as u32;
|
||||
const AF_INET6_U32: u32 = AF_INET6 as u32;
|
||||
|
||||
match self.sa_family() {
|
||||
AF_INET_U32 => Some(SockAddrIn::In(self.sa_in())),
|
||||
AF_INET6_U32 => Some(SockAddrIn::In6(self.sa_in6())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn sa_family(&self) -> u32 {
|
||||
unsafe { u32::from(self.inner.as_ref().sa_family) }
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn sa_in(&self) -> sockaddr_in {
|
||||
unsafe { *(self.inner.as_ptr() as *const sockaddr_in) }
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn sa_in6(&self) -> sockaddr_in6 {
|
||||
unsafe { *(self.inner.as_ptr() as *const sockaddr_in6) }
|
||||
}
|
||||
}
|
||||
47
veilid-tools/src/network_interfaces/tools.rs
Normal file
47
veilid-tools/src/network_interfaces/tools.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub fn convert_to_unsigned_4(x: [i8; 4]) -> [u8; 4] {
|
||||
let mut out: [u8; 4] = [0u8; 4];
|
||||
for i in 0..4 {
|
||||
out[i] = x[i] as u8;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn convert_to_unsigned_16(x: [i8; 16]) -> [u8; 16] {
|
||||
let mut out: [u8; 16] = [0u8; 16];
|
||||
for i in 0..16 {
|
||||
out[i] = x[i] as u8;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn get_netmask_from_prefix_length_v4(out: &mut [u8; 4], mut plen: i16) {
|
||||
for outb in out.iter_mut() {
|
||||
*outb = if plen >= 8 {
|
||||
plen -= 8;
|
||||
255u8
|
||||
} else if plen <= 0 {
|
||||
0u8
|
||||
} else {
|
||||
let v = 255u8 << (8 - plen);
|
||||
plen = 0;
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_netmask_from_prefix_length_v6(out: &mut [u8; 16], mut plen: i16) {
|
||||
for outb in out.iter_mut() {
|
||||
*outb = if plen >= 8 {
|
||||
plen -= 8;
|
||||
255u8
|
||||
} else if plen == 0 {
|
||||
0u8
|
||||
} else {
|
||||
let v = 255u8 << (8 - plen);
|
||||
plen = 0;
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
357
veilid-tools/src/network_interfaces/windows.rs
Normal file
357
veilid-tools/src/network_interfaces/windows.rs
Normal file
@@ -0,0 +1,357 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
// Copyright 2018 MaidSafe.net limited.
|
||||
//
|
||||
// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
|
||||
// http://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD
|
||||
// https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied,
|
||||
// modified, or distributed except according to those terms. Please review the Licences for the
|
||||
// specific language governing permissions and limitations relating to use of the SAFE Network
|
||||
// Software.
|
||||
|
||||
use super::*;
|
||||
|
||||
use libc::{self, c_ulong, c_void, size_t};
|
||||
use std::ffi::CStr;
|
||||
use std::{io, ptr};
|
||||
use winapi::shared::ifdef::IfOperStatusUp;
|
||||
use winapi::shared::ipifcons::{IF_TYPE_SOFTWARE_LOOPBACK, IF_TYPE_TUNNEL};
|
||||
use winapi::shared::nldef::{
|
||||
IpDadStatePreferred, IpPrefixOriginDhcp, IpSuffixOriginDhcp, IpSuffixOriginRandom,
|
||||
};
|
||||
use winapi::shared::winerror::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS};
|
||||
use winapi::um::iphlpapi::GetAdaptersAddresses;
|
||||
use winapi::um::iptypes::{
|
||||
GAA_FLAG_INCLUDE_GATEWAYS, GAA_FLAG_INCLUDE_PREFIX, GAA_FLAG_SKIP_ANYCAST,
|
||||
GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_FRIENDLY_NAME, GAA_FLAG_SKIP_MULTICAST,
|
||||
IP_ADAPTER_ADDRESSES, IP_ADAPTER_PREFIX, IP_ADAPTER_UNICAST_ADDRESS,
|
||||
};
|
||||
|
||||
pub struct PlatformSupportWindows {}
|
||||
|
||||
impl PlatformSupportWindows {
|
||||
pub fn new() -> Self {
|
||||
PlatformSupportWindows {}
|
||||
}
|
||||
|
||||
fn get_interface_flags(intf: &IpAdapterAddresses) -> InterfaceFlags {
|
||||
InterfaceFlags {
|
||||
is_loopback: intf.get_flag_loopback(),
|
||||
is_running: intf.get_flag_running(),
|
||||
is_point_to_point: intf.get_flag_point_to_point(),
|
||||
has_default_route: intf.get_has_default_route(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_address_flags(addr: *const IP_ADAPTER_UNICAST_ADDRESS) -> AddressFlags {
|
||||
let ds = unsafe { (*addr).DadState };
|
||||
let po = unsafe { (*addr).PrefixOrigin };
|
||||
let so = unsafe { (*addr).SuffixOrigin };
|
||||
AddressFlags {
|
||||
is_temporary: so == IpSuffixOriginRandom,
|
||||
is_dynamic: po == IpPrefixOriginDhcp || so == IpSuffixOriginDhcp,
|
||||
is_preferred: ds == IpDadStatePreferred,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_interfaces(
|
||||
&mut self,
|
||||
interfaces: &mut BTreeMap<String, NetworkInterface>,
|
||||
) -> io::Result<()> {
|
||||
// Iterate all the interfaces
|
||||
let windows_interfaces = WindowsInterfaces::new()?;
|
||||
for windows_interface in windows_interfaces.iter() {
|
||||
// Get name
|
||||
let intf_name = windows_interface.name();
|
||||
|
||||
// Get flags
|
||||
let flags = Self::get_interface_flags(&windows_interface);
|
||||
|
||||
let mut network_interface = NetworkInterface::new(intf_name.clone(), flags);
|
||||
|
||||
// Go through all addresses and add them if appropriate
|
||||
for addr in windows_interface.unicast_addresses() {
|
||||
let intf_addr = match sockaddr_tools::to_ipaddr(addr.Address.lpSockaddr) {
|
||||
None => continue,
|
||||
Some(IpAddr::V4(ipv4_addr)) => {
|
||||
let mut item_netmask = Ipv4Addr::new(0, 0, 0, 0);
|
||||
let mut item_broadcast = None;
|
||||
|
||||
// Search prefixes for a prefix matching addr
|
||||
'prefixloopv4: for prefix in windows_interface.prefixes() {
|
||||
let ipprefix = sockaddr_tools::to_ipaddr(prefix.Address.lpSockaddr);
|
||||
match ipprefix {
|
||||
Some(IpAddr::V4(ref a)) => {
|
||||
let mut netmask: [u8; 4] = [0; 4];
|
||||
for (n, netmask_elt) in netmask
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.take((prefix.PrefixLength as usize + 7) / 8)
|
||||
{
|
||||
let x_byte = ipv4_addr.octets()[n];
|
||||
let y_byte = a.octets()[n];
|
||||
for m in 0..8 {
|
||||
if (n * 8) + m > prefix.PrefixLength as usize {
|
||||
break;
|
||||
}
|
||||
let bit = 1 << m;
|
||||
if (x_byte & bit) == (y_byte & bit) {
|
||||
*netmask_elt |= bit;
|
||||
} else {
|
||||
continue 'prefixloopv4;
|
||||
}
|
||||
}
|
||||
}
|
||||
item_netmask = Ipv4Addr::new(
|
||||
netmask[0], netmask[1], netmask[2], netmask[3],
|
||||
);
|
||||
let mut broadcast: [u8; 4] = ipv4_addr.octets();
|
||||
for n in 0..4 {
|
||||
broadcast[n] |= !netmask[n];
|
||||
}
|
||||
item_broadcast = Some(Ipv4Addr::new(
|
||||
broadcast[0],
|
||||
broadcast[1],
|
||||
broadcast[2],
|
||||
broadcast[3],
|
||||
));
|
||||
break 'prefixloopv4;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
IfAddr::V4(Ifv4Addr {
|
||||
ip: ipv4_addr,
|
||||
netmask: item_netmask,
|
||||
broadcast: item_broadcast,
|
||||
})
|
||||
}
|
||||
Some(IpAddr::V6(ipv6_addr)) => {
|
||||
let mut item_netmask = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
// Search prefixes for a prefix matching addr
|
||||
'prefixloopv6: for prefix in windows_interface.prefixes() {
|
||||
let ipprefix = sockaddr_tools::to_ipaddr(prefix.Address.lpSockaddr);
|
||||
match ipprefix {
|
||||
Some(IpAddr::V6(ref a)) => {
|
||||
// Iterate the bits in the prefix, if they all match this prefix
|
||||
// is the right one, else try the next prefix
|
||||
let mut netmask: [u16; 8] = [0; 8];
|
||||
for (n, netmask_elt) in netmask
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.take((prefix.PrefixLength as usize + 15) / 16)
|
||||
{
|
||||
let x_word = ipv6_addr.segments()[n];
|
||||
let y_word = a.segments()[n];
|
||||
for m in 0..16 {
|
||||
if (n * 16) + m > prefix.PrefixLength as usize {
|
||||
break;
|
||||
}
|
||||
let bit = 1 << m;
|
||||
if (x_word & bit) == (y_word & bit) {
|
||||
*netmask_elt |= bit;
|
||||
} else {
|
||||
continue 'prefixloopv6;
|
||||
}
|
||||
}
|
||||
}
|
||||
item_netmask = Ipv6Addr::new(
|
||||
netmask[0], netmask[1], netmask[2], netmask[3], netmask[4],
|
||||
netmask[5], netmask[6], netmask[7],
|
||||
);
|
||||
break 'prefixloopv6;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
IfAddr::V6(Ifv6Addr {
|
||||
ip: ipv6_addr,
|
||||
netmask: item_netmask,
|
||||
broadcast: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let address_flags = Self::get_address_flags(addr);
|
||||
|
||||
network_interface
|
||||
.addrs
|
||||
.push(InterfaceAddress::new(intf_addr, address_flags))
|
||||
}
|
||||
|
||||
interfaces.insert(intf_name, network_interface);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IpAdapterAddresses {
|
||||
data: *const IP_ADAPTER_ADDRESSES,
|
||||
}
|
||||
|
||||
impl IpAdapterAddresses {
|
||||
#[allow(unsafe_code)]
|
||||
pub fn name(&self) -> String {
|
||||
unsafe { CStr::from_ptr((*self.data).AdapterName) }
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
pub fn prefixes(&self) -> PrefixesIterator {
|
||||
PrefixesIterator {
|
||||
_phantom: std::marker::PhantomData {},
|
||||
next: unsafe { (*self.data).FirstPrefix },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unicast_addresses(&self) -> UnicastAddressesIterator {
|
||||
UnicastAddressesIterator {
|
||||
_phantom: std::marker::PhantomData {},
|
||||
next: unsafe { (*self.data).FirstUnicastAddress },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_flag_loopback(&self) -> bool {
|
||||
unsafe { (*self.data).IfType == IF_TYPE_SOFTWARE_LOOPBACK }
|
||||
}
|
||||
pub fn get_flag_running(&self) -> bool {
|
||||
unsafe { (*self.data).OperStatus == IfOperStatusUp }
|
||||
}
|
||||
pub fn get_flag_point_to_point(&self) -> bool {
|
||||
unsafe { (*self.data).IfType == IF_TYPE_TUNNEL }
|
||||
}
|
||||
pub fn get_has_default_route(&self) -> bool {
|
||||
unsafe { !(*self.data).FirstGatewayAddress.is_null() }
|
||||
}
|
||||
}
|
||||
|
||||
struct WindowsInterfaces {
|
||||
data: *const IP_ADAPTER_ADDRESSES,
|
||||
}
|
||||
|
||||
impl WindowsInterfaces {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let mut buffersize: c_ulong = 16384;
|
||||
let mut ifaddrs: *mut IP_ADAPTER_ADDRESSES;
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
ifaddrs = libc::malloc(buffersize as size_t) as *mut IP_ADAPTER_ADDRESSES;
|
||||
if ifaddrs.is_null() {
|
||||
panic!("Failed to allocate buffer in get_if_addrs()");
|
||||
}
|
||||
|
||||
let retcode = GetAdaptersAddresses(
|
||||
0,
|
||||
GAA_FLAG_SKIP_ANYCAST
|
||||
| GAA_FLAG_SKIP_MULTICAST
|
||||
| GAA_FLAG_SKIP_DNS_SERVER
|
||||
| GAA_FLAG_INCLUDE_PREFIX
|
||||
| GAA_FLAG_SKIP_FRIENDLY_NAME
|
||||
| GAA_FLAG_INCLUDE_GATEWAYS,
|
||||
ptr::null_mut(),
|
||||
ifaddrs,
|
||||
&mut buffersize,
|
||||
);
|
||||
|
||||
match retcode {
|
||||
ERROR_SUCCESS => break,
|
||||
ERROR_BUFFER_OVERFLOW => {
|
||||
libc::free(ifaddrs as *mut c_void);
|
||||
buffersize *= 2;
|
||||
continue;
|
||||
}
|
||||
_ => return Err(io::Error::last_os_error()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { data: ifaddrs })
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> WindowsInterfacesIterator<'_> {
|
||||
WindowsInterfacesIterator {
|
||||
next: self.data,
|
||||
_phantom: std::marker::PhantomData {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowsInterfaces {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
libc::free(self.data as *mut c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowsInterfacesIterator<'a> {
|
||||
next: *const IP_ADAPTER_ADDRESSES,
|
||||
_phantom: std::marker::PhantomData<&'a u8>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WindowsInterfacesIterator<'a> {
|
||||
type Item = IpAdapterAddresses;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.next.is_null() {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(unsafe {
|
||||
let result = &*self.next;
|
||||
self.next = (*self.next).Next;
|
||||
|
||||
IpAdapterAddresses { data: result }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrefixesIterator<'a> {
|
||||
_phantom: std::marker::PhantomData<&'a u8>,
|
||||
next: *const IP_ADAPTER_PREFIX,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PrefixesIterator<'a> {
|
||||
type Item = &'a IP_ADAPTER_PREFIX;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.next.is_null() {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(unsafe {
|
||||
let result = &*self.next;
|
||||
self.next = (*self.next).Next;
|
||||
|
||||
result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnicastAddressesIterator<'a> {
|
||||
_phantom: std::marker::PhantomData<&'a u8>,
|
||||
next: *const IP_ADAPTER_UNICAST_ADDRESS,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for UnicastAddressesIterator<'a> {
|
||||
type Item = &'a IP_ADAPTER_UNICAST_ADDRESS;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.next.is_null() {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(unsafe {
|
||||
let result = &*self.next;
|
||||
self.next = (*self.next).Next;
|
||||
|
||||
result
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
mod test_assembly_buffer;
|
||||
mod test_async_peek_stream;
|
||||
mod test_network_interfaces;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -13,6 +14,8 @@ use super::*;
|
||||
pub async fn run_all_tests() {
|
||||
info!("TEST: exec_test_host_interface");
|
||||
test_host_interface::test_all().await;
|
||||
info!("TEST: exec_test_network_interfaces");
|
||||
test_network_interfaces::test_all().await;
|
||||
info!("TEST: exec_test_async_peek_stream");
|
||||
test_async_peek_stream::test_all().await;
|
||||
info!("TEST: exec_test_async_tag_lock");
|
||||
@@ -82,6 +85,15 @@ cfg_if! {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn run_test_network_interfaces() {
|
||||
setup();
|
||||
block_on(async {
|
||||
test_network_interfaces::test_all().await;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn run_test_async_peek_stream() {
|
||||
|
||||
29
veilid-tools/src/tests/native/test_network_interfaces.rs
Normal file
29
veilid-tools/src/tests/native/test_network_interfaces.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::*;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(not(target_arch = "wasm32"))] {
|
||||
use network_interfaces::NetworkInterfaces;
|
||||
|
||||
pub async fn test_network_interfaces() {
|
||||
info!("testing network interfaces");
|
||||
let t1 = get_timestamp();
|
||||
let interfaces = NetworkInterfaces::new();
|
||||
let count = 100;
|
||||
for x in 0..count {
|
||||
info!("loop {}", x);
|
||||
if let Err(e) = interfaces.refresh().await {
|
||||
error!("error refreshing interfaces: {}", e);
|
||||
}
|
||||
}
|
||||
let t2 = get_timestamp();
|
||||
let tdiff = ((t2 - t1) as f64)/1000000.0f64;
|
||||
info!("running network interface test with {} iterations took {} seconds", count, tdiff);
|
||||
//info!("interfaces: {:#?}", interfaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_all() {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
test_network_interfaces().await;
|
||||
}
|
||||
Reference in New Issue
Block a user