#![cfg(feature = "resolver")]
use std::io;
#[cfg(feature = "__dnssec")]
use std::sync::Arc;
use serde::Deserialize;
use tracing::{debug, info};
#[cfg(feature = "metrics")]
use crate::store::metrics::QueryStoreMetrics;
#[cfg(feature = "__dnssec")]
use crate::{authority::Nsec3QueryInfo, dnssec::NxProofKind, proto::dnssec::TrustAnchors};
use crate::{
authority::{
Authority, LookupControlFlow, LookupError, LookupObject, LookupOptions, MessageRequest,
UpdateResult, ZoneType,
},
proto::{
op::ResponseCode,
rr::{LowerName, Name, Record, RecordType},
},
resolver::{
Resolver,
config::{NameServerConfigGroup, ResolveHosts, ResolverConfig, ResolverOpts},
lookup::Lookup as ResolverLookup,
name_server::{ConnectionProvider, TokioConnectionProvider},
},
server::RequestInfo,
};
pub struct ForwardAuthorityBuilder<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> ForwardAuthorityBuilder<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<ForwardAuthority<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);
options.validate = true;
}
(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));
options.validate = true;
}
(None, None) => {}
}
*resolver_builder.options_mut() = options;
let resolver = resolver_builder.build();
info!(%origin, "forward resolver configured");
Ok(ForwardAuthority {
origin: origin.into(),
resolver,
#[cfg(feature = "metrics")]
metrics: QueryStoreMetrics::new("forwarder"),
})
}
}
pub struct ForwardAuthority<P: ConnectionProvider = TokioConnectionProvider> {
origin: LowerName,
resolver: Resolver<P>,
#[cfg(feature = "metrics")]
metrics: QueryStoreMetrics,
}
impl<P: ConnectionProvider> ForwardAuthority<P> {
pub fn builder(runtime: P) -> Result<ForwardAuthorityBuilder<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_vec().into(),
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) -> ForwardAuthorityBuilder<P> {
ForwardAuthorityBuilder {
origin: Name::root(),
config,
domain: None,
search: vec![],
runtime,
#[cfg(feature = "__dnssec")]
trust_anchor: None,
}
}
}
impl ForwardAuthority<TokioConnectionProvider> {
pub fn builder_tokio(
config: ForwardConfig,
) -> ForwardAuthorityBuilder<TokioConnectionProvider> {
Self::builder_with_config(config, TokioConnectionProvider::default())
}
}
#[async_trait::async_trait]
impl<P: ConnectionProvider> Authority for ForwardAuthority<P> {
type Lookup = ForwardLookup;
fn zone_type(&self) -> ZoneType {
ZoneType::External
}
fn is_axfr_allowed(&self) -> bool {
false
}
fn can_validate_dnssec(&self) -> bool {
self.resolver.options().validate
}
async fn update(&self, _update: &MessageRequest) -> UpdateResult<bool> {
Err(ResponseCode::NotImp)
}
fn origin(&self) -> &LowerName {
&self.origin
}
async fn lookup(
&self,
name: &LowerName,
rtype: RecordType,
_lookup_options: LookupOptions,
) -> LookupControlFlow<Self::Lookup> {
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::*;
let lookup = match self.resolver.lookup(name, rtype).await {
Ok(lookup) => Continue(Ok(ForwardLookup(lookup))),
Err(e) => Continue(Err(LookupError::from(e))),
};
#[cfg(feature = "metrics")]
self.metrics.increment_lookup(&lookup);
lookup
}
async fn search(
&self,
request_info: RequestInfo<'_>,
lookup_options: LookupOptions,
) -> LookupControlFlow<Self::Lookup> {
self.lookup(
request_info.query.name(),
request_info.query.query_type(),
lookup_options,
)
.await
}
async fn get_nsec_records(
&self,
_name: &LowerName,
_lookup_options: LookupOptions,
) -> LookupControlFlow<Self::Lookup> {
LookupControlFlow::Continue(Err(LookupError::from(io::Error::new(
io::ErrorKind::Other,
"Getting NSEC records is unimplemented for the forwarder",
))))
}
#[cfg(feature = "__dnssec")]
async fn get_nsec3_records(
&self,
_info: Nsec3QueryInfo<'_>,
_lookup_options: LookupOptions,
) -> LookupControlFlow<Self::Lookup> {
LookupControlFlow::Continue(Err(LookupError::from(io::Error::new(
io::ErrorKind::Other,
"getting NSEC3 records is unimplemented for the forwarder",
))))
}
#[cfg(feature = "__dnssec")]
fn nx_proof_kind(&self) -> Option<&NxProofKind> {
None
}
}
pub struct ForwardLookup(pub ResolverLookup);
impl LookupObject for ForwardLookup {
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a> {
Box::new(self.0.record_iter())
}
fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>> {
None
}
}
#[derive(Clone, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ForwardConfig {
pub name_servers: NameServerConfigGroup,
pub options: Option<ResolverOpts>,
}