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::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
31pub const N0_DNS_ENDPOINT_ORIGIN_PROD: &str = "dns.iroh.link";
33pub const N0_DNS_ENDPOINT_ORIGIN_STAGING: &str = "staging-dns.iroh.link";
35
36const MAX_JITTER_PERCENT: u64 = 20;
38
39pub trait Resolver: fmt::Debug + Send + Sync + 'static {
41 fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>, DnsError>>;
43
44 fn lookup_ipv6(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>, DnsError>>;
46
47 fn lookup_txt(&self, host: String) -> BoxFuture<Result<BoxIter<TxtRecordData>, DnsError>>;
49
50 fn clear_cache(&self);
52
53 fn reset(&mut self);
58}
59
60pub type BoxIter<T> = Box<dyn Iterator<Item = T> + Send + 'static>;
62
63#[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#[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 pub fn iter(&self) -> impl Iterator<Item = &E> {
108 self.errors.iter()
109 }
110}
111
112#[derive(Debug, Clone, Default)]
114pub struct Builder {
115 use_system_defaults: bool,
116 nameservers: Vec<(SocketAddr, DnsProtocol)>,
117}
118
119#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
121#[non_exhaustive]
122pub enum DnsProtocol {
123 #[default]
127 Udp,
128 Tcp,
132 Tls,
138 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 pub fn with_system_defaults(mut self) -> Self {
165 self.use_system_defaults = true;
166 self
167 }
168
169 pub fn with_nameserver(mut self, addr: SocketAddr, protocol: DnsProtocol) -> Self {
171 self.nameservers.push((addr, protocol));
172 self
173 }
174
175 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 pub fn build(self) -> DnsResolver {
186 let resolver = HickoryResolver::new(self);
187 DnsResolver(DnsResolverInner::Hickory(Arc::new(RwLock::new(resolver))))
188 }
189}
190
191#[derive(Debug, Clone)]
193pub struct DnsResolver(DnsResolverInner);
194
195impl DnsResolver {
196 pub fn new() -> Self {
202 Builder::default().with_system_defaults().build()
203 }
204
205 pub fn with_nameserver(nameserver: SocketAddr) -> Self {
207 Builder::default()
208 .with_nameserver(nameserver, DnsProtocol::Udp)
209 .build()
210 }
211
212 pub fn builder() -> Builder {
214 Builder::default()
215 }
216
217 pub fn custom(resolver: impl Resolver) -> Self {
223 Self(DnsResolverInner::Custom(Arc::new(RwLock::new(resolver))))
224 }
225
226 pub async fn clear_cache(&self) {
228 self.0.clear_cache().await
229 }
230
231 pub async fn reset(&self) {
233 self.0.reset().await
234 }
235
236 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 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 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 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 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 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 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 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 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 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 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 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 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#[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 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 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 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 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 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#[derive(Debug, Clone)]
647pub struct TxtRecordData(Box<[Box<[u8]>]>);
648
649impl TxtRecordData {
650 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
677enum 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
694const 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
708enum 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
727async 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 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 == 0 {
767 return Duration::ZERO;
768 }
769
770 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 #[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}