1use std::fmt;
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::Arc;
13use std::sync::atomic::AtomicU8;
14use std::task::{Context, Poll};
15
16use futures_util::{
17 FutureExt, Stream,
18 future::{self, BoxFuture},
19 lock::Mutex as AsyncMutex,
20};
21use tracing::debug;
22
23#[cfg(feature = "__tls")]
24use crate::connection_provider::TlsConfig;
25#[cfg(feature = "tokio")]
26use crate::net::runtime::TokioRuntimeProvider;
27use crate::{
28 cache::{MAX_TTL, ResponseCache, TtlConfig},
29 caching_client::CachingClient,
30 config::{OpportunisticEncryption, ResolveHosts, ResolverConfig, ResolverOpts},
31 connection_provider::ConnectionProvider,
32 hosts::Hosts,
33 lookup::Lookup,
34 lookup_ip::{LookupIp, LookupIpFuture},
35 name_server_pool::{NameServerPool, NameServerTransportState, PoolContext},
36 net::{
37 NetError,
38 xfer::{DnsHandle, RetryDnsHandle},
39 },
40 proto::{
41 op::{DnsRequest, DnsRequestOptions, DnsResponse, Query},
42 rr::domain::usage::ONION,
43 rr::{IntoName, Name, RData, Record, RecordType},
44 },
45};
46#[cfg(feature = "__dnssec")]
47use crate::{net::dnssec::DnssecDnsHandle, proto::dnssec::TrustAnchors};
48
49macro_rules! lookup_fn {
50 ($p:ident, $r:path) => {
51 pub async fn $p(&self, query: impl IntoName) -> Result<Lookup, NetError> {
59 self.inner_lookup(query.into_name()?, $r, self.request_options())
60 .await
61 }
62 };
63}
64
65#[cfg(feature = "tokio")]
67pub type TokioResolver = Resolver<TokioRuntimeProvider>;
68
69#[cfg(feature = "tokio")]
70impl TokioResolver {
71 #[cfg(any(unix, target_os = "windows"))]
75 #[cfg(feature = "system-config")]
76 pub fn builder_tokio() -> Result<ResolverBuilder<TokioRuntimeProvider>, NetError> {
77 Self::builder(TokioRuntimeProvider::default())
78 }
79}
80
81#[derive(Clone)]
95pub struct Resolver<P: ConnectionProvider> {
96 domain: Option<Name>,
97 search: Vec<Name>,
98 context: Arc<PoolContext>,
99 client_cache: CachingClient<LookupEither<P>>,
100 hosts: Arc<Hosts>,
101}
102
103impl<R: ConnectionProvider> Resolver<R> {
104 #[cfg(any(unix, target_os = "windows"))]
111 #[cfg(feature = "system-config")]
112 pub fn builder(provider: R) -> Result<ResolverBuilder<R>, NetError> {
113 let (config, options) = super::system_conf::read_system_conf()?;
114 let mut builder = Self::builder_with_config(config, provider);
115 *builder.options_mut() = options;
116 Ok(builder)
117 }
118
119 pub fn builder_with_config(config: ResolverConfig, provider: R) -> ResolverBuilder<R> {
121 ResolverBuilder {
122 config,
123 options: ResolverOpts::default(),
124 provider,
125 #[cfg(feature = "__tls")]
126 tls: None,
127 opportunistic_encryption: OpportunisticEncryption::default(),
128 encrypted_transport_state: NameServerTransportState::default(),
129 #[cfg(feature = "__dnssec")]
130 trust_anchor: None,
131 #[cfg(feature = "__dnssec")]
132 nsec3_soft_iteration_limit: None,
133 #[cfg(feature = "__dnssec")]
134 nsec3_hard_iteration_limit: None,
135 }
136 }
137
138 pub fn set_hosts(&mut self, hosts: Arc<Hosts>) {
140 self.hosts = hosts;
141 }
142
143 pub async fn lookup(
156 &self,
157 name: impl IntoName,
158 record_type: RecordType,
159 ) -> Result<Lookup, NetError> {
160 self.inner_lookup(name.into_name()?, record_type, self.request_options())
161 .await
162 }
163
164 pub(crate) async fn inner_lookup<L>(
165 &self,
166 name: Name,
167 record_type: RecordType,
168 options: DnsRequestOptions,
169 ) -> Result<L, NetError>
170 where
171 L: From<Lookup> + Send + Sync + 'static,
172 {
173 let names = self.build_names(name);
174 LookupFuture::lookup_with_hosts(
175 names,
176 record_type,
177 options,
178 self.client_cache.clone(),
179 self.hosts.clone(),
180 )
181 .await
182 .map(L::from)
183 }
184
185 pub async fn lookup_ip(&self, host: impl IntoName) -> Result<LookupIp, NetError> {
192 let mut finally_ip_addr = None;
193 let maybe_ip = host.to_ip().map(RData::from);
194 let maybe_name = host.into_name().map_err(NetError::from);
195
196 if let Some(ip_addr) = maybe_ip {
198 let name = maybe_name.clone().unwrap_or_default();
199 let record = Record::from_rdata(name.clone(), MAX_TTL, ip_addr.clone());
200
201 if self.context.options.ndots > 4 {
206 finally_ip_addr = Some(record);
207 } else {
208 let query = Query::query(name, ip_addr.record_type());
209 let lookup = Lookup::new_with_max_ttl(query, [record]);
210 return Ok(lookup.into());
211 }
212 }
213
214 let name = match (maybe_name, finally_ip_addr.as_ref()) {
215 (Ok(name), _) => name,
216 (Err(_), Some(ip_addr)) => {
217 let query = Query::query(ip_addr.name.clone(), ip_addr.record_type());
219 let lookup = Lookup::new_with_max_ttl(query, [ip_addr.clone()]);
220 return Ok(lookup.into());
221 }
222 (Err(err), None) => return Err(err),
223 };
224
225 let names = self.build_names(name);
226 let hosts = self.hosts.clone();
227
228 LookupIpFuture::lookup(
229 names,
230 self.context.options.ip_strategy,
231 self.client_cache.clone(),
232 self.request_options(),
233 hosts,
234 finally_ip_addr.map(|r| r.data),
235 )
236 .await
237 }
238
239 pub fn clear_lookup_cache(&self, name: impl IntoName, record_type: RecordType) {
247 let Ok(name) = name.into_name() else {
248 return;
249 };
250
251 for name in self.build_names(name) {
252 let query = Query::query(name, record_type);
253 self.client_cache.clear_cache_query(&query);
254 }
255 }
256
257 fn build_names(&self, name: Name) -> Vec<Name> {
258 if name.is_fqdn()
260 || ONION.zone_of(&name)
261 && name
262 .trim_to(2)
263 .iter()
264 .next()
265 .map(|name| name.len() == 56) .unwrap_or(false)
267 {
268 vec![name]
271 } else {
272 let mut names =
275 Vec::<Name>::with_capacity(1 + 1 + self.search.len());
276
277 let raw_name_first: bool =
279 name.num_labels() as usize > self.context.options.ndots || name.is_localhost();
280
281 if !raw_name_first {
283 let mut fqdn = name.clone();
284 fqdn.set_fqdn(true);
285 names.push(fqdn);
286 }
287
288 for search in self.search.iter().rev() {
289 let name_search = name.clone().append_domain(search);
290
291 match name_search {
292 Ok(name_search) => {
293 if !names.contains(&name_search) {
294 names.push(name_search);
295 }
296 }
297 Err(e) => debug!(
298 "Not adding {} to {} for search due to error: {}",
299 search, name, e
300 ),
301 }
302 }
303
304 if let Some(domain) = &self.domain {
305 let name_search = name.clone().append_domain(domain);
306
307 match name_search {
308 Ok(name_search) => {
309 if !names.contains(&name_search) {
310 names.push(name_search);
311 }
312 }
313 Err(e) => debug!(
314 "Not adding {} to {} for search due to error: {}",
315 domain, name, e
316 ),
317 }
318 }
319
320 if raw_name_first {
322 let mut fqdn = name.clone();
324 fqdn.set_fqdn(true);
325 names.push(fqdn);
326 }
327
328 names
329 }
330 }
331
332 lookup_fn!(reverse_lookup, RecordType::PTR);
333 lookup_fn!(ipv4_lookup, RecordType::A);
334 lookup_fn!(ipv6_lookup, RecordType::AAAA);
335 lookup_fn!(mx_lookup, RecordType::MX);
336 lookup_fn!(ns_lookup, RecordType::NS);
337 lookup_fn!(smimea_lookup, RecordType::SMIMEA);
338 lookup_fn!(soa_lookup, RecordType::SOA);
339 lookup_fn!(srv_lookup, RecordType::SRV);
340 lookup_fn!(tlsa_lookup, RecordType::TLSA);
341 lookup_fn!(txt_lookup, RecordType::TXT);
342 lookup_fn!(cert_lookup, RecordType::CERT);
343
344 pub fn clear_cache(&self) {
346 self.client_cache.clear_cache();
347 }
348
349 pub(crate) fn request_options(&self) -> DnsRequestOptions {
351 let mut request_opts = DnsRequestOptions::default();
352 request_opts.recursion_desired = self.context.options.recursion_desired;
353 request_opts.use_edns = self.context.options.edns0;
354 request_opts.edns_payload_len = self.context.options.edns_payload_len;
355 request_opts.case_randomization = self.context.options.case_randomization;
356
357 #[cfg(feature = "__dnssec")]
359 {
360 request_opts.edns_set_dnssec_ok = self.context.options.validate;
361 }
362
363 request_opts
364 }
365
366 pub fn options(&self) -> &ResolverOpts {
368 &self.context.options
369 }
370}
371
372impl<P: ConnectionProvider> fmt::Debug for Resolver<P> {
373 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374 f.debug_struct("Resolver").finish()
375 }
376}
377
378#[derive(Clone)]
380enum LookupEither<P: ConnectionProvider> {
381 Retry(RetryDnsHandle<NameServerPool<P>>),
382 #[cfg(feature = "__dnssec")]
383 Secure(DnssecDnsHandle<RetryDnsHandle<NameServerPool<P>>>),
384}
385
386impl<P: ConnectionProvider> DnsHandle for LookupEither<P> {
387 type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, NetError>> + Send>>;
388 type Runtime = P::RuntimeProvider;
389
390 fn is_verifying_dnssec(&self) -> bool {
391 match self {
392 Self::Retry(c) => c.is_verifying_dnssec(),
393 #[cfg(feature = "__dnssec")]
394 Self::Secure(c) => c.is_verifying_dnssec(),
395 }
396 }
397
398 fn send(&self, request: DnsRequest) -> Self::Response {
399 match self {
400 Self::Retry(c) => c.send(request),
401 #[cfg(feature = "__dnssec")]
402 Self::Secure(c) => c.send(request),
403 }
404 }
405}
406
407pub struct ResolverBuilder<P> {
411 config: ResolverConfig,
412 options: ResolverOpts,
413 provider: P,
414
415 #[cfg(feature = "__tls")]
416 tls: Option<rustls::ClientConfig>,
417 opportunistic_encryption: OpportunisticEncryption,
418 encrypted_transport_state: NameServerTransportState,
419 #[cfg(feature = "__dnssec")]
420 trust_anchor: Option<Arc<TrustAnchors>>,
421 #[cfg(feature = "__dnssec")]
422 nsec3_soft_iteration_limit: Option<u16>,
423 #[cfg(feature = "__dnssec")]
424 nsec3_hard_iteration_limit: Option<u16>,
425}
426
427impl<P: ConnectionProvider> ResolverBuilder<P> {
428 pub fn with_options(mut self, options: ResolverOpts) -> Self {
437 self.options = options;
438 self
439 }
440
441 pub fn options_mut(&mut self) -> &mut ResolverOpts {
443 &mut self.options
444 }
445
446 #[cfg(feature = "__dnssec")]
448 pub fn with_trust_anchor(mut self, trust_anchor: Arc<TrustAnchors>) -> Self {
449 self.trust_anchor = Some(trust_anchor);
450 self
451 }
452
453 #[cfg(feature = "__tls")]
455 pub fn with_tls_config(mut self, config: rustls::ClientConfig) -> Self {
456 self.tls = Some(config);
457 self
458 }
459
460 pub fn with_opportunistic_encryption(
462 mut self,
463 opportunistic_encryption: OpportunisticEncryption,
464 ) -> Self {
465 self.opportunistic_encryption = opportunistic_encryption;
466 self
467 }
468
469 pub fn with_encrypted_transport_state(
471 mut self,
472 encrypted_transport_state: NameServerTransportState,
473 ) -> Self {
474 self.encrypted_transport_state = encrypted_transport_state;
475 self
476 }
477
478 #[cfg(feature = "__dnssec")]
484 pub fn nsec3_iteration_limits(
485 mut self,
486 soft_limit: Option<u16>,
487 hard_limit: Option<u16>,
488 ) -> Self {
489 self.nsec3_soft_iteration_limit = soft_limit;
490 self.nsec3_hard_iteration_limit = hard_limit;
491 self
492 }
493
494 pub fn build(self) -> Result<Resolver<P>, NetError> {
496 #[cfg_attr(not(feature = "__dnssec"), allow(unused_mut))]
497 let Self {
498 config:
499 ResolverConfig {
500 domain,
501 search,
502 name_servers,
503 },
504 mut options,
505 provider,
506 #[cfg(feature = "__tls")]
507 tls,
508 #[cfg(feature = "__dnssec")]
509 trust_anchor,
510 #[cfg(feature = "__dnssec")]
511 nsec3_soft_iteration_limit,
512 #[cfg(feature = "__dnssec")]
513 nsec3_hard_iteration_limit,
514 opportunistic_encryption,
515 encrypted_transport_state,
516 } = self;
517
518 #[cfg(feature = "__dnssec")]
519 if trust_anchor.is_some() || options.trust_anchor.is_some() {
520 options.validate = true;
521 }
522
523 let context = Arc::new(PoolContext {
524 answer_address_filter: options.answer_address_filter(),
525 options,
526 #[cfg(feature = "__tls")]
527 tls: match tls {
528 Some(config) => config,
529 None => TlsConfig::new()?.config,
530 },
531 opportunistic_probe_budget: AtomicU8::new(
532 opportunistic_encryption
533 .max_concurrent_probes()
534 .unwrap_or_default(),
535 ),
536 opportunistic_encryption,
537 transport_state: AsyncMutex::new(encrypted_transport_state),
538 });
539
540 let pool = NameServerPool::from_config(name_servers, context.clone(), provider);
541
542 let client = RetryDnsHandle::new(pool, context.options.attempts);
543
544 #[cfg(feature = "__dnssec")]
545 let either = if context.options.validate {
546 let trust_anchor = trust_anchor.unwrap_or_else(|| Arc::new(TrustAnchors::default()));
547
548 LookupEither::Secure(
549 DnssecDnsHandle::with_trust_anchor(client, trust_anchor)
550 .nsec3_iteration_limits(nsec3_soft_iteration_limit, nsec3_hard_iteration_limit),
551 )
552 } else {
553 LookupEither::Retry(client)
554 };
555 #[cfg(not(feature = "__dnssec"))]
556 let either = LookupEither::Retry(client);
557
558 let cache = ResponseCache::new(
559 context.options.cache_size,
560 TtlConfig::from_opts(&context.options),
561 );
562 let client_cache =
563 CachingClient::with_cache(cache, either, context.options.preserve_intermediates);
564
565 let hosts = Arc::new(match context.options.use_hosts_file {
566 ResolveHosts::Always | ResolveHosts::Auto => Hosts::from_system().unwrap_or_default(),
567 ResolveHosts::Never => Hosts::default(),
568 });
569
570 Ok(Resolver {
571 domain,
572 search,
573 context,
574 client_cache,
575 hosts,
576 })
577 }
578}
579
580#[doc(hidden)]
582pub struct LookupFuture<C>
583where
584 C: DnsHandle + 'static,
585{
586 client_cache: CachingClient<C>,
587 names: Vec<Name>,
588 record_type: RecordType,
589 options: DnsRequestOptions,
590 query: BoxFuture<'static, Result<Lookup, NetError>>,
591}
592
593impl<C> LookupFuture<C>
594where
595 C: DnsHandle + 'static,
596{
597 #[doc(hidden)]
605 pub fn lookup(
606 names: Vec<Name>,
607 record_type: RecordType,
608 options: DnsRequestOptions,
609 client_cache: CachingClient<C>,
610 ) -> Self {
611 Self::lookup_with_hosts(
612 names,
613 record_type,
614 options,
615 client_cache,
616 Arc::new(Hosts::default()),
617 )
618 }
619
620 #[doc(hidden)]
630 pub fn lookup_with_hosts(
631 mut names: Vec<Name>,
632 record_type: RecordType,
633 options: DnsRequestOptions,
634 client_cache: CachingClient<C>,
635 hosts: Arc<Hosts>,
636 ) -> Self {
637 let name = names
638 .pop()
639 .ok_or(NetError::Message("can not lookup for no names"));
640
641 let query = match name {
642 Ok(name) => {
643 let query = Query::query(name, record_type);
644
645 if let Some(lookup) = hosts.lookup_static_host(&query) {
646 future::ok(lookup).boxed()
647 } else {
648 client_cache.lookup(query, options).boxed()
649 }
650 }
651 Err(err) => future::err(err).boxed(),
652 };
653
654 Self {
655 client_cache,
656 names,
657 record_type,
658 options,
659 query,
660 }
661 }
662}
663
664impl<C> Future for LookupFuture<C>
665where
666 C: DnsHandle + 'static,
667{
668 type Output = Result<Lookup, NetError>;
669
670 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
671 loop {
672 let query = self.query.as_mut().poll_unpin(cx);
674
675 let should_retry = match &query {
677 Poll::Pending => return Poll::Pending,
679 Poll::Ready(Ok(lookup)) => lookup.answers().is_empty(),
683 Poll::Ready(Err(_)) => true,
685 };
686
687 if should_retry {
688 if let Some(name) = self.names.pop() {
689 let record_type = self.record_type;
690 let options = self.options;
691
692 self.query = self
695 .client_cache
696 .lookup(Query::query(name, record_type), options)
697 .boxed();
698 continue;
701 }
702 }
703 return query;
707 }
712 }
713}
714
715#[cfg(all(test, feature = "tokio"))]
717pub(crate) mod testing {
718 use std::{net::*, str::FromStr};
719
720 use tokio::runtime::Runtime;
721
722 use crate::config::{GOOGLE, LookupIpStrategy, NameServerConfig, ResolverConfig};
723 use crate::connection_provider::ConnectionProvider;
724 use crate::net::runtime::TokioRuntimeProvider;
725 use crate::proto::rr::Name;
726 use crate::resolver::Resolver;
727
728 pub(crate) async fn lookup_test<R: ConnectionProvider>(config: ResolverConfig, handle: R) {
730 let resolver = Resolver::<R>::builder_with_config(config, handle)
731 .build()
732 .unwrap();
733
734 let response = resolver
735 .lookup_ip("www.example.com.")
736 .await
737 .expect("failed to run lookup");
738
739 assert_ne!(response.iter().count(), 0);
740 }
741
742 pub(crate) async fn ip_lookup_test<R: ConnectionProvider>(handle: R) {
744 let resolver =
745 Resolver::<R>::builder_with_config(ResolverConfig::udp_and_tcp(&GOOGLE), handle)
746 .build()
747 .unwrap();
748
749 let response = resolver
750 .lookup_ip("10.1.0.2")
751 .await
752 .expect("failed to run lookup");
753
754 assert_eq!(
755 Some(IpAddr::V4(Ipv4Addr::new(10, 1, 0, 2))),
756 response.iter().next()
757 );
758
759 let response = resolver
760 .lookup_ip("2606:2800:21f:cb07:6820:80da:af6b:8b2c")
761 .await
762 .expect("failed to run lookup");
763
764 assert_eq!(
765 Some(IpAddr::V6(Ipv6Addr::new(
766 0x2606, 0x2800, 0x21f, 0xcb07, 0x6820, 0x80da, 0xaf6b, 0x8b2c,
767 ))),
768 response.iter().next()
769 );
770 }
771
772 pub(crate) fn ip_lookup_across_threads_test(handle: TokioRuntimeProvider) {
774 use std::thread;
778 let resolver = Resolver::builder_with_config(ResolverConfig::udp_and_tcp(&GOOGLE), handle)
779 .build()
780 .unwrap();
781
782 let resolver_one = resolver.clone();
783 let resolver_two = resolver;
784
785 let test_fn = |resolver: Resolver<TokioRuntimeProvider>| {
786 let exec = Runtime::new().unwrap();
787
788 let response = exec
789 .block_on(resolver.lookup_ip("10.1.0.2"))
790 .expect("failed to run lookup");
791
792 assert_eq!(
793 Some(IpAddr::V4(Ipv4Addr::new(10, 1, 0, 2))),
794 response.iter().next()
795 );
796
797 let response = exec
798 .block_on(resolver.lookup_ip("2606:2800:21f:cb07:6820:80da:af6b:8b2c"))
799 .expect("failed to run lookup");
800
801 assert_eq!(
802 Some(IpAddr::V6(Ipv6Addr::new(
803 0x2606, 0x2800, 0x21f, 0xcb07, 0x6820, 0x80da, 0xaf6b, 0x8b2c,
804 ))),
805 response.iter().next()
806 );
807 };
808
809 let thread_one = thread::spawn(move || {
810 test_fn(resolver_one);
811 });
812
813 let thread_two = thread::spawn(move || {
814 test_fn(resolver_two);
815 });
816
817 thread_one.join().expect("thread_one failed");
818 thread_two.join().expect("thread_two failed");
819 }
820
821 #[cfg(feature = "__dnssec")]
823 #[allow(clippy::print_stdout)]
824 pub(crate) async fn sec_lookup_test<R: ConnectionProvider>(handle: R) {
825 let mut resolver_builder =
826 Resolver::builder_with_config(ResolverConfig::udp_and_tcp(&GOOGLE), handle);
827 resolver_builder.options_mut().validate = true;
828 resolver_builder.options_mut().try_tcp_on_error = true;
829 let resolver = resolver_builder.build().unwrap();
830
831 let response = resolver
832 .lookup_ip("cloudflare.com.")
833 .await
834 .expect("failed to run lookup");
835
836 assert_ne!(response.iter().count(), 0);
837 println!(
838 "{:?}",
839 response
840 .as_lookup()
841 .message()
842 .all_sections()
843 .collect::<Vec<_>>()
844 );
845 assert!(
846 response
847 .as_lookup()
848 .message()
849 .all_sections()
850 .any(|record| record.proof.is_secure())
851 );
852 }
853
854 #[cfg(feature = "__dnssec")]
856 #[allow(clippy::print_stdout)]
857 pub(crate) async fn sec_lookup_fails_test<R: ConnectionProvider>(handle: R) {
858 let mut resolver_builder =
859 Resolver::builder_with_config(ResolverConfig::udp_and_tcp(&GOOGLE), handle);
860 resolver_builder.options_mut().validate = true;
861 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
862 let resolver = resolver_builder.build().unwrap();
863
864 let response = resolver.lookup_ip("hickory-dns.org.").await;
866
867 let lookup_ip = response.unwrap();
868 println!(
869 "{:?}",
870 lookup_ip
871 .as_lookup()
872 .message()
873 .all_sections()
874 .collect::<Vec<_>>()
875 );
876 for record in lookup_ip.as_lookup().message().all_sections() {
877 assert!(record.proof.is_insecure());
878 }
879 }
880
881 #[cfg(feature = "system-config")]
883 pub(crate) async fn system_lookup_test<R: ConnectionProvider>(handle: R) {
884 let resolver = Resolver::<R>::builder(handle)
885 .expect("failed to create resolver")
886 .build()
887 .unwrap();
888
889 let response = resolver
890 .lookup_ip("www.example.com.")
891 .await
892 .expect("failed to run lookup");
893
894 assert_eq!(response.iter().count(), 2);
895 for address in response.iter() {
896 if address.is_ipv4() {
897 assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 215, 14)));
898 } else {
899 assert_eq!(
900 address,
901 IpAddr::V6(Ipv6Addr::new(
902 0x2606, 0x2800, 0x21f, 0xcb07, 0x6820, 0x80da, 0xaf6b, 0x8b2c,
903 ))
904 );
905 }
906 }
907 }
908
909 #[cfg(all(unix, feature = "system-config"))]
911 pub(crate) async fn hosts_lookup_test<R: ConnectionProvider>(handle: R) {
912 let resolver = Resolver::<R>::builder(handle)
913 .expect("failed to create resolver")
914 .build()
915 .unwrap();
916
917 let response = resolver
918 .lookup_ip("a.com")
919 .await
920 .expect("failed to run lookup");
921
922 assert_eq!(response.iter().count(), 1);
923 for address in response.iter() {
924 if address.is_ipv4() {
925 assert_eq!(address, IpAddr::V4(Ipv4Addr::new(10, 1, 0, 104)));
926 } else {
927 panic!("failed to run lookup");
928 }
929 }
930 }
931
932 pub(crate) async fn fqdn_test<R: ConnectionProvider>(handle: R) {
934 let domain = Name::from_str("incorrect.example.com.").unwrap();
935 let search = vec![
936 Name::from_str("bad.example.com.").unwrap(),
937 Name::from_str("wrong.example.com.").unwrap(),
938 ];
939 let name_servers: Vec<NameServerConfig> = ResolverConfig::udp_and_tcp(&GOOGLE)
940 .name_servers()
941 .to_owned();
942
943 let mut resolver_builder = Resolver::<R>::builder_with_config(
944 ResolverConfig::from_parts(Some(domain), search, name_servers),
945 handle,
946 );
947 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
948 let resolver = resolver_builder.build().unwrap();
949
950 let response = resolver
951 .lookup_ip("www.example.com.")
952 .await
953 .expect("failed to run lookup");
954
955 assert_ne!(response.iter().count(), 0);
956 for address in response.iter() {
957 assert!(address.is_ipv4(), "should only be looking up IPv4");
958 }
959 }
960
961 pub(crate) async fn ndots_test<R: ConnectionProvider>(handle: R) {
963 let domain = Name::from_str("incorrect.example.com.").unwrap();
964 let search = vec![
965 Name::from_str("bad.example.com.").unwrap(),
966 Name::from_str("wrong.example.com.").unwrap(),
967 ];
968 let name_servers: Vec<NameServerConfig> = ResolverConfig::udp_and_tcp(&GOOGLE)
969 .name_servers()
970 .to_owned();
971
972 let mut resolver_builder = Resolver::<R>::builder_with_config(
973 ResolverConfig::from_parts(Some(domain), search, name_servers),
974 handle,
975 );
976 resolver_builder.options_mut().ndots = 2;
978 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
979 let resolver = resolver_builder.build().unwrap();
980
981 let response = resolver
983 .lookup_ip("www.example.com")
984 .await
985 .expect("failed to run lookup");
986
987 assert_ne!(response.iter().count(), 0);
988 for address in response.iter() {
989 assert!(address.is_ipv4(), "should only be looking up IPv4");
990 }
991 }
992
993 pub(crate) async fn large_ndots_test<R: ConnectionProvider>(handle: R) {
995 let domain = Name::from_str("incorrect.example.com.").unwrap();
996 let search = vec![
997 Name::from_str("bad.example.com.").unwrap(),
998 Name::from_str("wrong.example.com.").unwrap(),
999 ];
1000 let name_servers: Vec<NameServerConfig> = ResolverConfig::udp_and_tcp(&GOOGLE)
1001 .name_servers()
1002 .to_owned();
1003
1004 let mut resolver_builder = Resolver::<R>::builder_with_config(
1005 ResolverConfig::from_parts(Some(domain), search, name_servers),
1006 handle,
1007 );
1008 resolver_builder.options_mut().ndots = 5;
1010 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
1011 let resolver = resolver_builder.build().unwrap();
1012
1013 let response = resolver
1015 .lookup_ip("www.example.com")
1016 .await
1017 .expect("failed to run lookup");
1018
1019 assert_ne!(response.iter().count(), 0);
1020 for address in response.iter() {
1021 assert!(address.is_ipv4(), "should only be looking up IPv4");
1022 }
1023 }
1024
1025 pub(crate) async fn domain_search_test<R: ConnectionProvider>(handle: R) {
1027 let domain = Name::from_str("example.com.").unwrap();
1029 let search = vec![
1030 Name::from_str("bad.example.com.").unwrap(),
1031 Name::from_str("wrong.example.com.").unwrap(),
1032 ];
1033 let name_servers: Vec<NameServerConfig> = ResolverConfig::udp_and_tcp(&GOOGLE)
1034 .name_servers()
1035 .to_owned();
1036
1037 let mut resolver_builder = Resolver::<R>::builder_with_config(
1038 ResolverConfig::from_parts(Some(domain), search, name_servers),
1039 handle,
1040 );
1041 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
1042 let resolver = resolver_builder.build().unwrap();
1043
1044 let response = resolver
1046 .lookup_ip("www")
1047 .await
1048 .expect("failed to run lookup");
1049
1050 assert_ne!(response.iter().count(), 0);
1051 for address in response.iter() {
1052 assert!(address.is_ipv4(), "should only be looking up IPv4");
1053 }
1054 }
1055
1056 pub(crate) async fn search_list_test<R: ConnectionProvider>(handle: R) {
1058 let domain = Name::from_str("incorrect.example.com.").unwrap();
1059 let search = vec![
1060 Name::from_str("bad.example.com.").unwrap(),
1062 Name::from_str("example.com.").unwrap(),
1064 ];
1065 let name_servers: Vec<NameServerConfig> = ResolverConfig::udp_and_tcp(&GOOGLE)
1066 .name_servers()
1067 .to_owned();
1068
1069 let mut resolver_builder = Resolver::<R>::builder_with_config(
1070 ResolverConfig::from_parts(Some(domain), search, name_servers),
1071 handle,
1072 );
1073 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
1074 let resolver = resolver_builder.build().unwrap();
1075
1076 let response = resolver
1078 .lookup_ip("www")
1079 .await
1080 .expect("failed to run lookup");
1081
1082 assert_ne!(response.iter().count(), 0);
1083 for address in response.iter() {
1084 assert!(address.is_ipv4(), "should only be looking up IPv4");
1085 }
1086 }
1087
1088 pub(crate) async fn idna_test<R: ConnectionProvider>(handle: R) {
1090 let resolver =
1091 Resolver::<R>::builder_with_config(ResolverConfig::udp_and_tcp(&GOOGLE), handle)
1092 .build()
1093 .unwrap();
1094
1095 let response = resolver
1096 .lookup_ip("中国.icom.museum.")
1097 .await
1098 .expect("failed to run lookup");
1099
1100 assert!(response.iter().next().is_some());
1103 }
1104
1105 pub(crate) async fn localhost_ipv4_test<R: ConnectionProvider>(handle: R) {
1107 let mut resolver_builder =
1108 Resolver::<R>::builder_with_config(ResolverConfig::udp_and_tcp(&GOOGLE), handle);
1109 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4thenIpv6;
1110 let resolver = resolver_builder.build().unwrap();
1111
1112 let response = resolver
1113 .lookup_ip("localhost")
1114 .await
1115 .expect("failed to run lookup");
1116
1117 let mut iter = response.iter();
1118 assert_eq!(iter.next().expect("no A"), IpAddr::V4(Ipv4Addr::LOCALHOST));
1119 }
1120
1121 pub(crate) async fn localhost_ipv6_test<R: ConnectionProvider>(handle: R) {
1123 let mut resolver_builder =
1124 Resolver::<R>::builder_with_config(ResolverConfig::udp_and_tcp(&GOOGLE), handle);
1125 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv6thenIpv4;
1126 let resolver = resolver_builder.build().unwrap();
1127
1128 let response = resolver
1129 .lookup_ip("localhost")
1130 .await
1131 .expect("failed to run lookup");
1132
1133 let mut iter = response.iter();
1134 assert_eq!(
1135 iter.next().expect("no AAAA"),
1136 IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1,))
1137 );
1138 }
1139
1140 pub(crate) async fn search_ipv4_large_ndots_test<R: ConnectionProvider>(handle: R) {
1142 let mut config = ResolverConfig::udp_and_tcp(&GOOGLE);
1143 config.add_search(Name::from_str("example.com").unwrap());
1144
1145 let mut resolver_builder = Resolver::<R>::builder_with_config(config, handle);
1146 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
1147 resolver_builder.options_mut().ndots = 5;
1148 let resolver = resolver_builder.build().unwrap();
1149
1150 let response = resolver
1151 .lookup_ip("198.51.100.35")
1152 .await
1153 .expect("failed to run lookup");
1154
1155 let mut iter = response.iter();
1156 assert_eq!(
1157 iter.next().expect("no rdatas"),
1158 IpAddr::V4(Ipv4Addr::new(198, 51, 100, 35))
1159 );
1160 }
1161
1162 pub(crate) async fn search_ipv6_large_ndots_test<R: ConnectionProvider>(handle: R) {
1164 let mut config = ResolverConfig::udp_and_tcp(&GOOGLE);
1165 config.add_search(Name::from_str("example.com").unwrap());
1166
1167 let mut resolver_builder = Resolver::<R>::builder_with_config(config, handle);
1168 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
1169 resolver_builder.options_mut().ndots = 5;
1170 let resolver = resolver_builder.build().unwrap();
1171
1172 let response = resolver
1173 .lookup_ip("2001:db8::c633:6423")
1174 .await
1175 .expect("failed to run lookup");
1176
1177 let mut iter = response.iter();
1178 assert_eq!(
1179 iter.next().expect("no rdatas"),
1180 IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc633, 0x6423))
1181 );
1182 }
1183
1184 pub(crate) async fn search_ipv6_name_parse_fails_test<R: ConnectionProvider>(handle: R) {
1186 let mut config = ResolverConfig::udp_and_tcp(&GOOGLE);
1187 config.add_search(Name::from_str("example.com").unwrap());
1188
1189 let mut resolver_builder = Resolver::<R>::builder_with_config(config, handle);
1190 resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
1191 resolver_builder.options_mut().ndots = 5;
1192 let resolver = resolver_builder.build().unwrap();
1193
1194 let response = resolver
1195 .lookup_ip("2001:db8::198.51.100.35")
1196 .await
1197 .expect("failed to run lookup");
1198
1199 let mut iter = response.iter();
1200 assert_eq!(
1201 iter.next().expect("no rdatas"),
1202 IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc633, 0x6423))
1203 );
1204 }
1205}
1206
1207#[cfg(test)]
1208#[cfg(feature = "tokio")]
1209#[allow(clippy::extra_unused_type_parameters)]
1210mod tests {
1211 use std::net::{IpAddr, Ipv4Addr};
1212 use std::sync::Mutex;
1213
1214 use futures_util::stream::once;
1215 use futures_util::{Stream, future};
1216 use test_support::subscribe;
1217
1218 #[cfg(all(unix, feature = "system-config"))]
1219 use super::testing::hosts_lookup_test;
1220 #[cfg(feature = "system-config")]
1221 use super::testing::system_lookup_test;
1222 use super::testing::{
1223 domain_search_test, fqdn_test, idna_test, ip_lookup_across_threads_test, ip_lookup_test,
1224 large_ndots_test, localhost_ipv4_test, localhost_ipv6_test, lookup_test, ndots_test,
1225 search_ipv4_large_ndots_test, search_ipv6_large_ndots_test,
1226 search_ipv6_name_parse_fails_test, search_list_test,
1227 };
1228 #[cfg(feature = "__dnssec")]
1229 use super::testing::{sec_lookup_fails_test, sec_lookup_test};
1230 use super::*;
1231 use crate::config::{CLOUDFLARE, GOOGLE, ResolverConfig, ResolverOpts};
1232 use crate::net::DnsError;
1233 use crate::net::xfer::DnsExchange;
1234 use crate::proto::op::{DnsRequest, DnsResponse, Message};
1235 use crate::proto::rr::rdata::A;
1236
1237 fn is_send_t<T: Send>() -> bool {
1238 true
1239 }
1240
1241 fn is_sync_t<T: Sync>() -> bool {
1242 true
1243 }
1244
1245 #[test]
1246 fn test_send_sync() {
1247 assert!(is_send_t::<ResolverConfig>());
1248 assert!(is_sync_t::<ResolverConfig>());
1249 assert!(is_send_t::<ResolverOpts>());
1250 assert!(is_sync_t::<ResolverOpts>());
1251
1252 assert!(is_send_t::<Resolver<TokioRuntimeProvider>>());
1253 assert!(is_sync_t::<Resolver<TokioRuntimeProvider>>());
1254
1255 assert!(is_send_t::<DnsRequest>());
1256 assert!(is_send_t::<LookupIpFuture<DnsExchange<TokioRuntimeProvider>>>());
1257 assert!(is_send_t::<LookupFuture<DnsExchange<TokioRuntimeProvider>>>());
1258 }
1259
1260 #[tokio::test]
1261 async fn test_lookup_google() {
1262 subscribe();
1263 let handle = TokioRuntimeProvider::default();
1264 lookup_test(ResolverConfig::udp_and_tcp(&GOOGLE), handle).await;
1265 }
1266
1267 #[tokio::test]
1268 async fn test_lookup_cloudflare() {
1269 subscribe();
1270 let handle = TokioRuntimeProvider::default();
1271 lookup_test(ResolverConfig::udp_and_tcp(&CLOUDFLARE), handle).await;
1272 }
1273
1274 #[tokio::test]
1275 async fn test_ip_lookup() {
1276 subscribe();
1277 let handle = TokioRuntimeProvider::default();
1278 ip_lookup_test(handle).await;
1279 }
1280
1281 #[test]
1282 fn test_ip_lookup_across_threads() {
1283 subscribe();
1284 let handle = TokioRuntimeProvider::default();
1285 ip_lookup_across_threads_test(handle);
1286 }
1287
1288 #[tokio::test]
1289 #[cfg(feature = "__dnssec")]
1290 #[ignore = "flaky test against internet server"]
1291 async fn test_sec_lookup() {
1292 subscribe();
1293 let handle = TokioRuntimeProvider::default();
1294 sec_lookup_test(handle).await;
1295 }
1296
1297 #[tokio::test]
1298 #[cfg(feature = "__dnssec")]
1299 #[ignore = "flaky test against internet server"]
1300 async fn test_sec_lookup_fails() {
1301 subscribe();
1302 let handle = TokioRuntimeProvider::default();
1303 sec_lookup_fails_test(handle).await;
1304 }
1305
1306 #[tokio::test]
1307 #[ignore]
1308 #[cfg(any(unix, target_os = "windows"))]
1309 #[cfg(feature = "system-config")]
1310 async fn test_system_lookup() {
1311 subscribe();
1312 let handle = TokioRuntimeProvider::default();
1313 system_lookup_test(handle).await;
1314 }
1315
1316 #[tokio::test]
1318 #[ignore]
1319 #[cfg(all(unix, feature = "system-config"))]
1320 async fn test_hosts_lookup() {
1321 subscribe();
1322 let handle = TokioRuntimeProvider::default();
1323 hosts_lookup_test(handle).await;
1324 }
1325
1326 #[tokio::test]
1327 async fn test_fqdn() {
1328 subscribe();
1329 let handle = TokioRuntimeProvider::default();
1330 fqdn_test(handle).await;
1331 }
1332
1333 #[tokio::test]
1334 async fn test_ndots() {
1335 subscribe();
1336 let handle = TokioRuntimeProvider::default();
1337 ndots_test(handle).await;
1338 }
1339
1340 #[tokio::test]
1341 async fn test_large_ndots() {
1342 subscribe();
1343 let handle = TokioRuntimeProvider::default();
1344 large_ndots_test(handle).await;
1345 }
1346
1347 #[tokio::test]
1348 async fn test_domain_search() {
1349 subscribe();
1350 let handle = TokioRuntimeProvider::default();
1351 domain_search_test(handle).await;
1352 }
1353
1354 #[tokio::test]
1355 async fn test_search_list() {
1356 subscribe();
1357 let handle = TokioRuntimeProvider::default();
1358 search_list_test(handle).await;
1359 }
1360
1361 #[tokio::test]
1362 async fn test_idna() {
1363 subscribe();
1364 let handle = TokioRuntimeProvider::default();
1365 idna_test(handle).await;
1366 }
1367
1368 #[tokio::test]
1369 async fn test_localhost_ipv4() {
1370 subscribe();
1371 let handle = TokioRuntimeProvider::default();
1372 localhost_ipv4_test(handle).await;
1373 }
1374
1375 #[tokio::test]
1376 async fn test_localhost_ipv6() {
1377 subscribe();
1378 let handle = TokioRuntimeProvider::default();
1379 localhost_ipv6_test(handle).await;
1380 }
1381
1382 #[tokio::test]
1383 async fn test_search_ipv4_large_ndots() {
1384 subscribe();
1385 let handle = TokioRuntimeProvider::default();
1386 search_ipv4_large_ndots_test(handle).await;
1387 }
1388
1389 #[tokio::test]
1390 async fn test_search_ipv6_large_ndots() {
1391 subscribe();
1392 let handle = TokioRuntimeProvider::default();
1393 search_ipv6_large_ndots_test(handle).await;
1394 }
1395
1396 #[tokio::test]
1397 async fn test_search_ipv6_name_parse_fails() {
1398 subscribe();
1399 let handle = TokioRuntimeProvider::default();
1400 search_ipv6_name_parse_fails_test(handle).await;
1401 }
1402
1403 #[test]
1404 fn test_build_names() {
1405 use std::str::FromStr;
1406
1407 let handle = TokioRuntimeProvider::default();
1408 let mut config = ResolverConfig::udp_and_tcp(&GOOGLE);
1409 config.add_search(Name::from_ascii("example.com.").unwrap());
1410 let resolver = Resolver::builder_with_config(config, handle)
1411 .build()
1412 .unwrap();
1413
1414 assert_eq!(resolver.build_names(Name::from_str("").unwrap()).len(), 2);
1415 assert_eq!(resolver.build_names(Name::from_str(".").unwrap()).len(), 1);
1416
1417 let fqdn = Name::from_str("foo.example.com.").unwrap();
1418 let name_list = resolver.build_names(Name::from_str("foo").unwrap());
1419 assert!(name_list.contains(&fqdn));
1420
1421 let name_list = resolver.build_names(fqdn.clone());
1422 assert_eq!(name_list.len(), 1);
1423 assert_eq!(name_list.first(), Some(&fqdn));
1424 }
1425
1426 #[test]
1427 fn test_build_names_onion() {
1428 let handle = TokioRuntimeProvider::default();
1429 let mut config = ResolverConfig::udp_and_tcp(&GOOGLE);
1430 config.add_search(Name::from_ascii("example.com.").unwrap());
1431 let resolver = Resolver::builder_with_config(config, handle)
1432 .build()
1433 .unwrap();
1434 let tor_address = [
1435 Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion")
1436 .unwrap(),
1437 Name::from_ascii("www.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion")
1438 .unwrap(), ];
1440 let not_tor_address = [
1441 Name::from_ascii("onion").unwrap(),
1442 Name::from_ascii("www.onion").unwrap(),
1443 Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.www.onion")
1444 .unwrap(), Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion.to")
1446 .unwrap(), ];
1448 for name in &tor_address {
1449 assert_eq!(resolver.build_names(name.clone()).len(), 1);
1450 }
1451 for name in ¬_tor_address {
1452 assert_eq!(resolver.build_names(name.clone()).len(), 2);
1453 }
1454 }
1455
1456 #[tokio::test]
1457 async fn test_lookup() {
1458 assert_eq!(
1459 LookupFuture::lookup(
1460 vec![Name::root()],
1461 RecordType::A,
1462 DnsRequestOptions::default(),
1463 CachingClient::new(0, mock(vec![v4_message()]), false),
1464 )
1465 .await
1466 .unwrap()
1467 .answers()
1468 .iter()
1469 .map(|r| r.data.ip_addr().unwrap())
1470 .collect::<Vec<IpAddr>>(),
1471 vec![Ipv4Addr::LOCALHOST]
1472 );
1473 }
1474
1475 #[tokio::test]
1476 async fn test_lookup_slice() {
1477 assert_eq!(
1478 LookupFuture::lookup(
1479 vec![Name::root()],
1480 RecordType::A,
1481 DnsRequestOptions::default(),
1482 CachingClient::new(0, mock(vec![v4_message()]), false),
1483 )
1484 .await
1485 .unwrap()
1486 .answers()[0]
1487 .data
1488 .ip_addr()
1489 .unwrap(),
1490 Ipv4Addr::LOCALHOST
1491 );
1492 }
1493
1494 #[tokio::test]
1495 async fn test_lookup_into_iter() {
1496 assert_eq!(
1497 LookupFuture::lookup(
1498 vec![Name::root()],
1499 RecordType::A,
1500 DnsRequestOptions::default(),
1501 CachingClient::new(0, mock(vec![v4_message()]), false),
1502 )
1503 .await
1504 .unwrap()
1505 .answers()
1506 .iter()
1507 .map(|r| r.data.ip_addr().unwrap())
1508 .collect::<Vec<IpAddr>>(),
1509 vec![Ipv4Addr::LOCALHOST]
1510 );
1511 }
1512
1513 #[tokio::test]
1514 async fn test_error() {
1515 assert!(
1516 LookupFuture::lookup(
1517 vec![Name::root()],
1518 RecordType::A,
1519 DnsRequestOptions::default(),
1520 CachingClient::new(0, mock(vec![error()]), false),
1521 )
1522 .await
1523 .is_err()
1524 );
1525 }
1526
1527 #[tokio::test]
1528 async fn test_empty_no_response() {
1529 let error = LookupFuture::lookup(
1530 vec![Name::root()],
1531 RecordType::A,
1532 DnsRequestOptions::default(),
1533 CachingClient::new(0, mock(vec![empty()]), false),
1534 )
1535 .await
1536 .expect_err("this should have been a NoRecordsFound");
1537
1538 let NetError::Dns(DnsError::NoRecordsFound(no_records)) = error else {
1539 panic!("wrong error received");
1540 };
1541
1542 assert_eq!(*no_records.query, Query::query(Name::root(), RecordType::A));
1543 assert_eq!(no_records.negative_ttl, None);
1544 }
1545
1546 #[derive(Clone)]
1547 struct MockDnsHandle {
1548 messages: Arc<Mutex<Vec<Result<DnsResponse, NetError>>>>,
1549 }
1550
1551 impl DnsHandle for MockDnsHandle {
1552 type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, NetError>> + Send>>;
1553 type Runtime = TokioRuntimeProvider;
1554
1555 fn send(&self, _: DnsRequest) -> Self::Response {
1556 Box::pin(once(future::ready(
1557 self.messages.lock().unwrap().pop().unwrap_or_else(empty),
1558 )))
1559 }
1560 }
1561
1562 fn v4_message() -> Result<DnsResponse, NetError> {
1563 let mut message = Message::query();
1564 message.add_query(Query::query(Name::root(), RecordType::A));
1565 message.insert_answers(vec![Record::from_rdata(
1566 Name::root(),
1567 86400,
1568 RData::A(A::new(127, 0, 0, 1)),
1569 )]);
1570
1571 let resp = DnsResponse::from_message(message.into_response()).unwrap();
1572 assert!(resp.contains_answer());
1573 Ok(resp)
1574 }
1575
1576 fn empty() -> Result<DnsResponse, NetError> {
1577 Ok(DnsResponse::from_message(Message::query().into_response()).unwrap())
1578 }
1579
1580 fn error() -> Result<DnsResponse, NetError> {
1581 Err(NetError::from(std::io::Error::from(
1582 std::io::ErrorKind::Other,
1583 )))
1584 }
1585
1586 fn mock(messages: Vec<Result<DnsResponse, NetError>>) -> MockDnsHandle {
1587 MockDnsHandle {
1588 messages: Arc::new(Mutex::new(messages)),
1589 }
1590 }
1591}