iroh_dns/dns.rs
1//! Configurable DNS resolver.
2//!
3//! The main export is the [`DnsResolver`] struct. It provides methods to resolve domain names
4//! to IPv4 and IPv6 addresses, and to look up TXT records. Additionally, the resolver features
5//! methods to resolve the [`EndpointInfo`] for an iroh [`EndpointId`] from `_iroh` TXT records.
6//! See the [`crate::endpoint_info`] module documentation for details on how iroh endpoint records
7//! are structured.
8
9use std::{
10 collections::VecDeque,
11 fmt,
12 future::Future,
13 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
14 pin::Pin,
15 sync::Arc,
16};
17
18use arc_swap::ArcSwap;
19use iroh_base::EndpointId;
20use n0_error::{AnyError, StackError, e, stack_error};
21use n0_future::{
22 Either, MaybeFuture, Stream, StreamExt,
23 boxed::BoxFuture,
24 stream,
25 time::{self, Duration},
26};
27use tokio::sync::Notify;
28use url::Url;
29
30use crate::{ParseError, endpoint_info::EndpointInfo};
31
32/// Default DNS query timeout.
33pub const DNS_TIMEOUT: Duration = Duration::from_secs(3);
34
35/// The n0 address lookup DNS origin, for production.
36pub const N0_DNS_ENDPOINT_ORIGIN_PROD: &str = "dns.iroh.link.";
37/// The n0 address lookup DNS origin, for testing.
38pub const N0_DNS_ENDPOINT_ORIGIN_STAGING: &str = "staging-dns.iroh.link.";
39
40/// Exposes a JVM to iroh so that we can read the system's DNS configuration.
41///
42/// This calls [`ndk_context::initialize_android_context`] to expose a
43/// `JavaVM` and Application Context to Rust code so that we can use JNI.
44/// This is required to get the configured nameservers on Android.
45///
46/// If this function is not called, fetching the configured nameservers will fail
47/// and the default [`DnsResolver`] will use fallback nameservers instead.
48///
49/// If you call [`ndk_context::initialize_android_context`] already somewhere
50/// up the stack in your app, or use a crate like `ndk-glue` or `android-activity`
51/// that do this for you, then there's no need to call this function.
52///
53/// If you don't use a glue crate, a typical way to initialize the context is
54/// via `JNI_OnLoad`:
55///
56/// *Note: `install_android_jni_context` is reexported from `iroh`, so you can substitute
57/// `iroh_dns` for `iroh` below.*
58///
59/// ```ignore
60/// #[cfg(target_os = "android")]
61/// #[no_mangle]
62/// pub extern "C" fn JNI_OnLoad(
63/// vm: jni::JavaVM,
64/// res: *mut std::os::raw::c_void,
65/// ) -> jni::sys::jint {
66/// use std::ffi::c_void;
67///
68/// let vm = vm.get_java_vm_pointer() as *mut c_void;
69/// unsafe {
70/// iroh_dns::install_android_jni_context(vm, res);
71/// }
72/// jni::JNIVersion::V6.into()
73/// }
74/// ```
75///
76/// # Safety
77///
78/// Both the `java_vm` and `context_jobject` pointers must remain valid until the process exits.
79/// See also [`ndk_context::initialize_android_context`].
80///
81/// [`ndk_context`]: https://docs.rs/ndk-context
82/// [`ndk_context::initialize_android_context`]: https://docs.rs/ndk-context/latest/ndk_context/fn.initialize_android_context.html
83//
84// Inlined rather than re-exported from `n0-dns-resolver`: a `pub use` of the
85// resolver crate's item does not resolve on a `doc`-only build (the dependency
86// is compiled without the `doc` cfg, so its item is absent), so we mirror the
87// signature and cfg here and delegate to it on Android.
88#[cfg(any(target_os = "android", doc))]
89pub unsafe fn install_android_jni_context(
90 java_vm: *mut std::ffi::c_void,
91 application_context: *mut std::ffi::c_void,
92) {
93 #[cfg(target_os = "android")]
94 unsafe {
95 n0_dns_resolver::install_android_jni_context(java_vm, application_context);
96 }
97 #[cfg(not(target_os = "android"))]
98 let _ = (java_vm, application_context);
99}
100
101/// Percent of total delay to jitter. 20 means +/- 20% of delay.
102const MAX_JITTER_PERCENT: u64 = 20;
103
104/// Trait for DNS resolvers used in iroh.
105pub trait Resolver: fmt::Debug + Send + Sync + 'static {
106 /// Looks up an IPv4 address.
107 fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>>;
108
109 /// Looks up an IPv6 address.
110 fn lookup_ipv6(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>>;
111
112 /// Looks up TXT records.
113 fn lookup_txt(&self, host: String) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>>;
114
115 /// Clears the internal cache.
116 fn clear_cache(&self);
117
118 /// Returns a freshly-built resolver to replace `self` after a network change.
119 ///
120 /// The returned resolver replaces the previous one inside [`DnsResolver`] via an
121 /// atomic swap. Build a new instance with re-bound sockets and re-read nameserver
122 /// configuration rather than mutating in place. Must not perform IO: defer DNS
123 /// queries and socket binds until the new resolver is first used. May be called
124 /// concurrently, in which case all but one allocated replacement is dropped unused.
125 fn reset(&self) -> Box<dyn Resolver>;
126}
127
128/// Boxed iterator alias.
129///
130/// Used in return types of [`Resolver`] methods.
131pub type BoxIter<T> = Box<dyn Iterator<Item = T> + Send + 'static>;
132
133/// Potential errors related to DNS operations.
134///
135/// Distinct, matchable causes have their own variant, such as [`Self::NxDomain`]
136/// (a nameserver reported the name does not exist, unlike a transient transport
137/// or timeout failure). Any other cause is reported as [`Self::Resolve`], whose
138/// [`AnyError`] source carries the underlying error.
139///
140/// The resolver backing this crate is an implementation detail and its error
141/// types are not part of the public API. If you depend on `n0-dns-resolver`
142/// directly and need the exact reason, downcast a [`Self::Resolve`] source:
143///
144/// ```ignore
145/// if let DnsError::Resolve { source, .. } = &err {
146/// if let Some(inner) = source.downcast_ref::<n0_dns_resolver::Error>() {
147/// // match on `inner` for the specific failure
148/// }
149/// }
150/// ```
151#[allow(missing_docs)]
152#[stack_error(derive, add_meta, std_sources)]
153#[non_exhaustive]
154pub enum DnsError {
155 #[error("Request timed out")]
156 Timeout {},
157 #[error("No response")]
158 NoResponse {},
159 #[error("Resolve failed, IPv4: {ipv4}, IPv6: {ipv6}")]
160 ResolveBoth {
161 ipv4: Box<DnsError>,
162 ipv6: Box<DnsError>,
163 },
164 #[error("Missing host")]
165 MissingHost {},
166 /// A resolution failure not covered by another variant.
167 ///
168 /// Downcast the source (see the type-level docs) to recover the specific cause.
169 #[error("Failed to resolve")]
170 Resolve {
171 #[error(from)]
172 source: AnyError,
173 },
174 #[error("Invalid DNS response: not a query for _iroh.z32encodedpubkey")]
175 InvalidResponse {},
176 /// The domain name does not exist (NXDOMAIN).
177 ///
178 /// A nameserver authoritatively reported that the name does not exist, as
179 /// opposed to a transient transport or timeout failure, so retrying will not
180 /// make the lookup succeed.
181 //
182 // Appended after `InvalidResponse` so the pre-existing variants keep their
183 // discriminants.
184 #[error("Domain name does not exist (NXDOMAIN)")]
185 NxDomain {},
186}
187
188/// Potential errors related to DNS endpoint address lookups.
189#[allow(missing_docs)]
190#[stack_error(derive, add_meta, from_sources)]
191#[non_exhaustive]
192pub enum LookupError {
193 #[error("Malformed txt from lookup")]
194 ParseError { source: ParseError },
195 #[error("Failed to resolve TXT record")]
196 LookupFailed { source: DnsError },
197}
198
199/// Error returned when a staggered call fails.
200#[stack_error(derive, add_meta)]
201#[error("no calls succeeded: [{}]", errors.iter().map(|e| e.to_string()).collect::<Vec<_>>().join(""))]
202pub struct StaggeredError<E: n0_error::StackError + 'static> {
203 errors: Vec<E>,
204}
205
206impl<E: StackError + 'static> StaggeredError<E> {
207 /// Returns an iterator over all encountered errors.
208 pub fn iter(&self) -> impl Iterator<Item = &E> {
209 self.errors.iter()
210 }
211}
212
213/// Builder for [`DnsResolver`].
214#[derive(Debug, Clone, Default)]
215pub struct Builder {
216 use_system_defaults: bool,
217 nameservers: Vec<NameserverConfig>,
218 fallback_mode: FallbackMode,
219 fallback_nameservers: Vec<NameserverConfig>,
220 #[cfg(not(wasm_browser))]
221 tls_client_config: Option<rustls::ClientConfig>,
222}
223
224/// How the resolver uses its fallback nameservers relative to the primary ones.
225///
226/// The *primary* nameservers come from the system DNS configuration and the
227/// nameservers added on the [`Builder`]. The *fallback* nameservers default to a
228/// set of public resolvers, which [`Builder::fallback_nameserver_configs`] can
229/// override. Select the mode with [`Builder::with_fallback_mode`].
230#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
231#[non_exhaustive]
232pub enum FallbackMode {
233 /// Never queries the fallback nameservers.
234 Never,
235 /// Races the fallback nameservers alongside the primary ones from the start.
236 Eager,
237 /// Uses the fallback nameservers only when the system configuration is empty.
238 ///
239 /// A system configuration counts as empty when it yields no nameservers,
240 /// whether because it could not be read or because it listed none. One that
241 /// did yield nameservers is never supplemented: if those fail at query time
242 /// the lookup fails rather than escalating. Without
243 /// [`Builder::with_system_defaults`] there is no configuration at all, which
244 /// also counts as empty, so this behaves like [`Self::Eager`] then.
245 IfSystemEmpty,
246 /// Keeps the fallback nameservers as a lower-priority tier.
247 ///
248 /// They are queried only once every primary nameserver has failed or timed
249 /// out. This is the default.
250 #[default]
251 Deferred,
252}
253
254impl FallbackMode {
255 /// Converts into the resolver crate's fallback mode.
256 ///
257 /// Returns `None` for [`Self::Never`]. The resolver crate has no such mode,
258 /// because its fallback tier starts empty and querying nothing is simply
259 /// adding nothing. Here the tier defaults to public resolvers, so we keep a
260 /// way to say no.
261 fn to_resolver_mode(self) -> Option<n0_dns_resolver::FallbackMode> {
262 match self {
263 FallbackMode::Never => None,
264 FallbackMode::Eager => Some(n0_dns_resolver::FallbackMode::Eager),
265 FallbackMode::IfSystemEmpty => Some(n0_dns_resolver::FallbackMode::IfSystemEmpty),
266 FallbackMode::Deferred => Some(n0_dns_resolver::FallbackMode::Deferred),
267 }
268 }
269}
270
271/// A DNS nameserver configuration for a single server.
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct NameserverConfig {
274 addr: SocketAddr,
275 protocol: DnsProtocol,
276 server_name: Option<String>,
277}
278
279impl NameserverConfig {
280 fn new(addr: IpAddr, port: u16, protocol: DnsProtocol) -> Self {
281 Self {
282 addr: SocketAddr::new(addr, port),
283 protocol,
284 server_name: None,
285 }
286 }
287
288 /// Creates a new nameserver config for DNS over UDP (Do53).
289 ///
290 /// This nameserver will be using DNS over UDP on port 53, see
291 /// [`Self::with_port`] to further customise this nameserver.
292 pub fn udp(addr: IpAddr) -> Self {
293 Self::new(addr, 53, DnsProtocol::Udp)
294 }
295
296 /// Creates a new nameserver config for DNS over TCP (Do53).
297 ///
298 /// This nameserver will be using DNS over TCP on port 53, see
299 /// [`Self::with_port`] to further customise this nameserver.
300 pub fn tcp(addr: IpAddr) -> Self {
301 Self::new(addr, 53, DnsProtocol::Tcp)
302 }
303
304 /// Creates a new nameserver config for DNS over TLS (DoT).
305 ///
306 /// This nameserver will be using DNS over TLS on port 853, see
307 /// [`Self::with_port`] and [`Self::with_tls_server_name`] to further customise
308 /// this nameserver.
309 pub fn tls(addr: IpAddr) -> Self {
310 Self::new(addr, 853, DnsProtocol::Tls)
311 }
312
313 /// Creates a new nameserver config for DNS over HTTPS (DoH).
314 ///
315 /// This nameserver will be using DNS over HTTPS on port 443, see
316 /// [`Self::with_port`] and [`Self::with_tls_server_name`] to further customise
317 /// this nameserver.
318 pub fn https(addr: IpAddr) -> Self {
319 Self::new(addr, 443, DnsProtocol::Https)
320 }
321
322 /// Sets the port for the nameserver.
323 ///
324 /// # Returns
325 ///
326 /// A new instance is returned, this struct is essentially a builder for itself.
327 pub fn with_port(self, port: u16) -> Self {
328 Self {
329 addr: SocketAddr::new(self.addr.ip(), port),
330 ..self
331 }
332 }
333
334 /// Sets the TLS server name for the nameserver.
335 ///
336 /// Nameservers are always connected to via IP address. However for the protocols
337 /// running over TLS (DoT & DoH), the server certificate may use a server name rather
338 /// than IP address. When enabled the TLS `ClientHello` will use this server name in the
339 /// SNI.
340 ///
341 /// # Returns
342 ///
343 /// A new instance is returned, this struct is essentially a builder for itself.
344 pub fn with_tls_server_name(self, server_name: impl Into<String>) -> Self {
345 Self {
346 server_name: Some(server_name.into()),
347 ..self
348 }
349 }
350
351 fn into_resolver_nameserver(self) -> n0_dns_resolver::Nameserver {
352 if let Some(server_name) = self.server_name {
353 return n0_dns_resolver::Nameserver::with_server_name(
354 self.addr,
355 self.protocol.to_resolver_protocol(),
356 server_name,
357 );
358 }
359 n0_dns_resolver::Nameserver::new(self.addr, self.protocol.to_resolver_protocol())
360 }
361}
362
363/// Protocols over which DNS records can be resolved.
364#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
365#[non_exhaustive]
366pub enum DnsProtocol {
367 /// DNS over UDP
368 ///
369 /// This is the classic DNS protocol and supported by most DNS servers.
370 #[default]
371 Udp,
372 /// DNS over TCP
373 ///
374 /// This is specified in the original DNS RFCs, but is not supported by all DNS servers.
375 Tcp,
376 /// DNS over TLS
377 ///
378 /// Performs DNS lookups over TLS-encrypted TCP connections, as defined in [RFC 7858].
379 ///
380 /// [RFC 7858]: https://www.rfc-editor.org/rfc/rfc7858.html
381 Tls,
382 /// DNS over HTTPS
383 ///
384 /// Performs DNS lookups over HTTPS, as defined in [RFC 8484].
385 ///
386 /// [RFC 8484]: https://www.rfc-editor.org/rfc/rfc8484.html
387 Https,
388}
389
390impl DnsProtocol {
391 /// Converts into the resolver crate's protocol type.
392 fn to_resolver_protocol(self) -> n0_dns_resolver::DnsProtocol {
393 match self {
394 DnsProtocol::Udp => n0_dns_resolver::DnsProtocol::Udp,
395 DnsProtocol::Tcp => n0_dns_resolver::DnsProtocol::Tcp,
396 DnsProtocol::Tls => n0_dns_resolver::DnsProtocol::Tls,
397 DnsProtocol::Https => n0_dns_resolver::DnsProtocol::Https,
398 }
399 }
400}
401
402impl Builder {
403 /// Makes the builder respect the host system's DNS configuration.
404 ///
405 /// The configuration is read in a platform-specific way. If that fails, or
406 /// if it lists no nameservers, the fallback tier answers instead, subject
407 /// to the [`FallbackMode`].
408 pub fn with_system_defaults(mut self) -> Self {
409 self.use_system_defaults = true;
410 self
411 }
412
413 /// Adds a single nameserver config.
414 ///
415 /// For DNS-over-TLS and DNS-over-HTTPS, the server name is inferred from the
416 /// IP address. To set a different one, build a [`NameserverConfig`] with
417 /// [`NameserverConfig::with_tls_server_name`] and pass it to
418 /// [`Self::add_nameserver_config`].
419 #[deprecated(since = "1.2.0", note = "use add_nameserver_config()")]
420 pub fn with_nameserver(mut self, addr: SocketAddr, protocol: DnsProtocol) -> Self {
421 self.nameservers.push(NameserverConfig {
422 addr,
423 protocol,
424 server_name: None,
425 });
426 self
427 }
428
429 /// Adds a list of nameserver configs.
430 ///
431 /// For DNS-over-TLS and DNS-over-HTTPS, the server name is inferred from the
432 /// IP address. To set a different one, build a [`NameserverConfig`] with
433 /// [`NameserverConfig::with_tls_server_name`] and pass it to
434 /// [`Self::add_nameserver_configs`].
435 #[deprecated(since = "1.2.0", note = "use add_nameserver_configs()")]
436 pub fn with_nameservers(
437 mut self,
438 nameservers: impl IntoIterator<Item = (SocketAddr, DnsProtocol)>,
439 ) -> Self {
440 self.nameservers.extend(
441 nameservers
442 .into_iter()
443 .map(|(addr, protocol)| NameserverConfig {
444 addr,
445 protocol,
446 server_name: None,
447 }),
448 );
449 self
450 }
451
452 /// Adds a single nameserver config.
453 pub fn add_nameserver_config(mut self, nameserver: NameserverConfig) -> Self {
454 self.nameservers.push(nameserver);
455 self
456 }
457
458 /// Adds a list of nameserver configs.
459 pub fn add_nameserver_configs(
460 mut self,
461 nameservers: impl IntoIterator<Item = NameserverConfig>,
462 ) -> Self {
463 self.nameservers.extend(nameservers);
464 self
465 }
466
467 /// Sets a custom TLS client configuration.
468 ///
469 /// This overrides the default configuration used for DNS-over-TLS and
470 /// DNS-over-HTTPS.
471 ///
472 /// When neither the `tls-ring` nor `tls-aws-lc-rs` feature is enabled,
473 /// DNS-over-TLS and DNS-over-HTTPS require a custom configuration containing
474 /// a crypto provider, supplied with [`Self::tls_client_config`].
475 #[cfg(not(wasm_browser))]
476 pub fn tls_client_config(mut self, client_config: rustls::ClientConfig) -> Self {
477 self.tls_client_config = Some(client_config);
478 self
479 }
480
481 /// Sets how the fallback nameservers are used relative to the primary ones.
482 ///
483 /// The default is [`FallbackMode::Deferred`]: the fallback nameservers are a
484 /// lower-priority tier, queried only when the primary nameservers fail or
485 /// time out. See [`FallbackMode`] for the other modes.
486 pub fn with_fallback_mode(mut self, mode: FallbackMode) -> Self {
487 self.fallback_mode = mode;
488 self
489 }
490
491 /// Disables the fallback nameservers, so only the primary ones are queried.
492 ///
493 /// Shorthand for [`Self::with_fallback_mode`] with [`FallbackMode::Never`].
494 pub fn disable_fallback(self) -> Self {
495 self.with_fallback_mode(FallbackMode::Never)
496 }
497
498 /// Adds nameservers to the fallback tier, in place of the default ones.
499 ///
500 /// Appends, so it can be called repeatedly. Adding any nameserver here
501 /// replaces the default public resolvers, which are only used when this
502 /// list is left empty. Has no effect when the fallback mode is
503 /// [`FallbackMode::Never`].
504 pub fn fallback_nameserver_configs(
505 mut self,
506 nameservers: impl IntoIterator<Item = NameserverConfig>,
507 ) -> Self {
508 self.fallback_nameservers.extend(nameservers);
509 self
510 }
511
512 /// Builds the DNS resolver.
513 pub fn build(self) -> DnsResolver {
514 DnsResolver::custom(DefaultResolver(Arc::new(
515 self.into_resolver_builder().build(),
516 )))
517 }
518
519 /// Maps this configuration onto the resolver crate's builder.
520 ///
521 /// Split out from [`Self::build`] so that the mapping can be asserted on
522 /// without a resolver in the way.
523 fn into_resolver_builder(self) -> n0_dns_resolver::Builder {
524 // The resolver crate's builder starts empty, so every source this
525 // builder was asked for is added explicitly.
526 let mut builder = n0_dns_resolver::DnsResolver::builder().nameservers(
527 self.nameservers
528 .into_iter()
529 .map(NameserverConfig::into_resolver_nameserver),
530 );
531 if self.use_system_defaults {
532 builder = builder.use_system_config();
533 }
534 // Unlike the resolver crate, the fallback tier is opt-out here: leaving
535 // the list empty means the default public resolvers rather than none.
536 if let Some(mode) = self.fallback_mode.to_resolver_mode() {
537 builder = builder.fallback_mode(mode);
538 builder = if self.fallback_nameservers.is_empty() {
539 builder.default_fallback_nameservers()
540 } else {
541 builder.fallback_nameservers(
542 self.fallback_nameservers
543 .into_iter()
544 .map(NameserverConfig::into_resolver_nameserver),
545 )
546 };
547 }
548 #[cfg(not(wasm_browser))]
549 if let Some(tls_client_config) = self.tls_client_config {
550 builder = builder.tls_client_config(tls_client_config);
551 }
552 builder
553 }
554}
555
556/// Adapts [`n0_dns_resolver::DnsResolver`] to the [`Resolver`] trait.
557///
558/// Converts the resolver crate's types to this crate's own, so that
559/// `n0-dns-resolver` stays an internal detail rather than part of the public API.
560#[derive(Debug)]
561struct DefaultResolver(Arc<n0_dns_resolver::DnsResolver>);
562
563impl Resolver for DefaultResolver {
564 fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>> {
565 let this = self.0.clone();
566 Box::pin(async move {
567 let list = this.lookup_ipv4(host).await.map_err(map_resolve_error)?;
568 let iter: BoxIter<_> = Box::new(list.into_iter());
569 Ok(iter)
570 })
571 }
572
573 fn lookup_ipv6(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>> {
574 let this = self.0.clone();
575 Box::pin(async move {
576 let list = this.lookup_ipv6(host).await.map_err(map_resolve_error)?;
577 let iter: BoxIter<_> = Box::new(list.into_iter());
578 Ok(iter)
579 })
580 }
581
582 fn lookup_txt(&self, host: String) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>> {
583 let this = self.0.clone();
584 Box::pin(async move {
585 let list = this.lookup_txt(host).await.map_err(map_resolve_error)?;
586 let iter: BoxIter<TxtRecordData> = Box::new(list.into_iter().map(convert_txt));
587 Ok(iter)
588 })
589 }
590
591 fn clear_cache(&self) {
592 self.0.clear_cache();
593 }
594
595 fn reset(&self) -> Box<dyn Resolver> {
596 Box::new(DefaultResolver(Arc::new(self.0.reset())))
597 }
598}
599
600/// Maps an [`n0_dns_resolver::Error`] onto this crate's [`DnsError`].
601///
602/// A private function rather than a `From` impl, so `n0_dns_resolver`'s error
603/// types stay out of this crate's public API. The distinct, matchable causes map
604/// to their own variant; every other cause becomes [`DnsError::Resolve`] carrying
605/// the original error as its [`AnyError`] source, which callers can downcast (see
606/// the [`DnsError`] docs).
607fn map_resolve_error(err: n0_dns_resolver::Error) -> DnsError {
608 use n0_dns_resolver::Error as E;
609 match err {
610 E::Timeout { .. } => e!(DnsError::Timeout),
611 E::NoResponse { .. } => e!(DnsError::NoResponse),
612 E::NxDomain { .. } => e!(DnsError::NxDomain),
613 E::InvalidResponse { .. } => e!(DnsError::InvalidResponse),
614 other => e!(DnsError::Resolve, AnyError::from_stack(other)),
615 }
616}
617
618/// Converts a resolver TXT record into this crate's [`TxtRecordData`].
619///
620/// Both types hold the character-strings as `Box<[Box<[u8]>]>`, so this hands
621/// over the resolver's owned slices directly rather than reallocating each one.
622fn convert_txt(txt: n0_dns_resolver::TxtRecordData) -> TxtRecordData {
623 TxtRecordData(txt.into_boxed_slices())
624}
625
626/// The DNS resolver used throughout `iroh`.
627///
628/// By default, we use a built-in resolver that reads the system's DNS configuration.
629/// The nameservers can be customized by constructing the resolver with [`Self::builder`].
630/// Alternatively, you can create a fully custom DNS resolver by implementing the [`Resolver`]
631/// trait and creating the resolver with [`Self::custom`].
632///
633/// # Usage on Android
634///
635/// The system-defaults reader uses JNI through [`ndk_context`], which must be
636/// initialized with a `JavaVM` and `Application` context before the resolver
637/// is constructed. Glue crates like ndk-glue and android-activity do this
638/// before `main`. Apps that don't use either should call [`install_android_jni_context`]
639/// once at startup, see docs there for details.
640///
641/// If `ndk_context` is not initialized, fetching the system config on Android will fail
642/// and the resolver will use the fallback nameservers instead, subject to the configured
643/// [`FallbackMode`]. Due to how things are implemented in `ndk_context`, detecting the
644/// failure relies on unwinding a panic. If your app uses `panic = "abort"` in its
645/// compilation profile, this doesn't work, so in that case your app will panic if no
646/// JNI context is initialized.
647/// Therefore, either make sure that the JNI context is installed, or don't use
648/// `panic = "abort"`.
649///
650/// [`install_android_jni_context`]: crate::install_android_jni_context
651/// [`ndk_context`]: https://docs.rs/ndk-context
652#[derive(Debug, Clone)]
653pub struct DnsResolver {
654 inner: Arc<Inner>,
655}
656
657/// Shared state behind [`DnsResolver`].
658#[derive(Debug)]
659struct Inner {
660 /// Wakes in-flight [`Self::op`] calls when the resolver is swapped.
661 notify_reset: Notify,
662 resolver: ArcSwap<Box<dyn Resolver>>,
663}
664
665impl Inner {
666 fn new(inner: Box<dyn Resolver>) -> Self {
667 Self {
668 notify_reset: Notify::new(),
669 resolver: ArcSwap::from_pointee(inner),
670 }
671 }
672
673 /// Atomically swaps the resolver and wakes in-flight [`Self::op`] calls.
674 ///
675 /// The swap happens before the wake. An op that observes or misses the wake is
676 /// then guaranteed to load the new resolver. Non-blocking.
677 ///
678 /// Under contention only the first concurrent caller's swap lands; the others
679 /// drop their freshly-built resolver. The winner's notification is enough since
680 /// every in-flight op will pick up the new resolver on its next iteration.
681 fn reset(&self) {
682 let current = self.resolver.load();
683 let new = Arc::new(current.reset());
684 let prev = self.resolver.compare_and_swap(¤t, new);
685 if Arc::ptr_eq(¤t, &prev) {
686 self.notify_reset.notify_waiters();
687 }
688 }
689
690 fn clear_cache(&self) {
691 self.resolver.load().clear_cache();
692 }
693
694 /// Runs `f(resolver)` with a timeout, restarting against the new resolver if
695 /// [`Self::reset`] fires.
696 ///
697 /// Three things race in `biased` order: the lookup completes (returned even if a
698 /// reset happened concurrently, since a successful result is still valid), a reset
699 /// is observed (drop the in-flight future and re-run `f`), or the timeout elapses.
700 ///
701 /// `timeout` is per-attempt. Each retry starts a fresh sleep, so the wall-clock
702 /// total can exceed it if many resets fire. This is intentional: a fresh attempt
703 /// against a just-changed network should not inherit the previous attempt's
704 /// remaining budget.
705 ///
706 /// `f` may be invoked more than once and so must be `Fn`. Captured state must be
707 /// reusable across calls, typically by cloning inside the closure body.
708 ///
709 /// `notified` is enabled before `load_full`. Combined with [`Self::reset`]'s
710 /// swap-then-notify ordering: a wake missed before `enable()` had already been
711 /// preceded by the swap, so the following `load_full` returns the new resolver.
712 async fn op<F, Fut, R, E>(&self, timeout: Duration, f: F) -> Result<R, DnsError>
713 where
714 E: 'static + Send + Into<DnsError>,
715 R: 'static + Send,
716 F: 'static + Send + Fn(Arc<Box<dyn Resolver>>) -> Fut,
717 Fut: 'static + Send + Future<Output = Result<R, E>>,
718 {
719 loop {
720 let notified = self.notify_reset.notified();
721 tokio::pin!(notified);
722 notified.as_mut().enable();
723
724 let timeout = n0_future::time::sleep(timeout);
725 tokio::pin!(timeout);
726
727 let resolver = self.resolver.load_full();
728 let fut = f(resolver);
729 tokio::pin!(fut);
730
731 tokio::select! {
732 biased;
733 res = fut => return res.map_err(Into::into),
734 _ = notified => continue,
735 _ = timeout => return Err(e!(DnsError::Timeout)),
736 }
737 }
738 }
739}
740
741impl DnsResolver {
742 /// Creates a new DNS resolver with sensible cross-platform defaults.
743 ///
744 /// Reads the host system's DNS configuration, with the default public
745 /// resolvers behind it as a fallback tier. Those are queried only when the
746 /// system configuration cannot be read, which happens on some Android
747 /// versions, or when its nameservers do not answer.
748 pub fn new() -> Self {
749 Builder::default().with_system_defaults().build()
750 }
751
752 /// Creates a new DNS resolver configured with a single UDP DNS nameserver.
753 pub fn with_nameserver(nameserver: SocketAddr) -> Self {
754 Builder::default()
755 .add_nameserver_config(
756 NameserverConfig::udp(nameserver.ip()).with_port(nameserver.port()),
757 )
758 .build()
759 }
760
761 /// Creates a builder to construct a DNS resolver with custom options.
762 pub fn builder() -> Builder {
763 Builder::default()
764 }
765
766 /// Creates a new [`DnsResolver`] from a struct that implements [`Resolver`].
767 ///
768 /// If you need more customization for DNS resolving than the [`Builder`] allows, you can
769 /// implement the [`Resolver`] trait on a struct and implement DNS resolution
770 /// however you see fit.
771 pub fn custom(resolver: impl Resolver) -> Self {
772 Self {
773 inner: Arc::new(Inner::new(Box::new(resolver))),
774 }
775 }
776
777 /// Removes all entries from the cache.
778 pub fn clear_cache(&self) {
779 self.inner.clear_cache();
780 }
781
782 /// Replaces the inner resolver with a freshly-built one.
783 ///
784 /// Call this on a major host network change to pick up the new system DNS
785 /// configuration and rebind sockets. The swap is atomic and non-blocking;
786 /// in-flight lookups retry against the new resolver. See [`Resolver::reset`].
787 pub fn reset(&self) {
788 self.inner.reset();
789 }
790
791 /// Looks up a TXT record.
792 pub async fn lookup_txt<T: ToString>(
793 &self,
794 host: T,
795 timeout: Duration,
796 ) -> Result<impl Iterator<Item = TxtRecordData>, DnsError> {
797 let host = host.to_string();
798 let res = self
799 .inner
800 .op(timeout, move |resolver| resolver.lookup_txt(host.clone()))
801 .await?;
802 Ok(res)
803 }
804
805 /// Performs an IPv4 lookup with a timeout.
806 pub async fn lookup_ipv4<T: ToString>(
807 &self,
808 host: T,
809 timeout: Duration,
810 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
811 let host = host.to_string();
812 let addrs = self
813 .inner
814 .op(timeout, move |resolver| resolver.lookup_ipv4(host.clone()))
815 .await?;
816 Ok(addrs.into_iter().map(IpAddr::V4))
817 }
818
819 /// Performs an IPv6 lookup with a timeout.
820 pub async fn lookup_ipv6<T: ToString>(
821 &self,
822 host: T,
823 timeout: Duration,
824 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
825 let host = host.to_string();
826 let addrs = self
827 .inner
828 .op(timeout, move |resolver| resolver.lookup_ipv6(host.clone()))
829 .await?;
830 Ok(addrs.into_iter().map(IpAddr::V6))
831 }
832
833 /// Resolves IPv4 and IPv6 in parallel with a timeout.
834 ///
835 /// `LookupIpStrategy::Ipv4AndIpv6` will wait for ipv6 resolution timeout, even if it is
836 /// not usable on the stack, so we manually query both lookups concurrently and time them out
837 /// individually.
838 pub async fn lookup_ipv4_ipv6<T: ToString>(
839 &self,
840 host: T,
841 timeout: Duration,
842 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
843 let host = host.to_string();
844 let res = tokio::join!(
845 self.lookup_ipv4(host.clone(), timeout),
846 self.lookup_ipv6(host, timeout)
847 );
848
849 match res {
850 (Ok(ipv4), Ok(ipv6)) => Ok(LookupIter::Both(ipv4.chain(ipv6))),
851 (Ok(ipv4), Err(_)) => Ok(LookupIter::Ipv4(ipv4)),
852 (Err(_), Ok(ipv6)) => Ok(LookupIter::Ipv6(ipv6)),
853 (Err(ipv4_err), Err(ipv6_err)) => Err(e!(DnsError::ResolveBoth {
854 ipv4: Box::new(ipv4_err),
855 ipv6: Box::new(ipv6_err)
856 })),
857 }
858 }
859
860 /// Resolves a hostname from a URL to an IP address.
861 pub async fn resolve_host(
862 &self,
863 url: &Url,
864 prefer_ipv6: bool,
865 timeout: Duration,
866 ) -> Result<IpAddr, DnsError> {
867 let host = url.host().ok_or_else(|| e!(DnsError::MissingHost))?;
868 match host {
869 url::Host::Domain(domain) => {
870 // Need to do a DNS lookup
871 let lookup = tokio::join!(
872 self.lookup_ipv4(domain, timeout),
873 self.lookup_ipv6(domain, timeout)
874 );
875 let (v4, v6) = match lookup {
876 (Err(ipv4_err), Err(ipv6_err)) => {
877 return Err(e!(DnsError::ResolveBoth {
878 ipv4: Box::new(ipv4_err),
879 ipv6: Box::new(ipv6_err)
880 }));
881 }
882 (Err(_), Ok(mut v6)) => (None, v6.next()),
883 (Ok(mut v4), Err(_)) => (v4.next(), None),
884 (Ok(mut v4), Ok(mut v6)) => (v4.next(), v6.next()),
885 };
886 if prefer_ipv6 {
887 v6.or(v4).ok_or_else(|| e!(DnsError::NoResponse))
888 } else {
889 v4.or(v6).ok_or_else(|| e!(DnsError::NoResponse))
890 }
891 }
892 url::Host::Ipv4(ip) => Ok(IpAddr::V4(ip)),
893 url::Host::Ipv6(ip) => Ok(IpAddr::V6(ip)),
894 }
895 }
896
897 /// Resolves a hostname from a URL to its IP addresses, streamed as they resolve.
898 ///
899 /// IPv4 and IPv6 are looked up concurrently and each address is yielded as
900 /// soon as its lookup completes, so a caller can start dialing without
901 /// waiting for both families.
902 ///
903 /// A lookup failure is swallowed as long as the other family yields an address.
904 /// Only if both lookups fail does the stream yield a single [`DnsError`].
905 ///
906 /// The stream ends once both lookups have finished and every address or the error
907 /// have been yielded.
908 pub fn resolve_host_all<'a>(
909 &'a self,
910 url: &Url,
911 timeout: Duration,
912 ) -> impl Stream<Item = Result<IpAddr, DnsError>> + Send + 'a {
913 let host = match url.host() {
914 None => {
915 return Either::Left(stream::once(Err(e!(DnsError::MissingHost))));
916 }
917 Some(url::Host::Ipv4(ip)) => {
918 return Either::Left(stream::once(Ok(IpAddr::V4(ip))));
919 }
920 Some(url::Host::Ipv6(ip)) => {
921 return Either::Left(stream::once(Ok(IpAddr::V6(ip))));
922 }
923 Some(url::Host::Domain(domain)) => domain.to_string(),
924 };
925
926 type Lookup<'a, A> =
927 Pin<Box<dyn Future<Output = Result<BoxIter<A>, DnsError>> + Send + 'a>>;
928
929 struct State<'a> {
930 v4_fut: MaybeFuture<Lookup<'a, Ipv4Addr>>,
931 v6_fut: MaybeFuture<Lookup<'a, Ipv6Addr>>,
932 v4_err: Option<DnsError>,
933 v6_err: Option<DnsError>,
934 queue: VecDeque<IpAddr>,
935 closed: bool,
936 yielded: bool,
937 }
938
939 let state = State {
940 v4_fut: MaybeFuture::Some(Box::pin({
941 let host = host.clone();
942 self.inner.op(timeout, move |r| r.lookup_ipv4(host.clone()))
943 })),
944 v6_fut: MaybeFuture::Some(Box::pin({
945 let host = host.clone();
946 self.inner.op(timeout, move |r| r.lookup_ipv6(host.clone()))
947 })),
948 v4_err: None,
949 v6_err: None,
950 queue: VecDeque::new(),
951 closed: false,
952 yielded: false,
953 };
954
955 Either::Right(stream::unfold(state, async |mut state| {
956 loop {
957 if state.closed {
958 return None;
959 }
960
961 if let Some(item) = state.queue.pop_front() {
962 state.yielded = true;
963 return Some((Ok(item), state));
964 }
965
966 // Return final error item once both futures completed, or None if items were yielded.
967 if state.v4_fut.is_none() && state.v6_fut.is_none() {
968 state.closed = true;
969 if let (Some(v4), Some(v6)) = (state.v4_err.take(), state.v6_err.take()) {
970 let error = e!(DnsError::ResolveBoth {
971 ipv4: Box::new(v4),
972 ipv6: Box::new(v6),
973 });
974 return Some((Err(error), state));
975 } else if !state.yielded {
976 return Some((Err(e!(DnsError::NoResponse)), state));
977 } else {
978 return None;
979 }
980 }
981 tokio::select! {
982 // We don't actually care about polling order, but `biased` saves the randomization cost.
983 biased;
984 res = &mut state.v4_fut => {
985 match res {
986 Ok(items) => state.queue.extend(items.map(IpAddr::V4)),
987 Err(err) => state.v4_err = Some(err),
988 }
989 }
990 res = &mut state.v6_fut => {
991 match res {
992 Ok(items) => state.queue.extend(items.map(IpAddr::V6)),
993 Err(err) => state.v6_err = Some(err),
994 }
995 }
996 }
997 }
998 }))
999 }
1000
1001 /// Performs an IPv4 lookup with a timeout in a staggered fashion.
1002 ///
1003 /// From the moment this function is called, each lookup is scheduled after the delays in
1004 /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
1005 /// at T+0ms, T+200ms and T+300ms. The `timeout` is applied to each call individually. The
1006 /// result of the first successful call is returned, or a summary of all errors otherwise.
1007 pub async fn lookup_ipv4_staggered(
1008 &self,
1009 host: impl ToString,
1010 timeout: Duration,
1011 delays_ms: &[u64],
1012 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
1013 let host = host.to_string();
1014 let f = || self.lookup_ipv4(host.clone(), timeout);
1015 stagger_call(f, delays_ms).await
1016 }
1017
1018 /// Performs an IPv6 lookup with a timeout in a staggered fashion.
1019 ///
1020 /// From the moment this function is called, each lookup is scheduled after the delays in
1021 /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
1022 /// at T+0ms, T+200ms and T+300ms. The `timeout` is applied to each call individually. The
1023 /// result of the first successful call is returned, or a summary of all errors otherwise.
1024 pub async fn lookup_ipv6_staggered(
1025 &self,
1026 host: impl ToString,
1027 timeout: Duration,
1028 delays_ms: &[u64],
1029 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
1030 let host = host.to_string();
1031 let f = || self.lookup_ipv6(host.clone(), timeout);
1032 stagger_call(f, delays_ms).await
1033 }
1034
1035 /// Races an IPv4 and IPv6 lookup with a timeout in a staggered fashion.
1036 ///
1037 /// From the moment this function is called, each lookup is scheduled after the delays in
1038 /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
1039 /// at T+0ms, T+200ms and T+300ms. The `timeout` is applied as stated in
1040 /// [`Self::lookup_ipv4_ipv6`]. The result of the first successful call is returned, or a
1041 /// summary of all errors otherwise.
1042 pub async fn lookup_ipv4_ipv6_staggered(
1043 &self,
1044 host: impl ToString,
1045 timeout: Duration,
1046 delays_ms: &[u64],
1047 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
1048 let host = host.to_string();
1049 let f = || self.lookup_ipv4_ipv6(host.clone(), timeout);
1050 stagger_call(f, delays_ms).await
1051 }
1052
1053 /// Looks up endpoint info by [`EndpointId`] and origin domain name.
1054 ///
1055 /// To lookup endpoints that published their endpoint info to the DNS servers run by n0,
1056 /// pass [`N0_DNS_ENDPOINT_ORIGIN_PROD`] as `origin`.
1057 pub async fn lookup_endpoint_by_id(
1058 &self,
1059 endpoint_id: &EndpointId,
1060 origin: &str,
1061 ) -> Result<EndpointInfo, LookupError> {
1062 let name = format!("_iroh.{}.{}", endpoint_id.to_z32(), origin);
1063 let lookup = self.lookup_txt(name.clone(), DNS_TIMEOUT).await?;
1064 let info = EndpointInfo::from_txt_lookup(name, lookup)?;
1065 Ok(info)
1066 }
1067
1068 /// Looks up endpoint info by DNS name.
1069 pub async fn lookup_endpoint_by_domain_name(
1070 &self,
1071 name: &str,
1072 ) -> Result<EndpointInfo, LookupError> {
1073 let name = if name.starts_with("_iroh.") {
1074 name.to_string()
1075 } else {
1076 format!("_iroh.{name}")
1077 };
1078 let lookup = self.lookup_txt(name.clone(), DNS_TIMEOUT).await?;
1079 let info = EndpointInfo::from_txt_lookup(name, lookup)?;
1080 Ok(info)
1081 }
1082
1083 /// Looks up endpoint info by DNS name in a staggered fashion.
1084 ///
1085 /// From the moment this function is called, each lookup is scheduled after the delays in
1086 /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
1087 /// at T+0ms, T+200ms and T+300ms. The result of the first successful call is returned, or a
1088 /// summary of all errors otherwise.
1089 pub async fn lookup_endpoint_by_domain_name_staggered(
1090 &self,
1091 name: &str,
1092 delays_ms: &[u64],
1093 ) -> Result<EndpointInfo, StaggeredError<LookupError>> {
1094 let f = || self.lookup_endpoint_by_domain_name(name);
1095 stagger_call(f, delays_ms).await
1096 }
1097
1098 /// Looks up endpoint info by [`EndpointId`] and origin domain name.
1099 ///
1100 /// From the moment this function is called, each lookup is scheduled after the delays in
1101 /// `delays_ms` with the first call being done immediately. `[200ms, 300ms]` results in calls
1102 /// at T+0ms, T+200ms and T+300ms. The result of the first successful call is returned, or a
1103 /// summary of all errors otherwise.
1104 pub async fn lookup_endpoint_by_id_staggered(
1105 &self,
1106 endpoint_id: &EndpointId,
1107 origin: &str,
1108 delays_ms: &[u64],
1109 ) -> Result<EndpointInfo, StaggeredError<LookupError>> {
1110 let f = || self.lookup_endpoint_by_id(endpoint_id, origin);
1111 stagger_call(f, delays_ms).await
1112 }
1113}
1114
1115impl Default for DnsResolver {
1116 fn default() -> Self {
1117 Self::new()
1118 }
1119}
1120
1121/// Record data for a TXT record.
1122///
1123/// This contains a list of character strings, as defined in [RFC 1035 Section 3.3.14].
1124///
1125/// [`TxtRecordData`] implements [`fmt::Display`], so you can call [`ToString::to_string`] to
1126/// convert the record data into a string. This will parse each character string with
1127/// [`String::from_utf8_lossy`] and then concatenate all strings without a separator.
1128///
1129/// If you want to process each character string individually, use [`Self::iter`].
1130///
1131/// [RFC 1035 Section 3.3.14]: https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14
1132#[derive(Debug, Clone)]
1133pub struct TxtRecordData(Box<[Box<[u8]>]>);
1134
1135impl TxtRecordData {
1136 /// Returns an iterator over the character strings contained in this TXT record.
1137 pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
1138 self.0.iter().map(|x| x.as_ref())
1139 }
1140}
1141
1142impl fmt::Display for TxtRecordData {
1143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1144 for s in self.iter() {
1145 write!(f, "{}", String::from_utf8_lossy(s))?
1146 }
1147 Ok(())
1148 }
1149}
1150
1151impl FromIterator<Box<[u8]>> for TxtRecordData {
1152 fn from_iter<T: IntoIterator<Item = Box<[u8]>>>(iter: T) -> Self {
1153 Self(iter.into_iter().collect())
1154 }
1155}
1156
1157impl From<Vec<Box<[u8]>>> for TxtRecordData {
1158 fn from(value: Vec<Box<[u8]>>) -> Self {
1159 Self(value.into_boxed_slice())
1160 }
1161}
1162
1163impl From<Vec<String>> for TxtRecordData {
1164 fn from(value: Vec<String>) -> Self {
1165 Self(
1166 value
1167 .into_iter()
1168 .map(|s| s.into_bytes().into_boxed_slice())
1169 .collect(),
1170 )
1171 }
1172}
1173
1174/// Helper enum to give a unified type to the iterators of [`DnsResolver::lookup_ipv4_ipv6`].
1175enum LookupIter<A, B> {
1176 Ipv4(A),
1177 Ipv6(B),
1178 Both(std::iter::Chain<A, B>),
1179}
1180
1181impl<A: Iterator<Item = IpAddr>, B: Iterator<Item = IpAddr>> Iterator for LookupIter<A, B> {
1182 type Item = IpAddr;
1183
1184 fn next(&mut self) -> Option<Self::Item> {
1185 match self {
1186 LookupIter::Ipv4(iter) => iter.next(),
1187 LookupIter::Ipv6(iter) => iter.next(),
1188 LookupIter::Both(iter) => iter.next(),
1189 }
1190 }
1191}
1192
1193/// Staggers calls to the future F with the given delays.
1194///
1195/// The first call is performed immediately. The first call to succeed generates an Ok result
1196/// ignoring any previous error. If all calls fail, an error summarizing all errors is returned.
1197async fn stagger_call<
1198 T,
1199 E: StackError + 'static,
1200 F: Fn() -> Fut,
1201 Fut: Future<Output = Result<T, E>>,
1202>(
1203 f: F,
1204 delays_ms: &[u64],
1205) -> Result<T, StaggeredError<E>> {
1206 let mut calls = n0_future::FuturesUnorderedBounded::new(delays_ms.len() + 1);
1207 // NOTE: we add the 0 delay here to have a uniform set of futures. This is more performant than
1208 // using alternatives that allow futures of different types.
1209 for delay in std::iter::once(&0u64).chain(delays_ms) {
1210 let delay = add_jitter(delay);
1211 let fut = f();
1212 let staggered_fut = async move {
1213 time::sleep(delay).await;
1214 fut.await
1215 };
1216 calls.push(staggered_fut)
1217 }
1218
1219 let mut errors = vec![];
1220 while let Some(call_result) = calls.next().await {
1221 match call_result {
1222 Ok(t) => return Ok(t),
1223 Err(e) => errors.push(e),
1224 }
1225 }
1226
1227 Err(e!(StaggeredError { errors }))
1228}
1229
1230fn add_jitter(delay: &u64) -> Duration {
1231 // If delay is 0, return 0 immediately.
1232 if *delay == 0 {
1233 return Duration::ZERO;
1234 }
1235
1236 // Calculate jitter as a random value in the range of +/- MAX_JITTER_PERCENT of the delay.
1237 let max_jitter = delay.saturating_mul(MAX_JITTER_PERCENT * 2) / 100;
1238 let jitter = rand::random::<u64>() % max_jitter;
1239
1240 Duration::from_millis(delay.saturating_sub(max_jitter / 2).saturating_add(jitter))
1241}
1242
1243#[cfg(test)]
1244pub(crate) mod tests {
1245 use std::sync::atomic::AtomicUsize;
1246
1247 use n0_tracing_test::traced_test;
1248
1249 use super::*;
1250
1251 #[test]
1252 fn builder_named_nameservers_carry_server_name() {
1253 let addr = SocketAddr::new(std::net::Ipv4Addr::new(1, 1, 1, 1).into(), 443);
1254 let builder = Builder::default()
1255 .add_nameserver_config(
1256 NameserverConfig::https(addr.ip()).with_tls_server_name("cloudflare-dns.com"),
1257 )
1258 .add_nameserver_config(
1259 NameserverConfig::tls(addr.ip())
1260 .with_port(addr.port())
1261 .with_tls_server_name("cloudflare-dns.com"),
1262 )
1263 .fallback_nameserver_configs([
1264 NameserverConfig::https(addr.ip()).with_tls_server_name("fallback.example.com")
1265 ]);
1266 let ns = &builder.nameservers;
1267 assert_eq!(ns[0].protocol, DnsProtocol::Https);
1268 assert_eq!(ns[0].addr.port(), 443);
1269 assert_eq!(ns[0].server_name.as_deref(), Some("cloudflare-dns.com"));
1270 assert_eq!(ns[1].protocol, DnsProtocol::Tls);
1271 assert_eq!(ns[1].addr.port(), 443);
1272 assert_eq!(ns[1].server_name.as_deref(), Some("cloudflare-dns.com"));
1273 assert_eq!(
1274 builder.fallback_nameservers[0].server_name.as_deref(),
1275 Some("fallback.example.com")
1276 );
1277 }
1278
1279 /// Collects the nameserver addresses the resolver crate ends up configured
1280 /// with, for a builder that reads nothing from the host.
1281 fn resolver_nameservers(builder: Builder) -> Vec<SocketAddr> {
1282 builder
1283 .into_resolver_builder()
1284 .build()
1285 .configured_nameservers()
1286 .iter()
1287 .map(|ns| ns.addr())
1288 .collect()
1289 }
1290
1291 /// An empty fallback list means the default public resolvers, not none.
1292 ///
1293 /// The resolver crate's fallback tier starts empty, so this mapping is ours
1294 /// to keep: leaving the list unset here has to opt into its defaults.
1295 #[test]
1296 fn empty_fallback_list_uses_public_resolvers() {
1297 let addrs = resolver_nameservers(Builder::default());
1298 assert!(!addrs.is_empty());
1299 assert!(addrs.iter().any(|addr| addr.ip() == CLOUDFLARE_IP));
1300 }
1301
1302 /// `disable_fallback` leaves the resolver with no nameservers at all.
1303 #[test]
1304 fn disable_fallback_drops_the_public_resolvers() {
1305 let addrs = resolver_nameservers(Builder::default().disable_fallback());
1306 assert!(addrs.is_empty(), "{addrs:?}");
1307 }
1308
1309 /// An explicit fallback list replaces the defaults rather than adding to them.
1310 #[test]
1311 fn explicit_fallback_list_replaces_the_defaults() {
1312 let custom = SocketAddr::new(std::net::Ipv4Addr::new(192, 0, 2, 1).into(), 53);
1313 let addrs = resolver_nameservers(
1314 Builder::default().fallback_nameserver_configs([NameserverConfig::udp(custom.ip())]),
1315 );
1316 assert_eq!(addrs, vec![custom]);
1317 }
1318
1319 const CLOUDFLARE_IP: IpAddr = IpAddr::V4(std::net::Ipv4Addr::new(1, 1, 1, 1));
1320
1321 #[tokio::test]
1322 #[traced_test]
1323 async fn stagger_basic() {
1324 const CALL_RESULTS: &[Result<u8, u8>] = &[Err(2), Ok(3), Ok(5), Ok(7)];
1325 static DONE_CALL: AtomicUsize = AtomicUsize::new(0);
1326 let f = || {
1327 let r_pos = DONE_CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1328 async move {
1329 tracing::info!(r_pos, "call");
1330 CALL_RESULTS[r_pos].map_err(|_| e!(DnsError::InvalidResponse))
1331 }
1332 };
1333
1334 let delays = [1000, 15];
1335 let result = stagger_call(f, &delays).await.unwrap();
1336 assert_eq!(result, 5)
1337 }
1338
1339 #[test]
1340 #[traced_test]
1341 fn jitter_test_zero() {
1342 let jittered_delay = add_jitter(&0);
1343 assert_eq!(jittered_delay, Duration::from_secs(0));
1344 }
1345
1346 //Sanity checks that I did the math right
1347 #[test]
1348 #[traced_test]
1349 fn jitter_test_nonzero_lower_bound() {
1350 let delay: u64 = 300;
1351 for _ in 0..100 {
1352 assert!(add_jitter(&delay) >= Duration::from_millis(delay * 8 / 10));
1353 }
1354 }
1355
1356 #[test]
1357 #[traced_test]
1358 fn jitter_test_nonzero_upper_bound() {
1359 let delay: u64 = 300;
1360 for _ in 0..100 {
1361 assert!(add_jitter(&delay) < Duration::from_millis(delay * 12 / 10));
1362 }
1363 }
1364
1365 #[tokio::test]
1366 #[traced_test]
1367 async fn custom_resolver() {
1368 #[derive(Debug)]
1369 struct MyResolver;
1370 impl Resolver for MyResolver {
1371 fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>> {
1372 Box::pin(async move {
1373 let addr = if host == "foo.example" {
1374 Ipv4Addr::new(1, 1, 1, 1)
1375 } else {
1376 return Err(e!(DnsError::NoResponse));
1377 };
1378 let iter: BoxIter<Ipv4Addr> = Box::new(vec![addr].into_iter());
1379 Ok(iter)
1380 })
1381 }
1382
1383 fn lookup_ipv6(&self, _host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>> {
1384 todo!()
1385 }
1386
1387 fn lookup_txt(
1388 &self,
1389 _host: String,
1390 ) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>> {
1391 todo!()
1392 }
1393
1394 fn clear_cache(&self) {
1395 todo!()
1396 }
1397
1398 fn reset(&self) -> Box<dyn Resolver> {
1399 todo!()
1400 }
1401 }
1402
1403 let resolver = DnsResolver::custom(MyResolver);
1404 let mut iter = resolver
1405 .lookup_ipv4("foo.example", Duration::from_secs(1))
1406 .await
1407 .expect("not to fail");
1408 let addr = iter.next().expect("one result");
1409 assert_eq!(addr, "1.1.1.1".parse::<IpAddr>().unwrap());
1410
1411 let res = resolver
1412 .lookup_ipv4("bar.example", Duration::from_secs(1))
1413 .await;
1414 assert!(matches!(res, Err(DnsError::NoResponse { .. })))
1415 }
1416}