use std::{
collections::BTreeMap,
fs,
ops::{Deref, DerefMut},
path::{Path, PathBuf},
};
use serde::Deserialize;
use tracing::{debug, info};
#[cfg(feature = "metrics")]
use crate::store::metrics::StoreMetrics;
use crate::{
authority::{
Authority, LookupControlFlow, LookupOptions, MessageRequest, UpdateResult, ZoneType,
},
proto::rr::{LowerName, Name, RecordSet, RecordType, RrKey},
proto::serialize::txt::Parser,
server::RequestInfo,
store::in_memory::InMemoryAuthority,
};
#[cfg(feature = "__dnssec")]
use crate::{
authority::{DnssecAuthority, Nsec3QueryInfo},
dnssec::NxProofKind,
proto::dnssec::{DnsSecResult, SigSigner, rdata::key::KEY},
};
pub struct FileAuthority {
in_memory: InMemoryAuthority,
#[cfg(feature = "metrics")]
metrics: StoreMetrics,
}
impl FileAuthority {
pub fn new(
origin: Name,
records: BTreeMap<RrKey, RecordSet>,
zone_type: ZoneType,
allow_axfr: bool,
#[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
) -> Result<Self, String> {
Ok(Self {
#[cfg(feature = "metrics")]
metrics: {
let new = StoreMetrics::new("file");
new.persistent.zone_records.increment(records.len() as f64);
new
},
in_memory: InMemoryAuthority::new(
origin,
records,
zone_type,
allow_axfr,
#[cfg(feature = "__dnssec")]
nx_proof_kind,
)?,
})
}
pub fn try_from_config(
origin: Name,
zone_type: ZoneType,
allow_axfr: bool,
root_dir: Option<&Path>,
config: &FileConfig,
#[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
) -> Result<Self, String> {
Self::try_from_config_internal(
origin,
zone_type,
allow_axfr,
root_dir,
config,
#[cfg(feature = "__dnssec")]
nx_proof_kind,
#[cfg(feature = "metrics")]
false,
)
}
pub(crate) fn try_from_config_internal(
origin: Name,
zone_type: ZoneType,
allow_axfr: bool,
root_dir: Option<&Path>,
config: &FileConfig,
#[cfg(feature = "__dnssec")] nx_proof_kind: Option<NxProofKind>,
#[cfg(feature = "metrics")] is_internal_load: bool,
) -> Result<Self, String> {
let root_dir_path = root_dir.map(PathBuf::from).unwrap_or_default();
let zone_path = root_dir_path.join(&config.zone_file_path);
info!("loading zone file: {:?}", zone_path);
let buf = fs::read_to_string(&zone_path).map_err(|e| {
format!(
"failed to read {}: {:?}",
config.zone_file_path.display(),
e
)
})?;
let (origin, records) = Parser::new(buf, Some(zone_path), Some(origin))
.parse()
.map_err(|e| {
format!(
"failed to parse {}: {:?}",
config.zone_file_path.display(),
e
)
})?;
info!(
"zone file loaded: {} with {} records",
origin,
records.len()
);
debug!("zone: {:#?}", records);
Ok(Self {
#[cfg(feature = "metrics")]
metrics: {
let new = StoreMetrics::new("file");
if !is_internal_load {
new.persistent.zone_records.increment(records.len() as f64);
}
new
},
in_memory: InMemoryAuthority::new(
origin,
records,
zone_type,
allow_axfr,
#[cfg(feature = "__dnssec")]
nx_proof_kind,
)?,
})
}
pub fn unwrap(self) -> InMemoryAuthority {
self.in_memory
}
}
impl Deref for FileAuthority {
type Target = InMemoryAuthority;
fn deref(&self) -> &Self::Target {
&self.in_memory
}
}
impl DerefMut for FileAuthority {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.in_memory
}
}
#[async_trait::async_trait]
impl Authority for FileAuthority {
type Lookup = <InMemoryAuthority as Authority>::Lookup;
fn zone_type(&self) -> ZoneType {
self.in_memory.zone_type()
}
fn is_axfr_allowed(&self) -> bool {
self.in_memory.is_axfr_allowed()
}
async fn update(&self, _update: &MessageRequest) -> UpdateResult<bool> {
use crate::proto::op::ResponseCode;
Err(ResponseCode::NotImp)
}
fn origin(&self) -> &LowerName {
self.in_memory.origin()
}
async fn lookup(
&self,
name: &LowerName,
rtype: RecordType,
lookup_options: LookupOptions,
) -> LookupControlFlow<Self::Lookup> {
let lookup = self.in_memory.lookup(name, rtype, lookup_options).await;
#[cfg(feature = "metrics")]
self.metrics.query.increment_lookup(&lookup);
lookup
}
async fn search(
&self,
request_info: RequestInfo<'_>,
lookup_options: LookupOptions,
) -> LookupControlFlow<Self::Lookup> {
let search = self.in_memory.search(request_info, lookup_options).await;
#[cfg(feature = "metrics")]
self.metrics.query.increment_lookup(&search);
search
}
async fn ns(&self, lookup_options: LookupOptions) -> LookupControlFlow<Self::Lookup> {
self.in_memory.ns(lookup_options).await
}
async fn get_nsec_records(
&self,
name: &LowerName,
lookup_options: LookupOptions,
) -> LookupControlFlow<Self::Lookup> {
self.in_memory.get_nsec_records(name, lookup_options).await
}
#[cfg(feature = "__dnssec")]
async fn get_nsec3_records(
&self,
info: Nsec3QueryInfo<'_>,
lookup_options: LookupOptions,
) -> LookupControlFlow<Self::Lookup> {
self.in_memory.get_nsec3_records(info, lookup_options).await
}
async fn soa(&self) -> LookupControlFlow<Self::Lookup> {
self.in_memory.soa().await
}
async fn soa_secure(&self, lookup_options: LookupOptions) -> LookupControlFlow<Self::Lookup> {
self.in_memory.soa_secure(lookup_options).await
}
#[cfg(feature = "__dnssec")]
fn nx_proof_kind(&self) -> Option<&NxProofKind> {
self.in_memory.nx_proof_kind()
}
}
#[cfg(feature = "__dnssec")]
#[async_trait::async_trait]
impl DnssecAuthority for FileAuthority {
async fn add_update_auth_key(&self, name: Name, key: KEY) -> DnsSecResult<()> {
self.in_memory.add_update_auth_key(name, key).await
}
async fn add_zone_signing_key(&self, signer: SigSigner) -> DnsSecResult<()> {
self.in_memory.add_zone_signing_key(signer).await
}
async fn secure_zone(&self) -> DnsSecResult<()> {
DnssecAuthority::secure_zone(&self.in_memory).await
}
}
#[derive(Deserialize, PartialEq, Eq, Debug)]
#[serde(deny_unknown_fields)]
pub struct FileConfig {
pub zone_file_path: PathBuf,
}
#[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::authority::ZoneType;
#[test]
fn test_load_zone() {
subscribe();
#[cfg(feature = "__dnssec")]
let config = FileConfig {
zone_file_path: PathBuf::from(
"../../tests/test-data/test_configs/dnssec/example.com.zone",
),
};
#[cfg(not(feature = "__dnssec"))]
let config = FileConfig {
zone_file_path: PathBuf::from("../../tests/test-data/test_configs/example.com.zone"),
};
let authority = FileAuthority::try_from_config(
Name::from_str("example.com.").unwrap(),
ZoneType::Primary,
false,
None,
&config,
#[cfg(feature = "__dnssec")]
Some(NxProofKind::Nsec),
)
.expect("failed to load file");
let lookup = block_on(Authority::lookup(
&authority,
&LowerName::from_str("www.example.com.").unwrap(),
RecordType::A,
LookupOptions::default(),
))
.expect("lookup failed");
match lookup
.into_iter()
.next()
.expect("A record not found in authority")
.data()
{
RData::A(ip) => assert_eq!(A::new(127, 0, 0, 1), *ip),
_ => panic!("wrong rdata type returned"),
}
let include_lookup = block_on(Authority::lookup(
&authority,
&LowerName::from_str("include.alias.example.com.").unwrap(),
RecordType::A,
LookupOptions::default(),
))
.expect("INCLUDE lookup failed");
match include_lookup
.into_iter()
.next()
.expect("A record not found in authority")
.data()
{
RData::A(ip) => assert_eq!(A::new(127, 0, 0, 5), *ip),
_ => panic!("wrong rdata type returned"),
}
}
}