summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2021-09-24 14:24:23 +0200
committerDavid Lönnhager <david.l@mullvad.net>2021-09-27 10:45:13 +0200
commitd354600f229723ef897ec3825a02d017f113992f (patch)
treee86a24f3942dcc7882d0ebf996671ec45c01b092
parent686498a15bc1e4cdff2899f35c01d1ad1ad34e02 (diff)
downloadmullvadvpn-d354600f229723ef897ec3825a02d017f113992f.tar.xz
mullvadvpn-d354600f229723ef897ec3825a02d017f113992f.zip
Reorganize windows functions in talpid-core
-rw-r--r--talpid-core/src/lib.rs4
-rw-r--r--talpid-core/src/tunnel/mod.rs3
-rw-r--r--talpid-core/src/tunnel/openvpn/mod.rs250
-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.rs2
-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(&params.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(