Skip to main content

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}