Merge pull request #49 from ferrous-systems/ja-resolver-builder-take-2

refactor: use builder pattern in Resolver ctor
This commit is contained in:
Jorge Aparicio 2024-04-22 16:22:38 +02:00 committed by GitHub
commit 75de211a06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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();