summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2022-08-10 14:20:12 +0200
committerDavid Lönnhager <david.l@mullvad.net>2022-08-10 14:20:12 +0200
commitf053c5c071b94b7ba2e3025569cf25b089ccdc81 (patch)
treefe4f2d6998e810f69e60728a81256dc118d7d05e
parent39486e0096a4d4d65b63fc91726afbd38e011532 (diff)
parenta4d9712396a5183ca5de5abcad9e0bb570339814 (diff)
downloadmullvadvpn-f053c5c071b94b7ba2e3025569cf25b089ccdc81.tar.xz
mullvadvpn-f053c5c071b94b7ba2e3025569cf25b089ccdc81.zip
Merge branch 'win-use-dnsapi'
-rw-r--r--CHANGELOG.md3
-rw-r--r--Cargo.lock6
-rw-r--r--talpid-core/Cargo.toml8
-rw-r--r--talpid-core/src/dns/windows/dnsapi.rs135
-rw-r--r--talpid-core/src/dns/windows/mod.rs32
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)
}