From 4327e2945aa791e9f0b9126d99a66a611e91fb23 Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 8 Jan 2025 12:02:54 -0700 Subject: [PATCH 1/9] DNS-over-X for internal domain name (i.e. API client) lookups --- Cargo.toml | 1 + common/http-api-client/Cargo.toml | 5 + common/http-api-client/src/dns.rs | 153 ++++++++++++++++++++++++++++++ common/http-api-client/src/lib.rs | 12 ++- 4 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 common/http-api-client/src/dns.rs diff --git a/Cargo.toml b/Cargo.toml index dc20a58404c..52c25f0e525 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -255,6 +255,7 @@ handlebars = "3.5.5" headers = "0.4.0" hex = "0.4.3" hex-literal = "0.3.3" +hickory-resolver = "0.24.2" hkdf = "0.12.3" hmac = "0.12.1" http = "1" diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index a0dab4b00a9..70ab9a7dd13 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -15,7 +15,9 @@ async-trait = { workspace = true } reqwest = { workspace = true, features = ["json"] } http.workspace = true url = { workspace = true } +once_cell = { workspace = true } serde = { workspace = true } +hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "dns-over-h3", "webpki-roots"] } serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } @@ -26,3 +28,6 @@ nym-bin-common = { path = "../bin-common" } [target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] workspace = true features = ["tokio"] + +[dev-dependencies] +tokio = { workspace = true, features=["rt", "macros"] } diff --git a/common/http-api-client/src/dns.rs b/common/http-api-client/src/dns.rs new file mode 100644 index 00000000000..3f005f2a5c8 --- /dev/null +++ b/common/http-api-client/src/dns.rs @@ -0,0 +1,153 @@ +//! DNS resolver configuration for internal lookups. +//! +//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints +//! supporting DoH, DoT, and for google DoH3 as well. +//! +//! This resolver implements a fallback mechanism where, should the DNS-over-TLS resolution fail, a +//! followup resolution will be done using the hosts configured default (e.g. `/etc/resolve.conf` on +//! linux). +//! +//! Requires the `dns-over-https-rustls`, `dns-over-h3`, `webpki-roots` feature for the +//! `hickory-resolver` crate + +use crate::ClientBuilder; + +use std::fmt; +use std::net::SocketAddr; +use std::sync::Arc; + +use hickory_resolver::{ + config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts}, + error::ResolveError, + lookup_ip::LookupIpIntoIter, + TokioAsyncResolver, +}; +use once_cell::sync::OnceCell; +use reqwest::dns::{Addrs, Name, Resolve, Resolving}; +use tracing::warn; + +impl ClientBuilder { + pub fn dns_resolver(mut self, resolver: Arc) -> Self { + self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver); + self + } +} + +struct SocketAddrs { + iter: LookupIpIntoIter, +} + +#[derive(Debug)] +struct HickoryDnsSystemConfError(ResolveError); + +/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait. +#[derive(Debug, Default, Clone)] +pub struct HickoryDnsResolver { + /// Since we might not have been called in the context of a + /// Tokio Runtime in initialization, so we must delay the actual + /// construction of the resolver. + state: Arc>, + fallback: Arc>, +} + +impl Resolve for HickoryDnsResolver { + fn resolve(&self, name: Name) -> Resolving { + let resolver = self.state.clone(); + let fallback = self.fallback.clone(); + Box::pin(async move { + let resolver = resolver.get_or_try_init(new_resolver)?; + + // try the primary DNS resolver that we set up (DoH or DoT or whatever) + let lookup = match resolver.lookup_ip(name.as_str()).await { + Ok(res) => res, + Err(e) => { + // on failure use the fall back system configured DNS resolver + warn!("primary DNS failed w/ error {e}: using system fallback"); + let resolver = fallback.get_or_try_init(new_resolver_system)?; + resolver.lookup_ip(name.as_str()).await? + } + }; + + let addrs: Addrs = Box::new(SocketAddrs { + iter: lookup.into_iter(), + }); + Ok(addrs) + }) + } +} + +impl Iterator for SocketAddrs { + type Item = SocketAddr; + + fn next(&mut self) -> Option { + self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0)) + } +} + +/// Create a new resolver with a custom DoT based configuration. The options are overridden to look +/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm. +fn new_resolver() -> Result { + let mut name_servers = NameServerConfigGroup::google_tls(); + name_servers.merge(NameServerConfigGroup::google_https()); + name_servers.merge(NameServerConfigGroup::google_h3()); + name_servers.merge(NameServerConfigGroup::quad9_tls()); + name_servers.merge(NameServerConfigGroup::quad9_https()); + name_servers.merge(NameServerConfigGroup::cloudflare_tls()); + name_servers.merge(NameServerConfigGroup::cloudflare_https()); + + let config = ResolverConfig::from_parts(None, Vec::new(), name_servers); + + let mut opts = ResolverOpts::default(); + opts.ip_strategy = LookupIpStrategy::Ipv4thenIpv6; + // Would like to enable this when 0.25 stabilizes + // opts.server_ordering_strategy = ServerOrderingStrategy::RoundRobin; + + Ok(TokioAsyncResolver::tokio(config, opts)) +} + +/// Create a new resolver with the default configuration, which reads from `/etc/resolve.conf`. The +/// options are overridden to look up for both IPv4 and IPv6 addresses to work with "happy eyeballs" +/// algorithm. +fn new_resolver_system() -> Result { + let (config, mut opts) = + hickory_resolver::system_conf::read_system_conf().map_err(HickoryDnsSystemConfError)?; + opts.ip_strategy = LookupIpStrategy::Ipv4thenIpv6; + Ok(TokioAsyncResolver::tokio(config, opts)) +} + +impl fmt::Display for HickoryDnsSystemConfError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("error reading DNS system conf for hickory-dns") + } +} + +impl std::error::Error for HickoryDnsSystemConfError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.0) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[tokio::test] + async fn reqwest_hickory_doh() { + let resolver = HickoryDnsResolver::default(); + let client = reqwest::ClientBuilder::new() + .dns_resolver(resolver.into()) + .build() + .unwrap(); + + let resp = client + .get("http://ifconfig.me") + .send() + .await + .unwrap() + .bytes() + .await + .unwrap(); + + assert!(!resp.is_empty()); + } +} diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 1792a85a224..6703f038ff7 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -3,20 +3,21 @@ use async_trait::async_trait; use reqwest::header::HeaderValue; +pub use reqwest::IntoUrl; use reqwest::{RequestBuilder, Response, StatusCode}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use std::fmt::Display; -use std::time::Duration; use thiserror::Error; use tracing::{instrument, warn}; use url::Url; -pub use reqwest::IntoUrl; +use std::{fmt::Display, sync::Arc, time::Duration}; +mod user_agent; pub use user_agent::UserAgent; -mod user_agent; +mod dns; +use dns::HickoryDnsResolver; // The timeout is relatively high as we are often making requests over the mixnet, where latency is // high and chatty protocols take a while to complete. @@ -90,7 +91,8 @@ impl ClientBuilder { url: url.into_url()?, timeout: None, custom_user_agent: false, - reqwest_client_builder: reqwest::ClientBuilder::new(), + reqwest_client_builder: reqwest::ClientBuilder::new() + .dns_resolver(Arc::new(HickoryDnsResolver::default())), }) } } From f594bfc9abc223c22f517d49937cd6de55a0f069 Mon Sep 17 00:00:00 2001 From: jmwample Date: Thu, 16 Jan 2025 11:06:25 -0700 Subject: [PATCH 2/9] remove h3 because it causes an error --- common/http-api-client/Cargo.toml | 2 +- common/http-api-client/src/dns.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index 70ab9a7dd13..fd7380bbfa1 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -17,7 +17,7 @@ http.workspace = true url = { workspace = true } once_cell = { workspace = true } serde = { workspace = true } -hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "dns-over-h3", "webpki-roots"] } +hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] } serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } diff --git a/common/http-api-client/src/dns.rs b/common/http-api-client/src/dns.rs index 3f005f2a5c8..3b76fd25022 100644 --- a/common/http-api-client/src/dns.rs +++ b/common/http-api-client/src/dns.rs @@ -1,13 +1,13 @@ //! DNS resolver configuration for internal lookups. //! //! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints -//! supporting DoH, DoT, and for google DoH3 as well. +//! supporting DoH and DoT. //! //! This resolver implements a fallback mechanism where, should the DNS-over-TLS resolution fail, a //! followup resolution will be done using the hosts configured default (e.g. `/etc/resolve.conf` on //! linux). //! -//! Requires the `dns-over-https-rustls`, `dns-over-h3`, `webpki-roots` feature for the +//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the //! `hickory-resolver` crate use crate::ClientBuilder; @@ -89,7 +89,7 @@ impl Iterator for SocketAddrs { fn new_resolver() -> Result { let mut name_servers = NameServerConfigGroup::google_tls(); name_servers.merge(NameServerConfigGroup::google_https()); - name_servers.merge(NameServerConfigGroup::google_h3()); + // name_servers.merge(NameServerConfigGroup::google_h3()); name_servers.merge(NameServerConfigGroup::quad9_tls()); name_servers.merge(NameServerConfigGroup::quad9_https()); name_servers.merge(NameServerConfigGroup::cloudflare_tls()); From 93140a1aa74cfca488db459ba981419ca86872b9 Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 22 Jan 2025 11:40:09 -0700 Subject: [PATCH 3/9] minor fixes for clarity, interface access, and wasm exclusion --- common/http-api-client/src/dns.rs | 20 +++++++++++++------- common/http-api-client/src/lib.rs | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/common/http-api-client/src/dns.rs b/common/http-api-client/src/dns.rs index 3b76fd25022..d3a6c25a258 100644 --- a/common/http-api-client/src/dns.rs +++ b/common/http-api-client/src/dns.rs @@ -9,7 +9,6 @@ //! //! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the //! `hickory-resolver` crate - use crate::ClientBuilder; use std::fmt; @@ -52,19 +51,26 @@ pub struct HickoryDnsResolver { impl Resolve for HickoryDnsResolver { fn resolve(&self, name: Name) -> Resolving { + self.resolve_str(name.as_str()) + } +} + +impl HickoryDnsResolver { + pub fn resolve_str(&self, name: &str) -> Resolving { let resolver = self.state.clone(); let fallback = self.fallback.clone(); + let domain = name.to_owned(); Box::pin(async move { let resolver = resolver.get_or_try_init(new_resolver)?; // try the primary DNS resolver that we set up (DoH or DoT or whatever) - let lookup = match resolver.lookup_ip(name.as_str()).await { + let lookup = match resolver.lookup_ip(&domain).await { Ok(res) => res, Err(e) => { // on failure use the fall back system configured DNS resolver warn!("primary DNS failed w/ error {e}: using system fallback"); let resolver = fallback.get_or_try_init(new_resolver_system)?; - resolver.lookup_ip(name.as_str()).await? + resolver.lookup_ip(&domain).await? } }; @@ -105,13 +111,13 @@ fn new_resolver() -> Result { Ok(TokioAsyncResolver::tokio(config, opts)) } -/// Create a new resolver with the default configuration, which reads from `/etc/resolve.conf`. The -/// options are overridden to look up for both IPv4 and IPv6 addresses to work with "happy eyeballs" -/// algorithm. +/// Create a new resolver with the default configuration, which reads from the system DNS config +/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6 +/// addresses to work with "happy eyeballs" algorithm. fn new_resolver_system() -> Result { let (config, mut opts) = hickory_resolver::system_conf::read_system_conf().map_err(HickoryDnsSystemConfError)?; - opts.ip_strategy = LookupIpStrategy::Ipv4thenIpv6; + opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6; Ok(TokioAsyncResolver::tokio(config, opts)) } diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 6703f038ff7..9805988ab8b 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -3,7 +3,6 @@ use async_trait::async_trait; use reqwest::header::HeaderValue; -pub use reqwest::IntoUrl; use reqwest::{RequestBuilder, Response, StatusCode}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -13,11 +12,15 @@ use url::Url; use std::{fmt::Display, sync::Arc, time::Duration}; +pub use reqwest::IntoUrl; + mod user_agent; pub use user_agent::UserAgent; +#[cfg(not(target_arch = "wasm32"))] mod dns; -use dns::HickoryDnsResolver; +#[cfg(not(target_arch = "wasm32"))] +pub use dns::HickoryDnsResolver; // The timeout is relatively high as we are often making requests over the mixnet, where latency is // high and chatty protocols take a while to complete. @@ -87,12 +90,18 @@ impl ClientBuilder { // TODO: or should we maybe default to https? Self::new(alt) } else { + #[cfg(target_arch = "wasm32")] + let reqwest_client_builder = reqwest::ClientBuilder::new(); + + #[cfg(not(target_arch = "wasm32"))] + let reqwest_client_builder = + reqwest::ClientBuilder::new().dns_resolver(Arc::new(HickoryDnsResolver::default())); + Ok(ClientBuilder { url: url.into_url()?, timeout: None, custom_user_agent: false, - reqwest_client_builder: reqwest::ClientBuilder::new() - .dns_resolver(Arc::new(HickoryDnsResolver::default())), + reqwest_client_builder, }) } } From bf88b34898a423f5eb835804b940da6a7729052e Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 22 Jan 2025 12:08:18 -0700 Subject: [PATCH 4/9] fix wasm compile (exclude wasm target from DoH / DoT) --- common/http-api-client/Cargo.toml | 4 +++- common/http-api-client/src/lib.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index fd7380bbfa1..a70c8d9259b 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -17,13 +17,15 @@ http.workspace = true url = { workspace = true } once_cell = { workspace = true } serde = { workspace = true } -hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] } serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } nym-bin-common = { path = "../bin-common" } +[target."cfg(not(target_arch = \"wasm32\"))".dependencies] +hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] } + # for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed [target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer] workspace = true diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 9805988ab8b..d1243f82641 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -10,7 +10,9 @@ use thiserror::Error; use tracing::{instrument, warn}; use url::Url; -use std::{fmt::Display, sync::Arc, time::Duration}; +#[cfg(not(target_arch = "wasm32"))] +use std:: sync::Arc; +use std::{fmt::Display, time::Duration}; pub use reqwest::IntoUrl; From 35b43d5b206870a21db1dd26a003c54c3f611077 Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 22 Jan 2025 12:14:08 -0700 Subject: [PATCH 5/9] missed Lookup strategy --- common/http-api-client/src/dns.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/http-api-client/src/dns.rs b/common/http-api-client/src/dns.rs index d3a6c25a258..3052f8eb0e3 100644 --- a/common/http-api-client/src/dns.rs +++ b/common/http-api-client/src/dns.rs @@ -104,7 +104,7 @@ fn new_resolver() -> Result { let config = ResolverConfig::from_parts(None, Vec::new(), name_servers); let mut opts = ResolverOpts::default(); - opts.ip_strategy = LookupIpStrategy::Ipv4thenIpv6; + opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6; // Would like to enable this when 0.25 stabilizes // opts.server_ordering_strategy = ServerOrderingStrategy::RoundRobin; From c964c137f490465627031ec20956e899079988f6 Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 22 Jan 2025 12:36:39 -0700 Subject: [PATCH 6/9] fmt --- common/http-api-client/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index d1243f82641..c75e7d64cdc 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -11,7 +11,7 @@ use tracing::{instrument, warn}; use url::Url; #[cfg(not(target_arch = "wasm32"))] -use std:: sync::Arc; +use std::sync::Arc; use std::{fmt::Display, time::Duration}; pub use reqwest::IntoUrl; From 117eb83a0be075d97d0af38c97e094b5086d6dea Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 22 Jan 2025 14:14:04 -0700 Subject: [PATCH 7/9] managing returned iterators --- common/http-api-client/src/dns.rs | 47 ++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/common/http-api-client/src/dns.rs b/common/http-api-client/src/dns.rs index 3052f8eb0e3..f0b6503c734 100644 --- a/common/http-api-client/src/dns.rs +++ b/common/http-api-client/src/dns.rs @@ -15,6 +15,7 @@ use std::fmt; use std::net::SocketAddr; use std::sync::Arc; +use hickory_resolver::lookup_ip::LookupIp; use hickory_resolver::{ config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts}, error::ResolveError, @@ -51,26 +52,19 @@ pub struct HickoryDnsResolver { impl Resolve for HickoryDnsResolver { fn resolve(&self, name: Name) -> Resolving { - self.resolve_str(name.as_str()) - } -} - -impl HickoryDnsResolver { - pub fn resolve_str(&self, name: &str) -> Resolving { let resolver = self.state.clone(); let fallback = self.fallback.clone(); - let domain = name.to_owned(); Box::pin(async move { let resolver = resolver.get_or_try_init(new_resolver)?; // try the primary DNS resolver that we set up (DoH or DoT or whatever) - let lookup = match resolver.lookup_ip(&domain).await { + let lookup = match resolver.lookup_ip(name.as_str()).await { Ok(res) => res, Err(e) => { // on failure use the fall back system configured DNS resolver warn!("primary DNS failed w/ error {e}: using system fallback"); let resolver = fallback.get_or_try_init(new_resolver_system)?; - resolver.lookup_ip(&domain).await? + resolver.lookup_ip(name.as_str()).await? } }; @@ -90,6 +84,27 @@ impl Iterator for SocketAddrs { } } +type BoxError = Box; + +impl HickoryDnsResolver { + pub async fn resolve_str(&self, name: &str) -> Result { + let resolver = self.state.get_or_try_init(new_resolver)?; + + // try the primary DNS resolver that we set up (DoH or DoT or whatever) + let lookup = match resolver.lookup_ip(name).await { + Ok(res) => res, + Err(e) => { + // on failure use the fall back system configured DNS resolver + warn!("primary DNS failed w/ error {e}: using system fallback"); + let resolver = self.fallback.get_or_try_init(new_resolver_system)?; + resolver.lookup_ip(name).await? + } + }; + + Ok(lookup) + } +} + /// Create a new resolver with a custom DoT based configuration. The options are overridden to look /// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm. fn new_resolver() -> Result { @@ -146,7 +161,7 @@ mod test { .unwrap(); let resp = client - .get("http://ifconfig.me") + .get("http://ifconfig.me:80") .send() .await .unwrap() @@ -156,4 +171,16 @@ mod test { assert!(!resp.is_empty()); } + + #[tokio::test] + async fn dns_lookup() -> Result<(), BoxError> { + let resolver = HickoryDnsResolver::default(); + + let domain = "ifconfig.me"; + let addrs = resolver.resolve_str(domain).await?; + + assert!(addrs.into_iter().next().is_some()); + + Ok(()) + } } From 32e39ebc6b2b3eafa26bc74a27a73eb74f67465c Mon Sep 17 00:00:00 2001 From: jmwample Date: Wed, 22 Jan 2025 14:32:04 -0700 Subject: [PATCH 8/9] square cargo.lock with upstream branch --- Cargo.lock | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 79f73ce681f..e2de42eb3f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2302,6 +2302,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "env_filter" version = "0.1.2" @@ -3109,6 +3121,61 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +[[package]] +name = "hickory-proto" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447afdcdb8afb9d0a852af6dc65d9b285ce720ed7a59e42a8bf2e931c67bc1b5" +dependencies = [ + "async-trait", + "bytes", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "h2", + "http 0.2.12", + "idna 1.0.3", + "ipnet", + "once_cell", + "rand", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tokio-rustls 0.24.1", + "tracing", + "url", + "webpki-roots 0.25.4", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2e2aba9c389ce5267d31cf1e4dace82390ae276b0b364ea55630b1fa1b44b4" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "rustls 0.21.12", + "smallvec", + "thiserror 1.0.69", + "tokio", + "tokio-rustls 0.24.1", + "tracing", + "webpki-roots 0.25.4", +] + [[package]] name = "hidapi" version = "1.5.0" @@ -3148,6 +3215,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "html5ever" version = "0.27.0" @@ -3756,6 +3834,18 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -4006,6 +4096,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -4080,6 +4176,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "mac" version = "0.1.1" @@ -4154,6 +4259,12 @@ dependencies = [ "tendril", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -5748,12 +5859,15 @@ name = "nym-http-api-client" version = "0.1.0" dependencies = [ "async-trait", + "hickory-resolver", "http 1.1.0", "nym-bin-common", + "once_cell", "reqwest 0.12.4", "serde", "serde_json", "thiserror 1.0.69", + "tokio", "tracing", "url", "wasmtimer", @@ -8242,6 +8356,16 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error 1.2.3", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -11352,6 +11476,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" From 8d10552d7cd16f3536af02526aa7d7e88456646e Mon Sep 17 00:00:00 2001 From: jmwample Date: Thu, 23 Jan 2025 08:29:56 -0700 Subject: [PATCH 9/9] hickory dns error mgmt --- common/http-api-client/src/dns.rs | 41 ++++++++++++------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/common/http-api-client/src/dns.rs b/common/http-api-client/src/dns.rs index f0b6503c734..137809d6a73 100644 --- a/common/http-api-client/src/dns.rs +++ b/common/http-api-client/src/dns.rs @@ -9,11 +9,11 @@ //! //! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the //! `hickory-resolver` crate +#![deny(missing_docs)] + use crate::ClientBuilder; -use std::fmt; -use std::net::SocketAddr; -use std::sync::Arc; +use std::{net::SocketAddr, sync::Arc}; use hickory_resolver::lookup_ip::LookupIp; use hickory_resolver::{ @@ -27,6 +27,7 @@ use reqwest::dns::{Addrs, Name, Resolve, Resolving}; use tracing::warn; impl ClientBuilder { + /// Override the DNS resolver implementation used by the underlying http client. pub fn dns_resolver(mut self, resolver: Arc) -> Self { self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver); self @@ -37,8 +38,12 @@ struct SocketAddrs { iter: LookupIpIntoIter, } -#[derive(Debug)] -struct HickoryDnsSystemConfError(ResolveError); +#[derive(Debug, thiserror::Error)] +#[error("hickory-dns resolver error: {hickory_error}")] +pub struct HickoryDnsError { + #[from] + hickory_error: ResolveError, +} /// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait. #[derive(Debug, Default, Clone)] @@ -84,10 +89,9 @@ impl Iterator for SocketAddrs { } } -type BoxError = Box; - impl HickoryDnsResolver { - pub async fn resolve_str(&self, name: &str) -> Result { + /// Attempt to resolve a domain name to a set of ['IpAddr']s + pub async fn resolve_str(&self, name: &str) -> Result { let resolver = self.state.get_or_try_init(new_resolver)?; // try the primary DNS resolver that we set up (DoH or DoT or whatever) @@ -107,7 +111,7 @@ impl HickoryDnsResolver { /// Create a new resolver with a custom DoT based configuration. The options are overridden to look /// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm. -fn new_resolver() -> Result { +fn new_resolver() -> Result { let mut name_servers = NameServerConfigGroup::google_tls(); name_servers.merge(NameServerConfigGroup::google_https()); // name_servers.merge(NameServerConfigGroup::google_h3()); @@ -129,25 +133,12 @@ fn new_resolver() -> Result { /// Create a new resolver with the default configuration, which reads from the system DNS config /// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6 /// addresses to work with "happy eyeballs" algorithm. -fn new_resolver_system() -> Result { - let (config, mut opts) = - hickory_resolver::system_conf::read_system_conf().map_err(HickoryDnsSystemConfError)?; +fn new_resolver_system() -> Result { + let (config, mut opts) = hickory_resolver::system_conf::read_system_conf()?; opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6; Ok(TokioAsyncResolver::tokio(config, opts)) } -impl fmt::Display for HickoryDnsSystemConfError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("error reading DNS system conf for hickory-dns") - } -} - -impl std::error::Error for HickoryDnsSystemConfError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.0) - } -} - #[cfg(test)] mod test { use super::*; @@ -173,7 +164,7 @@ mod test { } #[tokio::test] - async fn dns_lookup() -> Result<(), BoxError> { + async fn dns_lookup() -> Result<(), HickoryDnsError> { let resolver = HickoryDnsResolver::default(); let domain = "ifconfig.me";