diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-09-03 14:34:42 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-09-03 14:34:42 +0200 |
| commit | 5ba4fb290a2cb737147be093c82198500f3beaeb (patch) | |
| tree | b2403b9d3ccfb299d06b42b6e9279b925732dee0 | |
| parent | 89c12d3d3a01c03ecc4c4e85df18b4bd09795112 (diff) | |
| parent | dee999c8b0512a332194d681c9ce74d1b966d72d (diff) | |
| download | mullvadvpn-5ba4fb290a2cb737147be093c82198500f3beaeb.tar.xz mullvadvpn-5ba4fb290a2cb737147be093c82198500f3beaeb.zip | |
Merge branch 'macos-filter-aaaa-responses'
| -rw-r--r-- | CHANGELOG.md | 3 | ||||
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | talpid-core/src/resolver.rs | 64 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 10 |
4 files changed, 72 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 40b080002e..4c1da73931 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ Line wrap the file at 100 chars. Th - Fix version being labeled unsupported unexpectedly. So far, this only an issue when using development builds. +#### macOS +- Fix apps attempting to use IPv6 with in-tunnel IPv6 disabled. + ## [2025.9-beta1] - 2025-08-25 ### Added @@ -166,7 +166,11 @@ See [this](Release.md) for instructions on how to make a new release. * `netsh`: use the `netsh` program * `tcpip`: set TCP/IP parameters in the registry -- `TALPID_DISABLE_LOCAL_DNS_RESOLVER` - Set this variable to `1` to disable the local DNS resolver (macOS only). +* `TALPID_DISABLE_LOCAL_DNS_RESOLVER` - Set this variable to `1` to disable the local DNS resolver + (macOS only). + +* `TALPID_NEVER_FILTER_AAAA_QUERIES` - Set this variable to `1` to never ignore DNS AAAA queries + (macOS only). * `TALPID_FORCE_USERSPACE_WIREGUARD` - Forces the daemon to use the userspace implementation of WireGuard on Linux. diff --git a/talpid-core/src/resolver.rs b/talpid-core/src/resolver.rs index 2fb61fb35d..1510d805af 100644 --- a/talpid-core/src/resolver.rs +++ b/talpid-core/src/resolver.rs @@ -66,6 +66,20 @@ pub static LOCAL_DNS_RESOLVER: LazyLock<bool> = LazyLock::new(|| { !disable_local_dns_resolver }); +/// Override the `filter_out_aaaa` flag, which prevents getaddrinfo from returning IPv6 addresses. +/// See [ResolverHandle::enable_forward] for more details. +static NEVER_FILTER_AAAA_QUERIES: LazyLock<bool> = LazyLock::new(|| { + let never_filter_aaaa_queries = std::env::var("TALPID_NEVER_FILTER_AAAA_QUERIES") + .map(|v| v != "0") + // Disable this functionality + .unwrap_or(false); + + if never_filter_aaaa_queries { + log::debug!("Disabling filtering of AAAA queries"); + } + never_filter_aaaa_queries +}); + // Name of the loopback network device. const LOOPBACK: &str = "lo0"; @@ -173,6 +187,8 @@ enum Config { Forwarding { /// Remote DNS server to use dns_servers: Vec<IpAddr>, + /// Whether to give an empty response to AAAA queries + filter_out_aaaa: bool, }, } @@ -181,7 +197,10 @@ enum Resolver { Blocking, /// Forward DNS queries to a configured server - Forwarding(TokioAsyncResolver), + Forwarding { + resolver: TokioAsyncResolver, + filter_out_aaaa: bool, + }, } impl Resolver { @@ -194,10 +213,14 @@ impl Resolver { Resolver::Blocking => { let _ = tx.send(Self::resolve_blocked(query)); } - Resolver::Forwarding(resolver) => { + Resolver::Forwarding { + resolver, + filter_out_aaaa, + } => { let resolver = resolver.clone(); + let filter_out_aaaa = *filter_out_aaaa && !*NEVER_FILTER_AAAA_QUERIES; tokio::spawn(async move { - let lookup = Self::resolve_forward(resolver, query); + let lookup = Self::resolve_forward(resolver, query, filter_out_aaaa); let _ = tx.send(lookup.await); }); } @@ -243,9 +266,15 @@ impl Resolver { async fn resolve_forward( resolver: TokioAsyncResolver, query: LowerQuery, + filter_out_aaaa: bool, ) -> std::result::Result<Box<dyn LookupObject>, ResolveError> { let return_query = query.original().clone(); + if filter_out_aaaa && query.query_type() == RecordType::AAAA { + log::trace!("Giving empty response to AAAA query"); + return Ok(Box::new(EmptyLookup) as Box<_>); + } + let lookup = resolver .lookup(return_query.name().clone(), return_query.query_type()) .await; @@ -274,10 +303,21 @@ impl ResolverHandle { } /// Set the DNS server to forward queries to `dns_servers` - pub async fn enable_forward(&self, dns_servers: Vec<IpAddr>) { + /// + /// # Arguments + /// + /// `filter_out_aaaa`: This causes the resolver to always return empty responses for AAAA (IPv6) + /// queries. This is useful on macOS when the primary interface has IPv6 + /// connectivity, but the VPN tunnel does not. When this is true, and the VPN + /// tunnel lacks IPv6 connectivity, programs like Firefox will resolve IPv6 + /// addresses and may attempt to connect to them anyway (but fail). + pub async fn enable_forward(&self, dns_servers: Vec<IpAddr>, filter_out_aaaa: bool) { let (response_tx, response_rx) = oneshot::channel(); let _ = self.tx.unbounded_send(ResolverMessage::SetConfig { - new_config: Config::Forwarding { dns_servers }, + new_config: Config::Forwarding { + dns_servers, + filter_out_aaaa, + }, response_tx, }); @@ -523,10 +563,13 @@ impl LocalResolver { fn update_config(&mut self, config: Config) { match config { Config::Blocking => self.blocking(), - Config::Forwarding { mut dns_servers } => { + Config::Forwarding { + mut dns_servers, + filter_out_aaaa, + } => { // make sure not to accidentally forward queries to ourselves dns_servers.retain(|addr| *addr != self.bound_to.ip()); - self.forwarding(dns_servers); + self.forwarding(dns_servers, filter_out_aaaa); } } } @@ -537,7 +580,7 @@ impl LocalResolver { } /// Turn into a forwarding resolver (forward DNS queries to [dns_servers]). - fn forwarding(&mut self, dns_servers: Vec<IpAddr>) { + fn forwarding(&mut self, dns_servers: Vec<IpAddr>, filter_out_aaaa: bool) { let forward_server_config = NameServerConfigGroup::from_ips_clear(&dns_servers, DNS_PORT, true); @@ -546,7 +589,10 @@ impl LocalResolver { let resolver = TokioAsyncResolver::tokio(forward_config, resolver_opts); - self.inner_resolver = Resolver::Forwarding(resolver); + self.inner_resolver = Resolver::Forwarding { + resolver, + filter_out_aaaa, + }; } } diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 12767d7d65..7f23f3622f 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -184,12 +184,20 @@ impl ConnectedState { .map_err(BoxedError::new)?; } else { log::debug!("Enabling local DNS resolver"); + + // HACK: Ignore AAAA queries when in-tunnel IPv6 is disabled. See documentation in + // `resolver.rs`. + #[cfg(target_os = "macos")] + let filter_out_aaaa = self.metadata.ips.iter().all(|addr| addr.is_ipv4()); + #[cfg(not(target_os = "macos"))] + let filter_out_aaaa = false; + // Tell local DNS resolver to start forwarding DNS queries to whatever `dns_config` // specifies as DNS. shared_values.runtime.block_on( shared_values .filtering_resolver - .enable_forward(dns_config.addresses().collect()), + .enable_forward(dns_config.addresses().collect(), filter_out_aaaa), ); } |
