diff options
| author | David Lönnhager <david.l@mullvad.net> | 2021-05-20 11:02:01 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2021-05-20 11:02:01 +0200 |
| commit | 816d23437c799a511cb80e389d642b59c4e05cd4 (patch) | |
| tree | 70a7193d6e1e119306e597314c9e7764fdf55862 | |
| parent | 4cf35350a9d86c81d78acaed00e582ce045ccb3c (diff) | |
| parent | 43d1a133f023a61e3dd71f99e06966e77e0927fa (diff) | |
| download | mullvadvpn-816d23437c799a511cb80e389d642b59c4e05cd4.tar.xz mullvadvpn-816d23437c799a511cb80e389d642b59c4e05cd4.zip | |
Merge branch 'fix-metric-set'
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/openvpn/mod.rs | 13 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/openvpn/windows.rs | 130 |
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::*; |
