summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2021-05-20 11:02:01 +0200
committerDavid Lönnhager <david.l@mullvad.net>2021-05-20 11:02:01 +0200
commit816d23437c799a511cb80e389d642b59c4e05cd4 (patch)
tree70a7193d6e1e119306e597314c9e7764fdf55862
parent4cf35350a9d86c81d78acaed00e582ce045ccb3c (diff)
parent43d1a133f023a61e3dd71f99e06966e77e0927fa (diff)
downloadmullvadvpn-816d23437c799a511cb80e389d642b59c4e05cd4.tar.xz
mullvadvpn-816d23437c799a511cb80e389d642b59c4e05cd4.zip
Merge branch 'fix-metric-set'
-rw-r--r--CHANGELOG.md1
-rw-r--r--talpid-core/src/tunnel/openvpn/mod.rs13
-rw-r--r--talpid-core/src/tunnel/openvpn/windows.rs130
3 files changed, 142 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b57b14d27d..e81c2eab21 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,7 @@ Line wrap the file at 100 chars. Th
- Prevent tray icons from being extraced to `%TEMP%` directory.
- Fix failure to create Wintun adapter due to a residual network interface by upgrading Wintun to
0.10.4.
+- Wait for IP interfaces to be added to the Wintun adapter before setting metrics on them.
#### Linux
- Fix find `mullvad-vpn.desktop` in `XDG_DATA_DIRS` instead of using hardcoded path.
diff --git a/talpid-core/src/tunnel/openvpn/mod.rs b/talpid-core/src/tunnel/openvpn/mod.rs
index cbb89f912e..8d1aa03607 100644
--- a/talpid-core/src/tunnel/openvpn/mod.rs
+++ b/talpid-core/src/tunnel/openvpn/mod.rs
@@ -146,6 +146,11 @@ pub enum Error {
#[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")]
@@ -394,6 +399,14 @@ impl OpenVpnMonitor<OpenVpnCommand> {
log::warn!("You may need to restart Windows to complete the install of Wintun");
}
+ log::debug!("Wait for IP interfaces");
+ windows::wait_for_interfaces(
+ &adapter.adapter().luid(),
+ true,
+ params.generic_options.enable_ipv6,
+ )
+ .map_err(Error::IpInterfacesError)?;
+
let assigned_guid = adapter.adapter().guid();
let assigned_guid = assigned_guid.as_ref().unwrap_or_else(|error| {
log::error!(
diff --git a/talpid-core/src/tunnel/openvpn/windows.rs b/talpid-core/src/tunnel/openvpn/windows.rs
index c1aa80a631..a88c6c756f 100644
--- a/talpid-core/src/tunnel/openvpn/windows.rs
+++ b/talpid-core/src/tunnel/openvpn/windows.rs
@@ -4,7 +4,8 @@ use std::{
os::windows::{ffi::OsStrExt, io::RawHandle},
path::Path,
ptr,
- sync::Arc,
+ sync::{Arc, Mutex},
+ time::Duration,
};
use talpid_types::ErrorExt;
use widestring::{U16CStr, U16CString};
@@ -13,8 +14,13 @@ use winapi::{
guiddef::GUID,
ifdef::NET_LUID,
minwindef::{BOOL, FARPROC, HINSTANCE, HMODULE},
- netioapi::ConvertInterfaceLuidToGuid,
+ netioapi::{
+ CancelMibChangeNotify2, ConvertInterfaceLuidToGuid, GetIpInterfaceEntry,
+ MibAddInstance, NotifyIpInterfaceChange, MIB_IPINTERFACE_ROW,
+ },
+ ntdef::FALSE,
winerror::NO_ERROR,
+ ws2def::{AF_INET, AF_INET6, AF_UNSPEC},
},
um::{
libloaderapi::{
@@ -29,6 +35,8 @@ use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey};
/// Longest possible adapter name (in characters), including null terminator
const MAX_ADAPTER_NAME: usize = 128;
+const INTERFACE_WAIT_TIMEOUT: Duration = Duration::from_secs(5);
+
type WintunOpenAdapterFn =
unsafe extern "stdcall" fn(pool: *const u16, name: *const u16) -> RawHandle;
@@ -425,6 +433,124 @@ pub fn find_adapter_registry_key(find_guid: &str, permissions: REGSAM) -> io::Re
Err(io::Error::new(io::ErrorKind::NotFound, "device not found"))
}
+pub struct IpNotifierHandle<'a> {
+ mutex: Mutex<()>,
+ callback: Option<Box<dyn FnMut(&MIB_IPINTERFACE_ROW, u32) + Send + 'a>>,
+ handle: RawHandle,
+}
+
+impl<'a> Drop for IpNotifierHandle<'a> {
+ fn drop(&mut self) {
+ // Inner callback may be called while destructing
+ unsafe { CancelMibChangeNotify2(self.handle as *mut _) };
+
+ let _ = self
+ .mutex
+ .lock()
+ .expect("NotifyIpInterfaceChange mutex poisoned");
+ let _ = self.callback.take();
+ }
+}
+
+unsafe extern "system" fn inner_callback(
+ context: *mut winapi::ctypes::c_void,
+ row: *mut MIB_IPINTERFACE_ROW,
+ notify_type: u32,
+) {
+ let context = &mut *(context as *mut IpNotifierHandle<'_>);
+ let _ = context
+ .mutex
+ .lock()
+ .expect("NotifyIpInterfaceChange mutex poisoned");
+
+ if let Some(ref mut callback) = context.callback {
+ callback(&*row, notify_type);
+ }
+}
+
+pub fn notify_ip_interface_change<'a, T: FnMut(&MIB_IPINTERFACE_ROW, u32) + Send + 'a>(
+ callback: T,
+ family: u16,
+) -> io::Result<Box<IpNotifierHandle<'a>>> {
+ let mut context = Box::new(IpNotifierHandle {
+ mutex: Mutex::default(),
+ callback: Some(Box::new(callback)),
+ handle: std::ptr::null_mut(),
+ });
+
+ let status = unsafe {
+ NotifyIpInterfaceChange(
+ family,
+ Some(inner_callback),
+ &mut *context as *mut _ as *mut _,
+ FALSE,
+ (&mut context.handle) as *mut _,
+ )
+ };
+
+ if status != NO_ERROR {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(context)
+}
+
+pub fn get_ip_interface_entry(family: u16, luid: &NET_LUID) -> io::Result<MIB_IPINTERFACE_ROW> {
+ let mut row: MIB_IPINTERFACE_ROW = unsafe { mem::zeroed() };
+ row.Family = family;
+ row.InterfaceLuid = *luid;
+
+ let result = unsafe { GetIpInterfaceEntry(&mut row as *mut _) };
+ if result != NO_ERROR {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(row)
+}
+
+pub fn wait_for_interfaces(luid: &NET_LUID, ipv4: bool, ipv6: bool) -> io::Result<()> {
+ let (tx, rx) = std::sync::mpsc::channel();
+
+ let mut found_ipv4 = if ipv4 { false } else { true };
+ let mut found_ipv6 = if ipv6 { false } else { true };
+
+ let _handle = notify_ip_interface_change(
+ move |row, notification_type| {
+ if found_ipv4 && found_ipv6 {
+ return;
+ }
+ if notification_type != MibAddInstance {
+ return;
+ }
+ if row.InterfaceLuid.Value != luid.Value {
+ return;
+ }
+ match row.Family as i32 {
+ AF_INET => found_ipv4 = true,
+ AF_INET6 => found_ipv6 = true,
+ _ => (),
+ }
+ if found_ipv4 && found_ipv6 {
+ let _ = tx.send(());
+ }
+ },
+ AF_UNSPEC as u16,
+ )?;
+
+ // Make sure they don't already exist
+ if (!ipv4 || get_ip_interface_entry(AF_INET as u16, luid).is_ok())
+ && (!ipv6 || get_ip_interface_entry(AF_INET6 as u16, luid).is_ok())
+ {
+ return Ok(());
+ }
+
+ let _ = rx
+ .recv_timeout(INTERFACE_WAIT_TIMEOUT)
+ .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "timed out waiting on interfaces"))?;
+
+ Ok(())
+}
+
#[cfg(test)]
mod tests {
use super::*;