Skip to main content

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