iroh_relay/
dns.rs

1//! Configurable DNS resolver for `iroh-relay` and `iroh`.
2//!
3//! The main export is the [`DnsResolver`] struct. It provides methods to resolve domain names
4//! to IPv4 and IPv6 addresses. Additionally, the resolver features methods to resolve the
5//! [`EndpointInfo`] for an iroh [`EndpointId`] from `_iroh` TXT records.
6//! See the [`endpoint_info`] module documentation for details on how iroh endpoint records
7//! are structured.
8
9use std::{
10    fmt,
11    future::Future,
12    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
13    sync::Arc,
14};
15
16use hickory_resolver::{
17    TokioResolver,
18    config::{ResolverConfig, ResolverOpts},
19    name_server::TokioConnectionProvider,
20};
21use iroh_base::EndpointId;
22use n0_error::{StackError, e, stack_error};
23use n0_future::{
24    StreamExt,
25    boxed::BoxFuture,
26    time::{self, Duration},
27};
28use rustls::ClientConfig;
29use tokio::sync::RwLock;
30use tracing::debug;
31use url::Url;
32
33use crate::{
34    defaults::timeouts::DNS_TIMEOUT,
35    endpoint_info::{self, EndpointInfo, ParseError},
36};
37
38/// The n0 address lookup DNS origin, for production.
39pub const N0_DNS_ENDPOINT_ORIGIN_PROD: &str = "dns.iroh.link.";
40/// The n0 address lookup DNS origin, for testing.
41pub const N0_DNS_ENDPOINT_ORIGIN_STAGING: &str = "staging-dns.iroh.link.";
42
43/// Percent of total delay to jitter. 20 means +/- 20% of delay.
44const MAX_JITTER_PERCENT: u64 = 20;
45
46/// Trait for DNS resolvers used in iroh.
47pub trait Resolver: fmt::Debug + Send + Sync + 'static {
48    /// Looks up an IPv4 address.
49    fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>>;
50
51    /// Looks up an IPv6 address.
52    fn lookup_ipv6(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>>;
53
54    /// Looks up TXT records.
55    fn lookup_txt(&self, host: String) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>>;
56
57    /// Clears the internal cache.
58    fn clear_cache(&self);
59
60    /// Completely resets the DNS resolver.
61    ///
62    /// This is called when the host's network changes majorly. Implementations should rebind all sockets
63    /// and refresh the nameserver configuration if read from the host system.
64    fn reset(&mut self);
65}
66
67/// Boxed iterator alias.
68///
69/// Used in return types of [`Resolver`] methods.
70pub type BoxIter<T> = Box<dyn Iterator<Item = T> + Send + 'static>;
71
72/// Potential errors related to DNS operations.
73#[allow(missing_docs)]
74#[stack_error(derive, add_meta, from_sources, std_sources)]
75#[non_exhaustive]
76pub enum DnsError {
77    #[error(transparent)]
78    Timeout { source: tokio::time::error::Elapsed },
79    #[error("No response")]
80    NoResponse {},
81    #[error("Resolve failed ipv4: {ipv4}, ipv6 {ipv6}")]
82    ResolveBoth {
83        ipv4: Box<DnsError>,
84        ipv6: Box<DnsError>,
85    },
86    #[error("missing host")]
87    MissingHost {},
88    #[error(transparent)]
89    Resolve {
90        source: hickory_resolver::ResolveError,
91    },
92    #[error("invalid DNS response: not a query for _iroh.z32encodedpubkey")]
93    InvalidResponse {},
94}
95
96/// Potential errors related to DNS endpoint address lookups.
97#[cfg(not(wasm_browser))]
98#[allow(missing_docs)]
99#[stack_error(derive, add_meta, from_sources)]
100#[non_exhaustive]
101pub enum LookupError {
102    #[error("Malformed txt from lookup")]
103    ParseError { source: ParseError },
104    #[error("Failed to resolve TXT record")]
105    LookupFailed { source: DnsError },
106}
107
108/// Error returned when a staggered call fails.
109#[stack_error(derive, add_meta)]
110#[error("no calls succeeded: [{}]", errors.iter().map(|e| e.to_string()).collect::<Vec<_>>().join(""))]
111pub struct StaggeredError<E: n0_error::StackError + 'static> {
112    errors: Vec<E>,
113}
114
115impl<E: StackError + 'static> StaggeredError<E> {
116    /// Returns an iterator over all encountered errors.
117    pub fn iter(&self) -> impl Iterator<Item = &E> {
118        self.errors.iter()
119    }
120}
121
122/// Builder for [`DnsResolver`].
123#[derive(Debug, Clone, Default)]
124pub struct Builder {
125    use_system_defaults: bool,
126    nameservers: Vec<(SocketAddr, DnsProtocol)>,
127    tls_client_config: Option<ClientConfig>,
128}
129
130/// Protocols over which DNS records can be resolved.
131#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
132#[non_exhaustive]
133pub enum DnsProtocol {
134    /// DNS over UDP
135    ///
136    /// This is the classic DNS protocol and supported by most DNS servers.
137    #[default]
138    Udp,
139    /// DNS over TCP
140    ///
141    /// This is specified in the original DNS RFCs, but is not supported by all DNS servers.
142    Tcp,
143    /// DNS over TLS
144    ///
145    /// Performs DNS lookups over TLS-encrypted TCP connections, as defined in [RFC 7858].
146    ///
147    /// [RFC 7858]: https://www.rfc-editor.org/rfc/rfc7858.html
148    Tls,
149    /// DNS over HTTPS
150    ///
151    /// Performs DNS lookups over HTTPS, as defined in [RFC 8484].
152    ///
153    /// [RFC 8484]: https://www.rfc-editor.org/rfc/rfc8484.html
154    Https,
155}
156
157impl DnsProtocol {
158    fn to_hickory(self) -> hickory_resolver::proto::xfer::Protocol {
159        use hickory_resolver::proto::xfer::Protocol;
160        match self {
161            DnsProtocol::Udp => Protocol::Udp,
162            DnsProtocol::Tcp => Protocol::Tcp,
163            DnsProtocol::Tls => Protocol::Tls,
164            DnsProtocol::Https => Protocol::Https,
165        }
166    }
167}
168
169impl Builder {
170    /// Makes the builder respect the host system's DNS configuration.
171    ///
172    /// We first try to read the system's resolver from `/etc/resolv.conf`.
173    /// This does not work at least on some Androids, therefore we fallback
174    /// to a default config which uses Google's `8.8.8.8` or `8.8.4.4`.
175    pub fn with_system_defaults(mut self) -> Self {
176        self.use_system_defaults = true;
177        self
178    }
179
180    /// Adds a single nameserver.
181    pub fn with_nameserver(mut self, addr: SocketAddr, protocol: DnsProtocol) -> Self {
182        self.nameservers.push((addr, protocol));
183        self
184    }
185
186    /// Adds a list of nameservers.
187    pub fn with_nameservers(
188        mut self,
189        nameservers: impl IntoIterator<Item = (SocketAddr, DnsProtocol)>,
190    ) -> Self {
191        self.nameservers.extend(nameservers);
192        self
193    }
194
195    /// Sets a custom TLS verification config.
196    pub fn tls_client_config(mut self, client_config: ClientConfig) -> Self {
197        self.tls_client_config = Some(client_config);
198        self
199    }
200
201    /// Builds the DNS resolver.
202    pub fn build(self) -> DnsResolver {
203        let resolver = HickoryResolver::new(self);
204        DnsResolver(DnsResolverInner::Hickory(Arc::new(RwLock::new(resolver))))
205    }
206}
207
208/// The DNS resolver used throughout `iroh`.
209///
210/// By default, we use a built-in resolver that reads the system's DNS configuration.
211/// The nameservers can be customized by constructing the resolver with [`Self::builder`].
212/// Alternatively, you can create a fully custom DNS resolver by implementing the [`Resolver`]
213/// trait and creating the resolver with [`Self::custom`].
214#[derive(Debug, Clone)]
215pub struct DnsResolver(DnsResolverInner);
216
217impl DnsResolver {
218    /// Creates a new DNS resolver with sensible cross-platform defaults.
219    ///
220    /// We first try to read the system's resolver from `/etc/resolv.conf`.
221    /// This does not work at least on some Androids, therefore we fallback
222    /// to the default `ResolverConfig` which uses eg. to google's `8.8.8.8` or `8.8.4.4`.
223    pub fn new() -> Self {
224        Builder::default().with_system_defaults().build()
225    }
226
227    /// Creates a new DNS resolver configured with a single UDP DNS nameserver.
228    pub fn with_nameserver(nameserver: SocketAddr) -> Self {
229        Builder::default()
230            .with_nameserver(nameserver, DnsProtocol::Udp)
231            .build()
232    }
233
234    /// Creates a builder to construct a DNS resolver with custom options.
235    pub fn builder() -> Builder {
236        Builder::default()
237    }
238
239    /// Creates a new [`DnsResolver`] from a struct that implements [`Resolver`].
240    ///
241    /// If you need more customization for DNS resolving than the [`Builder`] allows, you can
242    /// implement the [`Resolver`] trait on a struct and implement DNS resolution
243    /// however you see fit.
244    pub fn custom(resolver: impl Resolver) -> Self {
245        Self(DnsResolverInner::Custom(Arc::new(RwLock::new(resolver))))
246    }
247
248    /// Removes all entries from the cache.
249    pub async fn clear_cache(&self) {
250        self.0.clear_cache().await
251    }
252
253    /// Recreates the inner resolver.
254    pub async fn reset(&self) {
255        self.0.reset().await
256    }
257
258    /// Looks up a TXT record.
259    pub async fn lookup_txt<T: ToString>(
260        &self,
261        host: T,
262        timeout: Duration,
263    ) -> Result<impl Iterator<Item = TxtRecordData>, DnsError> {
264        let host = host.to_string();
265        let res = time::timeout(timeout, self.0.lookup_txt(host)).await??;
266        Ok(res)
267    }
268
269    /// Performs an IPv4 lookup with a timeout.
270    pub async fn lookup_ipv4<T: ToString>(
271        &self,
272        host: T,
273        timeout: Duration,
274    ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
275        let host = host.to_string();
276        let addrs = time::timeout(timeout, self.0.lookup_ipv4(host)).await??;
277        Ok(addrs.into_iter().map(IpAddr::V4))
278    }
279
280    /// Performs an IPv6 lookup with a timeout.
281    pub async fn lookup_ipv6<T: ToString>(
282        &self,
283        host: T,
284        timeout: Duration,
285    ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
286        let host = host.to_string();
287        let addrs = time::timeout(timeout, self.0.lookup_ipv6(host)).await??;
288        Ok(addrs.into_iter().map(IpAddr::V6))
289    }
290
291    /// Resolves IPv4 and IPv6 in parallel with a timeout.
292    ///
293    /// `LookupIpStrategy::Ipv4AndIpv6` will wait for ipv6 resolution timeout, even if it is
294    /// not usable on the stack, so we manually query both lookups concurrently and time them out
295    /// individually.
296    pub async fn lookup_ipv4_ipv6<T: ToString>(
297        &self,
298        host: T,
299        timeout: Duration,
300    ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
301        let host = host.to_string();
302        let res = tokio::join!(
303            self.lookup_ipv4(host.clone(), timeout),
304            self.lookup_ipv6(host, timeout)
305        );
306
307        match res {
308            (Ok(ipv4), Ok(ipv6)) => Ok(LookupIter::Both(ipv4.chain(ipv6))),
309            (Ok(ipv4), Err(_)) => Ok(LookupIter::Ipv4(ipv4)),
310            (Err(_), Ok(ipv6)) => Ok(LookupIter::Ipv6(ipv6)),
311            (Err(ipv4_err), Err(ipv6_err)) => Err(e!(DnsError::ResolveBoth {
312                ipv4: Box::new(ipv4_err),
313                ipv6: Box::new(ipv6_err)
314            })),
315        }
316    }
317
318    /// Resolves a hostname from a URL to an IP address.
319    pub async fn resolve_host(
320        &self,
321        url: &Url,
322        prefer_ipv6: bool,
323        timeout: Duration,
324    ) -> Result<IpAddr, DnsError> {
325        let host = url.host().ok_or_else(|| e!(DnsError::MissingHost))?;
326        match host {
327            url::Host::Domain(domain) => {
328                // Need to do a DNS lookup
329                let lookup = tokio::join!(
330                    self.lookup_ipv4(domain, timeout),
331                    self.lookup_ipv6(domain, timeout)
332                );
333                let (v4, v6) = match lookup {
334                    (Err(ipv4_err), Err(ipv6_err)) => {
335                        return Err(e!(DnsError::ResolveBoth {
336                            ipv4: Box::new(ipv4_err),
337                            ipv6: Box::new(ipv6_err)
338                        }));
339                    }
340                    (Err(_), Ok(mut v6)) => (None, v6.next()),
341                    (Ok(mut v4), Err(_)) => (v4.next(), None),
342                    (Ok(mut v4), Ok(mut v6)) => (v4.next(), v6.next()),
343                };
344                if prefer_ipv6 {
345                    v6.or(v4).ok_or_else(|| e!(DnsError::NoResponse))
346                } else {
347                    v4.or(v6).ok_or_else(|| e!(DnsError::NoResponse))
348                }
349            }
350            url::Host::Ipv4(ip) => Ok(IpAddr::V4(ip)),
351            url::Host::Ipv6(ip) => Ok(IpAddr::V6(ip)),
352        }
353    }
354
355    /// Performs an IPv4 lookup with a timeout in a staggered fashion.
356    ///
357    /// From the moment this function is called, each lookup is scheduled after the delays in
358    /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
359    /// at T+0ms, T+200ms and T+300ms. The `timeout` is applied to each call individually. The
360    /// result of the first successful call is returned, or a summary of all errors otherwise.
361    pub async fn lookup_ipv4_staggered(
362        &self,
363        host: impl ToString,
364        timeout: Duration,
365        delays_ms: &[u64],
366    ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
367        let host = host.to_string();
368        let f = || self.lookup_ipv4(host.clone(), timeout);
369        stagger_call(f, delays_ms).await
370    }
371
372    /// Performs an IPv6 lookup with a timeout in a staggered fashion.
373    ///
374    /// From the moment this function is called, each lookup is scheduled after the delays in
375    /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
376    /// at T+0ms, T+200ms and T+300ms. The `timeout` is applied to each call individually. The
377    /// result of the first successful call is returned, or a summary of all errors otherwise.
378    pub async fn lookup_ipv6_staggered(
379        &self,
380        host: impl ToString,
381        timeout: Duration,
382        delays_ms: &[u64],
383    ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
384        let host = host.to_string();
385        let f = || self.lookup_ipv6(host.clone(), timeout);
386        stagger_call(f, delays_ms).await
387    }
388
389    /// Races an IPv4 and IPv6 lookup with a timeout in a staggered fashion.
390    ///
391    /// From the moment this function is called, each lookup is scheduled after the delays in
392    /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
393    /// at T+0ms, T+200ms and T+300ms. The `timeout` is applied as stated in
394    /// [`Self::lookup_ipv4_ipv6`]. The result of the first successful call is returned, or a
395    /// summary of all errors otherwise.
396    pub async fn lookup_ipv4_ipv6_staggered(
397        &self,
398        host: impl ToString,
399        timeout: Duration,
400        delays_ms: &[u64],
401    ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
402        let host = host.to_string();
403        let f = || self.lookup_ipv4_ipv6(host.clone(), timeout);
404        stagger_call(f, delays_ms).await
405    }
406
407    /// Looks up endpoint info by [`EndpointId`] and origin domain name.
408    ///
409    /// To lookup endpoints that published their endpoint info to the DNS servers run by n0,
410    /// pass [`N0_DNS_ENDPOINT_ORIGIN_PROD`] as `origin`.
411    pub async fn lookup_endpoint_by_id(
412        &self,
413        endpoint_id: &EndpointId,
414        origin: &str,
415    ) -> Result<EndpointInfo, LookupError> {
416        let name = endpoint_info::endpoint_domain(endpoint_id, origin);
417        let name = endpoint_info::ensure_iroh_txt_label(name);
418        let lookup = self.lookup_txt(name.clone(), DNS_TIMEOUT).await?;
419        let info = EndpointInfo::from_txt_lookup(name, lookup)?;
420        Ok(info)
421    }
422
423    /// Looks up endpoint info by DNS name.
424    pub async fn lookup_endpoint_by_domain_name(
425        &self,
426        name: &str,
427    ) -> Result<EndpointInfo, LookupError> {
428        let name = endpoint_info::ensure_iroh_txt_label(name.to_string());
429        let lookup = self.lookup_txt(name.clone(), DNS_TIMEOUT).await?;
430        let info = EndpointInfo::from_txt_lookup(name, lookup)?;
431        Ok(info)
432    }
433
434    /// Looks up endpoint info by DNS name in a staggered fashion.
435    ///
436    /// From the moment this function is called, each lookup is scheduled after the delays in
437    /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
438    /// at T+0ms, T+200ms and T+300ms. The result of the first successful call is returned, or a
439    /// summary of all errors otherwise.
440    pub async fn lookup_endpoint_by_domain_name_staggered(
441        &self,
442        name: &str,
443        delays_ms: &[u64],
444    ) -> Result<EndpointInfo, StaggeredError<LookupError>> {
445        let f = || self.lookup_endpoint_by_domain_name(name);
446        stagger_call(f, delays_ms).await
447    }
448
449    /// Looks up endpoint info by [`EndpointId`] and origin domain name.
450    ///
451    /// From the moment this function is called, each lookup is scheduled after the delays in
452    /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
453    /// at T+0ms, T+200ms and T+300ms. The result of the first successful call is returned, or a
454    /// summary of all errors otherwise.
455    pub async fn lookup_endpoint_by_id_staggered(
456        &self,
457        endpoint_id: &EndpointId,
458        origin: &str,
459        delays_ms: &[u64],
460    ) -> Result<EndpointInfo, StaggeredError<LookupError>> {
461        let f = || self.lookup_endpoint_by_id(endpoint_id, origin);
462        stagger_call(f, delays_ms).await
463    }
464}
465
466impl Default for DnsResolver {
467    fn default() -> Self {
468        Self::new()
469    }
470}
471
472impl reqwest::dns::Resolve for DnsResolver {
473    fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving {
474        let this = self.clone();
475        let name = name.as_str().to_string();
476        Box::pin(async move {
477            let res = this.lookup_ipv4_ipv6(name, DNS_TIMEOUT).await;
478            match res {
479                Ok(addrs) => {
480                    let addrs: reqwest::dns::Addrs =
481                        Box::new(addrs.map(|addr| SocketAddr::new(addr, 0)));
482                    Ok(addrs)
483                }
484                Err(err) => {
485                    let err: Box<dyn std::error::Error + Send + Sync> = Box::new(err);
486                    Err(err)
487                }
488            }
489        })
490    }
491}
492
493/// Wrapper enum that contains either a hickory resolver or a custom resolver.
494///
495/// We do this to save the cost of boxing the futures and iterators when using
496/// default hickory resolver.
497#[derive(Debug, Clone)]
498enum DnsResolverInner {
499    Hickory(Arc<RwLock<HickoryResolver>>),
500    Custom(Arc<RwLock<dyn Resolver>>),
501}
502
503impl DnsResolverInner {
504    async fn lookup_ipv4(
505        &self,
506        host: String,
507    ) -> Result<impl Iterator<Item = Ipv4Addr> + use<>, DnsError> {
508        Ok(match self {
509            Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_ipv4(host).await?),
510            Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_ipv4(host).await?),
511        })
512    }
513
514    async fn lookup_ipv6(
515        &self,
516        host: String,
517    ) -> Result<impl Iterator<Item = Ipv6Addr> + use<>, DnsError> {
518        Ok(match self {
519            Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_ipv6(host).await?),
520            Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_ipv6(host).await?),
521        })
522    }
523
524    async fn lookup_txt(
525        &self,
526        host: String,
527    ) -> Result<impl Iterator<Item = TxtRecordData> + use<>, DnsError> {
528        Ok(match self {
529            Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_txt(host).await?),
530            Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_txt(host).await?),
531        })
532    }
533
534    async fn clear_cache(&self) {
535        match self {
536            Self::Hickory(resolver) => resolver.read().await.clear_cache(),
537            Self::Custom(resolver) => resolver.read().await.clear_cache(),
538        }
539    }
540
541    async fn reset(&self) {
542        match self {
543            Self::Hickory(resolver) => resolver.write().await.reset(),
544            Self::Custom(resolver) => resolver.write().await.reset(),
545        }
546    }
547}
548
549#[derive(Debug)]
550struct HickoryResolver {
551    resolver: TokioResolver,
552    builder: Builder,
553}
554
555impl HickoryResolver {
556    fn new(builder: Builder) -> Self {
557        let resolver = Self::build_resolver(&builder);
558        Self { resolver, builder }
559    }
560
561    fn build_resolver(builder: &Builder) -> TokioResolver {
562        let (mut config, mut options) = if builder.use_system_defaults {
563            match Self::system_config() {
564                Ok((config, options)) => (config, options),
565                Err(error) => {
566                    debug!(%error, "Failed to read the system's DNS config, using fallback DNS servers.");
567                    (ResolverConfig::google(), ResolverOpts::default())
568                }
569            }
570        } else {
571            (ResolverConfig::new(), ResolverOpts::default())
572        };
573
574        if let Some(client_config) = builder.tls_client_config.clone() {
575            options.tls_config = client_config;
576        }
577
578        for (addr, proto) in builder.nameservers.iter() {
579            let nameserver =
580                hickory_resolver::config::NameServerConfig::new(*addr, proto.to_hickory());
581            config.add_name_server(nameserver);
582        }
583
584        // see [`DnsResolver::lookup_ipv4_ipv6`] for info on why we avoid `LookupIpStrategy::Ipv4AndIpv6`
585        options.ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4thenIpv6;
586        options.negative_max_ttl = Some(Duration::ZERO);
587
588        let mut hickory_builder =
589            TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
590        *hickory_builder.options_mut() = options;
591        hickory_builder.build()
592    }
593
594    fn system_config() -> Result<(ResolverConfig, ResolverOpts), hickory_resolver::ResolveError> {
595        let (system_config, options) = hickory_resolver::system_conf::read_system_conf()?;
596
597        // Copy all of the system config, but strip the bad windows nameservers.  Unfortunately
598        // there is no easy way to do this.
599        let mut config = hickory_resolver::config::ResolverConfig::new();
600        if let Some(name) = system_config.domain() {
601            config.set_domain(name.clone());
602        }
603        for name in system_config.search() {
604            config.add_search(name.clone());
605        }
606        for nameserver_cfg in system_config.name_servers() {
607            if !WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS.contains(&nameserver_cfg.socket_addr.ip()) {
608                config.add_name_server(nameserver_cfg.clone());
609            }
610        }
611        Ok((config, options))
612    }
613
614    async fn lookup_ipv4(
615        &self,
616        host: String,
617    ) -> Result<impl Iterator<Item = Ipv4Addr> + use<>, DnsError> {
618        Ok(self
619            .resolver
620            .ipv4_lookup(host)
621            .await?
622            .into_iter()
623            .map(Ipv4Addr::from))
624    }
625
626    /// Looks up an IPv6 address.
627    async fn lookup_ipv6(
628        &self,
629        host: String,
630    ) -> Result<impl Iterator<Item = Ipv6Addr> + use<>, DnsError> {
631        Ok(self
632            .resolver
633            .ipv6_lookup(host)
634            .await?
635            .into_iter()
636            .map(Ipv6Addr::from))
637    }
638
639    /// Looks up TXT records.
640    async fn lookup_txt(
641        &self,
642        host: String,
643    ) -> Result<impl Iterator<Item = TxtRecordData> + use<>, DnsError> {
644        Ok(self
645            .resolver
646            .txt_lookup(host)
647            .await?
648            .into_iter()
649            .map(|txt| TxtRecordData::from_iter(txt.iter().cloned())))
650    }
651
652    /// Clears the internal cache.
653    fn clear_cache(&self) {
654        self.resolver.clear_cache()
655    }
656
657    fn reset(&mut self) {
658        self.resolver = Self::build_resolver(&self.builder);
659    }
660}
661
662/// Record data for a TXT record.
663///
664/// This contains a list of character strings, as defined in [RFC 1035 Section 3.3.14].
665///
666/// [`TxtRecordData`] implements [`fmt::Display`], so you can call [`ToString::to_string`] to
667/// convert the record data into a string. This will parse each character string with
668/// [`String::from_utf8_lossy`] and then concatenate all strings without a separator.
669///
670/// If you want to process each character string individually, use [`Self::iter`].
671///
672/// [RFC 1035 Section 3.3.14]: https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14
673#[derive(Debug, Clone)]
674pub struct TxtRecordData(Box<[Box<[u8]>]>);
675
676impl TxtRecordData {
677    /// Returns an iterator over the character strings contained in this TXT record.
678    pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
679        self.0.iter().map(|x| x.as_ref())
680    }
681}
682
683impl fmt::Display for TxtRecordData {
684    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685        for s in self.iter() {
686            write!(f, "{}", &String::from_utf8_lossy(s))?
687        }
688        Ok(())
689    }
690}
691
692impl FromIterator<Box<[u8]>> for TxtRecordData {
693    fn from_iter<T: IntoIterator<Item = Box<[u8]>>>(iter: T) -> Self {
694        Self(iter.into_iter().collect())
695    }
696}
697
698impl From<Vec<Box<[u8]>>> for TxtRecordData {
699    fn from(value: Vec<Box<[u8]>>) -> Self {
700        Self(value.into_boxed_slice())
701    }
702}
703
704/// Helper enum to give a unified type to either of two iterators
705enum Either<A, B> {
706    Left(A),
707    Right(B),
708}
709
710impl<T, A: Iterator<Item = T>, B: Iterator<Item = T>> Iterator for Either<A, B> {
711    type Item = T;
712
713    fn next(&mut self) -> Option<Self::Item> {
714        match self {
715            Either::Left(iter) => iter.next(),
716            Either::Right(iter) => iter.next(),
717        }
718    }
719}
720
721/// Deprecated IPv6 site-local anycast addresses still configured by windows.
722///
723/// Windows still configures these site-local addresses as soon even as an IPv6 loopback
724/// interface is configured.  We do not want to use these DNS servers, the chances of them
725/// being usable are almost always close to zero, while the chance of DNS configuration
726/// **only** relying on these servers and not also being configured normally are also almost
727/// zero.  The chance of the DNS resolver accidentally trying one of these and taking a
728/// bunch of timeouts to figure out they're no good are on the other hand very high.
729const WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS: [IpAddr; 3] = [
730    IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 1)),
731    IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 2)),
732    IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 3)),
733];
734
735/// Helper enum to give a unified type to the iterators of [`DnsResolver::lookup_ipv4_ipv6`].
736enum LookupIter<A, B> {
737    Ipv4(A),
738    Ipv6(B),
739    Both(std::iter::Chain<A, B>),
740}
741
742impl<A: Iterator<Item = IpAddr>, B: Iterator<Item = IpAddr>> Iterator for LookupIter<A, B> {
743    type Item = IpAddr;
744
745    fn next(&mut self) -> Option<Self::Item> {
746        match self {
747            LookupIter::Ipv4(iter) => iter.next(),
748            LookupIter::Ipv6(iter) => iter.next(),
749            LookupIter::Both(iter) => iter.next(),
750        }
751    }
752}
753
754/// Staggers calls to the future F with the given delays.
755///
756/// The first call is performed immediately. The first call to succeed generates an Ok result
757/// ignoring any previous error. If all calls fail, an error summarizing all errors is returned.
758async fn stagger_call<
759    T,
760    E: StackError + 'static,
761    F: Fn() -> Fut,
762    Fut: Future<Output = Result<T, E>>,
763>(
764    f: F,
765    delays_ms: &[u64],
766) -> Result<T, StaggeredError<E>> {
767    let mut calls = n0_future::FuturesUnorderedBounded::new(delays_ms.len() + 1);
768    // NOTE: we add the 0 delay here to have a uniform set of futures. This is more performant than
769    // using alternatives that allow futures of different types.
770    for delay in std::iter::once(&0u64).chain(delays_ms) {
771        let delay = add_jitter(delay);
772        let fut = f();
773        let staggered_fut = async move {
774            time::sleep(delay).await;
775            fut.await
776        };
777        calls.push(staggered_fut)
778    }
779
780    let mut errors = vec![];
781    while let Some(call_result) = calls.next().await {
782        match call_result {
783            Ok(t) => return Ok(t),
784            Err(e) => errors.push(e),
785        }
786    }
787
788    Err(e!(StaggeredError { errors }))
789}
790
791fn add_jitter(delay: &u64) -> Duration {
792    // If delay is 0, return 0 immediately.
793    if *delay == 0 {
794        return Duration::ZERO;
795    }
796
797    // Calculate jitter as a random value in the range of +/- MAX_JITTER_PERCENT of the delay.
798    let max_jitter = delay.saturating_mul(MAX_JITTER_PERCENT * 2) / 100;
799    let jitter = rand::random::<u64>() % max_jitter;
800
801    Duration::from_millis(delay.saturating_sub(max_jitter / 2).saturating_add(jitter))
802}
803
804#[cfg(test)]
805pub(crate) mod tests {
806    use std::sync::atomic::AtomicUsize;
807
808    use n0_tracing_test::traced_test;
809
810    use super::*;
811
812    #[tokio::test]
813    #[traced_test]
814    async fn stagger_basic() {
815        const CALL_RESULTS: &[Result<u8, u8>] = &[Err(2), Ok(3), Ok(5), Ok(7)];
816        static DONE_CALL: AtomicUsize = AtomicUsize::new(0);
817        let f = || {
818            let r_pos = DONE_CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
819            async move {
820                tracing::info!(r_pos, "call");
821                CALL_RESULTS[r_pos].map_err(|_| e!(DnsError::InvalidResponse))
822            }
823        };
824
825        let delays = [1000, 15];
826        let result = stagger_call(f, &delays).await.unwrap();
827        assert_eq!(result, 5)
828    }
829
830    #[test]
831    #[traced_test]
832    fn jitter_test_zero() {
833        let jittered_delay = add_jitter(&0);
834        assert_eq!(jittered_delay, Duration::from_secs(0));
835    }
836
837    //Sanity checks that I did the math right
838    #[test]
839    #[traced_test]
840    fn jitter_test_nonzero_lower_bound() {
841        let delay: u64 = 300;
842        for _ in 0..100 {
843            assert!(add_jitter(&delay) >= Duration::from_millis(delay * 8 / 10));
844        }
845    }
846
847    #[test]
848    #[traced_test]
849    fn jitter_test_nonzero_upper_bound() {
850        let delay: u64 = 300;
851        for _ in 0..100 {
852            assert!(add_jitter(&delay) < Duration::from_millis(delay * 12 / 10));
853        }
854    }
855
856    #[tokio::test]
857    #[traced_test]
858    async fn custom_resolver() {
859        #[derive(Debug)]
860        struct MyResolver;
861        impl Resolver for MyResolver {
862            fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>> {
863                Box::pin(async move {
864                    let addr = if host == "foo.example" {
865                        Ipv4Addr::new(1, 1, 1, 1)
866                    } else {
867                        return Err(e!(DnsError::NoResponse));
868                    };
869                    let iter: BoxIter<Ipv4Addr> = Box::new(vec![addr].into_iter());
870                    Ok(iter)
871                })
872            }
873
874            fn lookup_ipv6(&self, _host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>> {
875                todo!()
876            }
877
878            fn lookup_txt(
879                &self,
880                _host: String,
881            ) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>> {
882                todo!()
883            }
884
885            fn clear_cache(&self) {
886                todo!()
887            }
888
889            fn reset(&mut self) {
890                todo!()
891            }
892        }
893
894        let resolver = DnsResolver::custom(MyResolver);
895        let mut iter = resolver
896            .lookup_ipv4("foo.example", Duration::from_secs(1))
897            .await
898            .expect("not to fail");
899        let addr = iter.next().expect("one result");
900        assert_eq!(addr, "1.1.1.1".parse::<IpAddr>().unwrap());
901
902        let res = resolver
903            .lookup_ipv4("bar.example", Duration::from_secs(1))
904            .await;
905        assert!(matches!(res, Err(DnsError::NoResponse { .. })))
906    }
907}