Skip to content

Commit f36b5de

Browse files
committed
fix(iroh-dns): drop link-local and unspecified system nameservers
## Description Android tethered through an iPhone publishes link-local nameservers in `LinkProperties.getDnsServers()`. Without a scope ID those are not routable from a connected UDP socket and every query times out before falling back. Generalize the existing `WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS` filter into `is_usable_nameserver`, which also drops link-local IPv4 (`169.254.0.0/16`), link-local IPv6 (`fe80::/10`), and the unspecified addresses. The filter applies on every platform. ## Breaking Changes None. ## Notes & open questions This is a reachability filter, not a trust boundary. Authenticated DNS still requires DNS-over-HTTPS or DNSSEC on top. ## Change checklist - [x] Self-review. - [x] Tests if relevant. - [x] All breaking changes documented.
1 parent 70ea715 commit f36b5de

1 file changed

Lines changed: 66 additions & 1 deletion

File tree

iroh-dns/src/dns.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ impl HickoryResolver {
661661
config.add_search(name.clone());
662662
}
663663
for nameserver_cfg in system_config.name_servers() {
664-
if !WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS.contains(&nameserver_cfg.ip) {
664+
if is_usable_nameserver(nameserver_cfg) {
665665
config.add_name_server(nameserver_cfg.clone());
666666
}
667667
}
@@ -789,6 +789,21 @@ const WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS: [IpAddr; 3] = [
789789
IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 3)),
790790
];
791791

792+
/// Returns whether `ns` can plausibly be queried from a connected UDP socket.
793+
///
794+
/// Drops the deprecated Windows IPv6 site-local anycast servers, link-local
795+
/// IPv6 (`fe80::/10`), link-local IPv4 (`169.254.0.0/16`), and the unspecified
796+
/// addresses.
797+
fn is_usable_nameserver(ns: &hickory_resolver::config::NameServerConfig) -> bool {
798+
if WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS.contains(&ns.ip) {
799+
return false;
800+
}
801+
match ns.ip {
802+
IpAddr::V4(ip) => ip != Ipv4Addr::UNSPECIFIED && !ip.is_link_local(),
803+
IpAddr::V6(ip) => ip != Ipv6Addr::UNSPECIFIED && (ip.segments()[0] & 0xffc0) != 0xfe80,
804+
}
805+
}
806+
792807
/// Helper enum to give a unified type to the iterators of [`DnsResolver::lookup_ipv4_ipv6`].
793808
enum LookupIter<A, B> {
794809
Ipv4(A),
@@ -862,10 +877,60 @@ fn add_jitter(delay: &u64) -> Duration {
862877
pub(crate) mod tests {
863878
use std::sync::atomic::AtomicUsize;
864879

880+
use hickory_resolver::config::{ConnectionConfig, NameServerConfig};
865881
use n0_tracing_test::traced_test;
866882

867883
use super::*;
868884

885+
fn ns(ip: &str) -> NameServerConfig {
886+
NameServerConfig::new(ip.parse().unwrap(), false, vec![ConnectionConfig::udp()])
887+
}
888+
889+
#[test]
890+
fn is_usable_drops_link_local_v6() {
891+
// Bounds of `fe80::/10`.
892+
assert!(!is_usable_nameserver(&ns("fe80::1")));
893+
assert!(!is_usable_nameserver(&ns(
894+
"febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
895+
)));
896+
}
897+
898+
#[test]
899+
fn is_usable_drops_link_local_v4() {
900+
assert!(!is_usable_nameserver(&ns("169.254.0.0")));
901+
assert!(!is_usable_nameserver(&ns("169.254.255.255")));
902+
}
903+
904+
#[test]
905+
fn is_usable_drops_unspecified() {
906+
assert!(!is_usable_nameserver(&ns("0.0.0.0")));
907+
assert!(!is_usable_nameserver(&ns("::")));
908+
}
909+
910+
#[test]
911+
fn is_usable_drops_windows_site_local_anycast() {
912+
for ip in WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS {
913+
assert!(!is_usable_nameserver(&NameServerConfig::new(
914+
ip,
915+
false,
916+
vec![ConnectionConfig::udp()]
917+
)));
918+
}
919+
}
920+
921+
#[test]
922+
fn is_usable_keeps_global_unicast() {
923+
assert!(is_usable_nameserver(&ns("8.8.8.8")));
924+
assert!(is_usable_nameserver(&ns("1.1.1.1")));
925+
assert!(is_usable_nameserver(&ns("2001:4860:4860::8888")));
926+
// ULA, valid for routed networks.
927+
assert!(is_usable_nameserver(&ns("fd00::1")));
928+
// Just outside `fe80::/10`, still global.
929+
assert!(is_usable_nameserver(&ns(
930+
"fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
931+
)));
932+
}
933+
869934
#[tokio::test]
870935
#[traced_test]
871936
async fn stagger_basic() {

0 commit comments

Comments
 (0)