diff options
| author | David Lönnhager <david.l@mullvad.net> | 2022-08-10 14:20:12 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2022-08-10 14:20:12 +0200 |
| commit | f053c5c071b94b7ba2e3025569cf25b089ccdc81 (patch) | |
| tree | fe4f2d6998e810f69e60728a81256dc118d7d05e | |
| parent | 39486e0096a4d4d65b63fc91726afbd38e011532 (diff) | |
| parent | a4d9712396a5183ca5de5abcad9e0bb570339814 (diff) | |
| download | mullvadvpn-f053c5c071b94b7ba2e3025569cf25b089ccdc81.tar.xz mullvadvpn-f053c5c071b94b7ba2e3025569cf25b089ccdc81.zip | |
Merge branch 'win-use-dnsapi'
| -rw-r--r-- | CHANGELOG.md | 3 | ||||
| -rw-r--r-- | Cargo.lock | 6 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 8 | ||||
| -rw-r--r-- | talpid-core/src/dns/windows/dnsapi.rs | 135 | ||||
| -rw-r--r-- | talpid-core/src/dns/windows/mod.rs | 32 |
5 files changed, 156 insertions, 28 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 922cb963c5..2b7c8c4fc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,9 @@ Line wrap the file at 100 chars. Th - Lowered default MTU to 1280 on Android. - Disable app icon badge for tunnel state notification/status. +#### Windows +- Remove dependency on `ipconfig.exe`. Call `DnsFlushResolverCache` to flush the DNS cache. + ### Removed #### Android - Remove WireGuard view as it's no longer needed with the new way of managing devices. diff --git a/Cargo.lock b/Cargo.lock index f9ef6eaa35..161335a35d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1906,9 +1906,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.8.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "opaque-debug" @@ -3061,6 +3061,7 @@ dependencies = [ "netlink-sys", "nftnl", "nix 0.23.1", + "once_cell", "os_pipe", "parity-tokio-ipc", "parking_lot 0.11.2", @@ -3095,6 +3096,7 @@ dependencies = [ "which", "widestring 0.5.1", "winapi", + "windows-sys", "winreg", "zeroize", ] diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 62e1fb2873..4f5484d2f3 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -18,6 +18,7 @@ futures = "0.3.15" hex = "0.4" ipnetwork = "0.16" lazy_static = "1.0" +once_cell = "1.13" libc = "0.2" log = "0.4" os_pipe = "0.9" @@ -84,6 +85,13 @@ winapi = { version = "0.3.6", features = ["combaseapi", "handleapi", "ifdef", "i talpid-platform-metadata = { path = "../talpid-platform-metadata" } memoffset = "0.6" +[target.'cfg(windows)'.dependencies.windows-sys] +version = "0.36.1" +features = [ + "Win32_Foundation", + "Win32_System_LibraryLoader", +] + [build-dependencies] tonic-build = { version = "0.5", default-features = false, features = ["transport", "prost"] } diff --git a/talpid-core/src/dns/windows/dnsapi.rs b/talpid-core/src/dns/windows/dnsapi.rs new file mode 100644 index 0000000000..f0c8567c59 --- /dev/null +++ b/talpid-core/src/dns/windows/dnsapi.rs @@ -0,0 +1,135 @@ +use once_cell::sync::OnceCell; +use std::{ + io, + sync::{ + atomic::{AtomicUsize, Ordering}, + mpsc, Arc, + }, + time::{Duration, Instant}, +}; +use windows_sys::Win32::{ + Foundation::BOOL, + System::LibraryLoader::{ + FreeLibrary, GetProcAddress, LoadLibraryExW, LOAD_LIBRARY_SEARCH_SYSTEM32, + }, +}; + +type FlushResolverCacheFn = unsafe extern "stdcall" fn() -> BOOL; + +static DNSAPI_HANDLE: OnceCell<DnsApi> = OnceCell::new(); +static FLUSH_TIMEOUT: Duration = Duration::from_secs(5); + +const MAX_CONCURRENT_FLUSHES: usize = 5; + +/// Errors that can happen when configuring DNS on Windows. +#[derive(err_derive::Error, Debug)] +#[error(no_from)] +pub enum Error { + /// Failed to load dnsapi.dll. + #[error(display = "Failed to load dnsapi.dll")] + LoadDll(#[error(source)] io::Error), + + /// Failed to obtain exported function. + #[error(display = "Failed to obtain flush function")] + GetFunction(#[error(source)] io::Error), + + /// Failed to flush the DNS cache. + #[error(display = "Call to flush DNS cache failed")] + FlushCache, + + /// Too many flush attempts in progress. + #[error(display = "Too many flush attempts in progress")] + TooManyFlushAttempts, + + /// Flushing the DNS cache timed out. + #[error(display = "Timeout while flushing DNS cache")] + Timeout, +} + +pub fn flush_resolver_cache() -> Result<(), Error> { + DNSAPI_HANDLE + .get_or_try_init(|| DnsApi::new())? + .flush_cache() +} + +struct DnsApi { + in_flight_flush_count: Arc<AtomicUsize>, + flush_fn: FlushResolverCacheFn, +} + +unsafe impl Send for DnsApi {} +unsafe impl Sync for DnsApi {} + +impl DnsApi { + fn new() -> Result<Self, Error> { + let handle = unsafe { + LoadLibraryExW( + b"d\0n\0s\0a\0p\0i\0.\0d\0l\0l\0\0\0" as *const u8 as *const u16, + 0, + LOAD_LIBRARY_SEARCH_SYSTEM32, + ) + }; + if handle == 0 { + return Err(Error::LoadDll(io::Error::last_os_error())); + } + + let flush_fn = unsafe { GetProcAddress(handle, b"DnsFlushResolverCache\0" as *const u8) }; + let flush_fn = flush_fn.ok_or_else(|| { + let error = io::Error::last_os_error(); + unsafe { FreeLibrary(handle) }; + Error::GetFunction(error) + })?; + + Ok(DnsApi { + in_flight_flush_count: Arc::new(AtomicUsize::new(0)), + flush_fn: unsafe { *(&flush_fn as *const _ as *const _) }, + }) + } + + fn flush_cache(&self) -> Result<(), Error> { + if self + .in_flight_flush_count + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |val| { + if val >= MAX_CONCURRENT_FLUSHES { + return None; + } + Some(val + 1) + }) + .is_err() + { + return Err(Error::TooManyFlushAttempts); + } + + let (tx, rx) = mpsc::channel(); + let flush_count = self.in_flight_flush_count.clone(); + + let flush_fn = self.flush_fn; + + std::thread::spawn(move || { + let begin = Instant::now(); + + let result = if unsafe { (flush_fn)() } != 0 { + let elapsed = begin.elapsed(); + if elapsed >= FLUSH_TIMEOUT { + log::warn!( + "Flushing system DNS cache took {} seconds", + elapsed.as_secs() + ); + } else { + log::debug!("Flushed system DNS cache"); + } + Ok(()) + } else { + Err(Error::FlushCache) + }; + let _ = tx.send(result); + + flush_count.fetch_sub(1, Ordering::SeqCst); + }); + + match rx.recv_timeout(FLUSH_TIMEOUT) { + Ok(result) => result, + Err(_timeout_err) => Err(Error::Timeout), + } + } +} diff --git a/talpid-core/src/dns/windows/mod.rs b/talpid-core/src/dns/windows/mod.rs index 78c167e769..85d964aca1 100644 --- a/talpid-core/src/dns/windows/mod.rs +++ b/talpid-core/src/dns/windows/mod.rs @@ -1,9 +1,5 @@ -use crate::windows::{get_system_dir, guid_from_luid, luid_from_alias, string_from_guid}; -use std::{ - io, - net::IpAddr, - process::{Command, Stdio}, -}; +use crate::windows::{guid_from_luid, luid_from_alias, string_from_guid}; +use std::{io, net::IpAddr}; use talpid_types::ErrorExt; use winapi::shared::guiddef::GUID; use winreg::{ @@ -12,6 +8,8 @@ use winreg::{ RegKey, }; +mod dnsapi; + /// Errors that can happen when configuring DNS on Windows. #[derive(err_derive::Error, Debug)] #[error(no_from)] @@ -25,20 +23,12 @@ pub enum Error { InterfaceGuidError(#[error(source)] io::Error), /// Failure to flush DNS cache. - #[error(display = "Failed to execute ipconfig")] - ExecuteIpconfigError(#[error(source)] io::Error), - - /// Failure to flush DNS cache. #[error(display = "Failed to flush DNS resolver cache")] - FlushResolverCacheError, + FlushResolverCacheError(#[error(source)] dnsapi::Error), /// Failed to update DNS servers for interface. #[error(display = "Failed to update interface DNS servers")] SetResolversError(#[error(source)] io::Error), - - /// Failed to locate system dir. - #[error(display = "Failed to locate the system directory")] - SystemDirError(#[error(source)] io::Error), } pub struct DnsMonitor { @@ -152,15 +142,5 @@ fn config_interface<'a>( } fn flush_dns_cache() -> Result<(), Error> { - let sysdir = get_system_dir().map_err(Error::SystemDirError)?; - Command::new(sysdir.join("ipconfig.exe")) - .arg("/flushdns") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .map_err(Error::ExecuteIpconfigError)?; - // The exit code cannot be trusted. And the stdout messages from Windows CLI tools - // are localized, so it can also not be checked. There is no way to verify if - // this flush succeeded or failed. - Ok(()) + dnsapi::flush_resolver_cache().map_err(Error::FlushResolverCacheError) } |
