Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor tunnel state machine DNS configuration #6783

Merged
merged 1 commit into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 67 additions & 10 deletions mullvad-daemon/src/dns.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use mullvad_types::settings::{DnsOptions, DnsState};
use std::net::{IpAddr, Ipv4Addr};
use talpid_core::{dns::DnsConfig, firewall::is_local_address};

/// When we want to block certain contents with the help of DNS server side,
/// we compute the resolver IP to use based on these constants. The last
Expand All @@ -12,9 +13,8 @@ const DNS_ADULT_BLOCKING_IP_BIT: u8 = 1 << 3; // 0b00001000
const DNS_GAMBLING_BLOCKING_IP_BIT: u8 = 1 << 4; // 0b00010000
const DNS_SOCIAL_MEDIA_BLOCKING_IP_BIT: u8 = 1 << 5; // 0b00100000

/// Return the resolvers as a vector of `IpAddr`s. Returns `None` when no special resolvers
/// are requested and the tunnel default gateway should be used.
pub fn addresses_from_options(options: &DnsOptions) -> Option<Vec<IpAddr>> {
/// Return the DNS resolvers to use
pub fn addresses_from_options(options: &DnsOptions) -> DnsConfig {
match options.state {
DnsState::Default => {
// Check if we should use a custom blocking DNS resolver.
Expand Down Expand Up @@ -43,17 +43,74 @@ pub fn addresses_from_options(options: &DnsOptions) -> Option<Vec<IpAddr>> {
if last_byte != 0 {
let mut dns_ip = DNS_BLOCKING_IP_BASE.octets();
dns_ip[dns_ip.len() - 1] |= last_byte;
Some(vec![IpAddr::V4(Ipv4Addr::from(dns_ip))])
DnsConfig::from_addresses(&[IpAddr::V4(Ipv4Addr::from(dns_ip))], &[])
} else {
None
DnsConfig::default()
}
}
DnsState::Custom if options.custom_options.addresses.is_empty() => DnsConfig::default(),
DnsState::Custom => {
if options.custom_options.addresses.is_empty() {
None
} else {
Some(options.custom_options.addresses.clone())
}
let (non_tunnel_config, tunnel_config): (Vec<_>, Vec<_>) = options
.custom_options
.addresses
.iter()
// Private IP ranges should not be tunneled
.partition(|&addr| is_local_address(addr));
DnsConfig::from_addresses(&tunnel_config, &non_tunnel_config)
}
}
}

#[cfg(test)]
mod test {
use crate::dns::addresses_from_options;
use mullvad_types::settings::{CustomDnsOptions, DefaultDnsOptions, DnsOptions, DnsState};
use talpid_core::dns::DnsConfig;

#[test]
fn test_default_dns() {
let public_cfg = DnsOptions {
state: DnsState::Default,
custom_options: CustomDnsOptions::default(),
default_options: DefaultDnsOptions::default(),
};

assert_eq!(addresses_from_options(&public_cfg), DnsConfig::default());
}

#[test]
fn test_content_blockers() {
let public_cfg = DnsOptions {
state: DnsState::Default,
custom_options: CustomDnsOptions::default(),
default_options: DefaultDnsOptions {
block_ads: true,
..DefaultDnsOptions::default()
},
};

assert_eq!(
addresses_from_options(&public_cfg),
DnsConfig::from_addresses(&["100.64.0.1".parse().unwrap()], &[],)
);
}

// Public IPs should be tunneled, but most private IPs should not be
#[test]
fn test_custom_dns() {
let public_ip = "1.2.3.4".parse().unwrap();
let private_ip = "172.16.10.1".parse().unwrap();
let public_cfg = DnsOptions {
state: DnsState::Custom,
custom_options: CustomDnsOptions {
addresses: vec![public_ip, private_ip],
},
default_options: DefaultDnsOptions::default(),
};

assert_eq!(
addresses_from_options(&public_cfg),
DnsConfig::from_addresses(&[public_ip], &[private_ip],)
);
}
}
2 changes: 1 addition & 1 deletion mullvad-daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ impl Daemon {
tunnel_state_machine::InitialTunnelState {
allow_lan: settings.allow_lan,
block_when_disconnected: settings.block_when_disconnected,
dns_servers: dns::addresses_from_options(&settings.tunnel_options.dns_options),
dns_config: dns::addresses_from_options(&settings.tunnel_options.dns_options),
allowed_endpoint: access_mode_handler
.get_current()
.await
Expand Down
4 changes: 2 additions & 2 deletions talpid-core/src/dns/android.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::net::IpAddr;
use crate::dns::ResolvedDnsConfig;

/// Stub error type for DNS errors on Android.
#[derive(Debug, thiserror::Error)]
Expand All @@ -14,7 +14,7 @@ impl super::DnsMonitorT for DnsMonitor {
Ok(DnsMonitor)
}

fn set(&mut self, _interface: &str, _servers: &[IpAddr]) -> Result<(), Self::Error> {
fn set(&mut self, _interface: &str, _servers: ResolvedDnsConfig) -> Result<(), Self::Error> {
Ok(())
}

Expand Down
5 changes: 4 additions & 1 deletion talpid-core/src/dns/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use self::{
use std::{env, fmt, net::IpAddr};
use talpid_routing::RouteManagerHandle;

use super::ResolvedDnsConfig;

pub type Result<T> = std::result::Result<T, Error>;

/// Errors that can happen in the Linux DNS monitor
Expand Down Expand Up @@ -53,7 +55,8 @@ impl super::DnsMonitorT for DnsMonitor {
})
}

fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<()> {
fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<()> {
let servers = config.tunnel_config();
self.reset()?;
// Creating a new DNS monitor for each set, in case the system changed how it manages DNS.
let mut inner = DnsMonitorHolder::new()?;
Expand Down
8 changes: 6 additions & 2 deletions talpid-core/src/dns/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use system_configuration::{
};
use talpid_routing::debounce::BurstGuard;

use super::ResolvedDnsConfig;

pub type Result<T> = std::result::Result<T, Error>;

/// Errors that can happen when setting/monitoring DNS on macOS.
Expand Down Expand Up @@ -357,9 +359,11 @@ impl super::DnsMonitorT for DnsMonitor {
})
}

fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<()> {
fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<()> {
let servers: Vec<_> = config.addresses().collect();

let mut state = self.state.lock();
state.apply_new_config(&self.store, interface, servers)
state.apply_new_config(&self.store, interface, &servers)
}

fn reset(&mut self) -> Result<()> {
Expand Down
127 changes: 116 additions & 11 deletions talpid-core/src/dns/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::fmt;
use std::net::IpAddr;

#[cfg(target_os = "linux")]
use talpid_routing::RouteManagerHandle;

Expand All @@ -23,6 +25,116 @@ mod imp;

pub use self::imp::Error;

/// DNS configuration
#[derive(Debug, Clone, PartialEq)]
pub struct DnsConfig {
config: InnerDnsConfig,
}

impl Default for DnsConfig {
fn default() -> Self {
Self {
config: InnerDnsConfig::Default,
}
}
}

impl DnsConfig {
/// Use the specified addresses for DNS resolution
pub fn from_addresses(tunnel_config: &[IpAddr], non_tunnel_config: &[IpAddr]) -> Self {
DnsConfig {
config: InnerDnsConfig::Override {
tunnel_config: tunnel_config.to_owned(),
non_tunnel_config: non_tunnel_config.to_owned(),
},
}
}
}

#[derive(Debug, Clone, PartialEq)]
enum InnerDnsConfig {
/// Use gateway addresses from the tunnel config
Default,
/// Use the specified addresses for DNS resolution
Override {
/// Addresses to configure on the tunnel interface
tunnel_config: Vec<IpAddr>,
/// Addresses to allow on non-tunnel interface.
/// For the most part, the tunnel state machine will not handle any of this configuration
/// on non-tunnel interface, only allow them in the firewall.
non_tunnel_config: Vec<IpAddr>,
},
}

impl DnsConfig {
pub(crate) fn resolve(&self, default_tun_config: &[IpAddr]) -> ResolvedDnsConfig {
match &self.config {
InnerDnsConfig::Default => ResolvedDnsConfig {
tunnel_config: default_tun_config.to_owned(),
non_tunnel_config: vec![],
},
InnerDnsConfig::Override {
tunnel_config,
non_tunnel_config,
} => ResolvedDnsConfig {
tunnel_config: tunnel_config.to_owned(),
non_tunnel_config: non_tunnel_config.to_owned(),
},
}
}
}

/// DNS configuration with `DnsConfig::Default` resolved
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedDnsConfig {
/// Addresses to configure on the tunnel interface
tunnel_config: Vec<IpAddr>,
/// Addresses to allow on non-tunnel interface.
/// For the most part, the tunnel state machine will not handle any of this configuration
/// on non-tunnel interface, only allow them in the firewall.
non_tunnel_config: Vec<IpAddr>,
}

impl fmt::Display for ResolvedDnsConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tunnel DNS: ")?;
Self::fmt_addr_set(f, &self.tunnel_config)?;

f.write_str(" Non-tunnel DNS: ")?;
Self::fmt_addr_set(f, &self.non_tunnel_config)
}
}

impl ResolvedDnsConfig {
fn fmt_addr_set(f: &mut fmt::Formatter<'_>, addrs: &[IpAddr]) -> fmt::Result {
f.write_str("{")?;
for (i, addr) in addrs.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{}", addr)?;
}
f.write_str("}")
}

/// Addresses to configure on the tunnel interface
pub fn tunnel_config(&self) -> &[IpAddr] {
&self.tunnel_config
}

/// Addresses to allow on non-tunnel interface.
/// For the most part, the tunnel state machine will not handle any of this configuration
/// on non-tunnel interface, only allow them in the firewall.
pub fn non_tunnel_config(&self) -> &[IpAddr] {
&self.non_tunnel_config
}

/// Consume `self` and return a vector of all addresses
pub fn addresses(self) -> impl Iterator<Item = IpAddr> {
self.non_tunnel_config.into_iter().chain(self.tunnel_config)
}
}

/// Sets and monitors system DNS settings. Makes sure the desired DNS servers are being used.
pub struct DnsMonitor {
inner: imp::DnsMonitor,
Expand All @@ -45,16 +157,9 @@ impl DnsMonitor {
}

/// Set DNS to the given servers. And start monitoring the system for changes.
pub fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<(), Error> {
log::info!(
"Setting DNS servers to {}",
servers
.iter()
.map(|ip| ip.to_string())
.collect::<Vec<String>>()
.join(", ")
);
self.inner.set(interface, servers)
pub fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Error> {
log::info!("Setting DNS servers: {config}",);
self.inner.set(interface, config)
}

/// Reset system DNS settings to what it was before being set by this instance.
Expand All @@ -81,7 +186,7 @@ trait DnsMonitorT: Sized {
#[cfg(target_os = "linux")] route_manager: RouteManagerHandle,
) -> Result<Self, Self::Error>;

fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<(), Self::Error>;
fn set(&mut self, interface: &str, servers: ResolvedDnsConfig) -> Result<(), Self::Error>;

fn reset(&mut self) -> Result<(), Self::Error>;

Expand Down
16 changes: 8 additions & 8 deletions talpid-core/src/dns/windows/auto.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{iphlpapi, netsh, tcpip};
use crate::dns::DnsMonitorT;
use crate::dns::{DnsMonitorT, ResolvedDnsConfig};
use windows_sys::Win32::System::Rpc::RPC_S_SERVER_UNAVAILABLE;

pub struct DnsMonitor {
Expand All @@ -12,11 +12,11 @@ enum InnerMonitor {
}

impl InnerMonitor {
fn set(&mut self, interface: &str, servers: &[std::net::IpAddr]) -> Result<(), super::Error> {
fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> 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)?,
InnerMonitor::Iphlpapi(monitor) => monitor.set(interface, config)?,
InnerMonitor::Netsh(monitor) => monitor.set(interface, config)?,
InnerMonitor::Tcpip(monitor) => monitor.set(interface, config)?,
}
Ok(())
}
Expand Down Expand Up @@ -53,10 +53,10 @@ impl DnsMonitorT for DnsMonitor {
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);
fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Self::Error> {
let result = self.current_monitor.set(interface, config.clone());
if self.fallback_due_to_dnscache(&result) {
return self.set(interface, servers);
return self.set(interface, config);
}
result
}
Expand Down
5 changes: 3 additions & 2 deletions talpid-core/src/dns/windows/iphlpapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! it requires at least Windows 10, build 19041. For that reason, use run-time linking and fall
//! back on other methods if it is not available.

use crate::dns::DnsMonitorT;
use crate::dns::{DnsMonitorT, ResolvedDnsConfig};
use once_cell::sync::OnceCell;
use std::{
ffi::OsString,
Expand Down Expand Up @@ -122,7 +122,8 @@ impl DnsMonitorT for DnsMonitor {
Ok(DnsMonitor { current_guid: None })
}

fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<(), Error> {
fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Error> {
let servers = config.tunnel_config();
let guid = guid_from_luid(&luid_from_alias(interface).map_err(Error::ObtainInterfaceLuid)?)
.map_err(Error::ObtainInterfaceGuid)?;

Expand Down
Loading
Loading