diff --git a/nym-vpn-core/CHANGELOG.md b/nym-vpn-core/CHANGELOG.md index 0a56d9dafa..5818309281 100644 --- a/nym-vpn-core/CHANGELOG.md +++ b/nym-vpn-core/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Change default entry and exit points to random (https://github.com/nymtech/nym-vpn-client/pull/5378) +- Enable secure DNS for requests forwarded by local resolver (https://github.com/nymtech/nym-vpn-client/pull/5458) ### Fixed diff --git a/nym-vpn-core/Cargo.lock b/nym-vpn-core/Cargo.lock index 7a18b716b8..f29d6669af 100644 --- a/nym-vpn-core/Cargo.lock +++ b/nym-vpn-core/Cargo.lock @@ -3111,6 +3111,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "h3" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" +dependencies = [ + "bytes", + "fastrand", + "futures-util", + "http 1.4.0", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "h3-quinn" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" +dependencies = [ + "bytes", + "futures", + "h3", + "quinn", + "tokio", + "tokio-util", +] + [[package]] name = "handlebars" version = "3.5.5" @@ -3278,11 +3306,15 @@ dependencies = [ "futures-io", "futures-util", "h2 0.4.13", + "h3", + "h3-quinn", "hickory-proto 0.26.1", "http 1.4.0", "idna", "ipnet", "jni 0.22.4", + "pin-project-lite", + "quinn", "rand 0.10.1", "rustls 0.23.40", "thiserror 2.0.18", @@ -3291,6 +3323,7 @@ dependencies = [ "tokio-rustls 0.26.4", "tracing", "url", + "webpki-roots 1.0.6", ] [[package]] @@ -3386,6 +3419,7 @@ dependencies = [ "ndk-context", "once_cell", "parking_lot", + "quinn", "rand 0.10.1", "resolv-conf", "rustls 0.23.40", @@ -3396,6 +3430,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tracing", + "webpki-roots 1.0.6", ] [[package]] @@ -5762,7 +5797,6 @@ dependencies = [ "nix 0.31.3", "nym-cgroup", "nym-common 2026.10.0-beta", - "nym-dns", "nym-firewall-config", "nym-platform-metadata", "pfctl", @@ -8645,6 +8679,7 @@ checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", + "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", diff --git a/nym-vpn-core/README.md b/nym-vpn-core/README.md index 951c5dc48c..ca909fdd06 100644 --- a/nym-vpn-core/README.md +++ b/nym-vpn-core/README.md @@ -303,9 +303,6 @@ make -C nym-vpn-core -f Android.mk - `netsh`: use the `netsh` program - `tcpip`: set TCP/IP parameters in the registry -- `NYM_DISABLE_LOCAL_DNS_RESOLVER` - Set this variable to `1` to disable the local DNS resolver - (macOS only). - - `NYM_DISABLE_OFFLINE_MONITOR` - Set to `1` to forces the daemon to always assume the host is online. - `NYM_USE_PATH_MONITOR` - Set to `1` to use Apple Network framework for offline monitoring. (macOS only) diff --git a/nym-vpn-core/crates/nym-dns/src/android.rs b/nym-vpn-core/crates/nym-dns/src/android.rs index 97bdf0f559..b4c1c8dc49 100644 --- a/nym-vpn-core/crates/nym-dns/src/android.rs +++ b/nym-vpn-core/crates/nym-dns/src/android.rs @@ -2,7 +2,7 @@ // Copyright 2024 Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::ResolvedDnsConfig; +use crate::DnsConfig; /// Stub error type for DNS errors on Android. #[derive(Debug, thiserror::Error)] @@ -18,11 +18,7 @@ impl super::DnsMonitorT for DnsMonitor { Ok(DnsMonitor) } - async fn set( - &mut self, - _interface: &str, - _servers: ResolvedDnsConfig, - ) -> Result<(), Self::Error> { + async fn set(&mut self, _interface: &str, _servers: DnsConfig) -> Result<(), Self::Error> { Ok(()) } diff --git a/nym-vpn-core/crates/nym-dns/src/lib.rs b/nym-vpn-core/crates/nym-dns/src/lib.rs index 1cbc44c804..ccf985343b 100644 --- a/nym-vpn-core/crates/nym-dns/src/lib.rs +++ b/nym-vpn-core/crates/nym-dns/src/lib.rs @@ -2,7 +2,7 @@ // Copyright 2024 Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use std::{fmt, net::IpAddr}; +use std::net::IpAddr; #[cfg(target_os = "linux")] use nym_routing::RouteManagerHandle; @@ -31,142 +31,13 @@ pub use self::imp::Error; pub use imp::flush_resolver_cache; /// DNS configuration -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] 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(), - }, - } - } -} + /// DNS server addresses + pub addresses: Vec, -impl DnsConfig { - pub fn resolve( - &self, - default_tun_config: &[IpAddr], - #[cfg(not(any(target_os = "android", target_os = "ios")))] port: u16, - ) -> ResolvedDnsConfig { - match &self.config { - InnerDnsConfig::Default => ResolvedDnsConfig { - tunnel_config: default_tun_config.to_owned(), - non_tunnel_config: vec![], - #[cfg(not(any(target_os = "android", target_os = "ios")))] - port, - }, - InnerDnsConfig::Override { - tunnel_config, - non_tunnel_config, - } => ResolvedDnsConfig { - tunnel_config: tunnel_config.to_owned(), - non_tunnel_config: non_tunnel_config.to_owned(), - #[cfg(not(any(target_os = "android", target_os = "ios")))] - port, - }, - } - } -} - -#[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, - /// 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, - }, -} - -/// DNS configuration with `DnsConfig::Default` resolved -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ResolvedDnsConfig { - /// Addresses to configure on the tunnel interface - tunnel_config: Vec, - /// 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, /// Port to use - #[cfg(not(any(target_os = "android", target_os = "ios")))] - port: u16, -} - -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)?; - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - write!(f, " Port: {}", self.port)?; - - Ok(()) - } -} - -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 { - self.non_tunnel_config.into_iter().chain(self.tunnel_config) - } - - /// Return whether the config contains only (and at least one) loopback addresses, and zero - /// non-loopback addresses - pub fn is_loopback(&self) -> bool { - let (loopback_addrs, non_loopback_addrs) = self - .tunnel_config - .iter() - .chain(self.non_tunnel_config.iter()) - .copied() - .partition::, _>(|ip| ip.is_loopback()); - - !loopback_addrs.is_empty() && non_loopback_addrs.is_empty() - } + pub port: u16, } /// Sets and monitors system DNS settings. Makes sure the desired DNS servers are being used. @@ -188,8 +59,8 @@ impl DnsMonitor { } /// Set DNS to the given servers. And start monitoring the system for changes. - pub async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Error> { - tracing::info!("Setting DNS servers on interface '{interface}': {config}"); + pub async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<(), Error> { + tracing::info!("Setting DNS servers on interface '{interface}': {config:?}"); self.inner.set(interface, config).await } @@ -216,8 +87,7 @@ trait DnsMonitorT: Sized { #[cfg(target_os = "linux")] route_manager: RouteManagerHandle, ) -> Result; - async fn set(&mut self, interface: &str, servers: ResolvedDnsConfig) - -> Result<(), Self::Error>; + async fn set(&mut self, interface: &str, servers: DnsConfig) -> Result<(), Self::Error>; async fn reset(&mut self) -> Result<(), Self::Error>; diff --git a/nym-vpn-core/crates/nym-dns/src/linux/mod.rs b/nym-vpn-core/crates/nym-dns/src/linux/mod.rs index 446a65b7fb..e6f3b80c56 100644 --- a/nym-vpn-core/crates/nym-dns/src/linux/mod.rs +++ b/nym-vpn-core/crates/nym-dns/src/linux/mod.rs @@ -15,7 +15,7 @@ use self::{ network_manager::NetworkManager, resolvconf::Resolvconf, static_resolv_conf::StaticResolvConf, systemd_resolved::SystemdResolved, }; -use super::ResolvedDnsConfig; +use super::DnsConfig; pub type Result = std::result::Result; @@ -58,8 +58,8 @@ impl super::DnsMonitorT for DnsMonitor { }) } - async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<()> { - let servers = config.tunnel_config(); + async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<()> { + let servers = &config.addresses; self.reset().await?; // 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/nym-vpn-core/crates/nym-dns/src/macos.rs b/nym-vpn-core/crates/nym-dns/src/macos.rs index d961f35e56..0eb4ce3221 100644 --- a/nym-vpn-core/crates/nym-dns/src/macos.rs +++ b/nym-vpn-core/crates/nym-dns/src/macos.rs @@ -30,7 +30,7 @@ use tokio::sync::Mutex; use nym_routing::debounce::BurstGuard; -use super::ResolvedDnsConfig; +use super::DnsConfig; pub type Result = std::result::Result; @@ -402,9 +402,9 @@ impl super::DnsMonitorT for DnsMonitor { }) } - async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<()> { + async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<()> { let port = config.port; - let servers: Vec<_> = config.addresses().collect(); + let servers: Vec<_> = config.addresses; let mut state = self.state.lock().await; state.apply_new_config(&self.store, interface, &servers, port) diff --git a/nym-vpn-core/crates/nym-dns/src/windows/auto.rs b/nym-vpn-core/crates/nym-dns/src/windows/auto.rs index cefd0eb9d4..9801cb19e0 100644 --- a/nym-vpn-core/crates/nym-dns/src/windows/auto.rs +++ b/nym-vpn-core/crates/nym-dns/src/windows/auto.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-only use super::{iphlpapi, netsh, tcpip}; -use crate::{DnsMonitorT, ResolvedDnsConfig}; +use crate::{DnsConfig, DnsMonitorT}; use windows::Win32::System::Rpc::RPC_S_SERVER_UNAVAILABLE; pub struct DnsMonitor { @@ -16,11 +16,7 @@ enum InnerMonitor { } impl InnerMonitor { - async fn set( - &mut self, - interface: &str, - config: ResolvedDnsConfig, - ) -> Result<(), super::Error> { + async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<(), super::Error> { match self { InnerMonitor::Iphlpapi(monitor) => monitor.set(interface, config).await?, InnerMonitor::Netsh(monitor) => monitor.set(interface, config).await?, @@ -61,7 +57,7 @@ impl DnsMonitorT for DnsMonitor { Ok(Self { current_monitor }) } - async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Self::Error> { + async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<(), Self::Error> { let result = self.current_monitor.set(interface, config.clone()).await; if self.fallback_due_to_dnscache(&result) { return Box::pin(self.set(interface, config)).await; diff --git a/nym-vpn-core/crates/nym-dns/src/windows/iphlpapi.rs b/nym-vpn-core/crates/nym-dns/src/windows/iphlpapi.rs index 38270f8792..e8ca0a05d0 100644 --- a/nym-vpn-core/crates/nym-dns/src/windows/iphlpapi.rs +++ b/nym-vpn-core/crates/nym-dns/src/windows/iphlpapi.rs @@ -7,7 +7,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::{DnsMonitorT, ResolvedDnsConfig}; +use crate::{DnsConfig, DnsMonitorT}; use nym_windows::net::{guid_from_luid, luid_from_alias}; use once_cell::sync::OnceCell; use std::{ @@ -126,8 +126,8 @@ impl DnsMonitorT for DnsMonitor { Ok(DnsMonitor { current_guid: None }) } - async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Error> { - let servers = config.tunnel_config(); + async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<(), Error> { + let servers = &config.addresses; let guid = guid_from_luid(&luid_from_alias(interface).map_err(Error::ObtainInterfaceLuid)?) .map_err(Error::ObtainInterfaceGuid)?; diff --git a/nym-vpn-core/crates/nym-dns/src/windows/mod.rs b/nym-vpn-core/crates/nym-dns/src/windows/mod.rs index d3ac4d9a6c..6b27e05076 100644 --- a/nym-vpn-core/crates/nym-dns/src/windows/mod.rs +++ b/nym-vpn-core/crates/nym-dns/src/windows/mod.rs @@ -4,7 +4,7 @@ use std::{env, fmt}; -use super::{DnsMonitorT, ResolvedDnsConfig}; +use super::{DnsConfig, DnsMonitorT}; mod auto; mod dnsapi; @@ -52,12 +52,12 @@ impl DnsMonitorT for DnsMonitor { Ok(DnsMonitor { inner }) } - async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Error> { + async fn set(&mut self, interface: &str, servers: DnsConfig) -> Result<(), Self::Error> { match self.inner { - DnsMonitorHolder::Auto(ref mut inner) => inner.set(interface, config).await?, - DnsMonitorHolder::Iphlpapi(ref mut inner) => inner.set(interface, config).await?, - DnsMonitorHolder::Netsh(ref mut inner) => inner.set(interface, config).await?, - DnsMonitorHolder::Tcpip(ref mut inner) => inner.set(interface, config).await?, + DnsMonitorHolder::Auto(ref mut inner) => inner.set(interface, servers).await?, + DnsMonitorHolder::Iphlpapi(ref mut inner) => inner.set(interface, servers).await?, + DnsMonitorHolder::Netsh(ref mut inner) => inner.set(interface, servers).await?, + DnsMonitorHolder::Tcpip(ref mut inner) => inner.set(interface, servers).await?, } Ok(()) } diff --git a/nym-vpn-core/crates/nym-dns/src/windows/netsh.rs b/nym-vpn-core/crates/nym-dns/src/windows/netsh.rs index 9f6d093b5a..6fd5c0eb3f 100644 --- a/nym-vpn-core/crates/nym-dns/src/windows/netsh.rs +++ b/nym-vpn-core/crates/nym-dns/src/windows/netsh.rs @@ -21,7 +21,7 @@ use windows::Win32::{Foundation::MAX_PATH, System::SystemInformation::GetSystemD use nym_common::trace_err_chain; use nym_windows::net::{index_from_luid, luid_from_alias}; -use crate::{DnsMonitorT, ResolvedDnsConfig}; +use crate::{DnsConfig, DnsMonitorT}; const NETSH_TIMEOUT: Duration = Duration::from_secs(10); @@ -74,8 +74,8 @@ impl DnsMonitorT for DnsMonitor { }) } - async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Error> { - let servers = config.tunnel_config(); + async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<(), Error> { + let servers = &config.addresses; 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/nym-vpn-core/crates/nym-dns/src/windows/tcpip.rs b/nym-vpn-core/crates/nym-dns/src/windows/tcpip.rs index a00f6bd45b..3590aa88c5 100644 --- a/nym-vpn-core/crates/nym-dns/src/windows/tcpip.rs +++ b/nym-vpn-core/crates/nym-dns/src/windows/tcpip.rs @@ -2,7 +2,7 @@ // Copyright 2024 Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::{DnsMonitorT, ResolvedDnsConfig}; +use crate::{DnsConfig, DnsMonitorT}; use nym_common::ErrorExt; use nym_windows::net::{guid_from_luid, luid_from_alias}; use std::{io, net::IpAddr}; @@ -48,8 +48,8 @@ impl DnsMonitorT for DnsMonitor { }) } - async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<(), Error> { - let servers = config.tunnel_config(); + async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<(), Error> { + let servers = &config.addresses; let guid = guid_from_luid(&luid_from_alias(interface).map_err(Error::ObtainInterfaceLuid)?) .map_err(Error::ObtainInterfaceGuid)?; diff --git a/nym-vpn-core/crates/nym-firewall/Cargo.toml b/nym-vpn-core/crates/nym-firewall/Cargo.toml index 518f15295c..8b7b814949 100644 --- a/nym-vpn-core/crates/nym-firewall/Cargo.toml +++ b/nym-vpn-core/crates/nym-firewall/Cargo.toml @@ -20,7 +20,6 @@ libc.workspace = true thiserror.workspace = true tracing.workspace = true -nym-dns.workspace = true nym-common.workspace = true nym-firewall-config.workspace = true diff --git a/nym-vpn-core/crates/nym-firewall/src/lib.rs b/nym-vpn-core/crates/nym-firewall/src/lib.rs index 746e279103..a9b926b4a3 100644 --- a/nym-vpn-core/crates/nym-firewall/src/lib.rs +++ b/nym-vpn-core/crates/nym-firewall/src/lib.rs @@ -7,8 +7,6 @@ use std::{borrow::Cow, fmt, net::IpAddr}; #[cfg(any(target_os = "linux", target_os = "macos"))] use ipnetwork::Ipv6Network; -#[cfg(not(target_os = "android"))] -use nym_dns::ResolvedDnsConfig; #[cfg(target_os = "linux")] use nym_cgroup::v2::CGroup2; @@ -34,6 +32,8 @@ mod imp; mod imp; mod net; +#[cfg(not(target_os = "android"))] +pub use net::AllowedDns; pub use net::{ AllowedClients, AllowedEndpoint, AllowedTunnelTraffic, Endpoint, TransportProtocol, TunnelInterface, TunnelMetadata, @@ -71,16 +71,7 @@ const DHCPV6_CLIENT_PORT: u16 = 546; #[cfg(all(unix, not(any(target_os = "android", target_os = "ios"))))] const ROOT_UID: u32 = 0; -/// Allowed TCP ports to DNS servers when connecting. -#[cfg(not(any(target_os = "android", target_os = "ios")))] -const DNS_TCP_PORTS: [u16; 2] = [443, 853]; - /// A enum that describes network security strategy -/// -/// # Firewall block/allow specification. -/// -/// See the [security](../../../docs/security.md) document for the specification on how to -/// implement these policies and what should and should not be allowed to flow. #[derive(Debug, Clone, Eq, PartialEq)] pub enum FirewallPolicy { /// Allow traffic only to server @@ -93,7 +84,7 @@ pub enum FirewallPolicy { allow_lan: bool, /// Servers that are allowed to respond to DNS requests. #[cfg(not(target_os = "android"))] - dns_config: ResolvedDnsConfig, + dns_config: AllowedDns, /// Hosts that should be reachable while connecting. allowed_endpoints: Vec, /// Networks for which to permit entry in-tunnel traffic. @@ -116,7 +107,7 @@ pub enum FirewallPolicy { allow_lan: bool, /// Servers that are allowed to respond to DNS requests. #[cfg(not(target_os = "android"))] - dns_config: ResolvedDnsConfig, + dns_config: AllowedDns, /// Interface to redirect (VPN tunnel) traffic to #[cfg(target_os = "macos")] redirect_interface: Option, @@ -214,7 +205,7 @@ impl FirewallPolicy { } #[cfg(not(target_os = "android"))] - pub fn dns_config(&self) -> Option<&ResolvedDnsConfig> { + pub fn dns_config(&self) -> Option<&AllowedDns> { match self { FirewallPolicy::Connecting { dns_config, .. } | FirewallPolicy::Connected { dns_config, .. } => Some(dns_config), @@ -313,14 +304,14 @@ impl fmt::Display for FirewallPolicy { } #[cfg(not(target_os = "android"))] -fn display_allowed_non_tunnel_dns(dns_config: &ResolvedDnsConfig) -> String { - if dns_config.non_tunnel_config().is_empty() { +fn display_allowed_non_tunnel_dns(dns_config: &AllowedDns) -> String { + if dns_config.non_tunnel_dns().is_empty() { "none".to_owned() } else { dns_config - .non_tunnel_config() + .non_tunnel_dns() .iter() - .map(|ip| ip.to_string()) + .map(|ep| ep.to_string()) .collect::>() .join(",") } diff --git a/nym-vpn-core/crates/nym-firewall/src/linux.rs b/nym-vpn-core/crates/nym-firewall/src/linux.rs index f5c5c5927d..f6e38271f5 100644 --- a/nym-vpn-core/crates/nym-firewall/src/linux.rs +++ b/nym-vpn-core/crates/nym-firewall/src/linux.rs @@ -6,7 +6,7 @@ use std::{ env, ffi::CStr, fs, io, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr}, sync::LazyLock, }; @@ -25,7 +25,7 @@ use super::{ use nym_cgroup::v2::CGroup2; use nym_firewall_config::{ALLOWED_LAN_MULTICAST_NETS, ALLOWED_LAN_NETS, SPLIT_TUNNEL_MARK}; -use crate::{AllowedClients, DNS_TCP_PORTS, TunnelInterface}; +use crate::{AllowedClients, TunnelInterface}; /// Priority for rules that tag split tunneling packets. Equals NF_IP_PRI_MANGLE. const MANGLE_CHAIN_PRIORITY: i32 = libc::NF_IP_PRI_MANGLE; @@ -261,22 +261,6 @@ impl Firewall { } } -fn get_allow_dns_endpoints_when_connecting(server: IpAddr) -> Vec { - let mut allowed_endpoints = Vec::with_capacity(DNS_TCP_PORTS.len()); - - // Allow requests on other interfaces - for tcp_port in DNS_TCP_PORTS { - let address = SocketAddr::new(server, tcp_port); - let allowed_endpoint = AllowedEndpoint::new( - Endpoint::from_socket_address(address, TransportProtocol::Tcp), - AllowedClients::Root, - ); - allowed_endpoints.push(allowed_endpoint); - } - - allowed_endpoints -} - struct PolicyBatch<'a> { batch: Batch, in_chain: Chain<'a>, @@ -416,20 +400,13 @@ impl<'a> PolicyBatch<'a> { tunnel, dns_config, .. } = policy { - for server in dns_config.tunnel_config() { + for ep in dns_config.tunnel_dns() { let tunnel = tunnel.exit_metadata(); let allow_rule = allow_tunnel_dns_rule( &self.mangle_chain, &tunnel.interface, - TransportProtocol::Udp, - *server, - )?; - self.batch.add(&allow_rule, nftnl::MsgType::Add); - let allow_rule = allow_tunnel_dns_rule( - &self.mangle_chain, - &tunnel.interface, - TransportProtocol::Tcp, - *server, + ep.protocol, + ep.address.ip(), )?; self.batch.add(&allow_rule, nftnl::MsgType::Add); } @@ -671,9 +648,9 @@ impl<'a> PolicyBatch<'a> { allowed_exit_tunnel_traffic, } => { let dns_endpoints = dns_config - .non_tunnel_config() + .non_tunnel_dns() .iter() - .flat_map(|server| get_allow_dns_endpoints_when_connecting(*server)) + .map(|ep| AllowedEndpoint::new(*ep, AllowedClients::Root)) .collect::>(); peer_endpoints @@ -758,28 +735,18 @@ impl<'a> PolicyBatch<'a> { .iter() .for_each(|endpoint| self.add_allow_tunnel_endpoint_rules(endpoint, fwmark)); - for server in dns_config.tunnel_config() { - self.add_allow_tunnel_dns_rule( - &tunnel.exit_metadata().interface, - TransportProtocol::Udp, - *server, - )?; + for ep in dns_config.tunnel_dns() { self.add_allow_tunnel_dns_rule( &tunnel.exit_metadata().interface, - TransportProtocol::Tcp, - *server, + ep.protocol, + ep.address.ip(), )?; } - for server in dns_config.non_tunnel_config() { - self.add_allow_local_dns_rule( - &tunnel.exit_metadata().interface, - TransportProtocol::Udp, - *server, - )?; + for ep in dns_config.non_tunnel_dns() { self.add_allow_local_dns_rule( &tunnel.exit_metadata().interface, - TransportProtocol::Tcp, - *server, + ep.protocol, + ep.address.ip(), )?; } diff --git a/nym-vpn-core/crates/nym-firewall/src/macos.rs b/nym-vpn-core/crates/nym-firewall/src/macos.rs index bcd48a65f1..52ed538d28 100644 --- a/nym-vpn-core/crates/nym-firewall/src/macos.rs +++ b/nym-vpn-core/crates/nym-firewall/src/macos.rs @@ -15,8 +15,10 @@ use libc::{c_int, sysctlbyname}; use nym_firewall_config::{ALLOWED_LAN_MULTICAST_NETS, ALLOWED_LAN_NETS}; use pfctl::{DropAction, FilterRuleAction, Ip, Uid}; +use crate::Endpoint; + use super::{ - DNS_TCP_PORTS, FirewallArguments, FirewallPolicy, + FirewallArguments, FirewallPolicy, net::{ AllowedEndpoint, AllowedTunnelTraffic, TransportProtocol, TunnelInterface, TunnelMetadata, }, @@ -154,18 +156,9 @@ impl Firewall { // Do not reset state for non-tunnel DNS traffic if policy.dns_config().is_some_and(|dns| { - let is_non_tunnel_dns = dns.non_tunnel_config().iter().any(|ip| { - *ip == remote_address.ip() && DNS_TCP_PORTS.contains(&remote_address.port()) - }) && proto == pfctl::Proto::Tcp; - - // Local DNS resolver is configured with the same hosts - // as non-tunnel DNS but uses TCP/UDP port 53 - let is_local_resolver_dns = dns - .non_tunnel_config() + dns.non_tunnel_dns() .iter() - .any(|ip| *ip == remote_address.ip() && remote_address.port() == 53); - - is_non_tunnel_dns || is_local_resolver_dns + .any(|ep| ep.address == remote_address && as_pfctl_proto(ep.protocol) == proto) }) { return Ok(false); } @@ -429,8 +422,8 @@ impl Firewall { } => { let mut rules = Vec::new(); - for server in dns_config.non_tunnel_config() { - rules.append(&mut self.get_allow_dns_rules_when_connecting(*server)?); + for dns_endpoint in dns_config.non_tunnel_dns() { + rules.push(self.get_allow_dns_rules_when_connecting(dns_endpoint)?); } for peer_endpoint in peer_endpoints { @@ -507,15 +500,19 @@ impl Firewall { TunnelInterface::Two { entry, exit, .. } => (Some(entry), exit), }; - for server in dns_config.tunnel_config() { - rules.append( - &mut self - .get_allow_tunnel_dns_rules_when_connected(exit_tunnel, *server)?, - ); + // Do not create rules for any other ports but 53 because "block all dns" targets port 53 specifically + for dns_endpoint in dns_config.tunnel_dns() { + if dns_endpoint.address.port() == 53 { + rules.push(self.get_allow_tunnel_dns_rules_when_connected( + exit_tunnel, + dns_endpoint, + )?); + } } - for server in dns_config.non_tunnel_config() { + for dns_endpoint in dns_config.non_tunnel_dns() { rules.append( - &mut self.get_allow_local_dns_rules_when_connected(exit_tunnel, *server)?, + &mut self + .get_allow_local_dns_rules_when_connected(exit_tunnel, dns_endpoint)?, ); } @@ -599,35 +596,31 @@ impl Firewall { fn get_allow_dns_rules_when_connecting( &self, - server: IpAddr, - ) -> Result> { - let mut rules = Vec::with_capacity(DNS_TCP_PORTS.len()); - - for tcp_port in DNS_TCP_PORTS { - 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, tcp_port)) - .user(Uid::from(super::ROOT_UID)) - .build()?; - rules.push(allow_nontunnel_tcp); - } - - Ok(rules) + dns_endpoint: &Endpoint, + ) -> Result { + let pfctl_proto = as_pfctl_proto(dns_endpoint.protocol); + let allow_nontunnel_dns = self + .create_rule_builder(FilterRuleAction::Pass) + .direction(pfctl::Direction::Out) + .quick(true) + .proto(pfctl_proto) + .keep_state(pfctl::StatePolicy::Keep) + .tcp_flags(Self::get_tcp_flags()) + .to(pfctl::Endpoint::from(dns_endpoint.address)) + .user(Uid::from(super::ROOT_UID)) + .build()?; + Ok(allow_nontunnel_dns) } fn get_allow_local_dns_rules_when_connected( &self, tunnel: &TunnelMetadata, - server: IpAddr, + dns_endpoint: &Endpoint, ) -> Result> { let mut rules = Vec::with_capacity(4); // Block requests on the tunnel interface + // Disregard dns endpoint protocol, block both tcp and udp since some servers may run on both let block_tunnel_tcp = self .create_rule_builder(FilterRuleAction::Drop(DropAction::Return)) .direction(pfctl::Direction::Out) @@ -635,7 +628,7 @@ impl Firewall { .interface(&tunnel.interface) .proto(pfctl::Proto::Tcp) .keep_state(pfctl::StatePolicy::None) - .to(pfctl::Endpoint::new(server, 53)) + .to(pfctl::Endpoint::from(dns_endpoint.address)) .build()?; rules.push(block_tunnel_tcp); let block_tunnel_udp = self @@ -645,7 +638,7 @@ impl Firewall { .interface(&tunnel.interface) .proto(pfctl::Proto::Udp) .keep_state(pfctl::StatePolicy::None) - .to(pfctl::Endpoint::new(server, 53)) + .to(pfctl::Endpoint::from(dns_endpoint.address)) .build()?; rules.push(block_tunnel_udp); @@ -657,7 +650,7 @@ impl Firewall { .proto(pfctl::Proto::Tcp) .keep_state(pfctl::StatePolicy::Keep) .tcp_flags(Self::get_tcp_flags()) - .to(pfctl::Endpoint::new(server, 53)) + .to(pfctl::Endpoint::from(dns_endpoint.address)) .build()?; rules.push(allow_nontunnel_tcp); let allow_nontunnel_udp = self @@ -666,7 +659,7 @@ impl Firewall { .quick(true) .proto(pfctl::Proto::Udp) .keep_state(pfctl::StatePolicy::Keep) - .to(pfctl::Endpoint::new(server, 53)) + .to(pfctl::Endpoint::from(dns_endpoint.address)) .build()?; rules.push(allow_nontunnel_udp); @@ -676,34 +669,20 @@ impl Firewall { fn get_allow_tunnel_dns_rules_when_connected( &self, tunnel: &TunnelMetadata, - server: IpAddr, - ) -> Result> { - let mut rules = Vec::with_capacity(2); - + dns_endpoint: &Endpoint, + ) -> Result { // Allow outgoing requests on the tunnel interface only - let allow_tunnel_tcp = self + let allow_tunnel_dns = self .create_rule_builder(FilterRuleAction::Pass) .direction(pfctl::Direction::Out) .quick(true) .interface(&tunnel.interface) - .proto(pfctl::Proto::Tcp) + .proto(as_pfctl_proto(dns_endpoint.protocol)) .keep_state(pfctl::StatePolicy::Keep) .tcp_flags(Self::get_tcp_flags()) - .to(pfctl::Endpoint::new(server, 53)) + .to(pfctl::Endpoint::from(dns_endpoint.address)) .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) + Ok(allow_tunnel_dns) } /// Allow traffic to relay_endpoint on the correct ip/port/protocol, for the root-user only. diff --git a/nym-vpn-core/crates/nym-firewall/src/net.rs b/nym-vpn-core/crates/nym-firewall/src/net.rs index ac7754b71a..0b0ca831b4 100644 --- a/nym-vpn-core/crates/nym-firewall/src/net.rs +++ b/nym-vpn-core/crates/nym-firewall/src/net.rs @@ -281,3 +281,46 @@ impl TunnelInterface { } } } + +/// Describes DNS allowed on tunnel and non-tunnel interfaces. +#[cfg(not(target_os = "android"))] +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct AllowedDns { + /// DNS addresses allowed on the tunnel interface + tunnel_dns: Vec, + + /// DNS addresses allowed on the non-tunnel interface + non_tunnel_dns: Vec, +} + +impl AllowedDns { + /// Initialize `AllowedDns` with two separate sets of tunnel and non-tunnel DNS. + /// No sanity checks are performed on the input. + pub fn new(tunnel_dns: Vec, non_tunnel_dns: Vec) -> Self { + Self { + tunnel_dns, + non_tunnel_dns, + } + } + + /// Initialize `AllowedDns` with tunnel DNS endpoints, automatically moving endpoints with private IPs to non-tunnel DNS. + pub fn new_with_tunnel_dns(endpoints: Vec) -> Self { + let (non_tunnel_dns, tunnel_dns): (Vec<_>, Vec<_>) = endpoints + .into_iter() + .partition(|ep| nym_firewall_config::is_local_address(&ep.address.ip())); + Self { + non_tunnel_dns, + tunnel_dns, + } + } + + /// Returns the tunnel DNS endpoints. + pub fn tunnel_dns(&self) -> &[Endpoint] { + &self.tunnel_dns + } + + /// Returns the non-tunnel DNS endpoints. + pub fn non_tunnel_dns(&self) -> &[Endpoint] { + &self.non_tunnel_dns + } +} diff --git a/nym-vpn-core/crates/nym-firewall/src/windows/mod.rs b/nym-vpn-core/crates/nym-firewall/src/windows/mod.rs index 4020dce8e2..0cf0d95469 100644 --- a/nym-vpn-core/crates/nym-firewall/src/windows/mod.rs +++ b/nym-vpn-core/crates/nym-firewall/src/windows/mod.rs @@ -5,8 +5,7 @@ #[macro_use] mod ffi; -use crate::{AllowedClients, DNS_TCP_PORTS, Endpoint, TransportProtocol, TunnelInterface}; -use nym_dns::ResolvedDnsConfig; +use crate::{AllowedClients, AllowedDns, TunnelInterface}; use std::{ffi::CStr, net::IpAddr, ptr, sync::LazyLock}; @@ -232,7 +231,7 @@ impl Firewall { endpoints: &[AllowedEndpoint], winfw_settings: &WinFwSettings, tunnel_interface: Option<&TunnelInterface>, - dns_config: &ResolvedDnsConfig, + dns_config: &AllowedDns, allowed_endpoints: &[AllowedEndpoint], allowed_entry_tunnel_traffic: AllowedTunnelTraffic, allowed_exit_tunnel_traffic: AllowedTunnelTraffic, @@ -270,23 +269,11 @@ impl Firewall { let allowed_exit_tunnel_traffic_bridge = AllowedTunnelTrafficBridge::from(allowed_exit_tunnel_traffic); - let non_tunnel_dns_servers = - dns_config - .non_tunnel_config() - .iter() - .cloned() - .flat_map(|dns_ip| { - DNS_TCP_PORTS - .iter() - .copied() - .map(|tcp_port| { - AllowedEndpoint::new( - Endpoint::new(dns_ip, tcp_port, TransportProtocol::Tcp), - AllowedClients::current_exe(), - ) - }) - .collect::>() - }); + let non_tunnel_dns_servers = dns_config + .non_tunnel_dns() + .iter() + .cloned() + .map(|ep| AllowedEndpoint::new(ep, AllowedClients::current_exe())); let allowed_endpoint_containers = allowed_endpoints .iter() @@ -330,7 +317,7 @@ impl Firewall { endpoints: &[AllowedEndpoint], winfw_settings: &WinFwSettings, tunnel_interface: &TunnelInterface, - dns_config: &ResolvedDnsConfig, + dns_config: &AllowedDns, ) -> Result<(), Error> { tracing::trace!("Applying 'connected' firewall policy"); @@ -360,18 +347,16 @@ impl Firewall { }; let tunnel_dns_servers: Vec = dns_config - .tunnel_config() + .tunnel_dns() .iter() - .cloned() - .map(widestring_ip) + .map(|ep| widestring_ip(ep.address.ip())) .collect(); let tunnel_dns_servers_refs: Vec<*const u16> = tunnel_dns_servers.iter().map(|ip| ip.as_ptr()).collect(); let non_tunnel_dns_servers: Vec = dns_config - .non_tunnel_config() + .non_tunnel_dns() .iter() - .cloned() - .map(widestring_ip) + .map(|ep| widestring_ip(ep.address.ip())) .collect(); let non_tunnel_dns_servers_refs: Vec<*const u16> = non_tunnel_dns_servers .iter() diff --git a/nym-vpn-core/crates/nym-vpn-lib-types/src/gateway_independence.rs b/nym-vpn-core/crates/nym-vpn-lib-types/src/gateway_independence.rs index 808f6e56da..1e1a71fa32 100644 --- a/nym-vpn-core/crates/nym-vpn-lib-types/src/gateway_independence.rs +++ b/nym-vpn-core/crates/nym-vpn-lib-types/src/gateway_independence.rs @@ -45,6 +45,13 @@ impl Default for GatewayIndependence { } } +impl fmt::Display for GatewayIndependence { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "different node family: {}; ", self.different_node_family)?; + write!(f, "different ASN: {}", self.different_asn) + } +} + #[cfg(test)] mod tests { use super::*; @@ -91,10 +98,3 @@ mod tests { assert!(gi.active()); } } - -impl fmt::Display for GatewayIndependence { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "different node family: {}; ", self.different_node_family)?; - write!(f, "different ASN: {}", self.different_asn) - } -} diff --git a/nym-vpn-core/crates/nym-vpn-lib/Cargo.toml b/nym-vpn-core/crates/nym-vpn-lib/Cargo.toml index 00d186acf9..2062fea7d7 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/Cargo.toml +++ b/nym-vpn-core/crates/nym-vpn-lib/Cargo.toml @@ -34,6 +34,9 @@ hickory-resolver = { workspace = true, features = [ "tokio", "tls-ring", "https-ring", + "h3-ring", + "quic-ring", + "webpki-roots", ] } hyper = { workspace = true, features = ["server", "http1"] } hyper-util = { workspace = true, features = ["tokio"] } diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/lib.rs b/nym-vpn-core/crates/nym-vpn-lib/src/lib.rs index 999ef6d0c0..b305a8c655 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/lib.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/lib.rs @@ -22,16 +22,14 @@ pub mod tunnel_provider; pub mod tunnel_state_machine; mod wg_config; +use std::sync::LazyLock; + use hickory_resolver::config::{CLOUDFLARE, NameServerConfig, QUAD9}; #[cfg(target_os = "windows")] pub use nym_split_tunnel::install_driver_service as install_split_tunnel_driver_service; #[cfg(target_os = "windows")] pub use nym_split_tunnel::uninstall_driver_service as uninstall_split_tunnel_driver_service; -use std::{net::IpAddr, sync::LazyLock}; - -use itertools::Itertools; - // Re-export some our nym dependencies pub use nym_config; pub use nym_gateway_directory as gateway_directory; @@ -59,15 +57,6 @@ static DEFAULT_DNS_SERVERS_CONFIG: LazyLock> = LazyLock::n .collect() }); -/// Default DNS server IP addresses. -pub static DEFAULT_DNS_SERVERS: LazyLock> = LazyLock::new(|| { - DEFAULT_DNS_SERVERS_CONFIG - .iter() - .map(|ns| ns.ip) - .unique() - .collect() -}); - /// Routing table id used for routing all traffic through the tunnel. #[cfg(target_os = "linux")] pub const TUNNEL_TABLE_ID: u32 = 0x14d; diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/resolver/mod.rs b/nym-vpn-core/crates/nym-vpn-lib/src/resolver/mod.rs index 8554d1e7a8..b634486c98 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/resolver/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/resolver/mod.rs @@ -15,7 +15,6 @@ #[cfg(any(target_os = "macos", target_os = "linux"))] mod unix; -use hickory_resolver::config::NameServerConfig; #[cfg(any(target_os = "macos", target_os = "linux"))] pub(crate) use unix::flush_system_cache; @@ -46,10 +45,11 @@ use std::{ }; use async_trait::async_trait; +use hickory_resolver::config::NameServerConfig; use hickory_server::{ net::runtime::Time, proto::{ - op::{Header, HeaderCounts, LowerQuery, MessageType, Metadata, OpCode}, + op::{Header, HeaderCounts, LowerQuery, MessageType, Metadata, OpCode, ResponseCode}, rr::{LowerName, RData, Record, RecordType, domain::Name, rdata}, }, resolver::{ @@ -60,6 +60,8 @@ use hickory_server::{ server::{Request, RequestHandler, ResponseHandler, ResponseInfo, Server}, zone_handler::{AuthLookup, MessageRequest, MessageResponse, MessageResponseBuilder}, }; +#[cfg(not(target_os = "ios"))] +use hickory_server::{net::runtime::TokioRuntimeProvider, resolver::TokioResolver}; use tokio::{ net::{TcpListener, UdpSocket}, sync::{mpsc, oneshot}, @@ -68,9 +70,6 @@ use tokio_util::{either::Either, sync::CancellationToken}; #[cfg(target_os = "ios")] use apple_connection_provider::{AppleConnectionProvider, TokioResolver}; -use hickory_server::proto::op::ResponseCode; -#[cfg(not(target_os = "ios"))] -use hickory_server::{net::runtime::TokioRuntimeProvider, resolver::TokioResolver}; #[async_trait] #[cfg_attr(target_os = "ios", allow(unused))] @@ -86,21 +85,6 @@ pub use crate::dns_filter::{ DnsFilter, DnsFilterDecision, DnsFilterStrategy, DnsFilterT, NullDnsFilter, }; -/// If a local DNS resolver should be used. -/// -/// Local DNS resolver is used to work around Apple's captive portals check. -/// More info can be found at -pub static LOCAL_DNS_RESOLVER: LazyLock = LazyLock::new(|| { - let disable_local_dns_resolver = std::env::var("NYM_DISABLE_LOCAL_DNS_RESOLVER") - .map(|v| v != "0") - // Use the local DNS resolver by default. - .unwrap_or(false); - if !disable_local_dns_resolver { - tracing::info!("Using local DNS resolver"); - } - !disable_local_dns_resolver -}); - /// Local DNS resolver listen port. const DNS_LISTEN_PORT: u16 = if cfg!(test) { 1053 } else { 53 }; @@ -195,8 +179,8 @@ enum Config { /// Forward DNS queries to a configured server Forwarding { - /// Remote DNS server to use - dns_servers: Vec, + /// Remote DNS servers to use + dns_servers: Vec, /// Interface to bind client socket to. /// iOS only @@ -338,7 +322,7 @@ impl ResolverHandle { /// Set the DNS servers to forward queries to `dns_servers`. pub async fn enable_forward( &self, - dns_servers: Vec, + dns_servers: Vec, #[cfg(target_os = "ios")] bind_interface: Option, ) -> Result<(), Error> { let (response_tx, response_rx) = oneshot::channel(); @@ -569,7 +553,7 @@ impl LocalResolver { bind_interface, } => { // make sure not to accidentally forward queries to ourselves - dns_servers.retain(|addr| *addr != self.bound_to.ip()); + dns_servers.retain(|addr| addr.ip != self.bound_to.ip()); self.forwarding( dns_servers, #[cfg(target_os = "ios")] @@ -587,15 +571,10 @@ impl LocalResolver { /// Turn into a forwarding resolver (forward DNS queries to [dns_servers]). fn forwarding( &mut self, - dns_servers: Vec, + dns_servers: Vec, #[cfg(target_os = "ios")] bind_interface: Option, ) -> Result<(), Error> { - let forward_server_config = dns_servers - .into_iter() - .map(NameServerConfig::udp_and_tcp) - .collect::>(); - - let forward_config = ResolverConfig::from_parts(None, vec![], forward_server_config); + let forward_config = ResolverConfig::from_parts(None, vec![], dns_servers); #[cfg(target_os = "ios")] let connection_provider = AppleConnectionProvider::new(bind_interface); diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/service/vpn_service.rs b/nym-vpn-core/crates/nym-vpn-lib/src/service/vpn_service.rs index 615bed36a3..cfe359199b 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/service/vpn_service.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/service/vpn_service.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use std::{ + collections::HashSet, net::IpAddr, path::PathBuf, pin::{Pin, pin}, @@ -70,7 +71,7 @@ use crate::tunnel_provider::OSTunProvider; #[cfg(target_os = "linux")] use crate::tunnel_state_machine::LinuxSplitTunnelConfiguration; use crate::{ - DEFAULT_DNS_SERVERS, NodeIdentity, UserAgent, VpnTopologyService, + DEFAULT_DNS_SERVERS_CONFIG, NodeIdentity, UserAgent, VpnTopologyService, config::GlobalConfig, gateway_directory::{self, GatewayCache, GatewayCacheHandle, GatewayClient}, logging::LogFileRemoverHandle, @@ -1039,7 +1040,7 @@ impl NymVpnService { let _ = tx.send(result); } VpnServiceCommand::GetDefaultDns(tx, ()) => { - let result = self.handle_get_default_dns().await; + let result = self.handle_get_default_dns(); let _ = tx.send(result); } VpnServiceCommand::ListGateways(tx, options) => { @@ -1455,8 +1456,14 @@ impl NymVpnService { .map(FeatureFlags::from) } - async fn handle_get_default_dns(&self) -> Vec { - DEFAULT_DNS_SERVERS.clone() + fn handle_get_default_dns(&self) -> Vec { + // todo: return protocols too! + let mut seen = HashSet::with_capacity(DEFAULT_DNS_SERVERS_CONFIG.len()); + DEFAULT_DNS_SERVERS_CONFIG + .iter() + .map(|v| v.ip) + .filter(|ip| seen.insert(*ip)) + .collect() } async fn handle_list_gateways( diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/dns_handler.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/dns_handler.rs index 08358e4900..dc1ee4c83c 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/dns_handler.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/dns_handler.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_dns::{DnsMonitor, ResolvedDnsConfig}; +use nym_dns::{DnsConfig, DnsMonitor}; use tokio::{ sync::{mpsc, oneshot}, task::JoinHandle, @@ -27,11 +27,7 @@ impl DnsHandler { }) } - pub async fn set( - &mut self, - interface: &str, - config: ResolvedDnsConfig, - ) -> Result<(), nym_dns::Error> { + pub async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<(), nym_dns::Error> { self.inner.set(interface, config).await } @@ -48,7 +44,7 @@ impl DnsHandler { enum DnsHandlerCommand { Set { interface: String, - config: ResolvedDnsConfig, + config: DnsConfig, reply_tx: oneshot::Sender>, }, #[allow(unused)] @@ -108,7 +104,7 @@ impl DnsHandlerHandle { Ok((Self { tx }, join_handle)) } - pub async fn set(&mut self, interface: &str, config: ResolvedDnsConfig) -> Result<()> { + pub async fn set(&mut self, interface: &str, config: DnsConfig) -> Result<()> { let (reply_tx, reply_rx) = oneshot::channel(); self.send_and_wait( diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs index 57b8f02652..e6a3202f09 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/mod.rs @@ -46,9 +46,10 @@ use crate::{ tunnel_state_machine::tunnel::gateway_provider::GatewayProvider, }; +use hickory_resolver::config::NameServerConfig; +#[cfg(not(target_os = "ios"))] +use hickory_resolver::config::ProtocolConfig; use nym_config::defaults::{WG_METADATA_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4}; -#[cfg(not(any(target_os = "android")))] -use nym_dns::ResolvedDnsConfig; use nym_offline_monitor::ConnectivityHandle; use nym_registration_client::MixnetClientConfig; use nym_statistics::StatisticsSender; @@ -60,8 +61,6 @@ use tokio::{ }; use tokio_util::sync::CancellationToken; -#[cfg(not(any(target_os = "android")))] -use nym_dns::DnsConfig; #[cfg(not(any(target_os = "android", target_os = "ios")))] use nym_firewall::{Firewall, FirewallArguments, InitialFirewallState}; use nym_gateway_directory::{Config as GatewayDirectoryConfig, GatewayCacheHandle}; @@ -206,34 +205,116 @@ impl TunnelSettings { } } - #[cfg(not(any(target_os = "android")))] - /// Returns resolved DNS config resolved against default DNS IPs. - pub fn resolved_dns_config(&self) -> ResolvedDnsConfig { - self.dns.to_dns_config().resolve( - &self.dns_ips(), - #[cfg(not(any(target_os = "android", target_os = "ios")))] - 53, - ) + pub fn resolver_config(&self) -> Vec { + let defaults = || crate::DEFAULT_DNS_SERVERS_CONFIG.clone(); + + let mut config = match self.dns { + DnsOptions::Default => defaults(), + DnsOptions::Custom(ref addrs) => { + if addrs.is_empty() { + defaults() + } else { + addrs + .iter() + .cloned() + .map(NameServerConfig::udp_and_tcp) + .collect() + } + } + }; + config.retain(|ns| ns.ip.is_ipv4() || (ns.ip.is_ipv6() && self.enable_ipv6)); + config } - /// Returns DNS IPs filtering out IPv6 addresses when IPv6 is disabled. - pub fn dns_ips(&self) -> Vec { - match self.dns { - DnsOptions::Custom(ref addrs) => addrs + /// Returns IP addresses of the DNS servers suitable for Android DNS configuraiton. + /// + /// If Private DNS is enabled, these IPs will be probed for DoT before falling back to UDP/TCP. + #[cfg(target_os = "android")] + pub fn android_tunnel_dns(&self) -> Vec { + let defaults = || { + let mut seen = HashSet::new(); + crate::DEFAULT_DNS_SERVERS_CONFIG .iter() - .filter(|ip| ip.is_ipv4() || (ip.is_ipv6() && self.enable_ipv6)) - .copied() - .collect(), - DnsOptions::Default => self.default_dns_ips(), + .filter(|ns| { + // Android only supports TCP/UDP 53 and TLS (853) + ns.connections.iter().any(|conn| { + matches!( + conn.protocol, + ProtocolConfig::Tls { .. } | ProtocolConfig::Udp | ProtocolConfig::Tcp + ) + }) + }) + .map(|ns| ns.ip) + .filter(|ip| seen.insert(*ip)) + .collect() + }; + + let mut addrs = match self.dns { + DnsOptions::Default => defaults(), + DnsOptions::Custom(ref addrs) => { + if addrs.is_empty() { + defaults() + } else { + addrs.clone() + } + } + }; + addrs.retain(|v| v.is_ipv4() || (v.is_ipv6() && self.enable_ipv6)); + addrs + } + + #[cfg(not(any(target_os = "android", target_os = "ios")))] + pub fn allowed_dns_endpoints(&self) -> Vec { + match self.dns { + DnsOptions::Custom(ref addrs) => { + if addrs.is_empty() { + self.allowed_default_dns_endpoints() + } else { + addrs + .iter() + .filter(|ip| ip.is_ipv4() || (ip.is_ipv6() && self.enable_ipv6)) + .copied() + .flat_map(|ip| { + // todo: add support for DoH/DoT in custom DNS options + [ + nym_firewall::Endpoint::new( + ip, + 53, + nym_firewall::TransportProtocol::Udp, + ), + nym_firewall::Endpoint::new( + ip, + 53, + nym_firewall::TransportProtocol::Tcp, + ), + ] + }) + .collect() + } + } + DnsOptions::Default => self.allowed_default_dns_endpoints(), } } - pub fn default_dns_ips(&self) -> Vec { - crate::DEFAULT_DNS_SERVERS + #[cfg(not(any(target_os = "android", target_os = "ios")))] + pub fn allowed_default_dns_endpoints(&self) -> Vec { + crate::DEFAULT_DNS_SERVERS_CONFIG .iter() - .filter(|ip| ip.is_ipv4() || (ip.is_ipv6() && self.enable_ipv6)) - .copied() - .collect() + .filter(|ns| ns.ip.is_ipv4() || (ns.ip.is_ipv6() && self.enable_ipv6)) + .flat_map(|ns| { + ns.connections.iter().map(|conn| { + let proto = match conn.protocol { + ProtocolConfig::Udp + | ProtocolConfig::H3 { .. } + | ProtocolConfig::Quic { .. } => nym_firewall::TransportProtocol::Udp, + ProtocolConfig::Tcp + | ProtocolConfig::Https { .. } + | ProtocolConfig::Tls { .. } => nym_firewall::TransportProtocol::Tcp, + }; + nym_firewall::Endpoint::new(ns.ip, conn.port, proto) + }) + }) + .collect::>() } pub fn bridges_enabled(&self) -> bool { @@ -486,35 +567,6 @@ pub enum DnsOptions { Custom(Vec), } -impl DnsOptions { - /// Convert dns options into [DnsConfig]. - #[cfg(not(any(target_os = "android")))] - fn to_dns_config(&self) -> DnsConfig { - match self { - Self::Default => DnsConfig::default(), - Self::Custom(addrs) => { - if addrs.is_empty() { - DnsConfig::default() - } else { - let (non_tunnel_config, tunnel_config): (Vec<_>, Vec<_>) = addrs - .iter() - // Private IP ranges should not be tunneled - .partition(|&addr| nym_firewall_config::is_local_address(addr)); - DnsConfig::from_addresses(&tunnel_config, &non_tunnel_config) - } - } - } - } - - #[cfg(any(target_os = "ios", target_os = "android"))] - pub fn ip_addresses<'a>(&'a self, default_addresses: &'a [IpAddr]) -> &'a [IpAddr] { - match self { - Self::Default => default_addresses, - Self::Custom(addrs) => addrs.as_slice(), - } - } -} - #[derive(Debug)] pub enum TunnelCommand { /// Connect the tunnel. diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connected_state.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connected_state.rs index b5ed8b45e1..2cc23146e5 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connected_state.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connected_state.rs @@ -4,18 +4,13 @@ #[cfg(not(any(target_os = "android", target_os = "ios")))] use std::net::SocketAddr; -#[cfg(any(target_os = "windows", target_os = "linux"))] +#[cfg(any(target_os = "linux", target_os = "windows"))] use nym_dns::DnsConfig; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use nym_dns::ResolvedDnsConfig; - use nym_vpn_lib_types::ErrorStateReason; use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; -#[cfg(not(any(target_os = "android")))] -use crate::resolver::LOCAL_DNS_RESOLVER; #[cfg(target_os = "ios")] use crate::tunnel_state_machine::Result; use crate::tunnel_state_machine::{ @@ -30,7 +25,9 @@ use crate::tunnel_state_machine::{Error, Result, gateway_ext::GatewayExt}; #[cfg(not(any(target_os = "android")))] use nym_common::trace_err_chain; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use nym_firewall::{AllowedClients, AllowedEndpoint, Endpoint, FirewallPolicy, TransportProtocol}; +use nym_firewall::{ + AllowedClients, AllowedDns, AllowedEndpoint, Endpoint, FirewallPolicy, TransportProtocol, +}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use nym_vpn_lib_types::TunnelConnectionData; @@ -40,7 +37,10 @@ pub struct ConnectedState { tunnel_monitor_handle: TunnelMonitorHandle, tunnel_monitor_event_receiver: TunnelMonitorEventReceiver, selected_gateways: SelectedGateways, - #[cfg_attr(any(target_os = "android", target_os = "ios"), allow(unused))] + #[cfg_attr( + not(any(target_os = "linux", target_os = "windows", target_os = "ios")), + allow(unused) + )] tunnel_interface: TunnelInterface, #[cfg(not(any(target_os = "android", target_os = "ios")))] firewall_policy_params: ConnectedPolicyParameters, @@ -85,7 +85,9 @@ impl ConnectedState { allow_lan: shared_state.tunnel_settings.allow_lan, wg_entry_endpoint, ws_entry_endpoints: ws_endpoints, - dns_config: shared_state.tunnel_settings.resolved_dns_config(), + dns_config: AllowedDns::new_with_tunnel_dns( + shared_state.tunnel_settings.allowed_dns_endpoints(), + ), tunnel_interface: tunnel_interface.clone(), #[cfg(target_os = "macos")] redirect_interface, @@ -149,50 +151,54 @@ impl ConnectedState { #[cfg(not(target_os = "android"))] async fn set_dns(&self, shared_state: &mut SharedState) -> Result<()> { - let dns_config = shared_state.tunnel_settings.resolved_dns_config(); - #[cfg(not(any(target_os = "android")))] + let dns_config = shared_state.tunnel_settings.resolver_config(); + + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "ios"))] let tunnel_metadata = self.tunnel_interface.exit_tunnel_metadata(); - // We do not want to forward DNS queries to *our* local resolver if we do not run a local - // DNS resolver *or* if the DNS config points to a loopback address. - if *LOCAL_DNS_RESOLVER { - let ips = dns_config.addresses().collect::>(); - tracing::debug!("Enabling local DNS forwarder to: {ips:?}"); - if let Err(err) = shared_state - .filtering_resolver - .enable_forward( - ips, - #[cfg(target_os = "ios")] - Some(tunnel_metadata.interface.clone()), - ) - .await - { - trace_err_chain!(err, "failed to enable dns forwarding"); - } + tracing::debug!( + "Enabling local DNS forwarder to: {}", + dns_config + .iter() + .map(|ns| { + let protos = ns + .connections + .iter() + .map(|conn| format!("{}/{}", conn.port, conn.protocol.to_protocol())) + .collect::>() + .join(","); + format!("{} ({})", ns.ip, protos) + }) + .collect::>() + .join(", ") + ); - #[cfg(any(target_os = "linux", target_os = "windows"))] - { - // Point the tunnel interface DNS at the local filtering resolver so that the OS actually - // sends DNS queries to it. - let listen_addr = shared_state.filtering_resolver.listen_addr(); - let system_dns = - DnsConfig::default().resolve(&[listen_addr.ip()], listen_addr.port()); - shared_state - .dns_handler - .set(&tunnel_metadata.interface, system_dns) - .await - .map_err(Error::SetDns)?; - } - } else { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - tracing::debug!("Not enabling local DNS resolver"); - shared_state - .dns_handler - .set(&tunnel_metadata.interface, dns_config) - .await - .map_err(Error::SetDns)?; - } + if let Err(err) = shared_state + .filtering_resolver + .enable_forward( + dns_config, + #[cfg(target_os = "ios")] + Some(tunnel_metadata.interface.clone()), + ) + .await + { + trace_err_chain!(err, "failed to enable dns forwarding"); + } + + #[cfg(any(target_os = "linux", target_os = "windows"))] + { + // Point the tunnel interface DNS at the local filtering resolver so that the OS actually + // sends DNS queries to it. + let listen_addr = shared_state.filtering_resolver.listen_addr(); + let system_dns = DnsConfig { + addresses: vec![listen_addr.ip()], + port: listen_addr.port(), + }; + shared_state + .dns_handler + .set(&tunnel_metadata.interface, system_dns) + .await + .map_err(Error::SetDns)?; } Ok(()) @@ -200,15 +206,8 @@ impl ConnectedState { #[cfg(not(target_os = "android"))] async fn reset_dns(shared_state: &mut SharedState) { - if *LOCAL_DNS_RESOLVER { - if let Err(err) = shared_state.filtering_resolver.disable_forward().await { - trace_err_chain!(err, "failed to disable dns forwarding"); - } - } else { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if let Err(error) = shared_state.dns_handler.reset().await { - trace_err_chain!(error, "Failed to reset DNS"); - } + if let Err(err) = shared_state.filtering_resolver.disable_forward().await { + trace_err_chain!(err, "failed to disable dns forwarding"); } #[cfg(any(target_os = "linux", target_os = "windows"))] @@ -427,7 +426,7 @@ struct ConnectedPolicyParameters { ws_entry_endpoints: Vec, /// Resolved DNS configuration including in-tunnel and out-of-tunnel DNS servers - dns_config: ResolvedDnsConfig, + dns_config: nym_firewall::AllowedDns, /// Tunnel interface tunnel_interface: TunnelInterface, @@ -496,7 +495,6 @@ impl ConnectedPolicyParameters { #[cfg(not(any(target_os = "android", target_os = "ios")))] mod tests { use super::*; - use nym_dns::DnsConfig; use std::net::{IpAddr, Ipv4Addr}; fn create_mock_gateway_with_websocket_endpoints( @@ -552,11 +550,20 @@ mod tests { }; let tunnel_interface = TunnelInterface::One(tunnel_metadata); - // Create ResolvedDnsConfig using DnsConfig::default().resolve() - let dns_config = DnsConfig::default().resolve( - &[IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))], - #[cfg(not(any(target_os = "android", target_os = "ios")))] - 53, + let dns_config = AllowedDns::new( + vec![ + Endpoint::new( + IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), + 53, + TransportProtocol::Tcp, + ), + Endpoint::new( + IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), + 53, + TransportProtocol::Udp, + ), + ], + vec![], ); let params = ConnectedPolicyParameters { diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connecting_state.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connecting_state.rs index 8e84f56d39..bb3bdd388d 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connecting_state.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/connecting_state.rs @@ -1,8 +1,6 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use std::net::IpAddr; #[cfg(not(any(target_os = "android", target_os = "ios")))] use std::net::SocketAddr; use std::time::Duration; @@ -11,11 +9,11 @@ use futures::{ FutureExt, future::{BoxFuture, Fuse}, }; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use nym_firewall::AllowedDns; use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; -#[cfg(target_os = "macos")] -use crate::resolver::LOCAL_DNS_RESOLVER; #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::tunnel_state_machine::Error; #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -32,7 +30,7 @@ use crate::tunnel_state_machine::{ }; use nym_common::trace_err_chain; -#[cfg(not(any(target_os = "android", target_os = "ios")))] +#[cfg(target_os = "macos")] use nym_dns::DnsConfig; #[cfg(not(any(target_os = "android", target_os = "ios")))] use nym_firewall::{ @@ -145,8 +143,8 @@ impl ConnectingState { .map(|v| v.entry_gateway().lp_endpoints()) .unwrap_or_default(), api_endpoints: Vec::new(), - // Allow default DNS servers since hickory does not rely on custom DNS - dns_servers: shared_state.tunnel_settings.default_dns_ips(), + // Allow default DNS servers when connecting since those are used by http/client + dns_servers: shared_state.tunnel_settings.allowed_default_dns_endpoints(), tunnel_interface: None, #[cfg(target_os = "macos")] redirect_interface, @@ -214,20 +212,16 @@ impl ConnectingState { #[cfg(target_os = "macos")] async fn set_local_dns_resolver(shared_state: &mut SharedState) -> Result<()> { - if *LOCAL_DNS_RESOLVER { - // Set system DNS to our local DNS resolver - let system_dns = DnsConfig::default().resolve( - &[shared_state.filtering_resolver.listen_addr().ip()], - shared_state.filtering_resolver.listen_addr().port(), - ); - shared_state - .dns_handler - .set("lo", system_dns) - .await - .map_err(Error::SetDns) - } else { - Ok(()) - } + // Set system DNS to our local DNS resolver + let system_dns = DnsConfig { + addresses: vec![shared_state.filtering_resolver.listen_addr().ip()], + port: shared_state.filtering_resolver.listen_addr().port(), + }; + shared_state + .dns_handler + .set("lo", system_dns) + .await + .map_err(Error::SetDns) } #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -864,8 +858,8 @@ struct ConnectingPolicyParameters { /// API endpoints api_endpoints: Vec, - /// DNS servers - dns_servers: Vec, + /// Non-tunnel DNS servers + dns_servers: Vec, /// Tunnel interface tunnel_interface: Option, @@ -963,13 +957,7 @@ impl ConnectingPolicyParameters { .clone() .map(nym_firewall::TunnelInterface::from); - // Set non-tunnel DNS to allow api client to use those DNS servers. - let dns_config = DnsConfig::from_addresses(&[], &self.dns_servers).resolve( - // pass empty because we already override the config with non-tunnel addresses. - &[], - #[cfg(not(any(target_os = "android", target_os = "ios")))] - 53, - ); + let dns_config = AllowedDns::new(vec![], self.dns_servers.clone()); FirewallPolicy::Connecting { peer_endpoints, diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/error_state.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/error_state.rs index a88b124dda..b2011acad1 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/error_state.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/error_state.rs @@ -24,8 +24,6 @@ use nym_common::trace_err_chain; #[cfg(not(any(target_os = "android", target_os = "ios")))] use nym_firewall::FirewallPolicy; -#[cfg(target_os = "macos")] -use crate::resolver::LOCAL_DNS_RESOLVER; #[cfg(target_os = "ios")] use crate::tunnel_provider::{OSTunProvider, TunnelSettings}; #[cfg(target_os = "ios")] @@ -70,7 +68,11 @@ impl ErrorState { #[cfg(target_os = "ios")] { - Self::set_blocking_network_settings(shared_state.tun_provider.clone()).await; + Self::set_blocking_network_settings( + shared_state.tun_provider.clone(), + shared_state.filtering_resolver.listen_addr().ip(), + ) + .await; } // todo: activate kill switch on Android @@ -129,10 +131,11 @@ impl ErrorState { #[cfg(target_os = "macos")] async fn set_local_dns_resolver(shared_state: &mut SharedState) -> Result<()> { // Set system DNS to our local DNS resolver - let system_dns = DnsConfig::default().resolve( - &[shared_state.filtering_resolver.listen_addr().ip()], - shared_state.filtering_resolver.listen_addr().port(), - ); + let system_dns = DnsConfig { + addresses: vec![shared_state.filtering_resolver.listen_addr().ip()], + port: shared_state.filtering_resolver.listen_addr().port(), + }; + shared_state .dns_handler .set("lo", system_dns) @@ -145,11 +148,14 @@ impl ErrorState { /// Configure tunnel with network settings blocking all traffic #[cfg(target_os = "ios")] - async fn set_blocking_network_settings(tun_provider: Arc) { + async fn set_blocking_network_settings( + tun_provider: Arc, + dns_server: IpAddr, + ) { let tunnel_network_settings = TunnelSettings { remote_addresses: vec![], interface_addresses: BLOCKING_INTERFACE_ADDRS.map(IpNetwork::from).to_vec(), - dns_servers: vec![], + dns_servers: vec![dns_server], mtu: MIN_IPV6_MTU, }; @@ -175,13 +181,6 @@ impl TunnelStateHandler for ErrorState { tracing::debug!("ErrorState received command: {command:?}"); match command { TunnelCommand::Connect => { - #[cfg(target_os = "macos")] - if !*LOCAL_DNS_RESOLVER { - // This is probably unnecessary, since DNS is already configured on the - // primary interface. - Self::reset_dns(shared_state).await; - } - #[cfg(any(target_os = "linux", target_os = "windows"))] Self::reset_dns(shared_state).await; diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/offline_state.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/offline_state.rs index 354e503e07..00595a2679 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/offline_state.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/states/offline_state.rs @@ -4,8 +4,6 @@ use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; -#[cfg(target_os = "macos")] -use crate::resolver::LOCAL_DNS_RESOLVER; #[cfg(target_os = "macos")] use crate::tunnel_state_machine::ErrorStateReason; #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -95,10 +93,11 @@ impl OfflineState { #[cfg(target_os = "macos")] async fn set_local_dns_resolver(shared_state: &mut SharedState) -> Result<()> { // Set system DNS to our local DNS resolver - let system_dns = DnsConfig::default().resolve( - &[shared_state.filtering_resolver.listen_addr().ip()], - shared_state.filtering_resolver.listen_addr().port(), - ); + let system_dns = DnsConfig { + addresses: vec![shared_state.filtering_resolver.listen_addr().ip()], + port: shared_state.filtering_resolver.listen_addr().port(), + }; + shared_state .dns_handler .set("lo", system_dns) @@ -190,13 +189,6 @@ impl TunnelStateHandler for OfflineState { if connectivity.is_offline() { NextTunnelState::SameState(self) } else { - #[cfg(target_os = "macos")] - if !*LOCAL_DNS_RESOLVER { - // This is probably unnecessary, since DNS is already configured on the - // primary interface. - Self::reset_dns(shared_state).await; - } - #[cfg(any(target_os = "linux", target_os = "windows"))] Self::reset_dns(shared_state).await; diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel/gateway_provider/selector.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel/gateway_provider/selector.rs index 6bd552d58a..d3570b1f50 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel/gateway_provider/selector.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel/gateway_provider/selector.rs @@ -494,10 +494,10 @@ mod tests { let identity: nym_vpn_lib_types::NodeIdentity = GW_ID_1.parse().unwrap(); let mut settings = default_tunnel_settings(); - settings.entry_point = Box::new(EntryPoint::Gateway { + *settings.entry_point = EntryPoint::Gateway { identity: identity.clone(), - }); - settings.exit_point = Box::new(ExitPoint::Gateway { identity }); + }; + *settings.exit_point = ExitPoint::Gateway { identity }; let result = select_gateways( gateway_cache, diff --git a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs index 3f552ca238..8b6feb509a 100644 --- a/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs +++ b/nym-vpn-core/crates/nym-vpn-lib/src/tunnel_state_machine/tunnel_monitor.rs @@ -1150,11 +1150,10 @@ impl TunnelMonitor { exit: WireguardNode::from(&conn_data.exit), }); - let dns_config = self.tunnel_parameters.tunnel_settings.resolved_dns_config(); let tunnel_options = TunnelOptions::Netstack(NetstackTunnelOptions { metadata_proxy_tx: entry_metadata_tx, exit_tun, - dns: dns_config.tunnel_config().to_vec(), + dns: vec![], // we configure system resolver ourselves }); let tunnel_metadata = TunnelMetadata { @@ -1228,13 +1227,12 @@ impl TunnelMonitor { #[cfg(not(target_os = "linux"))] let entry_endpoint = conn_data.effective_remote_entry_endpoint(); - let dns_config = self.tunnel_parameters.tunnel_settings.resolved_dns_config(); let tunnel_options = TunnelOptions::Netstack(NetstackTunnelOptions { metadata_proxy_tx: entry_metadata_tx, exit_tun_name: WG_EXIT_WINTUN_NAME.to_owned(), exit_tun_guid: WG_EXIT_WINTUN_GUID.to_owned(), wintun_tunnel_type: WINTUN_TUNNEL_TYPE.to_owned(), - dns: dns_config.tunnel_config().to_vec(), + dns: vec![], // we configure system resolver ourselves }); let mut tunnel_handle = connected_tunnel @@ -1377,11 +1375,10 @@ impl TunnelMonitor { exit: WireguardNode::from(&conn_data.exit), }); - let dns_config = self.tunnel_parameters.tunnel_settings.resolved_dns_config(); let tunnel_options = TunnelOptions::TunTun(TunTunTunnelOptions { entry_tun, exit_tun, - dns: dns_config.tunnel_config().to_vec(), + dns: vec![], // we configure system resolver ourselves }); let tunnel_handle = connected_tunnel @@ -1459,14 +1456,13 @@ impl TunnelMonitor { exit: WireguardNode::from(&conn_data.exit), }); - let dns_config = self.tunnel_parameters.tunnel_settings.resolved_dns_config(); let tunnel_options = TunnelOptions::TunTun(TunTunTunnelOptions { entry_tun_name: WG_ENTRY_WINTUN_NAME.to_owned(), entry_tun_guid: WG_ENTRY_WINTUN_GUID.to_owned(), exit_tun_name: WG_EXIT_WINTUN_NAME.to_owned(), exit_tun_guid: WG_EXIT_WINTUN_GUID.to_owned(), wintun_tunnel_type: WINTUN_TUNNEL_TYPE.to_owned(), - dns: dns_config.tunnel_config().to_vec(), + dns: vec![], // we configure system resolver ourselves }); let mut tunnel_handle = connected_tunnel @@ -1632,21 +1628,14 @@ impl TunnelMonitor { }) } - #[cfg(any(target_os = "ios", target_os = "android"))] + #[cfg(target_os = "ios")] fn get_mobile_dns_addresses(&self) -> Vec { - #[cfg(target_os = "ios")] - { - vec![self.tunnel_parameters.filtering_resolver_addr.ip()] - } + vec![self.tunnel_parameters.filtering_resolver_addr.ip()] + } - #[cfg(target_os = "android")] - { - self.tunnel_parameters - .tunnel_settings - .dns - .ip_addresses(&self.tunnel_parameters.tunnel_settings.dns_ips()) - .to_vec() - } + #[cfg(target_os = "android")] + fn get_mobile_dns_addresses(&self) -> Vec { + self.tunnel_parameters.tunnel_settings.android_tunnel_dns() } #[cfg(not(any(target_os = "android", target_os = "ios")))]