1use std::collections::HashMap;
2
3use crate::protocol::types::*;
4
5#[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 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 #[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 let result = zone.resolve(name, qtype).unwrap();
48 Some((zone, result))
49 } else {
50 None
51 }
52 }
53
54 pub fn insert(&mut self, zone: Zone) {
56 self.zones.insert(zone.apex.clone(), zone);
57 }
58
59 #[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 my_zone.merge(other_zone).unwrap();
66 } else {
67 self.insert(other_zone);
68 }
69 }
70
71 #[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 my_zone.merge(other_zone).unwrap();
78 } else {
79 self.insert(other_zone);
80 }
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Zone {
89 apex: DomainName,
91
92 soa: Option<SOA>,
94
95 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 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 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 pub fn get_apex(&self) -> &DomainName {
175 &self.apex
176 }
177
178 pub fn get_soa(&self) -> Option<&SOA> {
180 self.soa.as_ref()
181 }
182
183 pub fn is_authoritative(&self) -> bool {
185 self.soa.is_some()
186 }
187
188 pub fn soa_rr(&self) -> Option<ResourceRecord> {
190 self.soa.as_ref().map(|soa| soa.to_rr(&self.apex))
191 }
192
193 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 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 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 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 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 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 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 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#[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#[derive(Debug, Clone, PartialEq, Eq)]
310struct ZoneRecords {
311 nsdname: DomainName,
314
315 this: HashMap<RecordType, Vec<ZoneRecord>>,
317
318 wildcards: Option<HashMap<RecordType, Vec<ZoneRecord>>>,
321
322 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 pub fn resolve(
338 &self,
339 name: &DomainName,
340 qtype: QueryType,
341 relative_domain: &[Label],
342 ) -> ZoneResult {
343 if relative_domain.is_empty() {
344 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 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 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 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 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 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 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 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#[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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
558pub struct ZoneRecord {
559 pub rtype_with_data: RecordTypeWithData,
560 pub ttl: u32,
561}
562
563impl ZoneRecord {
564 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
575fn 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
640fn 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."); 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}