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