From 60ab7216260a30529471cdc3d26f44401131c0b4 Mon Sep 17 00:00:00 2001 From: iHsin Date: Tue, 16 Jun 2026 03:36:01 +0800 Subject: [PATCH] fix: always append IPv6 bootstrap DNS servers Commit #24 added IPv6 bootstrap nameservers for IPv6-only uplinks, but gated them behind `if config.dns.default_nameserver.is_empty()`. When a profile supplies its own `default-nameserver` (typically IPv4-only), the else branch cloned that list verbatim and the IPv6 fallback was dropped. On an IPv6-only uplink (e.g. 464XLAT cellular, interface has no IPv4 address) the IPv4 bootstrap servers are unreachable, so every bootstrap query times out, the DoT main nameservers cannot be resolved, and the resolver ends up with zero usable clients -> "no DNS clients available for query" on every connection. Always append the IPv6 bootstrap servers (with a dedup check) regardless of whether the list came from defaults or the profile, so resolution can still proceed on IPv6-only networks. Also factor the repeated NameServer literals into a small udp_bootstrap closure. Co-Authored-By: Claude Opus 4.8 (1M context) --- uniffi/clash-android-ffi/src/lib.rs | 85 ++++++++++------------------- 1 file changed, 30 insertions(+), 55 deletions(-) diff --git a/uniffi/clash-android-ffi/src/lib.rs b/uniffi/clash-android-ffi/src/lib.rs index eebb1fa..96cb6a8 100644 --- a/uniffi/clash-android-ffi/src/lib.rs +++ b/uniffi/clash-android-ffi/src/lib.rs @@ -214,66 +214,41 @@ async fn run_clash( }; config.general.ipv6 = over.ipv6; - let default_nameserver = if config.dns.default_nameserver.is_empty() { - // Bootstrap nameservers. These are built with no parent resolver, so the - // host must be a literal IP (a `Host::Domain` would hit the resolve path - // and fail). The IPv6 servers let resolution work on IPv6-only uplinks - // (e.g. 464XLAT cellular), where the interface has no IPv4 address and the - // IPv4 servers are unreachable. + // Bootstrap nameservers. These are built with no parent resolver, so the + // host must be a literal IP (a `Host::Domain` would hit the resolve path + // and fail). + let udp_bootstrap = |host: Host| NameServer { + net: DNSNetMode::Udp, + host, + port: 53, + interface: None, + proxy: None, + }; + let mut default_nameserver = if config.dns.default_nameserver.is_empty() { vec![ - NameServer { - net: DNSNetMode::Udp, - host: Host::Ipv4(Ipv4Addr::new(223, 5, 5, 5)), // AliDNS - port: 53, - interface: None, - proxy: None, - }, - NameServer { - net: DNSNetMode::Udp, - host: Host::Ipv4(Ipv4Addr::new(223, 6, 6, 6)), // AliDNS - port: 53, - interface: None, - proxy: None, - }, - NameServer { - net: DNSNetMode::Udp, - host: Host::Ipv4(Ipv4Addr::new(8, 8, 8, 8)), // Google - port: 53, - interface: None, - proxy: None, - }, - NameServer { - net: DNSNetMode::Udp, - // AliDNS public IPv6 - host: Host::Ipv6(Ipv6Addr::new(0x2400, 0x3200, 0, 0, 0, 0, 0, 1)), - port: 53, - interface: None, - proxy: None, - }, - NameServer { - net: DNSNetMode::Udp, - // AliDNS public IPv6 - host: Host::Ipv6(Ipv6Addr::new( - 0x2400, 0x3200, 0xbaba, 0, 0, 0, 0, 1, - )), - port: 53, - interface: None, - proxy: None, - }, - NameServer { - net: DNSNetMode::Udp, - // Google public IPv6 - host: Host::Ipv6(Ipv6Addr::new( - 0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888, - )), - port: 53, - interface: None, - proxy: None, - }, + udp_bootstrap(Host::Ipv4(Ipv4Addr::new(223, 5, 5, 5))), // AliDNS + udp_bootstrap(Host::Ipv4(Ipv4Addr::new(223, 6, 6, 6))), // AliDNS + udp_bootstrap(Host::Ipv4(Ipv4Addr::new(8, 8, 8, 8))), // Google ] } else { config.dns.default_nameserver.clone() }; + // Always append IPv6 bootstrap servers so resolution works on IPv6-only + // uplinks (e.g. 464XLAT cellular), where the interface has no IPv4 address + // and the IPv4 servers are unreachable. We append unconditionally — even + // when the profile supplies its own `default-nameserver` — because such + // profiles are almost always IPv4-only and would otherwise have no usable + // bootstrap on an IPv6-only network. + let ipv6_bootstrap = [ + Host::Ipv6(Ipv6Addr::new(0x2400, 0x3200, 0, 0, 0, 0, 0, 1)), // AliDNS + Host::Ipv6(Ipv6Addr::new(0x2400, 0x3200, 0xbaba, 0, 0, 0, 0, 1)), // AliDNS + Host::Ipv6(Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888)), // Google + ]; + for host in ipv6_bootstrap { + if !default_nameserver.iter().any(|ns| ns.host == host) { + default_nameserver.push(udp_bootstrap(host)); + } + } let nameserver = if config.dns.nameserver.is_empty() { vec![ NameServer {