hyper_trust_dns/
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 hyper::{
15    client::{connect::dns::Name, HttpConnector},
16    service::Service,
17};
18use trust_dns_resolver::{
19    config::{ResolverConfig, ResolverOpts},
20    error::ResolveError,
21    lookup_ip::LookupIpIntoIter,
22    TokioAsyncResolver,
23};
24
25/// A hyper resolver using `trust-dns`'s [`TokioAsyncResolver`].
26#[derive(Clone)]
27pub struct TrustDnsResolver {
28    resolver: Arc<TokioAsyncResolver>,
29}
30
31/// Iterator over DNS lookup results.
32pub struct SocketAddrs {
33    iter: LookupIpIntoIter,
34}
35
36impl Iterator for SocketAddrs {
37    type Item = SocketAddr;
38
39    fn next(&mut self) -> Option<Self::Item> {
40        self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
41    }
42}
43
44/// Get the default resolver options as configured per crate features.
45/// This allows us to enable DNSSEC conditionally.
46fn default_opts() -> ResolverOpts {
47    #[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
48    let mut opts = ResolverOpts::default();
49    #[cfg(not(any(feature = "dnssec-openssl", feature = "dnssec-ring")))]
50    let opts = ResolverOpts::default();
51
52    #[cfg(any(feature = "dnssec-openssl", feature = "dnssec-ring"))]
53    {
54        opts.validate = true;
55    }
56
57    opts
58}
59
60impl TrustDnsResolver {
61    /// Create a new [`TrustDnsResolver`] with the default config options.
62    /// This must be run inside a Tokio runtime context.
63    #[must_use]
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    /// Create a new [`TrustDnsResolver`] that uses the Google nameservers.
69    /// This must be run inside a Tokio runtime context.
70    #[must_use]
71    pub fn google() -> Self {
72        Self::with_config_and_options(ResolverConfig::google(), default_opts())
73    }
74
75    /// Create a new [`TrustDnsResolver`] that uses the Cloudflare nameservers.
76    /// This must be run inside a Tokio runtime context.
77    #[must_use]
78    pub fn cloudflare() -> Self {
79        Self::with_config_and_options(ResolverConfig::cloudflare(), default_opts())
80    }
81
82    /// Create a new [`TrustDnsResolver`] that uses the Cloudflare nameservers.
83    /// This limits the registered connections to just HTTPS lookups.
84    /// This must be run inside a Tokio runtime context.
85    #[cfg(feature = "dns-over-https-rustls")]
86    #[must_use]
87    pub fn cloudflare_https() -> Self {
88        Self::with_config_and_options(ResolverConfig::cloudflare_https(), default_opts())
89    }
90
91    /// Create a new [`TrustDnsResolver`] that uses the Cloudflare nameservers.
92    /// This limits the registered connections to just TLS lookups.
93    /// This must be run inside a Tokio runtime context.
94    #[cfg(any(
95        feature = "dns-over-rustls",
96        feature = "dns-over-native-tls",
97        feature = "dns-over-openssl"
98    ))]
99    #[must_use]
100    pub fn cloudflare_tls() -> Self {
101        Self::with_config_and_options(ResolverConfig::cloudflare_tls(), default_opts())
102    }
103
104    /// Create a new [`TrustDnsResolver`] that uses the Quad9 nameservers.
105    /// This must be run inside a Tokio runtime context.
106    #[must_use]
107    pub fn quad9() -> Self {
108        Self::with_config_and_options(ResolverConfig::quad9(), default_opts())
109    }
110
111    /// Create a new [`TrustDnsResolver`] that uses the Quad9 nameservers.
112    /// This limits the registered connections to just HTTPS lookups.
113    /// This must be run inside a Tokio runtime context.
114    #[cfg(feature = "dns-over-https-rustls")]
115    #[must_use]
116    pub fn quad9_https() -> Self {
117        Self::with_config_and_options(ResolverConfig::quad9_https(), default_opts())
118    }
119
120    /// Create a new [`TrustDnsResolver`] that uses the Quad9 nameservers.
121    /// This limits the registered connections to just TLS lookups.
122    /// This must be run inside a Tokio runtime context.
123    #[cfg(any(
124        feature = "dns-over-rustls",
125        feature = "dns-over-native-tls",
126        feature = "dns-over-openssl"
127    ))]
128    #[must_use]
129    pub fn quad9_tls() -> Self {
130        Self::with_config_and_options(ResolverConfig::quad9_tls(), default_opts())
131    }
132
133    /// Create a new [`TrustDnsResolver`] with the resolver configuration
134    /// options specified.
135    /// This must be run inside a Tokio runtime context.
136    #[allow(clippy::missing_panics_doc)]
137    #[must_use]
138    pub fn with_config_and_options(config: ResolverConfig, options: ResolverOpts) -> Self {
139        // This unwrap is safe because internally, there is nothing to be unwrapped
140        // TokioAsyncResolver::new cannot return Err
141        let resolver = Arc::new(TokioAsyncResolver::tokio(config, options).unwrap());
142
143        Self { resolver }
144    }
145
146    /// Create a new [`TrustDnsResolver`] with the system configuration.
147    /// This must be run inside a Tokio runtime context.
148    #[cfg(feature = "system-config")]
149    #[allow(clippy::missing_panics_doc)]
150    #[must_use]
151    pub fn from_system_conf() -> Self {
152        // This unwrap is safe because internally, there is nothing to be unwrapped
153        // TokioAsyncResolver::new cannot return Err
154        let resolver = Arc::new(TokioAsyncResolver::tokio_from_system_conf().unwrap());
155
156        Self { resolver }
157    }
158
159    /// Create a new [`TrustDnsHttpConnector`] with this resolver.
160    #[must_use]
161    pub fn into_http_connector(self) -> TrustDnsHttpConnector {
162        TrustDnsHttpConnector::new_with_resolver(self)
163    }
164
165    /// Create a new [`NativeTlsHttpsConnector`].
166    #[cfg(feature = "native-tls")]
167    #[must_use]
168    pub fn into_native_tls_https_connector(self) -> NativeTlsHttpsConnector {
169        let mut http_connector = self.into_http_connector();
170        http_connector.enforce_http(false);
171
172        let mut native_https_connector =
173            NativeTlsHttpsConnector::new_with_connector(http_connector);
174
175        #[cfg(feature = "https-only")]
176        native_https_connector.https_only(true);
177
178        #[cfg(not(feature = "https-only"))]
179        native_https_connector.https_only(false);
180
181        native_https_connector
182    }
183
184    /// Create a new [`RustlsHttpsConnector`] using the OS root store.
185    #[cfg(feature = "rustls-native")]
186    #[must_use]
187    pub fn into_rustls_native_https_connector(self) -> RustlsHttpsConnector {
188        let mut http_connector = self.into_http_connector();
189        http_connector.enforce_http(false);
190
191        let builder = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots();
192
193        #[cfg(feature = "https-only")]
194        let builder = builder.https_only();
195
196        #[cfg(not(feature = "https-only"))]
197        let builder = builder.https_or_http();
198
199        #[cfg(feature = "rustls-http1")]
200        let builder = builder.enable_http1();
201
202        #[cfg(feature = "rustls-http2")]
203        let builder = builder.enable_http2();
204
205        builder.wrap_connector(http_connector)
206    }
207
208    /// Create a new [`RustlsHttpsConnector`] using the `webpki_roots`.
209    #[cfg(feature = "rustls-webpki")]
210    #[must_use]
211    pub fn into_rustls_webpki_https_connector(self) -> RustlsHttpsConnector {
212        let mut http_connector = self.into_http_connector();
213        http_connector.enforce_http(false);
214
215        let builder = hyper_rustls::HttpsConnectorBuilder::new().with_webpki_roots();
216
217        #[cfg(feature = "https-only")]
218        let builder = builder.https_only();
219
220        #[cfg(not(feature = "https-only"))]
221        let builder = builder.https_or_http();
222
223        #[cfg(feature = "rustls-http1")]
224        let builder = builder.enable_http1();
225
226        #[cfg(feature = "rustls-http2")]
227        let builder = builder.enable_http2();
228
229        builder.wrap_connector(http_connector)
230    }
231}
232
233impl Default for TrustDnsResolver {
234    fn default() -> Self {
235        Self::with_config_and_options(ResolverConfig::default(), default_opts())
236    }
237}
238
239impl Service<Name> for TrustDnsResolver {
240    type Response = SocketAddrs;
241    type Error = ResolveError;
242    #[allow(clippy::type_complexity)]
243    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
244
245    fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
246        Poll::Ready(Ok(()))
247    }
248
249    fn call(&mut self, name: Name) -> Self::Future {
250        let resolver = self.resolver.clone();
251
252        Box::pin(async move {
253            let response = resolver.lookup_ip(name.as_str()).await?;
254            let addresses = response.into_iter();
255
256            Ok(SocketAddrs { iter: addresses })
257        })
258    }
259}
260
261/// A [`HttpConnector`] that uses the [`TrustDnsResolver`].
262pub type TrustDnsHttpConnector = HttpConnector<TrustDnsResolver>;
263
264/// A [`hyper_tls::HttpsConnector`] that uses a [`TrustDnsHttpConnector`].
265#[cfg(feature = "native-tls")]
266pub type NativeTlsHttpsConnector = hyper_tls::HttpsConnector<TrustDnsHttpConnector>;
267
268/// A [`hyper_rustls::HttpsConnector`] that uses a [`TrustDnsHttpConnector`].
269#[cfg(any(feature = "rustls-native", feature = "rustls-webpki"))]
270pub type RustlsHttpsConnector = hyper_rustls::HttpsConnector<TrustDnsHttpConnector>;