diff options
| author | David Lönnhager <david.l@mullvad.net> | 2024-09-05 19:42:18 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2024-09-18 11:01:45 +0200 |
| commit | beb063780d6f3a261e40ac7940c61c937e2b951d (patch) | |
| tree | 5d3d8e9ecb7101759957c1f702c1a59d5f426a7a /talpid-core/src | |
| parent | cdef6650f38dba21052f1c6e0d5e9e2ebadf0d97 (diff) | |
| download | mullvadvpn-beb063780d6f3a261e40ac7940c61c937e2b951d.tar.xz mullvadvpn-beb063780d6f3a261e40ac7940c61c937e2b951d.zip | |
Keep separate tunnel state machine configs for tunnel and non-tunnel DNS
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/dns/android.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/dns/linux/mod.rs | 5 | ||||
| -rw-r--r-- | talpid-core/src/dns/macos.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/dns/mod.rs | 127 | ||||
| -rw-r--r-- | talpid-core/src/dns/windows/auto.rs | 16 | ||||
| -rw-r--r-- | talpid-core/src/dns/windows/iphlpapi.rs | 5 | ||||
| -rw-r--r-- | talpid-core/src/dns/windows/mod.rs | 14 | ||||
| -rw-r--r-- | talpid-core/src/dns/windows/netsh.rs | 5 | ||||
| -rw-r--r-- | talpid-core/src/dns/windows/tcpip.rs | 6 | ||||
| -rw-r--r-- | talpid-core/src/firewall/linux.rs | 65 | ||||
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 158 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows.rs | 54 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 34 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 9 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 6 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/error_state.rs | 12 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 28 |
19 files changed, 340 insertions, 222 deletions
diff --git a/talpid-core/src/dns/android.rs b/talpid-core/src/dns/android.rs index 6f4e110d3f..01acae9eb5 100644 --- a/talpid-core/src/dns/android.rs +++ b/talpid-core/src/dns/android.rs @@ -1,4 +1,4 @@ -use std::net::IpAddr; +use crate::dns::ResolvedDnsConfig; /// Stub error type for DNS errors on Android. #[derive(Debug, thiserror::Error)] @@ -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(()) } diff --git a/talpid-core/src/dns/linux/mod.rs b/talpid-core/src/dns/linux/mod.rs index 3e7f6ac0b8..ad27237cd4 100644 --- a/talpid-core/src/dns/linux/mod.rs +++ b/talpid-core/src/dns/linux/mod.rs @@ -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 @@ -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()?; diff --git a/talpid-core/src/dns/macos.rs b/talpid-core/src/dns/macos.rs index e0535fedf7..78f4550688 100644 --- a/talpid-core/src/dns/macos.rs +++ b/talpid-core/src/dns/macos.rs @@ -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. @@ -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<()> { diff --git a/talpid-core/src/dns/mod.rs b/talpid-core/src/dns/mod.rs index 6f81829dab..d6fd333449 100644 --- a/talpid-core/src/dns/mod.rs +++ b/talpid-core/src/dns/mod.rs @@ -1,4 +1,6 @@ +use std::fmt; use std::net::IpAddr; + #[cfg(target_os = "linux")] use talpid_routing::RouteManagerHandle; @@ -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, @@ -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. @@ -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>; diff --git a/talpid-core/src/dns/windows/auto.rs b/talpid-core/src/dns/windows/auto.rs index c0b04742d4..3e17111956 100644 --- a/talpid-core/src/dns/windows/auto.rs +++ b/talpid-core/src/dns/windows/auto.rs @@ -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 { @@ -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(()) } @@ -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 } diff --git a/talpid-core/src/dns/windows/iphlpapi.rs b/talpid-core/src/dns/windows/iphlpapi.rs index 994b888d90..eeb4b3560c 100644 --- a/talpid-core/src/dns/windows/iphlpapi.rs +++ b/talpid-core/src/dns/windows/iphlpapi.rs @@ -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, @@ -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)?; diff --git a/talpid-core/src/dns/windows/mod.rs b/talpid-core/src/dns/windows/mod.rs index 889f8b4126..138f75c35f 100644 --- a/talpid-core/src/dns/windows/mod.rs +++ b/talpid-core/src/dns/windows/mod.rs @@ -1,6 +1,6 @@ -use std::{env, fmt, net::IpAddr}; +use std::{env, fmt}; -use super::DnsMonitorT; +use super::{DnsMonitorT, ResolvedDnsConfig}; mod auto; mod dnsapi; @@ -46,12 +46,12 @@ impl DnsMonitorT for DnsMonitor { Ok(DnsMonitor { inner }) } - fn set(&mut self, interface: &str, servers: &[IpAddr]) -> Result<(), Error> { + fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> 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)?, + DnsMonitorHolder::Auto(ref mut inner) => inner.set(interface, config)?, + DnsMonitorHolder::Iphlpapi(ref mut inner) => inner.set(interface, config)?, + DnsMonitorHolder::Netsh(ref mut inner) => inner.set(interface, config)?, + DnsMonitorHolder::Tcpip(ref mut inner) => inner.set(interface, config)?, } Ok(()) } diff --git a/talpid-core/src/dns/windows/netsh.rs b/talpid-core/src/dns/windows/netsh.rs index 25a11b826a..b731310056 100644 --- a/talpid-core/src/dns/windows/netsh.rs +++ b/talpid-core/src/dns/windows/netsh.rs @@ -1,4 +1,4 @@ -use crate::dns::DnsMonitorT; +use crate::dns::{DnsMonitorT, ResolvedDnsConfig}; use std::{ ffi::OsString, io::{self, Write}, @@ -69,7 +69,8 @@ impl DnsMonitorT for DnsMonitor { }) } - 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 interface_luid = luid_from_alias(interface).map_err(Error::ObtainInterfaceLuid)?; let interface_index = index_from_luid(&interface_luid).map_err(Error::ObtainInterfaceIndex)?; diff --git a/talpid-core/src/dns/windows/tcpip.rs b/talpid-core/src/dns/windows/tcpip.rs index a4c3ca8d76..70bb4660d6 100644 --- a/talpid-core/src/dns/windows/tcpip.rs +++ b/talpid-core/src/dns/windows/tcpip.rs @@ -1,4 +1,4 @@ -use crate::dns::DnsMonitorT; +use crate::dns::{DnsMonitorT, ResolvedDnsConfig}; use std::{io, net::IpAddr}; use talpid_types::ErrorExt; use talpid_windows::net::{guid_from_luid, luid_from_alias}; @@ -44,7 +44,9 @@ impl DnsMonitorT for DnsMonitor { }) } - 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)?; set_dns(&guid, servers)?; diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index 5f3489649a..71a13ad914 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -293,15 +293,10 @@ impl<'a> PolicyBatch<'a> { fn add_split_tunneling_rules(&mut self, policy: &FirewallPolicy, fwmark: u32) -> Result<()> { // Send select DNS requests in the tunnel if let FirewallPolicy::Connected { - tunnel, - dns_servers, - .. + tunnel, dns_config, .. } = policy { - for server in dns_servers - .iter() - .filter(|server| !is_local_dns_address(tunnel, server)) - { + for server in dns_config.tunnel_config() { let allow_rule = allow_tunnel_dns_rule( &self.mangle_chain, &tunnel.interface, @@ -559,11 +554,34 @@ impl<'a> PolicyBatch<'a> { peer_endpoint, tunnel, allow_lan, - dns_servers, + dns_config, } => { self.add_allow_tunnel_endpoint_rules(peer_endpoint, fwmark); - self.add_allow_dns_rules(tunnel, dns_servers, TransportProtocol::Udp)?; - self.add_allow_dns_rules(tunnel, dns_servers, TransportProtocol::Tcp)?; + + for server in dns_config.tunnel_config() { + self.add_allow_tunnel_dns_rule( + &tunnel.interface, + TransportProtocol::Udp, + *server, + )?; + self.add_allow_tunnel_dns_rule( + &tunnel.interface, + TransportProtocol::Tcp, + *server, + )?; + } + for server in dns_config.non_tunnel_config() { + self.add_allow_local_dns_rule( + &tunnel.interface, + TransportProtocol::Udp, + *server, + )?; + self.add_allow_local_dns_rule( + &tunnel.interface, + TransportProtocol::Tcp, + *server, + )?; + } // Important to block DNS *before* we allow the tunnel and allow LAN. So DNS // can't leak to the wrong IPs in the tunnel or on the LAN. @@ -685,27 +703,6 @@ impl<'a> PolicyBatch<'a> { self.batch.add(&out_rule, nftnl::MsgType::Add); } - fn add_allow_dns_rules( - &mut self, - tunnel: &tunnel::TunnelMetadata, - dns_servers: &[IpAddr], - protocol: TransportProtocol, - ) -> Result<()> { - let (local_resolvers, remote_resolvers): (Vec<IpAddr>, Vec<IpAddr>) = dns_servers - .iter() - .partition(|server| is_local_dns_address(tunnel, server)); - - for resolver in &local_resolvers { - self.add_allow_local_dns_rule(&tunnel.interface, protocol, *resolver)?; - } - - for resolver in &remote_resolvers { - self.add_allow_tunnel_dns_rule(&tunnel.interface, protocol, *resolver)?; - } - - Ok(()) - } - fn add_allow_tunnel_dns_rule( &mut self, interface: &str, @@ -895,12 +892,6 @@ impl<'a> PolicyBatch<'a> { } } -fn is_local_dns_address(tunnel: &tunnel::TunnelMetadata, server: &IpAddr) -> bool { - super::is_local_address(server) - && server != &tunnel.ipv4_gateway - && Some(server) != tunnel.ipv6_gateway.map(IpAddr::from).as_ref() -} - fn allow_tunnel_dns_rule<'a>( chain: &'a Chain<'_>, iface: &str, diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index 60cecf11de..78f72ab647 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -244,13 +244,20 @@ impl Firewall { peer_endpoint, tunnel, allow_lan, - dns_servers, + dns_config, redirect_interface, } => { let mut rules = vec![]; - for server in dns_servers.iter() { - rules.append(&mut self.get_allow_dns_rules_when_connected(tunnel, *server)?); + for server in dns_config.tunnel_config() { + rules.append( + &mut self.get_allow_tunnel_dns_rules_when_connected(tunnel, *server)?, + ); + } + for server in dns_config.non_tunnel_config() { + rules.append( + &mut self.get_allow_local_dns_rules_when_connected(tunnel, *server)?, + ); } rules.push(self.get_allow_relay_rule(peer_endpoint)?); @@ -299,86 +306,87 @@ impl Firewall { } } - fn get_allow_dns_rules_when_connected( + fn get_allow_local_dns_rules_when_connected( &self, tunnel: &crate::tunnel::TunnelMetadata, server: IpAddr, ) -> Result<Vec<pfctl::FilterRule>> { let mut rules = Vec::with_capacity(4); - let is_local = super::is_local_address(&server) - && server != tunnel.ipv4_gateway - && !tunnel - .ipv6_gateway - .map(|ref gateway| &server == gateway) - .unwrap_or(false); + // Block requests on the tunnel interface + let block_tunnel_tcp = self + .create_rule_builder(FilterRuleAction::Drop(DropAction::Return)) + .direction(pfctl::Direction::Out) + .quick(true) + .interface(&tunnel.interface) + .proto(pfctl::Proto::Tcp) + .keep_state(pfctl::StatePolicy::None) + .to(pfctl::Endpoint::new(server, 53)) + .build()?; + rules.push(block_tunnel_tcp); + let block_tunnel_udp = self + .create_rule_builder(FilterRuleAction::Drop(DropAction::Return)) + .direction(pfctl::Direction::Out) + .quick(true) + .interface(&tunnel.interface) + .proto(pfctl::Proto::Udp) + .keep_state(pfctl::StatePolicy::None) + .to(pfctl::Endpoint::new(server, 53)) + .build()?; + rules.push(block_tunnel_udp); - if is_local { - // Block requests on the tunnel interface - let block_tunnel_tcp = self - .create_rule_builder(FilterRuleAction::Drop(DropAction::Return)) - .direction(pfctl::Direction::Out) - .quick(true) - .interface(&tunnel.interface) - .proto(pfctl::Proto::Tcp) - .keep_state(pfctl::StatePolicy::None) - .to(pfctl::Endpoint::new(server, 53)) - .build()?; - rules.push(block_tunnel_tcp); - let block_tunnel_udp = self - .create_rule_builder(FilterRuleAction::Drop(DropAction::Return)) - .direction(pfctl::Direction::Out) - .quick(true) - .interface(&tunnel.interface) - .proto(pfctl::Proto::Udp) - .keep_state(pfctl::StatePolicy::None) - .to(pfctl::Endpoint::new(server, 53)) - .build()?; - rules.push(block_tunnel_udp); + // Allow requests on other interfaces + let allow_nontunnel_tcp = self + .create_rule_builder(FilterRuleAction::Pass) + .direction(pfctl::Direction::Out) + .quick(true) + .proto(pfctl::Proto::Tcp) + .keep_state(pfctl::StatePolicy::Keep) + .tcp_flags(Self::get_tcp_flags()) + .to(pfctl::Endpoint::new(server, 53)) + .build()?; + rules.push(allow_nontunnel_tcp); + let allow_nontunnel_udp = self + .create_rule_builder(FilterRuleAction::Pass) + .direction(pfctl::Direction::Out) + .quick(true) + .proto(pfctl::Proto::Udp) + .keep_state(pfctl::StatePolicy::Keep) + .to(pfctl::Endpoint::new(server, 53)) + .build()?; + rules.push(allow_nontunnel_udp); - // Allow requests on other interfaces - let allow_nontunnel_tcp = self - .create_rule_builder(FilterRuleAction::Pass) - .direction(pfctl::Direction::Out) - .quick(true) - .proto(pfctl::Proto::Tcp) - .keep_state(pfctl::StatePolicy::Keep) - .tcp_flags(Self::get_tcp_flags()) - .to(pfctl::Endpoint::new(server, 53)) - .build()?; - rules.push(allow_nontunnel_tcp); - let allow_nontunnel_udp = self - .create_rule_builder(FilterRuleAction::Pass) - .direction(pfctl::Direction::Out) - .quick(true) - .proto(pfctl::Proto::Udp) - .keep_state(pfctl::StatePolicy::Keep) - .to(pfctl::Endpoint::new(server, 53)) - .build()?; - rules.push(allow_nontunnel_udp); - } else { - // Allow outgoing requests on the tunnel interface only - let allow_tunnel_tcp = self - .create_rule_builder(FilterRuleAction::Pass) - .direction(pfctl::Direction::Out) - .quick(true) - .interface(&tunnel.interface) - .proto(pfctl::Proto::Tcp) - .keep_state(pfctl::StatePolicy::Keep) - .tcp_flags(Self::get_tcp_flags()) - .to(pfctl::Endpoint::new(server, 53)) - .build()?; - rules.push(allow_tunnel_tcp); - let allow_tunnel_udp = self - .create_rule_builder(FilterRuleAction::Pass) - .direction(pfctl::Direction::Out) - .quick(true) - .interface(&tunnel.interface) - .proto(pfctl::Proto::Udp) - .to(pfctl::Endpoint::new(server, 53)) - .build()?; - rules.push(allow_tunnel_udp); - }; + Ok(rules) + } + + fn get_allow_tunnel_dns_rules_when_connected( + &self, + tunnel: &crate::tunnel::TunnelMetadata, + server: IpAddr, + ) -> Result<Vec<pfctl::FilterRule>> { + let mut rules = Vec::with_capacity(4); + + // Allow outgoing requests on the tunnel interface only + let allow_tunnel_tcp = self + .create_rule_builder(FilterRuleAction::Pass) + .direction(pfctl::Direction::Out) + .quick(true) + .interface(&tunnel.interface) + .proto(pfctl::Proto::Tcp) + .keep_state(pfctl::StatePolicy::Keep) + .tcp_flags(Self::get_tcp_flags()) + .to(pfctl::Endpoint::new(server, 53)) + .build()?; + rules.push(allow_tunnel_tcp); + let allow_tunnel_udp = self + .create_rule_builder(FilterRuleAction::Pass) + .direction(pfctl::Direction::Out) + .quick(true) + .interface(&tunnel.interface) + .proto(pfctl::Proto::Udp) + .to(pfctl::Endpoint::new(server, 53)) + .build()?; + rules.push(allow_tunnel_udp); Ok(rules) } diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index c9c351bb59..8f36e905ff 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -1,3 +1,5 @@ +#[cfg(not(target_os = "android"))] +use crate::dns::ResolvedDnsConfig; use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; use std::{ fmt, @@ -104,7 +106,7 @@ pub enum FirewallPolicy { allow_lan: bool, /// Servers that are allowed to respond to DNS requests. #[cfg(not(target_os = "android"))] - dns_servers: Vec<IpAddr>, + dns_config: ResolvedDnsConfig, /// Interface to redirect (VPN tunnel) traffic to #[cfg(target_os = "macos")] redirect_interface: Option<String>, diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs index eda06ce5fd..0ee5a81ca2 100644 --- a/talpid-core/src/firewall/windows.rs +++ b/talpid-core/src/firewall/windows.rs @@ -1,4 +1,4 @@ -use crate::tunnel::TunnelMetadata; +use crate::{dns::ResolvedDnsConfig, tunnel::TunnelMetadata}; use std::{ffi::CStr, io, net::IpAddr, ptr}; @@ -113,10 +113,10 @@ impl Firewall { peer_endpoint, tunnel, allow_lan, - dns_servers, + dns_config, } => { let cfg = &WinFwSettings::new(allow_lan); - self.set_connected_state(&peer_endpoint, cfg, &tunnel, &dns_servers) + self.set_connected_state(&peer_endpoint, cfg, &tunnel, &dns_config) } FirewallPolicy::Blocked { allow_lan, @@ -250,14 +250,10 @@ impl Firewall { endpoint: &AllowedEndpoint, winfw_settings: &WinFwSettings, tunnel_metadata: &TunnelMetadata, - dns_servers: &[IpAddr], + dns_config: &ResolvedDnsConfig, ) -> Result<(), Error> { log::trace!("Applying 'connected' firewall policy"); let ip_str = widestring_ip(endpoint.endpoint.address.ip()); - let v4_gateway = widestring_ip(tunnel_metadata.ipv4_gateway.into()); - let v6_gateway = tunnel_metadata - .ipv6_gateway - .map(|v6_ip| widestring_ip(v6_ip.into())); let tunnel_alias = WideCString::from_str_truncate(&tunnel_metadata.interface); @@ -268,11 +264,6 @@ impl Firewall { protocol: WinFwProt::from(endpoint.endpoint.protocol), }; - let v6_gateway_ptr = match &v6_gateway { - Some(v6_ip) => v6_ip.as_ptr(), - None => ptr::null(), - }; - // SAFETY: `relay_client_wstrs` must not be dropped until `WinFw_ApplyPolicyConnected` has // returned. let relay_client_wstrs: Vec<_> = endpoint @@ -286,9 +277,24 @@ impl Firewall { .collect(); let relay_client_wstr_ptrs_len = relay_client_wstr_ptrs.len(); - let dns_servers: Vec<WideCString> = - dns_servers.iter().cloned().map(widestring_ip).collect(); - let dns_servers: Vec<*const u16> = dns_servers.iter().map(|ip| ip.as_ptr()).collect(); + let tunnel_dns_servers: Vec<WideCString> = dns_config + .tunnel_config() + .iter() + .cloned() + .map(widestring_ip) + .collect(); + let tunnel_dns_servers: Vec<*const u16> = + tunnel_dns_servers.iter().map(|ip| ip.as_ptr()).collect(); + let non_tunnel_dns_servers: Vec<WideCString> = dns_config + .non_tunnel_config() + .iter() + .cloned() + .map(widestring_ip) + .collect(); + let non_tunnel_dns_servers: Vec<*const u16> = non_tunnel_dns_servers + .iter() + .map(|ip| ip.as_ptr()) + .collect(); let result = unsafe { WinFw_ApplyPolicyConnected( @@ -297,10 +303,10 @@ impl Firewall { relay_client_wstr_ptrs.as_ptr(), relay_client_wstr_ptrs_len, tunnel_alias.as_ptr(), - v4_gateway.as_ptr(), - v6_gateway_ptr, - dns_servers.as_ptr(), - dns_servers.len(), + tunnel_dns_servers.as_ptr(), + tunnel_dns_servers.len(), + non_tunnel_dns_servers.as_ptr(), + non_tunnel_dns_servers.len(), ) .into_result() .map_err(Error::ApplyingConnectedPolicy) @@ -635,10 +641,10 @@ mod winfw { relayClient: *const *const libc::wchar_t, relayClientLen: usize, tunnelIfaceAlias: *const libc::wchar_t, - v4Gateway: *const libc::wchar_t, - v6Gateway: *const libc::wchar_t, - dnsServers: *const *const libc::wchar_t, - numDnsServers: usize, + tunnelDnsServers: *const *const libc::wchar_t, + numTunnelDnsServers: usize, + nonTunnelDnsServers: *const *const libc::wchar_t, + numNonTunnelDnsServers: usize, ) -> WinFwPolicyStatus; #[link_name = "WinFw_ApplyPolicyBlocked"] diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index c7f480ed6d..a5ca3395a4 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -4,6 +4,7 @@ use super::{ TunnelStateTransition, }; use crate::{ + dns::ResolvedDnsConfig, firewall::FirewallPolicy, tunnel::{TunnelEvent, TunnelMetadata}, }; @@ -12,7 +13,6 @@ use futures::{ stream::Fuse, StreamExt, }; -use std::net::IpAddr; use talpid_types::{ net::{AllowedClients, AllowedEndpoint, TunnelParameters}, tunnel::{ErrorStateCause, FirewallPolicyError}, @@ -106,13 +106,6 @@ impl ConnectedState { }) } - fn get_dns_servers(&self, shared_values: &SharedTunnelStateValues) -> Vec<IpAddr> { - shared_values - .dns_servers - .clone() - .unwrap_or_else(|| self.metadata.gateways()) - } - fn get_firewall_policy(&self, shared_values: &SharedTunnelStateValues) -> FirewallPolicy { let endpoint = self.tunnel_parameters.get_next_hop_endpoint(); @@ -146,28 +139,25 @@ impl ConnectedState { tunnel: self.metadata.clone(), allow_lan: shared_values.allow_lan, #[cfg(not(target_os = "android"))] - dns_servers: self.get_dns_servers(shared_values), + dns_config: Self::resolve_dns(&self.metadata, shared_values), #[cfg(target_os = "macos")] redirect_interface, } } - fn set_dns(&self, shared_values: &mut SharedTunnelStateValues) -> Result<(), BoxedError> { - let dns_ips = self.get_dns_servers(shared_values); + fn resolve_dns( + metadata: &TunnelMetadata, + shared_values: &SharedTunnelStateValues, + ) -> ResolvedDnsConfig { + shared_values.dns_config.resolve(&metadata.gateways()) + } - #[cfg(any(target_os = "linux", target_os = "windows"))] - let dns_ips = dns_ips - .into_iter() - .filter(|ip| { - !crate::firewall::is_local_address(ip) - || IpAddr::V4(self.metadata.ipv4_gateway) == *ip - || self.metadata.ipv6_gateway.map(IpAddr::V6) == Some(*ip) - }) - .collect::<Vec<_>>(); + fn set_dns(&self, shared_values: &mut SharedTunnelStateValues) -> Result<(), BoxedError> { + let dns_config = Self::resolve_dns(&self.metadata, shared_values); shared_values .dns_monitor - .set(&self.metadata.interface, &dns_ips) + .set(&self.metadata.interface, dns_config) .map_err(BoxedError::new)?; Ok(()) @@ -259,7 +249,7 @@ impl ConnectedState { SameState(self) } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let consequence = if shared_values.set_dns_servers(servers) { + let consequence = if shared_values.set_dns_config(servers) { #[cfg(target_os = "android")] { if let Err(_err) = shared_values.restart_tunnel(false) { diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index d9a58a0ea7..09ba4f18e1 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -439,7 +439,7 @@ impl ConnectingState { SameState(self) } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let consequence = if shared_values.set_dns_servers(servers) { + let consequence = if shared_values.set_dns_config(servers) { #[cfg(target_os = "android")] { if let Err(_err) = shared_values.restart_tunnel(false) { diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 74cd3b65c4..9da6520f61 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -131,9 +131,10 @@ impl DisconnectedState { fn setup_local_dns_config( shared_values: &mut SharedTunnelStateValues, ) -> Result<(), dns::Error> { - shared_values - .dns_monitor - .set("lo", &[Ipv4Addr::LOCALHOST.into()]) + shared_values.dns_monitor.set( + "lo", + dns::DnsConfig::default().resolve(&[Ipv4Addr::LOCALHOST.into()]), + ) } } @@ -164,7 +165,7 @@ impl TunnelState for DisconnectedState { } Some(TunnelCommand::Dns(servers, complete_tx)) => { // Same situation as allow LAN above. - shared_values.set_dns_servers(servers); + shared_values.set_dns_config(servers); let _ = complete_tx.send(()); SameState(self) } diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index ddcb3cebd2..4a108788e1 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -51,7 +51,7 @@ impl DisconnectingState { AfterDisconnect::Nothing } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let _ = shared_values.set_dns_servers(servers); + let _ = shared_values.set_dns_config(servers); let _ = complete_tx.send(()); AfterDisconnect::Nothing } @@ -105,7 +105,7 @@ impl DisconnectingState { AfterDisconnect::Block(reason) } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let _ = shared_values.set_dns_servers(servers); + let _ = shared_values.set_dns_config(servers); let _ = complete_tx.send(()); AfterDisconnect::Block(reason) } @@ -163,7 +163,7 @@ impl DisconnectingState { AfterDisconnect::Reconnect(retry_attempt) } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let _ = shared_values.set_dns_servers(servers); + let _ = shared_values.set_dns_config(servers); let _ = complete_tx.send(()); AfterDisconnect::Reconnect(retry_attempt) } diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index 99f8dc17c4..eeaf48956b 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -2,6 +2,8 @@ use super::{ ConnectingState, DisconnectedState, EventConsequence, SharedTunnelStateValues, TunnelCommand, TunnelCommandReceiver, TunnelState, TunnelStateTransition, }; +#[cfg(target_os = "macos")] +use crate::dns::DnsConfig; use crate::firewall::FirewallPolicy; use futures::StreamExt; #[cfg(target_os = "macos")] @@ -33,10 +35,10 @@ impl ErrorState { #[cfg(target_os = "macos")] if !block_reason.prevents_filtering_resolver() { - if let Err(err) = shared_values - .dns_monitor - .set("lo", &[Ipv4Addr::LOCALHOST.into()]) - { + if let Err(err) = shared_values.dns_monitor.set( + "lo", + DnsConfig::default().resolve(&[Ipv4Addr::LOCALHOST.into()]), + ) { log::error!( "{}", err.display_chain_with_msg( @@ -158,7 +160,7 @@ impl TunnelState for ErrorState { SameState(self) } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let consequence = if shared_values.set_dns_servers(servers) { + let consequence = if shared_values.set_dns_config(servers) { #[cfg(target_os = "android")] { // DNS is blocked in the error state, so only update tun config diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 1deb226e04..e0ef07850d 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -14,7 +14,7 @@ use self::{ #[cfg(any(windows, target_os = "android", target_os = "macos"))] use crate::split_tunnel; use crate::{ - dns::DnsMonitor, + dns::{DnsConfig, DnsMonitor}, firewall::{Firewall, FirewallArguments, InitialFirewallState}, mpsc::Sender, offline, @@ -37,7 +37,6 @@ use std::os::unix::io::RawFd; use std::{ future::Future, io, - net::IpAddr, path::PathBuf, pin::Pin, sync::{Arc, Mutex}, @@ -92,8 +91,8 @@ pub struct InitialTunnelState { pub allow_lan: bool, /// Block traffic unless connected to the VPN. pub block_when_disconnected: bool, - /// DNS servers to use. If `None`, the tunnel gateway is used. - pub dns_servers: Option<Vec<IpAddr>>, + /// DNS configuration to use + pub dns_config: DnsConfig, /// A single endpoint that is allowed to communicate outside the tunnel, i.e. /// in any of the blocking states. pub allowed_endpoint: AllowedEndpoint, @@ -188,8 +187,8 @@ pub enum TunnelCommand { /// channel after attempting to set the firewall policy, regardless /// of whether it succeeded. AllowEndpoint(AllowedEndpoint, oneshot::Sender<()>), - /// Set DNS servers to use. - Dns(Option<Vec<IpAddr>>, oneshot::Sender<()>), + /// Set DNS configuration to use. + Dns(crate::dns::DnsConfig, oneshot::Sender<()>), /// Enable or disable the block_when_disconnected feature. BlockWhenDisconnected(bool, oneshot::Sender<()>), /// Notify the state machine of the connectivity of the device. @@ -374,7 +373,7 @@ impl TunnelStateMachine { allow_lan: args.settings.allow_lan, block_when_disconnected: args.settings.block_when_disconnected, connectivity, - dns_servers: args.settings.dns_servers, + dns_config: args.settings.dns_config, allowed_endpoint: args.settings.allowed_endpoint, tunnel_parameters_generator: Box::new(args.tunnel_parameters_generator), tun_provider: Arc::new(Mutex::new(args.tun_provider)), @@ -467,8 +466,8 @@ struct SharedTunnelStateValues { block_when_disconnected: bool, /// True when the computer is known to be offline. connectivity: Connectivity, - /// DNS servers to use (overriding default). - dns_servers: Option<Vec<IpAddr>>, + /// DNS configuration to use. + dns_config: crate::dns::DnsConfig, /// Endpoint that should not be blocked by the firewall. allowed_endpoint: AllowedEndpoint, /// The generator of new `TunnelParameter`s @@ -514,6 +513,8 @@ impl SharedTunnelStateValues { &mut self, metadata: &TunnelMetadata, ) -> Result<(), ErrorStateCause> { + use std::net::IpAddr; + let v4_address = metadata .ips .iter() @@ -555,9 +556,9 @@ impl SharedTunnelStateValues { } } - pub fn set_dns_servers(&mut self, dns_servers: Option<Vec<IpAddr>>) -> bool { - if self.dns_servers != dns_servers { - self.dns_servers = dns_servers; + pub fn set_dns_config(&mut self, dns_config: DnsConfig) -> bool { + if self.dns_config != dns_config { + self.dns_config = dns_config; true } else { false @@ -627,7 +628,8 @@ impl SharedTunnelStateValues { if blocking { config.dns_servers = Some(vec![]); } else { - config.dns_servers = self.dns_servers.clone(); + let addrs: Vec<_> = self.dns_config.resolve(&[]).addresses().collect(); + config.dns_servers = if addrs.is_empty() { None } else { Some(addrs) }; } config.allow_lan = self.allow_lan; config.excluded_packages = self.excluded_packages.clone(); |
