blob: 11de3c7180e844839a07c7deabf86efb40e7d76a [file] [log] [blame] [edit]
// 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 std::ffi::{c_void, CStr};
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
use std::time::Duration;
use std::{io, ptr};
use windows_sys::Win32::Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS, HANDLE};
use windows_sys::Win32::NetworkManagement::IpHelper::{
CancelMibChangeNotify2, GetAdaptersAddresses, NotifyIpInterfaceChange, GAA_FLAG_INCLUDE_PREFIX,
GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST,
IP_ADAPTER_ADDRESSES_LH, IP_ADAPTER_PREFIX_XP, IP_ADAPTER_UNICAST_ADDRESS_LH,
MIB_IPINTERFACE_ROW, MIB_NOTIFICATION_TYPE,
};
use windows_sys::Win32::Networking::WinSock::AF_UNSPEC;
use windows_sys::Win32::System::Memory::{
GetProcessHeap, HeapAlloc, HeapFree, HEAP_NONE, HEAP_ZERO_MEMORY,
};
use crate::IfOperStatus;
#[repr(transparent)]
pub struct IpAdapterAddresses(*const IP_ADAPTER_ADDRESSES_LH);
impl IpAdapterAddresses {
#[allow(unsafe_code)]
pub fn name(&self) -> String {
let len = (0..)
.take_while(|&i| unsafe { *(*self.0).FriendlyName.offset(i) } != 0)
.count();
let slice = unsafe { std::slice::from_raw_parts((*self.0).FriendlyName, len) };
String::from_utf16_lossy(slice)
}
#[allow(unsafe_code)]
pub fn adapter_name(&self) -> String {
unsafe { CStr::from_ptr((*self.0).AdapterName as _) }
.to_string_lossy()
.into_owned()
}
pub fn ipv4_index(&self) -> Option<u32> {
let if_index = unsafe { (*self.0).Anonymous1.Anonymous.IfIndex };
if if_index == 0 {
None
} else {
Some(if_index)
}
}
pub fn ipv6_index(&self) -> Option<u32> {
let if_index = unsafe { (*self.0).Ipv6IfIndex };
if if_index == 0 {
None
} else {
Some(if_index)
}
}
pub fn prefixes(&self) -> PrefixesIterator {
PrefixesIterator {
_head: unsafe { &*self.0 },
next: unsafe { (*self.0).FirstPrefix },
}
}
pub fn unicast_addresses(&self) -> UnicastAddressesIterator {
UnicastAddressesIterator {
_head: unsafe { &*self.0 },
next: unsafe { (*self.0).FirstUnicastAddress },
}
}
pub fn oper_status(&self) -> IfOperStatus {
unsafe { (*self.0).OperStatus.into() }
}
}
pub struct IfAddrs {
inner: IpAdapterAddresses,
}
impl IfAddrs {
#[allow(unsafe_code)]
pub fn new() -> io::Result<Self> {
let mut buffersize = 15000;
let mut ifaddrs: *mut IP_ADAPTER_ADDRESSES_LH;
loop {
unsafe {
ifaddrs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, buffersize as _)
as *mut IP_ADAPTER_ADDRESSES_LH;
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,
ptr::null_mut(),
ifaddrs,
&mut buffersize,
);
match retcode {
ERROR_SUCCESS => break,
ERROR_BUFFER_OVERFLOW => {
HeapFree(GetProcessHeap(), HEAP_NONE, ifaddrs as _);
buffersize *= 2;
continue;
}
_ => {
HeapFree(GetProcessHeap(), HEAP_NONE, ifaddrs as _);
return Err(io::Error::last_os_error());
}
}
}
}
Ok(Self {
inner: IpAdapterAddresses(ifaddrs),
})
}
pub fn iter(&self) -> IfAddrsIterator {
IfAddrsIterator {
_head: self,
next: self.inner.0,
}
}
}
impl Drop for IfAddrs {
#[allow(unsafe_code)]
fn drop(&mut self) {
unsafe {
HeapFree(GetProcessHeap(), HEAP_NONE, self.inner.0 as _);
}
}
}
pub struct IfAddrsIterator<'a> {
_head: &'a IfAddrs,
next: *const IP_ADAPTER_ADDRESSES_LH,
}
impl<'a> Iterator for IfAddrsIterator<'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(result)
})
}
}
pub struct PrefixesIterator<'a> {
_head: &'a IP_ADAPTER_ADDRESSES_LH,
next: *const IP_ADAPTER_PREFIX_XP,
}
impl<'a> Iterator for PrefixesIterator<'a> {
type Item = &'a IP_ADAPTER_PREFIX_XP;
#[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> {
_head: &'a IP_ADAPTER_ADDRESSES_LH,
next: *const IP_ADAPTER_UNICAST_ADDRESS_LH,
}
impl<'a> Iterator for UnicastAddressesIterator<'a> {
type Item = &'a IP_ADAPTER_UNICAST_ADDRESS_LH;
#[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 WindowsIfChangeNotifier {
handle: HANDLE,
// maintain constant memory address for callback fn
tx: *mut Sender<()>,
rx: Receiver<()>,
}
impl WindowsIfChangeNotifier {
pub fn new() -> io::Result<Self> {
let (tx, rx) = channel();
let mut ret = Self {
handle: std::ptr::null_mut(),
tx: Box::into_raw(Box::new(tx)),
rx,
};
let stat = unsafe {
// Notes on the function used here and alternatives that were
// considered:
//
// NotifyAddrChange works pretty well, but only for IPv4, and the
// API itself is a little awkward, requiring overlapped IO
//
// NotifyRouteChange2 doesn't catch interfaces going away (e.g.
// unplugging ethernet, going into airplane mode)
//
// WlanRegisterNotification is only for WiFi
//
// Monitoring changes to MSFT_NetAdapter is possible, but requires
// WMI/COM, which is undesirable
//
// NotifyIpInterfaceChange produces several spurious messages
// (mostly related to WiFi speed, it seems), but they aren't too
// frequent (at most, I've seen a batch of 3 every 10 seconds), so
// don't pose a performance concern
NotifyIpInterfaceChange(
AF_UNSPEC,
Some(if_change_callback),
ret.tx as *const c_void,
false,
&mut ret.handle,
)
};
if stat != 0 {
Err(io::Error::from_raw_os_error(stat as i32))
} else {
Ok(ret)
}
}
pub fn wait(&self, timeout: Option<Duration>) -> io::Result<()> {
if let Some(timeout) = timeout {
self.rx.recv_timeout(timeout)
} else {
self.rx.recv().map_err(RecvTimeoutError::from)
}
.map_err(|_| io::Error::new(io::ErrorKind::WouldBlock, "Timed out"))
}
}
impl Drop for WindowsIfChangeNotifier {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe { CancelMibChangeNotify2(self.handle) };
}
unsafe { drop(Box::from_raw(self.tx)) };
}
}
unsafe extern "system" fn if_change_callback(
ctx: *const c_void,
_row: *const MIB_IPINTERFACE_ROW,
_notificationtype: MIB_NOTIFICATION_TYPE,
) {
if let Some(tx) = (ctx as *const Sender<()>).as_ref() {
tx.send(()).ok();
};
// note: `row` not used, as for all changes that we care for (interface
// add/remove), all the member values are 0, so it's useless.
}