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}