diff options
| author | David Lönnhager <david.l@mullvad.net> | 2021-09-24 14:24:23 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2021-09-27 10:45:13 +0200 |
| commit | d354600f229723ef897ec3825a02d017f113992f (patch) | |
| tree | e86a24f3942dcc7882d0ebf996671ec45c01b092 | |
| parent | 686498a15bc1e4cdff2899f35c01d1ad1ad34e02 (diff) | |
| download | mullvadvpn-d354600f229723ef897ec3825a02d017f113992f.tar.xz mullvadvpn-d354600f229723ef897ec3825a02d017f113992f.zip | |
Reorganize windows functions in talpid-core
| -rw-r--r-- | talpid-core/src/lib.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/mod.rs | 3 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/openvpn/mod.rs | 250 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/openvpn/wintun.rs (renamed from talpid-core/src/tunnel/openvpn/windows.rs) | 98 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/mod.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/windows.rs (renamed from talpid-core/src/tunnel/windows.rs) | 136 |
6 files changed, 251 insertions, 242 deletions
diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs index e4ac42857b..8d540fcdbd 100644 --- a/talpid-core/src/lib.rs +++ b/talpid-core/src/lib.rs @@ -13,6 +13,10 @@ mod ffi; #[cfg(windows)] mod winnet; +/// Windows API wrappers and utilities +#[cfg(target_os = "windows")] +pub mod windows; + #[cfg(any(target_os = "linux", target_os = "macos"))] /// Working with IP interface devices pub mod network_interface; diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs index 570cb64983..6501162616 100644 --- a/talpid-core/src/tunnel/mod.rs +++ b/talpid-core/src/tunnel/mod.rs @@ -22,9 +22,6 @@ pub mod wireguard; /// A module for low level platform specific tunnel device management. pub(crate) mod tun_provider; -#[cfg(target_os = "windows")] -mod windows; - const OPENVPN_LOG_FILENAME: &str = "openvpn.log"; const WIREGUARD_LOG_FILENAME: &str = "wireguard.log"; diff --git a/talpid-core/src/tunnel/openvpn/mod.rs b/talpid-core/src/tunnel/openvpn/mod.rs index db36e3d035..4b27b6163e 100644 --- a/talpid-core/src/tunnel/openvpn/mod.rs +++ b/talpid-core/src/tunnel/openvpn/mod.rs @@ -15,7 +15,7 @@ use lazy_static::lazy_static; #[cfg(target_os = "linux")] use std::collections::{HashMap, HashSet}; #[cfg(windows)] -use std::{ffi::OsString, time::Instant}; +use std::ffi::OsString; use std::{ fs, io::{self, Write}, @@ -35,42 +35,19 @@ use which; #[cfg(windows)] use widestring::U16CString; #[cfg(windows)] -use winapi::shared::{ - guiddef::GUID, - ifdef::NET_LUID, - netioapi::{GetUnicastIpAddressEntry, MIB_UNICASTIPADDRESS_ROW}, - nldef::{IpDadStatePreferred, IpDadStateTentative, NL_DAD_STATE}, - winerror::NO_ERROR, -}; -#[cfg(windows)] -use winreg::enums::{KEY_READ, KEY_WRITE}; +use winapi::shared::{guiddef::GUID, ifdef::NET_LUID}; #[cfg(windows)] -mod windows; +mod wintun; #[cfg(windows)] lazy_static! { - static ref WINTUN_DLL: Mutex<Option<Arc<windows::WintunDll>>> = Mutex::new(None); static ref ADAPTER_ALIAS: U16CString = U16CString::from_str("Mullvad").unwrap(); static ref ADAPTER_POOL: U16CString = U16CString::from_str("Mullvad").unwrap(); } #[cfg(windows)] -fn get_wintun_dll(resource_dir: &Path) -> Result<Arc<windows::WintunDll>> { - let mut dll = (*WINTUN_DLL).lock().expect("Wintun mutex poisoned"); - match &*dll { - Some(dll) => Ok(dll.clone()), - None => { - let new_dll = - Arc::new(windows::WintunDll::new(resource_dir).map_err(Error::WintunDllError)?); - *dll = Some(new_dll.clone()); - Ok(new_dll) - } - } -} - -#[cfg(windows)] const ADAPTER_GUID: GUID = GUID { Data1: 0xAFE43773, Data2: 0xE1F8, @@ -78,11 +55,6 @@ const ADAPTER_GUID: GUID = GUID { Data4: [0x85, 0x36, 0x57, 0x6A, 0xB8, 0x6A, 0xFE, 0x9A], }; -#[cfg(windows)] -const DEVICE_READY_TIMEOUT: Duration = Duration::from_secs(5); -#[cfg(windows)] -const DEVICE_CHECK_INTERVAL: Duration = Duration::from_millis(100); - /// Results from fallible operations on the OpenVPN tunnel. pub type Result<T> = std::result::Result<T, Error>; @@ -127,41 +99,6 @@ pub enum Error { #[error(display = "Failed to determine alias of Wintun adapter")] WintunFindAlias(#[error(source)] io::Error), - /// cannot delete wintun interface - #[cfg(windows)] - #[error(display = "Failed to delete existing Wintun adapter")] - WintunDeleteExistingError(#[error(source)] io::Error), - - /// Error while waiting for IP interfaces to become available - #[cfg(windows)] - #[error(display = "Failed while waiting for IP interfaces")] - IpInterfacesError(#[error(source)] io::Error), - - /// Error returned from `ConvertInterfaceAliasToLuid` - #[cfg(windows)] - #[error(display = "Cannot find LUID for virtual adapter")] - NoDeviceLuid(#[error(source)] io::Error), - - /// Error returned from `GetUnicastIpAddressTable`/`GetUnicastIpAddressEntry` - #[cfg(windows)] - #[error(display = "Cannot find LUID for virtual adapter")] - ObtainUnicastAddress(#[error(source)] io::Error), - - /// `GetUnicastIpAddressTable` contained no addresses for the tunnel interface - #[cfg(windows)] - #[error(display = "Found no addresses for virtual adapter")] - NoUnicastAddress, - - /// Unexpected DAD state returned for a unicast address - #[cfg(windows)] - #[error(display = "Unexpected DAD state")] - DadStateError(#[error(source)] DadStateError), - - /// DAD check failed. - #[cfg(windows)] - #[error(display = "Timed out waiting on tunnel device")] - DeviceReadyTimeout, - /// OpenVPN process died unexpectedly #[error(display = "OpenVPN process died unexpectedly")] ChildProcessDied, @@ -202,11 +139,6 @@ pub enum Error { )] ProxyExited(String), - /// Failure in Windows syscall. - #[cfg(windows)] - #[error(display = "Failure in Windows syscall")] - WinnetError(#[error(source)] crate::winnet::Error), - /// The map is missing 'dev' #[cfg(target_os = "linux")] #[error(display = "Failed to obtain tunnel interface name")] @@ -292,9 +224,9 @@ impl std::fmt::Debug for dyn WintunContext { #[cfg(windows)] #[derive(Debug)] struct WintunContextImpl { - adapter: windows::TemporaryWintunAdapter, + adapter: wintun::TemporaryWintunAdapter, wait_v6_interface: bool, - _logger: windows::WintunLoggerHandle, + _logger: wintun::WintunLoggerHandle, } #[cfg(windows)] @@ -310,7 +242,7 @@ impl WintunContext for WintunContextImpl { async fn wait_for_interfaces(&self) -> io::Result<()> { let luid = self.adapter.adapter().luid(); - super::windows::wait_for_interfaces(luid, true, self.wait_v6_interface).await + crate::windows::wait_for_interfaces(luid, true, self.wait_v6_interface).await } fn disable_unused_features(&self) { @@ -361,78 +293,18 @@ impl OpenVpnMonitor<OpenVpnCommand> { let proxy_monitor = Self::start_proxy(¶ms.proxy, &proxy_resources)?; #[cfg(windows)] - let dll = get_wintun_dll(resource_dir)?; + let dll = wintun::WintunDll::instance(resource_dir).map_err(Error::WintunDllError)?; #[cfg(windows)] let wintun_logger = dll.activate_logging(); #[cfg(windows)] - let wintun_adapter = { - { - if let Ok(adapter) = - windows::WintunAdapter::open(dll.clone(), &*ADAPTER_ALIAS, &*ADAPTER_POOL) - { - // Delete existing adapter in case it has residual config - adapter - .delete(false) - .map_err(Error::WintunDeleteExistingError)?; - } - } - - let (adapter, reboot_required) = windows::TemporaryWintunAdapter::create( - dll.clone(), - &*ADAPTER_ALIAS, - &*ADAPTER_POOL, - Some(ADAPTER_GUID.clone()), - ) - .map_err(Error::WintunCreateAdapterError)?; - if reboot_required { - log::warn!("You may need to restart Windows to complete the install of Wintun"); - } - - let assigned_guid = adapter.adapter().guid(); - let assigned_guid = assigned_guid.as_ref().unwrap_or_else(|error| { - log::error!( - "{}", - error.display_chain_with_msg("Cannot identify adapter guid") - ); - &ADAPTER_GUID - }); - let assigned_guid_string = windows::string_from_guid(assigned_guid); - - // Workaround: OpenVPN looks up "ComponentId" to identify tunnel devices. - // If Wintun fails to create this registry value, create it here. - let adapter_key = - windows::find_adapter_registry_key(&assigned_guid_string, KEY_READ | KEY_WRITE); - match adapter_key { - Ok(adapter_key) => { - let component_id: io::Result<String> = adapter_key.get_value("ComponentId"); - match component_id { - Ok(_) => (), - Err(error) => { - if error.kind() == io::ErrorKind::NotFound { - if let Err(error) = adapter_key.set_value("ComponentId", &"wintun") - { - log::error!( - "{}", - error.display_chain_with_msg( - "Failed to set ComponentId registry value" - ) - ); - } - } - } - } - } - Err(error) => { - log::error!( - "{}", - error.display_chain_with_msg("Failed to find network adapter registry key") - ); - } - } - - adapter - }; + let (wintun_adapter, _reboot_required) = wintun::TemporaryWintunAdapter::create( + dll.clone(), + &*ADAPTER_ALIAS, + &*ADAPTER_POOL, + Some(ADAPTER_GUID.clone()), + ) + .map_err(Error::WintunCreateAdapterError)?; #[cfg(windows)] let adapter_alias = wintun_adapter @@ -1058,15 +930,18 @@ mod event_server { #[cfg(windows)] { let tunnel_device = metadata.interface.clone(); - tokio::task::spawn_blocking(move || super::wait_for_ready_device(&tunnel_device)) + let luid = crate::windows::luid_from_alias(tunnel_device).map_err(|error| { + log::error!("{}", error.display_chain_with_msg("luid_from_alias failed")); + tonic::Status::unavailable("failed to obtain interface luid") + })?; + crate::windows::wait_for_addresses(luid) .await - .map_err(|_| tonic::Status::internal("task failed to complete"))? .map_err(|error| { log::error!( "{}", - error.display_chain_with_msg("wait_for_ready_device failed") + error.display_chain_with_msg("wait_for_addresses failed") ); - tonic::Status::unavailable("wait_for_ready_device failed") + tonic::Status::unavailable("wait_for_addresses failed") })?; } @@ -1235,89 +1110,6 @@ mod event_server { } } -#[cfg(windows)] -fn wait_for_ready_device(alias: &str) -> Result<()> { - // Obtain luid for alias - let luid = crate::tunnel::windows::luid_from_alias(alias).map_err(Error::NoDeviceLuid)?; - - // Obtain unicast IP addresses - let mut unicast_rows: Vec<MIB_UNICASTIPADDRESS_ROW> = - crate::tunnel::windows::get_unicast_table(None) - .map_err(Error::ObtainUnicastAddress)? - .into_iter() - .filter(|row| row.InterfaceLuid.Value == luid.Value) - .collect(); - if unicast_rows.is_empty() { - return Err(Error::NoUnicastAddress); - } - - // Poll DAD status using GetUnicastIpAddressEntry - // https://docs.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-createunicastipaddressentry - - let deadline = Instant::now() + DEVICE_READY_TIMEOUT; - while Instant::now() < deadline { - let mut ready = true; - - for row in &mut unicast_rows { - let status = unsafe { GetUnicastIpAddressEntry(row) }; - if status != NO_ERROR { - return Err(Error::ObtainUnicastAddress(io::Error::from_raw_os_error( - status as i32, - ))); - } - if row.DadState == IpDadStateTentative { - ready = false; - break; - } - if row.DadState != IpDadStatePreferred { - return Err(Error::DadStateError(DadStateError::from(row.DadState))); - } - } - - if ready { - return Ok(()); - } - std::thread::sleep(DEVICE_CHECK_INTERVAL); - } - - Err(Error::DeviceReadyTimeout) -} - -/// Handles cases where there DAD state is neither tentative nor preferred. -#[cfg(windows)] -#[derive(err_derive::Error, Debug)] -pub enum DadStateError { - /// Invalid DAD state. - #[error(display = "Invalid DAD state")] - Invalid, - - /// Duplicate unicast address. - #[error(display = "A duplicate IP address was detected")] - Duplicate, - - /// Deprecated unicast address. - #[error(display = "The IP address has been deprecated")] - Deprecated, - - /// Unknown DAD state constant. - #[error(display = "Unknown DAD state: {}", _0)] - Unknown(u32), -} - -#[cfg(windows)] -#[allow(non_upper_case_globals)] -impl From<NL_DAD_STATE> for DadStateError { - fn from(state: NL_DAD_STATE) -> DadStateError { - use winapi::shared::nldef::*; - match state { - IpDadStateInvalid => DadStateError::Invalid, - IpDadStateDuplicate => DadStateError::Duplicate, - IpDadStateDeprecated => DadStateError::Deprecated, - other => DadStateError::Unknown(other), - } - } -} - #[cfg(test)] mod tests { diff --git a/talpid-core/src/tunnel/openvpn/windows.rs b/talpid-core/src/tunnel/openvpn/wintun.rs index 8e73245815..a50d1d4490 100644 --- a/talpid-core/src/tunnel/openvpn/windows.rs +++ b/talpid-core/src/tunnel/openvpn/wintun.rs @@ -1,11 +1,12 @@ -use crate::tunnel::windows::{get_ip_interface_entry, set_ip_interface_entry, AddressFamily}; +use crate::windows::{get_ip_interface_entry, set_ip_interface_entry, AddressFamily}; +use lazy_static::lazy_static; use std::{ ffi::CStr, fmt, io, iter, mem, os::windows::{ffi::OsStrExt, io::RawHandle}, path::Path, ptr, - sync::Arc, + sync::{Arc, Mutex}, }; use talpid_types::ErrorExt; use widestring::{U16CStr, U16CString}; @@ -26,8 +27,15 @@ use winapi::{ winreg::REGSAM, }, }; -use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey}; +use winreg::{ + enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE}, + RegKey, +}; +lazy_static! { + /// Shared `WintunDll` instance + static ref WINTUN_DLL: Mutex<Option<Arc<WintunDll>>> = Mutex::new(None); +} /// Longest possible adapter name (in characters), including null terminator const MAX_ADAPTER_NAME: usize = 128; @@ -153,8 +161,28 @@ impl WintunAdapter { name: &U16CStr, requested_guid: Option<GUID>, ) -> io::Result<(Self, RebootRequired)> { + { + if let Ok(adapter) = Self::open(dll_handle.clone(), name, pool) { + // Delete existing adapter in case it has residual config + adapter.delete(false).map_err(|error| { + log::error!( + "{}", + error.display_chain_with_msg("Failed to delete existing Wintun adapter") + ); + error + })?; + } + } + let (handle, restart_required) = dll_handle.create_adapter(pool, name, requested_guid)?; - Ok((Self { dll_handle, handle }, restart_required)) + + if restart_required { + log::warn!("You may need to restart Windows to complete the install of Wintun"); + } + + let adapter = Self { dll_handle, handle }; + adapter.restore_missing_component_id(); + Ok((adapter, restart_required)) } pub fn try_disable_unused_features(&self) { @@ -202,6 +230,50 @@ impl WintunAdapter { } Ok(unsafe { guid.assume_init() }) } + + fn restore_missing_component_id(&self) { + let assigned_guid = match self.guid() { + Ok(guid) => guid, + Err(error) => { + log::error!( + "{}", + error.display_chain_with_msg("Cannot identify adapter guid") + ); + return; + } + }; + let assigned_guid_string = string_from_guid(&assigned_guid); + + // Workaround: OpenVPN looks up "ComponentId" to identify tunnel devices. + // If Wintun fails to create this registry value, create it here. + let adapter_key = find_adapter_registry_key(&assigned_guid_string, KEY_READ | KEY_WRITE); + match adapter_key { + Ok(adapter_key) => { + let component_id: io::Result<String> = adapter_key.get_value("ComponentId"); + match component_id { + Ok(_) => (), + Err(error) => { + if error.kind() == io::ErrorKind::NotFound { + if let Err(error) = adapter_key.set_value("ComponentId", &"wintun") { + log::error!( + "{}", + error.display_chain_with_msg( + "Failed to set ComponentId registry value" + ) + ); + } + } + } + } + } + Err(error) => { + log::error!( + "{}", + error.display_chain_with_msg("Failed to find network adapter registry key") + ); + } + } + } } impl Drop for WintunAdapter { @@ -211,7 +283,19 @@ impl Drop for WintunAdapter { } impl WintunDll { - pub fn new(resource_dir: &Path) -> io::Result<Self> { + pub fn instance(resource_dir: &Path) -> io::Result<Arc<Self>> { + let mut dll = (*WINTUN_DLL).lock().expect("Wintun mutex poisoned"); + match &*dll { + Some(dll) => Ok(dll.clone()), + None => { + let new_dll = Arc::new(Self::new(resource_dir)?); + *dll = Some(new_dll.clone()); + Ok(new_dll) + } + } + } + + fn new(resource_dir: &Path) -> io::Result<Self> { let wintun_dll: Vec<u16> = resource_dir .join("wintun.dll") .as_os_str() @@ -407,7 +491,7 @@ impl Drop for WintunLoggerHandle { } /// Obtain a string representation for a GUID object. -pub fn string_from_guid(guid: &GUID) -> String { +fn string_from_guid(guid: &GUID) -> String { use std::{ffi::OsString, os::windows::ffi::OsStringExt}; use winapi::um::combaseapi::StringFromGUID2; @@ -425,7 +509,7 @@ pub fn string_from_guid(guid: &GUID) -> String { } /// Returns the registry key for a network device identified by its GUID. -pub fn find_adapter_registry_key(find_guid: &str, permissions: REGSAM) -> io::Result<RegKey> { +fn find_adapter_registry_key(find_guid: &str, permissions: REGSAM) -> io::Result<RegKey> { let net_devs = RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags( r"SYSTEM\CurrentControlSet\Control\Class\{4d36e972-e325-11ce-bfc1-08002be10318}", permissions, diff --git a/talpid-core/src/tunnel/wireguard/mod.rs b/talpid-core/src/tunnel/wireguard/mod.rs index 71de9ad660..df3c7bb8b3 100644 --- a/talpid-core/src/tunnel/wireguard/mod.rs +++ b/talpid-core/src/tunnel/wireguard/mod.rs @@ -243,7 +243,7 @@ impl WireguardMonitor { use futures::future::FutureExt; use winapi::shared::ifdef::NET_LUID; let luid = NET_LUID { Value: iface_luid }; - let setup_future = super::windows::wait_for_interfaces(luid, true, enable_ipv6); + let setup_future = crate::windows::wait_for_interfaces(luid, true, enable_ipv6); futures::select! { result = setup_future.fuse() => { diff --git a/talpid-core/src/tunnel/windows.rs b/talpid-core/src/windows.rs index ea7cb646b7..8151165680 100644 --- a/talpid-core/src/tunnel/windows.rs +++ b/talpid-core/src/windows.rs @@ -3,23 +3,69 @@ use std::{ fmt, io, mem, os::windows::{ffi::OsStrExt, io::RawHandle}, sync::Mutex, + time::{Duration, Instant}, }; use winapi::shared::{ ifdef::NET_LUID, netioapi::{ CancelMibChangeNotify2, ConvertInterfaceAliasToLuid, FreeMibTable, GetIpInterfaceEntry, - GetUnicastIpAddressTable, MibAddInstance, NotifyIpInterfaceChange, SetIpInterfaceEntry, - MIB_IPINTERFACE_ROW, MIB_UNICASTIPADDRESS_ROW, MIB_UNICASTIPADDRESS_TABLE, + GetUnicastIpAddressEntry, GetUnicastIpAddressTable, MibAddInstance, + NotifyIpInterfaceChange, SetIpInterfaceEntry, MIB_IPINTERFACE_ROW, + MIB_UNICASTIPADDRESS_ROW, MIB_UNICASTIPADDRESS_TABLE, }, + nldef::{IpDadStatePreferred, IpDadStateTentative, NL_DAD_STATE}, ntdef::FALSE, winerror::{ERROR_NOT_FOUND, NO_ERROR}, ws2def::{AF_INET, AF_INET6, AF_UNSPEC}, }; +/// Result type for this module. +pub type Result<T> = std::result::Result<T, Error>; + +const DAD_CHECK_TIMEOUT: Duration = Duration::from_secs(5); +const DAD_CHECK_INTERVAL: Duration = Duration::from_millis(100); + +/// Errors returned by some functions in this module. +#[derive(err_derive::Error, Debug)] +#[error(no_from)] +pub enum Error { + /// Error returned from `ConvertInterfaceAliasToLuid` + #[cfg(windows)] + #[error(display = "Cannot find LUID for virtual adapter")] + NoDeviceLuid(#[error(source)] io::Error), + + /// Error returned from `GetUnicastIpAddressTable`/`GetUnicastIpAddressEntry` + #[cfg(windows)] + #[error(display = "Failed to obtain unicast IP address table")] + ObtainUnicastAddress(#[error(source)] io::Error), + + /// `GetUnicastIpAddressTable` contained no addresses for the interface + #[cfg(windows)] + #[error(display = "Found no addresses for the given adapter")] + NoUnicastAddress, + + /// Unexpected DAD state returned for a unicast address + #[cfg(windows)] + #[error(display = "Unexpected DAD state")] + DadStateError(#[error(source)] DadStateError), + + /// DAD check failed. + #[cfg(windows)] + #[error(display = "Timed out waiting on tunnel device")] + DeviceReadyTimeout, + + /// Unicast DAD check fail. + #[cfg(windows)] + #[error(display = "Unicast channel sender was unexpectedly dropped")] + UnicastSenderDropped, +} + /// Address family. These correspond to the `AF_*` constants. #[derive(Debug, Clone, Copy)] pub enum AddressFamily { + /// IPv4 address family Ipv4 = AF_INET as isize, + /// IPv6 address family Ipv6 = AF_INET6 as isize, } @@ -167,6 +213,92 @@ pub async fn wait_for_interfaces(luid: NET_LUID, ipv4: bool, ipv6: bool) -> io:: Ok(()) } +/// Handles cases where there DAD state is neither tentative nor preferred. +#[cfg(windows)] +#[derive(err_derive::Error, Debug)] +pub enum DadStateError { + /// Invalid DAD state. + #[error(display = "Invalid DAD state")] + Invalid, + + /// Duplicate unicast address. + #[error(display = "A duplicate IP address was detected")] + Duplicate, + + /// Deprecated unicast address. + #[error(display = "The IP address has been deprecated")] + Deprecated, + + /// Unknown DAD state constant. + #[error(display = "Unknown DAD state: {}", _0)] + Unknown(u32), +} + +#[cfg(windows)] +#[allow(non_upper_case_globals)] +impl From<NL_DAD_STATE> for DadStateError { + fn from(state: NL_DAD_STATE) -> DadStateError { + use winapi::shared::nldef::*; + match state { + IpDadStateInvalid => DadStateError::Invalid, + IpDadStateDuplicate => DadStateError::Duplicate, + IpDadStateDeprecated => DadStateError::Deprecated, + other => DadStateError::Unknown(other), + } + } +} + +/// Wait for addresses to be usable on an network adapter. +pub async fn wait_for_addresses(luid: NET_LUID) -> Result<()> { + // Obtain unicast IP addresses + let mut unicast_rows: Vec<MIB_UNICASTIPADDRESS_ROW> = get_unicast_table(None) + .map_err(Error::ObtainUnicastAddress)? + .into_iter() + .filter(|row| row.InterfaceLuid.Value == luid.Value) + .collect(); + if unicast_rows.is_empty() { + return Err(Error::NoUnicastAddress); + } + + let (tx, rx) = futures::channel::oneshot::channel(); + let mut addr_check_thread = move || { + // Poll DAD status using GetUnicastIpAddressEntry + // https://docs.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-createunicastipaddressentry + + let deadline = Instant::now() + DAD_CHECK_TIMEOUT; + while Instant::now() < deadline { + let mut ready = true; + + for row in &mut unicast_rows { + let status = unsafe { GetUnicastIpAddressEntry(row) }; + if status != NO_ERROR { + return Err(Error::ObtainUnicastAddress(io::Error::from_raw_os_error( + status as i32, + ))); + } + if row.DadState == IpDadStateTentative { + ready = false; + break; + } + if row.DadState != IpDadStatePreferred { + return Err(Error::DadStateError(DadStateError::from(row.DadState))); + } + } + + if ready { + return Ok(()); + } + std::thread::sleep(DAD_CHECK_INTERVAL); + } + + Err(Error::DeviceReadyTimeout) + }; + std::thread::spawn(move || { + let _ = tx.send(addr_check_thread()); + }); + rx.await.map_err(|_| Error::UnicastSenderDropped)? +} + /// Returns the unicast IP address table. If `family` is `None`, then addresses for all families are /// returned. pub fn get_unicast_table( |
