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#[derive(Debug, Copy, Clone, Eq, PartialEq)]
9pub enum ResolveMethod {
10 System,
12 Resolv,
14 Google,
16 Cloudflare,
18}
19
20#[derive(Debug, Copy, Clone, Eq, PartialEq)]
22pub enum IpAddrFamily {
23 Ipv4Only,
25 Ipv6Only,
27 Ipv6thenIpv4,
29 Ipv4thenIpv6,
31}
32
33impl Display for IpAddrFamily {
34 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
35 match self {
36 Self::Ipv4Only => write!(f, "Ipv4Only"),
37 Self::Ipv6Only => write!(f, "Ipv6Only"),
38 Self::Ipv6thenIpv4 => write!(f, "Ipv6thenIpv4"),
39 Self::Ipv4thenIpv6 => write!(f, "Ipv4thenIpv6"),
40 }
41 }
42}
43
44#[derive(Clone)]
46pub struct DnsResolver {
47 inner: Rc<inner::DnsResolver>,
48}
49
50impl DnsResolver {
51 pub fn start(config: Config) -> std::io::Result<Self> {
53 Ok(Self {
54 inner: Rc::new(inner::DnsResolver::start(config)?),
55 })
56 }
57
58 #[must_use]
60 pub fn config(&self) -> &Config {
61 self.inner.config()
62 }
63
64 pub fn flush(&self) {
66 self.inner.flush();
67 }
68}
69
70impl Resolver for DnsResolver {
71 fn lookup(&self, hostname: impl AsRef<str>) -> Result<ResolvedIpAddrs> {
72 self.inner.lookup(hostname.as_ref())
73 }
74 #[must_use]
75 fn reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry {
76 self.inner.reverse_lookup(addr.into(), false, false)
77 }
78 #[must_use]
79 fn reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry {
80 self.inner.reverse_lookup(addr.into(), true, false)
81 }
82 #[must_use]
83 fn lazy_reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry {
84 self.inner.reverse_lookup(addr.into(), false, true)
85 }
86 #[must_use]
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
92mod 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 const RESOLVER_MAX_QUEUE_SIZE: usize = 100;
114
115 const RESOLVER_QUEUE_TIMEOUT: Duration = Duration::from_millis(10);
118
119 type Cache = Arc<RwLock<HashMap<IpAddr, CacheEntry>>>;
121
122 #[derive(Debug, Clone)]
124 struct CacheEntry {
125 entry: DnsEntry,
127 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 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 let ip_strategy = match config.addr_family {
171 IpAddrFamily::Ipv4Only => LookupIpStrategy::Ipv4Only,
172 IpAddrFamily::Ipv6Only => LookupIpStrategy::Ipv6Only,
173 IpAddrFamily::Ipv6thenIpv4 => LookupIpStrategy::Ipv6thenIpv4,
174 IpAddrFamily::Ipv4thenIpv6 => LookupIpStrategy::Ipv4thenIpv6,
175 };
176 options.timeout = config.timeout;
177 options.ip_strategy = ip_strategy;
178 let res = match config.resolve_method {
179 ResolveMethod::Resolv => {
180 let (resolver_cfg, mut options) = read_system_conf()?;
181 options.timeout = config.timeout;
182 options.ip_strategy = ip_strategy;
183 Resolver::new(resolver_cfg, options)
184 }
185 ResolveMethod::Google => Resolver::new(ResolverConfig::google(), options),
186 ResolveMethod::Cloudflare => {
187 Resolver::new(ResolverConfig::cloudflare(), options)
188 }
189 ResolveMethod::System => unreachable!(),
190 }?;
191 let resolver = Arc::new(res);
192 DnsProvider::TrustDns(resolver)
193 };
194
195 {
197 let cache = addr_cache.clone();
198 let provider = provider.clone();
199 thread::spawn(move || resolver_queue_processor(rx, &provider, &cache));
200 }
201 Ok(Self {
202 config,
203 provider,
204 tx,
205 addr_cache,
206 })
207 }
208
209 pub(super) const fn config(&self) -> &Config {
210 &self.config
211 }
212
213 pub(super) fn lookup(&self, hostname: &str) -> Result<ResolvedIpAddrs> {
214 match &self.provider {
215 DnsProvider::TrustDns(resolver) => Ok(resolver
216 .lookup_ip(hostname)
217 .map_err(|err| Error::LookupFailed(Box::new(err)))?
218 .iter()
219 .collect::<Vec<_>>()),
220 DnsProvider::DnsLookup => {
221 let (ipv4, ipv6): (Vec<_>, Vec<_>) = dns_lookup::lookup_host(hostname)
222 .map_err(|err| Error::LookupFailed(Box::new(err)))?
223 .into_iter()
224 .partition_map(|ip| match ip {
225 IpAddr::V4(_) => Either::Left(ip),
226 IpAddr::V6(_) => Either::Right(ip),
227 });
228 Ok(match self.config.addr_family {
229 IpAddrFamily::Ipv4Only => {
230 if ipv4.is_empty() {
231 vec![]
232 } else {
233 ipv4
234 }
235 }
236 IpAddrFamily::Ipv6Only => {
237 if ipv6.is_empty() {
238 vec![]
239 } else {
240 ipv6
241 }
242 }
243 IpAddrFamily::Ipv6thenIpv4 => {
244 if ipv6.is_empty() {
245 ipv4
246 } else {
247 ipv6
248 }
249 }
250 IpAddrFamily::Ipv4thenIpv6 => {
251 if ipv4.is_empty() {
252 ipv6
253 } else {
254 ipv4
255 }
256 }
257 })
258 }
259 }
260 .map(ResolvedIpAddrs)
261 }
262
263 pub(super) fn reverse_lookup(
264 &self,
265 addr: IpAddr,
266 with_asinfo: bool,
267 lazy: bool,
268 ) -> DnsEntry {
269 if lazy {
270 self.lazy_reverse_lookup(addr, with_asinfo).entry
271 } else {
272 reverse_lookup(&self.provider, addr, with_asinfo).entry
273 }
274 }
275
276 fn lazy_reverse_lookup(&self, addr: IpAddr, with_asinfo: bool) -> CacheEntry {
277 let mut enqueue = false;
278 let now = SystemTime::now();
279
280 let mut dns_entry = self
283 .addr_cache
284 .write()
285 .entry(addr)
286 .or_insert_with(|| {
287 enqueue = true;
288 CacheEntry::new(DnsEntry::Pending(addr), now)
289 })
290 .clone();
291
292 match &dns_entry.entry {
296 DnsEntry::Resolved(_) | DnsEntry::NotFound(_) | DnsEntry::Failed(_) => {
297 if now.duration_since(dns_entry.timestamp).unwrap_or_default() > self.config.ttl
298 {
299 self.addr_cache
300 .write()
301 .get_mut(&addr)
302 .expect("addr must be in cache")
303 .set_timestamp(now);
304 enqueue = true;
305 }
306 }
307 _ => {}
308 }
309
310 if let DnsEntry::Timeout(addr) = dns_entry.entry {
313 *self
314 .addr_cache
315 .write()
316 .get_mut(&addr)
317 .expect("addr must be in cache") =
318 CacheEntry::new(DnsEntry::Pending(addr), now);
319 dns_entry = CacheEntry::new(DnsEntry::Pending(addr), now);
320 enqueue = true;
321 }
322
323 if enqueue {
327 if self
328 .tx
329 .send_timeout(
330 DnsResolveRequest { addr, with_asinfo },
331 RESOLVER_QUEUE_TIMEOUT,
332 )
333 .is_ok()
334 {
335 dns_entry
336 } else {
337 *self
338 .addr_cache
339 .write()
340 .get_mut(&addr)
341 .expect("addr must be in cache") =
342 CacheEntry::new(DnsEntry::Timeout(addr), now);
343 CacheEntry::new(DnsEntry::Timeout(addr), now)
344 }
345 } else {
346 dns_entry
347 }
348 }
349
350 pub fn flush(&self) {
351 self.addr_cache.write().clear();
352 }
353 }
354
355 fn resolver_queue_processor(
360 rx: Receiver<DnsResolveRequest>,
361 provider: &DnsProvider,
362 cache: &Cache,
363 ) {
364 for DnsResolveRequest { addr, with_asinfo } in rx {
365 let dns_entry = reverse_lookup(provider, addr, with_asinfo);
366 cache.write().insert(addr, dns_entry);
367 }
368 }
369
370 fn reverse_lookup(provider: &DnsProvider, addr: IpAddr, with_asinfo: bool) -> CacheEntry {
371 let now = SystemTime::now();
372 match &provider {
373 DnsProvider::DnsLookup => {
374 match dns_lookup::lookup_addr(&addr) {
377 Ok(dns) => {
378 CacheEntry::new(DnsEntry::Resolved(Resolved::Normal(addr, vec![dns])), now)
379 }
380 Err(_) => CacheEntry::new(DnsEntry::NotFound(Unresolved::Normal(addr)), now),
381 }
382 }
383 DnsProvider::TrustDns(resolver) => match resolver.reverse_lookup(addr) {
384 Ok(name) => {
385 let hostnames = name
386 .into_iter()
387 .map(|mut s| {
388 s.0.set_fqdn(false);
389 s
390 })
391 .map(|s| s.to_string())
392 .collect();
393 if with_asinfo {
394 let as_info = lookup_asinfo(resolver, addr).unwrap_or_default();
395 CacheEntry::new(
396 DnsEntry::Resolved(Resolved::WithAsInfo(addr, hostnames, as_info)),
397 now,
398 )
399 } else {
400 CacheEntry::new(DnsEntry::Resolved(Resolved::Normal(addr, hostnames)), now)
401 }
402 }
403 Err(err) => match err.kind() {
404 ResolveErrorKind::NoRecordsFound { .. } => {
405 if with_asinfo {
406 let as_info = lookup_asinfo(resolver, addr).unwrap_or_default();
407 CacheEntry::new(
408 DnsEntry::NotFound(Unresolved::WithAsInfo(addr, as_info)),
409 now,
410 )
411 } else {
412 CacheEntry::new(DnsEntry::NotFound(Unresolved::Normal(addr)), now)
413 }
414 }
415 ResolveErrorKind::Timeout => CacheEntry::new(DnsEntry::Timeout(addr), now),
416 _ => CacheEntry::new(DnsEntry::Failed(addr), now),
417 },
418 },
419 }
420 }
421
422 fn lookup_asinfo(resolver: &Arc<Resolver>, addr: IpAddr) -> Result<AsInfo> {
424 let origin_query_txt = match addr {
425 IpAddr::V4(addr) => query_asn_ipv4(resolver, addr)?,
426 IpAddr::V6(addr) => query_asn_ipv6(resolver, addr)?,
427 };
428 let asinfo = parse_origin_query_txt(&origin_query_txt)?;
429 let asn_query_txt = query_asn_name(resolver, &asinfo.asn)?;
430 let as_name = parse_asn_query_txt(&asn_query_txt)?;
431 Ok(AsInfo {
432 asn: asinfo.asn,
433 prefix: asinfo.prefix,
434 cc: asinfo.cc,
435 registry: asinfo.registry,
436 allocated: asinfo.allocated,
437 name: as_name,
438 })
439 }
440
441 fn query_asn_ipv4(resolver: &Arc<Resolver>, addr: Ipv4Addr) -> Result<String> {
443 let query = format!(
444 "{}.origin.asn.cymru.com.",
445 addr.octets().iter().rev().join(".")
446 );
447 let name = Name::from_str(query.as_str()).map_err(proto_error)?;
448 let response = resolver
449 .lookup(name, RecordType::TXT)
450 .map_err(resolve_error)?;
451 let data = response
452 .iter()
453 .next()
454 .ok_or_else(|| Error::QueryAsnOriginFailed)?;
455 let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnOriginFailed)?;
456 Ok(bytes.to_string())
457 }
458
459 fn query_asn_ipv6(resolver: &Arc<Resolver>, addr: Ipv6Addr) -> Result<String> {
461 let query = format!(
462 "{:x}.origin6.asn.cymru.com.",
463 addr.octets()
464 .iter()
465 .rev()
466 .flat_map(|o| [o & 0x0F, (o & 0xF0) >> 4])
467 .format(".")
468 );
469 let name = Name::from_str(query.as_str()).map_err(proto_error)?;
470 let response = resolver
471 .lookup(name, RecordType::TXT)
472 .map_err(resolve_error)?;
473 let data = response
474 .iter()
475 .next()
476 .ok_or_else(|| Error::QueryAsnOriginFailed)?;
477 let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnOriginFailed)?;
478 Ok(bytes.to_string())
479 }
480
481 fn query_asn_name(resolver: &Arc<Resolver>, asn: &str) -> Result<String> {
483 let query = format!("AS{asn}.asn.cymru.com.");
484 let name = Name::from_str(query.as_str()).map_err(proto_error)?;
485 let response = resolver
486 .lookup(name, RecordType::TXT)
487 .map_err(resolve_error)?;
488 let data = response
489 .iter()
490 .next()
491 .ok_or_else(|| Error::QueryAsnFailed)?;
492 let bytes = data.as_txt().ok_or_else(|| Error::QueryAsnFailed)?;
493 Ok(bytes.to_string())
494 }
495
496 fn parse_origin_query_txt(origin_query_txt: &str) -> Result<AsInfo> {
504 if origin_query_txt.chars().filter(|c| *c == '|').count() != 4 {
505 return Err(Error::ParseOriginQueryFailed(String::from(
506 origin_query_txt,
507 )));
508 }
509 let mut split = origin_query_txt.split('|');
510 let asn = split.next().unwrap_or_default().trim().to_string();
511 let prefix = split.next().unwrap_or_default().trim().to_string();
512 let cc = split.next().unwrap_or_default().trim().to_string();
513 let registry = split.next().unwrap_or_default().trim().to_string();
514 let allocated = split.next().unwrap_or_default().trim().to_string();
515 Ok(AsInfo {
516 asn,
517 prefix,
518 cc,
519 registry,
520 allocated,
521 name: String::default(),
522 })
523 }
524
525 fn parse_asn_query_txt(asn_query_txt: &str) -> Result<String> {
533 if asn_query_txt.chars().filter(|c| *c == '|').count() != 4 {
534 return Err(Error::ParseAsnQueryFailed(String::from(asn_query_txt)));
535 }
536 let mut split = asn_query_txt.split('|');
537 Ok(split.nth(4).unwrap_or_default().trim().to_string())
538 }
539
540 fn resolve_error(err: ResolveError) -> Error {
542 Error::LookupFailed(Box::new(err))
543 }
544
545 fn proto_error(err: ProtoError) -> Error {
547 Error::LookupFailed(Box::new(err))
548 }
549}