Skip to main content

saluki_io/net/dns/
hyper.rs

1use std::{
2    future::Future,
3    net::{IpAddr, SocketAddr},
4    pin::Pin,
5    sync::Arc,
6    task::{Context, Poll},
7};
8
9use hickory_resolver::{net::NetError, TokioResolver};
10use hyper_util::client::legacy::connect::{dns::Name, HttpConnector};
11use metrics::Counter;
12use saluki_error::{ErrorContext as _, GenericError};
13use tower::Service;
14
15/// An [`HttpConnector`] that uses [`HickoryResolver`].
16pub type HickoryHttpConnector = HttpConnector<HickoryResolver>;
17
18/// A DNS resolver for `hyper` based on `hickory`.
19#[derive(Clone)]
20pub struct HickoryResolver {
21    resolver: Arc<TokioResolver>,
22    lookup_errors: Option<Counter>,
23}
24
25impl HickoryResolver {
26    /// Creates a new [`HickoryResolver`] based on the system's resolver configuration.
27    ///
28    /// # Panics
29    ///
30    /// This will panic if called outside the context of a Tokio runtime.
31    ///
32    /// # Errors
33    ///
34    /// If there is an issue loading the system configuration, an error is returned.
35    pub fn from_system_conf() -> Result<Self, GenericError> {
36        let resolver = TokioResolver::builder_tokio()
37            .error_context("Failed to load the system resolver configuration.")?
38            .build()
39            .error_context("Failed to build the resolver.")?;
40
41        Ok(Self {
42            resolver: Arc::new(resolver),
43            lookup_errors: None,
44        })
45    }
46
47    /// Creates a placeholder [`HickoryResolver`] with no configured nameservers.
48    ///
49    /// This resolver will never successfully resolve any names. It exists solely as a
50    /// placeholder for code paths where DNS resolution is never needed (for example, vsock
51    /// transport), avoiding initialization failures in environments without system DNS
52    /// configuration such as Nitro Enclaves.
53    pub fn noop() -> Self {
54        use hickory_resolver::{config::ResolverConfig, net::runtime::TokioRuntimeProvider};
55        let config = ResolverConfig::from_parts(None, vec![], vec![]);
56        let resolver = TokioResolver::builder_with_config(config, TokioRuntimeProvider::default())
57            .build()
58            .expect("noop resolver with empty config should always succeed");
59        Self {
60            resolver: Arc::new(resolver),
61            lookup_errors: None,
62        }
63    }
64
65    /// Sets a counter that's incremented when DNS lookup fails.
66    pub fn with_lookup_errors_counter(mut self, counter: Counter) -> Self {
67        self.lookup_errors = Some(counter);
68        self
69    }
70
71    /// Consumes `self` and creates a new [`HickoryHttpConnector`] with this resolver.
72    pub fn into_http_connector(self) -> HickoryHttpConnector {
73        HickoryHttpConnector::new_with_resolver(self)
74    }
75}
76
77impl Service<Name> for HickoryResolver {
78    type Response = SocketAddrs;
79    type Error = NetError;
80
81    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
82
83    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
84        Poll::Ready(Ok(()))
85    }
86
87    fn call(&mut self, name: Name) -> Self::Future {
88        let resolver = self.resolver.clone();
89        let lookup_errors = self.lookup_errors.clone();
90
91        Box::pin(async move {
92            let response = match resolver.lookup_ip(name.as_str()).await {
93                Ok(response) => response,
94                Err(error) => {
95                    if let Some(lookup_errors) = lookup_errors {
96                        lookup_errors.increment(1);
97                    }
98                    return Err(error);
99                }
100            };
101            Ok(response.iter().collect())
102        })
103    }
104}
105
106pub struct SocketAddrs(Vec<IpAddr>);
107
108impl Iterator for SocketAddrs {
109    type Item = SocketAddr;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        self.0.pop().map(|ip| SocketAddr::new(ip, 0))
113    }
114}
115
116impl FromIterator<IpAddr> for SocketAddrs {
117    fn from_iter<I: IntoIterator<Item = IpAddr>>(iter: I) -> Self {
118        Self(iter.into_iter().collect())
119    }
120}