1use std::{
10 fmt,
11 future::Future,
12 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
13 sync::Arc,
14};
15
16use hickory_resolver::{
17 TokioResolver,
18 config::{ResolverConfig, ResolverOpts},
19 name_server::TokioConnectionProvider,
20};
21use iroh_base::EndpointId;
22use n0_error::{StackError, e, stack_error};
23use n0_future::{
24 StreamExt,
25 boxed::BoxFuture,
26 time::{self, Duration},
27};
28use rustls::ClientConfig;
29use tokio::sync::RwLock;
30use tracing::debug;
31use url::Url;
32
33use crate::{
34 defaults::timeouts::DNS_TIMEOUT,
35 endpoint_info::{self, EndpointInfo, ParseError},
36};
37
38pub const N0_DNS_ENDPOINT_ORIGIN_PROD: &str = "dns.iroh.link.";
40pub const N0_DNS_ENDPOINT_ORIGIN_STAGING: &str = "staging-dns.iroh.link.";
42
43const MAX_JITTER_PERCENT: u64 = 20;
45
46pub trait Resolver: fmt::Debug + Send + Sync + 'static {
48 fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>>;
50
51 fn lookup_ipv6(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>>;
53
54 fn lookup_txt(&self, host: String) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>>;
56
57 fn clear_cache(&self);
59
60 fn reset(&mut self);
65}
66
67pub type BoxIter<T> = Box<dyn Iterator<Item = T> + Send + 'static>;
71
72#[allow(missing_docs)]
74#[stack_error(derive, add_meta, from_sources, std_sources)]
75#[non_exhaustive]
76pub enum DnsError {
77 #[error(transparent)]
78 Timeout { source: tokio::time::error::Elapsed },
79 #[error("No response")]
80 NoResponse {},
81 #[error("Resolve failed ipv4: {ipv4}, ipv6 {ipv6}")]
82 ResolveBoth {
83 ipv4: Box<DnsError>,
84 ipv6: Box<DnsError>,
85 },
86 #[error("missing host")]
87 MissingHost {},
88 #[error(transparent)]
89 Resolve {
90 source: hickory_resolver::ResolveError,
91 },
92 #[error("invalid DNS response: not a query for _iroh.z32encodedpubkey")]
93 InvalidResponse {},
94}
95
96#[cfg(not(wasm_browser))]
98#[allow(missing_docs)]
99#[stack_error(derive, add_meta, from_sources)]
100#[non_exhaustive]
101pub enum LookupError {
102 #[error("Malformed txt from lookup")]
103 ParseError { source: ParseError },
104 #[error("Failed to resolve TXT record")]
105 LookupFailed { source: DnsError },
106}
107
108#[stack_error(derive, add_meta)]
110#[error("no calls succeeded: [{}]", errors.iter().map(|e| e.to_string()).collect::<Vec<_>>().join(""))]
111pub struct StaggeredError<E: n0_error::StackError + 'static> {
112 errors: Vec<E>,
113}
114
115impl<E: StackError + 'static> StaggeredError<E> {
116 pub fn iter(&self) -> impl Iterator<Item = &E> {
118 self.errors.iter()
119 }
120}
121
122#[derive(Debug, Clone, Default)]
124pub struct Builder {
125 use_system_defaults: bool,
126 nameservers: Vec<(SocketAddr, DnsProtocol)>,
127 tls_client_config: Option<ClientConfig>,
128}
129
130#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
132#[non_exhaustive]
133pub enum DnsProtocol {
134 #[default]
138 Udp,
139 Tcp,
143 Tls,
149 Https,
155}
156
157impl DnsProtocol {
158 fn to_hickory(self) -> hickory_resolver::proto::xfer::Protocol {
159 use hickory_resolver::proto::xfer::Protocol;
160 match self {
161 DnsProtocol::Udp => Protocol::Udp,
162 DnsProtocol::Tcp => Protocol::Tcp,
163 DnsProtocol::Tls => Protocol::Tls,
164 DnsProtocol::Https => Protocol::Https,
165 }
166 }
167}
168
169impl Builder {
170 pub fn with_system_defaults(mut self) -> Self {
176 self.use_system_defaults = true;
177 self
178 }
179
180 pub fn with_nameserver(mut self, addr: SocketAddr, protocol: DnsProtocol) -> Self {
182 self.nameservers.push((addr, protocol));
183 self
184 }
185
186 pub fn with_nameservers(
188 mut self,
189 nameservers: impl IntoIterator<Item = (SocketAddr, DnsProtocol)>,
190 ) -> Self {
191 self.nameservers.extend(nameservers);
192 self
193 }
194
195 pub fn tls_client_config(mut self, client_config: ClientConfig) -> Self {
197 self.tls_client_config = Some(client_config);
198 self
199 }
200
201 pub fn build(self) -> DnsResolver {
203 let resolver = HickoryResolver::new(self);
204 DnsResolver(DnsResolverInner::Hickory(Arc::new(RwLock::new(resolver))))
205 }
206}
207
208#[derive(Debug, Clone)]
215pub struct DnsResolver(DnsResolverInner);
216
217impl DnsResolver {
218 pub fn new() -> Self {
224 Builder::default().with_system_defaults().build()
225 }
226
227 pub fn with_nameserver(nameserver: SocketAddr) -> Self {
229 Builder::default()
230 .with_nameserver(nameserver, DnsProtocol::Udp)
231 .build()
232 }
233
234 pub fn builder() -> Builder {
236 Builder::default()
237 }
238
239 pub fn custom(resolver: impl Resolver) -> Self {
245 Self(DnsResolverInner::Custom(Arc::new(RwLock::new(resolver))))
246 }
247
248 pub async fn clear_cache(&self) {
250 self.0.clear_cache().await
251 }
252
253 pub async fn reset(&self) {
255 self.0.reset().await
256 }
257
258 pub async fn lookup_txt<T: ToString>(
260 &self,
261 host: T,
262 timeout: Duration,
263 ) -> Result<impl Iterator<Item = TxtRecordData>, DnsError> {
264 let host = host.to_string();
265 let res = time::timeout(timeout, self.0.lookup_txt(host)).await??;
266 Ok(res)
267 }
268
269 pub async fn lookup_ipv4<T: ToString>(
271 &self,
272 host: T,
273 timeout: Duration,
274 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
275 let host = host.to_string();
276 let addrs = time::timeout(timeout, self.0.lookup_ipv4(host)).await??;
277 Ok(addrs.into_iter().map(IpAddr::V4))
278 }
279
280 pub async fn lookup_ipv6<T: ToString>(
282 &self,
283 host: T,
284 timeout: Duration,
285 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
286 let host = host.to_string();
287 let addrs = time::timeout(timeout, self.0.lookup_ipv6(host)).await??;
288 Ok(addrs.into_iter().map(IpAddr::V6))
289 }
290
291 pub async fn lookup_ipv4_ipv6<T: ToString>(
297 &self,
298 host: T,
299 timeout: Duration,
300 ) -> Result<impl Iterator<Item = IpAddr> + use<T>, DnsError> {
301 let host = host.to_string();
302 let res = tokio::join!(
303 self.lookup_ipv4(host.clone(), timeout),
304 self.lookup_ipv6(host, timeout)
305 );
306
307 match res {
308 (Ok(ipv4), Ok(ipv6)) => Ok(LookupIter::Both(ipv4.chain(ipv6))),
309 (Ok(ipv4), Err(_)) => Ok(LookupIter::Ipv4(ipv4)),
310 (Err(_), Ok(ipv6)) => Ok(LookupIter::Ipv6(ipv6)),
311 (Err(ipv4_err), Err(ipv6_err)) => Err(e!(DnsError::ResolveBoth {
312 ipv4: Box::new(ipv4_err),
313 ipv6: Box::new(ipv6_err)
314 })),
315 }
316 }
317
318 pub async fn resolve_host(
320 &self,
321 url: &Url,
322 prefer_ipv6: bool,
323 timeout: Duration,
324 ) -> Result<IpAddr, DnsError> {
325 let host = url.host().ok_or_else(|| e!(DnsError::MissingHost))?;
326 match host {
327 url::Host::Domain(domain) => {
328 let lookup = tokio::join!(
330 self.lookup_ipv4(domain, timeout),
331 self.lookup_ipv6(domain, timeout)
332 );
333 let (v4, v6) = match lookup {
334 (Err(ipv4_err), Err(ipv6_err)) => {
335 return Err(e!(DnsError::ResolveBoth {
336 ipv4: Box::new(ipv4_err),
337 ipv6: Box::new(ipv6_err)
338 }));
339 }
340 (Err(_), Ok(mut v6)) => (None, v6.next()),
341 (Ok(mut v4), Err(_)) => (v4.next(), None),
342 (Ok(mut v4), Ok(mut v6)) => (v4.next(), v6.next()),
343 };
344 if prefer_ipv6 {
345 v6.or(v4).ok_or_else(|| e!(DnsError::NoResponse))
346 } else {
347 v4.or(v6).ok_or_else(|| e!(DnsError::NoResponse))
348 }
349 }
350 url::Host::Ipv4(ip) => Ok(IpAddr::V4(ip)),
351 url::Host::Ipv6(ip) => Ok(IpAddr::V6(ip)),
352 }
353 }
354
355 pub async fn lookup_ipv4_staggered(
362 &self,
363 host: impl ToString,
364 timeout: Duration,
365 delays_ms: &[u64],
366 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
367 let host = host.to_string();
368 let f = || self.lookup_ipv4(host.clone(), timeout);
369 stagger_call(f, delays_ms).await
370 }
371
372 pub async fn lookup_ipv6_staggered(
379 &self,
380 host: impl ToString,
381 timeout: Duration,
382 delays_ms: &[u64],
383 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
384 let host = host.to_string();
385 let f = || self.lookup_ipv6(host.clone(), timeout);
386 stagger_call(f, delays_ms).await
387 }
388
389 pub async fn lookup_ipv4_ipv6_staggered(
397 &self,
398 host: impl ToString,
399 timeout: Duration,
400 delays_ms: &[u64],
401 ) -> Result<impl Iterator<Item = IpAddr>, StaggeredError<DnsError>> {
402 let host = host.to_string();
403 let f = || self.lookup_ipv4_ipv6(host.clone(), timeout);
404 stagger_call(f, delays_ms).await
405 }
406
407 pub async fn lookup_endpoint_by_id(
412 &self,
413 endpoint_id: &EndpointId,
414 origin: &str,
415 ) -> Result<EndpointInfo, LookupError> {
416 let name = endpoint_info::endpoint_domain(endpoint_id, origin);
417 let name = endpoint_info::ensure_iroh_txt_label(name);
418 let lookup = self.lookup_txt(name.clone(), DNS_TIMEOUT).await?;
419 let info = EndpointInfo::from_txt_lookup(name, lookup)?;
420 Ok(info)
421 }
422
423 pub async fn lookup_endpoint_by_domain_name(
425 &self,
426 name: &str,
427 ) -> Result<EndpointInfo, LookupError> {
428 let name = endpoint_info::ensure_iroh_txt_label(name.to_string());
429 let lookup = self.lookup_txt(name.clone(), DNS_TIMEOUT).await?;
430 let info = EndpointInfo::from_txt_lookup(name, lookup)?;
431 Ok(info)
432 }
433
434 pub async fn lookup_endpoint_by_domain_name_staggered(
441 &self,
442 name: &str,
443 delays_ms: &[u64],
444 ) -> Result<EndpointInfo, StaggeredError<LookupError>> {
445 let f = || self.lookup_endpoint_by_domain_name(name);
446 stagger_call(f, delays_ms).await
447 }
448
449 pub async fn lookup_endpoint_by_id_staggered(
456 &self,
457 endpoint_id: &EndpointId,
458 origin: &str,
459 delays_ms: &[u64],
460 ) -> Result<EndpointInfo, StaggeredError<LookupError>> {
461 let f = || self.lookup_endpoint_by_id(endpoint_id, origin);
462 stagger_call(f, delays_ms).await
463 }
464}
465
466impl Default for DnsResolver {
467 fn default() -> Self {
468 Self::new()
469 }
470}
471
472impl reqwest::dns::Resolve for DnsResolver {
473 fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving {
474 let this = self.clone();
475 let name = name.as_str().to_string();
476 Box::pin(async move {
477 let res = this.lookup_ipv4_ipv6(name, DNS_TIMEOUT).await;
478 match res {
479 Ok(addrs) => {
480 let addrs: reqwest::dns::Addrs =
481 Box::new(addrs.map(|addr| SocketAddr::new(addr, 0)));
482 Ok(addrs)
483 }
484 Err(err) => {
485 let err: Box<dyn std::error::Error + Send + Sync> = Box::new(err);
486 Err(err)
487 }
488 }
489 })
490 }
491}
492
493#[derive(Debug, Clone)]
498enum DnsResolverInner {
499 Hickory(Arc<RwLock<HickoryResolver>>),
500 Custom(Arc<RwLock<dyn Resolver>>),
501}
502
503impl DnsResolverInner {
504 async fn lookup_ipv4(
505 &self,
506 host: String,
507 ) -> Result<impl Iterator<Item = Ipv4Addr> + use<>, DnsError> {
508 Ok(match self {
509 Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_ipv4(host).await?),
510 Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_ipv4(host).await?),
511 })
512 }
513
514 async fn lookup_ipv6(
515 &self,
516 host: String,
517 ) -> Result<impl Iterator<Item = Ipv6Addr> + use<>, DnsError> {
518 Ok(match self {
519 Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_ipv6(host).await?),
520 Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_ipv6(host).await?),
521 })
522 }
523
524 async fn lookup_txt(
525 &self,
526 host: String,
527 ) -> Result<impl Iterator<Item = TxtRecordData> + use<>, DnsError> {
528 Ok(match self {
529 Self::Hickory(resolver) => Either::Left(resolver.read().await.lookup_txt(host).await?),
530 Self::Custom(resolver) => Either::Right(resolver.read().await.lookup_txt(host).await?),
531 })
532 }
533
534 async fn clear_cache(&self) {
535 match self {
536 Self::Hickory(resolver) => resolver.read().await.clear_cache(),
537 Self::Custom(resolver) => resolver.read().await.clear_cache(),
538 }
539 }
540
541 async fn reset(&self) {
542 match self {
543 Self::Hickory(resolver) => resolver.write().await.reset(),
544 Self::Custom(resolver) => resolver.write().await.reset(),
545 }
546 }
547}
548
549#[derive(Debug)]
550struct HickoryResolver {
551 resolver: TokioResolver,
552 builder: Builder,
553}
554
555impl HickoryResolver {
556 fn new(builder: Builder) -> Self {
557 let resolver = Self::build_resolver(&builder);
558 Self { resolver, builder }
559 }
560
561 fn build_resolver(builder: &Builder) -> TokioResolver {
562 let (mut config, mut options) = if builder.use_system_defaults {
563 match Self::system_config() {
564 Ok((config, options)) => (config, options),
565 Err(error) => {
566 debug!(%error, "Failed to read the system's DNS config, using fallback DNS servers.");
567 (ResolverConfig::google(), ResolverOpts::default())
568 }
569 }
570 } else {
571 (ResolverConfig::new(), ResolverOpts::default())
572 };
573
574 if let Some(client_config) = builder.tls_client_config.clone() {
575 options.tls_config = client_config;
576 }
577
578 for (addr, proto) in builder.nameservers.iter() {
579 let nameserver =
580 hickory_resolver::config::NameServerConfig::new(*addr, proto.to_hickory());
581 config.add_name_server(nameserver);
582 }
583
584 options.ip_strategy = hickory_resolver::config::LookupIpStrategy::Ipv4thenIpv6;
586 options.negative_max_ttl = Some(Duration::ZERO);
587
588 let mut hickory_builder =
589 TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
590 *hickory_builder.options_mut() = options;
591 hickory_builder.build()
592 }
593
594 fn system_config() -> Result<(ResolverConfig, ResolverOpts), hickory_resolver::ResolveError> {
595 let (system_config, options) = hickory_resolver::system_conf::read_system_conf()?;
596
597 let mut config = hickory_resolver::config::ResolverConfig::new();
600 if let Some(name) = system_config.domain() {
601 config.set_domain(name.clone());
602 }
603 for name in system_config.search() {
604 config.add_search(name.clone());
605 }
606 for nameserver_cfg in system_config.name_servers() {
607 if !WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS.contains(&nameserver_cfg.socket_addr.ip()) {
608 config.add_name_server(nameserver_cfg.clone());
609 }
610 }
611 Ok((config, options))
612 }
613
614 async fn lookup_ipv4(
615 &self,
616 host: String,
617 ) -> Result<impl Iterator<Item = Ipv4Addr> + use<>, DnsError> {
618 Ok(self
619 .resolver
620 .ipv4_lookup(host)
621 .await?
622 .into_iter()
623 .map(Ipv4Addr::from))
624 }
625
626 async fn lookup_ipv6(
628 &self,
629 host: String,
630 ) -> Result<impl Iterator<Item = Ipv6Addr> + use<>, DnsError> {
631 Ok(self
632 .resolver
633 .ipv6_lookup(host)
634 .await?
635 .into_iter()
636 .map(Ipv6Addr::from))
637 }
638
639 async fn lookup_txt(
641 &self,
642 host: String,
643 ) -> Result<impl Iterator<Item = TxtRecordData> + use<>, DnsError> {
644 Ok(self
645 .resolver
646 .txt_lookup(host)
647 .await?
648 .into_iter()
649 .map(|txt| TxtRecordData::from_iter(txt.iter().cloned())))
650 }
651
652 fn clear_cache(&self) {
654 self.resolver.clear_cache()
655 }
656
657 fn reset(&mut self) {
658 self.resolver = Self::build_resolver(&self.builder);
659 }
660}
661
662#[derive(Debug, Clone)]
674pub struct TxtRecordData(Box<[Box<[u8]>]>);
675
676impl TxtRecordData {
677 pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
679 self.0.iter().map(|x| x.as_ref())
680 }
681}
682
683impl fmt::Display for TxtRecordData {
684 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685 for s in self.iter() {
686 write!(f, "{}", &String::from_utf8_lossy(s))?
687 }
688 Ok(())
689 }
690}
691
692impl FromIterator<Box<[u8]>> for TxtRecordData {
693 fn from_iter<T: IntoIterator<Item = Box<[u8]>>>(iter: T) -> Self {
694 Self(iter.into_iter().collect())
695 }
696}
697
698impl From<Vec<Box<[u8]>>> for TxtRecordData {
699 fn from(value: Vec<Box<[u8]>>) -> Self {
700 Self(value.into_boxed_slice())
701 }
702}
703
704enum Either<A, B> {
706 Left(A),
707 Right(B),
708}
709
710impl<T, A: Iterator<Item = T>, B: Iterator<Item = T>> Iterator for Either<A, B> {
711 type Item = T;
712
713 fn next(&mut self) -> Option<Self::Item> {
714 match self {
715 Either::Left(iter) => iter.next(),
716 Either::Right(iter) => iter.next(),
717 }
718 }
719}
720
721const WINDOWS_BAD_SITE_LOCAL_DNS_SERVERS: [IpAddr; 3] = [
730 IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 1)),
731 IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 2)),
732 IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0xffff, 0, 0, 0, 3)),
733];
734
735enum LookupIter<A, B> {
737 Ipv4(A),
738 Ipv6(B),
739 Both(std::iter::Chain<A, B>),
740}
741
742impl<A: Iterator<Item = IpAddr>, B: Iterator<Item = IpAddr>> Iterator for LookupIter<A, B> {
743 type Item = IpAddr;
744
745 fn next(&mut self) -> Option<Self::Item> {
746 match self {
747 LookupIter::Ipv4(iter) => iter.next(),
748 LookupIter::Ipv6(iter) => iter.next(),
749 LookupIter::Both(iter) => iter.next(),
750 }
751 }
752}
753
754async fn stagger_call<
759 T,
760 E: StackError + 'static,
761 F: Fn() -> Fut,
762 Fut: Future<Output = Result<T, E>>,
763>(
764 f: F,
765 delays_ms: &[u64],
766) -> Result<T, StaggeredError<E>> {
767 let mut calls = n0_future::FuturesUnorderedBounded::new(delays_ms.len() + 1);
768 for delay in std::iter::once(&0u64).chain(delays_ms) {
771 let delay = add_jitter(delay);
772 let fut = f();
773 let staggered_fut = async move {
774 time::sleep(delay).await;
775 fut.await
776 };
777 calls.push(staggered_fut)
778 }
779
780 let mut errors = vec![];
781 while let Some(call_result) = calls.next().await {
782 match call_result {
783 Ok(t) => return Ok(t),
784 Err(e) => errors.push(e),
785 }
786 }
787
788 Err(e!(StaggeredError { errors }))
789}
790
791fn add_jitter(delay: &u64) -> Duration {
792 if *delay == 0 {
794 return Duration::ZERO;
795 }
796
797 let max_jitter = delay.saturating_mul(MAX_JITTER_PERCENT * 2) / 100;
799 let jitter = rand::random::<u64>() % max_jitter;
800
801 Duration::from_millis(delay.saturating_sub(max_jitter / 2).saturating_add(jitter))
802}
803
804#[cfg(test)]
805pub(crate) mod tests {
806 use std::sync::atomic::AtomicUsize;
807
808 use n0_tracing_test::traced_test;
809
810 use super::*;
811
812 #[tokio::test]
813 #[traced_test]
814 async fn stagger_basic() {
815 const CALL_RESULTS: &[Result<u8, u8>] = &[Err(2), Ok(3), Ok(5), Ok(7)];
816 static DONE_CALL: AtomicUsize = AtomicUsize::new(0);
817 let f = || {
818 let r_pos = DONE_CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
819 async move {
820 tracing::info!(r_pos, "call");
821 CALL_RESULTS[r_pos].map_err(|_| e!(DnsError::InvalidResponse))
822 }
823 };
824
825 let delays = [1000, 15];
826 let result = stagger_call(f, &delays).await.unwrap();
827 assert_eq!(result, 5)
828 }
829
830 #[test]
831 #[traced_test]
832 fn jitter_test_zero() {
833 let jittered_delay = add_jitter(&0);
834 assert_eq!(jittered_delay, Duration::from_secs(0));
835 }
836
837 #[test]
839 #[traced_test]
840 fn jitter_test_nonzero_lower_bound() {
841 let delay: u64 = 300;
842 for _ in 0..100 {
843 assert!(add_jitter(&delay) >= Duration::from_millis(delay * 8 / 10));
844 }
845 }
846
847 #[test]
848 #[traced_test]
849 fn jitter_test_nonzero_upper_bound() {
850 let delay: u64 = 300;
851 for _ in 0..100 {
852 assert!(add_jitter(&delay) < Duration::from_millis(delay * 12 / 10));
853 }
854 }
855
856 #[tokio::test]
857 #[traced_test]
858 async fn custom_resolver() {
859 #[derive(Debug)]
860 struct MyResolver;
861 impl Resolver for MyResolver {
862 fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>> {
863 Box::pin(async move {
864 let addr = if host == "foo.example" {
865 Ipv4Addr::new(1, 1, 1, 1)
866 } else {
867 return Err(e!(DnsError::NoResponse));
868 };
869 let iter: BoxIter<Ipv4Addr> = Box::new(vec![addr].into_iter());
870 Ok(iter)
871 })
872 }
873
874 fn lookup_ipv6(&self, _host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>> {
875 todo!()
876 }
877
878 fn lookup_txt(
879 &self,
880 _host: String,
881 ) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>> {
882 todo!()
883 }
884
885 fn clear_cache(&self) {
886 todo!()
887 }
888
889 fn reset(&mut self) {
890 todo!()
891 }
892 }
893
894 let resolver = DnsResolver::custom(MyResolver);
895 let mut iter = resolver
896 .lookup_ipv4("foo.example", Duration::from_secs(1))
897 .await
898 .expect("not to fail");
899 let addr = iter.next().expect("one result");
900 assert_eq!(addr, "1.1.1.1".parse::<IpAddr>().unwrap());
901
902 let res = resolver
903 .lookup_ipv4("bar.example", Duration::from_secs(1))
904 .await;
905 assert!(matches!(res, Err(DnsError::NoResponse { .. })))
906 }
907}