qiniu_http_client/client/resolver/
c_ares_impl.rs

1#[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/// [`c-ares`](https://c-ares.org/) 域名解析器
19///
20/// 基于 [`c-ares`](https://c-ares.org/) 库的域名解析接口实现
21#[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    /// 创建 [`c-ares`](https://c-ares.org/) 域名解析器
34    #[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    /// 创建 [`c-ares`](https://c-ares.org/) 域名解析器
43    #[inline]
44    #[cfg(not(feature = "async"))]
45    pub fn new_with_resolver(resolver: BlockingResolver) -> Self {
46        Self(Arc::new(CAresResolverInner { resolver }))
47    }
48
49    /// 创建 [`c-ares`](https://c-ares.org/) 域名解析器
50    #[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    /// 创建 [`c-ares`](https://c-ares.org/) 域名解析器
63    #[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    /// 创建默认的 [`c-ares`](https://c-ares.org/) 域名解析器
73    #[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}