Skip to main content

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::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        /// Performs a lookup for the associated type.
52        ///
53        /// *hint* queries that end with a '.' are fully qualified names and are cheaper lookups
54        ///
55        /// # Arguments
56        ///
57        /// * `query` - a string which parses to a domain name, failure to parse will return an error
58        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/// A Resolver used with Tokio
66#[cfg(feature = "tokio")]
67pub type TokioResolver = Resolver<TokioRuntimeProvider>;
68
69#[cfg(feature = "tokio")]
70impl TokioResolver {
71    /// Constructs a new Tokio based Resolver with the system configuration.
72    ///
73    /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows.
74    #[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/// An asynchronous resolver for DNS generic over async Runtimes.
82///
83/// The lookup methods on `Resolver` spawn background tasks to perform
84/// queries. The futures returned by a `Resolver` and the corresponding
85/// background tasks need not be spawned on the same executor, or be in the
86/// same thread.
87///
88/// *NOTE* If lookup futures returned by a `Resolver` and the background
89/// tasks are spawned on two separate `CurrentThread` executors, one thread
90/// cannot run both executors simultaneously, so the `run` or `block_on`
91/// functions will cause the thread to deadlock. If both the background work
92/// and the lookup futures are intended to be run on the same thread, they
93/// should be spawned on the same executor.
94#[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    /// Constructs a new [`Resolver`] via [`ResolverBuilder`] with the operating system's
105    /// configuration.
106    ///
107    /// To use this with Tokio, see [TokioResolver::builder_tokio] instead.
108    ///
109    /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows.
110    #[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    /// Construct a new [`Resolver`] via [`ResolverBuilder`] with the provided configuration.
120    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    /// Customizes the static hosts used in this resolver.
139    pub fn set_hosts(&mut self, hosts: Arc<Hosts>) {
140        self.hosts = hosts;
141    }
142
143    /// Generic lookup for any RecordType
144    ///
145    /// *WARNING* this interface may change in the future, see if one of the specializations would be better.
146    ///
147    /// # Arguments
148    ///
149    /// * `name` - name of the record to lookup, if name is not a valid domain name, an error will be returned
150    /// * `record_type` - type of record to lookup, all RecordData responses will be filtered to this type
151    ///
152    /// # Returns
153    ///
154    //  A future for the returned Lookup RData
155    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    /// Performs a dual-stack DNS lookup for the IP for the given hostname.
186    ///
187    /// 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`.
188    ///
189    /// # Arguments
190    /// * `host` - string hostname, if this is an invalid hostname, an error will be returned.
191    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 host is a ip address, return directly.
197        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 ndots are greater than 4, then we can't assume the name is an IpAddr
202            //   this accepts IPv6 as well, b/c IPv6 can take the form: 2001:db8::198.51.100.35
203            //   but `:` is not a valid DNS character, so technically this will fail parsing.
204            //   TODO: should we always do search before returning this?
205            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                // it was a valid IP, return that...
218                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    /// Clear the cache for a specific lookup
240    ///
241    /// # Arguments
242    ///
243    /// * `name` - name of the record to clear
244    /// * `record_type` - type of record to clear
245    ///
246    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 it's fully qualified, we can short circuit the lookup logic
259        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) // size of onion v3 address
266                    .unwrap_or(false)
267        {
268            // if already fully qualified, or if onion address, don't assume it might be a
269            // sub-domain
270            vec![name]
271        } else {
272            // Otherwise we have to build the search list
273            // Note: the vec is built in reverse order of precedence, for stack semantics
274            let mut names =
275                Vec::<Name>::with_capacity(1 /*FQDN*/ + 1 /*DOMAIN*/ + self.search.len());
276
277            // if not meeting ndots, we always do the raw name in the final lookup, or it's a localhost...
278            let raw_name_first: bool =
279                name.num_labels() as usize > self.context.options.ndots || name.is_localhost();
280
281            // if not meeting ndots, we always do the raw name in the final lookup
282            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            // this is the direct name lookup
321            if raw_name_first {
322                // adding the name as though it's an FQDN for lookup
323                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    /// Flushes/Removes all entries from the cache
345    pub fn clear_cache(&self) {
346        self.client_cache.clear_cache();
347    }
348
349    /// Per request options based on the ResolverOpts
350    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        // Set DNSSEC OK bit when DNSSEC validation is enabled
358        #[cfg(feature = "__dnssec")]
359        {
360            request_opts.edns_set_dnssec_ok = self.context.options.validate;
361        }
362
363        request_opts
364    }
365
366    /// Read the options for this resolver.
367    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/// Different lookup options for the lookup attempts and validation
379#[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
407/// A builder to construct a [`Resolver`].
408///
409/// Created by [`Resolver::builder`].
410pub 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    /// Sets the [`ResolverOpts`] to be used by the resolver.
429    ///
430    /// NB: A [`ResolverBuilder<P>`] will use the system configuration e.g., `resolv.conf`, by
431    /// default. Usage of this method will overwrite any options set by the system configuration.
432    ///
433    /// See [`system_conf`][crate::system_conf] for functions that can parse a [`ResolverOpts`]
434    /// from the system configuration, or use [`options_mut()`][ResolverBuilder::with_options] to
435    /// acquire a muitable reference to the existing [`ResolverOpts`].
436    pub fn with_options(mut self, options: ResolverOpts) -> Self {
437        self.options = options;
438        self
439    }
440
441    /// Returns a mutable reference to the [`ResolverOpts`].
442    pub fn options_mut(&mut self) -> &mut ResolverOpts {
443        &mut self.options
444    }
445
446    /// Set the DNSSEC trust anchors to be used by the resolver.
447    #[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    /// Set the TLS configuration to be used by the resolver.
454    #[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    /// Set the opportunistic encryption configuration to be used by the resolver.
461    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    /// Set pre-existing encrypted transport state for use with opportunistic encryption.
470    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    /// Set maximum limits on NSEC3 additional iterations.
479    ///
480    /// See [RFC 9276](https://www.rfc-editor.org/rfc/rfc9276.html). Signed
481    /// zones that exceed the soft limit will be treated as insecure, and signed
482    /// zones that exceed the hard limit will be treated as bogus.
483    #[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    /// Construct the resolver.
495    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/// The Future returned from [`Resolver`] when performing a lookup.
581#[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    /// Perform a lookup from a name and type to a set of RDatas
598    ///
599    /// # Arguments
600    ///
601    /// * `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.
602    /// * `record_type` - type of record being sought
603    /// * `client_cache` - cache with a connection to use for performing all lookups
604    #[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    /// Perform a lookup from a name and type to a set of RDatas, taking the local
621    /// hosts file into account.
622    ///
623    /// # Arguments
624    ///
625    /// * `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.
626    /// * `record_type` - type of record being sought
627    /// * `client_cache` - cache with a connection to use for performing all lookups
628    /// * `hosts` - the local host file, the records inside it will be prioritized over the upstream DNS server
629    #[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            // Try polling the underlying DNS query.
673            let query = self.query.as_mut().poll_unpin(cx);
674
675            // Determine whether or not we will attempt to retry the query.
676            let should_retry = match &query {
677                // If the query is NotReady, yield immediately.
678                Poll::Pending => return Poll::Pending,
679                // If the query returned a successful lookup, we will attempt
680                // to retry if the lookup is empty. Otherwise, we will return
681                // that lookup.
682                Poll::Ready(Ok(lookup)) => lookup.answers().is_empty(),
683                // If the query failed, we will attempt to retry.
684                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                    // If there's another name left to try, build a new query
693                    // for that next name and continue looping.
694                    self.query = self
695                        .client_cache
696                        .lookup(Query::query(name, record_type), options)
697                        .boxed();
698                    // Continue looping with the new query. It will be polled
699                    // on the next iteration of the loop.
700                    continue;
701                }
702            }
703            // If we didn't have to retry the query, or we weren't able to
704            // retry because we've exhausted the names to search, return the
705            // current query.
706            return query;
707            // If we skipped retrying the  query, this will return the
708            // successful lookup, otherwise, if the retry failed, this will
709            // return the last  query result --- either an empty lookup or the
710            // last error we saw.
711        }
712    }
713}
714
715/// Unit tests compatible with different runtime.
716#[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    /// Test IP lookup from URLs.
729    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    /// Test IP lookup from IP literals.
743    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    /// Test IP lookup from IP literals across threads.
773    pub(crate) fn ip_lookup_across_threads_test(handle: TokioRuntimeProvider) {
774        // Test ensuring that running the background task on a separate
775        // executor in a separate thread from the futures returned by the
776        // Resolver works correctly.
777        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    /// Test IP lookup from URLs with DNSSEC validation.
822    #[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    /// Test IP lookup from domains that exist but unsigned with DNSSEC validation.
855    #[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        // needs to be a domain that exists, but is not signed (eventually this will be)
865        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    /// Test Resolver created from system configuration with IP lookup.
882    #[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    /// Test Resolver created from system configuration with host lookups.
910    #[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    /// Test fqdn.
933    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    /// Test ndots with non-fqdn.
962    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        // our name does have 2, the default should be fine, let's just narrow the test criteria a bit.
977        resolver_builder.options_mut().ndots = 2;
978        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
979        let resolver = resolver_builder.build().unwrap();
980
981        // notice this is not a FQDN, no trailing dot.
982        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    /// Test large ndots with non-fqdn.
994    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        // matches kubernetes default
1009        resolver_builder.options_mut().ndots = 5;
1010        resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
1011        let resolver = resolver_builder.build().unwrap();
1012
1013        // notice this is not a FQDN, no trailing dot.
1014        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    /// Test domain search.
1026    pub(crate) async fn domain_search_test<R: ConnectionProvider>(handle: R) {
1027        // domain is good now, should be combined with the name to form www.example.com
1028        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        // notice no dots, should not trigger ndots rule
1045        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    /// Test search lists.
1057    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            // let's skip one search domain to test the loop...
1061            Name::from_str("bad.example.com.").unwrap(),
1062            // this should combine with the search name to form www.example.com
1063            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        // notice no dots, should not trigger ndots rule
1077        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    /// Test idna.
1089    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        // we just care that the request succeeded, not about the actual content
1101        //   it's not certain that the ip won't change.
1102        assert!(response.iter().next().is_some());
1103    }
1104
1105    /// Test ipv4 localhost.
1106    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    /// Test ipv6 localhost.
1122    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    /// Test ipv4 search with large ndots.
1141    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    /// Test ipv6 search with large ndots.
1163    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    /// Test ipv6 name parse fails.
1185    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    // these appear to not work on CI, test on macos with `10.1.0.104  a.com`
1317    #[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(), // subdomain are allowed too
1439        ];
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(), // www before key
1445            Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion.to")
1446                .unwrap(), // Tor2web
1447        ];
1448        for name in &tor_address {
1449            assert_eq!(resolver.build_names(name.clone()).len(), 1);
1450        }
1451        for name in &not_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}