dns_types/zones/
types.rs

1use std::collections::HashMap;
2
3use crate::protocol::types::*;
4
5/// A collection of zones.
6#[derive(Debug, Clone)]
7pub struct Zones {
8    zones: HashMap<DomainName, Zone>,
9}
10
11impl Default for Zones {
12    fn default() -> Self {
13        Self::new()
14    }
15}
16
17impl Zones {
18    pub fn new() -> Self {
19        Self {
20            zones: HashMap::new(),
21        }
22    }
23
24    /// Find the zone for a domain, if there is one.
25    pub fn get(&self, name: &DomainName) -> Option<&Zone> {
26        for i in 0..name.labels.len() {
27            let labels = &name.labels[i..];
28            if let Some(name) = DomainName::from_labels(labels.into()) {
29                if let Some(zone) = self.zones.get(&name) {
30                    return Some(zone);
31                }
32            }
33        }
34
35        None
36    }
37
38    /// Resolve a query aginst the appropriate zone.  Returns `None` if there is
39    /// no zone.
40    ///
41    /// This corresponds to step 3 of the standard nameserver
42    /// algorithm (see section 4.3.2 of RFC 1034).
43    #[allow(clippy::missing_panics_doc)]
44    pub fn resolve(&self, name: &DomainName, qtype: QueryType) -> Option<(&Zone, ZoneResult)> {
45        if let Some(zone) = self.get(name) {
46            // safe becauze the domain matches the zone
47            let result = zone.resolve(name, qtype).unwrap();
48            Some((zone, result))
49        } else {
50            None
51        }
52    }
53
54    /// Create or replace a zone.
55    pub fn insert(&mut self, zone: Zone) {
56        self.zones.insert(zone.apex.clone(), zone);
57    }
58
59    /// Create a new zone or merge with an existing one.  See
60    /// `Zone.merge` for details.
61    #[allow(clippy::missing_panics_doc)]
62    pub fn insert_merge(&mut self, other_zone: Zone) {
63        if let Some(my_zone) = self.zones.get_mut(&other_zone.apex) {
64            // safe because of the apex check
65            my_zone.merge(other_zone).unwrap();
66        } else {
67            self.insert(other_zone);
68        }
69    }
70
71    /// Perform a zone-wise merge.  See `Zone.merge` for details.
72    #[allow(clippy::missing_panics_doc)]
73    pub fn merge(&mut self, other: Zones) {
74        for (apex, other_zone) in other.zones {
75            if let Some(my_zone) = self.zones.get_mut(&apex) {
76                // safe because of the apex check
77                my_zone.merge(other_zone).unwrap();
78            } else {
79                self.insert(other_zone);
80            }
81        }
82    }
83}
84
85/// A zone is a collection of records all belonging to the same domain
86/// name.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Zone {
89    /// The domain name which the records all belong to.
90    apex: DomainName,
91
92    /// The SOA record for this zone, if it is authoritative.
93    soa: Option<SOA>,
94
95    /// Records.  These are indexed by label, with the labels relative
96    /// to the apex.  For example, if the apex is "barrucadu.co.uk",
97    /// then records for "www.barrucadu.co.uk" would be indexed under
98    /// "www".
99    records: ZoneRecords,
100}
101
102impl Default for Zone {
103    fn default() -> Self {
104        Self::new(DomainName::root_domain(), None)
105    }
106}
107
108#[cfg(any(feature = "test-util", test))]
109impl<'a> arbitrary::Arbitrary<'a> for Zone {
110    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
111        let mut zone = if u.arbitrary()? {
112            Self::new(u.arbitrary()?, Some(u.arbitrary()?))
113        } else {
114            Self::new(DomainName::root_domain(), None)
115        };
116
117        let apex = zone.get_apex().clone();
118
119        let len = u.int_in_range::<usize>(0..=128)?;
120        for _ in 0..len {
121            let mut rr: ResourceRecord = u.arbitrary()?;
122            let mut combined_labels = rr.name.labels;
123            combined_labels.pop();
124            // crude shrinking to fit in the 255 octet limit -
125            // generated labels are up to 20 characters long,
126            // `num_labels * 21 <= 255` has to hold
127            while (combined_labels.len() + apex.labels.len()) * 21 > DOMAINNAME_MAX_LEN {
128                combined_labels.pop();
129            }
130            combined_labels.append(&mut apex.labels.clone());
131            rr.name = DomainName::from_labels(combined_labels).unwrap();
132
133            if rr.rtype_with_data.rtype() == RecordType::SOA
134                || rr.rtype_with_data.rtype().is_unknown()
135            {
136                rr.rtype_with_data = RecordTypeWithData::A {
137                    address: u.arbitrary()?,
138                };
139            }
140
141            if u.arbitrary()? {
142                zone.insert_wildcard(&rr.name, rr.rtype_with_data, rr.ttl);
143            } else {
144                zone.insert(&rr.name, rr.rtype_with_data, rr.ttl);
145            }
146        }
147
148        assert!(
149            zone.get_apex() == &DomainName::root_domain() || zone.is_authoritative(),
150            "non-authoritative zone with apex!\n\n{:?}\n\n",
151            zone.get_apex()
152        );
153
154        Ok(zone)
155    }
156}
157
158impl Zone {
159    /// Construct a new zone.
160    ///
161    /// If there is a `SOA` value, it is inserted as an RR at the root
162    /// of the zone.
163    pub fn new(apex: DomainName, soa: Option<SOA>) -> Self {
164        let mut records = ZoneRecords::new(apex.clone());
165        if let Some(soa) = &soa {
166            let rr = soa.to_rr(&apex);
167            records.insert(&[], rr.rtype_with_data, rr.ttl);
168        }
169
170        Self { apex, soa, records }
171    }
172
173    /// Returns the apex domain.
174    pub fn get_apex(&self) -> &DomainName {
175        &self.apex
176    }
177
178    /// Return the SOA if the zone is authoritative.
179    pub fn get_soa(&self) -> Option<&SOA> {
180        self.soa.as_ref()
181    }
182
183    /// Returns true if the zone is authoritative.
184    pub fn is_authoritative(&self) -> bool {
185        self.soa.is_some()
186    }
187
188    /// Returns the SOA RR if the zone is authoritative.
189    pub fn soa_rr(&self) -> Option<ResourceRecord> {
190        self.soa.as_ref().map(|soa| soa.to_rr(&self.apex))
191    }
192
193    /// Resolve a query.  Returns `None` if the domain is not a
194    /// subdomain of the apex.
195    ///
196    /// This corresponds to step 3 of the standard nameserver
197    /// algorithm (see section 4.3.2 of RFC 1034).
198    pub fn resolve(&self, name: &DomainName, qtype: QueryType) -> Option<ZoneResult> {
199        self.relative_domain(name)
200            .map(|relative| self.records.resolve(name, qtype, relative))
201    }
202
203    /// Insert a record for a domain.  This domain MUST be a subdomain
204    /// of the apex.
205    ///
206    /// Note that, for authoritative zones, the SOA `minimum` field is
207    /// a lower bound on the TTL of any RR in the zone.  So if this
208    /// TTL is lower, it will be raised.
209    pub fn insert(&mut self, name: &DomainName, rtype_with_data: RecordTypeWithData, ttl: u32) {
210        if let Some(relative_domain) = self.relative_domain(name) {
211            self.records
212                .insert(relative_domain, rtype_with_data, self.actual_ttl(ttl));
213        }
214    }
215
216    /// Insert a wildcard record for a domain.  This domain MUST be a
217    /// subdomain of the apex.
218    ///
219    /// Note that, for authoritative zones, the SOA `minimum` field is
220    /// a lower bound on the TTL of any RR in the zone.  So if this
221    /// TTL is lower, it will be raised.
222    pub fn insert_wildcard(
223        &mut self,
224        name: &DomainName,
225        rtype_with_data: RecordTypeWithData,
226        ttl: u32,
227    ) {
228        if let Some(relative_domain) = self.relative_domain(name) {
229            self.records
230                .insert_wildcard(relative_domain, rtype_with_data, self.actual_ttl(ttl));
231        }
232    }
233
234    /// Take a domain and chop off the suffix corresponding to the
235    /// apex of this zone.
236    ///
237    /// Returns `None` if the given domain does not match the apex.
238    pub fn relative_domain<'a>(&self, name: &'a DomainName) -> Option<&'a [Label]> {
239        if name.is_subdomain_of(&self.apex) {
240            Some(&name.labels[0..name.labels.len() - self.apex.labels.len()])
241        } else {
242            None
243        }
244    }
245
246    /// If this zone is authoritative, and the given TTL is below the
247    /// SOA `minimum` field, returns the SOA `minimum` field.
248    ///
249    /// Otherwise returns the given TTL.
250    pub fn actual_ttl(&self, ttl: u32) -> u32 {
251        if let Some(soa) = &self.soa {
252            std::cmp::max(soa.minimum, ttl)
253        } else {
254            ttl
255        }
256    }
257
258    /// Merge another zone into this one.
259    ///
260    /// # Errors
261    ///
262    /// If the apex does not match.
263    pub fn merge(&mut self, other: Zone) -> Result<(), (DomainName, DomainName)> {
264        if self.apex != other.apex {
265            return Err((self.apex.clone(), other.apex));
266        }
267
268        if other.soa.is_some() {
269            self.soa = other.soa;
270        }
271
272        self.records.merge(other.records);
273
274        Ok(())
275    }
276
277    /// Return all the records in the zone.
278    pub fn all_records(&self) -> HashMap<&DomainName, Vec<&ZoneRecord>> {
279        let mut map = HashMap::new();
280        self.records.all_records(&mut map);
281        map
282    }
283
284    /// Return all the wildcard records in the zone.
285    pub fn all_wildcard_records(&self) -> HashMap<&DomainName, Vec<&ZoneRecord>> {
286        let mut map = HashMap::new();
287        self.records.all_wildcard_records(&mut map);
288        map
289    }
290}
291
292/// The result of looking up a name in a zone.
293#[derive(Debug, Clone, PartialEq, Eq)]
294pub enum ZoneResult {
295    Answer {
296        rrs: Vec<ResourceRecord>,
297    },
298    CNAME {
299        cname: DomainName,
300        rr: ResourceRecord,
301    },
302    Delegation {
303        ns_rrs: Vec<ResourceRecord>,
304    },
305    NameError,
306}
307
308/// The tree of records in a zone.
309#[derive(Debug, Clone, PartialEq, Eq)]
310struct ZoneRecords {
311    /// Fully expanded domain name (labels + apex) of this part of the
312    /// tree.  For reconstructing NS RRs from ZRs.
313    nsdname: DomainName,
314
315    /// Records for this domain only.
316    this: HashMap<RecordType, Vec<ZoneRecord>>,
317
318    /// Wildcard records for subdomains of this which are not in the
319    /// `children` map.
320    wildcards: Option<HashMap<RecordType, Vec<ZoneRecord>>>,
321
322    /// Child domains, with their own records.
323    children: HashMap<Label, ZoneRecords>,
324}
325
326impl ZoneRecords {
327    pub fn new(nsdname: DomainName) -> Self {
328        Self {
329            nsdname,
330            this: HashMap::new(),
331            wildcards: None,
332            children: HashMap::new(),
333        }
334    }
335
336    /// Resolve a query
337    pub fn resolve(
338        &self,
339        name: &DomainName,
340        qtype: QueryType,
341        relative_domain: &[Label],
342    ) -> ZoneResult {
343        if relative_domain.is_empty() {
344            // Name matched entirely - this is either case 3.b (if
345            // this name is delegated elsewhere) or 3.a (if not) of
346            // the standard nameserver algorithm
347            zone_result_helper(name, qtype, &self.this, &self.nsdname)
348        } else {
349            let pos = relative_domain.len() - 1;
350            if let Some(child) = self.children.get(&relative_domain[pos]) {
351                child.resolve(name, qtype, &relative_domain[0..pos])
352            } else if let Some(wildcards) = &self.wildcards {
353                // Name cannot be matched further, but there are
354                // wildcards.  This is part of case 3.c of the standard
355                // nameserver algorithm.
356                //
357                // Semantics of wildcard NS records are "undefined"
358                // and the practice is "discouraged, but not barred"
359                // (RFC 4592).  So I've chosen to implement them as
360                // prepending the next label to the current name.
361                let mut labels = self.nsdname.labels.clone();
362                labels.insert(0, relative_domain[pos].clone());
363                let nsdname = DomainName::from_labels(labels).unwrap();
364                zone_result_helper(name, qtype, wildcards, &nsdname)
365            } else {
366                // Name cannot be matched further, and there are no
367                // wildcards.  Check if there are NS records here: if
368                // so, we can delegate (part 3.b of the standard
369                // nameserver algorithm), otherwise this is the other
370                // part of case 3.c.
371                match self.this.get(&RecordType::NS) {
372                    Some(ns_zrs) => {
373                        if ns_zrs.is_empty() {
374                            ZoneResult::NameError
375                        } else {
376                            ZoneResult::Delegation {
377                                ns_rrs: ns_zrs.iter().map(|zr| zr.to_rr(&self.nsdname)).collect(),
378                            }
379                        }
380                    }
381                    None => ZoneResult::NameError,
382                }
383            }
384        }
385    }
386
387    /// Add a record.  This will create children as needed.
388    pub fn insert(
389        &mut self,
390        relative_domain: &[Label],
391        rtype_with_data: RecordTypeWithData,
392        ttl: u32,
393    ) {
394        if relative_domain.is_empty() {
395            let rtype = rtype_with_data.rtype();
396            let new = ZoneRecord {
397                rtype_with_data,
398                ttl,
399            };
400            if let Some(entries) = self.this.get_mut(&rtype) {
401                if entries.iter().any(|e| e == &new) {
402                    return;
403                }
404
405                entries.push(new);
406            } else {
407                self.this.insert(rtype, vec![new]);
408            }
409        } else {
410            let label = relative_domain[relative_domain.len() - 1].clone();
411            let remainder = &relative_domain[0..relative_domain.len() - 1];
412            if let Some(child) = self.children.get_mut(&label) {
413                child.insert(remainder, rtype_with_data, ttl);
414            } else {
415                let mut labels = self.nsdname.labels.clone();
416                labels.insert(0, label.clone());
417
418                let mut child = ZoneRecords::new(DomainName::from_labels(labels).unwrap());
419                child.insert(remainder, rtype_with_data, ttl);
420                self.children.insert(label, child);
421            }
422        }
423    }
424
425    /// Add a wildcard record.  This will create children as needed.
426    pub fn insert_wildcard(
427        &mut self,
428        relative_domain: &[Label],
429        rtype_with_data: RecordTypeWithData,
430        ttl: u32,
431    ) {
432        if relative_domain.is_empty() {
433            let rtype = rtype_with_data.rtype();
434            let new = ZoneRecord {
435                rtype_with_data,
436                ttl,
437            };
438            if let Some(wildcards) = &mut self.wildcards {
439                if let Some(entries) = wildcards.get_mut(&rtype) {
440                    if entries.iter().any(|e| e == &new) {
441                        return;
442                    }
443
444                    entries.push(new);
445                } else {
446                    wildcards.insert(rtype, vec![new]);
447                }
448            } else {
449                let mut wildcards = HashMap::new();
450                wildcards.insert(rtype, vec![new]);
451                self.wildcards = Some(wildcards);
452            }
453        } else {
454            let label = relative_domain[relative_domain.len() - 1].clone();
455            let remainder = &relative_domain[0..relative_domain.len() - 1];
456            if let Some(child) = self.children.get_mut(&label) {
457                child.insert_wildcard(remainder, rtype_with_data, ttl);
458            } else {
459                let mut labels = self.nsdname.labels.clone();
460                labels.insert(0, label.clone());
461
462                let mut child = ZoneRecords::new(DomainName::from_labels(labels).unwrap());
463                child.insert_wildcard(remainder, rtype_with_data, ttl);
464                self.children.insert(label, child);
465            }
466        }
467    }
468
469    /// Recursively merge some other records into these.
470    pub fn merge(&mut self, other: ZoneRecords) {
471        merge_zrs_helper(&mut self.this, other.this);
472
473        if let Some(other_wildcards) = other.wildcards {
474            if let Some(my_wildcards) = self.wildcards.as_mut() {
475                merge_zrs_helper(my_wildcards, other_wildcards);
476            }
477        }
478
479        for (k, other_zrs) in other.children {
480            if let Some(my_zrs) = self.children.get_mut(&k) {
481                my_zrs.merge(other_zrs);
482            } else {
483                self.children.insert(k, other_zrs);
484            }
485        }
486    }
487
488    /// Return all the records in the zone.
489    pub fn all_records<'a>(&'a self, map: &mut HashMap<&'a DomainName, Vec<&'a ZoneRecord>>) {
490        let zrs: Vec<&ZoneRecord> = self.this.values().flatten().collect();
491        if !zrs.is_empty() {
492            map.insert(&self.nsdname, zrs);
493        }
494
495        for child in self.children.values() {
496            child.all_records(map);
497        }
498    }
499
500    /// Return all the wildcard records in the zone.
501    pub fn all_wildcard_records<'a>(
502        &'a self,
503        map: &mut HashMap<&'a DomainName, Vec<&'a ZoneRecord>>,
504    ) {
505        if let Some(ws) = &self.wildcards {
506            let zrs: Vec<&ZoneRecord> = ws.values().flatten().collect();
507            if !zrs.is_empty() {
508                map.insert(&self.nsdname, zrs);
509            }
510        }
511
512        for child in self.children.values() {
513            child.all_wildcard_records(map);
514        }
515    }
516}
517
518/// A SOA record.
519#[derive(Debug, Clone, PartialEq, Eq)]
520#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
521pub struct SOA {
522    pub mname: DomainName,
523    pub rname: DomainName,
524    pub serial: u32,
525    pub refresh: u32,
526    pub retry: u32,
527    pub expire: u32,
528    pub minimum: u32,
529}
530
531impl SOA {
532    /// Convert it into a SOA RR
533    pub fn to_rr(&self, name: &DomainName) -> ResourceRecord {
534        ResourceRecord {
535            name: name.clone(),
536            rtype_with_data: self.to_rdata(),
537            rclass: RecordClass::IN,
538            ttl: self.minimum,
539        }
540    }
541
542    /// Convert it into a SOA RDATA
543    pub fn to_rdata(&self) -> RecordTypeWithData {
544        RecordTypeWithData::SOA {
545            mname: self.mname.clone(),
546            rname: self.rname.clone(),
547            serial: self.serial,
548            refresh: self.refresh,
549            retry: self.retry,
550            expire: self.expire,
551            minimum: self.minimum,
552        }
553    }
554}
555
556/// A single record
557#[derive(Debug, Clone, PartialEq, Eq)]
558pub struct ZoneRecord {
559    pub rtype_with_data: RecordTypeWithData,
560    pub ttl: u32,
561}
562
563impl ZoneRecord {
564    /// Convert it into an RR
565    pub fn to_rr(&self, name: &DomainName) -> ResourceRecord {
566        ResourceRecord {
567            name: name.clone(),
568            rtype_with_data: self.rtype_with_data.clone(),
569            rclass: RecordClass::IN,
570            ttl: self.ttl,
571        }
572    }
573}
574
575/// Handles the terminal cases of step 3 of the standard nameserver
576/// algorithm.  If we're here, we've got a domain and records which
577/// are associated with it.  The possible cases are:
578///
579/// - There are `NS` record(s) delegating this name elsewhere, and the
580///   qtype is not *exactly equal to* `NS`.  In which case we should
581///   return those.  If this is a top-level non-recursive query they
582///   will be returned in the AUTHORITY section.
583///
584/// - There's a `CNAME` record on this name, and the qtype does *not*
585///   match `CNAME`.  In which case we return a `CNAME` response, and
586///   the upstream resolver will then try go resolve that.
587///
588/// - Otherwise, return all RRs which match the query: this answers
589///   the question.
590fn zone_result_helper(
591    name: &DomainName,
592    qtype: QueryType,
593    records: &HashMap<RecordType, Vec<ZoneRecord>>,
594    nsdname: &DomainName,
595) -> ZoneResult {
596    if QueryType::Record(RecordType::NS) != qtype {
597        if let Some(ns_zrs) = records.get(&RecordType::NS) {
598            if !ns_zrs.is_empty() {
599                return ZoneResult::Delegation {
600                    ns_rrs: ns_zrs.iter().map(|zr| zr.to_rr(nsdname)).collect(),
601                };
602            }
603        }
604    }
605
606    if !RecordType::CNAME.matches(qtype) {
607        if let Some(cname_zrs) = records.get(&RecordType::CNAME) {
608            if !cname_zrs.is_empty() {
609                let rr = cname_zrs[0].to_rr(name);
610                if let RecordTypeWithData::CNAME { cname } = &rr.rtype_with_data {
611                    return ZoneResult::CNAME {
612                        cname: cname.clone(),
613                        rr,
614                    };
615                }
616                panic!("got non-CNAME record for CNAME query: {rr:?}");
617            }
618        }
619    }
620
621    match qtype {
622        QueryType::Wildcard => {
623            let mut rrs = Vec::new();
624            for zrs in records.values() {
625                rrs.append(&mut zrs.iter().map(|zr| zr.to_rr(name)).collect());
626            }
627            ZoneResult::Answer { rrs }
628        }
629        QueryType::Record(rtype) => ZoneResult::Answer {
630            rrs: if let Some(zrs) = records.get(&rtype) {
631                zrs.iter().map(|zr| zr.to_rr(name)).collect()
632            } else {
633                Vec::new()
634            },
635        },
636        _ => ZoneResult::Answer { rrs: Vec::new() },
637    }
638}
639
640/// Handles merging two sets of records, discarding duplicates.
641fn merge_zrs_helper(
642    this: &mut HashMap<RecordType, Vec<ZoneRecord>>,
643    other: HashMap<RecordType, Vec<ZoneRecord>>,
644) {
645    for (k, other_zrs) in other {
646        if let Some(my_zrs) = this.get_mut(&k) {
647            for new in other_zrs {
648                if my_zrs.iter().any(|e| e == &new) {
649                    continue;
650                }
651
652                my_zrs.push(new);
653            }
654        } else {
655            this.insert(k, other_zrs);
656        }
657    }
658}
659
660#[cfg(test)]
661mod tests {
662    use std::net::Ipv4Addr;
663
664    use super::*;
665    use crate::protocol::types::test_util::*;
666
667    #[test]
668    fn zones_build_get_get() {
669        let apex = domain("example.com.");
670        let subdomain = "foo.bar.baz.example.com.";
671        let a_rr = a_record(subdomain, Ipv4Addr::new(1, 1, 1, 1));
672        let ns_rr = ns_record(subdomain, "ns1.example.com.");
673
674        let mut zone = Zone::new(apex, None);
675        zone.insert(&a_rr.name, a_rr.rtype_with_data.clone(), a_rr.ttl);
676        zone.insert(&ns_rr.name, ns_rr.rtype_with_data.clone(), ns_rr.ttl);
677
678        let mut zones = Zones::new();
679        zones.insert(zone.clone());
680
681        assert_eq!(None, zones.get(&domain(".")));
682        assert_eq!(None, zones.get(&domain("com.")));
683        assert_eq!(Some(&zone), zones.get(&domain("example.com.")));
684        assert_eq!(Some(&zone), zones.get(&domain("www.example.com.")));
685    }
686
687    #[test]
688    fn zone_merge_prefers_leftmost_some_authority() {
689        let name = domain("example.com.");
690        let soa1 = SOA {
691            mname: domain("mname."),
692            rname: domain("rname."),
693            serial: 1,
694            refresh: 2,
695            retry: 3,
696            expire: 4,
697            minimum: 300,
698        };
699        let soa2 = SOA {
700            mname: domain("mname."),
701            rname: domain("rname."),
702            serial: 100,
703            refresh: 200,
704            retry: 300,
705            expire: 400,
706            minimum: 30000,
707        };
708
709        let mut zone1 = Zone::new(name.clone(), None);
710        zone1
711            .merge(Zone::new(name.clone(), Some(soa2.clone())))
712            .unwrap();
713        assert_eq!(Some(soa2.clone()), zone1.soa);
714
715        let mut zone2 = Zone::new(name.clone(), Some(soa1.clone()));
716        zone2.merge(Zone::new(name.clone(), None)).unwrap();
717        assert_eq!(Some(soa1.clone()), zone2.soa);
718
719        let mut zone3 = Zone::new(name.clone(), Some(soa1));
720        zone3.merge(Zone::new(name, Some(soa2.clone()))).unwrap();
721        assert_eq!(Some(soa2), zone3.soa);
722    }
723
724    #[test]
725    fn zone_merge_checks_apex_consistency() {
726        Zone::new(domain("example.com."), None)
727            .merge(Zone::new(domain("example.com."), None))
728            .unwrap();
729
730        Zone::new(domain("example.com."), None)
731            .merge(Zone::new(domain("example.net."), None))
732            .unwrap_err();
733    }
734
735    #[test]
736    fn zone_merge_combines_and_deduplicates() {
737        let mut zone1 = Zone::new(domain("example.com."), None);
738        let mut zone2 = Zone::new(domain("example.com."), None);
739
740        let a_rr1 = a_record("www.example.com.", Ipv4Addr::new(1, 1, 1, 1));
741        let a_rr2 = a_record("www.example.com.", Ipv4Addr::new(2, 2, 2, 2));
742        zone1.insert(&a_rr1.name, a_rr1.rtype_with_data.clone(), a_rr1.ttl);
743        zone2.insert(&a_rr1.name, a_rr1.rtype_with_data.clone(), a_rr1.ttl);
744        zone2.insert(&a_rr2.name, a_rr2.rtype_with_data.clone(), a_rr2.ttl);
745
746        zone1.merge(zone2).unwrap();
747
748        if let Some(ZoneResult::Answer { mut rrs }) =
749            zone1.resolve(&domain("www.example.com."), QueryType::Wildcard)
750        {
751            let mut expected = vec![a_rr1, a_rr2];
752            expected.sort();
753            rrs.sort();
754
755            assert_eq!(expected, rrs);
756        } else {
757            panic!("expected answer");
758        }
759    }
760
761    #[test]
762    fn zone_authoritative_minimum_ttl() {
763        let zone = Zone::new(
764            domain("example.com."),
765            Some(SOA {
766                mname: domain("mname."),
767                rname: domain("rname."),
768                serial: 1,
769                refresh: 2,
770                retry: 3,
771                expire: 4,
772                minimum: 300,
773            }),
774        );
775
776        assert_eq!(300, zone.actual_ttl(30));
777        assert_eq!(301, zone.actual_ttl(301));
778    }
779
780    #[test]
781    fn zone_nonauthoritative_minimum_ttl() {
782        let zone = Zone::new(domain("example.com."), None);
783
784        assert_eq!(30, zone.actual_ttl(30));
785        assert_eq!(301, zone.actual_ttl(301));
786    }
787
788    #[test]
789    fn zone_resolve_soa() {
790        let apex = domain("example.com.");
791        let soa = SOA {
792            mname: domain("mname."),
793            rname: domain("rname."),
794            serial: 1,
795            refresh: 2,
796            retry: 3,
797            expire: 4,
798            minimum: 5,
799        };
800        let soa_rr = soa.to_rr(&apex);
801
802        let zone = Zone::new(apex.clone(), Some(soa));
803
804        assert_eq!(
805            Some(ZoneResult::Answer { rrs: vec![soa_rr] }),
806            zone.resolve(&apex, QueryType::Record(RecordType::SOA))
807        );
808    }
809
810    #[test]
811    fn zone_insert_resolve() {
812        for _ in 0..100 {
813            let mut zone = Zone::new(domain("example.com."), None);
814            let mut rr = arbitrary_resourcerecord();
815            rr.rclass = RecordClass::IN;
816            rr.name = rr.name.make_subdomain_of(&zone.apex).unwrap();
817
818            zone.insert(&rr.name, rr.rtype_with_data.clone(), rr.ttl);
819
820            let expected = Some(ZoneResult::Answer {
821                rrs: vec![rr.clone()],
822            });
823
824            assert_eq!(
825                expected,
826                zone.resolve(&rr.name, QueryType::Record(rr.rtype_with_data.rtype()))
827            );
828            assert_eq!(expected, zone.resolve(&rr.name, QueryType::Wildcard));
829        }
830    }
831
832    #[test]
833    fn zone_insert_wildcard_resolve() {
834        for _ in 0..100 {
835            let mut zone = Zone::new(domain("example.com."), None);
836            let mut rr = arbitrary_resourcerecord();
837            rr.rclass = RecordClass::IN;
838            rr.name = rr.name.make_subdomain_of(&zone.apex).unwrap();
839
840            zone.insert_wildcard(&rr.name, rr.rtype_with_data.clone(), rr.ttl);
841
842            rr.name = domain("foo.").make_subdomain_of(&rr.name).unwrap();
843
844            let expected = Some(ZoneResult::Answer {
845                rrs: vec![rr.clone()],
846            });
847
848            assert_eq!(
849                expected,
850                zone.resolve(&rr.name, QueryType::Record(rr.rtype_with_data.rtype()))
851            );
852            assert_eq!(expected, zone.resolve(&rr.name, QueryType::Wildcard));
853        }
854    }
855
856    #[test]
857    fn zone_insert_all_records() {
858        let mut zone = Zone::new(domain("example.com."), None);
859        let mut expected = Vec::with_capacity(100);
860        for _ in 0..expected.capacity() {
861            let mut rr = arbitrary_resourcerecord();
862            rr.rclass = RecordClass::IN;
863            rr.name = rr.name.make_subdomain_of(&zone.apex).unwrap();
864            expected.push(rr.clone());
865            zone.insert(&rr.name, rr.rtype_with_data, rr.ttl);
866        }
867        expected.sort();
868
869        let mut actual = Vec::with_capacity(expected.capacity());
870        for (name, zrs) in &zone.all_records() {
871            for zr in zrs {
872                actual.push(zr.to_rr(name));
873            }
874        }
875        actual.sort();
876
877        assert_eq!(expected, actual);
878        assert!(zone.all_wildcard_records().is_empty());
879    }
880
881    #[test]
882    fn zone_insert_all_wildcard_records() {
883        let mut zone = Zone::new(domain("example.com."), None);
884        let mut expected = Vec::with_capacity(100);
885        for _ in 0..expected.capacity() {
886            let mut rr = arbitrary_resourcerecord();
887            rr.rclass = RecordClass::IN;
888            rr.name = rr.name.make_subdomain_of(&zone.apex).unwrap();
889            expected.push(rr.clone());
890            zone.insert_wildcard(&rr.name, rr.rtype_with_data, rr.ttl);
891        }
892        expected.sort();
893
894        let mut actual = Vec::with_capacity(expected.capacity());
895        for (name, zrs) in &zone.all_wildcard_records() {
896            for zr in zrs {
897                actual.push(zr.to_rr(name));
898            }
899        }
900        actual.sort();
901
902        assert!(zone.all_records().is_empty());
903        assert_eq!(expected, actual);
904    }
905
906    #[test]
907    fn zone_resolve_cname() {
908        let mut zone = Zone::new(domain("example.com."), None);
909        let rr = cname_record("www.example.com.", "example.com.");
910        zone.insert(&rr.name, rr.rtype_with_data.clone(), rr.ttl);
911
912        assert_eq!(
913            Some(ZoneResult::CNAME {
914                cname: domain("example.com."),
915                rr: rr.clone()
916            }),
917            zone.resolve(&rr.name, QueryType::Record(RecordType::A))
918        );
919        assert_eq!(
920            Some(ZoneResult::Answer {
921                rrs: vec![rr.clone()]
922            }),
923            zone.resolve(&rr.name, QueryType::Record(RecordType::CNAME))
924        );
925        assert_eq!(
926            Some(ZoneResult::Answer {
927                rrs: vec![rr.clone()]
928            }),
929            zone.resolve(&rr.name, QueryType::Wildcard)
930        );
931    }
932
933    #[test]
934    fn zone_resolve_cname_wildcard() {
935        let mut zone = Zone::new(domain("example.com."), None);
936        let wildcard_rr = cname_record("example.com.", "example.com."); // *.example.com
937        let rr = cname_record("www.example.com.", "example.com.");
938        zone.insert_wildcard(
939            &wildcard_rr.name,
940            wildcard_rr.rtype_with_data.clone(),
941            wildcard_rr.ttl,
942        );
943
944        assert_eq!(
945            Some(ZoneResult::CNAME {
946                cname: domain("example.com."),
947                rr: rr.clone()
948            }),
949            zone.resolve(&rr.name, QueryType::Record(RecordType::A))
950        );
951        assert_eq!(
952            Some(ZoneResult::Answer {
953                rrs: vec![rr.clone()]
954            }),
955            zone.resolve(&rr.name, QueryType::Record(RecordType::CNAME))
956        );
957        assert_eq!(
958            Some(ZoneResult::Answer {
959                rrs: vec![rr.clone()]
960            }),
961            zone.resolve(&rr.name, QueryType::Wildcard)
962        );
963    }
964
965    #[test]
966    fn zone_resolve_delegation() {
967        let mut zone = Zone::new(domain("example.com."), None);
968        let rr = ns_record("www.example.com.", "ns.example.com.");
969        zone.insert(&rr.name, rr.rtype_with_data.clone(), rr.ttl);
970
971        assert_eq!(
972            Some(ZoneResult::Delegation {
973                ns_rrs: vec![rr.clone()]
974            }),
975            zone.resolve(&rr.name, QueryType::Record(RecordType::A))
976        );
977        assert_eq!(
978            Some(ZoneResult::Answer {
979                rrs: vec![rr.clone()]
980            }),
981            zone.resolve(&rr.name, QueryType::Record(RecordType::NS))
982        );
983        assert_eq!(
984            Some(ZoneResult::Delegation {
985                ns_rrs: vec![rr.clone()]
986            }),
987            zone.resolve(&rr.name, QueryType::Wildcard)
988        );
989    }
990
991    #[test]
992    fn zone_resolve_delegation_wildcard() {
993        let mut zone = Zone::new(domain("example.com."), None);
994        let wildcard_rr = ns_record("example.com.", "ns.example.com.");
995        let rr = ns_record("www.example.com.", "ns.example.com.");
996        zone.insert_wildcard(
997            &wildcard_rr.name,
998            wildcard_rr.rtype_with_data.clone(),
999            wildcard_rr.ttl,
1000        );
1001
1002        assert_eq!(
1003            Some(ZoneResult::Delegation {
1004                ns_rrs: vec![rr.clone()]
1005            }),
1006            zone.resolve(&rr.name, QueryType::Record(RecordType::A))
1007        );
1008        assert_eq!(
1009            Some(ZoneResult::Answer {
1010                rrs: vec![rr.clone()]
1011            }),
1012            zone.resolve(&rr.name, QueryType::Record(RecordType::NS))
1013        );
1014        assert_eq!(
1015            Some(ZoneResult::Delegation {
1016                ns_rrs: vec![rr.clone()]
1017            }),
1018            zone.resolve(&rr.name, QueryType::Wildcard)
1019        );
1020    }
1021
1022    #[test]
1023    fn zone_resolve_delegation_wildcard_multilabel() {
1024        let mut zone = Zone::new(domain("example.com."), None);
1025        let wildcard_rr = ns_record("example.com.", "ns.example.com.");
1026        zone.insert_wildcard(
1027            &wildcard_rr.name,
1028            wildcard_rr.rtype_with_data.clone(),
1029            wildcard_rr.ttl,
1030        );
1031
1032        assert_eq!(
1033            Some(ZoneResult::Delegation {
1034                ns_rrs: vec![ns_record("www.example.com.", "ns.example.com.")]
1035            }),
1036            zone.resolve(
1037                &domain("some.long.subdomain.of.www.example.com."),
1038                QueryType::Record(RecordType::A),
1039            )
1040        );
1041    }
1042
1043    #[test]
1044    fn zone_resolve_nameerror() {
1045        let mut zone = Zone::new(domain("example.com."), None);
1046        let rr = a_record("www.example.com.", Ipv4Addr::new(1, 1, 1, 1));
1047        zone.insert(&rr.name, rr.rtype_with_data, rr.ttl);
1048
1049        assert_eq!(
1050            Some(ZoneResult::NameError),
1051            zone.resolve(&domain("subdomain.www.example.com."), QueryType::Wildcard)
1052        );
1053        assert_eq!(
1054            Some(ZoneResult::NameError),
1055            zone.resolve(&domain("sibling.example.com."), QueryType::Wildcard)
1056        );
1057    }
1058
1059    #[test]
1060    fn zone_resolve_empty_answer_not_nameerror_for_subdomain() {
1061        let mut zone = Zone::new(domain("example.com."), None);
1062        let rr = a_record(
1063            "long.chain.of.subdomains.example.com.",
1064            Ipv4Addr::new(1, 1, 1, 1),
1065        );
1066        zone.insert(&rr.name, rr.rtype_with_data, rr.ttl);
1067
1068        assert_eq!(
1069            Some(ZoneResult::Answer { rrs: Vec::new() }),
1070            zone.resolve(
1071                &domain("chain.of.subdomains.example.com."),
1072                QueryType::Wildcard,
1073            )
1074        );
1075        assert_eq!(
1076            Some(ZoneResult::Answer { rrs: Vec::new() }),
1077            zone.resolve(&domain("of.subdomains.example.com."), QueryType::Wildcard)
1078        );
1079        assert_eq!(
1080            Some(ZoneResult::Answer { rrs: Vec::new() }),
1081            zone.resolve(&domain("subdomains.example.com."), QueryType::Wildcard)
1082        );
1083        assert_eq!(
1084            Some(ZoneResult::Answer { rrs: Vec::new() }),
1085            zone.resolve(&domain("example.com."), QueryType::Wildcard)
1086        );
1087    }
1088}