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