Skip to main content

reqwest/dns/
plain.rs

1//! Plain DNS (UDP/TCP) resolution via hickory-resolver
2
3use hickory_resolver::{
4    config::{LookupIpStrategy, NameServerConfig, ResolverConfig},
5    net::runtime::TokioRuntimeProvider,
6    TokioResolver,
7};
8
9use std::net::IpAddr;
10use std::str::FromStr;
11use std::sync::{Arc, Mutex};
12use std::time::Duration;
13
14use super::{Addrs, Name, Resolve, Resolving, SocketAddrs};
15use super::gai::GaiResolver;
16use crate::error::BoxError;
17
18/// A plain DNS (UDP/TCP) resolver backed by hickory-resolver.
19///
20/// Queries the specified DNS server directly using standard UDP/TCP on port 53.
21pub struct PlainDnsResolver {
22    state: Arc<Mutex<Option<Arc<TokioResolver>>>>,
23    bootstrap: Arc<dyn Resolve>,
24    dns_host: String,
25    dns_port: u16,
26}
27
28impl std::fmt::Debug for PlainDnsResolver {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("PlainDnsResolver")
31            .field("dns_host", &self.dns_host)
32            .field("dns_port", &self.dns_port)
33            .finish()
34    }
35}
36
37impl Clone for PlainDnsResolver {
38    fn clone(&self) -> Self {
39        Self {
40            state: self.state.clone(),
41            bootstrap: self.bootstrap.clone(),
42            dns_host: self.dns_host.clone(),
43            dns_port: self.dns_port,
44        }
45    }
46}
47
48impl PlainDnsResolver {
49    /// Create a new plain DNS resolver for a given host.
50    ///
51    /// The host can be an IP address (`"1.1.1.1"`) or a hostname (`"resolver.example.com"`).
52    /// The default port is 53.
53    pub fn new(host: &str) -> Self {
54        Self::new_with_port(host, 53)
55    }
56
57    /// Create a new plain DNS resolver with a custom port.
58    pub fn new_with_port(host: &str, port: u16) -> Self {
59        let bootstrap: Arc<dyn Resolve> = Arc::new(GaiResolver::new());
60        Self {
61            state: Arc::new(Mutex::new(None)),
62            bootstrap,
63            dns_host: host.to_string(),
64            dns_port: port,
65        }
66    }
67
68    async fn get_resolver(&self) -> Result<Arc<TokioResolver>, BoxError> {
69        if let Some(ref resolver) = *self.state.lock().unwrap() {
70            return Ok(resolver.clone());
71        }
72
73        let addrs = self
74            .bootstrap
75            .resolve(Name::from_str(&self.dns_host)?)
76            .await?;
77        let ips: Vec<_> = addrs.map(|a| a.ip()).collect();
78
79        let name_servers: Vec<NameServerConfig> = ips
80            .iter()
81            .map(|&ip| NameServerConfig::udp_and_tcp(ip))
82            .collect();
83        let config = ResolverConfig::from_parts(None, vec![], name_servers);
84
85        let mut builder =
86            TokioResolver::builder_with_config(config, TokioRuntimeProvider::default());
87        let opts = builder.options_mut();
88        opts.timeout = Duration::from_secs(5);
89        opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
90        let resolver = Arc::new(builder.build().expect("failed to build plain DNS resolver"));
91
92        let mut guard = self.state.lock().unwrap();
93        if guard.is_none() {
94            *guard = Some(resolver.clone());
95        }
96        Ok(guard.as_ref().unwrap().clone())
97    }
98}
99
100impl Resolve for PlainDnsResolver {
101    fn resolve(&self, name: Name) -> Resolving {
102        let this = self.clone();
103        Box::pin(async move {
104            let resolver = this.get_resolver().await?;
105            let lookup = resolver.lookup_ip(name.as_str()).await?;
106            let ips: Vec<IpAddr> = lookup.iter().collect();
107            let addrs: Addrs = Box::new(SocketAddrs {
108                iter: ips.into_iter(),
109            });
110            Ok(addrs)
111        })
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn new_default_port() {
121        let resolver = PlainDnsResolver::new("1.1.1.1");
122        assert_eq!(resolver.dns_host, "1.1.1.1");
123        assert_eq!(resolver.dns_port, 53);
124    }
125
126    #[test]
127    fn new_custom_port() {
128        let resolver = PlainDnsResolver::new_with_port("8.8.8.8", 5353);
129        assert_eq!(resolver.dns_host, "8.8.8.8");
130        assert_eq!(resolver.dns_port, 5353);
131    }
132
133    #[test]
134    fn debug_output() {
135        let resolver = PlainDnsResolver::new_with_port("1.1.1.1", 53);
136        let debug = format!("{:?}", resolver);
137        assert!(debug.contains("1.1.1.1"), "{debug}");
138        assert!(debug.contains("53"), "{debug}");
139    }
140}