Skip to main content

hickory_server/store/in_memory/
mod.rs

1// Copyright 2015-2019 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//! Zone handler with in-memory authoritative data storage
9
10use std::{
11    collections::BTreeMap,
12    fs,
13    marker::PhantomData,
14    ops::{Deref, DerefMut},
15    path::Path,
16    sync::Arc,
17};
18
19#[cfg(feature = "__dnssec")]
20use crate::{
21    dnssec::NxProofKind,
22    net::runtime::Time,
23    proto::dnssec::{
24        DnsSecResult, DnssecSigner,
25        rdata::{DNSKEY, DNSSECRData},
26    },
27    zone_handler::{DnssecZoneHandler, Nsec3QueryInfo},
28};
29use crate::{
30    net::runtime::{RuntimeProvider, TokioRuntimeProvider},
31    proto::{
32        op::ResponseCode,
33        rr::{DNSClass, LowerName, Name, RData, Record, RecordSet, RecordType, RrKey},
34        serialize::txt::Parser,
35    },
36    server::{Request, RequestInfo},
37    zone_handler::{
38        AuthLookup, AxfrPolicy, AxfrRecords, LookupControlFlow, LookupError, LookupOptions,
39        LookupRecords, ZoneHandler, ZoneTransfer, ZoneType,
40    },
41};
42use hickory_proto::rr::TSigResponseContext;
43#[cfg(feature = "__dnssec")]
44use time::OffsetDateTime;
45use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
46#[cfg(feature = "__dnssec")]
47use tracing::warn;
48use tracing::{debug, info};
49
50mod inner;
51use inner::InnerInMemory;
52
53/// InMemoryZoneHandler is responsible for storing the resource records for a particular zone.
54///
55/// Zone handlers default to DNSClass IN. The ZoneType specifies if this should be treated as the
56/// start of authority for the zone, is a Secondary, or a cached zone.
57pub struct InMemoryZoneHandler<P = TokioRuntimeProvider> {
58    origin: LowerName,
59    class: DNSClass,
60    zone_type: ZoneType,
61    axfr_policy: AxfrPolicy,
62    inner: RwLock<InnerInMemory>,
63    #[cfg(feature = "__dnssec")]
64    nx_proof_kind: Option<NxProofKind>,
65    _phantom: PhantomData<P>,
66}
67
68impl<P: RuntimeProvider + Send + Sync> InMemoryZoneHandler<P> {
69    /// Creates a new ZoneHandler.
70    ///
71    /// # Arguments
72    ///
73    /// * `origin` - The zone `Name` being created, this should match that of the `RecordType::SOA`
74    ///   record.
75    /// * `records` - The map of the initial set of records in the zone.
76    /// * `zone_type` - The type of zone, i.e. is this authoritative?
77    /// * `axfr_policy` - A policy for determining if AXFR is allowed.
78    /// * `nx_proof_kind` - The kind of non-existence proof to be used by the server.
79    ///
80    /// # Return value
81    ///
82    /// The new `ZoneHandler`.
83    pub fn new(
84        origin: Name,
85        records: BTreeMap<RrKey, RecordSet>,
86        zone_type: ZoneType,
87        axfr_policy: AxfrPolicy,
88        #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
89    ) -> Result<Self, String> {
90        let mut this = Self::empty(
91            origin.clone(),
92            zone_type,
93            axfr_policy,
94            #[cfg(feature = "__dnssec")]
95            nx_proof_kind,
96        );
97        let inner = this.inner.get_mut();
98
99        // SOA must be present
100        let soa = records
101            .get(&RrKey::new(origin.clone().into(), RecordType::SOA))
102            .and_then(|rrset| match &rrset.records_without_rrsigs().next()?.data {
103                RData::SOA(soa) => Some(soa),
104                _ => None,
105            })
106            .ok_or_else(|| format!("SOA record must be present: {origin}"))?;
107        let serial = soa.serial;
108
109        let iter = records.into_values();
110
111        // add soa to the records
112        for rrset in iter {
113            let name = rrset.name().clone();
114            let rr_type = rrset.record_type();
115
116            for record in rrset.records_without_rrsigs() {
117                if !inner.upsert(record.clone(), serial, this.class) {
118                    return Err(format!(
119                        "Failed to insert {name} {rr_type} to zone: {origin}"
120                    ));
121                };
122            }
123        }
124
125        Ok(this)
126    }
127
128    /// Creates an empty ZoneHandler
129    ///
130    /// # Warning
131    ///
132    /// This is an invalid zone, SOA must be added
133    pub fn empty(
134        origin: Name,
135        zone_type: ZoneType,
136        axfr_policy: AxfrPolicy,
137        #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
138    ) -> Self {
139        Self {
140            origin: LowerName::new(&origin),
141            class: DNSClass::IN,
142            zone_type,
143            axfr_policy,
144            inner: RwLock::new(InnerInMemory::default()),
145
146            #[cfg(feature = "__dnssec")]
147            nx_proof_kind,
148
149            _phantom: PhantomData,
150        }
151    }
152
153    /// The DNSClass of this zone
154    pub fn class(&self) -> DNSClass {
155        self.class
156    }
157
158    /// Set the AXFR policy for testing purposes
159    #[cfg(any(test, feature = "testing"))]
160    pub fn set_axfr_policy(&mut self, policy: AxfrPolicy) {
161        self.axfr_policy = policy;
162    }
163
164    /// Clears all records (including SOA, etc)
165    pub fn clear(&mut self) {
166        self.inner.get_mut().records.clear()
167    }
168
169    /// Retrieve the Signer, which contains the private keys, for this zone
170    #[cfg(all(feature = "__dnssec", feature = "testing"))]
171    pub async fn secure_keys(&self) -> impl Deref<Target = [DnssecSigner]> + '_ {
172        RwLockWriteGuard::map(self.inner.write().await, |i| i.secure_keys.as_mut_slice())
173    }
174
175    /// Get all the records
176    pub async fn records(&self) -> impl Deref<Target = BTreeMap<RrKey, Arc<RecordSet>>> + '_ {
177        RwLockReadGuard::map(self.inner.read().await, |i| &i.records)
178    }
179
180    /// Get a mutable reference to the records
181    pub async fn records_mut(
182        &self,
183    ) -> impl DerefMut<Target = BTreeMap<RrKey, Arc<RecordSet>>> + '_ {
184        RwLockWriteGuard::map(self.inner.write().await, |i| &mut i.records)
185    }
186
187    /// Get a mutable reference to the records
188    pub fn records_get_mut(&mut self) -> &mut BTreeMap<RrKey, Arc<RecordSet>> {
189        &mut self.inner.get_mut().records
190    }
191
192    /// Returns the minimum ttl (as used in the SOA record)
193    pub async fn minimum_ttl(&self) -> u32 {
194        self.inner.read().await.minimum_ttl(self.origin())
195    }
196
197    /// get the current serial number for the zone.
198    pub async fn serial(&self) -> u32 {
199        self.inner.read().await.serial(self.origin())
200    }
201
202    #[cfg(feature = "sqlite")]
203    pub(crate) async fn increment_soa_serial(&self) -> u32 {
204        self.inner
205            .write()
206            .await
207            .increment_soa_serial(self.origin(), self.class)
208    }
209
210    /// Inserts or updates a `Record` depending on its existence in the zone.
211    ///
212    /// Guarantees that SOA, CNAME only has one record, will implicitly update if they already exist.
213    ///
214    /// # Arguments
215    ///
216    /// * `record` - The `Record` to be inserted or updated.
217    /// * `serial` - Current serial number to be recorded against updates.
218    ///
219    /// # Return value
220    ///
221    /// true if the value was inserted, false otherwise
222    pub async fn upsert(&self, record: Record, serial: u32) -> bool {
223        self.inner.write().await.upsert(record, serial, self.class)
224    }
225
226    /// Non-async version of upsert when behind a mutable reference.
227    pub fn upsert_mut(&mut self, record: Record, serial: u32) -> bool {
228        self.inner.get_mut().upsert(record, serial, self.class)
229    }
230
231    /// By adding a secure key, this will implicitly enable dnssec for the zone.
232    ///
233    /// # Arguments
234    ///
235    /// * `signer` - Signer with associated private key
236    /// * `origin` - The origin `LowerName` for the signer record
237    /// * `dns_class` - The `DNSClass` for the signer record
238    #[cfg(feature = "__dnssec")]
239    fn inner_add_zone_signing_key(
240        inner: &mut InnerInMemory,
241        signer: DnssecSigner,
242        origin: &LowerName,
243        dns_class: DNSClass,
244    ) -> DnsSecResult<()> {
245        // also add the key to the zone
246        let zone_ttl = inner.minimum_ttl(origin);
247        let dnskey = DNSKEY::from_key(&signer.key().to_public_key()?);
248        let dnskey = Record::from_rdata(
249            origin.clone().into(),
250            zone_ttl,
251            RData::DNSSEC(DNSSECRData::DNSKEY(dnskey)),
252        );
253
254        // TODO: also generate the CDS and CDNSKEY
255        let serial = inner.serial(origin);
256        inner.upsert(dnskey, serial, dns_class);
257        inner.secure_keys.push(signer);
258        Ok(())
259    }
260
261    /// Non-async method of add_zone_signing_key when behind a mutable reference
262    #[cfg(feature = "__dnssec")]
263    pub fn add_zone_signing_key_mut(&mut self, signer: DnssecSigner) -> DnsSecResult<()> {
264        let Self {
265            origin,
266            inner,
267            class,
268            ..
269        } = self;
270
271        Self::inner_add_zone_signing_key(inner.get_mut(), signer, origin, *class)
272    }
273
274    /// (Re)generates the nsec records, increments the serial number and signs the zone
275    #[cfg(feature = "__dnssec")]
276    pub fn secure_zone_mut(&mut self) -> DnsSecResult<()> {
277        let Self { origin, inner, .. } = self;
278        inner.get_mut().secure_zone_mut(
279            origin,
280            self.class,
281            self.nx_proof_kind.as_ref(),
282            Self::current_time()?,
283        )
284    }
285
286    /// (Re)generates the nsec records, increments the serial number and signs the zone
287    #[cfg(not(feature = "__dnssec"))]
288    pub fn secure_zone_mut(&mut self) -> Result<(), &str> {
289        Err("DNSSEC was not enabled during compilation.")
290    }
291
292    #[cfg(feature = "__dnssec")]
293    fn current_time() -> DnsSecResult<OffsetDateTime> {
294        let timestamp_unsigned = P::Timer::current_time();
295        let timestamp_signed = timestamp_unsigned
296            .try_into()
297            .map_err(|_| "current time is out of range")?;
298        OffsetDateTime::from_unix_timestamp(timestamp_signed)
299            .map_err(|_| "current time is out of range".into())
300    }
301}
302
303#[async_trait::async_trait]
304impl<P: RuntimeProvider + Send + Sync> ZoneHandler for InMemoryZoneHandler<P> {
305    /// What type is this zone
306    fn zone_type(&self) -> ZoneType {
307        self.zone_type
308    }
309
310    /// Return the policy for determining if AXFR requests are allowed
311    fn axfr_policy(&self) -> AxfrPolicy {
312        self.axfr_policy
313    }
314
315    /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
316    fn origin(&self) -> &LowerName {
317        &self.origin
318    }
319
320    /// Looks up all Resource Records matching the given `Name` and `RecordType`.
321    ///
322    /// # Arguments
323    ///
324    /// * `name` - The name to look up.
325    /// * `query_type` - The `RecordType` to look up. `RecordType::ANY` will return all records
326    ///                  matching `name`. `RecordType::AXFR` will return all record types except
327    ///                  `RecordType::SOA` due to the requirements that on zone transfers the
328    ///                  `RecordType::SOA` must both precede and follow all other records.
329    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
330    ///                      algorithms, etc.)
331    ///
332    /// # Return value
333    ///
334    /// A LookupControlFlow containing the lookup that should be returned to the client.
335    async fn lookup(
336        &self,
337        name: &LowerName,
338        mut query_type: RecordType,
339        _request_info: Option<&RequestInfo<'_>>,
340        lookup_options: LookupOptions,
341    ) -> LookupControlFlow<AuthLookup> {
342        let inner = self.inner.read().await;
343
344        if query_type == RecordType::AXFR {
345            return Break(Err(LookupError::NetError(
346                "AXFR must be handled with ZoneHandler::zone_transfer()".into(),
347            )));
348        }
349
350        if query_type == RecordType::ANY {
351            query_type = inner.replace_any(name);
352        }
353
354        let answer = inner.inner_lookup(name, query_type, lookup_options);
355
356        // CNAME chasing: when the answer is a CNAME and the query was for a
357        // different type, restart the lookup at the canonical name and collect
358        // the full chain into the ANSWER section (RFC 1034 §3.6.2).
359        let (answer, cname_chain) = match answer {
360            Some(a) if a.record_type() == RecordType::CNAME && query_type != RecordType::CNAME => {
361                let chain = inner.chase_cnames(name, a, query_type, lookup_options);
362                // The terminal record drives additional section processing.
363                // If the chain ends in a non-CNAME record, use it; otherwise
364                // there is no terminal record to process.
365                let terminal = chain
366                    .last()
367                    .filter(|rr| rr.record_type() != RecordType::CNAME)
368                    .cloned();
369                (terminal, Some(chain))
370            }
371            _ => (answer, None),
372        };
373
374        // Evaluate additional section records for the answer (ANAME, MX,
375        // SRV, NS).  For CNAME-chased queries this processes the terminal
376        // record; for direct answers it processes the original answer.
377        let additionals_root_chain_type: Option<(_, _)> = answer
378            .as_ref()
379            .and_then(|a| maybe_next_name(a, query_type))
380            .and_then(|(search_name, search_type)| {
381                inner
382                    .additional_search(name, query_type, search_name, search_type, lookup_options)
383                    .map(|adds| (adds, search_type))
384            });
385
386        // if the chain started with an ANAME, take the A or AAAA record from the list
387        let (additionals, answer) = match (additionals_root_chain_type, answer, query_type) {
388            (Some((additionals, RecordType::ANAME)), Some(answer), RecordType::A)
389            | (Some((additionals, RecordType::ANAME)), Some(answer), RecordType::AAAA) => {
390                // This should always be true...
391                debug_assert_eq!(answer.record_type(), RecordType::ANAME);
392
393                // in the case of ANAME the final record should be the A or AAAA record
394                let (rdatas, a_aaaa_ttl) = {
395                    let last_record = additionals.last();
396                    let a_aaaa_ttl = last_record.map_or(u32::MAX, |r| r.ttl());
397
398                    // grap the rdatas
399                    let rdatas: Option<Vec<RData>> = last_record
400                        .and_then(|record| match record.record_type() {
401                            RecordType::A | RecordType::AAAA => {
402                                // the RRSIGS will be useless since we're changing the record type
403                                Some(record.records_without_rrsigs())
404                            }
405                            _ => None,
406                        })
407                        .map(|records| records.map(|r| &r.data).cloned().collect::<Vec<_>>());
408
409                    (rdatas, a_aaaa_ttl)
410                };
411
412                // now build up a new RecordSet
413                //   the name comes from the ANAME record
414                //   according to the rfc the ttl is from the ANAME
415                //   TODO: technically we should take the min of the potential CNAME chain
416                let ttl = answer.ttl().min(a_aaaa_ttl);
417                let mut new_answer = RecordSet::new(answer.name().clone(), query_type, ttl);
418
419                for rdata in rdatas.into_iter().flatten() {
420                    new_answer.add_rdata(rdata);
421                }
422
423                // if DNSSEC is enabled, and the request had the DO set, sign the recordset
424                #[cfg(feature = "__dnssec")]
425                // ANAME's are constructed on demand, so need to be signed before return
426                if lookup_options.dnssec_ok {
427                    let result = Self::current_time().and_then(|time| {
428                        InnerInMemory::sign_rrset(
429                            &mut new_answer,
430                            &inner.secure_keys,
431                            self.class(),
432                            time,
433                        )
434                    });
435                    if let Err(error) = result {
436                        // rather than failing the request, we'll just warn
437                        warn!(%error, "failed to sign ANAME record")
438                    }
439                }
440
441                // prepend answer to additionals here (answer is the ANAME record)
442                let additionals = std::iter::once(answer).chain(additionals).collect();
443
444                // return the new answer
445                //   because the searched set was an Arc, we need to arc too
446                (Some(additionals), Some(Arc::new(new_answer)))
447            }
448            (Some((additionals, _)), answer, _) => (Some(additionals), answer),
449            (None, answer, _) => (None, answer),
450        };
451
452        // This is annoying. The 1035 spec literally specifies that most DNS authorities would want to store
453        //   records in a list except when there are a lot of records. But this makes indexed lookups by name+type
454        //   always return empty sets. This is only important in the negative case, where other DNS authorities
455        //   generally return NoError and no results when other types exist at the same name. bah.
456        // TODO: can we get rid of this?
457        use LookupControlFlow::*;
458        let answers = match (cname_chain, answer) {
459            // CNAME chase produced a chain — use it as the answer.
460            (Some(chain), _) => LookupRecords::many(lookup_options, chain),
461            (None, Some(rr_set)) => LookupRecords::new(lookup_options, rr_set),
462            (None, None) => {
463                return Continue(Err(
464                    if inner
465                        .records
466                        .keys()
467                        .any(|key| key.name() == name || name.zone_of(key.name()))
468                    {
469                        LookupError::NameExists
470                    } else {
471                        LookupError::from(match self.origin().zone_of(name) {
472                            true => ResponseCode::NXDomain,
473                            false => ResponseCode::Refused,
474                        })
475                    },
476                ));
477            }
478        };
479
480        Continue(Ok(AuthLookup::answers(
481            answers,
482            additionals.map(|a| LookupRecords::many(lookup_options, a)),
483        )))
484    }
485
486    async fn search(
487        &self,
488        request: &Request,
489        lookup_options: LookupOptions,
490    ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
491        let request_info = match request.request_info() {
492            Ok(info) => info,
493            Err(e) => return (LookupControlFlow::Break(Err(e)), None),
494        };
495        debug!("searching InMemoryZoneHandler for: {}", request_info.query);
496
497        let lookup_name = request_info.query.name();
498        let record_type: RecordType = request_info.query.query_type();
499
500        // perform the actual lookup
501        match record_type {
502            RecordType::AXFR => (
503                LookupControlFlow::Break(Err(LookupError::NetError(
504                    "AXFR must be handled with ZoneHandler::zone_transfer()".into(),
505                ))),
506                None,
507            ),
508            // A standard Lookup path
509            _ => (
510                self.lookup(
511                    lookup_name,
512                    record_type,
513                    Some(&request_info),
514                    lookup_options,
515                )
516                .await,
517                None,
518            ),
519        }
520    }
521
522    async fn zone_transfer(
523        &self,
524        request: &Request,
525        lookup_options: LookupOptions,
526        _now: u64,
527    ) -> Option<(
528        Result<ZoneTransfer, LookupError>,
529        Option<TSigResponseContext>,
530    )> {
531        let request_info = match request.request_info() {
532            Ok(info) => info,
533            Err(e) => return Some((Err(e), None)),
534        };
535
536        if request_info.query.query_type() == RecordType::AXFR {
537            // TODO: support more advanced AXFR options
538            if !matches!(self.axfr_policy, AxfrPolicy::AllowAll) {
539                return Some((Err(LookupError::from(ResponseCode::Refused)), None));
540            }
541        }
542
543        let future = self.lookup(self.origin(), RecordType::SOA, None, lookup_options);
544        let start_soa = if let LookupControlFlow::Continue(Ok(res)) = future.await {
545            res.unwrap_records()
546        } else {
547            LookupRecords::Empty
548        };
549
550        let future = self.lookup(
551            self.origin(),
552            RecordType::SOA,
553            None,
554            LookupOptions::default(),
555        );
556        let end_soa = if let LookupControlFlow::Continue(Ok(res)) = future.await {
557            res.unwrap_records()
558        } else {
559            LookupRecords::Empty
560        };
561
562        let records = AxfrRecords::new(
563            lookup_options.dnssec_ok,
564            self.inner.read().await.records.values().cloned().collect(),
565        );
566
567        Some((
568            Ok(ZoneTransfer {
569                start_soa,
570                records,
571                end_soa,
572            }),
573            None,
574        ))
575    }
576
577    /// Return the NSEC records based on the given name
578    ///
579    /// # Arguments
580    ///
581    /// * `name` - given this name (i.e. the lookup name), return the NSEC record that is less than
582    ///            this
583    /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
584    ///                      algorithms, etc.)
585    #[cfg(feature = "__dnssec")]
586    async fn nsec_records(
587        &self,
588        name: &LowerName,
589        lookup_options: LookupOptions,
590    ) -> LookupControlFlow<AuthLookup> {
591        let inner = self.inner.read().await;
592
593        // TODO: need a BorrowdRrKey
594        let rr_key = RrKey::new(name.clone(), RecordType::NSEC);
595        let no_data = inner
596            .records
597            .get(&rr_key)
598            .map(|rr_set| LookupRecords::new(lookup_options, rr_set.clone()));
599
600        if let Some(no_data) = no_data {
601            return LookupControlFlow::Continue(Ok(no_data.into()));
602        }
603
604        let closest_proof = inner.closest_nsec(name);
605
606        // we need the wildcard proof, but make sure that it's still part of the zone.
607        let wildcard = name.base_name();
608        let origin = self.origin();
609        let wildcard = if origin.zone_of(&wildcard) {
610            wildcard
611        } else {
612            origin.clone()
613        };
614
615        // don't duplicate the record...
616        let wildcard_proof = if wildcard != *name {
617            inner.closest_nsec(&wildcard)
618        } else {
619            None
620        };
621
622        let proofs = match (closest_proof, wildcard_proof) {
623            (Some(closest_proof), Some(wildcard_proof)) => {
624                // dedup with the wildcard proof
625                if wildcard_proof != closest_proof {
626                    vec![wildcard_proof, closest_proof]
627                } else {
628                    vec![closest_proof]
629                }
630            }
631            (None, Some(proof)) | (Some(proof), None) => vec![proof],
632            (None, None) => vec![],
633        };
634
635        LookupControlFlow::Continue(Ok(LookupRecords::many(lookup_options, proofs).into()))
636    }
637
638    #[cfg(not(feature = "__dnssec"))]
639    async fn nsec_records(
640        &self,
641        _name: &LowerName,
642        _lookup_options: LookupOptions,
643    ) -> LookupControlFlow<AuthLookup> {
644        LookupControlFlow::Continue(Ok(AuthLookup::default()))
645    }
646
647    #[cfg(feature = "__dnssec")]
648    async fn nsec3_records(
649        &self,
650        info: Nsec3QueryInfo<'_>,
651        lookup_options: LookupOptions,
652    ) -> LookupControlFlow<AuthLookup> {
653        let inner = self.inner.read().await;
654        LookupControlFlow::Continue(
655            inner
656                .proof(info, self.origin())
657                .map(|proof| LookupRecords::many(lookup_options, proof).into()),
658        )
659    }
660
661    #[cfg(feature = "__dnssec")]
662    fn nx_proof_kind(&self) -> Option<&NxProofKind> {
663        self.nx_proof_kind.as_ref()
664    }
665
666    #[cfg(feature = "metrics")]
667    fn metrics_label(&self) -> &'static str {
668        "in-memory"
669    }
670}
671
672#[cfg(feature = "__dnssec")]
673#[async_trait::async_trait]
674impl<P: RuntimeProvider + Send + Sync> DnssecZoneHandler for InMemoryZoneHandler<P> {
675    /// By adding a secure key, this will implicitly enable dnssec for the zone.
676    ///
677    /// # Arguments
678    ///
679    /// * `signer` - Signer with associated private key
680    async fn add_zone_signing_key(&self, signer: DnssecSigner) -> DnsSecResult<()> {
681        let mut inner = self.inner.write().await;
682
683        Self::inner_add_zone_signing_key(&mut inner, signer, self.origin(), self.class)
684    }
685
686    /// Sign the zone for DNSSEC
687    async fn secure_zone(&self) -> DnsSecResult<()> {
688        let mut inner = self.inner.write().await;
689
690        inner.secure_zone_mut(
691            self.origin(),
692            self.class,
693            self.nx_proof_kind.as_ref(),
694            Self::current_time()?,
695        )
696    }
697}
698
699/// Gets the next search name, and returns the RecordType that it originated from
700fn maybe_next_name(
701    record_set: &RecordSet,
702    query_type: RecordType,
703) -> Option<(LowerName, RecordType)> {
704    let t = match (record_set.record_type(), query_type) {
705        // ANAME is similar to CNAME,
706        //  unlike CNAME, it is only something that continue to additional processing if the
707        //  the query was for address (A, AAAA, or ANAME itself) record types.
708        (t @ RecordType::ANAME, RecordType::A)
709        | (t @ RecordType::ANAME, RecordType::AAAA)
710        | (t @ RecordType::ANAME, RecordType::ANAME) => t,
711        (t @ RecordType::NS, RecordType::NS) => t,
712        (t @ RecordType::MX, RecordType::MX) => t,
713        (t @ RecordType::SRV, RecordType::SRV) => t,
714        // other additional collectors can be added here
715        _ => return None,
716    };
717
718    let name = match (&record_set.records_without_rrsigs().next()?.data, t) {
719        (RData::ANAME(name), RecordType::ANAME) => name,
720        (RData::NS(ns), RecordType::NS) => &ns.0,
721        (RData::MX(mx), RecordType::MX) => &mx.exchange,
722        (RData::SRV(srv), RecordType::SRV) => &srv.target,
723        _ => return None,
724    };
725
726    Some((LowerName::from(name), t))
727}
728
729// internal load for e.g. sqlite db creation
730pub(crate) fn zone_from_path(
731    zone_path: &Path,
732    origin: Name,
733) -> Result<BTreeMap<RrKey, RecordSet>, String> {
734    info!("loading zone file: {zone_path:?}");
735
736    // TODO: this should really use something to read line by line or some other method to
737    //  keep the usage down. and be a custom lexer...
738    let buf = fs::read_to_string(zone_path)
739        .map_err(|e| format!("failed to read {}: {e:?}", zone_path.display()))?;
740
741    let (origin, records) = Parser::new(buf, Some(zone_path.to_owned()), Some(origin))
742        .parse()
743        .map_err(|e| format!("failed to parse {}: {e:?}", zone_path.display()))?;
744
745    info!("zone file loaded: {origin} with {} records", records.len());
746    debug!("zone: {records:#?}");
747    Ok(records)
748}