dns_resolver/
resolver.rs

1use async_trait::async_trait;
2use hickory_resolver::net::{DnsError as NetDnsError, NetError};
3use hickory_resolver::proto::dnssec::Proof;
4use hickory_resolver::proto::op::ResponseCode;
5use hickory_resolver::proto::rr::rdata::{A, AAAA, PTR, TXT};
6#[cfg(feature = "unbound")]
7use hickory_resolver::proto::rr::DNSClass;
8use hickory_resolver::proto::rr::{
9    LowerName, Name, RData, RecordData, RecordSet, RecordType, RrKey,
10};
11use hickory_resolver::proto::serialize::txt::Parser;
12use hickory_resolver::TokioResolver;
13#[cfg(feature = "unbound")]
14use libunbound::{AsyncContext, Context};
15use std::collections::{BTreeMap, BTreeSet};
16use std::fmt;
17use std::net::IpAddr;
18use std::str::FromStr;
19use std::time::{Duration, Instant};
20use thiserror::Error;
21
22/// Negative-cache TTL, in seconds, applied when an answer has no TTL.
23pub(crate) const DEFAULT_NEGATIVE_TTL_SECS: u32 = 60;
24
25pub struct IpDisplay {
26    pub ip: IpAddr,
27    pub reverse: bool,
28}
29
30impl fmt::Display for IpDisplay {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self.ip {
33            IpAddr::V4(v4) => {
34                let mut bytes = v4.octets();
35                if self.reverse {
36                    bytes.reverse();
37                }
38                let mut first = true;
39                for byte in bytes {
40                    if !first {
41                        f.write_str(".")?;
42                    }
43                    write!(f, "{byte}")?;
44                    first = false;
45                }
46                Ok(())
47            }
48            IpAddr::V6(v6) => {
49                let mut bytes = v6.octets();
50                if self.reverse {
51                    bytes.reverse();
52                }
53                let mut first = true;
54                for byte in bytes {
55                    if !first {
56                        f.write_str(".")?;
57                    }
58                    let (upper, lower) = (byte >> 4, byte & 0xf);
59                    if self.reverse {
60                        write!(f, "{lower:x}.{upper:x}")?;
61                    } else {
62                        write!(f, "{upper:x}.{lower:x}")?;
63                    }
64                    first = false;
65                }
66                Ok(())
67            }
68        }
69    }
70}
71
72pub fn ptr_host(ip: IpAddr) -> String {
73    let mut out = reverse_ip(ip);
74    out.push_str(match ip {
75        IpAddr::V4(_) => ".in-addr.arpa",
76        IpAddr::V6(_) => ".ip6.arpa",
77    });
78    out
79}
80
81pub fn reverse_ip(ip: IpAddr) -> String {
82    IpDisplay { ip, reverse: true }.to_string()
83}
84
85#[derive(Debug)]
86pub struct Answer {
87    pub canon_name: Option<String>,
88    pub records: Vec<RData>,
89    pub nxdomain: bool,
90    pub secure: bool,
91    pub bogus: bool,
92    pub why_bogus: Option<String>,
93    pub expires: Instant,
94    pub response_code: ResponseCode,
95}
96
97impl Answer {
98    pub fn as_txt(&self) -> Vec<String> {
99        let mut result = vec![];
100        for r in &self.records {
101            if let RData::TXT(txt) = r {
102                let mut joined = String::new();
103                for t in txt.txt_data.iter() {
104                    joined.push_str(&String::from_utf8_lossy(t));
105                }
106                result.push(joined);
107            }
108        }
109        result
110    }
111
112    pub fn as_addr(&self) -> Vec<IpAddr> {
113        let mut result = vec![];
114        for r in &self.records {
115            match r {
116                RData::A(a) => result.push(a.0.into()),
117                RData::AAAA(a) => result.push(a.0.into()),
118                _ => {}
119            }
120        }
121        result
122    }
123}
124
125#[derive(Clone, Debug, Error, PartialEq)]
126pub enum DnsError {
127    #[error("invalid DNS name: {0}")]
128    InvalidName(String),
129    #[error("DNS: {0}")]
130    ResolveFailed(String),
131}
132
133impl DnsError {
134    pub(crate) fn from_resolve(name: &impl fmt::Display, err: NetError) -> Self {
135        DnsError::ResolveFailed(format!("failed to query DNS for {name}: {err}"))
136    }
137}
138
139fn no_records(err: &NetError) -> Option<&hickory_resolver::net::NoRecords> {
140    match err {
141        NetError::Dns(NetDnsError::NoRecordsFound(nr)) => Some(nr),
142        _ => None,
143    }
144}
145
146/// Summarize the DNSSEC status of a set of records from their validation
147/// proofs, returning `(secure, bogus)`. An answer is secure only when it
148/// carries at least one record and every record validated as secure; it is
149/// bogus when any record failed validation. With validation disabled every
150/// record is Indeterminate, so the result is simply not secure. This applies
151/// both to a positive answer (its answer section) and to a securely denied
152/// answer (the authority section that proves the absence).
153fn dnssec_status(proofs: impl Iterator<Item = Proof>) -> (bool, bool) {
154    let mut any = false;
155    let mut all_secure = true;
156    let mut bogus = false;
157    for proof in proofs {
158        any = true;
159        all_secure &= proof == Proof::Secure;
160        bogus |= proof == Proof::Bogus;
161    }
162    (any && all_secure, bogus)
163}
164
165#[async_trait]
166pub trait Resolver: Send + Sync + 'static {
167    async fn resolve_ip(&self, host: &str) -> Result<Vec<IpAddr>, DnsError>;
168
169    async fn resolve_mx(&self, host: &str) -> Result<Vec<Name>, DnsError>;
170
171    async fn resolve_ptr(&self, ip: IpAddr) -> Result<Vec<Name>, DnsError>;
172
173    async fn resolve_txt(&self, name: &str) -> Result<Answer, DnsError> {
174        let name = Name::from_str_relaxed(name)
175            .map_err(|err| DnsError::InvalidName(format!("invalid name {name}: {err}")))?;
176        self.resolve(name, RecordType::TXT).await
177    }
178
179    async fn resolve(&self, name: Name, rrtype: RecordType) -> Result<Answer, DnsError>;
180}
181
182#[async_trait]
183impl Resolver for Box<dyn Resolver> {
184    async fn resolve_ip(&self, host: &str) -> Result<Vec<IpAddr>, DnsError> {
185        (**self).resolve_ip(host).await
186    }
187
188    async fn resolve_mx(&self, host: &str) -> Result<Vec<Name>, DnsError> {
189        (**self).resolve_mx(host).await
190    }
191
192    async fn resolve_ptr(&self, ip: IpAddr) -> Result<Vec<Name>, DnsError> {
193        (**self).resolve_ptr(ip).await
194    }
195
196    async fn resolve_txt(&self, name: &str) -> Result<Answer, DnsError> {
197        (**self).resolve_txt(name).await
198    }
199
200    async fn resolve(&self, name: Name, rrtype: RecordType) -> Result<Answer, DnsError> {
201        (**self).resolve(name, rrtype).await
202    }
203}
204
205#[derive(Debug, Default)]
206struct TestZone {
207    records: BTreeMap<RrKey, RecordSet>,
208    /// Whether answers from this zone should be reported as DNSSEC validated.
209    secure: bool,
210}
211
212#[derive(Debug, Default)]
213pub struct TestResolver {
214    zones: BTreeMap<Name, TestZone>,
215    /// Owner names for which any lookup should return SERVFAIL, used to model
216    /// resolution failures (e.g. for DANE downgrade-resistance tests).
217    servfail: BTreeSet<Name>,
218    /// Owner names whose answers are flagged DNSSEC-bogus, used to model a
219    /// validating resolver returning a forged or tampered RRset. The records
220    /// of the zone, if any, are still returned alongside the flag: a test can
221    /// use that to cover both a bogus answer with records and a bogus answer
222    /// with none.
223    bogus: BTreeSet<Name>,
224}
225
226impl TestResolver {
227    /// Add an insecure (non-DNSSEC) zone.
228    pub fn with_zone(self, zone: &str) -> Result<Self, String> {
229        self.add_zone(zone, false)
230    }
231
232    /// Add a zone whose answers are reported as DNSSEC validated (secure).
233    pub fn with_secure_zone(self, zone: &str) -> Result<Self, String> {
234        self.add_zone(zone, true)
235    }
236
237    fn add_zone(mut self, zone: &str, secure: bool) -> Result<Self, String> {
238        let (mut name, records) = Parser::new(zone, None, None)
239            .parse()
240            .map_err(|err| format!("{err:#}"))?;
241        // The parser can create results with varying FQDN-ness, so let's
242        // ensure that they're all marked as FQDN, otherwise our get()
243        // function can fail to resolve data from the zone.
244        name.set_fqdn(true);
245        let fqdn_records = records
246            .into_iter()
247            .map(|(key, value)| {
248                if key.name().is_fqdn() {
249                    (key, value)
250                } else {
251                    let mut name: Name = key.name().into();
252                    name.set_fqdn(true);
253                    (RrKey::new(LowerName::new(&name), key.record_type), value)
254                }
255            })
256            .collect();
257        self.zones.insert(
258            name,
259            TestZone {
260                records: fqdn_records,
261                secure,
262            },
263        );
264        Ok(self)
265    }
266
267    /// Cause any lookup for `name` to return SERVFAIL.
268    pub fn with_servfail(mut self, name: &str) -> Self {
269        let mut name = Name::from_str_relaxed(name)
270            .expect("valid name passed to with_servfail")
271            .to_lowercase();
272        name.set_fqdn(true);
273        self.servfail.insert(name);
274        self
275    }
276
277    /// Makes any lookup for `name` return its normal records (if any) marked
278    /// DNSSEC-bogus, as a validating resolver would for a forged or tampered
279    /// RRset.
280    pub fn with_bogus(mut self, name: &str) -> Self {
281        let mut name = Name::from_str_relaxed(name)
282            .expect("valid name passed to with_bogus")
283            .to_lowercase();
284        name.set_fqdn(true);
285        self.bogus.insert(name);
286        self
287    }
288
289    pub fn with_txt(self, domain: &str, value: impl Into<String>) -> Self {
290        self.with_txt_multiple(domain, vec![value.into()])
291    }
292
293    /// Add multiple separate TXT records for the specified domain
294    pub fn with_txt_multiple(mut self, domain: &str, value: Vec<String>) -> Self {
295        let fqdn = format!("{}.", domain);
296        let authority = Name::from_str_relaxed(&fqdn).unwrap();
297        let key = RrKey {
298            name: LowerName::from_str(&fqdn).unwrap(),
299            record_type: RecordType::TXT,
300        };
301
302        let mut records = RecordSet::new(authority.clone(), RecordType::TXT, 0);
303        for item in value {
304            records.add_rdata(RData::TXT(TXT::new(vec![item])));
305        }
306        self.zones
307            .entry(authority)
308            .or_default()
309            .records
310            .insert(key, records);
311
312        self
313    }
314
315    fn get(&self, full: &Name, record_type: RecordType) -> Result<Answer, DnsError> {
316        let mut answer = self.get_impl(full, record_type)?;
317        let mut full_fqdn = full.clone();
318        full_fqdn.set_fqdn(true);
319        if self.bogus.contains(&full_fqdn.to_lowercase()) {
320            answer.bogus = true;
321            answer.why_bogus = Some("test bogus".to_string());
322        }
323        Ok(answer)
324    }
325
326    fn get_impl(&self, full: &Name, record_type: RecordType) -> Result<Answer, DnsError> {
327        let mut full_fqdn = full.clone();
328        full_fqdn.set_fqdn(true);
329
330        if self.servfail.contains(&full_fqdn.to_lowercase()) {
331            return Ok(Answer {
332                canon_name: None,
333                records: vec![],
334                nxdomain: false,
335                secure: false,
336                bogus: false,
337                why_bogus: None,
338                expires: Instant::now() + Duration::from_secs(60),
339                response_code: ResponseCode::ServFail,
340            });
341        }
342
343        let mut authority = full_fqdn.clone();
344
345        let zone = loop {
346            if let Some(zone) = self.zones.get(&authority) {
347                break zone;
348            };
349
350            if authority.num_labels() > 1 {
351                authority = authority.base_name();
352                continue;
353            }
354
355            return Ok(Answer {
356                canon_name: None,
357                records: vec![],
358                nxdomain: true,
359                secure: false,
360                bogus: false,
361                why_bogus: None,
362                expires: Instant::now() + Duration::from_secs(60),
363                response_code: ResponseCode::NXDomain,
364            });
365        };
366
367        let records = zone.records.get(&RrKey {
368            name: LowerName::from(&full_fqdn),
369            record_type,
370        });
371
372        let Some(records) = records else {
373            // Follow a single CNAME for non-CNAME queries, mirroring how a
374            // recursive resolver expands aliases. The DNSSEC secure status of
375            // the overall answer is the AND of every step, so a secure alias
376            // targeting an insecure zone yields an insecure address answer
377            // (while an explicit CNAME-type query still reports the alias's own
378            // secure bit, since it matches a record here and never reaches this
379            // branch).
380            if record_type != RecordType::CNAME {
381                if let Some(cname_rs) = zone.records.get(&RrKey {
382                    name: LowerName::from(&full_fqdn),
383                    record_type: RecordType::CNAME,
384                }) {
385                    if let Some(target) =
386                        cname_rs
387                            .records_without_rrsigs()
388                            .find_map(|r| match &r.data {
389                                RData::CNAME(cname) => Some(cname.0.clone()),
390                                _ => None,
391                            })
392                    {
393                        let mut target_answer = self.get(&target, record_type)?;
394                        target_answer.canon_name = Some(target.to_ascii());
395                        target_answer.secure = target_answer.secure && zone.secure;
396                        return Ok(target_answer);
397                    }
398                }
399            }
400
401            return Ok(Answer {
402                canon_name: None,
403                records: vec![],
404                nxdomain: false,
405                secure: zone.secure,
406                bogus: false,
407                why_bogus: None,
408                expires: Instant::now() + Duration::from_secs(60),
409                response_code: ResponseCode::NoError,
410            });
411        };
412
413        Ok(Answer {
414            canon_name: None,
415            records: records
416                .records_without_rrsigs()
417                .map(|r| r.data.clone())
418                .collect(),
419            nxdomain: false,
420            secure: zone.secure,
421            bogus: false,
422            why_bogus: None,
423            expires: Instant::now() + Duration::from_secs(60),
424            response_code: ResponseCode::NoError,
425        })
426    }
427}
428
429#[async_trait]
430impl Resolver for TestResolver {
431    async fn resolve_ip(&self, full: &str) -> Result<Vec<IpAddr>, DnsError> {
432        let name = Name::from_str_relaxed(full)
433            .map_err(|err| DnsError::InvalidName(format!("invalid name {full}: {err}")))?;
434
435        let mut values = vec![];
436        let answer = self.get(&name, RecordType::A)?;
437        for record in answer.records {
438            let a = A::try_borrow(&record).unwrap();
439            values.push(IpAddr::V4(a.0));
440        }
441
442        let answer = self.get(&name, RecordType::AAAA)?;
443        for record in answer.records {
444            let a = AAAA::try_borrow(&record).unwrap();
445            values.push(IpAddr::V6(a.0));
446        }
447
448        Ok(values)
449    }
450
451    async fn resolve_mx(&self, full: &str) -> Result<Vec<Name>, DnsError> {
452        let name = Name::from_str_relaxed(full)
453            .map_err(|err| DnsError::InvalidName(format!("invalid name {full}: {err}")))?;
454
455        let mut values = vec![];
456        let answer = self.get(&name, RecordType::MX)?;
457        for record in answer.records {
458            if let RData::MX(mx) = record {
459                values.push(mx.exchange.clone());
460            }
461        }
462
463        Ok(values)
464    }
465
466    async fn resolve_ptr(&self, ip: IpAddr) -> Result<Vec<Name>, DnsError> {
467        let name = ptr_host(ip);
468        let name = Name::from_str_relaxed(ptr_host(ip))
469            .map_err(|err| DnsError::InvalidName(format!("invalid name {name}: {err}")))?;
470
471        let mut values = vec![];
472        let answer = self.get(&name, RecordType::PTR)?;
473        for record in answer.records {
474            match PTR::try_borrow(&record) {
475                Some(ptr) => values.push(ptr.0.clone()),
476                None => {
477                    println!("invalid record found for PTR record for {ip}");
478                    return Err(DnsError::ResolveFailed(format!(
479                        "invalid record found for PTR record for {ip}"
480                    )));
481                }
482            };
483        }
484
485        Ok(values)
486    }
487
488    async fn resolve(&self, name: Name, rrtype: RecordType) -> Result<Answer, DnsError> {
489        self.get(&name, rrtype)
490    }
491}
492
493#[cfg(feature = "unbound")]
494pub struct UnboundResolver {
495    cx: AsyncContext,
496}
497
498#[cfg(feature = "unbound")]
499impl UnboundResolver {
500    pub fn new() -> Result<Self, libunbound::Error> {
501        // This resolves directly against the root
502        let context = Context::new()?;
503        // and enables DNSSEC
504        context.add_builtin_trust_anchors()?;
505        Ok(Self {
506            cx: context.into_async()?,
507        })
508    }
509}
510
511#[cfg(feature = "unbound")]
512#[async_trait]
513impl Resolver for UnboundResolver {
514    async fn resolve_ip(&self, host: &str) -> Result<Vec<IpAddr>, DnsError> {
515        let (a, aaaa) = tokio::join!(
516            self.cx.resolve(host, RecordType::A, DNSClass::IN),
517            self.cx.resolve(host, RecordType::AAAA, DNSClass::IN),
518        );
519
520        let mut records = vec![];
521        match (a, aaaa) {
522            (Ok(a), Ok(aaaa)) => {
523                records.extend(a.rdata().filter_map(|r| match r {
524                    Ok(RData::A(a)) => Some(IpAddr::from(a.0)),
525                    _ => None,
526                }));
527                records.extend(aaaa.rdata().filter_map(|r| match r {
528                    Ok(RData::AAAA(aaaa)) => Some(IpAddr::from(aaaa.0)),
529                    _ => None,
530                }));
531            }
532            (Ok(a), Err(_)) => {
533                records.extend(a.rdata().filter_map(|r| match r {
534                    Ok(RData::A(a)) => Some(IpAddr::from(a.0)),
535                    _ => None,
536                }));
537            }
538            (Err(_), Ok(aaaa)) => {
539                records.extend(aaaa.rdata().filter_map(|r| match r {
540                    Ok(RData::AAAA(aaaa)) => Some(IpAddr::from(aaaa.0)),
541                    _ => None,
542                }));
543            }
544            (Err(err), Err(_)) => {
545                return Err(DnsError::ResolveFailed(format!(
546                    "failed to query DNS for {host}: {err}"
547                )))
548            }
549        }
550
551        Ok(records)
552    }
553
554    async fn resolve_mx(&self, host: &str) -> Result<Vec<Name>, DnsError> {
555        let answer = self
556            .cx
557            .resolve(host, RecordType::A, DNSClass::IN)
558            .await
559            .map_err(|err| {
560                DnsError::ResolveFailed(format!("failed to query DNS for {host}: {err}"))
561            })?;
562
563        Ok(answer
564            .rdata()
565            .filter_map(|r| match r {
566                Ok(RData::MX(mx)) => Some(mx.exchange.clone()),
567                _ => None,
568            })
569            .collect())
570    }
571
572    async fn resolve_ptr(&self, ip: IpAddr) -> Result<Vec<Name>, DnsError> {
573        let name = ptr_host(ip);
574        let answer = self
575            .cx
576            .resolve(&name, RecordType::PTR, DNSClass::IN)
577            .await
578            .map_err(|err| {
579                DnsError::ResolveFailed(format!("failed to query DNS for {name}: {err}"))
580            })?;
581
582        Ok(answer
583            .rdata()
584            .filter_map(|r| match r {
585                Ok(RData::PTR(ptr)) => Some(ptr.0.clone()),
586                _ => None,
587            })
588            .collect())
589    }
590
591    async fn resolve(&self, name: Name, rrtype: RecordType) -> Result<Answer, DnsError> {
592        let name = name.to_ascii();
593        let answer = self
594            .cx
595            .resolve(&name, rrtype, DNSClass::IN)
596            .await
597            .map_err(|err| {
598                DnsError::ResolveFailed(format!("failed to query DNS for {name}: {err}"))
599            })?;
600
601        let mut records = vec![];
602        for r in answer.rdata() {
603            if let Ok(r) = r {
604                records.push(r);
605            }
606        }
607
608        Ok(Answer {
609            canon_name: answer.canon_name().map(|s| s.to_string()),
610            records,
611            nxdomain: answer.nxdomain(),
612            secure: answer.secure(),
613            bogus: answer.bogus(),
614            why_bogus: answer.why_bogus().map(|s| s.to_string()),
615            response_code: answer.rcode(),
616            expires: Instant::now() + Duration::from_secs(answer.ttl() as u64),
617        })
618    }
619}
620
621#[cfg(feature = "unbound")]
622impl From<AsyncContext> for UnboundResolver {
623    fn from(cx: AsyncContext) -> Self {
624        Self { cx }
625    }
626}
627
628pub struct HickoryResolver {
629    inner: TokioResolver,
630}
631
632impl HickoryResolver {
633    pub fn new() -> Result<Self, NetError> {
634        Ok(Self {
635            inner: TokioResolver::builder_tokio()?.build()?,
636        })
637    }
638}
639
640#[async_trait]
641impl Resolver for HickoryResolver {
642    async fn resolve_ip(&self, host: &str) -> Result<Vec<IpAddr>, DnsError> {
643        let name = Name::from_str_relaxed(host)
644            .map_err(|err| DnsError::InvalidName(format!("invalid name {host}: {err}")))?;
645
646        match self.inner.lookup_ip(name.clone()).await {
647            Ok(result) => Ok(result.iter().collect()),
648            Err(err) => match no_records(&err) {
649                Some(_) => Ok(vec![]),
650                None => Err(DnsError::from_resolve(&name, err)),
651            },
652        }
653    }
654
655    async fn resolve_mx(&self, host: &str) -> Result<Vec<Name>, DnsError> {
656        let name = Name::from_str_relaxed(host)
657            .map_err(|err| DnsError::InvalidName(format!("invalid name {host}: {err}")))?;
658
659        match self.inner.mx_lookup(name.clone()).await {
660            Ok(result) => Ok(result
661                .answers()
662                .iter()
663                .filter_map(|r| match &r.data {
664                    RData::MX(mx) => Some(mx.exchange.clone()),
665                    _ => None,
666                })
667                .collect()),
668            Err(err) => match no_records(&err) {
669                Some(_) => Ok(vec![]),
670                None => Err(DnsError::from_resolve(&name, err)),
671            },
672        }
673    }
674
675    async fn resolve_ptr(&self, ip: IpAddr) -> Result<Vec<Name>, DnsError> {
676        match self.inner.reverse_lookup(ip).await {
677            Ok(result) => Ok(result
678                .answers()
679                .iter()
680                .filter_map(|r| match &r.data {
681                    RData::PTR(ptr) => Some(ptr.0.clone()),
682                    _ => None,
683                })
684                .collect()),
685            Err(err) => match no_records(&err) {
686                Some(_) => Ok(vec![]),
687                None => Err(DnsError::from_resolve(&ip, err)),
688            },
689        }
690    }
691
692    async fn resolve(&self, name: Name, rrtype: RecordType) -> Result<Answer, DnsError> {
693        match self.inner.lookup(name.clone(), rrtype).await {
694            Ok(result) => {
695                let expires = result.valid_until();
696                let answers = result.answers();
697                // Surface bogus so that callers (e.g. DANE) can defer rather
698                // than downgrade.
699                let (secure, bogus) = dnssec_status(answers.iter().map(|r| r.proof));
700                let records = answers.iter().map(|r| r.data.clone()).collect();
701                Ok(Answer {
702                    canon_name: None,
703                    records,
704                    nxdomain: false,
705                    secure,
706                    bogus,
707                    why_bogus: None,
708                    expires,
709                    response_code: ResponseCode::NoError,
710                })
711            }
712            Err(err) => match no_records(&err) {
713                Some(no_records) => {
714                    // A securely denied answer (NODATA/NXDOMAIN in a signed
715                    // zone) is proven by DNSSEC-validated authority records
716                    // (SOA/NSEC/RRSIG); derive its secure/bogus status from
717                    // those just as we do for a positive answer, so that a
718                    // securely proven "no MX" can still engage DANE for the
719                    // implicit MX.
720                    let authorities = no_records.authorities.as_deref().unwrap_or(&[]);
721                    let (secure, bogus) = dnssec_status(authorities.iter().map(|r| r.proof));
722                    Ok(Answer {
723                        canon_name: None,
724                        records: vec![],
725                        nxdomain: no_records.response_code == ResponseCode::NXDomain,
726                        secure,
727                        bogus,
728                        why_bogus: None,
729                        response_code: no_records.response_code,
730                        expires: Instant::now()
731                            + Duration::from_secs(
732                                no_records.negative_ttl.unwrap_or(DEFAULT_NEGATIVE_TTL_SECS) as u64,
733                            ),
734                    })
735                }
736                None => Err(DnsError::from_resolve(&name, err)),
737            },
738        }
739    }
740}
741
742impl From<TokioResolver> for HickoryResolver {
743    fn from(inner: TokioResolver) -> Self {
744        Self { inner }
745    }
746}
747
748/// AggregateResolver aggregates the results from multiple
749/// resolver instances.
750/// This is most useful when you want to overlay or otherwise
751/// force in test data to take precedence over real resolver
752/// results.  In that situation, you'd push a TestResolver
753/// ahead of the HickoryResolver that you want to use for
754/// real DNS resolution.
755pub struct AggregateResolver {
756    resolvers: Vec<Box<dyn Resolver>>,
757}
758
759impl AggregateResolver {
760    pub fn new() -> Self {
761        Self { resolvers: vec![] }
762    }
763
764    pub fn push_resolver(&mut self, resolver: Box<dyn Resolver>) {
765        self.resolvers.push(resolver);
766    }
767}
768
769#[async_trait]
770impl Resolver for AggregateResolver {
771    async fn resolve_ip(&self, full: &str) -> Result<Vec<IpAddr>, DnsError> {
772        let mut errors = vec![];
773        for resolver in &self.resolvers {
774            match resolver.resolve_ip(full).await {
775                Ok(ips) if ips.is_empty() => {}
776                Ok(ips) => {
777                    return Ok(ips);
778                }
779                Err(err) => errors.push(err),
780            }
781        }
782
783        if let Some(err) = errors.pop() {
784            Err(err)
785        } else {
786            Ok(vec![])
787        }
788    }
789
790    async fn resolve_mx(&self, full: &str) -> Result<Vec<Name>, DnsError> {
791        let mut errors = vec![];
792        for resolver in &self.resolvers {
793            match resolver.resolve_mx(full).await {
794                Ok(ips) if ips.is_empty() => {}
795                Ok(ips) => {
796                    return Ok(ips);
797                }
798                Err(err) => errors.push(err),
799            }
800        }
801
802        if let Some(err) = errors.pop() {
803            Err(err)
804        } else {
805            Ok(vec![])
806        }
807    }
808
809    async fn resolve_ptr(&self, ip: IpAddr) -> Result<Vec<Name>, DnsError> {
810        let mut errors = vec![];
811        for resolver in &self.resolvers {
812            match resolver.resolve_ptr(ip).await {
813                Ok(ips) if ips.is_empty() => {}
814                Ok(ips) => {
815                    return Ok(ips);
816                }
817                Err(err) => errors.push(err),
818            }
819        }
820
821        if let Some(err) = errors.pop() {
822            Err(err)
823        } else {
824            Ok(vec![])
825        }
826    }
827
828    async fn resolve(&self, name: Name, rrtype: RecordType) -> Result<Answer, DnsError> {
829        let mut errors = vec![];
830        let mut nxdomain = None;
831        for resolver in &self.resolvers {
832            match resolver.resolve(name.clone(), rrtype).await {
833                Ok(answer) => {
834                    if !answer.nxdomain {
835                        return Ok(answer);
836                    }
837                    nxdomain.replace(answer);
838                }
839                Err(err) => errors.push(err),
840            }
841        }
842
843        if let Some(err) = errors.pop() {
844            Err(err)
845        } else {
846            match nxdomain.take() {
847                Some(answer) => Ok(answer),
848                None => Ok(Answer {
849                    canon_name: None,
850                    records: vec![],
851                    nxdomain: true,
852                    secure: false,
853                    bogus: false,
854                    why_bogus: None,
855                    expires: Instant::now() + Duration::from_secs(60),
856                    response_code: ResponseCode::NXDomain,
857                }),
858            }
859        }
860    }
861}
862
863#[cfg(test)]
864mod tests {
865    use super::ptr_host;
866    use std::net::{Ipv4Addr, Ipv6Addr};
867    use std::str::FromStr;
868
869    #[test]
870    fn test_ptr_host() {
871        assert_eq!(
872            ptr_host(Ipv4Addr::new(192, 0, 2, 1).into()),
873            "1.2.0.192.in-addr.arpa"
874        );
875        assert_eq!(
876            ptr_host(Ipv6Addr::from_str("2001:db8::1").unwrap().into()),
877            "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa"
878        );
879    }
880}