From c88b4d1bae76df2fd6b9af46706ed2db4051aee8 Mon Sep 17 00:00:00 2001 From: Kevin Green Date: Wed, 18 Mar 2026 15:08:00 -0400 Subject: [PATCH 1/4] Enable hickory-dns for reqwest --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c03a8d12..9c0a8045 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ hyper = { version = "1.2", default-features = false, optional = true } md-5 = { version = "0.10.6", default-features = false, optional = true } quick-xml = { version = "0.38.0", features = ["serialize", "overlapped-lists"], optional = true } rand = { version = "0.9", default-features = false, features = ["std", "std_rng", "thread_rng"], optional = true } -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"], optional = true } +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "hickory-dns"], optional = true } ring = { version = "0.17", default-features = false, features = ["std"], optional = true } rustls-pemfile = { version = "2.0", default-features = false, features = ["std"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } From a235a0a3a65698da3510758d70642c479922fb34 Mon Sep 17 00:00:00 2001 From: Kevin Green Date: Wed, 18 Mar 2026 15:46:18 -0400 Subject: [PATCH 2/4] Add hickory dns resolver to ShuffleResolver --- Cargo.toml | 4 ++- src/client/dns.rs | 89 +++++++++++++++++++++++++++++++++++++---------- src/client/mod.rs | 2 +- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c0a8045..eae05e80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,13 +48,14 @@ arc-swap = "1.7" # Cloud storage support base64 = { version = "0.22", default-features = false, features = ["std"], optional = true } form_urlencoded = { version = "1.2", optional = true } +hickory-resolver = { version = "0.25", optional = true } http-body-util = { version = "0.1.2", optional = true } httparse = { version = "1.8.0", default-features = false, features = ["std"], optional = true } hyper = { version = "1.2", default-features = false, optional = true } md-5 = { version = "0.10.6", default-features = false, optional = true } quick-xml = { version = "0.38.0", features = ["serialize", "overlapped-lists"], optional = true } rand = { version = "0.9", default-features = false, features = ["std", "std_rng", "thread_rng"], optional = true } -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "hickory-dns"], optional = true } +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"], optional = true } ring = { version = "0.17", default-features = false, features = ["std"], optional = true } rustls-pemfile = { version = "2.0", default-features = false, features = ["std"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } @@ -76,6 +77,7 @@ azure = ["cloud", "httparse"] fs = ["walkdir"] gcp = ["cloud", "rustls-pemfile"] aws = ["cloud", "md-5"] +hickory-dns = ["dep:hickory-resolver", "reqwest?/hickory-dns"] http = ["cloud"] tls-webpki-roots = ["reqwest?/rustls-tls-webpki-roots"] integration = ["rand"] diff --git a/src/client/dns.rs b/src/client/dns.rs index 32e9291b..d60e3dda 100644 --- a/src/client/dns.rs +++ b/src/client/dns.rs @@ -15,36 +15,89 @@ // specific language governing permissions and limitations // under the License. +#[cfg(not(feature = "hickory-dns"))] use std::net::ToSocketAddrs; +#[cfg(feature = "hickory-dns")] +use std::sync::Arc; +#[cfg(feature = "hickory-dns")] +use hickory_resolver::{config::LookupIpStrategy, TokioResolver}; use rand::prelude::SliceRandom; use reqwest::dns::{Addrs, Name, Resolve, Resolving}; +#[cfg(feature = "hickory-dns")] +use tokio::sync::OnceCell; +#[cfg(not(feature = "hickory-dns"))] use tokio::task::JoinSet; type DynErr = Box; -#[derive(Debug)] -pub(crate) struct ShuffleResolver; +#[derive(Debug, Default)] +pub(crate) struct ShuffleResolver { + #[cfg(feature = "hickory-dns")] + hickory: Arc>, +} impl Resolve for ShuffleResolver { fn resolve(&self, name: Name) -> Resolving { + #[cfg(feature = "hickory-dns")] + { + return self.resolve_hickory(name); + } + + #[cfg(not(feature = "hickory-dns"))] + { + resolve_socket_addrs(name) + } + } +} + +#[cfg(feature = "hickory-dns")] +impl ShuffleResolver { + fn resolve_hickory(&self, name: Name) -> Resolving { + let resolver = Arc::clone(&self.hickory); + Box::pin(async move { - // use `JoinSet` to propagate cancelation - let mut tasks = JoinSet::new(); - tasks.spawn_blocking(move || { - let it = (name.as_str(), 0).to_socket_addrs()?; - let mut addrs = it.collect::>(); - - addrs.shuffle(&mut rand::rng()); - - Ok(Box::new(addrs.into_iter()) as Addrs) - }); - - tasks - .join_next() - .await - .expect("spawned on task") - .map_err(|err| Box::new(err) as DynErr)? + let resolver = resolver + .get_or_try_init(|| async { + let mut builder = TokioResolver::builder_tokio() + .map_err(|err| -> DynErr { Box::new(err) })?; + builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6; + Ok::(builder.build()) + }) + .await?; + + let mut addrs = resolver + .lookup_ip(name.as_str()) + .await? + .into_iter() + .map(|ip_addr| std::net::SocketAddr::new(ip_addr, 0)) + .collect::>(); + + addrs.shuffle(&mut rand::rng()); + + Ok(Box::new(addrs.into_iter()) as Addrs) }) } } + +#[cfg(not(feature = "hickory-dns"))] +fn resolve_socket_addrs(name: Name) -> Resolving { + Box::pin(async move { + // use `JoinSet` to propagate cancelation + let mut tasks = JoinSet::new(); + tasks.spawn_blocking(move || { + let it = (name.as_str(), 0).to_socket_addrs()?; + let mut addrs = it.collect::>(); + + addrs.shuffle(&mut rand::rng()); + + Ok(Box::new(addrs.into_iter()) as Addrs) + }); + + tasks + .join_next() + .await + .expect("spawned on task") + .map_err(|err| Box::new(err) as DynErr)? + }) +} diff --git a/src/client/mod.rs b/src/client/mod.rs index a71814b9..2f91d788 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -706,7 +706,7 @@ impl ClientOptions { builder = builder.no_gzip().no_brotli().no_zstd().no_deflate(); if self.randomize_addresses.get()? { - builder = builder.dns_resolver(Arc::new(dns::ShuffleResolver)); + builder = builder.dns_resolver(Arc::new(dns::ShuffleResolver::default())); } builder From 9f6487a966eb782758684c39dd2f5134283b0330 Mon Sep 17 00:00:00 2001 From: Kevin Green Date: Wed, 18 Mar 2026 16:00:01 -0400 Subject: [PATCH 3/4] Fmt --- src/azure/client.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/azure/client.rs b/src/azure/client.rs index 0748ae84..78b0087b 100644 --- a/src/azure/client.rs +++ b/src/azure/client.rs @@ -1567,7 +1567,10 @@ Time:2018-06-14T16:46:54.6040685Z\r server.push_fn(move |req| { assert_eq!(req.method(), Method::PUT); assert_eq!(req.headers().get(IF_NONE_MATCH).unwrap(), "*"); - Response::builder().status(status).body(String::new()).unwrap() + Response::builder() + .status(status) + .body(String::new()) + .unwrap() }); let credential_provider = Arc::new(StaticCredentialProvider::new( From 86cf51caecb3f5c0b76ffaaffb30d3998896ab60 Mon Sep 17 00:00:00 2001 From: Kevin Green Date: Thu, 26 Mar 2026 15:57:19 -0400 Subject: [PATCH 4/4] Refactor hickory dns resolver into separate implementatino for easier patching --- src/client/dns.rs | 29 ++++++++++------------------- src/client/mod.rs | 10 +++++++++- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/client/dns.rs b/src/client/dns.rs index d60e3dda..e765e4fe 100644 --- a/src/client/dns.rs +++ b/src/client/dns.rs @@ -15,9 +15,7 @@ // specific language governing permissions and limitations // under the License. -#[cfg(not(feature = "hickory-dns"))] use std::net::ToSocketAddrs; -#[cfg(feature = "hickory-dns")] use std::sync::Arc; #[cfg(feature = "hickory-dns")] @@ -26,34 +24,28 @@ use rand::prelude::SliceRandom; use reqwest::dns::{Addrs, Name, Resolve, Resolving}; #[cfg(feature = "hickory-dns")] use tokio::sync::OnceCell; -#[cfg(not(feature = "hickory-dns"))] use tokio::task::JoinSet; type DynErr = Box; #[derive(Debug, Default)] -pub(crate) struct ShuffleResolver { - #[cfg(feature = "hickory-dns")] - hickory: Arc>, -} +pub(crate) struct ShuffleResolver; impl Resolve for ShuffleResolver { fn resolve(&self, name: Name) -> Resolving { - #[cfg(feature = "hickory-dns")] - { - return self.resolve_hickory(name); - } - - #[cfg(not(feature = "hickory-dns"))] - { - resolve_socket_addrs(name) - } + resolve_socket_addrs(name) } } #[cfg(feature = "hickory-dns")] -impl ShuffleResolver { - fn resolve_hickory(&self, name: Name) -> Resolving { +#[derive(Debug, Default)] +pub(crate) struct HickoryShuffleResolver { + hickory: Arc>, +} + +#[cfg(feature = "hickory-dns")] +impl Resolve for HickoryShuffleResolver { + fn resolve(&self, name: Name) -> Resolving { let resolver = Arc::clone(&self.hickory); Box::pin(async move { @@ -80,7 +72,6 @@ impl ShuffleResolver { } } -#[cfg(not(feature = "hickory-dns"))] fn resolve_socket_addrs(name: Name) -> Resolving { Box::pin(async move { // use `JoinSet` to propagate cancelation diff --git a/src/client/mod.rs b/src/client/mod.rs index 2f91d788..0da754f9 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -706,7 +706,15 @@ impl ClientOptions { builder = builder.no_gzip().no_brotli().no_zstd().no_deflate(); if self.randomize_addresses.get()? { - builder = builder.dns_resolver(Arc::new(dns::ShuffleResolver::default())); + #[cfg(feature = "hickory-dns")] + { + builder = builder.dns_resolver(Arc::new(dns::HickoryShuffleResolver::default())); + } + + #[cfg(not(feature = "hickory-dns"))] + { + builder = builder.dns_resolver(Arc::new(dns::ShuffleResolver::default())); + } } builder