qiniu_http_client/client/resolver/
trust_dns.rs

1use super::{super::ResponseError, ResolveOptions, ResolveResult, Resolver};
2use async_std::task::block_on;
3use async_std_resolver::{resolver, resolver_from_system_conf, AsyncStdResolver as AsyncResolver};
4use futures::future::BoxFuture;
5use qiniu_http::ResponseErrorKind as HttpResponseErrorKind;
6use std::fmt;
7pub use trust_dns_resolver;
8use trust_dns_resolver::{
9    config::{ResolverConfig, ResolverOpts},
10    error::ResolveError,
11};
12
13/// [`trust-dns`](https://trust-dns.org/) 域名解析器
14///
15/// 基于 [`trust-dns`](https://trust-dns.org/) 库的域名解析接口实现,由于该接口只有异步实现,即使使用阻塞接口,也会调用异步实现
16#[cfg_attr(feature = "docs", doc(cfg(all(feature = "trust_dns", feature = "async"))))]
17#[derive(Clone)]
18pub struct TrustDnsResolver {
19    #[cfg(feature = "async")]
20    resolver: AsyncResolver,
21}
22
23type TrustDnsResolveResult<T> = Result<T, ResolveError>;
24
25impl TrustDnsResolver {
26    /// 创建 [`trust-dns`](https://trust-dns.org/) 域名解析器
27    #[inline]
28    pub async fn new(config: ResolverConfig, options: ResolverOpts) -> TrustDnsResolveResult<Self> {
29        Ok(Self {
30            resolver: resolver(config, options).await?,
31        })
32    }
33
34    /// 创建默认的 [`trust-dns`](https://trust-dns.org/) 域名解析器
35    #[inline]
36    pub async fn default() -> TrustDnsResolveResult<Self> {
37        Ok(Self {
38            resolver: resolver(Default::default(), Default::default()).await?,
39        })
40    }
41
42    /// 通过系统的 system.conf 文件创建 [`trust-dns`](https://trust-dns.org/) 域名解析器
43    #[inline]
44    pub async fn from_system_conf() -> TrustDnsResolveResult<Self> {
45        Ok(Self {
46            resolver: resolver_from_system_conf().await?,
47        })
48    }
49}
50
51impl Resolver for TrustDnsResolver {
52    fn resolve(&self, domain: &str, opts: ResolveOptions) -> ResolveResult {
53        block_on(async move { self.async_resolve(domain, opts).await })
54    }
55
56    fn async_resolve<'a>(&'a self, domain: &'a str, opts: ResolveOptions<'a>) -> BoxFuture<'a, ResolveResult> {
57        Box::pin(async move {
58            Ok(self
59                .resolver
60                .lookup_ip(domain)
61                .await
62                .map_err(|err| convert_trust_dns_error_to_response_error(err, opts))?
63                .iter()
64                .collect::<Vec<_>>()
65                .into())
66        })
67    }
68}
69
70impl fmt::Debug for TrustDnsResolver {
71    #[inline]
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        f.debug_struct("TrustDnsResolver").finish()
74    }
75}
76
77fn convert_trust_dns_error_to_response_error(err: ResolveError, opts: ResolveOptions) -> ResponseError {
78    let mut err = ResponseError::new(HttpResponseErrorKind::DnsServerError.into(), err);
79    if let Some(retried) = opts.retried() {
80        err = err.retried(retried);
81    }
82    err
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::test_utils::{make_record_set, make_zone, start_mock_dns_server};
89    use anyhow::{anyhow, Result as AnyResult};
90    use futures::future::abortable;
91    use std::{
92        collections::{HashMap, HashSet},
93        net::{IpAddr, Ipv4Addr, SocketAddr},
94    };
95    use tokio::{net::UdpSocket, task::spawn};
96    use trust_dns_resolver::config::{LookupIpStrategy, NameServerConfigGroup};
97    use trust_dns_server::{
98        authority::Catalog,
99        proto::rr::{Name, RData, RecordType},
100        ServerFuture,
101    };
102
103    const DEFAULT_TTL: u32 = 3600;
104
105    #[tokio::test]
106    async fn test_trust_dns_resolver() -> AnyResult<()> {
107        let (dns_server_addr, dns_server) = mock_dns_server().await?;
108        let (dns_server, abort_handle) = abortable(async move { dns_server.block_until_done().await });
109        let join_handle = spawn(dns_server);
110
111        let mut opts = ResolverOpts::default();
112        opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
113        let resolver = TrustDnsResolver::new(
114            ResolverConfig::from_parts(
115                None,
116                vec![],
117                NameServerConfigGroup::from_ips_clear([dns_server_addr.ip()].as_slice(), dns_server_addr.port(), true),
118            ),
119            opts,
120        )
121        .await?;
122
123        {
124            let ips = resolver.async_resolve("dns.alidns.com", Default::default()).await?;
125            assert_eq!(
126                make_set(ips.ip_addrs()),
127                make_set([
128                    IpAddr::V4(Ipv4Addr::new(2, 3, 4, 5)),
129                    IpAddr::V4(Ipv4Addr::new(3, 4, 5, 6)),
130                ])
131            );
132        }
133
134        {
135            let ips = resolver.async_resolve("alidns.com", Default::default()).await?;
136            assert_eq!(
137                make_set(ips.ip_addrs()),
138                make_set([IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),])
139            );
140        }
141
142        {
143            let ips = resolver.async_resolve("www.alidns.com", Default::default()).await?;
144            assert_eq!(
145                make_set(ips.ip_addrs()),
146                make_set([IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),])
147            );
148        }
149
150        abort_handle.abort();
151        let _ = join_handle.await?;
152        Ok(())
153    }
154
155    fn make_set(ips: impl AsRef<[IpAddr]>) -> HashSet<IpAddr> {
156        let mut h = HashSet::new();
157        h.extend(ips.as_ref());
158        h
159    }
160
161    async fn mock_dns_server() -> AnyResult<(SocketAddr, ServerFuture<Catalog>)> {
162        let root_name = Name::from_str_relaxed("alidns.com")?;
163        let root_record_set = make_record_set(
164            root_name.to_owned(),
165            RecordType::A,
166            DEFAULT_TTL,
167            [(root_name.to_owned(), DEFAULT_TTL, RData::A(Ipv4Addr::new(1, 2, 3, 4)))],
168        );
169        let sub_name = Name::from_str_relaxed("dns.alidns.com.")?;
170        let sub_record_set = make_record_set(
171            sub_name.to_owned(),
172            RecordType::A,
173            DEFAULT_TTL,
174            [
175                (sub_name.to_owned(), DEFAULT_TTL, RData::A(Ipv4Addr::new(2, 3, 4, 5))),
176                (sub_name.to_owned(), DEFAULT_TTL, RData::A(Ipv4Addr::new(3, 4, 5, 6))),
177            ],
178        );
179        let other_names = Name::from_str_relaxed("*.alidns.com.")?;
180        let other_record_set = make_record_set(
181            other_names.to_owned(),
182            RecordType::CNAME,
183            DEFAULT_TTL,
184            [(other_names.to_owned(), DEFAULT_TTL, RData::CNAME(root_name.to_owned()))],
185        );
186
187        let zone = make_zone(
188            root_name.to_owned(),
189            [
190                (root_name.to_owned(), RecordType::A, root_record_set),
191                (sub_name.to_owned(), RecordType::A, sub_record_set),
192                (other_names.to_owned(), RecordType::CNAME, other_record_set),
193            ],
194        )
195        .map_err(|err| anyhow!(err))?;
196        let udp_socket = UdpSocket::bind("127.0.0.1:0").await?;
197        let socket_addr = udp_socket.local_addr()?;
198        let mut zones = HashMap::with_capacity(2);
199        zones.insert(root_name, zone);
200        Ok((socket_addr, start_mock_dns_server(udp_socket, zones)))
201    }
202}