1use std::{
11 collections::BTreeMap,
12 fs,
13 ops::{Deref, DerefMut},
14 path::{Path, PathBuf},
15};
16
17use serde::Deserialize;
18use tracing::{debug, info};
19
20#[cfg(feature = "metrics")]
21use crate::store::metrics::StoreMetrics;
22use crate::{
23 authority::{
24 Authority, LookupControlFlow, LookupOptions, MessageRequest, UpdateResult, ZoneType,
25 },
26 proto::rr::{LowerName, Name, RecordSet, RecordType, RrKey},
27 proto::serialize::txt::Parser,
28 server::RequestInfo,
29 store::in_memory::InMemoryAuthority,
30};
31#[cfg(feature = "__dnssec")]
32use crate::{
33 authority::{DnssecAuthority, Nsec3QueryInfo},
34 dnssec::NxProofKind,
35 proto::dnssec::{DnsSecResult, SigSigner, rdata::key::KEY},
36};
37
38pub struct FileAuthority {
43 in_memory: InMemoryAuthority,
44 #[cfg(feature = "metrics")]
45 metrics: StoreMetrics,
46}
47
48impl FileAuthority {
49 pub fn new(
64 origin: Name,
65 records: BTreeMap<RrKey, RecordSet>,
66 zone_type: ZoneType,
67 allow_axfr: bool,
68 #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
69 ) -> Result<Self, String> {
70 Ok(Self {
71 #[cfg(feature = "metrics")]
72 metrics: {
73 let new = StoreMetrics::new("file");
74 new.persistent.zone_records.increment(records.len() as f64);
75 new
76 },
77 in_memory: InMemoryAuthority::new(
78 origin,
79 records,
80 zone_type,
81 allow_axfr,
82 #[cfg(feature = "__dnssec")]
83 nx_proof_kind,
84 )?,
85 })
86 }
87
88 pub fn try_from_config(
90 origin: Name,
91 zone_type: ZoneType,
92 allow_axfr: bool,
93 root_dir: Option<&Path>,
94 config: &FileConfig,
95 #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
96 ) -> Result<Self, String> {
97 Self::try_from_config_internal(
98 origin,
99 zone_type,
100 allow_axfr,
101 root_dir,
102 config,
103 #[cfg(feature = "__dnssec")]
104 nx_proof_kind,
105 #[cfg(feature = "metrics")]
106 false,
107 )
108 }
109
110 pub(crate) fn try_from_config_internal(
112 origin: Name,
113 zone_type: ZoneType,
114 allow_axfr: bool,
115 root_dir: Option<&Path>,
116 config: &FileConfig,
117 #[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
118 #[cfg(feature = "metrics")] is_internal_load: bool,
119 ) -> Result<Self, String> {
120 let root_dir_path = root_dir.map(PathBuf::from).unwrap_or_default();
121 let zone_path = root_dir_path.join(&config.zone_file_path);
122
123 info!("loading zone file: {:?}", zone_path);
124
125 let buf = fs::read_to_string(&zone_path).map_err(|e| {
128 format!(
129 "failed to read {}: {:?}",
130 config.zone_file_path.display(),
131 e
132 )
133 })?;
134
135 let (origin, records) = Parser::new(buf, Some(zone_path), Some(origin))
136 .parse()
137 .map_err(|e| {
138 format!(
139 "failed to parse {}: {:?}",
140 config.zone_file_path.display(),
141 e
142 )
143 })?;
144
145 info!(
146 "zone file loaded: {} with {} records",
147 origin,
148 records.len()
149 );
150 debug!("zone: {:#?}", records);
151
152 Ok(Self {
153 #[cfg(feature = "metrics")]
154 metrics: {
155 let new = StoreMetrics::new("file");
156 if !is_internal_load {
157 new.persistent.zone_records.increment(records.len() as f64);
158 }
159 new
160 },
161 in_memory: InMemoryAuthority::new(
162 origin,
163 records,
164 zone_type,
165 allow_axfr,
166 #[cfg(feature = "__dnssec")]
167 nx_proof_kind,
168 )?,
169 })
170 }
171
172 pub fn unwrap(self) -> InMemoryAuthority {
174 self.in_memory
175 }
176}
177
178impl Deref for FileAuthority {
179 type Target = InMemoryAuthority;
180
181 fn deref(&self) -> &Self::Target {
182 &self.in_memory
183 }
184}
185
186impl DerefMut for FileAuthority {
187 fn deref_mut(&mut self) -> &mut Self::Target {
188 &mut self.in_memory
189 }
190}
191
192#[async_trait::async_trait]
193impl Authority for FileAuthority {
194 type Lookup = <InMemoryAuthority as Authority>::Lookup;
195
196 fn zone_type(&self) -> ZoneType {
198 self.in_memory.zone_type()
199 }
200
201 fn is_axfr_allowed(&self) -> bool {
203 self.in_memory.is_axfr_allowed()
204 }
205
206 async fn update(&self, _update: &MessageRequest) -> UpdateResult<bool> {
208 use crate::proto::op::ResponseCode;
209 Err(ResponseCode::NotImp)
210 }
211
212 fn origin(&self) -> &LowerName {
214 self.in_memory.origin()
215 }
216
217 async fn lookup(
233 &self,
234 name: &LowerName,
235 rtype: RecordType,
236 lookup_options: LookupOptions,
237 ) -> LookupControlFlow<Self::Lookup> {
238 let lookup = self.in_memory.lookup(name, rtype, lookup_options).await;
239
240 #[cfg(feature = "metrics")]
241 self.metrics.query.increment_lookup(&lookup);
242
243 lookup
244 }
245
246 async fn search(
258 &self,
259 request_info: RequestInfo<'_>,
260 lookup_options: LookupOptions,
261 ) -> LookupControlFlow<Self::Lookup> {
262 let search = self.in_memory.search(request_info, lookup_options).await;
263
264 #[cfg(feature = "metrics")]
265 self.metrics.query.increment_lookup(&search);
266
267 search
268 }
269
270 async fn ns(&self, lookup_options: LookupOptions) -> LookupControlFlow<Self::Lookup> {
272 self.in_memory.ns(lookup_options).await
273 }
274
275 async fn get_nsec_records(
284 &self,
285 name: &LowerName,
286 lookup_options: LookupOptions,
287 ) -> LookupControlFlow<Self::Lookup> {
288 self.in_memory.get_nsec_records(name, lookup_options).await
289 }
290
291 #[cfg(feature = "__dnssec")]
292 async fn get_nsec3_records(
293 &self,
294 info: Nsec3QueryInfo<'_>,
295 lookup_options: LookupOptions,
296 ) -> LookupControlFlow<Self::Lookup> {
297 self.in_memory.get_nsec3_records(info, lookup_options).await
298 }
299
300 async fn soa(&self) -> LookupControlFlow<Self::Lookup> {
305 self.in_memory.soa().await
306 }
307
308 async fn soa_secure(&self, lookup_options: LookupOptions) -> LookupControlFlow<Self::Lookup> {
310 self.in_memory.soa_secure(lookup_options).await
311 }
312
313 #[cfg(feature = "__dnssec")]
314 fn nx_proof_kind(&self) -> Option<&NxProofKind> {
315 self.in_memory.nx_proof_kind()
316 }
317}
318
319#[cfg(feature = "__dnssec")]
320#[async_trait::async_trait]
321impl DnssecAuthority for FileAuthority {
322 async fn add_update_auth_key(&self, name: Name, key: KEY) -> DnsSecResult<()> {
324 self.in_memory.add_update_auth_key(name, key).await
325 }
326
327 async fn add_zone_signing_key(&self, signer: SigSigner) -> DnsSecResult<()> {
329 self.in_memory.add_zone_signing_key(signer).await
330 }
331
332 async fn secure_zone(&self) -> DnsSecResult<()> {
334 DnssecAuthority::secure_zone(&self.in_memory).await
335 }
336}
337
338#[derive(Deserialize, PartialEq, Eq, Debug)]
340#[serde(deny_unknown_fields)]
341pub struct FileConfig {
342 pub zone_file_path: PathBuf,
344}
345
346#[cfg(test)]
347mod tests {
348 use std::str::FromStr;
349
350 use crate::proto::rr::{RData, rdata::A};
351 use futures_executor::block_on;
352 use test_support::subscribe;
353
354 use super::*;
355 use crate::authority::ZoneType;
356
357 #[test]
358 fn test_load_zone() {
359 subscribe();
360
361 #[cfg(feature = "__dnssec")]
362 let config = FileConfig {
363 zone_file_path: PathBuf::from(
364 "../../tests/test-data/test_configs/dnssec/example.com.zone",
365 ),
366 };
367 #[cfg(not(feature = "__dnssec"))]
368 let config = FileConfig {
369 zone_file_path: PathBuf::from("../../tests/test-data/test_configs/example.com.zone"),
370 };
371 let authority = FileAuthority::try_from_config(
372 Name::from_str("example.com.").unwrap(),
373 ZoneType::Primary,
374 false,
375 None,
376 &config,
377 #[cfg(feature = "__dnssec")]
378 Some(NxProofKind::Nsec),
379 )
380 .expect("failed to load file");
381
382 let lookup = block_on(Authority::lookup(
383 &authority,
384 &LowerName::from_str("www.example.com.").unwrap(),
385 RecordType::A,
386 LookupOptions::default(),
387 ))
388 .expect("lookup failed");
389
390 match lookup
391 .into_iter()
392 .next()
393 .expect("A record not found in authority")
394 .data()
395 {
396 RData::A(ip) => assert_eq!(A::new(127, 0, 0, 1), *ip),
397 _ => panic!("wrong rdata type returned"),
398 }
399
400 let include_lookup = block_on(Authority::lookup(
401 &authority,
402 &LowerName::from_str("include.alias.example.com.").unwrap(),
403 RecordType::A,
404 LookupOptions::default(),
405 ))
406 .expect("INCLUDE lookup failed");
407
408 match include_lookup
409 .into_iter()
410 .next()
411 .expect("A record not found in authority")
412 .data()
413 {
414 RData::A(ip) => assert_eq!(A::new(127, 0, 0, 5), *ip),
415 _ => panic!("wrong rdata type returned"),
416 }
417 }
418}