volo_http/client/
dns.rs

1//! DNS resolver implementation
2//!
3//! This module implements [`DnsResolver`] as a [`Discover`] for client.
4
5use std::{
6    net::{IpAddr, SocketAddr},
7    ops::Deref,
8    sync::Arc,
9};
10
11use async_broadcast::Receiver;
12use faststr::FastStr;
13use hickory_resolver::{
14    Resolver, TokioResolver,
15    config::{LookupIpStrategy, ResolverConfig, ResolverOpts},
16    name_server::TokioConnectionProvider,
17};
18use volo::{
19    context::Endpoint,
20    discovery::{Change, Discover, Instance},
21    loadbalance::error::LoadBalanceError,
22    net::Address,
23};
24
25use crate::error::client::{bad_host_name, no_address};
26
27/// The port for `DnsResolver`, and only used for `DnsResolver`.
28///
29/// When resolving domain name, the response is only an IP address without port, but to access the
30/// destination server, the port is needed.
31///
32/// For setting port to `DnsResolver`, you can insert it into `Endpoint` of `callee` in
33/// `ClientContext`, the resolver will apply it.
34#[derive(Clone, Copy, Debug, Default)]
35pub struct Port(pub u16);
36
37impl Deref for Port {
38    type Target = u16;
39
40    fn deref(&self) -> &Self::Target {
41        &self.0
42    }
43}
44
45/// A service discover implementation for DNS.
46#[derive(Clone)]
47pub struct DnsResolver {
48    resolver: TokioResolver,
49}
50
51impl DnsResolver {
52    /// Build a new `DnsResolver` through `ResolverConfig` and `ResolverOpts`.
53    ///
54    /// For using system config, you can create a new instance by `DnsResolver::default()`.
55    pub fn new(config: ResolverConfig, options: ResolverOpts) -> Self {
56        let mut builder = Resolver::builder_with_config(config, TokioConnectionProvider::default());
57        builder.options_mut().clone_from(&options);
58        let resolver = builder.build();
59        Self { resolver }
60    }
61
62    /// Resolve a host to an IP address.
63    pub async fn resolve(&self, host: &str) -> Option<IpAddr> {
64        // Note that the Resolver will try to parse the host as an IP address first, so we don't
65        // need to parse it manually.
66        self.resolver.lookup_ip(host).await.ok()?.into_iter().next()
67    }
68}
69
70impl Default for DnsResolver {
71    fn default() -> Self {
72        let (conf, mut opts) = hickory_resolver::system_conf::read_system_conf()
73            .expect("[Volo-HTTP] DnsResolver: failed to parse dns config");
74        if conf
75            .name_servers()
76            .first()
77            .expect("[Volo-HTTP] DnsResolver: no nameserver found")
78            .socket_addr
79            .is_ipv6()
80        {
81            // The default `LookupIpStrategy` is always `Ipv4thenIpv6`, it may not work in an IPv6
82            // only environment.
83            //
84            // Here we trust the system configuration and check its first name server.
85            //
86            // If the first nameserver is an IPv4 address, we keep the default configuration.
87            //
88            // If the first nameserver is an IPv6 address, we need to update the policy to prefer
89            // IPv6 addresses.
90            opts.ip_strategy = LookupIpStrategy::Ipv6thenIpv4;
91        }
92        Self::new(conf, opts)
93    }
94}
95
96/// `Key` used to cache for [`Discover`].
97#[derive(Clone, Debug, PartialEq, Eq, Hash)]
98pub struct DiscoverKey {
99    /// Service name for Service Discover, it's domain name for DNS by default.
100    pub name: FastStr,
101    /// Port for the service name, it's unnecessary for Service Discover, but it's important to
102    /// cache as key.
103    pub port: u16,
104}
105
106impl DiscoverKey {
107    /// Get [`DiscoverKey`] from an [`Endpoint`].
108    pub fn from_endpoint(ep: &Endpoint) -> Self {
109        let name = ep.service_name();
110        let port = ep.get::<Port>().cloned().unwrap_or_default().0;
111        Self { name, port }
112    }
113}
114
115impl Discover for DnsResolver {
116    type Key = DiscoverKey;
117    type Error = LoadBalanceError;
118
119    async fn discover<'s>(
120        &'s self,
121        endpoint: &'s Endpoint,
122    ) -> Result<Vec<Arc<Instance>>, Self::Error> {
123        if endpoint.address().is_some() {
124            return Ok(Vec::new());
125        }
126        if endpoint.service_name_ref().is_empty() {
127            tracing::error!("[Volo-HTTP] DnsResolver: no domain name found");
128            return Err(LoadBalanceError::Discover(Box::new(no_address())));
129        }
130        let port = match endpoint.get::<Port>() {
131            Some(port) => port.0,
132            None => {
133                unreachable!();
134            }
135        };
136
137        if let Some(ip) = self.resolve(endpoint.service_name_ref()).await {
138            let address = Address::Ip(SocketAddr::new(ip, port));
139            let instance = Instance {
140                address,
141                weight: 10,
142                tags: Default::default(),
143            };
144            return Ok(vec![Arc::new(instance)]);
145        };
146        tracing::error!("[Volo-HTTP] DnsResolver: no address resolved");
147        Err(LoadBalanceError::Discover(Box::new(bad_host_name(
148            endpoint.service_name(),
149        ))))
150    }
151
152    fn key(&self, endpoint: &Endpoint) -> Self::Key {
153        DiscoverKey::from_endpoint(endpoint)
154    }
155
156    fn watch(&self, _: Option<&[Self::Key]>) -> Option<Receiver<Change<Self::Key>>> {
157        None
158    }
159}
160
161#[cfg(test)]
162mod dns_tests {
163    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
164
165    use crate::client::dns::DnsResolver;
166
167    #[tokio::test]
168    async fn static_resolve() {
169        let resolver = DnsResolver::default();
170
171        assert_eq!(
172            resolver.resolve("127.0.0.1").await,
173            Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
174        );
175        assert_eq!(
176            resolver.resolve("::1").await,
177            Some(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
178        );
179        assert_eq!(resolver.resolve("[::1]").await, None);
180    }
181}