summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-09-03 14:34:42 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-09-03 14:34:42 +0200
commit5ba4fb290a2cb737147be093c82198500f3beaeb (patch)
treeb2403b9d3ccfb299d06b42b6e9279b925732dee0
parent89c12d3d3a01c03ecc4c4e85df18b4bd09795112 (diff)
parentdee999c8b0512a332194d681c9ce74d1b966d72d (diff)
downloadmullvadvpn-5ba4fb290a2cb737147be093c82198500f3beaeb.tar.xz
mullvadvpn-5ba4fb290a2cb737147be093c82198500f3beaeb.zip
Merge branch 'macos-filter-aaaa-responses'
-rw-r--r--CHANGELOG.md3
-rw-r--r--README.md6
-rw-r--r--talpid-core/src/resolver.rs64
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs10
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
diff --git a/README.md b/README.md
index dc72801818..a9c739601e 100644
--- a/README.md
+++ b/README.md
@@ -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),
);
}