trippy_dns/
lazy_resolver.rs

1use crate::config::Config;
2use crate::resolver::{DnsEntry, ResolvedIpAddrs, Resolver, Result};
3use std::fmt::{Display, Formatter};
4use std::net::IpAddr;
5use std::rc::Rc;
6
7/// How DNS queries will be resolved.
8#[derive(Debug, Copy, Clone, Eq, PartialEq)]
9pub enum ResolveMethod {
10    /// Resolve using the OS resolver.
11    System,
12    /// Resolve using the `/etc/resolv.conf` DNS configuration.
13    Resolv,
14    /// Resolve using the Google `8.8.8.8` DNS service.
15    Google,
16    /// Resolve using the Cloudflare `1.1.1.1` DNS service.
17    Cloudflare,
18}
19
20/// How to resolve IP addresses.
21#[derive(Debug, Copy, Clone, Eq, PartialEq)]
22pub enum IpAddrFamily {
23    /// Lookup IPv4 only.
24    Ipv4Only,
25    /// Lookup IPv6 only.
26    Ipv6Only,
27    /// Lookup IPv6 with a fallback to IPv4.
28    Ipv6thenIpv4,
29    /// Lookup IPv4 with a fallback to IPv6.
30    Ipv4thenIpv6,
31    /// Use the first IP address returned by the OS resolver when using `ResolveMethod::System`,
32    /// otherwise lookup IPv6 with a fallback to IPv4.
33    System,
34}
35
36impl Display for IpAddrFamily {
37    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38        match self {
39            Self::Ipv4Only => write!(f, "Ipv4Only"),
40            Self::Ipv6Only => write!(f, "Ipv6Only"),
41            Self::Ipv6thenIpv4 => write!(f, "Ipv6thenIpv4"),
42            Self::Ipv4thenIpv6 => write!(f, "Ipv4thenIpv6"),
43            Self::System => write!(f, "System"),
44        }
45    }
46}
47
48/// A cheaply cloneable, non-blocking, caching, forward and reverse DNS resolver.
49#[derive(Clone)]
50pub struct DnsResolver {
51    inner: Rc<inner::DnsResolver>,
52}
53
54impl DnsResolver {
55    /// Create and start a new `DnsResolver`.
56    pub fn start(config: Config) -> std::io::Result<Self> {
57        Ok(Self {
58            inner: Rc::new(inner::DnsResolver::start(config)?),
59        })
60    }
61
62    /// Get the `Config`.
63    #[must_use]
64    pub fn config(&self) -> &Config {
65        self.inner.config()
66    }
67
68    /// Flush the cache of responses.
69    pub fn flush(&self) {
70        self.inner.flush();
71    }
72}
73
74impl Resolver for DnsResolver {
75    fn lookup(&self, hostname: impl AsRef<str>) -> Result<ResolvedIpAddrs> {
76        self.inner.lookup(hostname.as_ref())
77    }
78    fn reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry {
79        self.inner.reverse_lookup(addr.into(), false, false)
80    }
81    fn reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry {
82        self.inner.reverse_lookup(addr.into(), true, false)
83    }
84    fn lazy_reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry {
85        self.inner.reverse_lookup(addr.into(), false, true)
86    }
87    fn lazy_reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry {
88        self.inner.reverse_lookup(addr.into(), true, true)
89    }
90}
91
92/// Private impl of resolver.
93mod inner {
94    use super::{Config, IpAddrFamily, ResolveMethod};
95    use crate::resolver::{AsInfo, DnsEntry, Error, Resolved, ResolvedIpAddrs, Result, Unresolved};
96    use crossbeam::channel::{bounded, Receiver, Sender};
97    use hickory_resolver::config::{LookupIpStrategy, ResolverConfig, ResolverOpts};
98    use hickory_resolver::error::{ResolveError, ResolveErrorKind};
99    use hickory_resolver::proto::error::ProtoError;
100    use hickory_resolver::proto::rr::RecordType;
101    use hickory_resolver::system_conf::read_system_conf;
102    use hickory_resolver::{Name, Resolver};
103    use itertools::{Either, Itertools};
104    use parking_lot::RwLock;
105    use std::collections::HashMap;
106    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
107    use std::str::FromStr;
108    use std::sync::Arc;
109    use std::thread;
110    use std::time::{Duration, SystemTime};
111
112    /// The maximum number of in-flight reverse DNS resolutions that may be
113    const RESOLVER_MAX_QUEUE_SIZE: usize = 100;
114
115    /// The duration wait to enqueue a `DnsEntry::Pending` to the resolver before returning
116    /// `DnsEntry::Timeout`.
117    const RESOLVER_QUEUE_TIMEOUT: Duration = Duration::from_millis(10);
118
119    /// Alias for a cache of reverse DNS lookup entries.
120    type Cache = Arc<RwLock<HashMap<IpAddr, CacheEntry>>>;
121
122    /// A cache entry for a reverse DNS lookup.
123    #[derive(Debug, Clone)]
124    struct CacheEntry {
125        /// The DNS entry to cache.
126        entry: DnsEntry,
127        /// The timestamp of the entry.
128        timestamp: SystemTime,
129    }
130
131    impl CacheEntry {
132        const fn new(entry: DnsEntry, timestamp: SystemTime) -> Self {
133            Self { entry, timestamp }
134        }
135
136        fn set_timestamp(&mut self, timestamp: SystemTime) {
137            self.timestamp = timestamp;
138        }
139    }
140
141    #[derive(Clone)]
142    enum DnsProvider {
143        TrustDns(Arc<Resolver>),
144        DnsLookup,
145    }
146
147    #[derive(Debug, Clone)]
148    struct DnsResolveRequest {
149        addr: IpAddr,
150        with_asinfo: bool,
151    }
152
153    /// Resolver implementation.
154    pub(super) struct DnsResolver {
155        config: Config,
156        provider: DnsProvider,
157        tx: Sender<DnsResolveRequest>,
158        addr_cache: Cache,
159    }
160
161    impl DnsResolver {
162        pub(super) fn start(config: Config) -> std::io::Result<Self> {
163            let (tx, rx) = bounded(RESOLVER_MAX_QUEUE_SIZE);
164            let addr_cache = Arc::new(RwLock::new(HashMap::new()));
165
166            let provider = if matches!(config.resolve_method, ResolveMethod::System) {
167                DnsProvider::DnsLookup
168            } else {
169                let mut options = ResolverOpts::default();
170                #[allow(clippy::match_same_arms)]
171                let ip_strategy = match config.addr_family {
172                    IpAddrFamily::Ipv4Only => LookupIpStrategy::Ipv4Only,
173                    IpAddrFamily::Ipv6Only => LookupIpStrategy::Ipv6Only,
174                    IpAddrFamily::Ipv6thenIpv4 => LookupIpStrategy::Ipv6thenIpv4,
175                    IpAddrFamily::Ipv4thenIpv6 => LookupIpStrategy::Ipv4thenIpv6,
176                    // see issue #1469
177                    IpAddrFamily::System => LookupIpStrategy::Ipv6thenIpv4,
178                };
179                options.timeout = config.timeout;
180                options.ip_strategy = ip_strategy;
181                let res = match config.resolve_method {
182                    ResolveMethod::Resolv => {
183                        let (resolver_cfg, mut options) = read_system_conf()?;
184                        options.timeout = config.timeout;
185                        options.ip_strategy = ip_strategy;
186                        Resolver::new(resolver_cfg, options)
187                    }
188                    ResolveMethod::Google => Resolver::new(ResolverConfig::google(), options),
189                    ResolveMethod::Cloudflare => {
190                        Resolver::new(ResolverConfig::cloudflare(), options)
191                    }
192                    ResolveMethod::System => unreachable!(),
193                }?;
194                let resolver = Arc::new(res);
195                DnsProvider::TrustDns(resolver)
196            };
197
198            // spawn a thread to process the resolve queue
199            {
200                let cache = addr_cache.clone();
201                let provider = provider.clone();
202                thread::spawn(move || resolver_queue_processor(rx, &provider, &cache));
203            }
204            Ok(Self {
205                config,
206                provider,
207                tx,
208                addr_cache,
209            })
210        }
211
212        pub(super) const fn config(&self) -> &Config {
213            &self.config
214        }
215
216        pub(super) fn lookup(&self, hostname: &str) -> Result<ResolvedIpAddrs> {
217            fn partition(all: Vec<IpAddr>) -> (Vec<IpAddr>, Vec<IpAddr>) {
218                all.into_iter().partition_map(|ip| match ip {
219                    IpAddr::V4(_) => Either::Left(ip),
220                    IpAddr::V6(_) => Either::Right(ip),
221                })
222            }
223            match &self.provider {
224                DnsProvider::TrustDns(resolver) => Ok(resolver
225                    .lookup_ip(hostname)
226                    .map_err(|err| Error::LookupFailed(Box::new(err)))?
227                    .iter()
228                    .collect::<Vec<_>>()),
229                DnsProvider::DnsLookup => {
230                    let all = dns_lookup::lookup_host(hostname)
231                        .map_err(|err| Error::LookupFailed(Box::new(err)))?;
232                    Ok(match self.config.addr_family {
233                        IpAddrFamily::Ipv4Only => {
234                            let (ipv4, _) = partition(all);
235                            if ipv4.is_empty() {
236                                vec![]
237                            } else {
238                                ipv4
239                            }
240                        }
241                        IpAddrFamily::Ipv6Only => {
242                            let (_, ipv6) = partition(all);
243                            if ipv6.is_empty() {
244                                vec![]
245                            } else {
246                                ipv6
247                            }
248                        }
249                        IpAddrFamily::Ipv6thenIpv4 => {
250                            let (ipv4, ipv6) = partition(all);
251                            if ipv6.is_empty() {
252                                ipv4
253                            } else {
254                                ipv6
255                            }
256                        }
257                        IpAddrFamily::Ipv4thenIpv6 => {
258                            let (ipv4, ipv6) = partition(all);
259                            if ipv4.is_empty() {
260                                ipv6
261                            } else {
262                                ipv4
263                            }
264                        }
265                        IpAddrFamily::System => all,
266                    })
267                }
268            }
269            .map(ResolvedIpAddrs)
270        }
271
272        pub(super) fn reverse_lookup(
273            &self,
274            addr: IpAddr,
275            with_asinfo: bool,
276            lazy: bool,
277        ) -> DnsEntry {
278            if lazy {
279                self.lazy_reverse_lookup(addr, with_asinfo).entry
280            } else {
281                reverse_lookup(&self.provider, addr, with_asinfo).entry
282            }
283        }
284
285        fn lazy_reverse_lookup(&self, addr: IpAddr, with_asinfo: bool) -> CacheEntry {
286            let mut enqueue = false;
287            let now = SystemTime::now();
288
289            // Check if we have already attempted to resolve this `IpAddr` and return the current
290            // `DnsEntry` if so, otherwise add it in a state of `DnsEntry::Pending`.
291            let mut dns_entry = self
292                .addr_cache
293                .write()
294                .entry(addr)
295                .or_insert_with(|| {
296                    enqueue = true;
297                    CacheEntry::new(DnsEntry::Pending(addr), now)
298                })
299                .clone();
300
301            // If the entry exists but is stale then enqueue it again.  The existing entry will
302            // be returned until it is refreshed but with an updated timestamp to prevent it from
303            // being enqueued multiple times.
304            match &dns_entry.entry {
305                DnsEntry::Resolved(_) | DnsEntry::NotFound(_) | DnsEntry::Failed(_) => {
306                    if now.duration_since(dns_entry.timestamp).unwrap_or_default() > self.config.ttl
307                    {
308                        self.addr_cache
309                            .write()
310                            .get_mut(&addr)
311                            .expect("addr must be in cache")
312                            .set_timestamp(now);
313                        enqueue = true;
314                    }
315                }
316                _ => {}
317            }
318
319            // If the entry exists but has timed out, then set it as `DnsEntry::Pending` and enqueue
320            // it again.
321            if let DnsEntry::Timeout(addr) = dns_entry.entry {
322                *self
323                    .addr_cache
324                    .write()
325                    .get_mut(&addr)
326                    .expect("addr must be in cache") =
327                    CacheEntry::new(DnsEntry::Pending(addr), now);
328                dns_entry = CacheEntry::new(DnsEntry::Pending(addr), now);
329                enqueue = true;
330            }
331
332            // If this is a newly added `DnsEntry` then send it to the channel to be resolved in the
333            // background.  We do this after the above to ensure we aren't holding the
334            // lock on the cache, which is used by the resolver and so would deadlock.
335            if enqueue {
336                if self
337                    .tx
338                    .send_timeout(
339                        DnsResolveRequest { addr, with_asinfo },
340                        RESOLVER_QUEUE_TIMEOUT,
341                    )
342                    .is_ok()
343                {
344                    dns_entry
345                } else {
346                    *self
347                        .addr_cache
348                        .write()
349                        .get_mut(&addr)
350                        .expect("addr must be in cache") =
351                        CacheEntry::new(DnsEntry::Timeout(addr), now);
352                    CacheEntry::new(DnsEntry::Timeout(addr), now)
353                }
354            } else {
355                dns_entry
356            }
357        }
358
359        pub fn flush(&self) {
360            self.addr_cache.write().clear();
361        }
362    }
363
364    /// Process each `IpAddr` from the resolver queue and perform the reverse DNS lookup.
365    ///
366    /// For each `IpAddr`, perform the reverse DNS lookup and update the cache with the result
367    /// (`Resolved`, `NotFound`, `Timeout` or `Failed`) for that addr.
368    fn resolver_queue_processor(
369        rx: Receiver<DnsResolveRequest>,
370        provider: &DnsProvider,
371        cache: &Cache,
372    ) {
373        for DnsResolveRequest { addr, with_asinfo } in rx {
374            let dns_entry = reverse_lookup(provider, addr, with_asinfo);
375            cache.write().insert(addr, dns_entry);
376        }
377    }
378
379    fn reverse_lookup(provider: &DnsProvider, addr: IpAddr, with_asinfo: bool) -> CacheEntry {
380        let now = SystemTime::now();
381        match &provider {
382            DnsProvider::DnsLookup => {
383                // we can't distinguish between a failed lookup or a genuine error, and so we just
384                // assume all failures are `DnsEntry::NotFound`.
385                match dns_lookup::lookup_addr(&addr) {
386                    Ok(dns) => {
387                        CacheEntry::new(DnsEntry::Resolved(Resolved::Normal(addr, vec![dns])), now)
388                    }
389                    Err(_) => CacheEntry::new(DnsEntry::NotFound(Unresolved::Normal(addr)), now),
390                }
391            }
392            DnsProvider::TrustDns(resolver) => match resolver.reverse_lookup(addr) {
393                Ok(name) => {
394                    let hostnames = name
395                        .into_iter()
396                        .map(|mut s| {
397                            s.0.set_fqdn(false);
398                            s
399                        })
400                        .map(|s| s.to_string())
401                        .collect();
402                    if with_asinfo {
403                        let as_info = lookup_asinfo(resolver, addr).unwrap_or_default();
404                        CacheEntry::new(
405                            DnsEntry::Resolved(Resolved::WithAsInfo(addr, hostnames, as_info)),
406                            now,
407                        )
408                    } else {
409                        CacheEntry::new(DnsEntry::Resolved(Resolved::Normal(addr, hostnames)), now)
410                    }
411                }
412                Err(err) => match err.kind() {
413                    ResolveErrorKind::NoRecordsFound { .. } => {
414                        if with_asinfo {
415                            let as_info = lookup_asinfo(resolver, addr).unwrap_or_default();
416                            CacheEntry::new(
417                                DnsEntry::NotFound(Unresolved::WithAsInfo(addr, as_info)),
418                                now,
419                            )
420                        } else {
421                            CacheEntry::new(DnsEntry::NotFound(Unresolved::Normal(addr)), now)
422                        }
423                    }
424                    ResolveErrorKind::Timeout => CacheEntry::new(DnsEntry::Timeout(addr), now),
425                    _ => CacheEntry::new(DnsEntry::Failed(addr), now),
426                },
427            },
428        }
429    }
430
431    /// Lookup up `AsInfo` for an `IpAddr` address.
432    fn lookup_asinfo(resolver: &Arc<Resolver>, addr: IpAddr) -> Result<AsInfo> {
433        let origin_query_txt = match addr {
434            IpAddr::V4(addr) => query_asn_ipv4(resolver, addr)?,
435            IpAddr::V6(addr) => query_asn_ipv6(resolver, addr)?,
436        };
437        let asinfo = parse_origin_query_txt(&origin_query_txt)?;
438        let asn_query_txt = query_asn_name(resolver, &asinfo.asn)?;
439        let as_name = parse_asn_query_txt(&asn_query_txt)?;
440        Ok(AsInfo {
441            asn: asinfo.asn,
442            prefix: asinfo.prefix,
443            cc: asinfo.cc,
444            registry: asinfo.registry,
445            allocated: asinfo.allocated,
446            name: as_name,
447        })
448    }
449
450    /// Perform the `origin` query.
451    fn query_asn_ipv4(resolver: &Arc<Resolver>, addr: Ipv4Addr) -> Result<String> {
452        let query = format!(
453            "{}.origin.asn.cymru.com.",
454            addr.octets().iter().rev().join(".")
455        );
456        let name = Name::from_str(query.as_str()).map_err(proto_error)?;
457        let response = resolver
458            .lookup(name, RecordType::TXT)
459            .map_err(resolve_error)?;
460        let data = response
461            .iter()
462            .next()
463            .ok_or_else(|| Error::QueryAsnOriginFailed)?;
464        let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnOriginFailed)?;
465        Ok(bytes.to_string())
466    }
467
468    /// Perform the `origin` query.
469    fn query_asn_ipv6(resolver: &Arc<Resolver>, addr: Ipv6Addr) -> Result<String> {
470        let query = format!(
471            "{:x}.origin6.asn.cymru.com.",
472            addr.octets()
473                .iter()
474                .rev()
475                .flat_map(|o| [o & 0x0F, (o & 0xF0) >> 4])
476                .format(".")
477        );
478        let name = Name::from_str(query.as_str()).map_err(proto_error)?;
479        let response = resolver
480            .lookup(name, RecordType::TXT)
481            .map_err(resolve_error)?;
482        let data = response
483            .iter()
484            .next()
485            .ok_or_else(|| Error::QueryAsnOriginFailed)?;
486        let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnOriginFailed)?;
487        Ok(bytes.to_string())
488    }
489
490    /// Perform the `asn` query.
491    fn query_asn_name(resolver: &Arc<Resolver>, asn: &str) -> Result<String> {
492        let query = format!("AS{asn}.asn.cymru.com.");
493        let name = Name::from_str(query.as_str()).map_err(proto_error)?;
494        let response = resolver
495            .lookup(name, RecordType::TXT)
496            .map_err(resolve_error)?;
497        let data = response
498            .iter()
499            .next()
500            .ok_or_else(|| Error::QueryAsnFailed)?;
501        let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnFailed)?;
502        Ok(bytes.to_string())
503    }
504
505    /// The `origin` DNS query returns a TXT record in the formal:
506    ///      `asn | prefix | cc | registry | allocated`
507    ///
508    /// For example:
509    ///      `12301 | 81.0.100.0/22 | HU | ripencc | 2001-12-06`
510    ///
511    /// From this we extract all fields.
512    fn parse_origin_query_txt(origin_query_txt: &str) -> Result<AsInfo> {
513        if origin_query_txt.chars().filter(|c| *c == '|').count() != 4 {
514            return Err(Error::ParseOriginQueryFailed(String::from(
515                origin_query_txt,
516            )));
517        }
518        let mut split = origin_query_txt.split('|');
519        let asn = split.next().unwrap_or_default().trim().to_string();
520        let prefix = split.next().unwrap_or_default().trim().to_string();
521        let cc = split.next().unwrap_or_default().trim().to_string();
522        let registry = split.next().unwrap_or_default().trim().to_string();
523        let allocated = split.next().unwrap_or_default().trim().to_string();
524        Ok(AsInfo {
525            asn,
526            prefix,
527            cc,
528            registry,
529            allocated,
530            name: String::default(),
531        })
532    }
533
534    /// The `asn` DNS query returns a TXT record in the formal:
535    ///      `asn | cc | registry | allocated | name`
536    ///
537    /// For example:
538    ///      `12301 | HU | ripencc | 1999-02-25 | INVITECH, HU`
539    ///
540    /// From this we extract the 4th field (name, `INVITECH, HU` in this example)
541    fn parse_asn_query_txt(asn_query_txt: &str) -> Result<String> {
542        if asn_query_txt.chars().filter(|c| *c == '|').count() != 4 {
543            return Err(Error::ParseAsnQueryFailed(String::from(asn_query_txt)));
544        }
545        let mut split = asn_query_txt.split('|');
546        Ok(split.nth(4).unwrap_or_default().trim().to_string())
547    }
548
549    /// Convert a `ResolveError` to an `Error::LookupFailed`.
550    fn resolve_error(err: ResolveError) -> Error {
551        Error::LookupFailed(Box::new(err))
552    }
553
554    /// Convert a `ProtoError` to an `Error::LookupFailed`.
555    fn proto_error(err: ProtoError) -> Error {
556        Error::LookupFailed(Box::new(err))
557    }
558}