windows upnp work
This commit is contained in:
		@@ -69,7 +69,7 @@ futures-util = { version = "0.3.28", default-features = false, features = [
 | 
			
		||||
chrono = "0.4.31"
 | 
			
		||||
 | 
			
		||||
libc = "0.2.148"
 | 
			
		||||
nix = { version = "0.27.1", features = [ "user" ] }
 | 
			
		||||
nix = { version = "0.27.1", features = ["user"] }
 | 
			
		||||
 | 
			
		||||
# Dependencies for WASM builds only
 | 
			
		||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
 | 
			
		||||
@@ -93,9 +93,8 @@ paranoid-android = { version = "0.2.1", optional = true }
 | 
			
		||||
android_logger = "0.13.3"
 | 
			
		||||
 | 
			
		||||
# Dependencies for Windows
 | 
			
		||||
# [target.'cfg(target_os = "windows")'.dependencies]
 | 
			
		||||
# windows = { version = "^0", features = [ "Win32_NetworkManagement_Dns", "Win32_Foundation", "alloc" ]}
 | 
			
		||||
# windows-permissions = "^0"
 | 
			
		||||
[target.'cfg(target_os = "windows")'.dependencies]
 | 
			
		||||
winapi = { version = "0.3.9", features = ["iptypes", "iphlpapi"] }
 | 
			
		||||
 | 
			
		||||
# Dependencies for iOS
 | 
			
		||||
[target.'cfg(target_os = "ios")'.dependencies]
 | 
			
		||||
 
 | 
			
		||||
@@ -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