From 65f85c1edbf2ebd48ece8420e7ac396064e063d2 Mon Sep 17 00:00:00 2001 From: Ryan Fowler Date: Sat, 8 Aug 2026 11:21:12 +0000 Subject: [PATCH] fix(dns): deduplicate platform addresses Reuse one first-seen-order helper for DNS inspection and timing output so repeated resolver addresses are not displayed twice. --- src/dns/inspect.rs | 26 ++++++++++++++++++++++++++ src/dns/inspect/rdata.rs | 2 +- src/dns/mod.rs | 12 ++++++++++++ src/http/client.rs | 16 +++------------- src/http/transport/client.rs | 12 +----------- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/dns/inspect.rs b/src/dns/inspect.rs index c8748435..13151c24 100644 --- a/src/dns/inspect.rs +++ b/src/dns/inspect.rs @@ -1644,6 +1644,32 @@ mod tests { assert!(!records[1].has_ttl); } + #[test] + fn test_platform_records_deduplicate_addresses_in_first_seen_order() { + let records = records_from_ip_addrs([ + "2001:db8::2".parse().unwrap(), + "192.0.2.2".parse().unwrap(), + "2001:db8::1".parse().unwrap(), + "192.0.2.2".parse().unwrap(), + "192.0.2.1".parse().unwrap(), + "2001:db8::2".parse().unwrap(), + ]); + + let actual: Vec<_> = records + .iter() + .map(|record| (record.typ.as_str(), record.value.as_str())) + .collect(); + assert_eq!( + actual, + [ + ("AAAA", "2001:db8::2"), + ("A", "192.0.2.2"), + ("AAAA", "2001:db8::1"), + ("A", "192.0.2.1"), + ] + ); + } + #[test] fn test_render_shows_unavailable_ttl_per_record() { let out = render(&Inspection { diff --git a/src/dns/inspect/rdata.rs b/src/dns/inspect/rdata.rs index 1a02f5f4..5f37b4b8 100644 --- a/src/dns/inspect/rdata.rs +++ b/src/dns/inspect/rdata.rs @@ -18,7 +18,7 @@ const DNS_TYPE_HTTPS: u16 = wire::TYPE_HTTPS; const DNS_TYPE_CAA: u16 = wire::TYPE_CAA; pub(super) fn records_from_ip_addrs(addrs: impl IntoIterator) -> Vec { - addrs + crate::dns::ordered_unique_ip_addrs(addrs) .into_iter() .map(|ip| { let typ = if ip.is_ipv4() { "A" } else { "AAAA" }; diff --git a/src/dns/mod.rs b/src/dns/mod.rs index ca8d79a8..0d8c87c4 100644 --- a/src/dns/mod.rs +++ b/src/dns/mod.rs @@ -1,3 +1,5 @@ +use std::net::IpAddr; + pub(crate) mod custom; pub mod doh; pub(crate) mod error; @@ -7,3 +9,13 @@ pub(crate) mod svcb; pub(crate) mod transport; pub(crate) mod util; pub(crate) mod wire; + +pub(crate) fn ordered_unique_ip_addrs(addrs: impl IntoIterator) -> Vec { + let mut unique = Vec::new(); + for addr in addrs { + if !unique.contains(&addr) { + unique.push(addr); + } + } + unique +} diff --git a/src/http/client.rs b/src/http/client.rs index a3360722..691ac44f 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -363,7 +363,7 @@ async fn resolve_dns_for_client_inner( ) .await?; let https_records = https_lookup.records; - let timing_addrs = dns_timing_addrs(addrs.iter().copied()); + let timing_addrs = crate::dns::ordered_unique_ip_addrs(addrs.iter().copied()); let socket_addrs = custom::socket_addrs_for_override(&addrs); let auto_http3_config = auto_http3_config_for_records( &https_records, @@ -459,7 +459,7 @@ async fn resolve_dns_for_client_inner( .await?; let effective_host = https_records.fallback_target; let https_records = https_records.records; - let addrs = dns_timing_addrs(socket_addrs.iter().map(|addr| addr.ip())); + let addrs = crate::dns::ordered_unique_ip_addrs(socket_addrs.iter().map(|addr| addr.ip())); let auto_http3_config = auto_http3_config_for_records(&https_records, &effective_host, &socket_addrs); if auto_http3 && https_lookup_succeeded { @@ -702,16 +702,6 @@ fn auto_http3_optional_lookup_timeout_for_remaining( } } -fn dns_timing_addrs(addrs: impl IntoIterator) -> Vec { - let mut unique = Vec::new(); - for addr in addrs { - if !unique.contains(&addr) { - unique.push(addr); - } - } - unique -} - fn configure_dns_resolution( builder: ClientBuilder, host: Option<&str>, @@ -1246,7 +1236,7 @@ mod tests { "::2".parse().unwrap(), ]; - let display_addrs = dns_timing_addrs(addrs); + let display_addrs = crate::dns::ordered_unique_ip_addrs(addrs); assert_eq!( display_addrs, diff --git a/src/http/transport/client.rs b/src/http/transport/client.rs index f8d702f4..86a7e6de 100644 --- a/src/http/transport/client.rs +++ b/src/http/transport/client.rs @@ -760,22 +760,12 @@ pub(super) fn record_dns_addrs_trace( socket_addrs: addrs.to_vec(), timing: Some(DnsTiming { host: host.to_string(), - addrs: dns_timing_addrs(addrs.iter().map(|addr| addr.ip())), + addrs: crate::dns::ordered_unique_ip_addrs(addrs.iter().map(|addr| addr.ip())), duration, }), }); } -fn dns_timing_addrs(addrs: impl IntoIterator) -> Vec { - let mut unique = Vec::new(); - for addr in addrs { - if !unique.contains(&addr) { - unique.push(addr); - } - } - unique -} - pub(super) fn alpn_for_config(config: &ClientConfig) -> Vec> { match config.mode { Some(HttpVersion::Http1) => vec![b"http/1.1".to_vec()],