#![cfg(feature = "resolver")]
use std::io;
#[cfg(feature = "__dnssec")]
use std::sync::Arc;
use serde::Deserialize;
use tracing::{debug, info};
#[cfg(feature = "__dnssec")]
use crate::{dnssec::NxProofKind, proto::dnssec::TrustAnchors, zone_handler::Nsec3QueryInfo};
use crate::{
net::runtime::TokioRuntimeProvider,
proto::rr::{LowerName, Name, RecordType, TSigResponseContext},
resolver::{
ConnectionProvider, Resolver,
config::{NameServerConfig, ResolveHosts, ResolverConfig, ResolverOpts},
},
server::{Request, RequestInfo},
zone_handler::{
AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler,
ZoneType,
},
};
pub struct ForwardZoneHandlerBuilder<P: ConnectionProvider> {
origin: Name,
config: ForwardConfig,
domain: Option<Name>,
search: Vec<Name>,
runtime: P,
#[cfg(feature = "__dnssec")]
trust_anchor: Option<Arc<TrustAnchors>>,
}
impl<P: ConnectionProvider> ForwardZoneHandlerBuilder<P> {
pub fn with_origin(mut self, origin: Name) -> Self {
self.origin = origin;
self
}
#[cfg(feature = "__dnssec")]
pub fn with_trust_anchor(mut self, trust_anchor: Arc<TrustAnchors>) -> Self {
self.trust_anchor = Some(trust_anchor);
self
}
pub fn options_mut(&mut self) -> &mut ResolverOpts {
self.config
.options
.get_or_insert_with(ResolverOpts::default)
}
pub fn with_domain(mut self, domain: Name) -> Self {
self.domain = Some(domain);
self
}
pub fn with_search(mut self, search: Vec<Name>) -> Self {
self.search = search;
self
}
pub fn build(self) -> Result<ForwardZoneHandler<P>, String> {
let Self {
origin,
config,
domain,
search,
runtime,
#[cfg(feature = "__dnssec")]
trust_anchor,
} = self;
info!(%origin, "loading forwarder config");
let name_servers = config.name_servers;
let mut options = config.options.unwrap_or_default();
if !options.preserve_intermediates {
tracing::warn!(
"preserve_intermediates set to false, which is invalid \
for a forwarder; switching to true"
);
options.preserve_intermediates = true;
}
if options.use_hosts_file == ResolveHosts::Auto {
options.use_hosts_file = ResolveHosts::Never;
}
let config = ResolverConfig::from_parts(domain, search, name_servers);
let mut resolver_builder = Resolver::builder_with_config(config, runtime);
#[cfg(feature = "__dnssec")]
match (trust_anchor, &options.trust_anchor) {
(Some(trust_anchor), _) => {
resolver_builder = resolver_builder.with_trust_anchor(trust_anchor);
}
(None, Some(path)) => {
let trust_anchor = TrustAnchors::from_file(path).map_err(|err| err.to_string())?;
resolver_builder = resolver_builder.with_trust_anchor(Arc::new(trust_anchor));
}
(None, None) => {}
}
*resolver_builder.options_mut() = options;
let resolver = resolver_builder.build().map_err(|err| err.to_string())?;
info!(%origin, "forward resolver configured");
Ok(ForwardZoneHandler {
origin: origin.into(),
resolver,
})
}
}
pub struct ForwardZoneHandler<P: ConnectionProvider = TokioRuntimeProvider> {
origin: LowerName,
resolver: Resolver<P>,
}
impl<P: ConnectionProvider> ForwardZoneHandler<P> {
pub fn builder(runtime: P) -> Result<ForwardZoneHandlerBuilder<P>, String> {
let (resolver_config, options) = hickory_resolver::system_conf::read_system_conf()
.map_err(|e| format!("error reading system configuration: {e}"))?;
let forward_config = ForwardConfig {
name_servers: resolver_config.name_servers().to_owned(),
options: Some(options),
};
let mut builder = Self::builder_with_config(forward_config, runtime);
if let Some(domain) = resolver_config.domain() {
builder = builder.with_domain(domain.clone());
}
builder = builder.with_search(resolver_config.search().to_vec());
Ok(builder)
}
pub fn builder_with_config(config: ForwardConfig, runtime: P) -> ForwardZoneHandlerBuilder<P> {
ForwardZoneHandlerBuilder {
origin: Name::root(),
config,
domain: None,
search: vec![],
runtime,
#[cfg(feature = "__dnssec")]
trust_anchor: None,
}
}
}
impl ForwardZoneHandler<TokioRuntimeProvider> {
pub fn builder_tokio(config: ForwardConfig) -> ForwardZoneHandlerBuilder<TokioRuntimeProvider> {
Self::builder_with_config(config, TokioRuntimeProvider::default())
}
}
#[async_trait::async_trait]
impl<P: ConnectionProvider> ZoneHandler for ForwardZoneHandler<P> {
fn zone_type(&self) -> ZoneType {
ZoneType::External
}
fn axfr_policy(&self) -> AxfrPolicy {
AxfrPolicy::Deny
}
fn can_validate_dnssec(&self) -> bool {
#[cfg(feature = "__dnssec")]
{
self.resolver.options().validate
}
#[cfg(not(feature = "__dnssec"))]
{
false
}
}
fn origin(&self) -> &LowerName {
&self.origin
}
async fn lookup(
&self,
name: &LowerName,
rtype: RecordType,
_request_info: Option<&RequestInfo<'_>>,
_lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> {
debug_assert!(self.origin.zone_of(name));
debug!("forwarding lookup: {} {}", name, rtype);
let mut name: Name = name.clone().into();
name.set_fqdn(false);
use LookupControlFlow::*;
match self.resolver.lookup(name, rtype).await {
Ok(lookup) => Continue(Ok(AuthLookup::from(lookup))),
Err(e) => Continue(Err(LookupError::from(e))),
}
}
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 forwarder",
))))
}
#[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 forwarder",
))))
}
#[cfg(feature = "__dnssec")]
fn nx_proof_kind(&self) -> Option<&NxProofKind> {
None
}
#[cfg(feature = "metrics")]
fn metrics_label(&self) -> &'static str {
"forwarder"
}
}
#[derive(Clone, Deserialize, Debug, Default)]
#[serde(deny_unknown_fields)]
pub struct ForwardConfig {
pub name_servers: Vec<NameServerConfig>,
pub options: Option<ResolverOpts>,
}