make Recursor configurable via a "builder"

and make security-awareness opt-in
This commit is contained in:
Jorge Aparicio 2024-05-08 18:31:00 +02:00 committed by Benjamin Fry
parent f3a012cc36
commit 36258a8a03
4 changed files with 78 additions and 11 deletions

View File

@ -34,7 +34,7 @@ pub use error::{Error, ErrorKind};
pub use hickory_proto as proto; pub use hickory_proto as proto;
pub use hickory_resolver as resolver; pub use hickory_resolver as resolver;
pub use hickory_resolver::config::NameServerConfig; pub use hickory_resolver::config::NameServerConfig;
pub use recursor::Recursor; pub use recursor::{Recursor, RecursorBuilder};
fn is_security_aware() -> bool { fn is_security_aware() -> bool {
cfg!(feature = "dnssec") cfg!(feature = "dnssec")

View File

@ -37,6 +37,66 @@ use crate::{
/// Set of nameservers by the zone name /// Set of nameservers by the zone name
type NameServerCache<P> = LruCache<Name, RecursorPool<P>>; type NameServerCache<P> = LruCache<Name, RecursorPool<P>>;
/// A `Recursor` builder
#[derive(Clone, Copy)]
pub struct RecursorBuilder {
ns_cache_size: usize,
record_cache_size: usize,
#[cfg(feature = "dnssec")]
security_aware: bool,
}
impl Default for RecursorBuilder {
fn default() -> Self {
Self {
ns_cache_size: 1024,
record_cache_size: 1048576,
#[cfg(feature = "dnssec")]
security_aware: false,
}
}
}
impl RecursorBuilder {
/// Sets the size of the list of cached name servers
pub fn ns_cache_size(&mut self, size: usize) -> &mut Self {
self.ns_cache_size = size;
self
}
/// Sets the size of the list of cached records
pub fn record_cache_size(&mut self, size: usize) -> &mut Self {
self.record_cache_size = size;
self
}
/// Enables or disables (DNSSEC) security awareness
#[cfg(feature = "dnssec")]
pub fn security_aware(&mut self, security_aware: bool) -> &mut Self {
self.security_aware = security_aware;
self
}
/// Construct a new recursor using the list of NameServerConfigs for the root node list
///
/// # Panics
///
/// This will panic if the roots are empty.
pub fn build(&self, roots: impl Into<NameServerConfigGroup>) -> Result<Recursor, ResolveError> {
#[cfg(not(feature = "dnssec"))]
let security_aware = false;
#[cfg(feature = "dnssec")]
let security_aware = self.security_aware;
Recursor::build(
roots,
self.ns_cache_size,
self.record_cache_size,
security_aware,
)
}
}
/// A top down recursive resolver which operates off a list of roots for initial recursive requests. /// A top down recursive resolver which operates off a list of roots for initial recursive requests.
/// ///
/// This is the well known root nodes, referred to as hints in RFCs. See the IANA [Root Servers](https://www.iana.org/domains/root/servers) list. /// This is the well known root nodes, referred to as hints in RFCs. See the IANA [Root Servers](https://www.iana.org/domains/root/servers) list.
@ -44,18 +104,21 @@ pub struct Recursor {
roots: RecursorPool<TokioRuntimeProvider>, roots: RecursorPool<TokioRuntimeProvider>,
name_server_cache: Mutex<NameServerCache<TokioRuntimeProvider>>, name_server_cache: Mutex<NameServerCache<TokioRuntimeProvider>>,
record_cache: DnsLru, record_cache: DnsLru,
security_aware: bool,
} }
impl Recursor { impl Recursor {
/// Construct a new recursor using the list of NameServerConfigs for the root node list /// Short-hand for `RecursorBuilder::default`
/// #[allow(clippy::new_ret_no_self)]
/// # Panics pub fn new() -> RecursorBuilder {
/// RecursorBuilder::default()
/// This will panic if the roots are empty. }
pub fn new(
fn build(
roots: impl Into<NameServerConfigGroup>, roots: impl Into<NameServerConfigGroup>,
ns_cache_size: usize, ns_cache_size: usize,
record_cache_size: usize, record_cache_size: usize,
security_aware: bool,
) -> Result<Self, ResolveError> { ) -> Result<Self, ResolveError> {
// configure the hickory-resolver // configure the hickory-resolver
let roots: NameServerConfigGroup = roots.into(); let roots: NameServerConfigGroup = roots.into();
@ -74,6 +137,7 @@ impl Recursor {
roots, roots,
name_server_cache, name_server_cache,
record_cache, record_cache,
security_aware,
}) })
} }
@ -244,7 +308,7 @@ impl Recursor {
request_time: Instant, request_time: Instant,
query_has_dnssec_ok: bool, query_has_dnssec_ok: bool,
) -> Result<Lookup, Error> { ) -> Result<Lookup, Error> {
if crate::is_security_aware() { if self.security_aware {
// TODO RFC4035 section 4.5 recommends caching "each response as a single atomic entry // TODO RFC4035 section 4.5 recommends caching "each response as a single atomic entry
// containing the entire answer, including the named RRset and any associated DNSSEC // containing the entire answer, including the named RRset and any associated DNSSEC
// RRs" // RRs"
@ -289,7 +353,7 @@ impl Recursor {
let ns = ns.ok_or_else(|| Error::from(format!("no nameserver found for {zone}")))?; let ns = ns.ok_or_else(|| Error::from(format!("no nameserver found for {zone}")))?;
debug!("found zone {} for {}", ns.zone(), query); debug!("found zone {} for {}", ns.zone(), query);
let dnssec = if crate::is_security_aware() { let dnssec = if self.security_aware {
Dnssec::Aware { Dnssec::Aware {
query_has_dnssec_ok, query_has_dnssec_ok,
} }

View File

@ -73,7 +73,10 @@ impl RecursiveAuthority {
}); });
} }
let recursor = Recursor::new(roots, config.ns_cache_size, config.record_cache_size) let recursor = Recursor::new()
.ns_cache_size(config.ns_cache_size)
.record_cache_size(config.record_cache_size)
.build(roots)
.map_err(|e| format!("failed to initialize recursor: {e}"))?; .map_err(|e| format!("failed to initialize recursor: {e}"))?;
Ok(Self { Ok(Self {

View File

@ -157,7 +157,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let name = opts.domainname; let name = opts.domainname;
let ty = opts.ty; let ty = opts.ty;
let recursor = Recursor::new(roots, 1024, 1048576)?; let recursor = Recursor::new().build(roots)?;
// execute query // execute query
println!( println!(