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