trust_dns_resolver/
resolver.rs

1// Copyright 2015-2017 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 Resolver
9use std::io;
10use std::net::IpAddr;
11use std::sync::Mutex;
12
13use proto::rr::domain::TryParseIp;
14use proto::rr::IntoName;
15use proto::rr::RecordType;
16use tokio::runtime::{self, Runtime};
17
18use crate::config::{ResolverConfig, ResolverOpts};
19use crate::error::*;
20use crate::lookup;
21use crate::lookup::Lookup;
22use crate::lookup_ip::LookupIp;
23use crate::name_server::TokioConnectionProvider;
24use crate::AsyncResolver;
25
26/// The Resolver is used for performing DNS queries.
27///
28/// For forward (A) lookups, hostname -> IP address, see: `Resolver::lookup_ip`
29///
30/// Special note about resource consumption. The Resolver and all Trust-DNS software is built around the Tokio async-io library. This synchronous Resolver is intended to be a simpler wrapper for of the [`AsyncResolver`]. To allow the `Resolver` to be [`Send`] + [`Sync`], the construction of the `AsyncResolver` is lazy, this means some of the features of the `AsyncResolver`, like performance based resolution via the most efficient `NameServer` will be lost (the lookup cache is shared across invocations of the `Resolver`). If these other features of the Trust-DNS Resolver are desired, please use the tokio based [`AsyncResolver`].
31///
32/// *Note: Threaded/Sync usage*: In multithreaded scenarios, the internal Tokio Runtime will block on an internal Mutex for the tokio Runtime in use. For higher performance, it's recommended to use the [`AsyncResolver`].
33pub struct Resolver {
34    // TODO: Mutex allows this to be Sync, another option would be to instantiate a thread_local, but that has other
35    //   drawbacks. One major issues, is if this Resolver is shared across threads, it will cause all to block on any
36    //   query. A TLS on the other hand would not, at the cost of only allowing a Resolver to be configured once per Thread
37    runtime: Mutex<Runtime>,
38    async_resolver: AsyncResolver<TokioConnectionProvider>,
39}
40
41macro_rules! lookup_fn {
42    ($p:ident, $l:ty) => {
43        /// Performs a lookup for the associated type.
44        ///
45        /// *hint* queries that end with a '.' are fully qualified names and are cheaper lookups
46        ///
47        /// # Arguments
48        ///
49        /// * `query` - a `&str` which parses to a domain name, failure to parse will return an error
50        pub fn $p<N: IntoName>(&self, query: N) -> ResolveResult<$l> {
51            let lookup = self.async_resolver.$p(query);
52            self.runtime.lock()?.block_on(lookup)
53        }
54    };
55    ($p:ident, $l:ty, $t:ty) => {
56        /// Performs a lookup for the associated type.
57        ///
58        /// # Arguments
59        ///
60        /// * `query` - a type which can be converted to `Name` via `From`.
61        pub fn $p(&self, query: $t) -> ResolveResult<$l> {
62            let lookup = self.async_resolver.$p(query);
63            self.runtime.lock()?.block_on(lookup)
64        }
65    };
66}
67
68impl Resolver {
69    /// Constructs a new Resolver with the specified configuration.
70    ///
71    /// # Arguments
72    /// * `config` - configuration for the resolver
73    /// * `options` - resolver options for performing lookups
74    ///
75    /// # Returns
76    ///
77    /// A new `Resolver` or an error if there was an error with the configuration.
78    pub fn new(config: ResolverConfig, options: ResolverOpts) -> io::Result<Self> {
79        let mut builder = runtime::Builder::new_current_thread();
80        builder.enable_all();
81
82        let runtime = builder.build()?;
83        let async_resolver =
84            AsyncResolver::new(config, options, TokioConnectionProvider::default());
85
86        Ok(Self {
87            runtime: Mutex::new(runtime),
88            async_resolver,
89        })
90    }
91
92    /// Constructs a new Resolver with default config and default options.
93    ///
94    /// See [`ResolverConfig::default`] and [`ResolverOpts::default`] for more information.
95    ///
96    /// # Returns
97    ///
98    /// A new `Resolver` or an error if there was an error with the configuration.
99    #[allow(clippy::should_implement_trait)]
100    pub fn default() -> io::Result<Self> {
101        Self::new(ResolverConfig::default(), ResolverOpts::default())
102    }
103
104    /// Constructs a new Resolver with the system configuration.
105    ///
106    /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows.
107    #[cfg(any(unix, target_os = "windows"))]
108    #[cfg(feature = "system-config")]
109    #[cfg_attr(
110        docsrs,
111        doc(cfg(all(feature = "system-config", any(unix, target_os = "windows"))))
112    )]
113    pub fn from_system_conf() -> io::Result<Self> {
114        let (config, options) = super::system_conf::read_system_conf()?;
115        Self::new(config, options)
116    }
117
118    /// Flushes/Removes all entries from the cache
119    pub fn clear_cache(&self) {
120        self.async_resolver.clear_cache();
121    }
122
123    /// Generic lookup for any RecordType
124    ///
125    /// *WARNING* This interface may change in the future, please use [`Self::lookup_ip`] or another variant for more stable interfaces.
126    ///
127    /// # Arguments
128    ///
129    /// * `name` - name of the record to lookup, if name is not a valid domain name, an error will be returned
130    /// * `record_type` - type of record to lookup
131    pub fn lookup<N: IntoName>(&self, name: N, record_type: RecordType) -> ResolveResult<Lookup> {
132        let lookup = self.async_resolver.lookup(name, record_type);
133        self.runtime.lock()?.block_on(lookup)
134    }
135
136    /// Performs a dual-stack DNS lookup for the IP for the given hostname.
137    ///
138    /// 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`.
139    ///
140    /// # Arguments
141    ///
142    /// * `host` - string hostname, if this is an invalid hostname, an error will be returned.
143    pub fn lookup_ip<N: IntoName + TryParseIp>(&self, host: N) -> ResolveResult<LookupIp> {
144        let lookup = self.async_resolver.lookup_ip(host);
145        self.runtime.lock()?.block_on(lookup)
146    }
147
148    lookup_fn!(reverse_lookup, lookup::ReverseLookup, IpAddr);
149    lookup_fn!(ipv4_lookup, lookup::Ipv4Lookup);
150    lookup_fn!(ipv6_lookup, lookup::Ipv6Lookup);
151    lookup_fn!(mx_lookup, lookup::MxLookup);
152    lookup_fn!(ns_lookup, lookup::NsLookup);
153    lookup_fn!(soa_lookup, lookup::SoaLookup);
154    lookup_fn!(srv_lookup, lookup::SrvLookup);
155    lookup_fn!(tlsa_lookup, lookup::TlsaLookup);
156    lookup_fn!(txt_lookup, lookup::TxtLookup);
157}
158
159#[cfg(test)]
160mod tests {
161    #![allow(clippy::dbg_macro, clippy::print_stdout)]
162
163    use std::net::*;
164
165    use super::*;
166
167    fn require_send_sync<S: Send + Sync>() {}
168
169    #[test]
170    fn test_resolver_sendable() {
171        require_send_sync::<Resolver>();
172    }
173
174    #[test]
175    fn test_lookup() {
176        let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
177
178        let response = resolver.lookup_ip("www.example.com.").unwrap();
179        println!("response records: {response:?}");
180
181        assert_eq!(response.iter().count(), 1);
182        for address in response.iter() {
183            if address.is_ipv4() {
184                assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)));
185            } else {
186                assert_eq!(
187                    address,
188                    IpAddr::V6(Ipv6Addr::new(
189                        0x2606, 0x2800, 0x220, 0x1, 0x248, 0x1893, 0x25c8, 0x1946,
190                    ))
191                );
192            }
193        }
194    }
195
196    #[test]
197    #[ignore]
198    #[cfg(any(unix, target_os = "windows"))]
199    #[cfg(feature = "system-config")]
200    fn test_system_lookup() {
201        let resolver = Resolver::from_system_conf().unwrap();
202
203        let response = resolver.lookup_ip("www.example.com.").unwrap();
204        println!("response records: {response:?}");
205
206        assert_eq!(response.iter().count(), 1);
207        for address in response.iter() {
208            if address.is_ipv4() {
209                assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)));
210            } else {
211                assert_eq!(
212                    address,
213                    IpAddr::V6(Ipv6Addr::new(
214                        0x2606, 0x2800, 0x220, 0x1, 0x248, 0x1893, 0x25c8, 0x1946,
215                    ))
216                );
217            }
218        }
219    }
220}