#![cfg(feature = "recursor")]
use std::{io, path::Path, time::Instant};
#[cfg(all(feature = "toml", any(feature = "__tls", feature = "__quic")))]
use crate::resolver::{OpportunisticEncryptionStatePersistTask, config::OpportunisticEncryption};
#[cfg(feature = "__dnssec")]
use crate::{dnssec::NxProofKind, zone_handler::Nsec3QueryInfo};
use crate::{
net::runtime::RuntimeProvider,
proto::{
op::Query,
rr::{LowerName, Name, RecordType},
},
resolver::recursor::{RecursiveConfig, Recursor},
server::{Request, RequestInfo},
zone_handler::{
AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler,
ZoneType,
},
};
use hickory_proto::rr::TSigResponseContext;
use tracing::{debug, info};
pub struct RecursiveZoneHandler<P: RuntimeProvider> {
origin: LowerName,
recursor: Recursor<P>,
#[allow(dead_code)] opportunistic_encryption_persistence_task: Option<P::Handle>,
}
impl<P: RuntimeProvider> RecursiveZoneHandler<P> {
pub async fn try_from_config(
origin: Name,
_zone_type: ZoneType,
config: RecursiveConfig,
root_dir: Option<&Path>,
conn_provider: P,
) -> Result<Self, String> {
info!("loading recursor config: {}", origin);
#[cfg(all(feature = "toml", any(feature = "__tls", feature = "__quic")))]
let persistence_config = match &config.options.opportunistic_encryption {
OpportunisticEncryption::Enabled { config } => config.persistence.clone(),
_ => None,
};
let recursor = Recursor::from_config(config, root_dir, conn_provider.clone())
.map_err(|e| format!("failed to build recursor: {e}"))?;
Ok(Self {
origin: origin.into(),
#[cfg(all(feature = "toml", any(feature = "__tls", feature = "__quic")))]
opportunistic_encryption_persistence_task: match persistence_config {
Some(config) => {
OpportunisticEncryptionStatePersistTask::<P::Timer>::start(
config,
recursor.pool_context(),
conn_provider.clone(),
)
.await?
}
_ => None,
},
#[cfg(not(all(feature = "toml", any(feature = "__tls", feature = "__quic"))))]
opportunistic_encryption_persistence_task: None,
recursor,
})
}
}
#[async_trait::async_trait]
impl<P: RuntimeProvider> ZoneHandler for RecursiveZoneHandler<P> {
fn zone_type(&self) -> ZoneType {
ZoneType::External
}
fn axfr_policy(&self) -> AxfrPolicy {
AxfrPolicy::Deny
}
fn can_validate_dnssec(&self) -> bool {
self.recursor.is_validating()
}
fn origin(&self) -> &LowerName {
&self.origin
}
async fn lookup(
&self,
name: &LowerName,
rtype: RecordType,
_request_info: Option<&RequestInfo<'_>>,
lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> {
debug!("recursive lookup: {} {}", name, rtype);
let query = Query::query(name.into(), rtype);
let now = Instant::now();
let result = self
.recursor
.resolve(query.clone(), now, lookup_options.dnssec_ok)
.await;
let response = match result {
Ok(response) => response,
Err(error) => return LookupControlFlow::Continue(Err(LookupError::from(error))),
};
LookupControlFlow::Continue(Ok(AuthLookup::Response(response)))
}
async fn search(
&self,
request: &Request,
lookup_options: LookupOptions,
) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
let request_info = match request.request_info() {
Ok(info) => info,
Err(e) => return (LookupControlFlow::Break(Err(e)), None),
};
(
self.lookup(
request_info.query.name(),
request_info.query.query_type(),
Some(&request_info),
lookup_options,
)
.await,
None,
)
}
async fn nsec_records(
&self,
_name: &LowerName,
_lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> {
LookupControlFlow::Continue(Err(LookupError::from(io::Error::other(
"Getting NSEC records is unimplemented for the recursor",
))))
}
#[cfg(feature = "__dnssec")]
async fn nsec3_records(
&self,
_info: Nsec3QueryInfo<'_>,
_lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> {
LookupControlFlow::Continue(Err(LookupError::from(io::Error::other(
"getting NSEC3 records is unimplemented for the recursor",
))))
}
#[cfg(feature = "__dnssec")]
fn nx_proof_kind(&self) -> Option<&NxProofKind> {
None
}
#[cfg(feature = "metrics")]
fn metrics_label(&self) -> &'static str {
"recursive"
}
}