hickory_server/store/sqlite/mod.rs
1// Copyright 2015-2018 Benjamin Fry <benjaminfry -@- me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! SQLite serving with Dynamic DNS and journaling support
9
10#[cfg(feature = "__dnssec")]
11use std::fs;
12use std::marker::PhantomData;
13#[cfg(feature = "__dnssec")]
14use std::str::FromStr;
15use std::{
16 ops::{Deref, DerefMut},
17 path::{Path, PathBuf},
18 sync::Arc,
19};
20
21use futures_util::lock::Mutex;
22use serde::Deserialize;
23use tracing::{debug, error, info, warn};
24
25#[cfg(feature = "metrics")]
26use crate::metrics::PersistentStoreMetrics;
27#[cfg(feature = "__dnssec")]
28use crate::proto::rr::{
29 TSigner,
30 rdata::tsig::{TSIG, TsigAlgorithm, TsigError},
31};
32#[cfg(feature = "__dnssec")]
33use crate::{
34 dnssec::NxProofKind,
35 proto::dnssec::{DnsSecResult, DnssecSigner},
36 zone_handler::{DnssecZoneHandler, Nsec3QueryInfo, UpdateRequest},
37};
38use crate::{
39 net::runtime::{RuntimeProvider, TokioRuntimeProvider},
40 proto::{
41 op::ResponseCode,
42 rr::{
43 DNSClass, LowerName, Name, RData, Record, RecordSet, RecordType, RrKey,
44 TSigResponseContext,
45 },
46 },
47 server::{Request, RequestInfo},
48 store::in_memory::{InMemoryZoneHandler, zone_from_path},
49 store::rooted,
50 zone_handler::{
51 AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler,
52 ZoneTransfer, ZoneType,
53 },
54};
55
56pub mod persistence;
57pub use persistence::{Journal, PersistenceError};
58
59/// SqliteZoneHandler is responsible for storing the resource records for a particular zone.
60///
61/// Zone handlers default to DNSClass IN. The ZoneType specifies if this should be treated as the
62/// start of authority for the zone, is a Secondary, or a cached zone.
63#[allow(dead_code)]
64pub struct SqliteZoneHandler<P = TokioRuntimeProvider> {
65 in_memory: InMemoryZoneHandler<P>,
66 journal: Mutex<Option<Journal>>,
67 axfr_policy: AxfrPolicy,
68 allow_update: bool,
69 is_dnssec_enabled: bool,
70 #[cfg(feature = "metrics")]
71 metrics: PersistentStoreMetrics,
72 #[cfg(feature = "__dnssec")]
73 tsig_signers: Vec<TSigner>,
74 _phantom: PhantomData<P>,
75}
76
77impl<P: RuntimeProvider + Send + Sync> SqliteZoneHandler<P> {
78 /// Creates a new ZoneHandler.
79 ///
80 /// # Arguments
81 ///
82 /// * `in_memory` - InMemoryZoneHandler for all records.
83 /// * `axfr_policy` - A policy for determining if AXFR requests are allowed.
84 /// * `allow_update` - If true, then this zone accepts dynamic updates.
85 /// * `is_dnssec_enabled` - If true, then the zone will sign the zone with all registered keys,
86 /// (see `add_zone_signing_key()`)
87 ///
88 /// # Return value
89 ///
90 /// The new `ZoneHandler`.
91 pub fn new(
92 in_memory: InMemoryZoneHandler<P>,
93 axfr_policy: AxfrPolicy,
94 allow_update: bool,
95 is_dnssec_enabled: bool,
96 ) -> Self {
97 Self {
98 in_memory,
99 journal: Mutex::new(None),
100 axfr_policy,
101 allow_update,
102 is_dnssec_enabled,
103 #[cfg(feature = "metrics")]
104 metrics: PersistentStoreMetrics::new("sqlite"),
105 #[cfg(feature = "__dnssec")]
106 tsig_signers: Vec::new(),
107 _phantom: PhantomData,
108 }
109 }
110
111 /// load the zone handler from the configuration
112 pub async fn try_from_config(
113 origin: Name,
114 zone_type: ZoneType,
115 axfr_policy: AxfrPolicy,
116 enable_dnssec: bool,
117 root_dir: Option<&Path>,
118 config: &SqliteConfig,
119 #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
120 ) -> Result<Self, String> {
121 let zone_name = origin;
122
123 // to be compatible with previous versions, the extension might be zone, not jrnl
124 let zone_path = rooted(&config.zone_path, root_dir);
125 let journal_path = rooted(&config.journal_path, root_dir);
126
127 #[cfg_attr(not(feature = "__dnssec"), allow(unused_mut))]
128 let mut handler = if journal_path.exists() {
129 // load the zone
130 info!("recovering zone from journal: {journal_path:?}",);
131 let journal = Journal::from_file(&journal_path)
132 .map_err(|e| format!("error opening journal: {journal_path:?}: {e}"))?;
133
134 let in_memory = InMemoryZoneHandler::empty(
135 zone_name.clone(),
136 zone_type,
137 AxfrPolicy::AllowAll, // We apply our own AXFR policy before invoking the InMemoryZoneHandler.
138 #[cfg(feature = "__dnssec")]
139 nx_proof_kind,
140 );
141 let mut handler = Self::new(in_memory, axfr_policy, config.allow_update, enable_dnssec);
142
143 handler
144 .recover_with_journal(&journal)
145 .await
146 .map_err(|e| format!("error recovering from journal: {e}"))?;
147
148 handler.set_journal(journal).await;
149 info!("recovered zone: {zone_name}");
150
151 handler
152 } else if zone_path.exists() {
153 // TODO: deprecate this portion of loading, instantiate the journal through a separate tool
154 info!("loading zone file: {zone_path:?}");
155
156 let records = zone_from_path(&zone_path, zone_name.clone())
157 .map_err(|e| format!("failed to load zone file: {e}"))?;
158
159 let in_memory = InMemoryZoneHandler::new(
160 zone_name.clone(),
161 records,
162 zone_type,
163 AxfrPolicy::AllowAll, // We apply our own AXFR policy before invoking the InMemoryZoneHandler.
164 #[cfg(feature = "__dnssec")]
165 nx_proof_kind,
166 )?;
167
168 let mut handler = Self::new(in_memory, axfr_policy, config.allow_update, enable_dnssec);
169
170 // if dynamic update is enabled, enable the journal
171 info!("creating new journal: {journal_path:?}");
172 let journal = Journal::from_file(&journal_path)
173 .map_err(|e| format!("error creating journal {journal_path:?}: {e}"))?;
174
175 handler.set_journal(journal).await;
176
177 // preserve to the new journal, i.e. we just loaded the zone from disk, start the journal
178 handler
179 .persist_to_journal()
180 .await
181 .map_err(|e| format!("error persisting to journal {journal_path:?}: {e}"))?;
182
183 info!("zone file loaded: {zone_name}");
184 handler
185 } else {
186 return Err(format!("no zone file or journal defined at: {zone_path:?}"));
187 };
188
189 #[cfg(feature = "__dnssec")]
190 for config in &config.tsig_keys {
191 handler
192 .tsig_signers
193 .push(config.to_signer(&zone_name, root_dir)?);
194 }
195
196 Ok(handler)
197 }
198
199 /// Recovers the zone from a Journal, returns an error on failure to recover the zone.
200 ///
201 /// # Arguments
202 ///
203 /// * `journal` - the journal from which to load the persisted zone.
204 pub async fn recover_with_journal(
205 &mut self,
206 journal: &Journal,
207 ) -> Result<(), PersistenceError> {
208 assert!(
209 self.in_memory.records_get_mut().is_empty(),
210 "records should be empty during a recovery"
211 );
212
213 info!("recovering from journal");
214 for record in journal.iter() {
215 // AXFR is special, it is used to mark the dump of a full zone.
216 // when recovering, if an AXFR is encountered, we should remove all the records in the
217 // zone.
218 if record.record_type() == RecordType::AXFR {
219 self.in_memory.clear();
220 } else {
221 match self.update_records(&[record], false).await {
222 Ok(_) => {
223 #[cfg(feature = "metrics")]
224 self.metrics.zone_records.increment(1);
225 }
226 Err(error) => return Err(PersistenceError::Recovery(error.to_str())),
227 }
228 }
229 }
230
231 Ok(())
232 }
233
234 /// Persist the state of the current zone to the journal, does nothing if there is no associated
235 /// Journal.
236 ///
237 /// Returns an error if there was an issue writing to the persistence layer.
238 pub async fn persist_to_journal(&self) -> Result<(), PersistenceError> {
239 if let Some(journal) = self.journal.lock().await.as_ref() {
240 let serial = self.in_memory.serial().await;
241
242 info!("persisting zone to journal at SOA.serial: {serial}");
243
244 // TODO: THIS NEEDS TO BE IN A TRANSACTION!!!
245 journal.insert_record(
246 serial,
247 &Record::update0(Name::new(), 0, RecordType::AXFR).into_record_of_rdata(),
248 )?;
249
250 for rr_set in self.in_memory.records().await.values() {
251 // TODO: should we preserve rr_sets or not?
252 for record in rr_set.records_without_rrsigs() {
253 journal.insert_record(serial, record)?;
254
255 #[cfg(feature = "metrics")]
256 self.metrics.zone_records.increment(1);
257 }
258 }
259
260 // TODO: COMMIT THE TRANSACTION!!!
261 }
262
263 Ok(())
264 }
265
266 /// Associate a backing Journal with this ZoneHandler for Updatable zones
267 pub async fn set_journal(&mut self, journal: Journal) {
268 *self.journal.lock().await = Some(journal);
269 }
270
271 /// Returns the associated Journal
272 #[cfg(any(test, feature = "testing"))]
273 pub async fn journal(&self) -> impl Deref<Target = Option<Journal>> + '_ {
274 self.journal.lock().await
275 }
276
277 /// Enables the zone for dynamic DNS updates
278 pub fn set_allow_update(&mut self, allow_update: bool) {
279 self.allow_update = allow_update;
280 }
281
282 /// Set the TSIG signers allowed to authenticate updates when `allow_update` is true
283 #[cfg(all(any(test, feature = "testing"), feature = "__dnssec"))]
284 pub fn set_tsig_signers(&mut self, signers: Vec<TSigner>) {
285 self.tsig_signers = signers;
286 }
287
288 /// Set the AXFR policy for testing purposes
289 #[cfg(feature = "testing")]
290 pub fn set_axfr_policy(&mut self, policy: AxfrPolicy) {
291 self.axfr_policy = policy;
292 }
293
294 /// Get serial
295 #[cfg(any(test, feature = "testing"))]
296 pub async fn serial(&self) -> u32 {
297 self.in_memory.serial().await
298 }
299
300 /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
301 ///
302 /// ```text
303 ///
304 /// 3.2 - Process Prerequisite Section
305 ///
306 /// Next, the Prerequisite Section is checked to see that all
307 /// prerequisites are satisfied by the current state of the zone. Using
308 /// the definitions expressed in Section 1.2, if any RR's NAME is not
309 /// within the zone specified in the Zone Section, signal NOTZONE to the
310 /// requestor.
311 ///
312 /// 3.2.1. For RRs in this section whose CLASS is ANY, test to see that
313 /// TTL and RDLENGTH are both zero (0), else signal FORMERR to the
314 /// requestor. If TYPE is ANY, test to see that there is at least one RR
315 /// in the zone whose NAME is the same as that of the Prerequisite RR,
316 /// else signal NXDOMAIN to the requestor. If TYPE is not ANY, test to
317 /// see that there is at least one RR in the zone whose NAME and TYPE are
318 /// the same as that of the Prerequisite RR, else signal NXRRSET to the
319 /// requestor.
320 ///
321 /// 3.2.2. For RRs in this section whose CLASS is NONE, test to see that
322 /// the TTL and RDLENGTH are both zero (0), else signal FORMERR to the
323 /// requestor. If the TYPE is ANY, test to see that there are no RRs in
324 /// the zone whose NAME is the same as that of the Prerequisite RR, else
325 /// signal YXDOMAIN to the requestor. If the TYPE is not ANY, test to
326 /// see that there are no RRs in the zone whose NAME and TYPE are the
327 /// same as that of the Prerequisite RR, else signal YXRRSET to the
328 /// requestor.
329 ///
330 /// 3.2.3. For RRs in this section whose CLASS is the same as the ZCLASS,
331 /// test to see that the TTL is zero (0), else signal FORMERR to the
332 /// requestor. Then, build an RRset for each unique <NAME,TYPE> and
333 /// compare each resulting RRset for set equality (same members, no more,
334 /// no less) with RRsets in the zone. If any Prerequisite RRset is not
335 /// entirely and exactly matched by a zone RRset, signal NXRRSET to the
336 /// requestor. If any RR in this section has a CLASS other than ZCLASS
337 /// or NONE or ANY, signal FORMERR to the requestor.
338 ///
339 /// 3.2.4 - Table Of Metavalues Used In Prerequisite Section
340 ///
341 /// CLASS TYPE RDATA Meaning
342 /// ------------------------------------------------------------
343 /// ANY ANY empty Name is in use
344 /// ANY rrset empty RRset exists (value independent)
345 /// NONE ANY empty Name is not in use
346 /// NONE rrset empty RRset does not exist
347 /// zone rrset rr RRset exists (value dependent)
348 /// ```
349 pub async fn verify_prerequisites(
350 &self,
351 pre_requisites: &[Record],
352 ) -> Result<(), ResponseCode> {
353 // 3.2.5 - Pseudocode for Prerequisite Section Processing
354 //
355 // for rr in prerequisites
356 // if (rr.ttl != 0)
357 // return (FORMERR)
358 // if (zone_of(rr.name) != ZNAME)
359 // return (NOTZONE);
360 // if (rr.class == ANY)
361 // if (rr.rdlength != 0)
362 // return (FORMERR)
363 // if (rr.type == ANY)
364 // if (!zone_name<rr.name>)
365 // return (NXDOMAIN)
366 // else
367 // if (!zone_rrset<rr.name, rr.type>)
368 // return (NXRRSET)
369 // if (rr.class == NONE)
370 // if (rr.rdlength != 0)
371 // return (FORMERR)
372 // if (rr.type == ANY)
373 // if (zone_name<rr.name>)
374 // return (YXDOMAIN)
375 // else
376 // if (zone_rrset<rr.name, rr.type>)
377 // return (YXRRSET)
378 // if (rr.class == zclass)
379 // temp<rr.name, rr.type> += rr
380 // else
381 // return (FORMERR)
382 //
383 // for rrset in temp
384 // if (zone_rrset<rrset.name, rrset.type> != rrset)
385 // return (NXRRSET)
386 for require in pre_requisites {
387 let required_name = LowerName::from(&require.name);
388
389 if require.ttl != 0 {
390 warn!("ttl must be 0 for: {require:?}");
391 return Err(ResponseCode::FormErr);
392 }
393
394 let origin = self.origin();
395 if !origin.zone_of(&(&require.name).into()) {
396 warn!("{} is not a zone_of {origin}", require.name);
397 return Err(ResponseCode::NotZone);
398 }
399
400 match require.dns_class {
401 DNSClass::ANY => {
402 if let RData::Update0(_) | RData::NULL(..) = require.data {
403 match require.record_type() {
404 // ANY ANY empty Name is in use
405 RecordType::ANY => {
406 if self
407 .lookup(
408 &required_name,
409 RecordType::ANY,
410 None,
411 LookupOptions::default(),
412 )
413 .await
414 .unwrap_or_default()
415 .was_empty()
416 {
417 return Err(ResponseCode::NXDomain);
418 } else {
419 continue;
420 }
421 }
422 // ANY rrset empty RRset exists (value independent)
423 rrset => {
424 if self
425 .lookup(&required_name, rrset, None, LookupOptions::default())
426 .await
427 .unwrap_or_default()
428 .was_empty()
429 {
430 return Err(ResponseCode::NXRRSet);
431 } else {
432 continue;
433 }
434 }
435 }
436 } else {
437 return Err(ResponseCode::FormErr);
438 }
439 }
440 DNSClass::NONE => {
441 if let RData::Update0(_) | RData::NULL(..) = require.data {
442 match require.record_type() {
443 // NONE ANY empty Name is not in use
444 RecordType::ANY => {
445 if !self
446 .lookup(
447 &required_name,
448 RecordType::ANY,
449 None,
450 LookupOptions::default(),
451 )
452 .await
453 .unwrap_or_default()
454 .was_empty()
455 {
456 return Err(ResponseCode::YXDomain);
457 } else {
458 continue;
459 }
460 }
461 // NONE rrset empty RRset does not exist
462 rrset => {
463 if !self
464 .lookup(&required_name, rrset, None, LookupOptions::default())
465 .await
466 .unwrap_or_default()
467 .was_empty()
468 {
469 return Err(ResponseCode::YXRRSet);
470 } else {
471 continue;
472 }
473 }
474 }
475 } else {
476 return Err(ResponseCode::FormErr);
477 }
478 }
479 class if class == self.in_memory.class() =>
480 // zone rrset rr RRset exists (value dependent)
481 {
482 if !self
483 .lookup(
484 &required_name,
485 require.record_type(),
486 None,
487 LookupOptions::default(),
488 )
489 .await
490 .unwrap_or_default()
491 .iter()
492 .any(|rr| rr == require)
493 {
494 return Err(ResponseCode::NXRRSet);
495 } else {
496 continue;
497 }
498 }
499 _ => return Err(ResponseCode::FormErr),
500 }
501 }
502
503 // if we didn't bail everything checked out...
504 Ok(())
505 }
506
507 /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
508 ///
509 /// ```text
510 ///
511 /// 3.3 - Check Requestor's Permissions
512 ///
513 /// 3.3.1. Next, the requestor's permission to update the RRs named in
514 /// the Update Section may be tested in an implementation dependent
515 /// fashion or using mechanisms specified in a subsequent Secure DNS
516 /// Update protocol. If the requestor does not have permission to
517 /// perform these updates, the server may write a warning message in its
518 /// operations log, and may either signal REFUSED to the requestor, or
519 /// ignore the permission problem and proceed with the update.
520 ///
521 /// 3.3.2. While the exact processing is implementation defined, if these
522 /// verification activities are to be performed, this is the point in the
523 /// server's processing where such performance should take place, since
524 /// if a REFUSED condition is encountered after an update has been
525 /// partially applied, it will be necessary to undo the partial update
526 /// and restore the zone to its original state before answering the
527 /// requestor.
528 /// ```
529 ///
530 #[cfg(feature = "__dnssec")]
531 pub async fn authorize_update(
532 &self,
533 request: &Request,
534 now: u64,
535 ) -> (Result<(), ResponseCode>, Option<TSigResponseContext>) {
536 // 3.3.3 - Pseudocode for Permission Checking
537 //
538 // if (security policy exists)
539 // if (this update is not permitted)
540 // if (local option)
541 // log a message about permission problem
542 // if (local option)
543 // return (REFUSED)
544
545 // does this zone handler allow_updates?
546 if !self.allow_update {
547 warn!(
548 "update attempted on non-updatable ZoneHandler: {}",
549 self.origin()
550 );
551 return (Err(ResponseCode::Refused), None);
552 }
553
554 match request.signature() {
555 Some(tsig) => {
556 let (resp, signer) = self.authorized_tsig(tsig, request, now).await;
557 (resp, Some(signer))
558 }
559 None => (Err(ResponseCode::Refused), None),
560 }
561 }
562
563 /// Checks that an AXFR `Request` has a valid signature, or returns an error
564 async fn authorize_axfr(
565 &self,
566 _request: &Request,
567 _now: u64,
568 ) -> (Result<(), ResponseCode>, Option<TSigResponseContext>) {
569 match self.axfr_policy {
570 // Deny without checking any signatures.
571 AxfrPolicy::Deny => (Err(ResponseCode::Refused), None),
572 // Allow without checking any signatures.
573 AxfrPolicy::AllowAll => (Ok(()), None),
574 // Allow only if a valid signature is present.
575 #[cfg(feature = "__dnssec")]
576 AxfrPolicy::AllowSigned => match _request.signature() {
577 Some(tsig) => {
578 let (resp, signer) = self.authorized_tsig(tsig, _request, _now).await;
579 (resp, Some(signer))
580 }
581 None => {
582 warn!("AXFR request was not signed");
583 (Err(ResponseCode::Refused), None)
584 }
585 },
586 }
587 }
588
589 /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
590 ///
591 /// ```text
592 ///
593 /// 3.4 - Process Update Section
594 ///
595 /// Next, the Update Section is processed as follows.
596 ///
597 /// 3.4.1 - Prescan
598 ///
599 /// The Update Section is parsed into RRs and each RR's CLASS is checked
600 /// to see if it is ANY, NONE, or the same as the Zone Class, else signal
601 /// a FORMERR to the requestor. Using the definitions in Section 1.2,
602 /// each RR's NAME must be in the zone specified by the Zone Section,
603 /// else signal NOTZONE to the requestor.
604 ///
605 /// 3.4.1.2. For RRs whose CLASS is not ANY, check the TYPE and if it is
606 /// ANY, AXFR, MAILA, MAILB, or any other QUERY metatype, or any
607 /// unrecognized type, then signal FORMERR to the requestor. For RRs
608 /// whose CLASS is ANY or NONE, check the TTL to see that it is zero (0),
609 /// else signal a FORMERR to the requestor. For any RR whose CLASS is
610 /// ANY, check the RDLENGTH to make sure that it is zero (0) (that is,
611 /// the RDATA field is empty), and that the TYPE is not AXFR, MAILA,
612 /// MAILB, or any other QUERY metatype besides ANY, or any unrecognized
613 /// type, else signal FORMERR to the requestor.
614 /// ```
615 pub async fn pre_scan(&self, records: &[Record]) -> Result<(), ResponseCode> {
616 // 3.4.1.3 - Pseudocode For Update Section Prescan
617 //
618 // [rr] for rr in updates
619 // if (zone_of(rr.name) != ZNAME)
620 // return (NOTZONE);
621 // if (rr.class == zclass)
622 // if (rr.type & ANY|AXFR|MAILA|MAILB)
623 // return (FORMERR)
624 // elsif (rr.class == ANY)
625 // if (rr.ttl != 0 || rr.rdlength != 0
626 // || rr.type & AXFR|MAILA|MAILB)
627 // return (FORMERR)
628 // elsif (rr.class == NONE)
629 // if (rr.ttl != 0 || rr.type & ANY|AXFR|MAILA|MAILB)
630 // return (FORMERR)
631 // else
632 // return (FORMERR)
633 for rr in records {
634 if !self.origin().zone_of(&(&rr.name).into()) {
635 return Err(ResponseCode::NotZone);
636 }
637
638 let class: DNSClass = rr.dns_class;
639 if class == self.in_memory.class() {
640 match rr.record_type() {
641 RecordType::ANY | RecordType::AXFR | RecordType::IXFR => {
642 return Err(ResponseCode::FormErr);
643 }
644 _ => (),
645 }
646 } else {
647 match class {
648 DNSClass::ANY => {
649 if rr.ttl != 0 {
650 return Err(ResponseCode::FormErr);
651 }
652
653 match rr.data {
654 RData::Update0(_) | RData::NULL(..) => {}
655 _ => return Err(ResponseCode::FormErr),
656 }
657
658 match rr.record_type() {
659 RecordType::AXFR | RecordType::IXFR => {
660 return Err(ResponseCode::FormErr);
661 }
662 _ => (),
663 }
664 }
665 DNSClass::NONE => {
666 if rr.ttl != 0 {
667 return Err(ResponseCode::FormErr);
668 }
669 match rr.record_type() {
670 RecordType::ANY | RecordType::AXFR | RecordType::IXFR => {
671 return Err(ResponseCode::FormErr);
672 }
673 _ => (),
674 }
675 }
676 _ => return Err(ResponseCode::FormErr),
677 }
678 }
679 }
680
681 Ok(())
682 }
683
684 /// Updates the specified records according to the update section.
685 ///
686 /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
687 ///
688 /// ```text
689 ///
690 /// 3.4.2.6 - Table Of Metavalues Used In Update Section
691 ///
692 /// CLASS TYPE RDATA Meaning
693 /// ---------------------------------------------------------
694 /// ANY ANY empty Delete all RRsets from a name
695 /// ANY rrset empty Delete an RRset
696 /// NONE rrset rr Delete an RR from an RRset
697 /// zone rrset rr Add to an RRset
698 /// ```
699 ///
700 /// # Arguments
701 ///
702 /// * `records` - set of record instructions for update following above rules
703 /// * `auto_signing_and_increment` - if true, the zone will sign and increment the SOA, this
704 /// should be disabled during recovery.
705 pub async fn update_records(
706 &self,
707 records: &[Record],
708 auto_signing_and_increment: bool,
709 ) -> Result<bool, ResponseCode> {
710 let mut updated = false;
711 let serial: u32 = self.in_memory.serial().await;
712
713 // the persistence act as a write-ahead log. The WAL will also be used for recovery of a zone
714 // subsequent to a failure of the server.
715 if let Some(journal) = &*self.journal.lock().await {
716 if let Err(error) = journal.insert_records(serial, records) {
717 error!("could not persist update records: {error}");
718 return Err(ResponseCode::ServFail);
719 }
720 }
721
722 // 3.4.2.7 - Pseudocode For Update Section Processing
723 //
724 // [rr] for rr in updates
725 // if (rr.class == zclass)
726 // if (rr.type == CNAME)
727 // if (zone_rrset<rr.name, ~CNAME>)
728 // next [rr]
729 // elsif (zone_rrset<rr.name, CNAME>)
730 // next [rr]
731 // if (rr.type == SOA)
732 // if (!zone_rrset<rr.name, SOA> ||
733 // zone_rr<rr.name, SOA>.serial > rr.soa.serial)
734 // next [rr]
735 // for zrr in zone_rrset<rr.name, rr.type>
736 // if (rr.type == CNAME || rr.type == SOA ||
737 // (rr.type == WKS && rr.proto == zrr.proto &&
738 // rr.address == zrr.address) ||
739 // rr.rdata == zrr.rdata)
740 // zrr = rr
741 // next [rr]
742 // zone_rrset<rr.name, rr.type> += rr
743 // elsif (rr.class == ANY)
744 // if (rr.type == ANY)
745 // if (rr.name == zname)
746 // zone_rrset<rr.name, ~(SOA|NS)> = Nil
747 // else
748 // zone_rrset<rr.name, *> = Nil
749 // elsif (rr.name == zname &&
750 // (rr.type == SOA || rr.type == NS))
751 // next [rr]
752 // else
753 // zone_rrset<rr.name, rr.type> = Nil
754 // elsif (rr.class == NONE)
755 // if (rr.type == SOA)
756 // next [rr]
757 // if (rr.type == NS && zone_rrset<rr.name, NS> == rr)
758 // next [rr]
759 // zone_rr<rr.name, rr.type, rr.data> = Nil
760 // return (NOERROR)
761 for rr in records {
762 let rr_name = LowerName::from(&rr.name);
763 let rr_key = RrKey::new(rr_name.clone(), rr.record_type());
764
765 match rr.dns_class {
766 class if class == self.in_memory.class() => {
767 // RFC 2136 - 3.4.2.2. Any Update RR whose CLASS is the same as ZCLASS is added to
768 // the zone. In case of duplicate RDATAs (which for SOA RRs is always
769 // the case, and for WKS RRs is the case if the ADDRESS and PROTOCOL
770 // fields both match), the Zone RR is replaced by Update RR. If the
771 // TYPE is SOA and there is no Zone SOA RR, or the new SOA.SERIAL is
772 // lower (according to [RFC1982]) than or equal to the current Zone SOA
773 // RR's SOA.SERIAL, the Update RR is ignored. In the case of a CNAME
774 // Update RR and a non-CNAME Zone RRset or vice versa, ignore the CNAME
775 // Update RR, otherwise replace the CNAME Zone RR with the CNAME Update
776 // RR.
777
778 // zone rrset rr Add to an RRset
779 info!("upserting record: {rr:?}");
780 let upserted = self.in_memory.upsert(rr.clone(), serial).await;
781
782 #[cfg(all(feature = "metrics", feature = "__dnssec"))]
783 if auto_signing_and_increment {
784 if upserted {
785 self.metrics.added();
786 } else {
787 self.metrics.updated();
788 }
789 }
790
791 updated = upserted || updated
792 }
793 DNSClass::ANY => {
794 // This is a delete of entire RRSETs, either many or one. In either case, the spec is clear:
795 match rr.record_type() {
796 t @ RecordType::SOA | t @ RecordType::NS if rr_name == *self.origin() => {
797 // SOA and NS records are not to be deleted if they are the origin records
798 info!("skipping delete of {t:?} see RFC 2136 - 3.4.2.3");
799 continue;
800 }
801 RecordType::ANY => {
802 // RFC 2136 - 3.4.2.3. For any Update RR whose CLASS is ANY and whose TYPE is ANY,
803 // all Zone RRs with the same NAME are deleted, unless the NAME is the
804 // same as ZNAME in which case only those RRs whose TYPE is other than
805 // SOA or NS are deleted.
806
807 // ANY ANY empty Delete all RRsets from a name
808 info!(
809 "deleting all records at name (not SOA or NS at origin): {rr_name:?}"
810 );
811 let origin = self.origin();
812
813 let mut records = self.in_memory.records_mut().await;
814 let old_size = records.len();
815 records.retain(|k, _| {
816 k.name != rr_name
817 || ((k.record_type == RecordType::SOA
818 || k.record_type == RecordType::NS)
819 && k.name != *origin)
820 });
821 let new_size = records.len();
822 drop(records);
823
824 if new_size < old_size {
825 updated = true;
826 }
827
828 #[cfg(all(feature = "metrics", feature = "__dnssec"))]
829 for _ in 0..old_size - new_size {
830 if auto_signing_and_increment {
831 self.metrics.deleted()
832 }
833 }
834 }
835 _ => {
836 // RFC 2136 - 3.4.2.3. For any Update RR whose CLASS is ANY and
837 // whose TYPE is not ANY all Zone RRs with the same NAME and TYPE are
838 // deleted, unless the NAME is the same as ZNAME in which case neither
839 // SOA or NS RRs will be deleted.
840
841 // ANY rrset empty Delete an RRset
842 if let RData::Update0(_) | RData::NULL(..) = rr.data {
843 let deleted = self.in_memory.records_mut().await.remove(&rr_key);
844 info!("deleted rrset: {deleted:?}");
845 updated = updated || deleted.is_some();
846
847 #[cfg(all(feature = "metrics", feature = "__dnssec"))]
848 if auto_signing_and_increment {
849 self.metrics.deleted()
850 }
851 } else {
852 info!("expected empty rdata: {rr:?}");
853 return Err(ResponseCode::FormErr);
854 }
855 }
856 }
857 }
858 DNSClass::NONE => {
859 info!("deleting specific record: {rr:?}");
860 // NONE rrset rr Delete an RR from an RRset
861 if let Some(rrset) = self.in_memory.records_mut().await.get_mut(&rr_key) {
862 // b/c this is an Arc, we need to clone, then remove, and replace the node.
863 let mut rrset_clone: RecordSet = RecordSet::clone(&*rrset);
864 let deleted = rrset_clone.remove(rr, serial);
865 info!("deleted ({deleted}) specific record: {rr:?}");
866 updated = updated || deleted;
867
868 if deleted {
869 *rrset = Arc::new(rrset_clone);
870 }
871
872 #[cfg(all(feature = "metrics", feature = "__dnssec"))]
873 if auto_signing_and_increment {
874 self.metrics.deleted()
875 }
876 }
877 }
878 class => {
879 info!("unexpected DNS Class: {:?}", class);
880 return Err(ResponseCode::FormErr);
881 }
882 }
883 }
884
885 if !(updated && auto_signing_and_increment) {
886 return Ok(false);
887 }
888
889 let new_serial = if self.is_dnssec_enabled {
890 cfg_if::cfg_if! {
891 if #[cfg(feature = "__dnssec")] {
892 self.secure_zone().await.map_err(|error| {
893 error!(%error, "failure securing zone");
894 ResponseCode::ServFail
895 })?;
896 self.in_memory.serial().await
897 } else {
898 error!("failure securing zone, dnssec feature not enabled");
899 return Err(ResponseCode::ServFail)
900 }
901 }
902 } else {
903 // the secure_zone() function increments the SOA during it's operation, if we're not
904 // dnssec, then we need to do it here...
905 self.in_memory.increment_soa_serial().await
906 };
907
908 // Persist the post-update SOA record (including the incremented serial) so journal
909 // replay reconstructs the monotonic SOA serial across restarts.
910 //
911 // Note: `recover_with_journal()` replays with `auto_signing_and_increment = false`,
912 // so without journaling the updated SOA record, the in-memory serial bump would be
913 // lost after restart even though the updated RRsets are recovered.
914 let records = self.in_memory.records().await;
915 let Some(soa_record) = records
916 .get(&RrKey::new(self.origin().clone(), RecordType::SOA))
917 .and_then(|rrset| rrset.records_without_rrsigs().next())
918 else {
919 error!(origin = %self.origin(), "SOA record missing after serial increment");
920 return Err(ResponseCode::ServFail);
921 };
922
923 let journal_guard = self.journal.lock().await;
924 let Some(journal) = journal_guard.as_ref() else {
925 return Ok(updated);
926 };
927
928 if let Err(error) = journal.insert_record(new_serial, soa_record) {
929 error!("could not persist updated SOA record: {error}");
930 return Err(ResponseCode::ServFail);
931 }
932
933 Ok(true)
934 }
935
936 #[cfg(feature = "__dnssec")]
937 async fn authorized_tsig(
938 &self,
939 tsig: &Record<TSIG>,
940 request: &Request,
941 now: u64,
942 ) -> (Result<(), ResponseCode>, TSigResponseContext) {
943 let req_id = request.id();
944
945 debug!("authorizing with: {tsig:?}");
946 // RFC 8945 Section 5.5: "To prevent cross-algorithm attacks, there SHOULD only be
947 // one algorithm associated with any given key name." We rely on this and only check
948 // the key name when filtering TSIG keys.
949 let Some(tsigner) = self
950 .tsig_signers
951 .iter()
952 .find(|tsigner| tsigner.signer_name() == &tsig.name)
953 else {
954 warn!("no TSIG key name matched: id {req_id}");
955 return (
956 Err(ResponseCode::NotAuth),
957 TSigResponseContext::unknown_key(req_id, now, tsig.name.clone()),
958 );
959 };
960
961 let Ok((_, _, range)) = tsigner.verify_message_byte(request.as_slice(), None, true) else {
962 warn!("invalid TSIG signature: id {req_id}");
963 return (
964 Err(ResponseCode::NotAuth),
965 TSigResponseContext::bad_signature(req_id, now, tsigner.clone()),
966 );
967 };
968
969 let mut error = None;
970 let mut response = Ok(());
971
972 if !range.contains(&now) {
973 warn!("expired TSIG signature: id {req_id}");
974 // "A response indicating a BADTIME error MUST be signed by the same key as the request."
975 response = Err(ResponseCode::NotAuth);
976 error = Some(TsigError::BadTime);
977 }
978
979 (
980 response,
981 TSigResponseContext::new(req_id, now, tsigner.clone(), tsig.data.mac.clone(), error),
982 )
983 }
984}
985
986impl<P> Deref for SqliteZoneHandler<P> {
987 type Target = InMemoryZoneHandler<P>;
988
989 fn deref(&self) -> &Self::Target {
990 &self.in_memory
991 }
992}
993
994impl<P> DerefMut for SqliteZoneHandler<P> {
995 fn deref_mut(&mut self) -> &mut Self::Target {
996 &mut self.in_memory
997 }
998}
999
1000#[async_trait::async_trait]
1001impl<P: RuntimeProvider + Send + Sync> ZoneHandler for SqliteZoneHandler<P> {
1002 /// What type is this zone
1003 fn zone_type(&self) -> ZoneType {
1004 self.in_memory.zone_type()
1005 }
1006
1007 /// Return a policy that can be used to determine how AXFR requests should be handled.
1008 fn axfr_policy(&self) -> AxfrPolicy {
1009 self.axfr_policy
1010 }
1011
1012 /// Takes the UpdateMessage, extracts the Records, and applies the changes to the record set.
1013 ///
1014 /// # Arguments
1015 ///
1016 /// * `update` - The `UpdateMessage` records will be extracted and used to perform the update
1017 /// actions as specified in the above RFC.
1018 ///
1019 /// # Return value
1020 ///
1021 /// Always returns `Err(NotImp)` if DNSSEC is disabled. Returns `Ok(true)` if any of additions,
1022 /// updates or deletes were made to the zone, false otherwise. Err is returned in the case of
1023 /// bad data, etc.
1024 ///
1025 /// See [RFC 2136](https://datatracker.ietf.org/doc/html/rfc2136#section-3) section 3.4 for
1026 /// details.
1027 async fn update(
1028 &self,
1029 _request: &Request,
1030 _now: u64,
1031 ) -> (Result<bool, ResponseCode>, Option<TSigResponseContext>) {
1032 #[cfg(feature = "__dnssec")]
1033 {
1034 // the spec says to authorize after prereqs, seems better to auth first.
1035 let signer = match self.authorize_update(_request, _now).await {
1036 (Err(e), signer) => return (Err(e), signer),
1037 (_, signer) => signer,
1038 };
1039
1040 if let Err(code) = self.verify_prerequisites(_request.prerequisites()).await {
1041 return (Err(code), signer);
1042 }
1043
1044 if let Err(code) = self.pre_scan(_request.updates()).await {
1045 return (Err(code), signer);
1046 }
1047
1048 (self.update_records(_request.updates(), true).await, signer)
1049 }
1050 #[cfg(not(feature = "__dnssec"))]
1051 {
1052 // if we don't have dnssec, we can't do updates.
1053 (Err(ResponseCode::NotImp), None)
1054 }
1055 }
1056
1057 /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
1058 fn origin(&self) -> &LowerName {
1059 self.in_memory.origin()
1060 }
1061
1062 /// Looks up all Resource Records matching the given `Name` and `RecordType`.
1063 ///
1064 /// # Arguments
1065 ///
1066 /// * `name` - The name to look up.
1067 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
1068 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
1069 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
1070 /// precede and follow all other records.
1071 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
1072 /// algorithms, etc.)
1073 ///
1074 /// # Return value
1075 ///
1076 /// A LookupControlFlow containing the lookup that should be returned to the client.
1077 async fn lookup(
1078 &self,
1079 name: &LowerName,
1080 rtype: RecordType,
1081 request_info: Option<&RequestInfo<'_>>,
1082 lookup_options: LookupOptions,
1083 ) -> LookupControlFlow<AuthLookup> {
1084 self.in_memory
1085 .lookup(name, rtype, request_info, lookup_options)
1086 .await
1087 }
1088
1089 async fn search(
1090 &self,
1091 request: &Request,
1092 lookup_options: LookupOptions,
1093 ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
1094 let request_info = match request.request_info() {
1095 Ok(info) => info,
1096 Err(e) => return (LookupControlFlow::Break(Err(e)), None),
1097 };
1098
1099 if request_info.query.query_type() == RecordType::AXFR {
1100 return (
1101 LookupControlFlow::Break(Err(LookupError::NetError(
1102 "AXFR must be handled with ZoneHandler::zone_transfer()".into(),
1103 ))),
1104 None,
1105 );
1106 }
1107
1108 let (search, _) = self.in_memory.search(request, lookup_options).await;
1109
1110 (search, None)
1111 }
1112
1113 async fn zone_transfer(
1114 &self,
1115 request: &Request,
1116 lookup_options: LookupOptions,
1117 now: u64,
1118 ) -> Option<(
1119 Result<ZoneTransfer, LookupError>,
1120 Option<TSigResponseContext>,
1121 )> {
1122 let (resp, signer) = self.authorize_axfr(request, now).await;
1123 if let Err(code) = resp {
1124 warn!(axfr_policy = ?self.axfr_policy, "rejected AXFR");
1125 return Some((Err(LookupError::ResponseCode(code)), signer));
1126 }
1127 debug!(axfr_policy = ?self.axfr_policy, "authorized AXFR");
1128
1129 let (zone_transfer, _) = self
1130 .in_memory
1131 .zone_transfer(request, lookup_options, now)
1132 .await?;
1133
1134 Some((zone_transfer, signer))
1135 }
1136
1137 /// Return the NSEC records based on the given name
1138 ///
1139 /// # Arguments
1140 ///
1141 /// * `name` - given this name (i.e. the lookup name), return the NSEC record that is less than
1142 /// this
1143 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
1144 /// algorithms, etc.)
1145 async fn nsec_records(
1146 &self,
1147 name: &LowerName,
1148 lookup_options: LookupOptions,
1149 ) -> LookupControlFlow<AuthLookup> {
1150 self.in_memory.nsec_records(name, lookup_options).await
1151 }
1152
1153 #[cfg(feature = "__dnssec")]
1154 async fn nsec3_records(
1155 &self,
1156 info: Nsec3QueryInfo<'_>,
1157 lookup_options: LookupOptions,
1158 ) -> LookupControlFlow<AuthLookup> {
1159 self.in_memory.nsec3_records(info, lookup_options).await
1160 }
1161
1162 #[cfg(feature = "__dnssec")]
1163 fn nx_proof_kind(&self) -> Option<&NxProofKind> {
1164 self.in_memory.nx_proof_kind()
1165 }
1166
1167 #[cfg(feature = "metrics")]
1168 fn metrics_label(&self) -> &'static str {
1169 "sqlite"
1170 }
1171}
1172
1173#[cfg(feature = "__dnssec")]
1174#[async_trait::async_trait]
1175impl<P: RuntimeProvider + Send + Sync> DnssecZoneHandler for SqliteZoneHandler<P> {
1176 /// By adding a secure key, this will implicitly enable dnssec for the zone.
1177 ///
1178 /// # Arguments
1179 ///
1180 /// * `signer` - Signer with associated private key
1181 async fn add_zone_signing_key(&self, signer: DnssecSigner) -> DnsSecResult<()> {
1182 self.in_memory.add_zone_signing_key(signer).await
1183 }
1184
1185 /// (Re)generates the nsec records, increments the serial number and signs the zone
1186 async fn secure_zone(&self) -> DnsSecResult<()> {
1187 self.in_memory.secure_zone().await
1188 }
1189}
1190
1191/// Configuration for zone file for sqlite based zones
1192#[derive(Deserialize, PartialEq, Eq, Debug)]
1193#[serde(deny_unknown_fields)]
1194pub struct SqliteConfig {
1195 /// path to initial zone file
1196 pub zone_path: PathBuf,
1197 /// path to the sqlite journal file
1198 pub journal_path: PathBuf,
1199 /// Are updates allowed to this zone
1200 #[serde(default)]
1201 pub allow_update: bool,
1202 /// TSIG keys allowed to authenticate updates if `allow_update` is true
1203 #[cfg(feature = "__dnssec")]
1204 #[serde(default)]
1205 pub tsig_keys: Vec<TsigKeyConfig>,
1206}
1207
1208/// Configuration for a TSIG authentication signer key
1209#[derive(Deserialize, PartialEq, Eq, Debug)]
1210#[serde(deny_unknown_fields)]
1211#[cfg(feature = "__dnssec")]
1212pub struct TsigKeyConfig {
1213 /// The key name
1214 pub name: String,
1215 /// A path to the unencoded symmetric HMAC key data
1216 pub key_file: PathBuf,
1217 /// The key algorithm
1218 pub algorithm: TsigAlgorithm,
1219 /// Allowed +/- difference (in seconds) between the time a TSIG request was signed
1220 /// and when it is verified.
1221 ///
1222 /// A fudge value that is too large may leave the server open to replay attacks.
1223 /// A fudge value that is too small may cause failures from latency and clock
1224 /// desynchronization.
1225 ///
1226 /// RFC 8945 recommends a fudge value of 300 seconds (the default if not specified).
1227 #[serde(default = "default_fudge")]
1228 pub fudge: u16,
1229}
1230
1231#[cfg(feature = "__dnssec")]
1232impl TsigKeyConfig {
1233 fn to_signer(&self, zone_name: &Name, root_dir: Option<&Path>) -> Result<TSigner, String> {
1234 let key_file = rooted(&self.key_file, root_dir);
1235 let key_data = fs::read(&key_file)
1236 .map_err(|e| format!("error reading TSIG key file: {}: {e}", key_file.display()))?;
1237 let signer_name = Name::from_str(&self.name).unwrap_or_else(|_| zone_name.clone());
1238
1239 TSigner::new(key_data, self.algorithm.clone(), signer_name, self.fudge)
1240 .map_err(|e| format!("invalid TSIG key configuration: {e}"))
1241 }
1242}
1243
1244/// Default TSIG fudge value (seconds).
1245///
1246/// Per RFC 8945 ยง10:
1247/// "The RECOMMENDED value in most situations is 300 seconds."
1248#[cfg(feature = "__dnssec")]
1249pub(crate) fn default_fudge() -> u16 {
1250 300
1251}
1252
1253#[cfg(test)]
1254#[allow(clippy::extra_unused_type_parameters)]
1255mod tests {
1256 use std::env::temp_dir;
1257 use std::fs::remove_file;
1258 use std::net::Ipv4Addr;
1259 use std::path::Path;
1260 use std::process;
1261 use std::str::FromStr;
1262 use std::time::SystemTime;
1263
1264 use crate::net::runtime::TokioRuntimeProvider;
1265 use crate::proto::rr::{Name, RData, Record};
1266 use crate::store::in_memory::{InMemoryZoneHandler, zone_from_path};
1267 use crate::store::sqlite::{Journal, SqliteZoneHandler};
1268 use crate::zone_handler::{AxfrPolicy, ZoneType};
1269
1270 #[test]
1271 fn test_is_send_sync() {
1272 fn send_sync<T: Send + Sync>() -> bool {
1273 true
1274 }
1275
1276 assert!(send_sync::<SqliteZoneHandler>());
1277 }
1278
1279 #[tokio::test]
1280 async fn test_soa_serial_is_monotonic_across_journal_recovery() {
1281 let origin = Name::from_str("example.com.").unwrap();
1282 let zone_path = Path::new(env!("CARGO_MANIFEST_DIR"))
1283 .join("../../tests/test-data/test_configs/example.com.zone");
1284
1285 let in_memory: InMemoryZoneHandler<TokioRuntimeProvider> = InMemoryZoneHandler::new(
1286 origin.clone(),
1287 zone_from_path(&zone_path, origin.clone()).unwrap(),
1288 ZoneType::Primary,
1289 AxfrPolicy::AllowAll,
1290 #[cfg(feature = "__dnssec")]
1291 None,
1292 )
1293 .unwrap();
1294
1295 // Use a file-backed journal so we can simulate a restart by reopening it.
1296 let journal_path = temp_dir().join(format!(
1297 "hickory-sqlite-journal-serial-test-{}-{}.sqlite",
1298 process::id(),
1299 SystemTime::now()
1300 .duration_since(std::time::UNIX_EPOCH)
1301 .unwrap()
1302 .as_nanos()
1303 ));
1304
1305 // Create a handler with journaling enabled and persist the initial zone snapshot.
1306 let mut handler = SqliteZoneHandler::new(
1307 in_memory,
1308 AxfrPolicy::AllowAll,
1309 true, // allow_update
1310 false, // dnssec disabled
1311 );
1312 handler
1313 .set_journal(Journal::from_file(&journal_path).unwrap())
1314 .await;
1315 handler.persist_to_journal().await.unwrap();
1316
1317 let s1 = handler.serial().await;
1318
1319 // Apply a dynamic update that modifies zone contents; this must increment SOA.
1320 let update_record = Record::from_rdata(
1321 Name::from_str("serialtest.example.com.").unwrap(),
1322 0,
1323 RData::A(Ipv4Addr::new(192, 0, 2, 55).into()),
1324 );
1325
1326 assert!(
1327 handler
1328 .update_records(&[update_record], true)
1329 .await
1330 .unwrap()
1331 );
1332 let s2 = handler.serial().await;
1333 assert_eq!(s2, s1 + 1);
1334
1335 // "Restart": recover into a new handler from the same journal.
1336 let in_memory_recovered: InMemoryZoneHandler<TokioRuntimeProvider> =
1337 InMemoryZoneHandler::empty(
1338 origin.clone(),
1339 ZoneType::Primary,
1340 AxfrPolicy::AllowAll,
1341 #[cfg(feature = "__dnssec")]
1342 None,
1343 );
1344 let mut recovered =
1345 SqliteZoneHandler::new(in_memory_recovered, AxfrPolicy::AllowAll, true, false);
1346 recovered
1347 .recover_with_journal(&Journal::from_file(&journal_path).unwrap())
1348 .await
1349 .unwrap();
1350
1351 let s3 = recovered.serial().await;
1352 assert_eq!(s3, s2);
1353
1354 let _ = remove_file(&journal_path);
1355 }
1356}