hyper_hickory/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(clippy::pedantic, missing_docs)]
3#![allow(clippy::module_name_repetitions)]
4#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5
6use std::{
7    future::Future,
8    net::SocketAddr,
9    pin::Pin,
10    sync::Arc,
11    task::{self, Poll},
12};
13
14use hickory_resolver::{
15    error::ResolveError, lookup_ip::LookupIpIntoIter, name_server::ConnectionProvider,
16    AsyncResolver,
17};
18
19use hickory_resolver::{
20    config::{ResolverConfig, ResolverOpts},
21    name_server::TokioConnectionProvider,
22    TokioAsyncResolver,
23};
24use hyper_util::client::legacy::connect::{dns::Name, HttpConnector};
25use tower_service::Service;
26
27/// A hyper resolver using `hickory`'s [`TokioAsyncResolver`].
28pub type TokioHickoryResolver = HickoryResolver<TokioConnectionProvider>;
29
30/// A hyper resolver using `hickory`'s [`AsyncResolver`] and any implementor of [`ConnectionProvider`].
31#[derive(Clone)]
32pub struct HickoryResolver<C: ConnectionProvider> {
33    resolver: Arc<AsyncResolver<C>>,
34}
35
36/// Iterator over DNS lookup results.
37pub struct SocketAddrs {
38    iter: LookupIpIntoIter,
39}
40
41impl Iterator for SocketAddrs {
42    type Item = SocketAddr;
43
44    fn next(&mut self) -> Option<Self::Item> {
45        self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
46    }
47}
48
49/// Get the default resolver options as configured per crate features.
50/// This allows us to enable DNSSEC conditionally.
51fn default_opts() -> ResolverOpts {
52    #[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
53    let mut opts = ResolverOpts::default();
54    #[cfg(not(any(feature = "dnssec-openssl", feature = "dnssec-ring")))]
55    let opts = ResolverOpts::default();
56
57    #[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
58    {
59        opts.validate = true;
60    }
61
62    opts
63}
64
65impl TokioHickoryResolver {
66    /// Create a new [`TokioHickoryResolver`] with the default config options.
67    /// This must be run inside a Tokio runtime context.
68    #[must_use]
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    /// Create a new [`TokioHickoryResolver`] that uses the Google nameservers.
74    /// This must be run inside a Tokio runtime context.
75    #[must_use]
76    pub fn google() -> Self {
77        Self::with_config_and_options(ResolverConfig::google(), default_opts())
78    }
79
80    /// Create a new [`TokioHickoryResolver`] that uses the Cloudflare nameservers.
81    /// This must be run inside a Tokio runtime context.
82    #[must_use]
83    pub fn cloudflare() -> Self {
84        Self::with_config_and_options(ResolverConfig::cloudflare(), default_opts())
85    }
86
87    /// Create a new [`TokioHickoryResolver`] that uses the Cloudflare nameservers.
88    /// This limits the registered connections to just HTTPS lookups.
89    /// This must be run inside a Tokio runtime context.
90    #[cfg(feature = "dns-over-https-rustls")]
91    #[must_use]
92    pub fn cloudflare_https() -> Self {
93        Self::with_config_and_options(ResolverConfig::cloudflare_https(), default_opts())
94    }
95
96    /// Create a new [`TokioHickoryResolver`] that uses the Cloudflare nameservers.
97    /// This limits the registered connections to just TLS lookups.
98    /// This must be run inside a Tokio runtime context.
99    #[cfg(any(
100        feature = "dns-over-rustls",
101        feature = "dns-over-native-tls",
102        feature = "dns-over-openssl"
103    ))]
104    #[must_use]
105    pub fn cloudflare_tls() -> Self {
106        Self::with_config_and_options(ResolverConfig::cloudflare_tls(), default_opts())
107    }
108
109    /// Create a new [`TokioHickoryResolver`] that uses the Quad9 nameservers.
110    /// This must be run inside a Tokio runtime context.
111    #[must_use]
112    pub fn quad9() -> Self {
113        Self::with_config_and_options(ResolverConfig::quad9(), default_opts())
114    }
115
116    /// Create a new [`TokioHickoryResolver`] that uses the Quad9 nameservers.
117    /// This limits the registered connections to just HTTPS lookups.
118    /// This must be run inside a Tokio runtime context.
119    #[cfg(feature = "dns-over-https-rustls")]
120    #[must_use]
121    pub fn quad9_https() -> Self {
122        Self::with_config_and_options(ResolverConfig::quad9_https(), default_opts())
123    }
124
125    /// Create a new [`TokioHickoryResolver`] that uses the Quad9 nameservers.
126    /// This limits the registered connections to just TLS lookups.
127    /// This must be run inside a Tokio runtime context.
128    #[cfg(any(
129        feature = "dns-over-rustls",
130        feature = "dns-over-native-tls",
131        feature = "dns-over-openssl"
132    ))]
133    #[must_use]
134    pub fn quad9_tls() -> Self {
135        Self::with_config_and_options(ResolverConfig::quad9_tls(), default_opts())
136    }
137
138    /// Create a new [`TokioHickoryResolver`] with the resolver configuration
139    /// options specified.
140    /// This must be run inside a Tokio runtime context.
141    #[must_use]
142    pub fn with_config_and_options(config: ResolverConfig, options: ResolverOpts) -> Self {
143        Self::from_async_resolver(TokioAsyncResolver::tokio(config, options))
144    }
145
146    /// Create a new [`TokioHickoryResolver`] with the system configuration.
147    /// This must be run inside a Tokio runtime context.
148    ///
149    /// # Errors
150    ///
151    /// This method returns an error if loading the system configuration fails.
152    #[cfg(feature = "system-config")]
153    pub fn from_system_conf() -> Result<Self, hickory_resolver::error::ResolveError> {
154        Ok(Self::from_async_resolver(
155            TokioAsyncResolver::tokio_from_system_conf()?,
156        ))
157    }
158}
159
160impl Default for TokioHickoryResolver {
161    fn default() -> Self {
162        Self::with_config_and_options(ResolverConfig::default(), default_opts())
163    }
164}
165
166impl<C: ConnectionProvider> HickoryResolver<C> {
167    /// Create a [`HickoryResolver`] from the given [`AsyncResolver`]
168    #[must_use]
169    pub fn from_async_resolver(async_resolver: AsyncResolver<C>) -> Self {
170        let resolver = Arc::new(async_resolver);
171
172        Self { resolver }
173    }
174
175    /// Create a new [`HickoryHttpConnector`] with this resolver.
176    #[must_use]
177    pub fn into_http_connector(self) -> HickoryHttpConnector<C> {
178        HickoryHttpConnector::new_with_resolver(self)
179    }
180}
181
182impl<C: ConnectionProvider> Service<Name> for HickoryResolver<C> {
183    type Response = SocketAddrs;
184    type Error = ResolveError;
185    #[allow(clippy::type_complexity)]
186    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
187
188    fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
189        Poll::Ready(Ok(()))
190    }
191
192    fn call(&mut self, name: Name) -> Self::Future {
193        let resolver = self.resolver.clone();
194
195        Box::pin(async move {
196            let response = resolver.lookup_ip(name.as_str()).await?;
197            let addresses = response.into_iter();
198
199            Ok(SocketAddrs { iter: addresses })
200        })
201    }
202}
203
204/// A [`HttpConnector`] that uses the [`HickoryResolver`].
205pub type HickoryHttpConnector<C> = HttpConnector<HickoryResolver<C>>;
206
207/// A [`HttpConnector`] that uses the [`TokioHickoryResolver`].
208pub type TokioHickoryHttpConnector = HickoryHttpConnector<TokioConnectionProvider>;