iroh_relay/
dns.rs

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