1use 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::NodeId;
16use n0_future::{
17 StreamExt,
18 boxed::BoxFuture,
19 time::{self, Duration},
20};
21use nested_enum_utils::common_fields;
22use snafu::{Backtrace, GenerateImplicitData, OptionExt, ResultExt, Snafu};
23use tokio::sync::RwLock;
24use tracing::debug;
25use url::Url;
26
27use crate::{
28 defaults::timeouts::DNS_TIMEOUT,
29 node_info::{self, NodeInfo, ParseError},
30};
31
32pub const N0_DNS_NODE_ORIGIN_PROD: &str = "dns.iroh.link";
34pub const N0_DNS_NODE_ORIGIN_STAGING: &str = "staging-dns.iroh.link";
36
37const MAX_JITTER_PERCENT: u64 = 20;
39
40pub trait Resolver: fmt::Debug + Send + Sync + 'static {
42 fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>>;
44
45 fn lookup_ipv6(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>>;
47
48 fn lookup_txt(&self, host: String) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>>;
50
51 fn clear_cache(&self);
53
54 fn reset(&mut self);
59}
60
61pub type BoxIter<T> = Box<dyn Iterator<Item = T> + Send + 'static>;
63
64#[common_fields({
66 backtrace: Option<Backtrace>,
67 #[snafu(implicit)]
68 span_trace: n0_snafu::SpanTrace,
69})]
70#[allow(missing_docs)]
71#[derive(Debug, Snafu)]
72#[non_exhaustive]
73#[snafu(visibility(pub(crate)))]
74pub enum DnsError {
75 #[snafu(transparent)]
76 Timeout { source: tokio::time::error::Elapsed },
77 #[snafu(display("No response"))]
78 NoResponse {},
79 #[snafu(display("Resolve failed ipv4: {ipv4}, ipv6 {ipv6}"))]
80 ResolveBoth {
81 ipv4: Box<DnsError>,
82 ipv6: Box<DnsError>,
83 },
84 #[snafu(display("missing host"))]
85 MissingHost {},
86 #[snafu(transparent)]
87 Resolve {
88 source: hickory_resolver::ResolveError,
89 },
90 #[snafu(display("invalid DNS response: not a query for _iroh.z32encodedpubkey"))]
91 InvalidResponse {},
92}
93
94#[cfg(not(wasm_browser))]
95#[common_fields({
96 backtrace: Option<Backtrace>,
97 #[snafu(implicit)]
98 span_trace: n0_snafu::SpanTrace,
99})]
100#[allow(missing_docs)]
101#[derive(Debug, Snafu)]
102#[non_exhaustive]
103#[snafu(visibility(pub(crate)))]
104pub enum LookupError {
105 #[snafu(display("Malformed txt from lookup"))]
106 ParseError {
107 #[snafu(source(from(ParseError, Box::new)))]
108 source: Box<ParseError>,
109 },
110 #[snafu(display("Failed to resolve TXT record"))]
111 LookupFailed {
112 #[snafu(source(from(DnsError, Box::new)))]
113 source: Box<DnsError>,
114 },
115}
116
117#[allow(missing_docs)]
119#[derive(Debug, Snafu)]
120#[snafu(module)]
121#[snafu(display("no calls succeeded: [{}]", errors.iter().map(|e| e.to_string()).collect::<Vec<_>>().join("")))]
122pub struct StaggeredError<E: std::fmt::Debug + std::fmt::Display> {
123 backtrace: Option<Backtrace>,
124 #[snafu(implicit)]
125 span_trace: n0_snafu::SpanTrace,
126 errors: Vec<E>,
127}
128
129impl<E: std::fmt::Debug + std::fmt::Display> StaggeredError<E> {
130 pub(crate) fn new(errors: Vec<E>) -> Self {
131 Self {
132 errors,
133 backtrace: GenerateImplicitData::generate(),
134 span_trace: n0_snafu::SpanTrace::generate(),
135 }
136 }
137}
138
139#[derive(Debug, Clone, Default)]
141pub struct Builder {
142 use_system_defaults: bool,
143 nameservers: Vec<(SocketAddr, DnsProtocol)>,
144}
145
146#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
148#[non_exhaustive]
149pub enum DnsProtocol {
150 #[default]
154 Udp,
155 Tcp,
159 Tls,
165 Https,
171}
172
173impl DnsProtocol {
174 fn to_hickory(self) -> hickory_resolver::proto::xfer::Protocol {
175 use hickory_resolver::proto::xfer::Protocol;
176 match self {
177 DnsProtocol::Udp => Protocol::Udp,
178 DnsProtocol::Tcp => Protocol::Tcp,
179 DnsProtocol::Tls => Protocol::Tls,
180 DnsProtocol::Https => Protocol::Https,
181 }
182 }
183}
184
185impl Builder {
186 pub fn with_system_defaults(mut self) -> Self {
192 self.use_system_defaults = true;
193 self
194 }
195
196 pub fn with_nameserver(mut self, addr: SocketAddr, protocol: DnsProtocol) -> Self {
198 self.nameservers.push((addr, protocol));
199 self
200 }
201
202 pub fn with_nameservers(
204 mut self,
205 nameservers: impl IntoIterator<Item = (SocketAddr, DnsProtocol)>,
206 ) -> Self {
207 self.nameservers.extend(nameservers);
208 self
209 }
210
211 pub fn build(self) -> DnsResolver {
213 let resolver = HickoryResolver::new(self);
214 DnsResolver(DnsResolverInner::Hickory(Arc::new(RwLock::new(resolver))))
215 }
216}
217
218#[derive(Debug, Clone)]
220pub struct DnsResolver(DnsResolverInner);
221
222impl DnsResolver {
223 pub fn new() -> Self {
229 Builder::default().with_system_defaults().build()
230 }
231
232 pub fn with_nameserver(nameserver: SocketAddr) -> Self {
234 Builder::default()
235 .with_nameserver(nameserver, DnsProtocol::Udp)
236 .build()
237 }
238
239 pub fn builder() -> Builder {
241 Builder::default()
242 }
243
244 pub fn custom(resolver: impl Resolver) -> Self {
250 Self(DnsResolverInner::Custom(Arc::new(RwLock::new(resolver))))
251 }
252
253 pub async fn clear_cache(&self) {
255 self.0.clear_cache().await
256 }
257
258 pub async fn reset(&self) {
260 self.0.reset().await
261 }
262
263 pub async fn lookup_txt<T: ToString>(
265 &self,
266 host: T,
267 timeout: Duration,
268 ) -> Result<impl Iterator<Item = TxtRecordData>, DnsError> {
269 let host = host.to_string();
270 let res = time::timeout(timeout, self.0.lookup_txt(host)).await??;
271 Ok(res)
272 }
273
274 pub async fn lookup_ipv4<T: ToString>(
276 &self,
277 host: T,
278 timeout: Duration,
279 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
280 let host = host.to_string();
281 let addrs = time::timeout(timeout, self.0.lookup_ipv4(host)).await??;
282 Ok(addrs.into_iter().map(IpAddr::V4))
283 }
284
285 pub async fn lookup_ipv6<T: ToString>(
287 &self,
288 host: T,
289 timeout: Duration,
290 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
291 let host = host.to_string();
292 let addrs = time::timeout(timeout, self.0.lookup_ipv6(host)).await??;
293 Ok(addrs.into_iter().map(IpAddr::V6))
294 }
295
296 pub async fn lookup_ipv4_ipv6<T: ToString>(
302 &self,
303 host: T,
304 timeout: Duration,
305 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
306 let host = host.to_string();
307 let res = tokio::join!(
308 self.lookup_ipv4(host.clone(), timeout),
309 self.lookup_ipv6(host, timeout)
310 );
311
312 match res {
313 (Ok(ipv4), Ok(ipv6)) => Ok(LookupIter::Both(ipv4.chain(ipv6))),
314 (Ok(ipv4), Err(_)) => Ok(LookupIter::Ipv4(ipv4)),
315 (Err(_), Ok(ipv6)) => Ok(LookupIter::Ipv6(ipv6)),
316 (Err(ipv4_err), Err(ipv6_err)) => Err(ResolveBothSnafu {
317 ipv4: Box::new(ipv4_err),
318 ipv6: Box::new(ipv6_err),
319 }
320 .build()),
321 }
322 }
323
324 pub async fn resolve_host(
326 &self,
327 url: &Url,
328 prefer_ipv6: bool,
329 timeout: Duration,
330 ) -> Result<IpAddr, DnsError> {
331 let host = url.host().context(MissingHostSnafu)?;
332 match host {
333 url::Host::Domain(domain) => {
334 let lookup = tokio::join!(
336 self.lookup_ipv4(domain, timeout),
337 self.lookup_ipv6(domain, timeout)
338 );
339 let (v4, v6) = match lookup {
340 (Err(ipv4_err), Err(ipv6_err)) => {
341 return Err(ResolveBothSnafu {
342 ipv4: Box::new(ipv4_err),
343 ipv6: Box::new(ipv6_err),
344 }
345 .build());
346 }
347 (Err(_), Ok(mut v6)) => (None, v6.next()),
348 (Ok(mut v4), Err(_)) => (v4.next(), None),
349 (Ok(mut v4), Ok(mut v6)) => (v4.next(), v6.next()),
350 };
351 if prefer_ipv6 {
352 v6.or(v4).context(NoResponseSnafu)
353 } else {
354 v4.or(v6).context(NoResponseSnafu)
355 }
356 }
357 url::Host::Ipv4(ip) => Ok(IpAddr::V4(ip)),
358 url::Host::Ipv6(ip) => Ok(IpAddr::V6(ip)),
359 }
360 }
361
362 pub async fn lookup_ipv4_staggered(
369 &self,
370 host: impl ToString,
371 timeout: Duration,
372 delays_ms: &[u64],
373 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
374 let host = host.to_string();
375 let f = || self.lookup_ipv4(host.clone(), timeout);
376 stagger_call(f, delays_ms).await
377 }
378
379 pub async fn lookup_ipv6_staggered(
386 &self,
387 host: impl ToString,
388 timeout: Duration,
389 delays_ms: &[u64],
390 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
391 let host = host.to_string();
392 let f = || self.lookup_ipv6(host.clone(), timeout);
393 stagger_call(f, delays_ms).await
394 }
395
396 pub async fn lookup_ipv4_ipv6_staggered(
404 &self,
405 host: impl ToString,
406 timeout: Duration,
407 delays_ms: &[u64],
408 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
409 let host = host.to_string();
410 let f = || self.lookup_ipv4_ipv6(host.clone(), timeout);
411 stagger_call(f, delays_ms).await
412 }
413
414 pub async fn lookup_node_by_id(
419 &self,
420 node_id: &NodeId,
421 origin: &str,
422 ) -> Result<NodeInfo, LookupError> {
423 let name = node_info::node_domain(node_id, origin);
424 let name = node_info::ensure_iroh_txt_label(name);
425 let lookup = self
426 .lookup_txt(name.clone(), DNS_TIMEOUT)
427 .await
428 .context(LookupFailedSnafu)?;
429 let info = NodeInfo::from_txt_lookup(name, lookup).context(ParseSnafu)?;
430 Ok(info)
431 }
432
433 pub async fn lookup_node_by_domain_name(&self, name: &str) -> Result<NodeInfo, LookupError> {
435 let name = node_info::ensure_iroh_txt_label(name.to_string());
436 let lookup = self
437 .lookup_txt(name.clone(), DNS_TIMEOUT)
438 .await
439 .context(LookupFailedSnafu)?;
440 let info = NodeInfo::from_txt_lookup(name, lookup).context(ParseSnafu)?;
441 Ok(info)
442 }
443
444 pub async fn lookup_node_by_domain_name_staggered(
451 &self,
452 name: &str,
453 delays_ms: &[u64],
454 ) -> Result<NodeInfo, StaggeredError<LookupError>> {
455 let f = || self.lookup_node_by_domain_name(name);
456 stagger_call(f, delays_ms).await
457 }
458
459 pub async fn lookup_node_by_id_staggered(
466 &self,
467 node_id: &NodeId,
468 origin: &str,
469 delays_ms: &[u64],
470 ) -> Result<NodeInfo, StaggeredError<LookupError>> {
471 let f = || self.lookup_node_by_id(node_id, origin);
472 stagger_call(f, delays_ms).await
473 }
474}
475
476impl Default for DnsResolver {
477 fn default() -> Self {
478 Self::new()
479 }
480}
481
482impl reqwest::dns::Resolve for DnsResolver {
483 fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving {
484 let this = self.clone();
485 let name = name.as_str().to_string();
486 Box::pin(async move {
487 let res = this.lookup_ipv4_ipv6(name, DNS_TIMEOUT).await;
488 match res {
489 Ok(addrs) => {
490 let addrs: reqwest::dns::Addrs =
491 Box::new(addrs.map(|addr| SocketAddr::new(addr, 0)));
492 Ok(addrs)
493 }
494 Err(err) => {
495 let err: Box<dyn std::error::Error + Send + Sync> = Box::new(err);
496 Err(err)
497 }
498 }
499 })
500 }
501}
502
503#[derive(Debug, Clone)]
508enum DnsResolverInner {
509 Hickory(Arc<RwLock<HickoryResolver>>),
510 Custom(Arc<RwLock<dyn Resolver>>),
511}
512
513impl DnsResolverInner {
514 async fn lookup_ipv4(
515 &self,
516 host: String,
517 ) -> Result<impl Iterator<Item = Ipv4Addr> + use<>, DnsError> {
518 Ok(match self {
519 Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_ipv4(host).await?),
520 Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_ipv4(host).await?),
521 })
522 }
523
524 async fn lookup_ipv6(
525 &self,
526 host: String,
527 ) -> Result<impl Iterator<Item = Ipv6Addr> + use<>, DnsError> {
528 Ok(match self {
529 Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_ipv6(host).await?),
530 Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_ipv6(host).await?),
531 })
532 }
533
534 async fn lookup_txt(
535 &self,
536 host: String,
537 ) -> Result<impl Iterator<Item = TxtRecordData> + use<>, DnsError> {
538 Ok(match self {
539 Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_txt(host).await?),
540 Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_txt(host).await?),
541 })
542 }
543
544 async fn clear_cache(&self) {
545 match self {
546 Self::Hickory(resolver) => resolver.read().await.clear_cache(),
547 Self::Custom(resolver) => resolver.read().await.clear_cache(),
548 }
549 }
550
551 async fn reset(&self) {
552 match self {
553 Self::Hickory(resolver) => resolver.write().await.reset(),
554 Self::Custom(resolver) => resolver.write().await.reset(),
555 }
556 }
557}
558
559#[derive(Debug)]
560struct HickoryResolver {
561 resolver: TokioResolver,
562 builder: Builder,
563}
564
565impl HickoryResolver {
566 fn new(builder: Builder) -> Self {
567 let resolver = Self::build_resolver(&builder);
568 Self { resolver, builder }
569 }
570
571 fn build_resolver(builder: &Builder) -> TokioResolver {
572 let (mut config, mut options) = if builder.use_system_defaults {
573 match Self::system_config() {
574 Ok((config, options)) => (config, options),
575 Err(error) => {
576 debug!(%error, "Failed to read the system's DNS config, using fallback DNS servers.");
577 (ResolverConfig::google(), ResolverOpts::default())
578 }
579 }
580 } else {
581 (ResolverConfig::new(), ResolverOpts::default())
582 };
583
584 for (addr, proto) in builder.nameservers.iter() {
585 let nameserver =
586 hickory_resolver::config::NameServerConfig::new(*addr, proto.to_hickory());
587 config.add_name_server(nameserver);
588 }
589
590 options.ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4thenIpv6;
592
593 let mut hickory_builder =
594 TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
595 *hickory_builder.options_mut() = options;
596 hickory_builder.build()
597 }
598
599 fn system_config() -> Result<(ResolverConfig, ResolverOpts), hickory_resolver::ResolveError> {
600 let (system_config, options) = hickory_resolver::system_conf::read_system_conf()?;
601
602 let mut config = hickory_resolver::config::ResolverConfig::new();
605 if let Some(name) = system_config.domain() {
606 config.set_domain(name.clone());
607 }
608 for name in system_config.search() {
609 config.add_search(name.clone());
610 }
611 for nameserver_cfg in system_config.name_servers() {
612 if !WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS.contains(&nameserver_cfg.socket_addr.ip()) {
613 config.add_name_server(nameserver_cfg.clone());
614 }
615 }
616 Ok((config, options))
617 }
618
619 async fn lookup_ipv4(
620 &self,
621 host: String,
622 ) -> Result<impl Iterator<Item = Ipv4Addr> + use<>, DnsError> {
623 Ok(self
624 .resolver
625 .ipv4_lookup(host)
626 .await?
627 .into_iter()
628 .map(Ipv4Addr::from))
629 }
630
631 async fn lookup_ipv6(
633 &self,
634 host: String,
635 ) -> Result<impl Iterator<Item = Ipv6Addr> + use<>, DnsError> {
636 Ok(self
637 .resolver
638 .ipv6_lookup(host)
639 .await?
640 .into_iter()
641 .map(Ipv6Addr::from))
642 }
643
644 async fn lookup_txt(
646 &self,
647 host: String,
648 ) -> Result<impl Iterator<Item = TxtRecordData> + use<>, DnsError> {
649 Ok(self
650 .resolver
651 .txt_lookup(host)
652 .await?
653 .into_iter()
654 .map(|txt| TxtRecordData::from_iter(txt.iter().cloned())))
655 }
656
657 fn clear_cache(&self) {
659 self.resolver.clear_cache()
660 }
661
662 fn reset(&mut self) {
663 self.resolver = Self::build_resolver(&self.builder);
664 }
665}
666
667#[derive(Debug, Clone)]
679pub struct TxtRecordData(Box<[Box<[u8]>]>);
680
681impl TxtRecordData {
682 pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
684 self.0.iter().map(|x| x.as_ref())
685 }
686}
687
688impl fmt::Display for TxtRecordData {
689 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
690 for s in self.iter() {
691 write!(f, "{}", &String::from_utf8_lossy(s))?
692 }
693 Ok(())
694 }
695}
696
697impl FromIterator<Box<[u8]>> for TxtRecordData {
698 fn from_iter<T: IntoIterator<Item = Box<[u8]>>>(iter: T) -> Self {
699 Self(iter.into_iter().collect())
700 }
701}
702
703impl From<Vec<Box<[u8]>>> for TxtRecordData {
704 fn from(value: Vec<Box<[u8]>>) -> Self {
705 Self(value.into_boxed_slice())
706 }
707}
708
709enum Either<A, B> {
711 Left(A),
712 Right(B),
713}
714
715impl<T, A: Iterator<Item = T>, B: Iterator<Item = T>> Iterator for Either<A, B> {
716 type Item = T;
717
718 fn next(&mut self) -> Option<Self::Item> {
719 match self {
720 Either::Left(iter) => iter.next(),
721 Either::Right(iter) => iter.next(),
722 }
723 }
724}
725
726const WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS: [IpAddr; 3] = [
735 IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 1)),
736 IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 2)),
737 IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 3)),
738];
739
740enum LookupIter<A, B> {
742 Ipv4(A),
743 Ipv6(B),
744 Both(std::iter::Chain<A, B>),
745}
746
747impl<A: Iterator<Item = IpAddr>, B: Iterator<Item = IpAddr>> Iterator for LookupIter<A, B> {
748 type Item = IpAddr;
749
750 fn next(&mut self) -> Option<Self::Item> {
751 match self {
752 LookupIter::Ipv4(iter) => iter.next(),
753 LookupIter::Ipv6(iter) => iter.next(),
754 LookupIter::Both(iter) => iter.next(),
755 }
756 }
757}
758
759async fn stagger_call<
764 T,
765 E: std::fmt::Debug + std::fmt::Display,
766 F: Fn() -> Fut,
767 Fut: Future<Output = Result<T, E>>,
768>(
769 f: F,
770 delays_ms: &[u64],
771) -> Result<T, StaggeredError<E>> {
772 let mut calls = n0_future::FuturesUnorderedBounded::new(delays_ms.len() + 1);
773 for delay in std::iter::once(&0u64).chain(delays_ms) {
776 let delay = add_jitter(delay);
777 let fut = f();
778 let staggered_fut = async move {
779 time::sleep(delay).await;
780 fut.await
781 };
782 calls.push(staggered_fut)
783 }
784
785 let mut errors = vec![];
786 while let Some(call_result) = calls.next().await {
787 match call_result {
788 Ok(t) => return Ok(t),
789 Err(e) => errors.push(e),
790 }
791 }
792
793 Err(StaggeredError::new(errors))
794}
795
796fn add_jitter(delay: &u64) -> Duration {
797 if *delay == 0 {
799 return Duration::ZERO;
800 }
801
802 let max_jitter = delay.saturating_mul(MAX_JITTER_PERCENT * 2) / 100;
804 let jitter = rand::random::<u64>() % max_jitter;
805
806 Duration::from_millis(delay.saturating_sub(max_jitter / 2).saturating_add(jitter))
807}
808
809#[cfg(test)]
810pub(crate) mod tests {
811 use std::sync::atomic::AtomicUsize;
812
813 use tracing_test::traced_test;
814
815 use super::*;
816
817 #[tokio::test]
818 #[traced_test]
819 async fn stagger_basic() {
820 const CALL_RESULTS: &[Result<u8, u8>] = &[Err(2), Ok(3), Ok(5), Ok(7)];
821 static DONE_CALL: AtomicUsize = AtomicUsize::new(0);
822 let f = || {
823 let r_pos = DONE_CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
824 async move {
825 tracing::info!(r_pos, "call");
826 CALL_RESULTS[r_pos].map_err(|_| InvalidResponseSnafu.build())
827 }
828 };
829
830 let delays = [1000, 15];
831 let result = stagger_call(f, &delays).await.unwrap();
832 assert_eq!(result, 5)
833 }
834
835 #[test]
836 #[traced_test]
837 fn jitter_test_zero() {
838 let jittered_delay = add_jitter(&0);
839 assert_eq!(jittered_delay, Duration::from_secs(0));
840 }
841
842 #[test]
844 #[traced_test]
845 fn jitter_test_nonzero_lower_bound() {
846 let delay: u64 = 300;
847 for _ in 0..100 {
848 assert!(add_jitter(&delay) >= Duration::from_millis(delay * 8 / 10));
849 }
850 }
851
852 #[test]
853 #[traced_test]
854 fn jitter_test_nonzero_upper_bound() {
855 let delay: u64 = 300;
856 for _ in 0..100 {
857 assert!(add_jitter(&delay) < Duration::from_millis(delay * 12 / 10));
858 }
859 }
860
861 #[tokio::test]
862 #[traced_test]
863 async fn custom_resolver() {
864 #[derive(Debug)]
865 struct MyResolver;
866 impl Resolver for MyResolver {
867 fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>> {
868 Box::pin(async move {
869 let addr = if host == "foo.example" {
870 Ipv4Addr::new(1, 1, 1, 1)
871 } else {
872 return Err(NoResponseSnafu.build());
873 };
874 let iter: BoxIter<Ipv4Addr> = Box::new(vec![addr].into_iter());
875 Ok(iter)
876 })
877 }
878
879 fn lookup_ipv6(&self, _host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>> {
880 todo!()
881 }
882
883 fn lookup_txt(
884 &self,
885 _host: String,
886 ) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>> {
887 todo!()
888 }
889
890 fn clear_cache(&self) {
891 todo!()
892 }
893
894 fn reset(&mut self) {
895 todo!()
896 }
897 }
898
899 let resolver = DnsResolver::custom(MyResolver);
900 let mut iter = resolver
901 .lookup_ipv4("foo.example", Duration::from_secs(1))
902 .await
903 .expect("not to fail");
904 let addr = iter.next().expect("one result");
905 assert_eq!(addr, "1.1.1.1".parse::<IpAddr>().unwrap());
906
907 let res = resolver
908 .lookup_ipv4("bar.example", Duration::from_secs(1))
909 .await;
910 assert!(matches!(res, Err(DnsError::NoResponse { .. })))
911 }
912}