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