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