1use std::{
11 ops::{Deref, DerefMut},
12 path::{Path, PathBuf},
13};
14
15#[cfg(feature = "metrics")]
16use crate::metrics::PersistentStoreMetrics;
17#[cfg(feature = "__dnssec")]
18use crate::{
19 dnssec::NxProofKind,
20 proto::dnssec::{DnsSecResult, DnssecSigner},
21 zone_handler::{DnssecZoneHandler, Nsec3QueryInfo},
22};
23use crate::{
24 proto::rr::{LowerName, Name, RecordType},
25 server::{Request, RequestInfo},
26 store::in_memory::{InMemoryZoneHandler, zone_from_path},
27 store::rooted,
28 zone_handler::{
29 AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler,
30 ZoneTransfer, ZoneType,
31 },
32};
33use hickory_proto::rr::TSigResponseContext;
34use serde::Deserialize;
35
36pub struct FileZoneHandler {
41 in_memory: InMemoryZoneHandler,
42 #[cfg(feature = "metrics")]
43 #[allow(unused)]
44 metrics: PersistentStoreMetrics,
45}
46
47impl FileZoneHandler {
48 pub async fn new(in_memory: InMemoryZoneHandler) -> Self {
63 Self {
64 #[cfg(feature = "metrics")]
65 metrics: {
66 let new = PersistentStoreMetrics::new("file");
67 let records = in_memory.records().await;
68 new.zone_records.increment(records.len() as f64);
69 new
70 },
71 in_memory,
72 }
73 }
74
75 pub fn try_from_config(
77 origin: Name,
78 zone_type: ZoneType,
79 axfr_policy: AxfrPolicy,
80 root_dir: Option<&Path>,
81 config: &FileConfig,
82 #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
83 ) -> Result<Self, String> {
84 let zone_path = rooted(&config.zone_path, root_dir);
85 let records = zone_from_path(&zone_path, origin.clone())
86 .map_err(|e| format!("failed to load zone file: {e}"))?;
87
88 Ok(Self {
90 #[cfg(feature = "metrics")]
91 metrics: {
92 let new = PersistentStoreMetrics::new("file");
93 new.zone_records.increment(records.len() as f64);
94 new
95 },
96 in_memory: InMemoryZoneHandler::new(
97 origin,
98 records,
99 zone_type,
100 axfr_policy,
101 #[cfg(feature = "__dnssec")]
102 nx_proof_kind,
103 )?,
104 })
105 }
106}
107
108impl Deref for FileZoneHandler {
109 type Target = InMemoryZoneHandler;
110
111 fn deref(&self) -> &Self::Target {
112 &self.in_memory
113 }
114}
115
116impl DerefMut for FileZoneHandler {
117 fn deref_mut(&mut self) -> &mut Self::Target {
118 &mut self.in_memory
119 }
120}
121
122#[async_trait::async_trait]
123impl ZoneHandler for FileZoneHandler {
124 fn zone_type(&self) -> ZoneType {
126 self.in_memory.zone_type()
127 }
128
129 fn axfr_policy(&self) -> AxfrPolicy {
131 self.in_memory.axfr_policy()
132 }
133
134 fn origin(&self) -> &LowerName {
136 self.in_memory.origin()
137 }
138
139 async fn lookup(
155 &self,
156 name: &LowerName,
157 rtype: RecordType,
158 request_info: Option<&RequestInfo<'_>>,
159 lookup_options: LookupOptions,
160 ) -> LookupControlFlow<AuthLookup> {
161 self.in_memory
162 .lookup(name, rtype, request_info, lookup_options)
163 .await
164 }
165
166 async fn search(
178 &self,
179 request: &Request,
180 lookup_options: LookupOptions,
181 ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
182 self.in_memory.search(request, lookup_options).await
183 }
184
185 async fn zone_transfer(
186 &self,
187 request: &Request,
188 lookup_options: LookupOptions,
189 now: u64,
190 ) -> Option<(
191 Result<ZoneTransfer, LookupError>,
192 Option<TSigResponseContext>,
193 )> {
194 self.in_memory
195 .zone_transfer(request, lookup_options, now)
196 .await
197 }
198
199 async fn nsec_records(
208 &self,
209 name: &LowerName,
210 lookup_options: LookupOptions,
211 ) -> LookupControlFlow<AuthLookup> {
212 self.in_memory.nsec_records(name, lookup_options).await
213 }
214
215 #[cfg(feature = "__dnssec")]
216 async fn nsec3_records(
217 &self,
218 info: Nsec3QueryInfo<'_>,
219 lookup_options: LookupOptions,
220 ) -> LookupControlFlow<AuthLookup> {
221 self.in_memory.nsec3_records(info, lookup_options).await
222 }
223
224 #[cfg(feature = "__dnssec")]
225 fn nx_proof_kind(&self) -> Option<&NxProofKind> {
226 self.in_memory.nx_proof_kind()
227 }
228
229 #[cfg(feature = "metrics")]
230 fn metrics_label(&self) -> &'static str {
231 "file"
232 }
233}
234
235#[cfg(feature = "__dnssec")]
236#[async_trait::async_trait]
237impl DnssecZoneHandler for FileZoneHandler {
238 async fn add_zone_signing_key(&self, signer: DnssecSigner) -> DnsSecResult<()> {
240 self.in_memory.add_zone_signing_key(signer).await
241 }
242
243 async fn secure_zone(&self) -> DnsSecResult<()> {
245 DnssecZoneHandler::secure_zone(&self.in_memory).await
246 }
247}
248
249#[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
251#[serde(deny_unknown_fields)]
252pub struct FileConfig {
253 pub zone_path: PathBuf,
255}
256
257#[cfg(test)]
258mod tests {
259 use std::str::FromStr;
260
261 use crate::proto::rr::{RData, rdata::A};
262
263 use futures_executor::block_on;
264 use test_support::subscribe;
265
266 use super::*;
267 use crate::zone_handler::ZoneType;
268
269 #[test]
270 fn test_load_zone() {
271 subscribe();
272
273 #[cfg(feature = "__dnssec")]
274 let config = FileConfig {
275 zone_path: PathBuf::from("../../tests/test-data/test_configs/dnssec/example.com.zone"),
276 };
277 #[cfg(not(feature = "__dnssec"))]
278 let config = FileConfig {
279 zone_path: PathBuf::from("../../tests/test-data/test_configs/example.com.zone"),
280 };
281 let handler = FileZoneHandler::try_from_config(
282 Name::from_str("example.com.").unwrap(),
283 ZoneType::Primary,
284 AxfrPolicy::Deny,
285 None,
286 &config,
287 #[cfg(feature = "__dnssec")]
288 Some(NxProofKind::Nsec),
289 )
290 .expect("failed to load file");
291
292 let lookup = block_on(ZoneHandler::lookup(
293 &handler,
294 &LowerName::from_str("www.example.com.").unwrap(),
295 RecordType::A,
296 None,
297 LookupOptions::default(),
298 ))
299 .expect("lookup failed");
300
301 match lookup
302 .into_iter()
303 .next()
304 .expect("A record not found in zone handler")
305 .data
306 {
307 RData::A(ip) => assert_eq!(A::new(127, 0, 0, 1), ip),
308 _ => panic!("wrong rdata type returned"),
309 }
310
311 let include_lookup = block_on(ZoneHandler::lookup(
312 &handler,
313 &LowerName::from_str("include.alias.example.com.").unwrap(),
314 RecordType::A,
315 None,
316 LookupOptions::default(),
317 ))
318 .expect("INCLUDE lookup failed");
319
320 match include_lookup
321 .into_iter()
322 .next()
323 .expect("A record not found in zone handler")
324 .data
325 {
326 RData::A(ip) => assert_eq!(A::new(127, 0, 0, 5), ip),
327 _ => panic!("wrong rdata type returned"),
328 }
329 }
330}