hickory_resolver/
async_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 AsyncResolver
9use std::fmt;
10use std::net::IpAddr;
11use std::sync::Arc;
12
13use proto::error::ProtoResult;
14use proto::op::Query;
15use proto::rr::domain::usage::ONION;
16use proto::rr::domain::TryParseIp;
17use proto::rr::{IntoName, Name, Record, RecordType};
18use proto::xfer::{DnsRequestOptions, RetryDnsHandle};
19use tracing::{debug, trace};
20
21use crate::caching_client::CachingClient;
22use crate::config::{ResolverConfig, ResolverOpts};
23use crate::dns_lru::{self, DnsLru};
24use crate::error::*;
25use crate::lookup::{self, Lookup, LookupEither, LookupFuture};
26use crate::lookup_ip::{LookupIp, LookupIpFuture};
27#[cfg(feature = "tokio-runtime")]
28use crate::name_server::TokioConnectionProvider;
29use crate::name_server::{ConnectionProvider, NameServerPool};
30
31use crate::Hosts;
32
33/// An asynchronous resolver for DNS generic over async Runtimes.
34///
35/// Creating a `AsyncResolver` returns a new handle and a future that should
36/// be spawned on an executor to drive the background work. The lookup methods
37/// on `AsyncResolver` request lookups from the background task.
38///
39/// The futures returned by a `AsyncResolver` and the corresponding background
40/// task need not be spawned on the same executor, or be in the same thread.
41///  Additionally, one background task may have any number of handles; calling
42/// `clone()` on a handle will create a new handle linked to the same
43/// background task.
44///
45/// *NOTE* If lookup futures returned by a `AsyncResolver` and the background
46/// future are spawned on two separate `CurrentThread` executors, one thread
47/// cannot run both executors simultaneously, so the `run` or `block_on`
48/// functions will cause the thread to deadlock. If both the background work
49/// and the lookup futures are intended to be run on the same thread, they
50/// should be spawned on the same executor.
51///
52/// The background task manages the name server pool and other state used
53/// to drive lookups. When this future is spawned on an executor, it will
54/// first construct and configure the necessary client state, before checking
55/// for any incoming lookup requests, handling them, and yielding. It will
56/// continue to do so as long as there are still any [`AsyncResolver`] handle
57/// linked to it. When all of its [`AsyncResolver`]s have been dropped, the
58/// background future will finish.
59#[derive(Clone)]
60pub struct AsyncResolver<P: ConnectionProvider> {
61    config: ResolverConfig,
62    options: ResolverOpts,
63    client_cache: CachingClient<LookupEither<P>, ResolveError>,
64    hosts: Option<Arc<Hosts>>,
65}
66
67/// An AsyncResolver used with Tokio
68#[cfg(feature = "tokio-runtime")]
69#[cfg_attr(docsrs, doc(cfg(feature = "tokio-runtime")))]
70pub type TokioAsyncResolver = AsyncResolver<TokioConnectionProvider>;
71
72macro_rules! lookup_fn {
73    ($p:ident, $l:ty, $r:path) => {
74        /// Performs a lookup for the associated type.
75        ///
76        /// *hint* queries that end with a '.' are fully qualified names and are cheaper lookups
77        ///
78        /// # Arguments
79        ///
80        /// * `query` - a string which parses to a domain name, failure to parse will return an error
81        pub async fn $p<N: IntoName>(&self, query: N) -> Result<$l, ResolveError> {
82            let name = match query.into_name() {
83                Ok(name) => name,
84                Err(err) => {
85                    return Err(err.into());
86                }
87            };
88
89            self.inner_lookup(name, $r, self.request_options()).await
90        }
91    };
92    ($p:ident, $l:ty, $r:path, $t:ty) => {
93        /// Performs a lookup for the associated type.
94        ///
95        /// # Arguments
96        ///
97        /// * `query` - a type which can be converted to `Name` via `From`.
98        pub async fn $p(&self, query: $t) -> Result<$l, ResolveError> {
99            let name = Name::from(query);
100            self.inner_lookup(name, $r, self.request_options()).await
101        }
102    };
103}
104
105#[cfg(feature = "tokio-runtime")]
106#[cfg_attr(docsrs, doc(cfg(feature = "tokio-runtime")))]
107impl TokioAsyncResolver {
108    /// Construct a new Tokio based `AsyncResolver` with the provided configuration.
109    ///
110    /// # Arguments
111    ///
112    /// * `config` - configuration, name_servers, etc. for the Resolver
113    /// * `options` - basic lookup options for the resolver
114    ///
115    /// # Returns
116    ///
117    /// A tuple containing the new `AsyncResolver` and a future that drives the
118    /// background task that runs resolutions for the `AsyncResolver`. See the
119    /// documentation for `AsyncResolver` for more information on how to use
120    /// the background future.
121    pub fn tokio(config: ResolverConfig, options: ResolverOpts) -> Self {
122        Self::new(config, options, TokioConnectionProvider::default())
123    }
124
125    /// Constructs a new Tokio based Resolver with the system configuration.
126    ///
127    /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows.
128    #[cfg(any(unix, target_os = "windows"))]
129    #[cfg(feature = "system-config")]
130    #[cfg_attr(
131        docsrs,
132        doc(cfg(all(feature = "system-config", any(unix, target_os = "windows"))))
133    )]
134    pub fn tokio_from_system_conf() -> Result<Self, ResolveError> {
135        Self::from_system_conf(TokioConnectionProvider::default())
136    }
137}
138
139impl<R: ConnectionProvider> AsyncResolver<R> {
140    /// Construct a new generic `AsyncResolver` with the provided configuration.
141    ///
142    /// see [TokioAsyncResolver::tokio(..)] instead.
143    ///
144    /// # Arguments
145    ///
146    /// * `config` - configuration, name_servers, etc. for the Resolver
147    /// * `options` - basic lookup options for the resolver
148    ///
149    /// # Returns
150    ///
151    /// A tuple containing the new `AsyncResolver` and a future that drives the
152    /// background task that runs resolutions for the `AsyncResolver`. See the
153    /// documentation for `AsyncResolver` for more information on how to use
154    /// the background future.
155    pub fn new(config: ResolverConfig, options: ResolverOpts, provider: R) -> Self {
156        Self::new_with_conn(config, options, provider)
157    }
158
159    /// Constructs a new Resolver with the system configuration.
160    ///
161    /// see [TokioAsyncResolver::tokio_from_system_conf(..)] instead.
162    ///
163    /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows.
164    #[cfg(any(unix, target_os = "windows"))]
165    #[cfg(feature = "system-config")]
166    #[cfg_attr(
167        docsrs,
168        doc(cfg(all(feature = "system-config", any(unix, target_os = "windows"))))
169    )]
170    pub fn from_system_conf(runtime: R) -> Result<Self, ResolveError> {
171        Self::from_system_conf_with_provider(runtime)
172    }
173
174    /// Flushes/Removes all entries from the cache
175    pub fn clear_cache(&self) {
176        self.client_cache.clear_cache();
177    }
178}
179
180impl<P: ConnectionProvider> AsyncResolver<P> {
181    /// Construct a new `AsyncResolver` with the provided configuration.
182    ///
183    /// # Arguments
184    ///
185    /// * `config` - configuration, name_servers, etc. for the Resolver
186    /// * `options` - basic lookup options for the resolver
187    ///
188    /// # Returns
189    ///
190    /// A tuple containing the new `AsyncResolver` and a future that drives the
191    /// background task that runs resolutions for the `AsyncResolver`. See the
192    /// documentation for `AsyncResolver` for more information on how to use
193    /// the background future.
194    pub fn new_with_conn(config: ResolverConfig, options: ResolverOpts, conn_provider: P) -> Self {
195        let pool =
196            NameServerPool::from_config_with_provider(&config, options.clone(), conn_provider);
197        let either;
198        let client = RetryDnsHandle::new(pool, options.attempts);
199        if options.validate {
200            #[cfg(feature = "dnssec")]
201            {
202                use proto::xfer::DnssecDnsHandle;
203                either = LookupEither::Secure(DnssecDnsHandle::new(client));
204            }
205
206            #[cfg(not(feature = "dnssec"))]
207            {
208                // TODO: should this just be a panic, or a pinned error?
209                tracing::warn!("validate option is only available with 'dnssec' feature");
210                either = LookupEither::Retry(client);
211            }
212        } else {
213            either = LookupEither::Retry(client);
214        }
215
216        let hosts = if options.use_hosts_file {
217            Some(Arc::new(Hosts::new()))
218        } else {
219            None
220        };
221
222        trace!("handle passed back");
223        let lru = DnsLru::new(options.cache_size, dns_lru::TtlConfig::from_opts(&options));
224        Self {
225            config,
226            client_cache: CachingClient::with_cache(lru, either, options.preserve_intermediates),
227            options,
228            hosts,
229        }
230    }
231
232    /// Constructs a new Resolver with the system configuration.
233    ///
234    /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows.
235    #[cfg(any(unix, target_os = "windows"))]
236    #[cfg(feature = "system-config")]
237    #[cfg_attr(
238        docsrs,
239        doc(cfg(all(feature = "system-config", any(unix, target_os = "windows"))))
240    )]
241    pub fn from_system_conf_with_provider(conn_provider: P) -> Result<Self, ResolveError> {
242        let (config, options) = super::system_conf::read_system_conf()?;
243        Ok(Self::new_with_conn(config, options, conn_provider))
244    }
245
246    /// Per request options based on the ResolverOpts
247    pub(crate) fn request_options(&self) -> DnsRequestOptions {
248        let mut request_opts = DnsRequestOptions::default();
249        request_opts.recursion_desired = self.options.recursion_desired;
250        request_opts.use_edns = self.options.edns0;
251
252        request_opts
253    }
254
255    /// Generic lookup for any RecordType
256    ///
257    /// *WARNING* this interface may change in the future, see if one of the specializations would be better.
258    ///
259    /// # Arguments
260    ///
261    /// * `name` - name of the record to lookup, if name is not a valid domain name, an error will be returned
262    /// * `record_type` - type of record to lookup, all RecordData responses will be filtered to this type
263    ///
264    /// # Returns
265    ///
266    //  A future for the returned Lookup RData
267    pub async fn lookup<N: IntoName>(
268        &self,
269        name: N,
270        record_type: RecordType,
271    ) -> Result<Lookup, ResolveError> {
272        let name = match name.into_name() {
273            Ok(name) => name,
274            Err(err) => return Err(err.into()),
275        };
276
277        self.inner_lookup(name, record_type, self.request_options())
278            .await
279    }
280
281    fn push_name(name: Name, names: &mut Vec<Name>) {
282        if !names.contains(&name) {
283            names.push(name);
284        }
285    }
286
287    fn build_names(&self, name: Name) -> Vec<Name> {
288        // if it's fully qualified, we can short circuit the lookup logic
289        if name.is_fqdn()
290            || ONION.zone_of(&name)
291                && name
292                    .trim_to(2)
293                    .iter()
294                    .next()
295                    .map(|name| name.len() == 56) // size of onion v3 address
296                    .unwrap_or(false)
297        {
298            // if already fully qualified, or if onion address, don't assume it might be a
299            // sub-domain
300            vec![name]
301        } else {
302            // Otherwise we have to build the search list
303            // Note: the vec is built in reverse order of precedence, for stack semantics
304            let mut names =
305                Vec::<Name>::with_capacity(1 /*FQDN*/ + 1 /*DOMAIN*/ + self.config.search().len());
306
307            // if not meeting ndots, we always do the raw name in the final lookup, or it's a localhost...
308            let raw_name_first: bool =
309                name.num_labels() as usize > self.options.ndots || name.is_localhost();
310
311            // if not meeting ndots, we always do the raw name in the final lookup
312            if !raw_name_first {
313                names.push(name.clone());
314            }
315
316            for search in self.config.search().iter().rev() {
317                let name_search = name.clone().append_domain(search);
318
319                match name_search {
320                    Ok(name_search) => Self::push_name(name_search, &mut names),
321                    Err(e) => debug!(
322                        "Not adding {} to {} for search due to error: {}",
323                        search, name, e
324                    ),
325                }
326            }
327
328            if let Some(domain) = self.config.domain() {
329                let name_search = name.clone().append_domain(domain);
330
331                match name_search {
332                    Ok(name_search) => Self::push_name(name_search, &mut names),
333                    Err(e) => debug!(
334                        "Not adding {} to {} for search due to error: {}",
335                        domain, name, e
336                    ),
337                }
338            }
339
340            // this is the direct name lookup
341            if raw_name_first {
342                // adding the name as though it's an FQDN for lookup
343                names.push(name);
344            }
345
346            names
347        }
348    }
349
350    pub(crate) async fn inner_lookup<L>(
351        &self,
352        name: Name,
353        record_type: RecordType,
354        options: DnsRequestOptions,
355    ) -> Result<L, ResolveError>
356    where
357        L: From<Lookup> + Send + 'static,
358    {
359        let names = self.build_names(name);
360        LookupFuture::lookup(names, record_type, options, self.client_cache.clone())
361            .await
362            .map(L::from)
363    }
364
365    /// Performs a dual-stack DNS lookup for the IP for the given hostname.
366    ///
367    /// 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`.
368    ///
369    /// # Arguments
370    /// * `host` - string hostname, if this is an invalid hostname, an error will be returned.
371    pub async fn lookup_ip<N: IntoName + TryParseIp>(
372        &self,
373        host: N,
374    ) -> Result<LookupIp, ResolveError> {
375        let mut finally_ip_addr: Option<Record> = None;
376        let maybe_ip = host.try_parse_ip();
377        let maybe_name: ProtoResult<Name> = host.into_name();
378
379        // if host is a ip address, return directly.
380        if let Some(ip_addr) = maybe_ip {
381            let name = maybe_name.clone().unwrap_or_default();
382            let record = Record::from_rdata(name.clone(), dns_lru::MAX_TTL, ip_addr.clone());
383
384            // if ndots are greater than 4, then we can't assume the name is an IpAddr
385            //   this accepts IPv6 as well, b/c IPv6 can take the form: 2001:db8::198.51.100.35
386            //   but `:` is not a valid DNS character, so technically this will fail parsing.
387            //   TODO: should we always do search before returning this?
388            if self.options.ndots > 4 {
389                finally_ip_addr = Some(record);
390            } else {
391                let query = Query::query(name, ip_addr.record_type());
392                let lookup = Lookup::new_with_max_ttl(query, Arc::from([record]));
393                return Ok(lookup.into());
394            }
395        }
396
397        let name = match (maybe_name, finally_ip_addr.as_ref()) {
398            (Ok(name), _) => name,
399            (Err(_), Some(ip_addr)) => {
400                // it was a valid IP, return that...
401                let query = Query::query(ip_addr.name().clone(), ip_addr.record_type());
402                let lookup = Lookup::new_with_max_ttl(query, Arc::from([ip_addr.clone()]));
403                return Ok(lookup.into());
404            }
405            (Err(err), None) => {
406                return Err(err.into());
407            }
408        };
409
410        let names = self.build_names(name);
411        let hosts = self.hosts.as_ref().cloned();
412
413        LookupIpFuture::lookup(
414            names,
415            self.options.ip_strategy,
416            self.client_cache.clone(),
417            self.request_options(),
418            hosts,
419            finally_ip_addr.and_then(Record::into_data),
420        )
421        .await
422    }
423
424    /// Customizes the static hosts used in this resolver.
425    pub fn set_hosts(&mut self, hosts: Option<Hosts>) {
426        self.hosts = hosts.map(Arc::new);
427    }
428
429    lookup_fn!(
430        reverse_lookup,
431        lookup::ReverseLookup,
432        RecordType::PTR,
433        IpAddr
434    );
435    lookup_fn!(ipv4_lookup, lookup::Ipv4Lookup, RecordType::A);
436    lookup_fn!(ipv6_lookup, lookup::Ipv6Lookup, RecordType::AAAA);
437    lookup_fn!(mx_lookup, lookup::MxLookup, RecordType::MX);
438    lookup_fn!(ns_lookup, lookup::NsLookup, RecordType::NS);
439    lookup_fn!(soa_lookup, lookup::SoaLookup, RecordType::SOA);
440    lookup_fn!(srv_lookup, lookup::SrvLookup, RecordType::SRV);
441    lookup_fn!(tlsa_lookup, lookup::TlsaLookup, RecordType::TLSA);
442    lookup_fn!(txt_lookup, lookup::TxtLookup, RecordType::TXT);
443}
444
445impl<P: ConnectionProvider> fmt::Debug for AsyncResolver<P> {
446    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447        f.debug_struct("AsyncResolver")
448            .field("request_tx", &"...")
449            .finish()
450    }
451}
452
453/// Unit tests compatible with different runtime.
454#[cfg(any(test, feature = "testing"))]
455#[cfg_attr(docsrs, doc(cfg(feature = "testing")))]
456#[allow(dead_code, unreachable_pub)]
457pub mod testing {
458    use std::{net::*, str::FromStr};
459
460    use crate::config::{LookupIpStrategy, NameServerConfig, ResolverConfig, ResolverOpts};
461    use crate::name_server::ConnectionProvider;
462    use crate::AsyncResolver;
463    use proto::{rr::Name, Executor};
464
465    /// Test IP lookup from URLs.
466    pub fn lookup_test<E: Executor, R: ConnectionProvider>(
467        config: ResolverConfig,
468        mut exec: E,
469        handle: R,
470    ) {
471        let resolver = AsyncResolver::<R>::new(config, ResolverOpts::default(), handle);
472
473        let response = exec
474            .block_on(resolver.lookup_ip("www.example.com."))
475            .expect("failed to run lookup");
476
477        assert_ne!(response.iter().count(), 0);
478    }
479
480    /// Test IP lookup from IP literals.
481    pub fn ip_lookup_test<E: Executor, R: ConnectionProvider>(mut exec: E, handle: R) {
482        let resolver =
483            AsyncResolver::<R>::new(ResolverConfig::default(), ResolverOpts::default(), handle);
484
485        let response = exec
486            .block_on(resolver.lookup_ip("10.1.0.2"))
487            .expect("failed to run lookup");
488
489        assert_eq!(
490            Some(IpAddr::V4(Ipv4Addr::new(10, 1, 0, 2))),
491            response.iter().next()
492        );
493
494        let response = exec
495            .block_on(resolver.lookup_ip("2606:2800:21f:cb07:6820:80da:af6b:8b2c"))
496            .expect("failed to run lookup");
497
498        assert_eq!(
499            Some(IpAddr::V6(Ipv6Addr::new(
500                0x2606, 0x2800, 0x21f, 0xcb07, 0x6820, 0x80da, 0xaf6b, 0x8b2c,
501            ))),
502            response.iter().next()
503        );
504    }
505
506    /// Test IP lookup from IP literals across threads.
507    pub fn ip_lookup_across_threads_test<E: Executor + Send + 'static, R: ConnectionProvider>(
508        handle: R,
509    ) {
510        // Test ensuring that running the background task on a separate
511        // executor in a separate thread from the futures returned by the
512        // AsyncResolver works correctly.
513        use std::thread;
514        let resolver =
515            AsyncResolver::<R>::new(ResolverConfig::default(), ResolverOpts::default(), handle);
516
517        let resolver_one = resolver.clone();
518        let resolver_two = resolver;
519
520        let test_fn = |resolver: AsyncResolver<R>| {
521            let mut exec = E::new();
522
523            let response = exec
524                .block_on(resolver.lookup_ip("10.1.0.2"))
525                .expect("failed to run lookup");
526
527            assert_eq!(
528                Some(IpAddr::V4(Ipv4Addr::new(10, 1, 0, 2))),
529                response.iter().next()
530            );
531
532            let response = exec
533                .block_on(resolver.lookup_ip("2606:2800:21f:cb07:6820:80da:af6b:8b2c"))
534                .expect("failed to run lookup");
535
536            assert_eq!(
537                Some(IpAddr::V6(Ipv6Addr::new(
538                    0x2606, 0x2800, 0x21f, 0xcb07, 0x6820, 0x80da, 0xaf6b, 0x8b2c,
539                ))),
540                response.iter().next()
541            );
542        };
543
544        let thread_one = thread::spawn(move || {
545            test_fn(resolver_one);
546        });
547
548        let thread_two = thread::spawn(move || {
549            test_fn(resolver_two);
550        });
551
552        thread_one.join().expect("thread_one failed");
553        thread_two.join().expect("thread_two failed");
554    }
555
556    /// Test IP lookup from URLs with DNSSEC validation.
557    #[cfg(feature = "dnssec")]
558    #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
559    pub fn sec_lookup_test<E: Executor + Send + 'static, R: ConnectionProvider>(
560        mut exec: E,
561        handle: R,
562    ) {
563        //env_logger::try_init().ok();
564
565        let resolver = AsyncResolver::new(
566            ResolverConfig::default(),
567            ResolverOpts {
568                validate: true,
569                try_tcp_on_error: true,
570                ..ResolverOpts::default()
571            },
572            handle,
573        );
574
575        let response = exec
576            .block_on(resolver.lookup_ip("www.internetsociety.org."))
577            .expect("failed to run lookup");
578
579        assert_ne!(response.iter().count(), 0);
580    }
581
582    /// Test IP lookup from domains that exist but unsigned with DNSSEC validation.
583    #[allow(deprecated)]
584    #[cfg(feature = "dnssec")]
585    #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
586    pub fn sec_lookup_fails_test<E: Executor + Send + 'static, R: ConnectionProvider>(
587        mut exec: E,
588        handle: R,
589    ) {
590        use crate::error::*;
591        use proto::rr::RecordType;
592        let resolver = AsyncResolver::new(
593            ResolverConfig::default(),
594            ResolverOpts {
595                validate: true,
596                ip_strategy: LookupIpStrategy::Ipv4Only,
597                ..ResolverOpts::default()
598            },
599            handle,
600        );
601
602        // needs to be a domain that exists, but is not signed (eventually this will be)
603        let response = exec.block_on(resolver.lookup_ip("hickory-dns.org."));
604
605        assert!(response.is_err());
606        let error = response.unwrap_err();
607
608        use proto::error::{ProtoError, ProtoErrorKind};
609
610        let error_str = format!("{error}");
611        let name = Name::from_str("hickory-dns.org.").unwrap();
612        let expected_str = format!(
613            "{}",
614            ResolveError::from(ProtoError::from(ProtoErrorKind::RrsigsNotPresent {
615                name,
616                record_type: RecordType::A
617            }))
618        );
619        assert_eq!(error_str, expected_str);
620        if let ResolveErrorKind::Proto(_) = *error.kind() {
621        } else {
622            panic!("wrong error")
623        }
624    }
625
626    /// Test AsyncResolver created from system configuration with IP lookup.
627    #[cfg(feature = "system-config")]
628    #[cfg_attr(docsrs, doc(cfg(feature = "system-config")))]
629    pub fn system_lookup_test<E: Executor + Send + 'static, R: ConnectionProvider>(
630        mut exec: E,
631        handle: R,
632    ) {
633        let resolver =
634            AsyncResolver::<R>::from_system_conf(handle).expect("failed to create resolver");
635
636        let response = exec
637            .block_on(resolver.lookup_ip("www.example.com."))
638            .expect("failed to run lookup");
639
640        assert_ne!(response.iter().count(), 0);
641    }
642
643    /// Test AsyncResolver created from system configuration with host lookups.
644    #[cfg(feature = "system-config")]
645    #[cfg_attr(docsrs, doc(cfg(feature = "system-config")))]
646    pub fn hosts_lookup_test<E: Executor + Send + 'static, R: ConnectionProvider>(
647        mut exec: E,
648        handle: R,
649    ) {
650        let resolver =
651            AsyncResolver::<R>::from_system_conf(handle).expect("failed to create resolver");
652
653        let response = exec
654            .block_on(resolver.lookup_ip("a.com"))
655            .expect("failed to run lookup");
656
657        assert_eq!(response.iter().count(), 1);
658        for address in response.iter() {
659            if address.is_ipv4() {
660                assert_eq!(address, IpAddr::V4(Ipv4Addr::new(10, 1, 0, 104)));
661            } else {
662                panic!("failed to run lookup");
663            }
664        }
665    }
666
667    /// Test fqdn.
668    pub fn fqdn_test<E: Executor + Send + 'static, R: ConnectionProvider>(mut exec: E, handle: R) {
669        let domain = Name::from_str("incorrect.example.com.").unwrap();
670        let search = vec![
671            Name::from_str("bad.example.com.").unwrap(),
672            Name::from_str("wrong.example.com.").unwrap(),
673        ];
674        let name_servers: Vec<NameServerConfig> =
675            ResolverConfig::default().name_servers().to_owned();
676
677        let resolver = AsyncResolver::<R>::new(
678            ResolverConfig::from_parts(Some(domain), search, name_servers),
679            ResolverOpts {
680                ip_strategy: LookupIpStrategy::Ipv4Only,
681                ..ResolverOpts::default()
682            },
683            handle,
684        );
685
686        let response = exec
687            .block_on(resolver.lookup_ip("www.example.com."))
688            .expect("failed to run lookup");
689
690        assert_ne!(response.iter().count(), 0);
691    }
692
693    /// Test ndots with non-fqdn.
694    pub fn ndots_test<E: Executor + Send + 'static, R: ConnectionProvider>(mut exec: E, handle: R) {
695        let domain = Name::from_str("incorrect.example.com.").unwrap();
696        let search = vec![
697            Name::from_str("bad.example.com.").unwrap(),
698            Name::from_str("wrong.example.com.").unwrap(),
699        ];
700        let name_servers: Vec<NameServerConfig> =
701            ResolverConfig::default().name_servers().to_owned();
702
703        let resolver = AsyncResolver::<R>::new(
704            ResolverConfig::from_parts(Some(domain), search, name_servers),
705            ResolverOpts {
706                // our name does have 2, the default should be fine, let's just narrow the test criteria a bit.
707                ndots: 2,
708                ip_strategy: LookupIpStrategy::Ipv4Only,
709                ..ResolverOpts::default()
710            },
711            handle,
712        );
713
714        // notice this is not a FQDN, no trailing dot.
715        let response = exec
716            .block_on(resolver.lookup_ip("www.example.com"))
717            .expect("failed to run lookup");
718
719        assert_ne!(response.iter().count(), 0);
720    }
721
722    /// Test large ndots with non-fqdn.
723    pub fn large_ndots_test<E: Executor + Send + 'static, R: ConnectionProvider>(
724        mut exec: E,
725        handle: R,
726    ) {
727        let domain = Name::from_str("incorrect.example.com.").unwrap();
728        let search = vec![
729            Name::from_str("bad.example.com.").unwrap(),
730            Name::from_str("wrong.example.com.").unwrap(),
731        ];
732        let name_servers: Vec<NameServerConfig> =
733            ResolverConfig::default().name_servers().to_owned();
734
735        let resolver = AsyncResolver::<R>::new(
736            ResolverConfig::from_parts(Some(domain), search, name_servers),
737            ResolverOpts {
738                // matches kubernetes default
739                ndots: 5,
740                ip_strategy: LookupIpStrategy::Ipv4Only,
741                ..ResolverOpts::default()
742            },
743            handle,
744        );
745
746        // notice this is not a FQDN, no trailing dot.
747        let response = exec
748            .block_on(resolver.lookup_ip("www.example.com"))
749            .expect("failed to run lookup");
750
751        assert_ne!(response.iter().count(), 0);
752    }
753
754    /// Test domain search.
755    pub fn domain_search_test<E: Executor + Send + 'static, R: ConnectionProvider>(
756        mut exec: E,
757        handle: R,
758    ) {
759        //env_logger::try_init().ok();
760
761        // domain is good now, should be combined with the name to form www.example.com
762        let domain = Name::from_str("example.com.").unwrap();
763        let search = vec![
764            Name::from_str("bad.example.com.").unwrap(),
765            Name::from_str("wrong.example.com.").unwrap(),
766        ];
767        let name_servers: Vec<NameServerConfig> =
768            ResolverConfig::default().name_servers().to_owned();
769
770        let resolver = AsyncResolver::<R>::new(
771            ResolverConfig::from_parts(Some(domain), search, name_servers),
772            ResolverOpts {
773                ip_strategy: LookupIpStrategy::Ipv4Only,
774                ..ResolverOpts::default()
775            },
776            handle,
777        );
778
779        // notice no dots, should not trigger ndots rule
780        let response = exec
781            .block_on(resolver.lookup_ip("www"))
782            .expect("failed to run lookup");
783
784        assert_ne!(response.iter().count(), 0);
785    }
786
787    /// Test search lists.
788    pub fn search_list_test<E: Executor + Send + 'static, R: ConnectionProvider>(
789        mut exec: E,
790        handle: R,
791    ) {
792        let domain = Name::from_str("incorrect.example.com.").unwrap();
793        let search = vec![
794            // let's skip one search domain to test the loop...
795            Name::from_str("bad.example.com.").unwrap(),
796            // this should combine with the search name to form www.example.com
797            Name::from_str("example.com.").unwrap(),
798        ];
799        let name_servers: Vec<NameServerConfig> =
800            ResolverConfig::default().name_servers().to_owned();
801
802        let resolver = AsyncResolver::<R>::new(
803            ResolverConfig::from_parts(Some(domain), search, name_servers),
804            ResolverOpts {
805                ip_strategy: LookupIpStrategy::Ipv4Only,
806                ..ResolverOpts::default()
807            },
808            handle,
809        );
810
811        // notice no dots, should not trigger ndots rule
812        let response = exec
813            .block_on(resolver.lookup_ip("www"))
814            .expect("failed to run lookup");
815
816        assert_ne!(response.iter().count(), 0);
817    }
818
819    /// Test idna.
820    pub fn idna_test<E: Executor + Send + 'static, R: ConnectionProvider>(mut exec: E, handle: R) {
821        let resolver =
822            AsyncResolver::<R>::new(ResolverConfig::default(), ResolverOpts::default(), handle);
823
824        let response = exec
825            .block_on(resolver.lookup_ip("中国.icom.museum."))
826            .expect("failed to run lookup");
827
828        // we just care that the request succeeded, not about the actual content
829        //   it's not certain that the ip won't change.
830        assert!(response.iter().next().is_some());
831    }
832
833    /// Test ipv4 localhost.
834    pub fn localhost_ipv4_test<E: Executor + Send + 'static, R: ConnectionProvider>(
835        mut exec: E,
836        handle: R,
837    ) {
838        let resolver = AsyncResolver::<R>::new(
839            ResolverConfig::default(),
840            ResolverOpts {
841                ip_strategy: LookupIpStrategy::Ipv4thenIpv6,
842                ..ResolverOpts::default()
843            },
844            handle,
845        );
846
847        let response = exec
848            .block_on(resolver.lookup_ip("localhost"))
849            .expect("failed to run lookup");
850
851        let mut iter = response.iter();
852        assert_eq!(
853            iter.next().expect("no A"),
854            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
855        );
856    }
857
858    /// Test ipv6 localhost.
859    pub fn localhost_ipv6_test<E: Executor + Send + 'static, R: ConnectionProvider>(
860        mut exec: E,
861        handle: R,
862    ) {
863        let resolver = AsyncResolver::<R>::new(
864            ResolverConfig::default(),
865            ResolverOpts {
866                ip_strategy: LookupIpStrategy::Ipv6thenIpv4,
867                ..ResolverOpts::default()
868            },
869            handle,
870        );
871
872        let response = exec
873            .block_on(resolver.lookup_ip("localhost"))
874            .expect("failed to run lookup");
875
876        let mut iter = response.iter();
877        assert_eq!(
878            iter.next().expect("no AAAA"),
879            IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1,))
880        );
881    }
882
883    /// Test ipv4 search with large ndots.
884    pub fn search_ipv4_large_ndots_test<E: Executor + Send + 'static, R: ConnectionProvider>(
885        mut exec: E,
886        handle: R,
887    ) {
888        let mut config = ResolverConfig::default();
889        config.add_search(Name::from_str("example.com").unwrap());
890
891        let resolver = AsyncResolver::<R>::new(
892            config,
893            ResolverOpts {
894                ip_strategy: LookupIpStrategy::Ipv4Only,
895                ndots: 5,
896                ..ResolverOpts::default()
897            },
898            handle,
899        );
900
901        let response = exec
902            .block_on(resolver.lookup_ip("198.51.100.35"))
903            .expect("failed to run lookup");
904
905        let mut iter = response.iter();
906        assert_eq!(
907            iter.next().expect("no rdatas"),
908            IpAddr::V4(Ipv4Addr::new(198, 51, 100, 35))
909        );
910    }
911
912    /// Test ipv6 search with large ndots.
913    pub fn search_ipv6_large_ndots_test<E: Executor + Send + 'static, R: ConnectionProvider>(
914        mut exec: E,
915        handle: R,
916    ) {
917        let mut config = ResolverConfig::default();
918        config.add_search(Name::from_str("example.com").unwrap());
919
920        let resolver = AsyncResolver::<R>::new(
921            config,
922            ResolverOpts {
923                ip_strategy: LookupIpStrategy::Ipv4Only,
924                ndots: 5,
925                ..ResolverOpts::default()
926            },
927            handle,
928        );
929
930        let response = exec
931            .block_on(resolver.lookup_ip("2001:db8::c633:6423"))
932            .expect("failed to run lookup");
933
934        let mut iter = response.iter();
935        assert_eq!(
936            iter.next().expect("no rdatas"),
937            IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc633, 0x6423))
938        );
939    }
940
941    /// Test ipv6 name parse fails.
942    pub fn search_ipv6_name_parse_fails_test<
943        E: Executor + Send + 'static,
944        R: ConnectionProvider,
945    >(
946        mut exec: E,
947        handle: R,
948    ) {
949        let mut config = ResolverConfig::default();
950        config.add_search(Name::from_str("example.com").unwrap());
951
952        let resolver = AsyncResolver::<R>::new(
953            config,
954            ResolverOpts {
955                ip_strategy: LookupIpStrategy::Ipv4Only,
956                ndots: 5,
957                ..ResolverOpts::default()
958            },
959            handle,
960        );
961
962        let response = exec
963            .block_on(resolver.lookup_ip("2001:db8::198.51.100.35"))
964            .expect("failed to run lookup");
965
966        let mut iter = response.iter();
967        assert_eq!(
968            iter.next().expect("no rdatas"),
969            IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc633, 0x6423))
970        );
971    }
972}
973#[cfg(test)]
974#[cfg(feature = "tokio-runtime")]
975#[allow(clippy::extra_unused_type_parameters)]
976mod tests {
977    use proto::xfer::DnsRequest;
978    use tokio::runtime::Runtime;
979
980    use crate::config::{ResolverConfig, ResolverOpts};
981    use crate::name_server::GenericConnection;
982
983    use super::*;
984
985    fn is_send_t<T: Send>() -> bool {
986        true
987    }
988
989    fn is_sync_t<T: Sync>() -> bool {
990        true
991    }
992
993    #[test]
994    fn test_send_sync() {
995        assert!(is_send_t::<ResolverConfig>());
996        assert!(is_sync_t::<ResolverConfig>());
997        assert!(is_send_t::<ResolverOpts>());
998        assert!(is_sync_t::<ResolverOpts>());
999
1000        assert!(is_send_t::<AsyncResolver<TokioConnectionProvider>>());
1001        assert!(is_sync_t::<AsyncResolver<TokioConnectionProvider>>());
1002
1003        assert!(is_send_t::<DnsRequest>());
1004        assert!(is_send_t::<LookupIpFuture<GenericConnection, ResolveError>>());
1005        assert!(is_send_t::<LookupFuture<GenericConnection, ResolveError>>());
1006    }
1007
1008    #[test]
1009    fn test_lookup_google() {
1010        use super::testing::lookup_test;
1011        let io_loop = Runtime::new().expect("failed to create tokio runtime");
1012        let handle = TokioConnectionProvider::default();
1013        lookup_test::<Runtime, TokioConnectionProvider>(ResolverConfig::google(), io_loop, handle)
1014    }
1015
1016    #[test]
1017    fn test_lookup_cloudflare() {
1018        use super::testing::lookup_test;
1019        let io_loop = Runtime::new().expect("failed to create tokio runtime");
1020        let handle = TokioConnectionProvider::default();
1021        lookup_test::<Runtime, TokioConnectionProvider>(
1022            ResolverConfig::cloudflare(),
1023            io_loop,
1024            handle,
1025        )
1026    }
1027
1028    #[test]
1029    fn test_lookup_quad9() {
1030        use super::testing::lookup_test;
1031        let io_loop = Runtime::new().expect("failed to create tokio runtime");
1032        let handle = TokioConnectionProvider::default();
1033        lookup_test::<Runtime, TokioConnectionProvider>(ResolverConfig::quad9(), io_loop, handle)
1034    }
1035
1036    #[test]
1037    fn test_ip_lookup() {
1038        use super::testing::ip_lookup_test;
1039        let io_loop = Runtime::new().expect("failed to create tokio runtime");
1040        let handle = TokioConnectionProvider::default();
1041        ip_lookup_test::<Runtime, TokioConnectionProvider>(io_loop, handle)
1042    }
1043
1044    #[test]
1045    fn test_ip_lookup_across_threads() {
1046        use super::testing::ip_lookup_across_threads_test;
1047        let _io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1048        let handle = TokioConnectionProvider::default();
1049        ip_lookup_across_threads_test::<Runtime, TokioConnectionProvider>(handle)
1050    }
1051
1052    #[test]
1053    #[cfg(feature = "dnssec")]
1054    fn test_sec_lookup() {
1055        use super::testing::sec_lookup_test;
1056        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1057        let handle = TokioConnectionProvider::default();
1058        sec_lookup_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1059    }
1060
1061    #[test]
1062    #[cfg(feature = "dnssec")]
1063    fn test_sec_lookup_fails() {
1064        use super::testing::sec_lookup_fails_test;
1065        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1066        let handle = TokioConnectionProvider::default();
1067        sec_lookup_fails_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1068    }
1069
1070    #[test]
1071    #[ignore]
1072    #[cfg(any(unix, target_os = "windows"))]
1073    #[cfg(feature = "system-config")]
1074    fn test_system_lookup() {
1075        use super::testing::system_lookup_test;
1076        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1077        let handle = TokioConnectionProvider::default();
1078        system_lookup_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1079    }
1080
1081    #[test]
1082    #[ignore]
1083    // these appear to not work on CI, test on macos with `10.1.0.104  a.com`
1084    #[cfg(unix)]
1085    #[cfg(feature = "system-config")]
1086    fn test_hosts_lookup() {
1087        use super::testing::hosts_lookup_test;
1088        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1089        let handle = TokioConnectionProvider::default();
1090        hosts_lookup_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1091    }
1092
1093    #[test]
1094    fn test_fqdn() {
1095        use super::testing::fqdn_test;
1096        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1097        let handle = TokioConnectionProvider::default();
1098        fqdn_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1099    }
1100
1101    #[test]
1102    fn test_ndots() {
1103        use super::testing::ndots_test;
1104        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1105        let handle = TokioConnectionProvider::default();
1106        ndots_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1107    }
1108
1109    #[test]
1110    fn test_large_ndots() {
1111        use super::testing::large_ndots_test;
1112        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1113        let handle = TokioConnectionProvider::default();
1114        large_ndots_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1115    }
1116
1117    #[test]
1118    fn test_domain_search() {
1119        use super::testing::domain_search_test;
1120        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1121        let handle = TokioConnectionProvider::default();
1122        domain_search_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1123    }
1124
1125    #[test]
1126    fn test_search_list() {
1127        use super::testing::search_list_test;
1128        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1129        let handle = TokioConnectionProvider::default();
1130        search_list_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1131    }
1132
1133    #[test]
1134    fn test_idna() {
1135        use super::testing::idna_test;
1136        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1137        let handle = TokioConnectionProvider::default();
1138        idna_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1139    }
1140
1141    #[test]
1142    fn test_localhost_ipv4() {
1143        use super::testing::localhost_ipv4_test;
1144        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1145        let handle = TokioConnectionProvider::default();
1146        localhost_ipv4_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1147    }
1148
1149    #[test]
1150    fn test_localhost_ipv6() {
1151        use super::testing::localhost_ipv6_test;
1152        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1153        let handle = TokioConnectionProvider::default();
1154        localhost_ipv6_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1155    }
1156
1157    #[test]
1158    fn test_search_ipv4_large_ndots() {
1159        use super::testing::search_ipv4_large_ndots_test;
1160        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1161        let handle = TokioConnectionProvider::default();
1162        search_ipv4_large_ndots_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1163    }
1164
1165    #[test]
1166    fn test_search_ipv6_large_ndots() {
1167        use super::testing::search_ipv6_large_ndots_test;
1168        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1169        let handle = TokioConnectionProvider::default();
1170        search_ipv6_large_ndots_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1171    }
1172
1173    #[test]
1174    fn test_search_ipv6_name_parse_fails() {
1175        use super::testing::search_ipv6_name_parse_fails_test;
1176        let io_loop = Runtime::new().expect("failed to create tokio runtime io_loop");
1177        let handle = TokioConnectionProvider::default();
1178        search_ipv6_name_parse_fails_test::<Runtime, TokioConnectionProvider>(io_loop, handle);
1179    }
1180
1181    #[test]
1182    fn test_build_names_onion() {
1183        let handle = TokioConnectionProvider::default();
1184        let mut config = ResolverConfig::default();
1185        config.add_search(Name::from_ascii("example.com.").unwrap());
1186        let resolver =
1187            AsyncResolver::<TokioConnectionProvider>::new(config, ResolverOpts::default(), handle);
1188        let tor_address = [
1189            Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion")
1190                .unwrap(),
1191            Name::from_ascii("www.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion")
1192                .unwrap(), // subdomain are allowed too
1193        ];
1194        let not_tor_address = [
1195            Name::from_ascii("onion").unwrap(),
1196            Name::from_ascii("www.onion").unwrap(),
1197            Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.www.onion")
1198                .unwrap(), // www before key
1199            Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion.to")
1200                .unwrap(), // Tor2web
1201        ];
1202        for name in &tor_address {
1203            assert_eq!(resolver.build_names(name.clone()).len(), 1);
1204        }
1205        for name in &not_tor_address {
1206            assert_eq!(resolver.build_names(name.clone()).len(), 2);
1207        }
1208    }
1209}