qiniu_http_client/client/resolver/
c_ares_impl.rs1#[cfg_attr(feature = "docs", doc(cfg(feature = "c_ares")))]
2pub use c_ares;
3#[cfg_attr(feature = "docs", doc(cfg(feature = "c_ares")))]
4pub use c_ares_resolver;
5
6use super::{super::ResponseError, ResolveOptions, ResolveResult, Resolver};
7use c_ares::{AddressFamily::UNSPEC, Error as CAresError};
8use c_ares_resolver::{BlockingResolver, Error as CAresResolverError, Options as CAresResolverOptions};
9use cfg_if::cfg_if;
10use qiniu_http::ResponseErrorKind as HttpResponseErrorKind;
11use std::{fmt, net::IpAddr, sync::Arc};
12
13#[cfg(feature = "async")]
14use {c_ares_resolver::FutureResolver, futures::future::BoxFuture};
15
16type CAresResolverResult<T> = Result<T, CAresResolverError>;
17
18#[cfg_attr(feature = "docs", doc(cfg(feature = "c_ares")))]
22#[derive(Clone)]
23pub struct CAresResolver(Arc<CAresResolverInner>);
24
25struct CAresResolverInner {
26 resolver: BlockingResolver,
27
28 #[cfg(feature = "async")]
29 future_resolver: FutureResolver,
30}
31
32impl CAresResolver {
33 #[inline]
35 #[cfg(not(feature = "async"))]
36 pub fn new_with_options(options: CAresResolverOptions) -> CAresResolverResult<Self> {
37 Ok(Self(Arc::new(CAresResolverInner {
38 resolver: BlockingResolver::with_options(options)?,
39 })))
40 }
41
42 #[inline]
44 #[cfg(not(feature = "async"))]
45 pub fn new_with_resolver(resolver: BlockingResolver) -> Self {
46 Self(Arc::new(CAresResolverInner { resolver }))
47 }
48
49 #[inline]
51 #[cfg(feature = "async")]
52 pub fn new_with_options(
53 sync_options: CAresResolverOptions,
54 async_options: CAresResolverOptions,
55 ) -> CAresResolverResult<Self> {
56 Ok(Self(Arc::new(CAresResolverInner {
57 resolver: BlockingResolver::with_options(sync_options)?,
58 future_resolver: FutureResolver::with_options(async_options)?,
59 })))
60 }
61
62 #[inline]
64 #[cfg(feature = "async")]
65 pub fn new_with_resolvers(resolver: BlockingResolver, future_resolver: FutureResolver) -> Self {
66 Self(Arc::new(CAresResolverInner {
67 resolver,
68 future_resolver,
69 }))
70 }
71
72 #[inline]
74 pub fn new() -> CAresResolverResult<Self> {
75 cfg_if! {
76 if #[cfg(feature = "async")] {
77 Self::new_with_options(CAresResolverOptions::new(), CAresResolverOptions::new())
78 } else {
79 Self::new_with_options(CAresResolverOptions::new())
80 }
81 }
82 }
83}
84
85impl fmt::Debug for CAresResolver {
86 #[inline]
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.debug_struct("CAresResolver").finish()
89 }
90}
91
92impl Resolver for CAresResolver {
93 fn resolve(&self, domain: &str, opts: ResolveOptions) -> ResolveResult {
94 self.0
95 .resolver
96 .get_host_by_name(domain, UNSPEC)
97 .map(convert_resolver_hosts_to_ip_addrs)
98 .or_else(convert_no_data)
99 .map_err(|err| convert_c_ares_error_to_response_error(err, opts))
100 .map(|answers| answers.into())
101 }
102
103 #[cfg(feature = "async")]
104 #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
105 fn async_resolve<'a>(&'a self, domain: &'a str, opts: ResolveOptions<'a>) -> BoxFuture<'a, ResolveResult> {
106 Box::pin(async move {
107 self.0
108 .future_resolver
109 .get_host_by_name(domain, UNSPEC)
110 .await
111 .map(convert_resolver_hosts_to_ip_addrs)
112 .or_else(convert_no_data)
113 .map_err(|err| convert_c_ares_error_to_response_error(err, opts))
114 .map(|answers| answers.into())
115 })
116 }
117}
118
119fn convert_no_data(err: CAresError) -> Result<Box<[IpAddr]>, CAresError> {
120 if err == CAresError::ENODATA {
121 Ok(Default::default())
122 } else {
123 Err(err)
124 }
125}
126
127use c_ares_resolver::HostResults as CAresResolverHostResults;
128
129fn convert_resolver_hosts_to_ip_addrs(results: CAresResolverHostResults) -> Box<[IpAddr]> {
130 results.addresses.into_boxed_slice()
131}
132
133fn convert_c_ares_error_to_response_error(err: CAresError, opts: ResolveOptions) -> ResponseError {
134 let mut err = ResponseError::new(HttpResponseErrorKind::DnsServerError.into(), err);
135 if let Some(retried) = opts.retried() {
136 err = err.retried(retried);
137 }
138 err
139}
140
141#[cfg(all(test, feature = "async"))]
142mod tests {
143 use super::*;
144 use crate::test_utils::{make_record_set, make_zone, start_mock_dns_server};
145 use anyhow::{anyhow, Result as AnyResult};
146 use futures::future::abortable;
147 use std::{
148 collections::{HashMap, HashSet},
149 net::{IpAddr, Ipv4Addr, SocketAddr},
150 };
151 use tokio::{net::UdpSocket, task::spawn, task::spawn_blocking};
152 use trust_dns_server::{
153 authority::Catalog,
154 proto::rr::{Name, RData, RecordType},
155 ServerFuture,
156 };
157
158 const DEFAULT_TTL: u32 = 3600;
159
160 #[tokio::test]
161 async fn test_c_ares_resolver() -> AnyResult<()> {
162 let (dns_server_addr, dns_server) = mock_dns_server().await?;
163 let (dns_server, abort_handle) = abortable(async move { dns_server.block_until_done().await });
164 let join_handle = spawn(dns_server);
165
166 spawn_blocking(move || -> AnyResult<()> {
167 let callback_resolver = BlockingResolver::new()?;
168 callback_resolver.set_servers(&[&dns_server_addr.to_string()])?;
169 let future_resolver = FutureResolver::new()?;
170 let resolver = CAresResolver::new_with_resolvers(callback_resolver, future_resolver);
171 {
172 let ips = resolver.resolve("dns.alidns.com", Default::default())?;
173 assert_eq!(
174 make_set(ips.ip_addrs()),
175 make_set([
176 IpAddr::V4(Ipv4Addr::new(2, 3, 4, 5)),
177 IpAddr::V4(Ipv4Addr::new(3, 4, 5, 6)),
178 ])
179 );
180 }
181
182 {
183 let ips = resolver.resolve("alidns.com", Default::default())?;
184 assert_eq!(
185 make_set(ips.ip_addrs()),
186 make_set([IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),])
187 );
188 }
189
190 Ok(())
191 })
192 .await??;
193 abort_handle.abort();
194 let _ = join_handle.await?;
195 Ok(())
196 }
197
198 #[tokio::test]
199 async fn test_async_c_ares_resolver() -> AnyResult<()> {
200 let (dns_server_addr, dns_server) = mock_dns_server().await?;
201 let (dns_server, abort_handle) = abortable(async move { dns_server.block_until_done().await });
202 let join_handle = spawn(dns_server);
203
204 let callback_resolver = BlockingResolver::new()?;
205 let future_resolver = FutureResolver::new()?;
206 future_resolver.set_servers(&[&dns_server_addr.to_string()])?;
207 let resolver = CAresResolver::new_with_resolvers(callback_resolver, future_resolver);
208 {
209 let ips = resolver.async_resolve("dns.alidns.com", Default::default()).await?;
210 assert_eq!(
211 make_set(ips.ip_addrs()),
212 make_set([
213 IpAddr::V4(Ipv4Addr::new(2, 3, 4, 5)),
214 IpAddr::V4(Ipv4Addr::new(3, 4, 5, 6)),
215 ])
216 );
217 }
218
219 {
220 let ips = resolver.async_resolve("alidns.com", Default::default()).await?;
221 assert_eq!(
222 make_set(ips.ip_addrs()),
223 make_set([IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),])
224 );
225 }
226
227 abort_handle.abort();
228 let _ = join_handle.await?;
229 Ok(())
230 }
231
232 fn make_set(ips: impl AsRef<[IpAddr]>) -> HashSet<IpAddr> {
233 HashSet::from_iter(ips.as_ref().iter().copied())
234 }
235
236 async fn mock_dns_server() -> AnyResult<(SocketAddr, ServerFuture<Catalog>)> {
237 let root_name = Name::from_str_relaxed("alidns.com")?;
238 let root_record_set = make_record_set(
239 root_name.to_owned(),
240 RecordType::A,
241 DEFAULT_TTL,
242 [(root_name.to_owned(), DEFAULT_TTL, RData::A(Ipv4Addr::new(1, 2, 3, 4)))],
243 );
244 let sub_name = Name::from_str_relaxed("dns.alidns.com.")?;
245 let sub_record_set = make_record_set(
246 sub_name.to_owned(),
247 RecordType::A,
248 DEFAULT_TTL,
249 [
250 (sub_name.to_owned(), DEFAULT_TTL, RData::A(Ipv4Addr::new(2, 3, 4, 5))),
251 (sub_name.to_owned(), DEFAULT_TTL, RData::A(Ipv4Addr::new(3, 4, 5, 6))),
252 ],
253 );
254
255 let zone = make_zone(
256 root_name.to_owned(),
257 [
258 (root_name.to_owned(), RecordType::A, root_record_set),
259 (sub_name.to_owned(), RecordType::A, sub_record_set),
260 ],
261 )
262 .map_err(|err| anyhow!(err))?;
263 let udp_socket = UdpSocket::bind("127.0.0.1:0").await?;
264 let socket_addr = udp_socket.local_addr()?;
265 let mut zones = HashMap::with_capacity(2);
266 zones.insert(root_name, zone);
267 Ok((socket_addr, start_mock_dns_server(udp_socket, zones)))
268 }
269}