hickory_resolver/
resolver.rs

1// Copyright 2015-2019 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! Structs for creating and using a Resolver
9use std::fmt;
10use std::future::Future;
11use std::net::IpAddr;
12use std::pin::Pin;
13use std::sync::Arc;
14use std::task::{Context, Poll};
15
16use futures_util::{FutureExt, future};
17use tracing::debug;
18
19use crate::caching_client::CachingClient;
20use crate::config::{ResolveHosts, ResolverConfig, ResolverOpts};
21use crate::dns_lru::{self, DnsLru, TtlConfig};
22use crate::error::{ResolveError, ResolveErrorKind};
23use crate::hosts::Hosts;
24use crate::lookup::{self, Lookup, LookupEither};
25use crate::lookup_ip::{LookupIp, LookupIpFuture};
26#[cfg(feature = "tokio")]
27use crate::name_server::TokioConnectionProvider;
28use crate::name_server::{ConnectionProvider, NameServerPool};
29#[cfg(feature = "__dnssec")]
30use crate::proto::dnssec::{DnssecDnsHandle, TrustAnchors};
31use crate::proto::op::Query;
32use crate::proto::rr::domain::usage::ONION;
33use crate::proto::rr::{IntoName, Name, RData, Record, RecordType};
34use crate::proto::xfer::{DnsHandle, DnsRequestOptions, RetryDnsHandle};
35
36/// A builder to construct a [`Resolver`].
37///
38/// Created by [`Resolver::builder`].
39pub struct ResolverBuilder<P> {
40    config: ResolverConfig,
41    options: ResolverOpts,
42    provider: P,
43
44    #[cfg(feature = "__dnssec")]
45    trust_anchor: Option<Arc<TrustAnchors>>,
46}
47
48impl<P> ResolverBuilder<P>
49where
50    P: ConnectionProvider,
51{
52    /// Returns a mutable reference to the [`ResolverOpts`].
53    pub fn options_mut(&mut self) -> &mut ResolverOpts {
54        &mut self.options
55    }
56
57    /// Set the DNSSEC trust anchors to be used by the resolver.
58    #[cfg(feature = "__dnssec")]
59    pub fn with_trust_anchor(mut self, trust_anchor: Arc<TrustAnchors>) -> Self {
60        self.trust_anchor = Some(trust_anchor);
61        self
62    }
63
64    /// Construct the resolver.
65    pub fn build(self) -> Resolver<P> {
66        let Self {
67            config,
68            options,
69            provider,
70            #[cfg(feature = "__dnssec")]
71            trust_anchor,
72        } = self;
73
74        let pool = NameServerPool::from_config_with_provider(&config, options.clone(), provider);
75        let client = RetryDnsHandle::new(pool, options.attempts);
76        let either;
77
78        if options.validate {
79            #[cfg(feature = "__dnssec")]
80            {
81                let trust_anchor =
82                    trust_anchor.unwrap_or_else(|| Arc::new(TrustAnchors::default()));
83                either =
84                    LookupEither::Secure(DnssecDnsHandle::with_trust_anchor(client, trust_anchor));
85            }
86
87            #[cfg(not(feature = "__dnssec"))]
88            {
89                tracing::warn!("validate option is only available with dnssec features");
90                either = LookupEither::Retry(client);
91            }
92        } else {
93            either = LookupEither::Retry(client);
94        }
95
96        let lru = DnsLru::new(options.cache_size, TtlConfig::from_opts(&options));
97        let client_cache = CachingClient::with_cache(lru, either, options.preserve_intermediates);
98
99        let hosts = Arc::new(match options.use_hosts_file {
100            ResolveHosts::Always | ResolveHosts::Auto => Hosts::from_system().unwrap_or_default(),
101            ResolveHosts::Never => Hosts::default(),
102        });
103
104        Resolver {
105            config,
106            options,
107            client_cache,
108            hosts,
109        }
110    }
111}
112
113/// An asynchronous resolver for DNS generic over async Runtimes.
114///
115/// The lookup methods on `Resolver` spawn background tasks to perform
116/// queries. The futures returned by a `Resolver` and the corresponding
117/// background tasks need not be spawned on the same executor, or be in the
118/// same thread.
119///
120/// *NOTE* If lookup futures returned by a `Resolver` and the background
121/// tasks are spawned on two separate `CurrentThread` executors, one thread
122/// cannot run both executors simultaneously, so the `run` or `block_on`
123/// functions will cause the thread to deadlock. If both the background work
124/// and the lookup futures are intended to be run on the same thread, they
125/// should be spawned on the same executor.
126#[derive(Clone)]
127pub struct Resolver<P: ConnectionProvider> {
128    config: ResolverConfig,
129    options: ResolverOpts,
130    client_cache: CachingClient<LookupEither<P>>,
131    hosts: Arc<Hosts>,
132}
133
134/// A Resolver used with Tokio
135#[cfg(feature = "tokio")]
136pub type TokioResolver = Resolver<TokioConnectionProvider>;
137
138macro_rules! lookup_fn {
139    ($p:ident, $l:ty, $r:path) => {
140        /// Performs a lookup for the associated type.
141        ///
142        /// *hint* queries that end with a '.' are fully qualified names and are cheaper lookups
143        ///
144        /// # Arguments
145        ///
146        /// * `query` - a string which parses to a domain name, failure to parse will return an error
147        pub async fn $p<N: IntoName>(&self, query: N) -> Result<$l, ResolveError> {
148            let name = match query.into_name() {
149                Ok(name) => name,
150                Err(err) => {
151                    return Err(err.into());
152                }
153            };
154
155            self.inner_lookup(name, $r, self.request_options()).await
156        }
157    };
158    ($p:ident, $l:ty, $r:path, $t:ty) => {
159        /// Performs a lookup for the associated type.
160        ///
161        /// # Arguments
162        ///
163        /// * `query` - a type which can be converted to `Name` via `From`.
164        pub async fn $p(&self, query: $t) -> Result<$l, ResolveError> {
165            let name = Name::from(query);
166            self.inner_lookup(name, $r, self.request_options()).await
167        }
168    };
169}
170
171#[cfg(feature = "tokio")]
172impl TokioResolver {
173    /// Constructs a new Tokio based Resolver with the system configuration.
174    ///
175    /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows.
176    #[cfg(any(unix, target_os = "windows"))]
177    #[cfg(feature = "system-config")]
178    pub fn builder_tokio() -> Result<ResolverBuilder<TokioConnectionProvider>, ResolveError> {
179        Self::builder(TokioConnectionProvider::default())
180    }
181}
182
183impl<R: ConnectionProvider> Resolver<R> {
184    /// Constructs a new [`Resolver`] via [`ResolverBuilder`] with the operating system's
185    /// configuration.
186    ///
187    /// To use this with Tokio, see [TokioResolver::builder_tokio] instead.
188    ///
189    /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows.
190    #[cfg(any(unix, target_os = "windows"))]
191    #[cfg(feature = "system-config")]
192    pub fn builder(provider: R) -> Result<ResolverBuilder<R>, ResolveError> {
193        let (config, options) = super::system_conf::read_system_conf()?;
194        let mut builder = Self::builder_with_config(config, provider);
195        *builder.options_mut() = options;
196        Ok(builder)
197    }
198
199    /// Construct a new [`Resolver`] via [`ResolverBuilder`] with the provided configuration.
200    pub fn builder_with_config(config: ResolverConfig, provider: R) -> ResolverBuilder<R> {
201        ResolverBuilder {
202            config,
203            options: ResolverOpts::default(),
204            provider,
205            #[cfg(feature = "__dnssec")]
206            trust_anchor: None,
207        }
208    }
209
210    /// Flushes/Removes all entries from the cache
211    pub fn clear_cache(&self) {
212        self.client_cache.clear_cache();
213    }
214
215    /// Read the config for this resolver.
216    pub fn config(&self) -> &ResolverConfig {
217        &self.config
218    }
219
220    /// Read the options for this resolver.
221    pub fn options(&self) -> &ResolverOpts {
222        &self.options
223    }
224}
225
226impl<P: ConnectionProvider> Resolver<P> {
227    /// Per request options based on the ResolverOpts
228    pub(crate) fn request_options(&self) -> DnsRequestOptions {
229        let mut request_opts = DnsRequestOptions::default();
230        request_opts.recursion_desired = self.options.recursion_desired;
231        request_opts.use_edns = self.options.edns0;
232        request_opts.case_randomization = self.options.case_randomization;
233
234        request_opts
235    }
236
237    /// Generic lookup for any RecordType
238    ///
239    /// *WARNING* this interface may change in the future, see if one of the specializations would be better.
240    ///
241    /// # Arguments
242    ///
243    /// * `name` - name of the record to lookup, if name is not a valid domain name, an error will be returned
244    /// * `record_type` - type of record to lookup, all RecordData responses will be filtered to this type
245    ///
246    /// # Returns
247    ///
248    //  A future for the returned Lookup RData
249    pub async fn lookup<N: IntoName>(
250        &self,
251        name: N,
252        record_type: RecordType,
253    ) -> Result<Lookup, ResolveError> {
254        let name = match name.into_name() {
255            Ok(name) => name,
256            Err(err) => return Err(err.into()),
257        };
258
259        self.inner_lookup(name, record_type, self.request_options())
260            .await
261    }
262
263    fn push_name(name: Name, names: &mut Vec<Name>) {
264        if !names.contains(&name) {
265            names.push(name);
266        }
267    }
268
269    fn build_names(&self, name: Name) -> Vec<Name> {
270        // if it's fully qualified, we can short circuit the lookup logic
271        if name.is_fqdn()
272            || ONION.zone_of(&name)
273                && name
274                    .trim_to(2)
275                    .iter()
276                    .next()
277                    .map(|name| name.len() == 56) // size of onion v3 address
278                    .unwrap_or(false)
279        {
280            // if already fully qualified, or if onion address, don't assume it might be a
281            // sub-domain
282            vec![name]
283        } else {
284            // Otherwise we have to build the search list
285            // Note: the vec is built in reverse order of precedence, for stack semantics
286            let mut names =
287                Vec::<Name>::with_capacity(1 /*FQDN*/ + 1 /*DOMAIN*/ + self.config.search().len());
288
289            // if not meeting ndots, we always do the raw name in the final lookup, or it's a localhost...
290            let raw_name_first: bool =
291                name.num_labels() as usize > self.options.ndots || name.is_localhost();
292
293            // if not meeting ndots, we always do the raw name in the final lookup
294            if !raw_name_first {
295                let mut fqdn = name.clone();
296                fqdn.set_fqdn(true);
297                names.push(fqdn);
298            }
299
300            for search in self.config.search().iter().rev() {
301                let name_search = name.clone().append_domain(search);
302
303                match name_search {
304                    Ok(name_search) => Self::push_name(name_search, &mut names),
305                    Err(e) => debug!(
306                        "Not adding {} to {} for search due to error: {}",
307                        search, name, e
308                    ),
309                }
310            }
311
312            if let Some(domain) = self.config.domain() {
313                let name_search = name.clone().append_domain(domain);
314
315                match name_search {
316                    Ok(name_search) => Self::push_name(name_search, &mut names),
317                    Err(e) => debug!(
318                        "Not adding {} to {} for search due to error: {}",
319                        domain, name, e
320                    ),
321                }
322            }
323
324            // this is the direct name lookup
325            if raw_name_first {
326                // adding the name as though it's an FQDN for lookup
327                let mut fqdn = name.clone();
328                fqdn.set_fqdn(true);
329                names.push(fqdn);
330            }
331
332            names
333        }
334    }
335
336    pub(crate) async fn inner_lookup<L>(
337        &self,
338        name: Name,
339        record_type: RecordType,
340        options: DnsRequestOptions,
341    ) -> Result<L, ResolveError>
342    where
343        L: From<Lookup> + Send + Sync + 'static,
344    {
345        let names = self.build_names(name);
346        LookupFuture::lookup_with_hosts(
347            names,
348            record_type,
349            options,
350            self.client_cache.clone(),
351            self.hosts.clone(),
352        )
353        .await
354        .map(L::from)
355    }
356
357    /// Performs a dual-stack DNS lookup for the IP for the given hostname.
358    ///
359    /// See the configuration and options parameters for controlling the way in which A(Ipv4) and AAAA(Ipv6) lookups will be performed. For the least expensive query a fully-qualified-domain-name, FQDN, which ends in a final `.`, e.g. `www.example.com.`, will only issue one query. Anything else will always incur the cost of querying the `ResolverConfig::domain` and `ResolverConfig::search`.
360    ///
361    /// # Arguments
362    /// * `host` - string hostname, if this is an invalid hostname, an error will be returned.
363    pub async fn lookup_ip(&self, host: impl IntoName) -> Result<LookupIp, ResolveError> {
364        let mut finally_ip_addr = None;
365        let maybe_ip = host.to_ip().map(RData::from);
366        let maybe_name = host.into_name();
367
368        // if host is a ip address, return directly.
369        if let Some(ip_addr) = maybe_ip {
370            let name = maybe_name.clone().unwrap_or_default();
371            let record = Record::from_rdata(name.clone(), dns_lru::MAX_TTL, ip_addr.clone());
372
373            // if ndots are greater than 4, then we can't assume the name is an IpAddr
374            //   this accepts IPv6 as well, b/c IPv6 can take the form: 2001:db8::198.51.100.35
375            //   but `:` is not a valid DNS character, so technically this will fail parsing.
376            //   TODO: should we always do search before returning this?
377            if self.options.ndots > 4 {
378                finally_ip_addr = Some(record);
379            } else {
380                let query = Query::query(name, ip_addr.record_type());
381                let lookup = Lookup::new_with_max_ttl(query, Arc::from([record]));
382                return Ok(lookup.into());
383            }
384        }
385
386        let name = match (maybe_name, finally_ip_addr.as_ref()) {
387            (Ok(name), _) => name,
388            (Err(_), Some(ip_addr)) => {
389                // it was a valid IP, return that...
390                let query = Query::query(ip_addr.name().clone(), ip_addr.record_type());
391                let lookup = Lookup::new_with_max_ttl(query, Arc::from([ip_addr.clone()]));
392                return Ok(lookup.into());
393            }
394            (Err(err), None) => {
395                return Err(err.into());
396            }
397        };
398
399        let names = self.build_names(name);
400        let hosts = self.hosts.clone();
401
402        LookupIpFuture::lookup(
403            names,
404            self.options.ip_strategy,
405            self.client_cache.clone(),
406            self.request_options(),
407            hosts,
408            finally_ip_addr.map(Record::into_data),
409        )
410        .await
411    }
412
413    /// Customizes the static hosts used in this resolver.
414    pub fn set_hosts(&mut self, hosts: Arc<Hosts>) {
415        self.hosts = hosts;
416    }
417
418    lookup_fn!(
419        reverse_lookup,
420        lookup::ReverseLookup,
421        RecordType::PTR,
422        IpAddr
423    );
424    lookup_fn!(ipv4_lookup, lookup::Ipv4Lookup, RecordType::A);
425    lookup_fn!(ipv6_lookup, lookup::Ipv6Lookup, RecordType::AAAA);
426    lookup_fn!(mx_lookup, lookup::MxLookup, RecordType::MX);
427    lookup_fn!(ns_lookup, lookup::NsLookup, RecordType::NS);
428    lookup_fn!(soa_lookup, lookup::SoaLookup, RecordType::SOA);
429    lookup_fn!(srv_lookup, lookup::SrvLookup, RecordType::SRV);
430    lookup_fn!(tlsa_lookup, lookup::TlsaLookup, RecordType::TLSA);
431    lookup_fn!(txt_lookup, lookup::TxtLookup, RecordType::TXT);
432    lookup_fn!(cert_lookup, lookup::CertLookup, RecordType::CERT);
433}
434
435impl<P: ConnectionProvider> fmt::Debug for Resolver<P> {
436    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437        f.debug_struct("Resolver").finish()
438    }
439}
440
441/// The Future returned from [`Resolver`] when performing a lookup.
442#[doc(hidden)]
443pub struct LookupFuture<C>
444where
445    C: DnsHandle + 'static,
446{
447    client_cache: CachingClient<C>,
448    names: Vec<Name>,
449    record_type: RecordType,
450    options: DnsRequestOptions,
451    query: Pin<Box<dyn Future<Output = Result<Lookup, ResolveError>> + Send>>,
452}
453
454impl<C> LookupFuture<C>
455where
456    C: DnsHandle + 'static,
457{
458    /// Perform a lookup from a name and type to a set of RDatas
459    ///
460    /// # Arguments
461    ///
462    /// * `names` - a set of DNS names to attempt to resolve, they will be attempted in queue order, i.e. the first is `names.pop()`. Upon each failure, the next will be attempted.
463    /// * `record_type` - type of record being sought
464    /// * `client_cache` - cache with a connection to use for performing all lookups
465    #[doc(hidden)]
466    pub fn lookup(
467        names: Vec<Name>,
468        record_type: RecordType,
469        options: DnsRequestOptions,
470        client_cache: CachingClient<C>,
471    ) -> Self {
472        Self::lookup_with_hosts(
473            names,
474            record_type,
475            options,
476            client_cache,
477            Arc::new(Hosts::default()),
478        )
479    }
480
481    /// Perform a lookup from a name and type to a set of RDatas, taking the local
482    /// hosts file into account.
483    ///
484    /// # Arguments
485    ///
486    /// * `names` - a set of DNS names to attempt to resolve, they will be attempted in queue order, i.e. the first is `names.pop()`. Upon each failure, the next will be attempted.
487    /// * `record_type` - type of record being sought
488    /// * `client_cache` - cache with a connection to use for performing all lookups
489    /// * `hosts` - the local host file, the records inside it will be prioritized over the upstream DNS server
490    #[doc(hidden)]
491    pub fn lookup_with_hosts(
492        mut names: Vec<Name>,
493        record_type: RecordType,
494        options: DnsRequestOptions,
495        client_cache: CachingClient<C>,
496        hosts: Arc<Hosts>,
497    ) -> Self {
498        let name = names.pop().ok_or_else(|| {
499            ResolveError::from(ResolveErrorKind::Message("can not lookup for no names"))
500        });
501
502        let query: Pin<Box<dyn Future<Output = Result<Lookup, ResolveError>> + Send>> = match name {
503            Ok(name) => {
504                let query = Query::query(name, record_type);
505
506                if let Some(lookup) = hosts.lookup_static_host(&query) {
507                    future::ok(lookup).boxed()
508                } else {
509                    client_cache.lookup(query, options).boxed()
510                }
511            }
512            Err(err) => future::err(err).boxed(),
513        };
514
515        Self {
516            client_cache,
517            names,
518            record_type,
519            options,
520            query,
521        }
522    }
523}
524
525impl<C> Future for LookupFuture<C>
526where
527    C: DnsHandle + 'static,
528{
529    type Output = Result<Lookup, ResolveError>;
530
531    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
532        loop {
533            // Try polling the underlying DNS query.
534            let query = self.query.as_mut().poll_unpin(cx);
535
536            // Determine whether or not we will attempt to retry the query.
537            let should_retry = match &query {
538                // If the query is NotReady, yield immediately.
539                Poll::Pending => return Poll::Pending,
540                // If the query returned a successful lookup, we will attempt
541                // to retry if the lookup is empty. Otherwise, we will return
542                // that lookup.
543                Poll::Ready(Ok(lookup)) => lookup.records().is_empty(),
544                // If the query failed, we will attempt to retry.
545                Poll::Ready(Err(_)) => true,
546            };
547
548            if should_retry {
549                if let Some(name) = self.names.pop() {
550                    let record_type = self.record_type;
551                    let options = self.options;
552
553                    // If there's another name left to try, build a new query
554                    // for that next name and continue looping.
555                    self.query = self
556                        .client_cache
557                        .lookup(Query::query(name, record_type), options);
558                    // Continue looping with the new query. It will be polled
559                    // on the next iteration of the loop.
560                    continue;
561                }
562            }
563            // If we didn't have to retry the query, or we weren't able to
564            // retry because we've exhausted the names to search, return the
565            // current query.
566            return query;
567            // If we skipped retrying the  query, this will return the
568            // successful lookup, otherwise, if the retry failed, this will
569            // return the last  query result --- either an empty lookup or the
570            // last error we saw.
571        }
572    }
573}
574
575/// Unit tests compatible with different runtime.
576#[cfg(all(test, feature = "tokio"))]
577pub(crate) mod testing {
578    use std::{net::*, str::FromStr};
579
580    use crate::Resolver;
581    use crate::config::{LookupIpStrategy, NameServerConfig, ResolverConfig};
582    use crate::name_server::ConnectionProvider;
583    use crate::proto::{rr::Name, runtime::Executor};
584
585    /// Test IP lookup from URLs.
586    pub(crate) async fn lookup_test<R: ConnectionProvider>(config: ResolverConfig, handle: R) {
587        let resolver = Resolver::<R>::builder_with_config(config, handle).build();
588
589        let response = resolver
590            .lookup_ip("www.example.com.")
591            .await
592            .expect("failed to run lookup");
593
594        assert_ne!(response.iter().count(), 0);
595    }
596
597    /// Test IP lookup from IP literals.
598    pub(crate) async fn ip_lookup_test<R: ConnectionProvider>(handle: R) {
599        let resolver =
600            Resolver::<R>::builder_with_config(ResolverConfig::default(), handle).build();
601
602        let response = resolver
603            .lookup_ip("10.1.0.2")
604            .await
605            .expect("failed to run lookup");
606
607        assert_eq!(
608            Some(IpAddr::V4(Ipv4Addr::new(10, 1, 0, 2))),
609            response.iter().next()
610        );
611
612        let response = resolver
613            .lookup_ip("2606:2800:21f:cb07:6820:80da:af6b:8b2c")
614            .await
615            .expect("failed to run lookup");
616
617        assert_eq!(
618            Some(IpAddr::V6(Ipv6Addr::new(
619                0x2606, 0x2800, 0x21f, 0xcb07, 0x6820, 0x80da, 0xaf6b, 0x8b2c,
620            ))),
621            response.iter().next()
622        );
623    }
624
625    /// Test IP lookup from IP literals across threads.
626    pub(crate) fn ip_lookup_across_threads_test<E: Executor, R: ConnectionProvider>(handle: R) {
627        // Test ensuring that running the background task on a separate
628        // executor in a separate thread from the futures returned by the
629        // Resolver works correctly.
630        use std::thread;
631        let resolver =
632            Resolver::<R>::builder_with_config(ResolverConfig::default(), handle).build();
633
634        let resolver_one = resolver.clone();
635        let resolver_two = resolver;
636
637        let test_fn = |resolver: Resolver<R>| {
638            let mut exec = E::new();
639
640            let response = exec
641                .block_on(resolver.lookup_ip("10.1.0.2"))
642                .expect("failed to run lookup");
643
644            assert_eq!(
645                Some(IpAddr::V4(Ipv4Addr::new(10, 1, 0, 2))),
646                response.iter().next()
647            );
648
649            let response = exec
650                .block_on(resolver.lookup_ip("2606:2800:21f:cb07:6820:80da:af6b:8b2c"))
651                .expect("failed to run lookup");
652
653            assert_eq!(
654                Some(IpAddr::V6(Ipv6Addr::new(
655                    0x2606, 0x2800, 0x21f, 0xcb07, 0x6820, 0x80da, 0xaf6b, 0x8b2c,
656                ))),
657                response.iter().next()
658            );
659        };
660
661        let thread_one = thread::spawn(move || {
662            test_fn(resolver_one);
663        });
664
665        let thread_two = thread::spawn(move || {
666            test_fn(resolver_two);
667        });
668
669        thread_one.join().expect("thread_one failed");
670        thread_two.join().expect("thread_two failed");
671    }
672
673    /// Test IP lookup from URLs with DNSSEC validation.
674    #[cfg(feature = "__dnssec")]
675    pub(crate) async fn sec_lookup_test<R: ConnectionProvider>(handle: R) {
676        let mut resolver_builder = Resolver::builder_with_config(ResolverConfig::default(), handle);
677        resolver_builder.options_mut().validate = true;
678        resolver_builder.options_mut().try_tcp_on_error = true;
679        let resolver = resolver_builder.build();
680
681        let response = resolver
682            .lookup_ip("www.example.com.")
683            .await
684            .expect("failed to run lookup");
685
686        assert_ne!(response.iter().count(), 0);
687        assert!(
688            response
689                .as_lookup()
690                .record_iter()
691                .any(|record| record.proof().is_secure())
692        );
693    }
694
695    /// Test IP lookup from domains that exist but unsigned with DNSSEC validation.
696    #[allow(deprecated)]
697    #[cfg(feature = "__dnssec")]
698    pub(crate) async fn sec_lookup_fails_test<R: ConnectionProvider>(handle: R) {
699        let mut resolver_builder = Resolver::builder_with_config(ResolverConfig::default(), handle);
700        resolver_builder.options_mut().validate = true;
701        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
702        let resolver = resolver_builder.build();
703
704        // needs to be a domain that exists, but is not signed (eventually this will be)
705        let response = resolver.lookup_ip("hickory-dns.org.").await;
706
707        let lookup_ip = response.unwrap();
708        for record in lookup_ip.as_lookup().record_iter() {
709            assert!(record.proof().is_insecure());
710        }
711    }
712
713    /// Test Resolver created from system configuration with IP lookup.
714    #[cfg(feature = "system-config")]
715    pub(crate) async fn system_lookup_test<R: ConnectionProvider>(handle: R) {
716        let resolver = Resolver::<R>::builder(handle)
717            .expect("failed to create resolver")
718            .build();
719
720        let response = resolver
721            .lookup_ip("www.example.com.")
722            .await
723            .expect("failed to run lookup");
724
725        assert_eq!(response.iter().count(), 2);
726        for address in response.iter() {
727            if address.is_ipv4() {
728                assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 215, 14)));
729            } else {
730                assert_eq!(
731                    address,
732                    IpAddr::V6(Ipv6Addr::new(
733                        0x2606, 0x2800, 0x21f, 0xcb07, 0x6820, 0x80da, 0xaf6b, 0x8b2c,
734                    ))
735                );
736            }
737        }
738    }
739
740    /// Test Resolver created from system configuration with host lookups.
741    #[cfg(feature = "system-config")]
742    pub(crate) async fn hosts_lookup_test<R: ConnectionProvider>(handle: R) {
743        let resolver = Resolver::<R>::builder(handle)
744            .expect("failed to create resolver")
745            .build();
746
747        let response = resolver
748            .lookup_ip("a.com")
749            .await
750            .expect("failed to run lookup");
751
752        assert_eq!(response.iter().count(), 1);
753        for address in response.iter() {
754            if address.is_ipv4() {
755                assert_eq!(address, IpAddr::V4(Ipv4Addr::new(10, 1, 0, 104)));
756            } else {
757                panic!("failed to run lookup");
758            }
759        }
760    }
761
762    /// Test fqdn.
763    pub(crate) async fn fqdn_test<R: ConnectionProvider>(handle: R) {
764        let domain = Name::from_str("incorrect.example.com.").unwrap();
765        let search = vec![
766            Name::from_str("bad.example.com.").unwrap(),
767            Name::from_str("wrong.example.com.").unwrap(),
768        ];
769        let name_servers: Vec<NameServerConfig> =
770            ResolverConfig::default().name_servers().to_owned();
771
772        let mut resolver_builder = Resolver::<R>::builder_with_config(
773            ResolverConfig::from_parts(Some(domain), search, name_servers),
774            handle,
775        );
776        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
777        let resolver = resolver_builder.build();
778
779        let response = resolver
780            .lookup_ip("www.example.com.")
781            .await
782            .expect("failed to run lookup");
783
784        assert_ne!(response.iter().count(), 0);
785        for address in response.iter() {
786            assert!(address.is_ipv4(), "should only be looking up IPv4");
787        }
788    }
789
790    /// Test ndots with non-fqdn.
791    pub(crate) async fn ndots_test<R: ConnectionProvider>(handle: R) {
792        let domain = Name::from_str("incorrect.example.com.").unwrap();
793        let search = vec![
794            Name::from_str("bad.example.com.").unwrap(),
795            Name::from_str("wrong.example.com.").unwrap(),
796        ];
797        let name_servers: Vec<NameServerConfig> =
798            ResolverConfig::default().name_servers().to_owned();
799
800        let mut resolver_builder = Resolver::<R>::builder_with_config(
801            ResolverConfig::from_parts(Some(domain), search, name_servers),
802            handle,
803        );
804        // our name does have 2, the default should be fine, let's just narrow the test criteria a bit.
805        resolver_builder.options_mut().ndots = 2;
806        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
807        let resolver = resolver_builder.build();
808
809        // notice this is not a FQDN, no trailing dot.
810        let response = resolver
811            .lookup_ip("www.example.com")
812            .await
813            .expect("failed to run lookup");
814
815        assert_ne!(response.iter().count(), 0);
816        for address in response.iter() {
817            assert!(address.is_ipv4(), "should only be looking up IPv4");
818        }
819    }
820
821    /// Test large ndots with non-fqdn.
822    pub(crate) async fn large_ndots_test<R: ConnectionProvider>(handle: R) {
823        let domain = Name::from_str("incorrect.example.com.").unwrap();
824        let search = vec![
825            Name::from_str("bad.example.com.").unwrap(),
826            Name::from_str("wrong.example.com.").unwrap(),
827        ];
828        let name_servers: Vec<NameServerConfig> =
829            ResolverConfig::default().name_servers().to_owned();
830
831        let mut resolver_builder = Resolver::<R>::builder_with_config(
832            ResolverConfig::from_parts(Some(domain), search, name_servers),
833            handle,
834        );
835        // matches kubernetes default
836        resolver_builder.options_mut().ndots = 5;
837        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
838        let resolver = resolver_builder.build();
839
840        // notice this is not a FQDN, no trailing dot.
841        let response = resolver
842            .lookup_ip("www.example.com")
843            .await
844            .expect("failed to run lookup");
845
846        assert_ne!(response.iter().count(), 0);
847        for address in response.iter() {
848            assert!(address.is_ipv4(), "should only be looking up IPv4");
849        }
850    }
851
852    /// Test domain search.
853    pub(crate) async fn domain_search_test<R: ConnectionProvider>(handle: R) {
854        // domain is good now, should be combined with the name to form www.example.com
855        let domain = Name::from_str("example.com.").unwrap();
856        let search = vec![
857            Name::from_str("bad.example.com.").unwrap(),
858            Name::from_str("wrong.example.com.").unwrap(),
859        ];
860        let name_servers: Vec<NameServerConfig> =
861            ResolverConfig::default().name_servers().to_owned();
862
863        let mut resolver_builder = Resolver::<R>::builder_with_config(
864            ResolverConfig::from_parts(Some(domain), search, name_servers),
865            handle,
866        );
867        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
868        let resolver = resolver_builder.build();
869
870        // notice no dots, should not trigger ndots rule
871        let response = resolver
872            .lookup_ip("www")
873            .await
874            .expect("failed to run lookup");
875
876        assert_ne!(response.iter().count(), 0);
877        for address in response.iter() {
878            assert!(address.is_ipv4(), "should only be looking up IPv4");
879        }
880    }
881
882    /// Test search lists.
883    pub(crate) async fn search_list_test<R: ConnectionProvider>(handle: R) {
884        let domain = Name::from_str("incorrect.example.com.").unwrap();
885        let search = vec![
886            // let's skip one search domain to test the loop...
887            Name::from_str("bad.example.com.").unwrap(),
888            // this should combine with the search name to form www.example.com
889            Name::from_str("example.com.").unwrap(),
890        ];
891        let name_servers: Vec<NameServerConfig> =
892            ResolverConfig::default().name_servers().to_owned();
893
894        let mut resolver_builder = Resolver::<R>::builder_with_config(
895            ResolverConfig::from_parts(Some(domain), search, name_servers),
896            handle,
897        );
898        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
899        let resolver = resolver_builder.build();
900
901        // notice no dots, should not trigger ndots rule
902        let response = resolver
903            .lookup_ip("www")
904            .await
905            .expect("failed to run lookup");
906
907        assert_ne!(response.iter().count(), 0);
908        for address in response.iter() {
909            assert!(address.is_ipv4(), "should only be looking up IPv4");
910        }
911    }
912
913    /// Test idna.
914    pub(crate) async fn idna_test<R: ConnectionProvider>(handle: R) {
915        let resolver =
916            Resolver::<R>::builder_with_config(ResolverConfig::default(), handle).build();
917
918        let response = resolver
919            .lookup_ip("中国.icom.museum.")
920            .await
921            .expect("failed to run lookup");
922
923        // we just care that the request succeeded, not about the actual content
924        //   it's not certain that the ip won't change.
925        assert!(response.iter().next().is_some());
926    }
927
928    /// Test ipv4 localhost.
929    pub(crate) async fn localhost_ipv4_test<R: ConnectionProvider>(handle: R) {
930        let mut resolver_builder =
931            Resolver::<R>::builder_with_config(ResolverConfig::default(), handle);
932        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4thenIpv6;
933        let resolver = resolver_builder.build();
934
935        let response = resolver
936            .lookup_ip("localhost")
937            .await
938            .expect("failed to run lookup");
939
940        let mut iter = response.iter();
941        assert_eq!(iter.next().expect("no A"), IpAddr::V4(Ipv4Addr::LOCALHOST));
942    }
943
944    /// Test ipv6 localhost.
945    pub(crate) async fn localhost_ipv6_test<R: ConnectionProvider>(handle: R) {
946        let mut resolver_builder =
947            Resolver::<R>::builder_with_config(ResolverConfig::default(), handle);
948        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv6thenIpv4;
949        let resolver = resolver_builder.build();
950
951        let response = resolver
952            .lookup_ip("localhost")
953            .await
954            .expect("failed to run lookup");
955
956        let mut iter = response.iter();
957        assert_eq!(
958            iter.next().expect("no AAAA"),
959            IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1,))
960        );
961    }
962
963    /// Test ipv4 search with large ndots.
964    pub(crate) async fn search_ipv4_large_ndots_test<R: ConnectionProvider>(handle: R) {
965        let mut config = ResolverConfig::default();
966        config.add_search(Name::from_str("example.com").unwrap());
967
968        let mut resolver_builder = Resolver::<R>::builder_with_config(config, handle);
969        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
970        resolver_builder.options_mut().ndots = 5;
971        let resolver = resolver_builder.build();
972
973        let response = resolver
974            .lookup_ip("198.51.100.35")
975            .await
976            .expect("failed to run lookup");
977
978        let mut iter = response.iter();
979        assert_eq!(
980            iter.next().expect("no rdatas"),
981            IpAddr::V4(Ipv4Addr::new(198, 51, 100, 35))
982        );
983    }
984
985    /// Test ipv6 search with large ndots.
986    pub(crate) async fn search_ipv6_large_ndots_test<R: ConnectionProvider>(handle: R) {
987        let mut config = ResolverConfig::default();
988        config.add_search(Name::from_str("example.com").unwrap());
989
990        let mut resolver_builder = Resolver::<R>::builder_with_config(config, handle);
991        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
992        resolver_builder.options_mut().ndots = 5;
993        let resolver = resolver_builder.build();
994
995        let response = resolver
996            .lookup_ip("2001:db8::c633:6423")
997            .await
998            .expect("failed to run lookup");
999
1000        let mut iter = response.iter();
1001        assert_eq!(
1002            iter.next().expect("no rdatas"),
1003            IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc633, 0x6423))
1004        );
1005    }
1006
1007    /// Test ipv6 name parse fails.
1008    pub(crate) async fn search_ipv6_name_parse_fails_test<R: ConnectionProvider>(handle: R) {
1009        let mut config = ResolverConfig::default();
1010        config.add_search(Name::from_str("example.com").unwrap());
1011
1012        let mut resolver_builder = Resolver::<R>::builder_with_config(config, handle);
1013        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
1014        resolver_builder.options_mut().ndots = 5;
1015        let resolver = resolver_builder.build();
1016
1017        let response = resolver
1018            .lookup_ip("2001:db8::198.51.100.35")
1019            .await
1020            .expect("failed to run lookup");
1021
1022        let mut iter = response.iter();
1023        assert_eq!(
1024            iter.next().expect("no rdatas"),
1025            IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc633, 0x6423))
1026        );
1027    }
1028}
1029
1030#[cfg(test)]
1031#[cfg(feature = "tokio")]
1032#[allow(clippy::extra_unused_type_parameters)]
1033mod tests {
1034    use std::net::{IpAddr, Ipv4Addr};
1035    use std::sync::Mutex;
1036
1037    use futures_util::stream::once;
1038    use futures_util::{Stream, future};
1039    use test_support::subscribe;
1040    use tokio::runtime::Runtime;
1041
1042    use super::testing::{
1043        domain_search_test, fqdn_test, idna_test, ip_lookup_across_threads_test, ip_lookup_test,
1044        large_ndots_test, localhost_ipv4_test, localhost_ipv6_test, lookup_test, ndots_test,
1045        search_ipv4_large_ndots_test, search_ipv6_large_ndots_test,
1046        search_ipv6_name_parse_fails_test, search_list_test,
1047    };
1048    #[cfg(feature = "system-config")]
1049    use super::testing::{hosts_lookup_test, system_lookup_test};
1050    #[cfg(feature = "__dnssec")]
1051    use super::testing::{sec_lookup_fails_test, sec_lookup_test};
1052    use super::*;
1053    use crate::config::{ResolverConfig, ResolverOpts};
1054    use crate::name_server::GenericConnection;
1055    use crate::proto::op::Message;
1056    use crate::proto::rr::rdata::A;
1057    use crate::proto::xfer::{DnsRequest, DnsResponse};
1058    use crate::proto::{ProtoError, ProtoErrorKind};
1059
1060    fn is_send_t<T: Send>() -> bool {
1061        true
1062    }
1063
1064    fn is_sync_t<T: Sync>() -> bool {
1065        true
1066    }
1067
1068    #[test]
1069    fn test_send_sync() {
1070        assert!(is_send_t::<ResolverConfig>());
1071        assert!(is_sync_t::<ResolverConfig>());
1072        assert!(is_send_t::<ResolverOpts>());
1073        assert!(is_sync_t::<ResolverOpts>());
1074
1075        assert!(is_send_t::<Resolver<TokioConnectionProvider>>());
1076        assert!(is_sync_t::<Resolver<TokioConnectionProvider>>());
1077
1078        assert!(is_send_t::<DnsRequest>());
1079        assert!(is_send_t::<LookupIpFuture<GenericConnection>>());
1080        assert!(is_send_t::<LookupFuture<GenericConnection>>());
1081    }
1082
1083    #[tokio::test]
1084    async fn test_lookup_google() {
1085        subscribe();
1086        let handle = TokioConnectionProvider::default();
1087        lookup_test(ResolverConfig::google(), handle).await;
1088    }
1089
1090    #[tokio::test]
1091    async fn test_lookup_cloudflare() {
1092        subscribe();
1093        let handle = TokioConnectionProvider::default();
1094        lookup_test(ResolverConfig::cloudflare(), handle).await;
1095    }
1096
1097    #[tokio::test]
1098    async fn test_ip_lookup() {
1099        subscribe();
1100        let handle = TokioConnectionProvider::default();
1101        ip_lookup_test(handle).await;
1102    }
1103
1104    #[test]
1105    fn test_ip_lookup_across_threads() {
1106        subscribe();
1107        let handle = TokioConnectionProvider::default();
1108        ip_lookup_across_threads_test::<Runtime, _>(handle);
1109    }
1110
1111    #[tokio::test]
1112    #[cfg(feature = "__dnssec")]
1113    async fn test_sec_lookup() {
1114        subscribe();
1115        let handle = TokioConnectionProvider::default();
1116        sec_lookup_test(handle).await;
1117    }
1118
1119    #[tokio::test]
1120    #[cfg(feature = "__dnssec")]
1121    async fn test_sec_lookup_fails() {
1122        subscribe();
1123        let handle = TokioConnectionProvider::default();
1124        sec_lookup_fails_test(handle).await;
1125    }
1126
1127    #[tokio::test]
1128    #[ignore]
1129    #[cfg(any(unix, target_os = "windows"))]
1130    #[cfg(feature = "system-config")]
1131    async fn test_system_lookup() {
1132        subscribe();
1133        let handle = TokioConnectionProvider::default();
1134        system_lookup_test(handle).await;
1135    }
1136
1137    #[tokio::test]
1138    #[ignore]
1139    // these appear to not work on CI, test on macos with `10.1.0.104  a.com`
1140    #[cfg(unix)]
1141    #[cfg(feature = "system-config")]
1142    async fn test_hosts_lookup() {
1143        subscribe();
1144        let handle = TokioConnectionProvider::default();
1145        hosts_lookup_test(handle).await;
1146    }
1147
1148    #[tokio::test]
1149    async fn test_fqdn() {
1150        subscribe();
1151        let handle = TokioConnectionProvider::default();
1152        fqdn_test(handle).await;
1153    }
1154
1155    #[tokio::test]
1156    async fn test_ndots() {
1157        subscribe();
1158        let handle = TokioConnectionProvider::default();
1159        ndots_test(handle).await;
1160    }
1161
1162    #[tokio::test]
1163    async fn test_large_ndots() {
1164        subscribe();
1165        let handle = TokioConnectionProvider::default();
1166        large_ndots_test(handle).await;
1167    }
1168
1169    #[tokio::test]
1170    async fn test_domain_search() {
1171        subscribe();
1172        let handle = TokioConnectionProvider::default();
1173        domain_search_test(handle).await;
1174    }
1175
1176    #[tokio::test]
1177    async fn test_search_list() {
1178        subscribe();
1179        let handle = TokioConnectionProvider::default();
1180        search_list_test(handle).await;
1181    }
1182
1183    #[tokio::test]
1184    async fn test_idna() {
1185        subscribe();
1186        let handle = TokioConnectionProvider::default();
1187        idna_test(handle).await;
1188    }
1189
1190    #[tokio::test]
1191    async fn test_localhost_ipv4() {
1192        subscribe();
1193        let handle = TokioConnectionProvider::default();
1194        localhost_ipv4_test(handle).await;
1195    }
1196
1197    #[tokio::test]
1198    async fn test_localhost_ipv6() {
1199        subscribe();
1200        let handle = TokioConnectionProvider::default();
1201        localhost_ipv6_test(handle).await;
1202    }
1203
1204    #[tokio::test]
1205    async fn test_search_ipv4_large_ndots() {
1206        subscribe();
1207        let handle = TokioConnectionProvider::default();
1208        search_ipv4_large_ndots_test(handle).await;
1209    }
1210
1211    #[tokio::test]
1212    async fn test_search_ipv6_large_ndots() {
1213        subscribe();
1214        let handle = TokioConnectionProvider::default();
1215        search_ipv6_large_ndots_test(handle).await;
1216    }
1217
1218    #[tokio::test]
1219    async fn test_search_ipv6_name_parse_fails() {
1220        subscribe();
1221        let handle = TokioConnectionProvider::default();
1222        search_ipv6_name_parse_fails_test(handle).await;
1223    }
1224
1225    #[test]
1226    fn test_build_names() {
1227        use std::str::FromStr;
1228
1229        let handle = TokioConnectionProvider::default();
1230        let mut config = ResolverConfig::default();
1231        config.add_search(Name::from_ascii("example.com.").unwrap());
1232        let resolver = Resolver::builder_with_config(config, handle).build();
1233
1234        assert_eq!(resolver.build_names(Name::from_str("").unwrap()).len(), 2);
1235        assert_eq!(resolver.build_names(Name::from_str(".").unwrap()).len(), 1);
1236
1237        let fqdn = Name::from_str("foo.example.com.").unwrap();
1238        let name_list = resolver.build_names(Name::from_str("foo").unwrap());
1239        assert!(name_list.contains(&fqdn));
1240
1241        let name_list = resolver.build_names(fqdn.clone());
1242        assert_eq!(name_list.len(), 1);
1243        assert_eq!(name_list.first(), Some(&fqdn));
1244    }
1245
1246    #[test]
1247    fn test_build_names_onion() {
1248        let handle = TokioConnectionProvider::default();
1249        let mut config = ResolverConfig::default();
1250        config.add_search(Name::from_ascii("example.com.").unwrap());
1251        let resolver = Resolver::builder_with_config(config, handle).build();
1252        let tor_address = [
1253            Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion")
1254                .unwrap(),
1255            Name::from_ascii("www.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion")
1256                .unwrap(), // subdomain are allowed too
1257        ];
1258        let not_tor_address = [
1259            Name::from_ascii("onion").unwrap(),
1260            Name::from_ascii("www.onion").unwrap(),
1261            Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.www.onion")
1262                .unwrap(), // www before key
1263            Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion.to")
1264                .unwrap(), // Tor2web
1265        ];
1266        for name in &tor_address {
1267            assert_eq!(resolver.build_names(name.clone()).len(), 1);
1268        }
1269        for name in &not_tor_address {
1270            assert_eq!(resolver.build_names(name.clone()).len(), 2);
1271        }
1272    }
1273
1274    #[tokio::test]
1275    async fn test_lookup() {
1276        assert_eq!(
1277            LookupFuture::lookup(
1278                vec![Name::root()],
1279                RecordType::A,
1280                DnsRequestOptions::default(),
1281                CachingClient::new(0, mock(vec![v4_message()]), false),
1282            )
1283            .await
1284            .unwrap()
1285            .iter()
1286            .map(|r| r.ip_addr().unwrap())
1287            .collect::<Vec<IpAddr>>(),
1288            vec![Ipv4Addr::LOCALHOST]
1289        );
1290    }
1291
1292    #[tokio::test]
1293    async fn test_lookup_slice() {
1294        assert_eq!(
1295            Record::data(
1296                &LookupFuture::lookup(
1297                    vec![Name::root()],
1298                    RecordType::A,
1299                    DnsRequestOptions::default(),
1300                    CachingClient::new(0, mock(vec![v4_message()]), false),
1301                )
1302                .await
1303                .unwrap()
1304                .records()[0]
1305            )
1306            .ip_addr()
1307            .unwrap(),
1308            Ipv4Addr::LOCALHOST
1309        );
1310    }
1311
1312    #[tokio::test]
1313    async fn test_lookup_into_iter() {
1314        assert_eq!(
1315            LookupFuture::lookup(
1316                vec![Name::root()],
1317                RecordType::A,
1318                DnsRequestOptions::default(),
1319                CachingClient::new(0, mock(vec![v4_message()]), false),
1320            )
1321            .await
1322            .unwrap()
1323            .into_iter()
1324            .map(|r| r.ip_addr().unwrap())
1325            .collect::<Vec<IpAddr>>(),
1326            vec![Ipv4Addr::LOCALHOST]
1327        );
1328    }
1329
1330    #[tokio::test]
1331    async fn test_error() {
1332        assert!(
1333            LookupFuture::lookup(
1334                vec![Name::root()],
1335                RecordType::A,
1336                DnsRequestOptions::default(),
1337                CachingClient::new(0, mock(vec![error()]), false),
1338            )
1339            .await
1340            .is_err()
1341        );
1342    }
1343
1344    #[tokio::test]
1345    async fn test_empty_no_response() {
1346        if let ProtoErrorKind::NoRecordsFound {
1347            query,
1348            negative_ttl,
1349            ..
1350        } = LookupFuture::lookup(
1351            vec![Name::root()],
1352            RecordType::A,
1353            DnsRequestOptions::default(),
1354            CachingClient::new(0, mock(vec![empty()]), false),
1355        )
1356        .await
1357        .expect_err("this should have been a NoRecordsFound")
1358        .proto()
1359        .expect("it should have been a ProtoError")
1360        .kind()
1361        {
1362            assert_eq!(**query, Query::query(Name::root(), RecordType::A));
1363            assert_eq!(*negative_ttl, None);
1364        } else {
1365            panic!("wrong error received");
1366        }
1367    }
1368
1369    #[derive(Clone)]
1370    struct MockDnsHandle {
1371        messages: Arc<Mutex<Vec<Result<DnsResponse, ProtoError>>>>,
1372    }
1373
1374    impl DnsHandle for MockDnsHandle {
1375        type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, ProtoError>> + Send>>;
1376
1377        fn send<R: Into<DnsRequest>>(&self, _: R) -> Self::Response {
1378            Box::pin(once(
1379                future::ready(self.messages.lock().unwrap().pop().unwrap_or_else(empty)).boxed(),
1380            ))
1381        }
1382    }
1383
1384    fn v4_message() -> Result<DnsResponse, ProtoError> {
1385        let mut message = Message::new();
1386        message.add_query(Query::query(Name::root(), RecordType::A));
1387        message.insert_answers(vec![Record::from_rdata(
1388            Name::root(),
1389            86400,
1390            RData::A(A::new(127, 0, 0, 1)),
1391        )]);
1392
1393        let resp = DnsResponse::from_message(message).unwrap();
1394        assert!(resp.contains_answer());
1395        Ok(resp)
1396    }
1397
1398    fn empty() -> Result<DnsResponse, ProtoError> {
1399        Ok(DnsResponse::from_message(Message::new()).unwrap())
1400    }
1401
1402    fn error() -> Result<DnsResponse, ProtoError> {
1403        Err(ProtoError::from(std::io::Error::from(
1404            std::io::ErrorKind::Other,
1405        )))
1406    }
1407
1408    fn mock(messages: Vec<Result<DnsResponse, ProtoError>>) -> MockDnsHandle {
1409        MockDnsHandle {
1410            messages: Arc::new(Mutex::new(messages)),
1411        }
1412    }
1413}