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