1use std::{collections::BTreeMap, fs, io, path::Path};
2
3use hickory_proto::{
4 rr::{DNSClass, LowerName, RecordType, RrKey, rdata},
5 serialize::txt::Parser,
6};
7use rusqlite::{ToSql, types::FromSql};
8use tracing::{debug, error, info};
9
10use crate::{
11 authority::{Lookup, ZoneAuthority, ZoneInfo},
12 database::FromRow,
13};
14
15use super::{
16 Name, Record, SerialNumber, SqlName, TimeToLive, ZoneID,
17 rset::{Mismatch, RecordSet},
18};
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
22pub enum ZoneType {
23 Primary = 1,
25 Secondary = 2,
27 External = 3,
29}
30
31impl From<ZoneType> for hickory_server::authority::ZoneType {
32 fn from(value: ZoneType) -> Self {
33 match value {
34 ZoneType::Primary => hickory_server::authority::ZoneType::Primary,
35 ZoneType::Secondary => hickory_server::authority::ZoneType::Secondary,
36 ZoneType::External => hickory_server::authority::ZoneType::External,
37 }
38 }
39}
40
41impl From<hickory_server::authority::ZoneType> for ZoneType {
42 fn from(value: hickory_server::authority::ZoneType) -> Self {
43 match value {
44 hickory_server::authority::ZoneType::Primary => ZoneType::Primary,
45 hickory_server::authority::ZoneType::Secondary => ZoneType::Secondary,
46 hickory_server::authority::ZoneType::External => ZoneType::External,
47 _ => panic!("Deprecated zone type"),
48 }
49 }
50}
51
52impl ToSql for ZoneType {
53 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
54 match self {
55 ZoneType::Primary => Ok(rusqlite::types::ToSqlOutput::Owned(
56 rusqlite::types::Value::Integer(1),
57 )),
58 ZoneType::Secondary => Ok(rusqlite::types::ToSqlOutput::Owned(
59 rusqlite::types::Value::Integer(2),
60 )),
61 ZoneType::External => Ok(rusqlite::types::ToSqlOutput::Owned(
62 rusqlite::types::Value::Integer(3),
63 )),
64 }
65 }
66}
67
68impl FromSql for ZoneType {
69 fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
70 match value {
71 rusqlite::types::ValueRef::Integer(1) => Ok(ZoneType::Primary),
72 rusqlite::types::ValueRef::Integer(2) => Ok(ZoneType::Secondary),
73 rusqlite::types::ValueRef::Integer(3) => Ok(ZoneType::External),
74 rusqlite::types::ValueRef::Integer(i) => {
75 Err(rusqlite::types::FromSqlError::OutOfRange(i))
76 }
77 _ => Err(rusqlite::types::FromSqlError::InvalidType),
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct Zone {
85 id: ZoneID,
86 zone_type: ZoneType,
87 name: Name,
88 origin: LowerName,
89 allow_axfr: bool,
90 dns_class: DNSClass,
91 records: BTreeMap<RrKey, RecordSet>,
92}
93
94impl Zone {
95 pub fn empty(
111 name: Name,
112 soa: Record<rdata::SOA>,
113 zone_type: ZoneType,
114 allow_axfr: bool,
115 ) -> Self {
116 let mut records = BTreeMap::new();
117 records.insert(soa.rrkey(), RecordSet::from_record(name.clone(), soa));
118 let origin = (&name).into();
119
120 Self {
121 id: ZoneID::new(),
122 zone_type,
123 name,
124 origin,
125 allow_axfr,
126 dns_class: DNSClass::IN,
127 records,
128 }
129 }
130
131 pub fn from_rrsets(
146 name: Name,
147 records: impl Iterator<Item = RecordSet>,
148 zone_type: ZoneType,
149 ) -> Self {
150 let mut rrsets = BTreeMap::new();
151 for record in records {
152 rrsets.insert(record.rrkey(), record);
153 }
154 let origin = (&name).into();
155
156 Self {
157 id: ZoneID::new(),
158 zone_type,
159 name,
160 origin,
161 allow_axfr: false,
162 dns_class: DNSClass::IN,
163 records: rrsets,
164 }
165 }
166}
167
168impl Zone {
169 pub fn id(&self) -> ZoneID {
178 self.id
179 }
180
181 pub fn name(&self) -> &Name {
189 &self.name
190 }
191
192 pub fn zone_type(&self) -> ZoneType {
201 self.zone_type
202 }
203
204 pub fn allow_axfr(&self) -> bool {
212 self.allow_axfr
213 }
214
215 pub fn set_allow_axfr(&mut self, allow_axfr: bool) -> &mut Self {
227 self.allow_axfr = allow_axfr;
228 self
229 }
230
231 pub fn set_dns_class(&mut self, dns_class: DNSClass) -> &mut Self {
243 self.dns_class = dns_class;
244 self
245 }
246
247 pub fn records(&self) -> impl Iterator<Item = &Record> {
256 self.records
257 .values()
258 .flat_map(|rrset| rrset.signed_records())
259 }
260
261 pub fn replace(&mut self, rrset: RecordSet) -> Option<RecordSet> {
274 let key = rrset.rrkey();
275 self.records.insert(key, rrset)
276 }
277
278 pub fn is_empty(&self) -> bool {
287 self.records.is_empty()
288 }
289}
290
291impl FromRow for Zone {
292 fn from_row(row: &rusqlite::Row) -> rusqlite::Result<Self> {
293 let id = row.get("id")?;
294 let name: Name = row.get::<_, SqlName>("name")?.into();
295 let zone_type = row.get("zone_type")?;
296 let allow_axfr = row.get("allow_axfr")?;
297 let dns_class = row.get::<_, u16>("dns_class")?.into();
298 let origin = (&name).into();
299
300 Ok(Zone {
301 id,
302 name,
303 origin,
304 zone_type,
305 allow_axfr,
306 dns_class,
307 records: Default::default(),
308 })
309 }
310}
311
312impl ZoneInfo for Zone {
313 fn name(&self) -> &Name {
314 &self.name
315 }
316
317 fn origin(&self) -> &LowerName {
318 &self.origin
319 }
320
321 fn zone_type(&self) -> ZoneType {
322 self.zone_type
323 }
324
325 fn is_axfr_allowed(&self) -> bool {
326 self.allow_axfr
327 }
328
329 fn dns_class(&self) -> DNSClass {
330 self.dns_class
331 }
332
333 fn soa(&self) -> Option<&Record> {
334 let rrset = self
335 .records
336 .get(&RrKey::new(self.name().into(), RecordType::SOA))?;
337
338 rrset.records().next()
339 }
340
341 fn serial(&self) -> SerialNumber {
343 self.soa()
344 .and_then(|soa| soa.rdata().as_soa())
345 .map_or(SerialNumber::ZERO, |soa| SerialNumber::from(soa.serial()))
346 }
347
348 fn minimum_ttl(&self) -> TimeToLive {
350 self.soa()
351 .and_then(|soa| soa.rdata().as_soa())
352 .map_or(TimeToLive::ZERO, |soa| TimeToLive::from(soa.minimum()))
353 }
354
355 fn increment_soa_serial(&mut self) -> SerialNumber {
357 let Some(rrset) = self
358 .records
359 .get_mut(&RrKey::new(self.name().into(), RecordType::SOA))
360 else {
361 return SerialNumber::ZERO;
362 };
363
364 let Some(soa_data) = rrset
365 .records_mut()
366 .next()
367 .and_then(|record| record.rdata_mut().as_soa_mut())
368 else {
369 return SerialNumber::ZERO;
370 };
371
372 soa_data.increment_serial();
373 SerialNumber::from(soa_data.serial())
374 }
375}
376
377impl Lookup for Zone {
378 fn get(&self, key: &RrKey) -> Option<&RecordSet> {
379 self.records.get(key)
380 }
381
382 fn get_mut(&mut self, key: &RrKey) -> Option<&mut RecordSet> {
383 self.records.get_mut(key)
384 }
385
386 fn keys(&self) -> impl Iterator<Item = &RrKey> {
387 self.records.keys()
388 }
389
390 fn records(&self) -> impl Iterator<Item = &RecordSet> {
391 self.records.values()
392 }
393
394 fn records_mut(&mut self) -> impl Iterator<Item = &mut RecordSet> {
395 self.records.values_mut()
396 }
397
398 fn records_reversed(&self) -> impl Iterator<Item = &RecordSet> {
399 self.records.values().rev()
400 }
401
402 fn upsert(&mut self, record: Record, serial: SerialNumber) -> Result<bool, Mismatch> {
403 if record.dns_class() != self.dns_class() {
404 return Err(Mismatch("DNS Class"));
405 }
406
407 fn is_nsec(upsert_type: RecordType, occupied_type: RecordType) -> bool {
408 upsert_type == RecordType::NSEC
410 || upsert_type == RecordType::NSEC3
411 || occupied_type == RecordType::NSEC
412 || occupied_type == RecordType::NSEC3
413 }
414
415 fn label_does_not_allow_multiple(
416 upsert_type: RecordType,
417 occupied_type: RecordType,
418 check_type: RecordType,
419 ) -> bool {
420 (upsert_type == check_type && occupied_type != check_type) ||
422 (upsert_type != check_type && occupied_type == check_type)
424 }
425
426 let start_range_key = RrKey::new(record.name().into(), RecordType::Unknown(u16::MIN));
427 let end_range_key = RrKey::new(record.name().into(), RecordType::Unknown(u16::MAX));
428
429 let multiple_records_at_label_disallowed = self
430 .records
431 .range(&start_range_key..&end_range_key)
432 .any(|(key, _)| {
434 !is_nsec(record.record_type(), key.record_type)
435 && label_does_not_allow_multiple(
436 record.record_type(),
437 key.record_type,
438 RecordType::CNAME,
439 )
440 });
441
442 if multiple_records_at_label_disallowed {
443 return Ok(false);
445 }
446
447 let rrset = self
448 .records
449 .entry(record.rrkey())
450 .or_insert_with(|| RecordSet::new(record.name().clone(), record.record_type()));
451
452 rrset.insert(record, serial)
453 }
454
455 fn range<T, R>(&self, range: R) -> impl Iterator<Item = (&RrKey, &RecordSet)>
456 where
457 T: Ord + ?Sized,
458 RrKey: std::borrow::Borrow<T> + Ord,
459 R: std::ops::RangeBounds<T>,
460 {
461 self.records.range(range)
462 }
463
464 fn remove(&mut self, key: &RrKey) -> Option<RecordSet> {
465 self.records.remove(key)
466 }
467
468 fn replace(&mut self, rrset: RecordSet) -> Option<RecordSet> {
469 self.records.insert(rrset.rrkey(), rrset)
470 }
471}
472
473impl From<Zone> for ZoneAuthority<Zone> {
474 fn from(value: Zone) -> Self {
475 ZoneAuthority::new(value)
476 }
477}
478
479impl Zone {
480 pub fn read_from_file(
499 origin: Name,
500 path: impl AsRef<Path>,
501 zone_type: ZoneType,
502 ) -> io::Result<Self> {
503 let zone_path = path.as_ref();
504 info!("loading zone file: {:?}", zone_path);
505
506 let buf = fs::read_to_string(zone_path).inspect_err(|e| {
507 error!("failed to read {}: {:?}", zone_path.display(), e);
508 })?;
509
510 let (origin, records) = Parser::new(buf, Some(zone_path.to_path_buf()), Some(origin))
511 .parse()
512 .map_err(|e| {
513 error!("failed to parse {}: {:?}", zone_path.display(), e);
514 io::Error::other(e)
515 })?;
516
517 info!(
518 "zone file loaded: {} with {} records",
519 origin,
520 records.len()
521 );
522 debug!("zone: {:#?}", records);
523
524 let records = records.into_values().map(Into::into);
525 let zone = Zone::from_rrsets(origin, records, zone_type);
526 Ok(zone)
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533 use hickory_proto::rr::{RecordType, rdata::A};
534
535 fn create_test_name() -> Name {
536 Name::from_utf8("test.example.com").unwrap()
537 }
538
539 fn create_test_soa() -> Record<rdata::SOA> {
540 let name = create_test_name();
541 let soa = rdata::SOA::new(
542 name.clone(),
543 Name::from_utf8("admin.example.com").unwrap(),
544 1,
545 3600,
546 1800,
547 604800,
548 86400,
549 );
550 Record::from_rdata(name, TimeToLive::from(3600), soa)
551 }
552
553 #[test]
554 fn test_zone_type_conversions() {
555 assert_eq!(
557 hickory_server::authority::ZoneType::from(ZoneType::Primary),
558 hickory_server::authority::ZoneType::Primary
559 );
560 assert_eq!(
561 hickory_server::authority::ZoneType::from(ZoneType::Secondary),
562 hickory_server::authority::ZoneType::Secondary
563 );
564 assert_eq!(
565 hickory_server::authority::ZoneType::from(ZoneType::External),
566 hickory_server::authority::ZoneType::External
567 );
568
569 assert_eq!(
571 ZoneType::from(hickory_server::authority::ZoneType::Primary),
572 ZoneType::Primary
573 );
574 assert_eq!(
575 ZoneType::from(hickory_server::authority::ZoneType::Secondary),
576 ZoneType::Secondary
577 );
578 assert_eq!(
579 ZoneType::from(hickory_server::authority::ZoneType::External),
580 ZoneType::External
581 );
582 }
583
584 #[test]
585 fn test_zone_type_debug() {
586 assert!(format!("{:?}", ZoneType::Primary).contains("Primary"));
587 assert!(format!("{:?}", ZoneType::Secondary).contains("Secondary"));
588 assert!(format!("{:?}", ZoneType::External).contains("External"));
589 }
590
591 #[test]
592 fn test_zone_creation() {
593 let name = create_test_name();
594 let soa = create_test_soa();
595 let zone = Zone::empty(name.clone(), soa, ZoneType::Primary, false);
596
597 assert_eq!(zone.name(), &name);
598 assert_eq!(zone.zone_type(), ZoneType::Primary);
599 assert_eq!(zone.dns_class(), DNSClass::IN);
600 assert!(!zone.allow_axfr());
601 assert_eq!(zone.records().count(), 1); assert!(!zone.is_empty()); }
604
605 #[test]
606 fn test_zone_with_allow_axfr() {
607 let name = create_test_name();
608 let soa = create_test_soa();
609
610 let mut zone = Zone::empty(
611 name.clone(),
612 soa,
613 ZoneType::Primary,
614 true, );
616
617 assert_eq!(zone.name(), &name);
618 assert_eq!(zone.zone_type(), ZoneType::Primary);
619 assert!(zone.allow_axfr());
620
621 zone.set_allow_axfr(false);
623 assert!(!zone.allow_axfr());
624 }
625
626 #[test]
627 fn test_zone_upsert_record() {
628 let name = create_test_name();
629 let soa = create_test_soa();
630 let mut zone = Zone::empty(name.clone(), soa, ZoneType::Primary, false);
631
632 let a_record = Record::from_rdata(name, TimeToLive::from(300), A::new(192, 168, 1, 1))
634 .into_record_rdata();
635
636 let result = zone.upsert(a_record.clone(), SerialNumber::from(1));
637 assert!(result.is_ok());
638 assert!(result.unwrap());
639 assert_eq!(zone.records().count(), 2); }
641
642 #[test]
643 fn test_zone_remove_record() {
644 let name = create_test_name();
645 let soa = create_test_soa();
646 let mut zone = Zone::empty(name.clone(), soa, ZoneType::Primary, false);
647
648 let a_record = Record::from_rdata(name, TimeToLive::from(300), A::new(192, 168, 1, 1))
650 .into_record_rdata();
651
652 zone.upsert(a_record.clone(), SerialNumber::from(1))
653 .unwrap();
654 assert_eq!(zone.records().count(), 2);
655
656 let key = a_record.rrkey();
658 let removed = zone.remove(&key);
659 assert!(removed.is_some());
660 assert_eq!(zone.records().count(), 1); }
662
663 #[test]
664 fn test_zone_get_by_key() {
665 let name = create_test_name();
666 let soa = create_test_soa();
667 let mut zone = Zone::empty(name.clone(), soa, ZoneType::Primary, false);
668
669 let a_record =
671 Record::from_rdata(name.clone(), TimeToLive::from(300), A::new(192, 168, 1, 1))
672 .into_record_rdata();
673
674 zone.upsert(a_record.clone(), SerialNumber::from(1))
675 .unwrap();
676
677 let key = a_record.rrkey();
679 let result = zone.get(&key);
680 assert!(result.is_some());
681
682 let rrset = result.unwrap();
683 assert_eq!(rrset.record_type(), RecordType::A);
684 assert_eq!(rrset.len(), 1);
685 }
686
687 #[test]
688 fn test_zone_soa_access() {
689 let name = create_test_name();
690 let soa = create_test_soa();
691 let zone = Zone::empty(name.clone(), soa, ZoneType::Primary, false);
692
693 let soa_record = zone.soa().unwrap();
695 assert_eq!(soa_record.record_type(), RecordType::SOA);
696
697 let serial = zone.serial();
699 assert_eq!(serial.get(), 1); }
701
702 #[test]
703 fn test_zone_is_empty() {
704 let name = create_test_name();
705 let soa = create_test_soa();
706 let zone = Zone::empty(name, soa, ZoneType::Primary, false);
707
708 assert!(!zone.is_empty());
710 }
711
712 #[test]
713 fn test_zone_iterators() {
714 let name = create_test_name();
715 let soa = create_test_soa();
716 let mut zone = Zone::empty(name.clone(), soa, ZoneType::Primary, false);
717
718 let a_record = Record::from_rdata(name, TimeToLive::from(300), A::new(192, 168, 1, 1))
720 .into_record_rdata();
721
722 zone.upsert(a_record, SerialNumber::from(1)).unwrap();
723
724 assert_eq!(zone.records().count(), 2); }
727
728 #[test]
729 fn test_zone_properties() {
730 let name = create_test_name();
731 let soa = create_test_soa();
732 let zone1 = Zone::empty(name.clone(), soa, ZoneType::Primary, false);
733
734 assert_eq!(zone1.name(), &name);
736 assert_eq!(zone1.zone_type(), ZoneType::Primary);
737 assert_eq!(zone1.records().count(), 1);
738 }
739
740 #[test]
741 fn test_zone_id_uniqueness() {
742 let name = create_test_name();
743 let zone1 = Zone::empty(name.clone(), create_test_soa(), ZoneType::Primary, false);
744 let zone2 = Zone::empty(name, create_test_soa(), ZoneType::Primary, false);
745
746 assert_ne!(zone1.id(), zone2.id());
747 }
748}