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::record::{Record, RecordType};
use dns_test::zone_file::Root;
use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN};
use dns_test::{Network, Resolver, Result, FQDN};
#[test]
fn can_resolve() -> Result<()> {
@ -39,8 +39,11 @@ fn can_resolve() -> Result<()> {
eprintln!("root.zone:\n{}", root_ns.zone_file());
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())];
let resolver = Resolver::start(&dns_test::subject(), roots, &TrustAnchor::empty(), &network)?;
let resolver = Resolver::new(
&network,
Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr()),
)
.start(&dns_test::subject())?;
let resolver_ip_addr = resolver.ipv4_addr();
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());
let root_ns = root_ns.start()?;
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())];
let resolver = Resolver::start(&dns_test::subject(), roots, &TrustAnchor::empty(), &network)?;
let resolver = Resolver::new(
&network,
Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr()),
)
.start(&dns_test::subject())?;
let resolver_ip_addr = resolver.ipv4_addr();
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::tshark::{Capture, Direction};
use dns_test::zone_file::Root;
use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN};
use dns_test::{Network, Resolver, Result, FQDN};
#[test]
#[ignore]
fn edns_support() -> Result<()> {
let network = &Network::new()?;
let ns = NameServer::new(&dns_test::peer(), FQDN::ROOT, network)?.start()?;
let resolver = Resolver::start(
&dns_test::subject(),
&[Root::new(ns.fqdn().clone(), ns.ipv4_addr())],
&TrustAnchor::empty(),
network,
)?;
let resolver = Resolver::new(network, Root::new(ns.fqdn().clone(), ns.ipv4_addr()))
.start(&dns_test::subject())?;
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::record::{Record, RecordType};
use dns_test::zone_file::Root;
use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN};
use dns_test::{Network, Resolver, Result, FQDN};
#[ignore]
#[test]
@ -64,10 +64,13 @@ fn bad_signature_in_leaf_nameserver() -> Result<()> {
let root_ns = root_ns.start()?;
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())];
let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]);
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_key(root_ksk)
.trust_anchor_key(root_zsk)
.start(&dns_test::subject())?;
let resolver_addr = resolver.ipv4_addr();
let client = Client::new(&network)?;

View File

@ -24,10 +24,10 @@ fn can_validate_without_delegation() -> Result<()> {
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::start(&dns_test::subject(), roots, &trust_anchor, &network)?;
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()))
.trust_anchor(trust_anchor)
.start(&dns_test::subject())?;
let resolver_addr = resolver.ipv4_addr();
let client = Client::new(&network)?;
@ -37,7 +37,7 @@ fn can_validate_without_delegation() -> Result<()> {
assert!(output.status.is_noerror());
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"));
Ok(())
@ -91,10 +91,13 @@ fn can_validate_with_delegation() -> Result<()> {
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.clone(), root_zsk.clone()]);
let resolver = Resolver::start(&dns_test::subject(), roots, &trust_anchor, &network)?;
let trust_anchor = &TrustAnchor::from_iter([root_ksk, root_zsk]);
let resolver = Resolver::new(
&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 client = Client::new(&network)?;
@ -111,7 +114,7 @@ fn can_validate_with_delegation() -> Result<()> {
assert_eq!(needle_fqdn, a.fqdn);
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"));
Ok(())

View File

@ -44,13 +44,16 @@ fn main() -> Result<()> {
let root_zsk = root_ns.zone_signing_key().clone();
let root_ns = root_ns.start()?;
let roots = &[Root::new(root_ns.fqdn().clone(), root_ns.ipv4_addr())];
println!("DONE");
let trust_anchor = TrustAnchor::from_iter([root_ksk.clone(), root_zsk.clone()]);
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");
let resolver_addr = resolver.ipv4_addr();

View File

@ -3,6 +3,7 @@ use std::net::Ipv4Addr;
use crate::container::{Child, Container, Network};
use crate::implementation::{Config, Role};
use crate::record::DNSKEY;
use crate::trust_anchor::TrustAnchor;
use crate::tshark::Tshark;
use crate::zone_file::Root;
@ -15,67 +16,13 @@ pub struct Resolver {
}
impl Resolver {
/// Starts a DNS server in the recursive resolver role
///
/// This server is not an authoritative name server; it does not server a zone file to clients
///
/// # Panics
///
/// 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();
#[allow(clippy::new_ret_no_self)]
pub fn new(network: &Network, root: Root) -> ResolverSettings {
ResolverSettings {
network: network.clone(),
roots: vec![root],
trust_anchor: TrustAnchor::empty(),
}
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> {
@ -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)]
mod tests {
use crate::{name_server::NameServer, FQDN};
@ -122,12 +146,8 @@ mod tests {
fn terminate_unbound_works() -> Result<()> {
let network = Network::new()?;
let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
let resolver = Resolver::start(
&Implementation::Unbound,
&[Root::new(ns.fqdn().clone(), ns.ipv4_addr())],
&TrustAnchor::empty(),
&network,
)?;
let resolver = Resolver::new(&network, Root::new(ns.fqdn().clone(), ns.ipv4_addr()))
.start(&Implementation::Unbound)?;
let logs = resolver.terminate()?;
eprintln!("{logs}");
@ -140,12 +160,8 @@ mod tests {
fn terminate_bind_works() -> Result<()> {
let network = Network::new()?;
let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
let resolver = Resolver::start(
&Implementation::Bind,
&[Root::new(ns.fqdn().clone(), ns.ipv4_addr())],
&TrustAnchor::empty(),
&network,
)?;
let resolver = Resolver::new(&network, Root::new(ns.fqdn().clone(), ns.ipv4_addr()))
.start(&Implementation::Bind)?;
let logs = resolver.terminate()?;
eprintln!("{logs}");

View File

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

View File

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