1use std::{
11 collections::BTreeMap,
12 fs,
13 marker::PhantomData,
14 ops::{Deref, DerefMut},
15 path::Path,
16 sync::Arc,
17};
18
19#[cfg(feature = "__dnssec")]
20use crate::{
21 dnssec::NxProofKind,
22 net::runtime::Time,
23 proto::dnssec::{
24 DnsSecResult, DnssecSigner,
25 rdata::{DNSKEY, DNSSECRData},
26 },
27 zone_handler::{DnssecZoneHandler, Nsec3QueryInfo},
28};
29use crate::{
30 net::runtime::{RuntimeProvider, TokioRuntimeProvider},
31 proto::{
32 op::ResponseCode,
33 rr::{DNSClass, LowerName, Name, RData, Record, RecordSet, RecordType, RrKey},
34 serialize::txt::Parser,
35 },
36 server::{Request, RequestInfo},
37 zone_handler::{
38 AuthLookup, AxfrPolicy, AxfrRecords, LookupControlFlow, LookupError, LookupOptions,
39 LookupRecords, ZoneHandler, ZoneTransfer, ZoneType,
40 },
41};
42use hickory_proto::rr::TSigResponseContext;
43#[cfg(feature = "__dnssec")]
44use time::OffsetDateTime;
45use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
46#[cfg(feature = "__dnssec")]
47use tracing::warn;
48use tracing::{debug, info};
49
50mod inner;
51use inner::InnerInMemory;
52
53pub struct InMemoryZoneHandler<P = TokioRuntimeProvider> {
58 origin: LowerName,
59 class: DNSClass,
60 zone_type: ZoneType,
61 axfr_policy: AxfrPolicy,
62 inner: RwLock<InnerInMemory>,
63 #[cfg(feature = "__dnssec")]
64 nx_proof_kind: Option<NxProofKind>,
65 _phantom: PhantomData<P>,
66}
67
68impl<P: RuntimeProvider + Send + Sync> InMemoryZoneHandler<P> {
69 pub fn new(
84 origin: Name,
85 records: BTreeMap<RrKey, RecordSet>,
86 zone_type: ZoneType,
87 axfr_policy: AxfrPolicy,
88 #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
89 ) -> Result<Self, String> {
90 let mut this = Self::empty(
91 origin.clone(),
92 zone_type,
93 axfr_policy,
94 #[cfg(feature = "__dnssec")]
95 nx_proof_kind,
96 );
97 let inner = this.inner.get_mut();
98
99 let soa = records
101 .get(&RrKey::new(origin.clone().into(), RecordType::SOA))
102 .and_then(|rrset| match &rrset.records_without_rrsigs().next()?.data {
103 RData::SOA(soa) => Some(soa),
104 _ => None,
105 })
106 .ok_or_else(|| format!("SOA record must be present: {origin}"))?;
107 let serial = soa.serial;
108
109 let iter = records.into_values();
110
111 for rrset in iter {
113 let name = rrset.name().clone();
114 let rr_type = rrset.record_type();
115
116 for record in rrset.records_without_rrsigs() {
117 if !inner.upsert(record.clone(), serial, this.class) {
118 return Err(format!(
119 "Failed to insert {name} {rr_type} to zone: {origin}"
120 ));
121 };
122 }
123 }
124
125 Ok(this)
126 }
127
128 pub fn empty(
134 origin: Name,
135 zone_type: ZoneType,
136 axfr_policy: AxfrPolicy,
137 #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
138 ) -> Self {
139 Self {
140 origin: LowerName::new(&origin),
141 class: DNSClass::IN,
142 zone_type,
143 axfr_policy,
144 inner: RwLock::new(InnerInMemory::default()),
145
146 #[cfg(feature = "__dnssec")]
147 nx_proof_kind,
148
149 _phantom: PhantomData,
150 }
151 }
152
153 pub fn class(&self) -> DNSClass {
155 self.class
156 }
157
158 #[cfg(any(test, feature = "testing"))]
160 pub fn set_axfr_policy(&mut self, policy: AxfrPolicy) {
161 self.axfr_policy = policy;
162 }
163
164 pub fn clear(&mut self) {
166 self.inner.get_mut().records.clear()
167 }
168
169 #[cfg(all(feature = "__dnssec", feature = "testing"))]
171 pub async fn secure_keys(&self) -> impl Deref<Target = [DnssecSigner]> + '_ {
172 RwLockWriteGuard::map(self.inner.write().await, |i| i.secure_keys.as_mut_slice())
173 }
174
175 pub async fn records(&self) -> impl Deref<Target = BTreeMap<RrKey, Arc<RecordSet>>> + '_ {
177 RwLockReadGuard::map(self.inner.read().await, |i| &i.records)
178 }
179
180 pub async fn records_mut(
182 &self,
183 ) -> impl DerefMut<Target = BTreeMap<RrKey, Arc<RecordSet>>> + '_ {
184 RwLockWriteGuard::map(self.inner.write().await, |i| &mut i.records)
185 }
186
187 pub fn records_get_mut(&mut self) -> &mut BTreeMap<RrKey, Arc<RecordSet>> {
189 &mut self.inner.get_mut().records
190 }
191
192 pub async fn minimum_ttl(&self) -> u32 {
194 self.inner.read().await.minimum_ttl(self.origin())
195 }
196
197 pub async fn serial(&self) -> u32 {
199 self.inner.read().await.serial(self.origin())
200 }
201
202 #[cfg(feature = "sqlite")]
203 pub(crate) async fn increment_soa_serial(&self) -> u32 {
204 self.inner
205 .write()
206 .await
207 .increment_soa_serial(self.origin(), self.class)
208 }
209
210 pub async fn upsert(&self, record: Record, serial: u32) -> bool {
223 self.inner.write().await.upsert(record, serial, self.class)
224 }
225
226 pub fn upsert_mut(&mut self, record: Record, serial: u32) -> bool {
228 self.inner.get_mut().upsert(record, serial, self.class)
229 }
230
231 #[cfg(feature = "__dnssec")]
239 fn inner_add_zone_signing_key(
240 inner: &mut InnerInMemory,
241 signer: DnssecSigner,
242 origin: &LowerName,
243 dns_class: DNSClass,
244 ) -> DnsSecResult<()> {
245 let zone_ttl = inner.minimum_ttl(origin);
247 let dnskey = DNSKEY::from_key(&signer.key().to_public_key()?);
248 let dnskey = Record::from_rdata(
249 origin.clone().into(),
250 zone_ttl,
251 RData::DNSSEC(DNSSECRData::DNSKEY(dnskey)),
252 );
253
254 let serial = inner.serial(origin);
256 inner.upsert(dnskey, serial, dns_class);
257 inner.secure_keys.push(signer);
258 Ok(())
259 }
260
261 #[cfg(feature = "__dnssec")]
263 pub fn add_zone_signing_key_mut(&mut self, signer: DnssecSigner) -> DnsSecResult<()> {
264 let Self {
265 origin,
266 inner,
267 class,
268 ..
269 } = self;
270
271 Self::inner_add_zone_signing_key(inner.get_mut(), signer, origin, *class)
272 }
273
274 #[cfg(feature = "__dnssec")]
276 pub fn secure_zone_mut(&mut self) -> DnsSecResult<()> {
277 let Self { origin, inner, .. } = self;
278 inner.get_mut().secure_zone_mut(
279 origin,
280 self.class,
281 self.nx_proof_kind.as_ref(),
282 Self::current_time()?,
283 )
284 }
285
286 #[cfg(not(feature = "__dnssec"))]
288 pub fn secure_zone_mut(&mut self) -> Result<(), &str> {
289 Err("DNSSEC was not enabled during compilation.")
290 }
291
292 #[cfg(feature = "__dnssec")]
293 fn current_time() -> DnsSecResult<OffsetDateTime> {
294 let timestamp_unsigned = P::Timer::current_time();
295 let timestamp_signed = timestamp_unsigned
296 .try_into()
297 .map_err(|_| "current time is out of range")?;
298 OffsetDateTime::from_unix_timestamp(timestamp_signed)
299 .map_err(|_| "current time is out of range".into())
300 }
301}
302
303#[async_trait::async_trait]
304impl<P: RuntimeProvider + Send + Sync> ZoneHandler for InMemoryZoneHandler<P> {
305 fn zone_type(&self) -> ZoneType {
307 self.zone_type
308 }
309
310 fn axfr_policy(&self) -> AxfrPolicy {
312 self.axfr_policy
313 }
314
315 fn origin(&self) -> &LowerName {
317 &self.origin
318 }
319
320 async fn lookup(
336 &self,
337 name: &LowerName,
338 mut query_type: RecordType,
339 _request_info: Option<&RequestInfo<'_>>,
340 lookup_options: LookupOptions,
341 ) -> LookupControlFlow<AuthLookup> {
342 let inner = self.inner.read().await;
343
344 if query_type == RecordType::AXFR {
345 return Break(Err(LookupError::NetError(
346 "AXFR must be handled with ZoneHandler::zone_transfer()".into(),
347 )));
348 }
349
350 if query_type == RecordType::ANY {
351 query_type = inner.replace_any(name);
352 }
353
354 let answer = inner.inner_lookup(name, query_type, lookup_options);
355
356 let (answer, cname_chain) = match answer {
360 Some(a) if a.record_type() == RecordType::CNAME && query_type != RecordType::CNAME => {
361 let chain = inner.chase_cnames(name, a, query_type, lookup_options);
362 let terminal = chain
366 .last()
367 .filter(|rr| rr.record_type() != RecordType::CNAME)
368 .cloned();
369 (terminal, Some(chain))
370 }
371 _ => (answer, None),
372 };
373
374 let additionals_root_chain_type: Option<(_, _)> = answer
378 .as_ref()
379 .and_then(|a| maybe_next_name(a, query_type))
380 .and_then(|(search_name, search_type)| {
381 inner
382 .additional_search(name, query_type, search_name, search_type, lookup_options)
383 .map(|adds| (adds, search_type))
384 });
385
386 let (additionals, answer) = match (additionals_root_chain_type, answer, query_type) {
388 (Some((additionals, RecordType::ANAME)), Some(answer), RecordType::A)
389 | (Some((additionals, RecordType::ANAME)), Some(answer), RecordType::AAAA) => {
390 debug_assert_eq!(answer.record_type(), RecordType::ANAME);
392
393 let (rdatas, a_aaaa_ttl) = {
395 let last_record = additionals.last();
396 let a_aaaa_ttl = last_record.map_or(u32::MAX, |r| r.ttl());
397
398 let rdatas: Option<Vec<RData>> = last_record
400 .and_then(|record| match record.record_type() {
401 RecordType::A | RecordType::AAAA => {
402 Some(record.records_without_rrsigs())
404 }
405 _ => None,
406 })
407 .map(|records| records.map(|r| &r.data).cloned().collect::<Vec<_>>());
408
409 (rdatas, a_aaaa_ttl)
410 };
411
412 let ttl = answer.ttl().min(a_aaaa_ttl);
417 let mut new_answer = RecordSet::new(answer.name().clone(), query_type, ttl);
418
419 for rdata in rdatas.into_iter().flatten() {
420 new_answer.add_rdata(rdata);
421 }
422
423 #[cfg(feature = "__dnssec")]
425 if lookup_options.dnssec_ok {
427 let result = Self::current_time().and_then(|time| {
428 InnerInMemory::sign_rrset(
429 &mut new_answer,
430 &inner.secure_keys,
431 self.class(),
432 time,
433 )
434 });
435 if let Err(error) = result {
436 warn!(%error, "failed to sign ANAME record")
438 }
439 }
440
441 let additionals = std::iter::once(answer).chain(additionals).collect();
443
444 (Some(additionals), Some(Arc::new(new_answer)))
447 }
448 (Some((additionals, _)), answer, _) => (Some(additionals), answer),
449 (None, answer, _) => (None, answer),
450 };
451
452 use LookupControlFlow::*;
458 let answers = match (cname_chain, answer) {
459 (Some(chain), _) => LookupRecords::many(lookup_options, chain),
461 (None, Some(rr_set)) => LookupRecords::new(lookup_options, rr_set),
462 (None, None) => {
463 return Continue(Err(
464 if inner
465 .records
466 .keys()
467 .any(|key| key.name() == name || name.zone_of(key.name()))
468 {
469 LookupError::NameExists
470 } else {
471 LookupError::from(match self.origin().zone_of(name) {
472 true => ResponseCode::NXDomain,
473 false => ResponseCode::Refused,
474 })
475 },
476 ));
477 }
478 };
479
480 Continue(Ok(AuthLookup::answers(
481 answers,
482 additionals.map(|a| LookupRecords::many(lookup_options, a)),
483 )))
484 }
485
486 async fn search(
487 &self,
488 request: &Request,
489 lookup_options: LookupOptions,
490 ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
491 let request_info = match request.request_info() {
492 Ok(info) => info,
493 Err(e) => return (LookupControlFlow::Break(Err(e)), None),
494 };
495 debug!("searching InMemoryZoneHandler for: {}", request_info.query);
496
497 let lookup_name = request_info.query.name();
498 let record_type: RecordType = request_info.query.query_type();
499
500 match record_type {
502 RecordType::AXFR => (
503 LookupControlFlow::Break(Err(LookupError::NetError(
504 "AXFR must be handled with ZoneHandler::zone_transfer()".into(),
505 ))),
506 None,
507 ),
508 _ => (
510 self.lookup(
511 lookup_name,
512 record_type,
513 Some(&request_info),
514 lookup_options,
515 )
516 .await,
517 None,
518 ),
519 }
520 }
521
522 async fn zone_transfer(
523 &self,
524 request: &Request,
525 lookup_options: LookupOptions,
526 _now: u64,
527 ) -> Option<(
528 Result<ZoneTransfer, LookupError>,
529 Option<TSigResponseContext>,
530 )> {
531 let request_info = match request.request_info() {
532 Ok(info) => info,
533 Err(e) => return Some((Err(e), None)),
534 };
535
536 if request_info.query.query_type() == RecordType::AXFR {
537 if !matches!(self.axfr_policy, AxfrPolicy::AllowAll) {
539 return Some((Err(LookupError::from(ResponseCode::Refused)), None));
540 }
541 }
542
543 let future = self.lookup(self.origin(), RecordType::SOA, None, lookup_options);
544 let start_soa = if let LookupControlFlow::Continue(Ok(res)) = future.await {
545 res.unwrap_records()
546 } else {
547 LookupRecords::Empty
548 };
549
550 let future = self.lookup(
551 self.origin(),
552 RecordType::SOA,
553 None,
554 LookupOptions::default(),
555 );
556 let end_soa = if let LookupControlFlow::Continue(Ok(res)) = future.await {
557 res.unwrap_records()
558 } else {
559 LookupRecords::Empty
560 };
561
562 let records = AxfrRecords::new(
563 lookup_options.dnssec_ok,
564 self.inner.read().await.records.values().cloned().collect(),
565 );
566
567 Some((
568 Ok(ZoneTransfer {
569 start_soa,
570 records,
571 end_soa,
572 }),
573 None,
574 ))
575 }
576
577 #[cfg(feature = "__dnssec")]
586 async fn nsec_records(
587 &self,
588 name: &LowerName,
589 lookup_options: LookupOptions,
590 ) -> LookupControlFlow<AuthLookup> {
591 let inner = self.inner.read().await;
592
593 let rr_key = RrKey::new(name.clone(), RecordType::NSEC);
595 let no_data = inner
596 .records
597 .get(&rr_key)
598 .map(|rr_set| LookupRecords::new(lookup_options, rr_set.clone()));
599
600 if let Some(no_data) = no_data {
601 return LookupControlFlow::Continue(Ok(no_data.into()));
602 }
603
604 let closest_proof = inner.closest_nsec(name);
605
606 let wildcard = name.base_name();
608 let origin = self.origin();
609 let wildcard = if origin.zone_of(&wildcard) {
610 wildcard
611 } else {
612 origin.clone()
613 };
614
615 let wildcard_proof = if wildcard != *name {
617 inner.closest_nsec(&wildcard)
618 } else {
619 None
620 };
621
622 let proofs = match (closest_proof, wildcard_proof) {
623 (Some(closest_proof), Some(wildcard_proof)) => {
624 if wildcard_proof != closest_proof {
626 vec![wildcard_proof, closest_proof]
627 } else {
628 vec![closest_proof]
629 }
630 }
631 (None, Some(proof)) | (Some(proof), None) => vec![proof],
632 (None, None) => vec![],
633 };
634
635 LookupControlFlow::Continue(Ok(LookupRecords::many(lookup_options, proofs).into()))
636 }
637
638 #[cfg(not(feature = "__dnssec"))]
639 async fn nsec_records(
640 &self,
641 _name: &LowerName,
642 _lookup_options: LookupOptions,
643 ) -> LookupControlFlow<AuthLookup> {
644 LookupControlFlow::Continue(Ok(AuthLookup::default()))
645 }
646
647 #[cfg(feature = "__dnssec")]
648 async fn nsec3_records(
649 &self,
650 info: Nsec3QueryInfo<'_>,
651 lookup_options: LookupOptions,
652 ) -> LookupControlFlow<AuthLookup> {
653 let inner = self.inner.read().await;
654 LookupControlFlow::Continue(
655 inner
656 .proof(info, self.origin())
657 .map(|proof| LookupRecords::many(lookup_options, proof).into()),
658 )
659 }
660
661 #[cfg(feature = "__dnssec")]
662 fn nx_proof_kind(&self) -> Option<&NxProofKind> {
663 self.nx_proof_kind.as_ref()
664 }
665
666 #[cfg(feature = "metrics")]
667 fn metrics_label(&self) -> &'static str {
668 "in-memory"
669 }
670}
671
672#[cfg(feature = "__dnssec")]
673#[async_trait::async_trait]
674impl<P: RuntimeProvider + Send + Sync> DnssecZoneHandler for InMemoryZoneHandler<P> {
675 async fn add_zone_signing_key(&self, signer: DnssecSigner) -> DnsSecResult<()> {
681 let mut inner = self.inner.write().await;
682
683 Self::inner_add_zone_signing_key(&mut inner, signer, self.origin(), self.class)
684 }
685
686 async fn secure_zone(&self) -> DnsSecResult<()> {
688 let mut inner = self.inner.write().await;
689
690 inner.secure_zone_mut(
691 self.origin(),
692 self.class,
693 self.nx_proof_kind.as_ref(),
694 Self::current_time()?,
695 )
696 }
697}
698
699fn maybe_next_name(
701 record_set: &RecordSet,
702 query_type: RecordType,
703) -> Option<(LowerName, RecordType)> {
704 let t = match (record_set.record_type(), query_type) {
705 (t @ RecordType::ANAME, RecordType::A)
709 | (t @ RecordType::ANAME, RecordType::AAAA)
710 | (t @ RecordType::ANAME, RecordType::ANAME) => t,
711 (t @ RecordType::NS, RecordType::NS) => t,
712 (t @ RecordType::MX, RecordType::MX) => t,
713 (t @ RecordType::SRV, RecordType::SRV) => t,
714 _ => return None,
716 };
717
718 let name = match (&record_set.records_without_rrsigs().next()?.data, t) {
719 (RData::ANAME(name), RecordType::ANAME) => name,
720 (RData::NS(ns), RecordType::NS) => &ns.0,
721 (RData::MX(mx), RecordType::MX) => &mx.exchange,
722 (RData::SRV(srv), RecordType::SRV) => &srv.target,
723 _ => return None,
724 };
725
726 Some((LowerName::from(name), t))
727}
728
729pub(crate) fn zone_from_path(
731 zone_path: &Path,
732 origin: Name,
733) -> Result<BTreeMap<RrKey, RecordSet>, String> {
734 info!("loading zone file: {zone_path:?}");
735
736 let buf = fs::read_to_string(zone_path)
739 .map_err(|e| format!("failed to read {}: {e:?}", zone_path.display()))?;
740
741 let (origin, records) = Parser::new(buf, Some(zone_path.to_owned()), Some(origin))
742 .parse()
743 .map_err(|e| format!("failed to parse {}: {e:?}", zone_path.display()))?;
744
745 info!("zone file loaded: {origin} with {} records", records.len());
746 debug!("zone: {records:#?}");
747 Ok(records)
748}