summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2023-03-01 15:07:13 +0100
committerDavid Lönnhager <david.l@mullvad.net>2023-03-01 15:07:13 +0100
commit2bd5e536d34bb60ba4a77fb6d37f2c5eff23463c (patch)
treeca569321f905e3b23a411a759e4bd39bb7e131ce
parentafcd5313b2daacf1c4f2dca138de0fc0745fb96e (diff)
parent34d96528d55d78889be936dda556cb1d5c3cc3c7 (diff)
downloadmullvadvpn-2bd5e536d34bb60ba4a77fb6d37f2c5eff23463c.tar.xz
mullvadvpn-2bd5e536d34bb60ba4a77fb6d37f2c5eff23463c.zip
Merge branch 'win-iphlpapi-dns-method' into main
-rw-r--r--CHANGELOG.md4
-rw-r--r--README.md1
-rw-r--r--talpid-core/Cargo.toml1
-rw-r--r--talpid-core/src/dns/windows/auto.rs112
-rw-r--r--talpid-core/src/dns/windows/iphlpapi.rs212
-rw-r--r--talpid-core/src/dns/windows/mod.rs24
-rw-r--r--talpid-core/src/dns/windows/tcpip.rs22
7 files changed, 371 insertions, 5 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 148e65b012..fcf376d7a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,10 @@ Line wrap the file at 100 chars. Th
- Add setting for quantum resistant tunnels to the desktop GUI.
- Enable `TCP_NODELAY` for the socket used by WireGuard over TCP. Improves latency and performance.
+#### Windows
+- Use `SetInterfaceDnsSettings` to config DNS when it's available (on Windows 10, version 1809 and
+ above).
+
### Changed
- Update the Post-Quantum secure key exchange gRPC client to use the stabilized
`PskExchangeV1` endpoint
diff --git a/README.md b/README.md
index 2f40f6e3e6..b87f4aaa62 100644
--- a/README.md
+++ b/README.md
@@ -139,6 +139,7 @@ See [this](Release.md) for instructions on how to make a new release.
* `"network-manager"`: use `NetworkManager` service through DBus
* Windows
+ * `iphlpapi`: use the IP helper API
* `netsh`: use the `netsh` program
* `tcpip`: set TCP/IP parameters in the registry
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index 24203d4a11..285d0c0d1b 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -94,6 +94,7 @@ features = [
"Win32_System_LibraryLoader",
"Win32_System_ProcessStatus",
"Win32_System_Registry",
+ "Win32_System_Rpc",
"Win32_System_Services",
"Win32_System_SystemServices",
"Win32_System_Threading",
diff --git a/talpid-core/src/dns/windows/auto.rs b/talpid-core/src/dns/windows/auto.rs
new file mode 100644
index 0000000000..a09804b700
--- /dev/null
+++ b/talpid-core/src/dns/windows/auto.rs
@@ -0,0 +1,112 @@
+use super::{iphlpapi, netsh, tcpip};
+use crate::dns::DnsMonitorT;
+use windows_sys::Win32::System::Rpc::RPC_S_SERVER_UNAVAILABLE;
+
+pub struct DnsMonitor {
+ current_monitor: InnerMonitor,
+}
+enum InnerMonitor {
+ Iphlpapi(iphlpapi::DnsMonitor),
+ Netsh(netsh::DnsMonitor),
+ Tcpip(tcpip::DnsMonitor),
+}
+
+impl InnerMonitor {
+ fn set(&mut self, interface: &str, servers: &[std::net::IpAddr]) -> Result<(), super::Error> {
+ match self {
+ InnerMonitor::Iphlpapi(monitor) => monitor.set(interface, servers)?,
+ InnerMonitor::Netsh(monitor) => monitor.set(interface, servers)?,
+ InnerMonitor::Tcpip(monitor) => monitor.set(interface, servers)?,
+ }
+ Ok(())
+ }
+
+ fn reset(&mut self) -> Result<(), super::Error> {
+ match self {
+ InnerMonitor::Iphlpapi(monitor) => monitor.reset()?,
+ InnerMonitor::Netsh(monitor) => monitor.reset()?,
+ InnerMonitor::Tcpip(monitor) => monitor.reset()?,
+ }
+ Ok(())
+ }
+
+ fn reset_before_interface_removal(&mut self) -> Result<(), super::Error> {
+ match self {
+ InnerMonitor::Iphlpapi(monitor) => monitor.reset_before_interface_removal()?,
+ InnerMonitor::Netsh(monitor) => monitor.reset_before_interface_removal()?,
+ InnerMonitor::Tcpip(monitor) => monitor.reset_before_interface_removal()?,
+ }
+ Ok(())
+ }
+}
+
+impl DnsMonitorT for DnsMonitor {
+ type Error = super::Error;
+
+ fn new() -> Result<Self, Self::Error> {
+ let current_monitor;
+
+ if iphlpapi::DnsMonitor::is_supported() {
+ current_monitor = InnerMonitor::Iphlpapi(iphlpapi::DnsMonitor::new()?);
+ } else {
+ current_monitor = InnerMonitor::Netsh(netsh::DnsMonitor::new()?);
+ }
+
+ Ok(Self { current_monitor })
+ }
+
+ fn set(&mut self, interface: &str, servers: &[std::net::IpAddr]) -> Result<(), Self::Error> {
+ let result = self.current_monitor.set(interface, servers);
+ if self.fallback_due_to_dnscache(&result) {
+ return self.set(interface, servers);
+ }
+ result
+ }
+
+ fn reset(&mut self) -> Result<(), Self::Error> {
+ let result = self.current_monitor.reset();
+ if self.fallback_due_to_dnscache(&result) {
+ return self.reset();
+ }
+ result
+ }
+
+ fn reset_before_interface_removal(&mut self) -> Result<(), Self::Error> {
+ let result = self.current_monitor.reset_before_interface_removal();
+ if self.fallback_due_to_dnscache(&result) {
+ return self.reset_before_interface_removal();
+ }
+ result
+ }
+}
+
+impl DnsMonitor {
+ fn fallback_due_to_dnscache(&mut self, result: &Result<(), super::Error>) -> bool {
+ let is_dnscache_error = match result {
+ Err(super::Error::Iphlpapi(iphlpapi::Error::SetInterfaceDnsSettings(error))) => {
+ *error == RPC_S_SERVER_UNAVAILABLE
+ }
+ Err(super::Error::Netsh(netsh::Error::NetshError(Some(1)))) => true,
+ _ => false,
+ };
+ if is_dnscache_error {
+ log::warn!("dnscache is not running? Falling back on tcpip method");
+
+ match tcpip::DnsMonitor::new() {
+ Ok(mut tcpip) => {
+ // We need to disable flushing here since it may fail.
+ // Because dnscache is disabled, there's nothing to flush anyhow.
+ tcpip.disable_flushing();
+ self.current_monitor = InnerMonitor::Tcpip(tcpip);
+ true
+ }
+ Err(error) => {
+ log::error!("Failed to init tcpip DNS module: {error}");
+ false
+ }
+ }
+ } else {
+ false
+ }
+ }
+}
diff --git a/talpid-core/src/dns/windows/iphlpapi.rs b/talpid-core/src/dns/windows/iphlpapi.rs
new file mode 100644
index 0000000000..6f33cf3cf9
--- /dev/null
+++ b/talpid-core/src/dns/windows/iphlpapi.rs
@@ -0,0 +1,212 @@
+use crate::dns::DnsMonitorT;
+use once_cell::sync::OnceCell;
+use std::{
+ ffi::OsString,
+ io,
+ net::{IpAddr, Ipv4Addr, Ipv6Addr},
+ os::windows::ffi::OsStrExt,
+ ptr,
+};
+use talpid_windows_net::{guid_from_luid, luid_from_alias};
+use windows_sys::{
+ core::GUID,
+ s, w,
+ Win32::{
+ Foundation::{ERROR_PROC_NOT_FOUND, NO_ERROR, NTSTATUS},
+ NetworkManagement::IpHelper::{
+ DNS_INTERFACE_SETTINGS, DNS_INTERFACE_SETTINGS_VERSION1, DNS_SETTING_IPV6,
+ DNS_SETTING_NAMESERVER,
+ },
+ System::LibraryLoader::{
+ FreeLibrary, GetProcAddress, LoadLibraryExW, LOAD_LIBRARY_SEARCH_SYSTEM32,
+ },
+ },
+};
+
+/// Errors that can happen when configuring DNS on Windows.
+#[derive(err_derive::Error, Debug)]
+#[error(no_from)]
+pub enum Error {
+ /// Failure to obtain an interface LUID given an alias.
+ #[error(display = "Failed to obtain LUID for the interface alias")]
+ InterfaceLuidError(#[error(source)] io::Error),
+
+ /// Failure to obtain an interface GUID.
+ #[error(display = "Failed to obtain GUID for the interface")]
+ InterfaceGuidError(#[error(source)] io::Error),
+
+ /// Failed to set DNS settings on interface.
+ #[error(display = "Failed to set DNS settings on interface: {}", _0)]
+ SetInterfaceDnsSettings(i32),
+
+ /// Failure to flush DNS cache.
+ #[error(display = "Failed to flush DNS resolver cache")]
+ FlushResolverCacheError(#[error(source)] super::dnsapi::Error),
+
+ /// Failed to load iphlpapi.dll.
+ #[error(display = "Failed to load iphlpapi.dll")]
+ LoadDll(#[error(source)] io::Error),
+
+ /// Failed to obtain exported function.
+ #[error(display = "Failed to obtain DNS function")]
+ GetFunction(#[error(source)] io::Error),
+}
+
+type SetInterfaceDnsSettingsFn = unsafe extern "stdcall" fn(
+ interface: GUID,
+ settings: *const DNS_INTERFACE_SETTINGS,
+) -> NTSTATUS;
+
+struct IphlpApi {
+ set_interface_dns_settings: SetInterfaceDnsSettingsFn,
+}
+
+unsafe impl Send for IphlpApi {}
+unsafe impl Sync for IphlpApi {}
+
+static IPHLPAPI_HANDLE: OnceCell<IphlpApi> = OnceCell::new();
+
+impl IphlpApi {
+ fn new() -> Result<Self, Error> {
+ let module = unsafe { LoadLibraryExW(w!("iphlpapi.dll"), 0, LOAD_LIBRARY_SEARCH_SYSTEM32) };
+ if module == 0 {
+ log::error!("Failed to load iphlpapi.dll");
+ return Err(Error::LoadDll(io::Error::last_os_error()));
+ }
+
+ let set_interface_dns_settings =
+ unsafe { GetProcAddress(module, s!("SetInterfaceDnsSettings")) };
+ let set_interface_dns_settings = set_interface_dns_settings.ok_or_else(|| {
+ let error = io::Error::last_os_error();
+
+ if error.raw_os_error() != Some(ERROR_PROC_NOT_FOUND as i32) {
+ log::error!(
+ "Could not find SetInterfaceDnsSettings due to an unexpected error: {error}"
+ );
+ }
+
+ unsafe { FreeLibrary(module) };
+ Error::GetFunction(error)
+ })?;
+
+ // NOTE: Leaking `module` here, since we're lazily initializing it
+
+ Ok(Self {
+ set_interface_dns_settings: unsafe {
+ *(&set_interface_dns_settings as *const _ as *const _)
+ },
+ })
+ }
+}
+
+pub struct DnsMonitor {
+ current_guid: Option<GUID>,
+}
+
+impl DnsMonitor {
+ pub fn is_supported() -> bool {
+ IPHLPAPI_HANDLE.get_or_try_init(|| IphlpApi::new()).is_ok()
+ }
+}
+
+impl DnsMonitorT for DnsMonitor {
+ type Error = Error;
+
+ fn new() -> Result<Self, Error> {
+ Ok(DnsMonitor { current_guid: None })
+ }
+
+ fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<(), Error> {
+ let guid = guid_from_luid(&luid_from_alias(interface).map_err(Error::InterfaceLuidError)?)
+ .map_err(Error::InterfaceGuidError)?;
+
+ let mut v4_servers = vec![];
+ let mut v6_servers = vec![];
+
+ for server in servers {
+ match server {
+ IpAddr::V4(addr) => v4_servers.push(addr),
+ IpAddr::V6(addr) => v6_servers.push(addr),
+ }
+ }
+
+ self.current_guid = Some(guid);
+
+ if !v4_servers.is_empty() {
+ set_interface_dns_servers_v4(&guid, &v4_servers)?;
+ }
+ if !v6_servers.is_empty() {
+ set_interface_dns_servers_v6(&guid, &v6_servers)?;
+ }
+
+ flush_dns_cache()?;
+
+ Ok(())
+ }
+
+ fn reset(&mut self) -> Result<(), Error> {
+ if let Some(guid) = self.current_guid.take() {
+ set_interface_dns_servers_v4(&guid, &[])
+ .and(set_interface_dns_servers_v6(&guid, &[]))
+ .and(flush_dns_cache())?;
+ }
+ Ok(())
+ }
+
+ fn reset_before_interface_removal(&mut self) -> Result<(), Self::Error> {
+ // do nothing since the tunnel interface goes away
+ let _ = self.current_guid.take();
+ Ok(())
+ }
+}
+
+fn set_interface_dns_servers_v4(guid: &GUID, servers: &[&Ipv4Addr]) -> Result<(), Error> {
+ set_interface_dns_servers(guid, servers, DNS_SETTING_NAMESERVER)
+}
+
+fn set_interface_dns_servers_v6(guid: &GUID, servers: &[&Ipv6Addr]) -> Result<(), Error> {
+ set_interface_dns_servers(guid, servers, DNS_SETTING_NAMESERVER | DNS_SETTING_IPV6)
+}
+
+fn set_interface_dns_servers<T: ToString>(
+ guid: &GUID,
+ servers: &[T],
+ flags: u32,
+) -> Result<(), Error> {
+ let iphlpapi = IPHLPAPI_HANDLE.get_or_try_init(|| IphlpApi::new())?;
+
+ // Create comma-separated nameserver list
+ let nameservers = servers
+ .iter()
+ .map(|addr| addr.to_string())
+ .collect::<Vec<String>>()
+ .join(",");
+ let mut nameservers: Vec<u16> = OsString::from(nameservers)
+ .encode_wide()
+ .chain(std::iter::once(0u16))
+ .collect();
+
+ let dns_interface_settings = DNS_INTERFACE_SETTINGS {
+ Version: DNS_INTERFACE_SETTINGS_VERSION1,
+ Flags: u64::from(flags),
+ Domain: ptr::null_mut(),
+ NameServer: nameservers.as_mut_ptr(),
+ SearchList: ptr::null_mut(),
+ RegistrationEnabled: 0,
+ RegisterAdapterName: 0,
+ EnableLLMNR: 0,
+ QueryAdapterName: 0,
+ ProfileNameServer: ptr::null_mut(),
+ };
+
+ let result =
+ unsafe { (iphlpapi.set_interface_dns_settings)(guid.to_owned(), &dns_interface_settings) };
+ if result != (NO_ERROR as i32) {
+ return Err(Error::SetInterfaceDnsSettings(result));
+ }
+ Ok(())
+}
+
+fn flush_dns_cache() -> Result<(), Error> {
+ super::dnsapi::flush_resolver_cache().map_err(Error::FlushResolverCacheError)
+}
diff --git a/talpid-core/src/dns/windows/mod.rs b/talpid-core/src/dns/windows/mod.rs
index 10ce85c055..4ab9976417 100644
--- a/talpid-core/src/dns/windows/mod.rs
+++ b/talpid-core/src/dns/windows/mod.rs
@@ -1,12 +1,20 @@
use std::{env, fmt, net::IpAddr};
+use super::DnsMonitorT;
+
+mod auto;
mod dnsapi;
+mod iphlpapi;
mod netsh;
mod tcpip;
/// Errors that can happen when configuring DNS on Windows.
#[derive(err_derive::Error, Debug)]
pub enum Error {
+ /// Failed to set DNS config using the iphlpapi module.
+ #[error(display = "Error in iphlpapi module")]
+ Iphlpapi(#[error(source)] iphlpapi::Error),
+
/// Failed to set DNS config using the netsh module.
#[error(display = "Error in netsh module")]
Netsh(#[error(source)] netsh::Error),
@@ -20,15 +28,17 @@ pub struct DnsMonitor {
inner: DnsMonitorHolder,
}
-impl super::DnsMonitorT for DnsMonitor {
+impl DnsMonitorT for DnsMonitor {
type Error = Error;
fn new() -> Result<Self, Error> {
let dns_module = env::var_os("TALPID_DNS_MODULE");
let inner = match dns_module.as_ref().and_then(|value| value.to_str()) {
+ Some("iphlpapi") => DnsMonitorHolder::Iphlpapi(iphlpapi::DnsMonitor::new()?),
Some("tcpip") => DnsMonitorHolder::Tcpip(tcpip::DnsMonitor::new()?),
- Some(_) | None => DnsMonitorHolder::Netsh(netsh::DnsMonitor::new()?),
+ Some("netsh") => DnsMonitorHolder::Netsh(netsh::DnsMonitor::new()?),
+ Some(_) | None => DnsMonitorHolder::Auto(auto::DnsMonitor::new()?),
};
log::debug!("DNS monitor: {}", inner);
@@ -38,6 +48,8 @@ impl super::DnsMonitorT for DnsMonitor {
fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<(), Error> {
match self.inner {
+ DnsMonitorHolder::Auto(ref mut inner) => inner.set(interface, servers)?,
+ DnsMonitorHolder::Iphlpapi(ref mut inner) => inner.set(interface, servers)?,
DnsMonitorHolder::Netsh(ref mut inner) => inner.set(interface, servers)?,
DnsMonitorHolder::Tcpip(ref mut inner) => inner.set(interface, servers)?,
}
@@ -46,6 +58,8 @@ impl super::DnsMonitorT for DnsMonitor {
fn reset(&mut self) -> Result<(), Error> {
match self.inner {
+ DnsMonitorHolder::Auto(ref mut inner) => inner.reset()?,
+ DnsMonitorHolder::Iphlpapi(ref mut inner) => inner.reset()?,
DnsMonitorHolder::Netsh(ref mut inner) => inner.reset()?,
DnsMonitorHolder::Tcpip(ref mut inner) => inner.reset()?,
}
@@ -54,6 +68,8 @@ impl super::DnsMonitorT for DnsMonitor {
fn reset_before_interface_removal(&mut self) -> Result<(), Error> {
match self.inner {
+ DnsMonitorHolder::Auto(ref mut inner) => inner.reset_before_interface_removal()?,
+ DnsMonitorHolder::Iphlpapi(ref mut inner) => inner.reset_before_interface_removal()?,
DnsMonitorHolder::Netsh(ref mut inner) => inner.reset_before_interface_removal()?,
DnsMonitorHolder::Tcpip(ref mut inner) => inner.reset_before_interface_removal()?,
}
@@ -62,6 +78,8 @@ impl super::DnsMonitorT for DnsMonitor {
}
enum DnsMonitorHolder {
+ Auto(auto::DnsMonitor),
+ Iphlpapi(iphlpapi::DnsMonitor),
Netsh(netsh::DnsMonitor),
Tcpip(tcpip::DnsMonitor),
}
@@ -69,6 +87,8 @@ enum DnsMonitorHolder {
impl fmt::Display for DnsMonitorHolder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
+ DnsMonitorHolder::Auto(_) => f.write_str("auto (iphlpapi > netsh > tcpip)"),
+ DnsMonitorHolder::Iphlpapi(_) => f.write_str("SetInterfaceDnsSettings (iphlpapi)"),
DnsMonitorHolder::Netsh(_) => f.write_str("netsh"),
DnsMonitorHolder::Tcpip(_) => f.write_str("TCP/IP registry parameter"),
}
diff --git a/talpid-core/src/dns/windows/tcpip.rs b/talpid-core/src/dns/windows/tcpip.rs
index f7536eaed1..2fdb7e3c28 100644
--- a/talpid-core/src/dns/windows/tcpip.rs
+++ b/talpid-core/src/dns/windows/tcpip.rs
@@ -32,13 +32,17 @@ pub enum Error {
pub struct DnsMonitor {
current_guid: Option<GUID>,
+ should_flush: bool,
}
impl DnsMonitorT for DnsMonitor {
type Error = Error;
fn new() -> Result<Self, Error> {
- Ok(DnsMonitor { current_guid: None })
+ Ok(DnsMonitor {
+ current_guid: None,
+ should_flush: true,
+ })
}
fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<(), Error> {
@@ -46,18 +50,30 @@ impl DnsMonitorT for DnsMonitor {
.map_err(Error::InterfaceGuidError)?;
set_dns(&guid, servers)?;
self.current_guid = Some(guid);
- flush_dns_cache()?;
+ if self.should_flush {
+ flush_dns_cache()?;
+ }
Ok(())
}
fn reset(&mut self) -> Result<(), Error> {
if let Some(guid) = self.current_guid.take() {
- return set_dns(&guid, &[]).and(flush_dns_cache());
+ let mut result = set_dns(&guid, &[]);
+ if self.should_flush {
+ result = result.and(flush_dns_cache());
+ }
+ return result;
}
Ok(())
}
}
+impl DnsMonitor {
+ pub fn disable_flushing(&mut self) {
+ self.should_flush = false;
+ }
+}
+
fn set_dns(interface: &GUID, servers: &[IpAddr]) -> Result<(), Error> {
let transaction = Transaction::new().map_err(Error::SetResolversError)?;
let result = match set_dns_inner(&transaction, interface, servers) {