1use 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#[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#[derive(Clone)]
47pub struct DnsResolver {
48 resolver: TokioResolver,
49}
50
51impl DnsResolver {
52 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 pub async fn resolve(&self, host: &str) -> Option<IpAddr> {
64 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 opts.ip_strategy = LookupIpStrategy::Ipv6thenIpv4;
91 }
92 Self::new(conf, opts)
93 }
94}
95
96#[derive(Clone, Debug, PartialEq, Eq, Hash)]
98pub struct DiscoverKey {
99 pub name: FastStr,
101 pub port: u16,
104}
105
106impl DiscoverKey {
107 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}