use std::{
ops::{Deref, DerefMut},
path::{Path, PathBuf},
};
#[cfg(feature = "metrics")]
use crate::metrics::PersistentStoreMetrics;
#[cfg(feature = "__dnssec")]
use crate::{
dnssec::NxProofKind,
proto::dnssec::{DnsSecResult, DnssecSigner},
zone_handler::{DnssecZoneHandler, Nsec3QueryInfo},
};
use crate::{
proto::rr::{LowerName, Name, RecordType},
server::{Request, RequestInfo},
store::in_memory::{InMemoryZoneHandler, zone_from_path},
zone_handler::{
AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler,
ZoneTransfer, ZoneType,
},
};
use hickory_proto::rr::TSigResponseContext;
use serde::Deserialize;
pub struct FileZoneHandler {
in_memory: InMemoryZoneHandler,
#[cfg(feature = "metrics")]
#[allow(unused)]
metrics: PersistentStoreMetrics,
}
impl FileZoneHandler {
pub async fn new(in_memory: InMemoryZoneHandler) -> Self {
Self {
#[cfg(feature = "metrics")]
metrics: {
let new = PersistentStoreMetrics::new("file");
let records = in_memory.records().await;
new.zone_records.increment(records.len() as f64);
new
},
in_memory,
}
}
pub fn try_from_config(
origin: Name,
zone_type: ZoneType,
axfr_policy: AxfrPolicy,
root_dir: Option<&Path>,
config: &FileConfig,
#[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
) -> Result<Self, String> {
let zone_path = rooted(&config.zone_path, root_dir);
let records = zone_from_path(&zone_path, origin.clone())
.map_err(|e| format!("failed to load zone file: {e}"))?;
Ok(Self {
#[cfg(feature = "metrics")]
metrics: {
let new = PersistentStoreMetrics::new("file");
new.zone_records.increment(records.len() as f64);
new
},
in_memory: InMemoryZoneHandler::new(
origin,
records,
zone_type,
axfr_policy,
#[cfg(feature = "__dnssec")]
nx_proof_kind,
)?,
})
}
}
impl Deref for FileZoneHandler {
type Target = InMemoryZoneHandler;
fn deref(&self) -> &Self::Target {
&self.in_memory
}
}
impl DerefMut for FileZoneHandler {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.in_memory
}
}
#[async_trait::async_trait]
impl ZoneHandler for FileZoneHandler {
fn zone_type(&self) -> ZoneType {
self.in_memory.zone_type()
}
fn axfr_policy(&self) -> AxfrPolicy {
self.in_memory.axfr_policy()
}
fn origin(&self) -> &LowerName {
self.in_memory.origin()
}
async fn lookup(
&self,
name: &LowerName,
rtype: RecordType,
request_info: Option<&RequestInfo<'_>>,
lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> {
self.in_memory
.lookup(name, rtype, request_info, lookup_options)
.await
}
async fn search(
&self,
request: &Request,
lookup_options: LookupOptions,
) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
self.in_memory.search(request, lookup_options).await
}
async fn zone_transfer(
&self,
request: &Request,
lookup_options: LookupOptions,
now: u64,
) -> Option<(
Result<ZoneTransfer, LookupError>,
Option<TSigResponseContext>,
)> {
self.in_memory
.zone_transfer(request, lookup_options, now)
.await
}
async fn nsec_records(
&self,
name: &LowerName,
lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> {
self.in_memory.nsec_records(name, lookup_options).await
}
#[cfg(feature = "__dnssec")]
async fn nsec3_records(
&self,
info: Nsec3QueryInfo<'_>,
lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> {
self.in_memory.nsec3_records(info, lookup_options).await
}
#[cfg(feature = "__dnssec")]
fn nx_proof_kind(&self) -> Option<&NxProofKind> {
self.in_memory.nx_proof_kind()
}
#[cfg(feature = "metrics")]
fn metrics_label(&self) -> &'static str {
"file"
}
}
#[cfg(feature = "__dnssec")]
#[async_trait::async_trait]
impl DnssecZoneHandler for FileZoneHandler {
async fn add_zone_signing_key(&self, signer: DnssecSigner) -> DnsSecResult<()> {
self.in_memory.add_zone_signing_key(signer).await
}
async fn secure_zone(&self) -> DnsSecResult<()> {
DnssecZoneHandler::secure_zone(&self.in_memory).await
}
}
#[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
#[serde(deny_unknown_fields)]
pub struct FileConfig {
pub zone_path: PathBuf,
}
pub(crate) fn rooted(zone_file: &Path, root_dir: Option<&Path>) -> PathBuf {
match root_dir {
Some(root) => root.join(zone_file),
None => zone_file.to_owned(),
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::proto::rr::{RData, rdata::A};
use futures_executor::block_on;
use test_support::subscribe;
use super::*;
use crate::zone_handler::ZoneType;
#[test]
fn test_load_zone() {
subscribe();
#[cfg(feature = "__dnssec")]
let config = FileConfig {
zone_path: PathBuf::from("../../tests/test-data/test_configs/dnssec/example.com.zone"),
};
#[cfg(not(feature = "__dnssec"))]
let config = FileConfig {
zone_path: PathBuf::from("../../tests/test-data/test_configs/example.com.zone"),
};
let handler = FileZoneHandler::try_from_config(
Name::from_str("example.com.").unwrap(),
ZoneType::Primary,
AxfrPolicy::Deny,
None,
&config,
#[cfg(feature = "__dnssec")]
Some(NxProofKind::Nsec),
)
.expect("failed to load file");
let lookup = block_on(ZoneHandler::lookup(
&handler,
&LowerName::from_str("www.example.com.").unwrap(),
RecordType::A,
None,
LookupOptions::default(),
))
.expect("lookup failed");
match lookup
.into_iter()
.next()
.expect("A record not found in zone handler")
.data()
{
RData::A(ip) => assert_eq!(A::new(127, 0, 0, 1), *ip),
_ => panic!("wrong rdata type returned"),
}
let include_lookup = block_on(ZoneHandler::lookup(
&handler,
&LowerName::from_str("include.alias.example.com.").unwrap(),
RecordType::A,
None,
LookupOptions::default(),
))
.expect("INCLUDE lookup failed");
match include_lookup
.into_iter()
.next()
.expect("A record not found in zone handler")
.data()
{
RData::A(ip) => assert_eq!(A::new(127, 0, 0, 5), *ip),
_ => panic!("wrong rdata type returned"),
}
}
}