aws_smithy_dns/
hickory.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use std::{
7    io::{Error as IoError, ErrorKind as IoErrorKind},
8    net::IpAddr,
9    time::Duration,
10};
11
12use aws_smithy_runtime_api::client::dns::{DnsFuture, ResolveDns, ResolveDnsError};
13use hickory_resolver::{
14    config::{NameServerConfigGroup, ResolverConfig},
15    name_server::TokioConnectionProvider,
16    Resolver,
17};
18
19/// DNS resolver that uses [hickory_resolver] and caches DNS entries in memory.
20///
21/// This resolver requires a [tokio] runtime to function and isn't available for WASM targets.
22#[non_exhaustive]
23#[derive(Debug, Clone)]
24pub struct HickoryDnsResolver {
25    resolver: Resolver<TokioConnectionProvider>,
26}
27
28impl Default for HickoryDnsResolver {
29    /// Constructs a new Tokio based [ResolveDns] with the system configuration.
30    /// This uses `/etc/resolv.conf` on Unix OSes and registry settings on Windows.
31    fn default() -> Self {
32        Self {
33            resolver: Resolver::builder_tokio().expect("In tokio runtime").build(),
34        }
35    }
36}
37
38impl HickoryDnsResolver {
39    /// Creates a new DNS resolver that caches IP addresses in memory.
40    pub fn builder() -> HickoryDnsResolverBuilder {
41        HickoryDnsResolverBuilder {
42            nameservers: None,
43            timeout: None,
44            attempts: None,
45            cache_size: None,
46            num_concurrent_reqs: None,
47        }
48    }
49
50    /// Flush the cache
51    pub fn clear_cache(&self) {
52        self.resolver.clear_cache();
53    }
54}
55
56impl ResolveDns for HickoryDnsResolver {
57    fn resolve_dns<'a>(&'a self, name: &'a str) -> DnsFuture<'a> {
58        DnsFuture::new(async move {
59            let result = self.resolver.lookup_ip(name).await;
60
61            match result {
62                Ok(ips) => Ok(ips.into_iter().collect()),
63                Err(failure) => Err(ResolveDnsError::new(IoError::new(
64                    IoErrorKind::Other,
65                    failure,
66                ))),
67            }
68        })
69    }
70}
71
72/// Builder for [HickoryDnsResolver]
73pub struct HickoryDnsResolverBuilder {
74    nameservers: Option<Nameservers>,
75    timeout: Option<Duration>,
76    attempts: Option<usize>,
77    cache_size: Option<usize>,
78    num_concurrent_reqs: Option<usize>,
79}
80
81struct Nameservers {
82    ips: Vec<IpAddr>,
83    port: u16,
84}
85
86impl HickoryDnsResolverBuilder {
87    /// Configure upstream nameservers and the port to use for resolution. Defaults to the system
88    /// configuration.
89    pub fn nameservers(mut self, ips: &[IpAddr], port: u16) -> Self {
90        self.nameservers = Some(Nameservers {
91            ips: ips.to_vec(),
92            port,
93        });
94        self
95    }
96
97    /// Specify the timeout for a request. Defaults to 5 seconds.
98    pub fn timeout(mut self, timeout: Duration) -> Self {
99        self.timeout = Some(timeout);
100        self
101    }
102
103    /// Number of retries after lookup failure before giving up. Defaults to 2.
104    pub fn attempts(mut self, attempts: usize) -> Self {
105        self.attempts = Some(attempts);
106        self
107    }
108
109    /// Cache size is in number of records (some records can be large). Defaults to 32.
110    pub fn cache_size(mut self, cache_size: usize) -> Self {
111        self.cache_size = Some(cache_size);
112        self
113    }
114
115    /// Number of concurrent requests per query.
116    ///
117    /// Where more than one nameserver is configured, this configures the resolver
118    /// to send queries to a number of servers in parallel. Defaults to 2. Setting
119    /// to 0 or 1 will execute requests serially.
120    pub fn num_concurrent_reqs(mut self, num_concurrent_reqs: usize) -> Self {
121        self.num_concurrent_reqs = Some(num_concurrent_reqs);
122        self
123    }
124
125    /// Build a [HickoryDnsResolver]
126    pub fn build(self) -> HickoryDnsResolver {
127        let mut builder = if let Some(nameservers) = self.nameservers {
128            let nameserver_config =
129                NameServerConfigGroup::from_ips_clear(&nameservers.ips, nameservers.port, true);
130            let resolver_config = ResolverConfig::from_parts(None, vec![], nameserver_config);
131
132            Resolver::builder_with_config(resolver_config, TokioConnectionProvider::default())
133        } else {
134            Resolver::builder_tokio().expect("Successfully read system config")
135        };
136
137        let opts = builder.options_mut();
138
139        if let Some(timeout) = self.timeout {
140            opts.timeout = timeout;
141        }
142
143        if let Some(attempts) = self.attempts {
144            opts.attempts = attempts;
145        }
146
147        if let Some(cache_size) = self.cache_size {
148            opts.cache_size = cache_size;
149        }
150
151        if let Some(num_concurrent_reqs) = self.num_concurrent_reqs {
152            opts.num_concurrent_reqs = num_concurrent_reqs;
153        }
154
155        HickoryDnsResolver {
156            resolver: builder.build(),
157        }
158    }
159}