refactor: use builder pattern in Resolver ctor

the `start` constructor's parameter list was getting long and we want to
add even more configuration options, like EDE, in the future.

using the builder pattern lets us introduce new settings without
breaking changes
This commit is contained in:
Jorge Aparicio 2024-03-05 14:41:45 +01:00
parent 63c95fd0db
commit 70245e7ff8
8 changed files with 138 additions and 109 deletions

View File

@ -4,7 +4,7 @@ use dns_test::client::{Client, DigSettings};
use dns_test::name_server::NameServer; use dns_test::name_server::NameServer;
use dns_test::record::{Record, RecordType}; use dns_test::record::{Record, RecordType};
use dns_test::zone_file::Root; use dns_test::zone_file::Root;
use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN}; use dns_test::{Network, Resolver, Result, FQDN};
#[test] #[test]
fn can_resolve() -> Result<()> { fn can_resolve() -> Result<()> {
@ -39,8 +39,11 @@ fn can_resolve() -> Result<()> {
eprintln!("root.zone:\n{}", root_ns.zone_file()); eprintln!("root.zone:\n{}", root_ns.zone_file());
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())]; let resolver = Resolver::new(
let resolver = Resolver::start(&dns_test::subject(), roots, &TrustAnchor::empty(), &network)?; &network,
Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr()),
)
.start(&dns_test::subject())?;
let resolver_ip_addr = resolver.ipv4_addr(); let resolver_ip_addr = resolver.ipv4_addr();
let client = Client::new(&network)?; let client = Client::new(&network)?;
@ -85,8 +88,11 @@ fn nxdomain() -> Result<()> {
root_ns.referral(FQDN::COM, com_ns.fqdn().clone(), com_ns.ipv4_addr()); root_ns.referral(FQDN::COM, com_ns.fqdn().clone(), com_ns.ipv4_addr());
let root_ns = root_ns.start()?; let root_ns = root_ns.start()?;
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())]; let resolver = Resolver::new(
let resolver = Resolver::start(&dns_test::subject(), roots, &TrustAnchor::empty(), &network)?; &network,
Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr()),
)
.start(&dns_test::subject())?;
let resolver_ip_addr = resolver.ipv4_addr(); let resolver_ip_addr = resolver.ipv4_addr();
let client = Client::new(&network)?; let client = Client::new(&network)?;

View File

@ -3,19 +3,15 @@ use dns_test::name_server::NameServer;
use dns_test::record::RecordType; use dns_test::record::RecordType;
use dns_test::tshark::{Capture, Direction}; use dns_test::tshark::{Capture, Direction};
use dns_test::zone_file::Root; use dns_test::zone_file::Root;
use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN}; use dns_test::{Network, Resolver, Result, FQDN};
#[test] #[test]
#[ignore] #[ignore]
fn edns_support() -> Result<()> { fn edns_support() -> Result<()> {
let network = &Network::new()?; let network = &Network::new()?;
let ns = NameServer::new(&dns_test::peer(), FQDN::ROOT, network)?.start()?; let ns = NameServer::new(&dns_test::peer(), FQDN::ROOT, network)?.start()?;
let resolver = Resolver::start( let resolver = Resolver::new(network, Root::new(ns.fqdn().clone(), ns.ipv4_addr()))
&dns_test::subject(), .start(&dns_test::subject())?;
&[Root::new(ns.fqdn().clone(), ns.ipv4_addr())],
&TrustAnchor::empty(),
network,
)?;
let mut tshark = resolver.eavesdrop()?; let mut tshark = resolver.eavesdrop()?;

View File

@ -5,7 +5,7 @@ use dns_test::client::{Client, DigSettings};
use dns_test::name_server::NameServer; use dns_test::name_server::NameServer;
use dns_test::record::{Record, RecordType}; use dns_test::record::{Record, RecordType};
use dns_test::zone_file::Root; use dns_test::zone_file::Root;
use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN}; use dns_test::{Network, Resolver, Result, FQDN};
#[ignore] #[ignore]
#[test] #[test]
@ -64,10 +64,13 @@ fn bad_signature_in_leaf_nameserver() -> Result<()> {
let root_ns = root_ns.start()?; let root_ns = root_ns.start()?;
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())]; let resolver = Resolver::new(
&network,
let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]); Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr()),
let resolver = Resolver::start(&dns_test::subject(), roots, &trust_anchor, &network)?; )
.trust_anchor_key(root_ksk)
.trust_anchor_key(root_zsk)
.start(&dns_test::subject())?;
let resolver_addr = resolver.ipv4_addr(); let resolver_addr = resolver.ipv4_addr();
let client = Client::new(&network)?; let client = Client::new(&network)?;

View File

@ -24,10 +24,10 @@ fn can_validate_without_delegation() -> Result<()> {
eprintln!("root.zone:\n{}", ns.zone_file()); eprintln!("root.zone:\n{}", ns.zone_file());
let roots = &[Root::new(ns.fqdn().clone(), ns.ipv4_addr())]; let trust_anchor = &TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]);
let resolver = Resolver::new(&network, Root::new(ns.fqdn().clone(), ns.ipv4_addr()))
let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]); .trust_anchor(trust_anchor)
let resolver = Resolver::start(&dns_test::subject(), roots, &trust_anchor, &network)?; .start(&dns_test::subject())?;
let resolver_addr = resolver.ipv4_addr(); let resolver_addr = resolver.ipv4_addr();
let client = Client::new(&network)?; let client = Client::new(&network)?;
@ -37,7 +37,7 @@ fn can_validate_without_delegation() -> Result<()> {
assert!(output.status.is_noerror()); assert!(output.status.is_noerror());
assert!(output.flags.authenticated_data); assert!(output.flags.authenticated_data);
let output = client.delv(resolver_addr, RecordType::SOA, &FQDN::ROOT, &trust_anchor)?; let output = client.delv(resolver_addr, RecordType::SOA, &FQDN::ROOT, trust_anchor)?;
assert!(output.starts_with("; fully validated")); assert!(output.starts_with("; fully validated"));
Ok(()) Ok(())
@ -91,10 +91,13 @@ fn can_validate_with_delegation() -> Result<()> {
eprintln!("root.zone:\n{}", root_ns.zone_file()); eprintln!("root.zone:\n{}", root_ns.zone_file());
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())]; let trust_anchor = &TrustAnchor::from_iter([root_ksk, root_zsk]);
let resolver = Resolver::new(
let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]); &network,
let resolver = Resolver::start(&dns_test::subject(), roots, &trust_anchor, &network)?; Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr()),
)
.trust_anchor(trust_anchor)
.start(&dns_test::subject())?;
let resolver_addr = resolver.ipv4_addr(); let resolver_addr = resolver.ipv4_addr();
let client = Client::new(&network)?; let client = Client::new(&network)?;
@ -111,7 +114,7 @@ fn can_validate_with_delegation() -> Result<()> {
assert_eq!(needle_fqdn, a.fqdn); assert_eq!(needle_fqdn, a.fqdn);
assert_eq!(expected_ipv4_addr, a.ipv4_addr); assert_eq!(expected_ipv4_addr, a.ipv4_addr);
let output = client.delv(resolver_addr, RecordType::A, &needle_fqdn, &trust_anchor)?; let output = client.delv(resolver_addr, RecordType::A, &needle_fqdn, trust_anchor)?;
assert!(output.starts_with("; fully validated")); assert!(output.starts_with("; fully validated"));
Ok(()) Ok(())

View File

@ -44,13 +44,16 @@ fn main() -> Result<()> {
let root_zsk = root_ns.zone_signing_key().clone(); let root_zsk = root_ns.zone_signing_key().clone();
let root_ns = root_ns.start()?; let root_ns = root_ns.start()?;
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())];
println!("DONE"); println!("DONE");
let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]); let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]);
println!("building docker image..."); println!("building docker image...");
let resolver = Resolver::start(&dns_test::subject(), roots, &trust_anchor, &network)?; let resolver = Resolver::new(
&network,
Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr()),
)
.trust_anchor(&trust_anchor)
.start(&dns_test::subject())?;
println!("DONE\n\n"); println!("DONE\n\n");
let resolver_addr = resolver.ipv4_addr(); let resolver_addr = resolver.ipv4_addr();

View File

@ -3,6 +3,7 @@ use std::net::Ipv4Addr;
use crate::container::{Child, Container, Network}; use crate::container::{Child, Container, Network};
use crate::implementation::{Config, Role}; use crate::implementation::{Config, Role};
use crate::record::DNSKEY;
use crate::trust_anchor::TrustAnchor; use crate::trust_anchor::TrustAnchor;
use crate::tshark::Tshark; use crate::tshark::Tshark;
use crate::zone_file::Root; use crate::zone_file::Root;
@ -15,67 +16,13 @@ pub struct Resolver {
} }
impl Resolver { impl Resolver {
/// Starts a DNS server in the recursive resolver role #[allow(clippy::new_ret_no_self)]
/// pub fn new(network: &Network, root: Root) -> ResolverSettings {
/// This server is not an authoritative name server; it does not server a zone file to clients ResolverSettings {
/// network: network.clone(),
/// # Panics roots: vec![root],
/// trust_anchor: TrustAnchor::empty(),
/// This constructor panics if `roots` is an empty slice
pub fn start(
implementation: &Implementation,
roots: &[Root],
trust_anchor: &TrustAnchor,
network: &Network,
) -> Result<Self> {
assert!(
!roots.is_empty(),
"must configure at least one local root server"
);
let image = implementation.clone().into();
let container = Container::run(&image, network)?;
let mut hints = String::new();
for root in roots {
writeln!(hints, "{root}").unwrap();
} }
container.cp("/etc/root.hints", &hints)?;
let use_dnssec = !trust_anchor.is_empty();
let config = Config::Resolver {
use_dnssec,
netmask: network.netmask(),
};
container.cp(
implementation.conf_file_path(config.role()),
&implementation.format_config(config),
)?;
if use_dnssec {
let path = if implementation.is_bind() {
"/etc/bind/bind.keys"
} else {
"/etc/trusted-key.key"
};
let contents = if implementation.is_bind() {
trust_anchor.delv()
} else {
trust_anchor.to_string()
};
container.cp(path, &contents)?;
}
let child = container.spawn(implementation.cmd_args(config.role()))?;
Ok(Self {
child,
container,
implementation: implementation.clone(),
})
} }
pub fn eavesdrop(&self) -> Result<Tshark> { pub fn eavesdrop(&self) -> Result<Tshark> {
@ -112,6 +59,83 @@ kill -TERM $(cat {pidfile})"
} }
} }
pub struct ResolverSettings {
network: Network,
roots: Vec<Root>,
trust_anchor: TrustAnchor,
}
impl ResolverSettings {
/// Starts a DNS server in the recursive resolver role
///
/// This server is not an authoritative name server; it does not serve a zone file to clients
pub fn start(&self, implementation: &Implementation) -> Result<Resolver> {
let image = implementation.clone().into();
let container = Container::run(&image, &self.network)?;
let mut hints = String::new();
for root in &self.roots {
writeln!(hints, "{root}").unwrap();
}
container.cp("/etc/root.hints", &hints)?;
let use_dnssec = !self.trust_anchor.is_empty();
let config = Config::Resolver {
use_dnssec,
netmask: self.network.netmask(),
};
container.cp(
implementation.conf_file_path(config.role()),
&implementation.format_config(config),
)?;
if use_dnssec {
let path = if implementation.is_bind() {
"/etc/bind/bind.keys"
} else {
"/etc/trusted-key.key"
};
let contents = if implementation.is_bind() {
self.trust_anchor.delv()
} else {
self.trust_anchor.to_string()
};
container.cp(path, &contents)?;
}
let child = container.spawn(implementation.cmd_args(config.role()))?;
Ok(Resolver {
child,
container,
implementation: implementation.clone(),
})
}
/// Adds a root hint
pub fn root(&mut self, root: Root) -> &mut Self {
self.roots.push(root);
self
}
/// Adds a DNSKEY record to the trust anchor
pub fn trust_anchor_key(&mut self, key: DNSKEY) -> &mut Self {
self.trust_anchor.add(key.clone());
self
}
/// Adds all the keys in the `other` trust anchor to ours
pub fn trust_anchor(&mut self, other: &TrustAnchor) -> &mut Self {
for key in other.keys() {
self.trust_anchor.add(key.clone());
}
self
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{name_server::NameServer, FQDN}; use crate::{name_server::NameServer, FQDN};
@ -122,12 +146,8 @@ mod tests {
fn terminate_unbound_works() -> Result<()> { fn terminate_unbound_works() -> Result<()> {
let network = Network::new()?; let network = Network::new()?;
let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?; let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
let resolver = Resolver::start( let resolver = Resolver::new(&network, Root::new(ns.fqdn().clone(), ns.ipv4_addr()))
&Implementation::Unbound, .start(&Implementation::Unbound)?;
&[Root::new(ns.fqdn().clone(), ns.ipv4_addr())],
&TrustAnchor::empty(),
&network,
)?;
let logs = resolver.terminate()?; let logs = resolver.terminate()?;
eprintln!("{logs}"); eprintln!("{logs}");
@ -140,12 +160,8 @@ mod tests {
fn terminate_bind_works() -> Result<()> { fn terminate_bind_works() -> Result<()> {
let network = Network::new()?; let network = Network::new()?;
let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?; let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
let resolver = Resolver::start( let resolver = Resolver::new(&network, Root::new(ns.fqdn().clone(), ns.ipv4_addr()))
&Implementation::Bind, .start(&Implementation::Bind)?;
&[Root::new(ns.fqdn().clone(), ns.ipv4_addr())],
&TrustAnchor::empty(),
&network,
)?;
let logs = resolver.terminate()?; let logs = resolver.terminate()?;
eprintln!("{logs}"); eprintln!("{logs}");

View File

@ -20,6 +20,10 @@ impl TrustAnchor {
self self
} }
pub(crate) fn keys(&self) -> &[DNSKEY] {
&self.keys
}
/// formats the `TrustAnchor` in the format `delv` expects /// formats the `TrustAnchor` in the format `delv` expects
pub(super) fn delv(&self) -> String { pub(super) fn delv(&self) -> String {
let mut buf = "trust-anchors {".to_string(); let mut buf = "trust-anchors {".to_string();

View File

@ -248,7 +248,7 @@ mod tests {
use crate::name_server::NameServer; use crate::name_server::NameServer;
use crate::record::{Record, RecordType}; use crate::record::{Record, RecordType};
use crate::zone_file::Root; use crate::zone_file::Root;
use crate::{Implementation, Network, Resolver, TrustAnchor, FQDN}; use crate::{Implementation, Network, Resolver, FQDN};
use super::*; use super::*;
@ -310,13 +310,11 @@ mod tests {
root_ns.referral(FQDN::COM, com_ns.fqdn().clone(), com_ns.ipv4_addr()); root_ns.referral(FQDN::COM, com_ns.fqdn().clone(), com_ns.ipv4_addr());
let root_ns = root_ns.start()?; let root_ns = root_ns.start()?;
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())]; let resolver = Resolver::new(
let resolver = Resolver::start(
&Implementation::Unbound,
roots,
&TrustAnchor::empty(),
network, network,
)?; Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr()),
)
.start(&Implementation::Unbound)?;
let mut tshark = resolver.eavesdrop()?; let mut tshark = resolver.eavesdrop()?;
let resolver_addr = resolver.ipv4_addr(); let resolver_addr = resolver.ipv4_addr();