diff --git a/packages/conformance-tests/src/resolver/dns/scenarios.rs b/packages/conformance-tests/src/resolver/dns/scenarios.rs index 9dcdcbd2..b3c4da7f 100644 --- a/packages/conformance-tests/src/resolver/dns/scenarios.rs +++ b/packages/conformance-tests/src/resolver/dns/scenarios.rs @@ -12,10 +12,11 @@ fn can_resolve() -> Result<()> { let needle_fqdn = FQDN("example.nameservers.com.")?; let network = Network::new()?; - let mut root_ns = NameServer::new(FQDN::ROOT, &network)?; - let mut com_ns = NameServer::new(FQDN::COM, &network)?; + let mut root_ns = NameServer::new(dns_test::peer(), FQDN::ROOT, &network)?; + let mut com_ns = NameServer::new(dns_test::peer(), FQDN::COM, &network)?; - let mut nameservers_ns = NameServer::new(FQDN("nameservers.com.")?, &network)?; + let mut nameservers_ns = + NameServer::new(dns_test::peer(), FQDN("nameservers.com.")?, &network)?; nameservers_ns .a(root_ns.fqdn().clone(), root_ns.ipv4_addr()) .a(com_ns.fqdn().clone(), com_ns.ipv4_addr()) @@ -68,10 +69,11 @@ fn nxdomain() -> Result<()> { let needle_fqdn = FQDN("unicorn.nameservers.com.")?; let network = Network::new()?; - let mut root_ns = NameServer::new(FQDN::ROOT, &network)?; - let mut com_ns = NameServer::new(FQDN::COM, &network)?; + let mut root_ns = NameServer::new(dns_test::peer(), FQDN::ROOT, &network)?; + let mut com_ns = NameServer::new(dns_test::peer(), FQDN::COM, &network)?; - let mut nameservers_ns = NameServer::new(FQDN("nameservers.com.")?, &network)?; + let mut nameservers_ns = + NameServer::new(dns_test::peer(), FQDN("nameservers.com.")?, &network)?; nameservers_ns .a(root_ns.fqdn().clone(), root_ns.ipv4_addr()) .a(com_ns.fqdn().clone(), com_ns.ipv4_addr()); diff --git a/packages/conformance-tests/src/resolver/dnssec/rfc4035/section_4/section_4_1.rs b/packages/conformance-tests/src/resolver/dnssec/rfc4035/section_4/section_4_1.rs index 533e31b3..9772e309 100644 --- a/packages/conformance-tests/src/resolver/dnssec/rfc4035/section_4/section_4_1.rs +++ b/packages/conformance-tests/src/resolver/dnssec/rfc4035/section_4/section_4_1.rs @@ -9,7 +9,7 @@ use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN}; #[ignore] fn edns_support() -> Result<()> { let network = &Network::new()?; - let ns = NameServer::new(FQDN::ROOT, network)?.start()?; + 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())], diff --git a/packages/conformance-tests/src/resolver/dnssec/scenarios.rs b/packages/conformance-tests/src/resolver/dnssec/scenarios.rs index 1300d9b4..554e7417 100644 --- a/packages/conformance-tests/src/resolver/dnssec/scenarios.rs +++ b/packages/conformance-tests/src/resolver/dnssec/scenarios.rs @@ -11,7 +11,7 @@ use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN}; #[test] fn can_validate_without_delegation() -> Result<()> { let network = Network::new()?; - let mut ns = NameServer::new(FQDN::ROOT, &network)?; + let mut ns = NameServer::new(dns_test::peer(), FQDN::ROOT, &network)?; ns.a(ns.fqdn().clone(), ns.ipv4_addr()); let ns = ns.sign()?; @@ -55,10 +55,11 @@ fn can_validate_with_delegation() -> Result<()> { let needle_fqdn = FQDN("example.nameservers.com.")?; let network = Network::new()?; - let mut root_ns = NameServer::new(FQDN::ROOT, &network)?; - let mut com_ns = NameServer::new(FQDN::COM, &network)?; + let mut root_ns = NameServer::new(dns_test::peer(), FQDN::ROOT, &network)?; + let mut com_ns = NameServer::new(dns_test::peer(), FQDN::COM, &network)?; - let mut nameservers_ns = NameServer::new(FQDN("nameservers.com.")?, &network)?; + let mut nameservers_ns = + NameServer::new(dns_test::peer(), FQDN("nameservers.com.")?, &network)?; nameservers_ns .a(root_ns.fqdn().clone(), root_ns.ipv4_addr()) .a(com_ns.fqdn().clone(), com_ns.ipv4_addr()) diff --git a/packages/dns-test/src/client.rs b/packages/dns-test/src/client.rs index e283498d..069f2562 100644 --- a/packages/dns-test/src/client.rs +++ b/packages/dns-test/src/client.rs @@ -1,10 +1,10 @@ use core::str::FromStr; use std::net::Ipv4Addr; -use crate::container::{Container, Network}; +use crate::container::{Container, Image, Network}; use crate::record::{Record, RecordType}; use crate::trust_anchor::TrustAnchor; -use crate::{Error, Implementation, Result, FQDN}; +use crate::{Error, Result, FQDN}; pub struct Client { inner: Container, @@ -13,7 +13,7 @@ pub struct Client { impl Client { pub fn new(network: &Network) -> Result { Ok(Self { - inner: Container::run(&Implementation::Unbound, network)?, + inner: Container::run(&Image::Client, network)?, }) } diff --git a/packages/dns-test/src/container.rs b/packages/dns-test/src/container.rs index 2ada6ca6..5bdfe19b 100644 --- a/packages/dns-test/src/container.rs +++ b/packages/dns-test/src/container.rs @@ -1,17 +1,17 @@ mod network; -use core::str; +use core::{fmt, str}; use std::net::Ipv4Addr; use std::process::{self, ChildStdout, ExitStatus}; use std::process::{Command, Stdio}; use std::sync::atomic::AtomicUsize; -use std::sync::{atomic, Arc}; +use std::sync::{atomic, Arc, Once}; use std::{env, fs}; use tempfile::{NamedTempFile, TempDir}; pub use crate::container::network::Network; -use crate::{Error, Implementation, Result}; +use crate::{Error, Implementation, Repository, Result}; #[derive(Clone)] pub struct Container { @@ -20,16 +20,72 @@ pub struct Container { const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME"); +#[derive(Clone)] +pub enum Image { + Client, + Hickory(Repository<'static>), + Unbound, +} + +impl Image { + fn dockerfile(&self) -> &'static str { + match self { + Self::Unbound => include_str!("docker/unbound.Dockerfile"), + Self::Hickory { .. } => include_str!("docker/hickory.Dockerfile"), + Self::Client => include_str!("docker/client.Dockerfile"), + } + } + + fn once(&self) -> &'static Once { + match self { + Self::Client { .. } => { + static CLIENT_ONCE: Once = Once::new(); + &CLIENT_ONCE + } + + Self::Hickory { .. } => { + static HICKORY_ONCE: Once = Once::new(); + &HICKORY_ONCE + } + + Self::Unbound { .. } => { + static UNBOUND_ONCE: Once = Once::new(); + &UNBOUND_ONCE + } + } + } +} + +impl From for Image { + fn from(implementation: Implementation) -> Self { + match implementation { + Implementation::Unbound => Self::Unbound, + Implementation::Hickory(repo) => Self::Hickory(repo), + } + } +} + +impl fmt::Display for Image { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + Self::Client => "client", + Self::Hickory { .. } => "hickory", + Self::Unbound => "unbound", + }; + f.write_str(s) + } +} + impl Container { /// Starts the container in a "parked" state - pub fn run(implementation: &Implementation, network: &Network) -> Result { + pub fn run(image: &Image, network: &Network) -> Result { // TODO make this configurable and support hickory & bind - let dockerfile = implementation.dockerfile(); + let dockerfile = image.dockerfile(); let docker_build_dir = TempDir::new()?; let docker_build_dir = docker_build_dir.path(); fs::write(docker_build_dir.join("Dockerfile"), dockerfile)?; - let image_tag = format!("{PACKAGE_NAME}-{implementation}"); + let image_tag = format!("{PACKAGE_NAME}-{image}"); let mut command = Command::new("docker"); command @@ -37,13 +93,13 @@ impl Container { .arg(&image_tag) .arg(docker_build_dir); - let repo = if let Implementation::Hickory(repo) = implementation { + let repo = if let Image::Hickory(repo) = image { Some(repo) } else { None }; - implementation.once().call_once(|| { + image.once().call_once(|| { if let Some(repo) = repo { let mut cp_r = Command::new("git"); cp_r.args([ @@ -66,7 +122,7 @@ impl Container { let mut command = Command::new("docker"); let pid = process::id(); let count = container_count(); - let name = format!("{PACKAGE_NAME}-{implementation}-{pid}-{count}"); + let name = format!("{PACKAGE_NAME}-{image}-{pid}-{count}"); command .args([ "run", @@ -334,7 +390,7 @@ mod tests { #[test] fn run_works() -> Result<()> { let network = Network::new()?; - let container = Container::run(&Implementation::Unbound, &network)?; + let container = Container::run(&Image::Client, &network)?; let output = container.output(&["true"])?; assert!(output.status.success()); @@ -345,7 +401,7 @@ mod tests { #[test] fn ipv4_addr_works() -> Result<()> { let network = Network::new()?; - let container = Container::run(&Implementation::Unbound, &network)?; + let container = Container::run(&Image::Client, &network)?; let ipv4_addr = container.ipv4_addr(); let output = container.output(&["ping", "-c1", &format!("{ipv4_addr}")])?; @@ -357,7 +413,7 @@ mod tests { #[test] fn cp_works() -> Result<()> { let network = Network::new()?; - let container = Container::run(&Implementation::Unbound, &network)?; + let container = Container::run(&Image::Client, &network)?; let path = "/tmp/somefile"; let contents = "hello"; diff --git a/packages/dns-test/src/container/network.rs b/packages/dns-test/src/container/network.rs index 7e9f81b1..6c1751ca 100644 --- a/packages/dns-test/src/container/network.rs +++ b/packages/dns-test/src/container/network.rs @@ -113,7 +113,7 @@ fn network_count() -> usize { #[cfg(test)] mod tests { - use crate::{container::Container, Implementation}; + use crate::container::{Container, Image}; use super::*; @@ -146,7 +146,7 @@ mod tests { let network = Network::new().expect("Failed to create network"); let network_name = network.name().to_string(); let container = - Container::run(&Implementation::Unbound, &network).expect("Failed to start container"); + Container::run(&Image::Client, &network).expect("Failed to start container"); assert!(exists_network(&network_name)); drop(network); diff --git a/packages/dns-test/src/docker/client.Dockerfile b/packages/dns-test/src/docker/client.Dockerfile new file mode 100644 index 00000000..9ab60a70 --- /dev/null +++ b/packages/dns-test/src/docker/client.Dockerfile @@ -0,0 +1,8 @@ +FROM debian:bookworm-slim + +# dnsutils = dig & delv +# iputils-ping = ping +RUN apt-get update && \ + apt-get install -y \ + dnsutils \ + iputils-ping diff --git a/packages/dns-test/src/docker/unbound.Dockerfile b/packages/dns-test/src/docker/unbound.Dockerfile index b42777ef..4b8139f0 100644 --- a/packages/dns-test/src/docker/unbound.Dockerfile +++ b/packages/dns-test/src/docker/unbound.Dockerfile @@ -1,12 +1,8 @@ FROM debian:bookworm-slim -# dnsutils = dig & delv -# iputils-ping = ping # ldns-utils = ldns-{key2ds,keygen,signzone} RUN apt-get update && \ apt-get install -y \ - dnsutils \ - iputils-ping \ ldnsutils \ nsd \ tshark \ diff --git a/packages/dns-test/src/lib.rs b/packages/dns-test/src/lib.rs index 75ea0f58..4f8b5c01 100644 --- a/packages/dns-test/src/lib.rs +++ b/packages/dns-test/src/lib.rs @@ -1,9 +1,7 @@ //! A test framework for all things DNS -use core::fmt; use std::borrow::Cow; use std::path::Path; -use std::sync::Once; use url::Url; @@ -57,45 +55,12 @@ pub fn Repository(input: impl Into>) -> Repository<'static> { Repository { inner: input } } -impl Implementation { - fn dockerfile(&self) -> &'static str { - match self { - Implementation::Unbound => include_str!("docker/unbound.Dockerfile"), - Implementation::Hickory { .. } => include_str!("docker/hickory.Dockerfile"), - } - } - - fn once(&self) -> &'static Once { - match self { - Implementation::Unbound => { - static UNBOUND_ONCE: Once = Once::new(); - &UNBOUND_ONCE - } - - Implementation::Hickory { .. } => { - static HICKORY_ONCE: Once = Once::new(); - &HICKORY_ONCE - } - } - } -} - impl Default for Implementation { fn default() -> Self { Self::Unbound } } -impl fmt::Display for Implementation { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - Implementation::Unbound => "unbound", - Implementation::Hickory { .. } => "hickory", - }; - f.write_str(s) - } -} - pub fn subject() -> Implementation { if let Ok(subject) = std::env::var("DNS_TEST_SUBJECT") { if subject == "unbound" { @@ -115,3 +80,7 @@ pub fn subject() -> Implementation { Implementation::default() } } + +pub fn peer() -> Implementation { + Implementation::default() +} diff --git a/packages/dns-test/src/name_server.rs b/packages/dns-test/src/name_server.rs index dc97b89f..db9aab0a 100644 --- a/packages/dns-test/src/name_server.rs +++ b/packages/dns-test/src/name_server.rs @@ -25,7 +25,12 @@ impl<'a> NameServer<'a, Stopped> { /// - one SOA record, with the primary name server field set to this name server's FQDN /// - one NS record, with this name server's FQDN set as the only available name server for /// the zone - pub fn new(zone: FQDN<'a>, network: &Network) -> Result { + pub fn new(implementation: Implementation, zone: FQDN<'a>, network: &Network) -> Result { + assert!( + matches!(implementation, Implementation::Unbound), + "currently only `unbound` (`nsd`) can be used as a `NameServer`" + ); + let ns_count = ns_count(); let nameserver = primary_ns(ns_count); @@ -42,8 +47,9 @@ impl<'a> NameServer<'a, Stopped> { nameserver: nameserver.clone(), }); + let image = implementation.into(); Ok(Self { - container: Container::run(&Implementation::Unbound, network)?, + container: Container::run(&image, network)?, zone_file, state: Stopped, }) @@ -301,7 +307,7 @@ mod tests { #[test] fn simplest() -> Result<()> { let network = Network::new()?; - let tld_ns = NameServer::new(FQDN::COM, &network)?.start()?; + let tld_ns = NameServer::new(Implementation::Unbound, FQDN::COM, &network)?.start()?; let ip_addr = tld_ns.ipv4_addr(); let client = Client::new(&network)?; @@ -322,7 +328,7 @@ mod tests { fn with_referral() -> Result<()> { let network = Network::new()?; let expected_ip_addr = Ipv4Addr::new(172, 17, 200, 1); - let mut root_ns = NameServer::new(FQDN::ROOT, &network)?; + let mut root_ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, &network)?; root_ns.referral( FQDN::COM, FQDN("primary.tld-server.com.")?, @@ -351,7 +357,7 @@ mod tests { #[test] fn signed() -> Result<()> { let network = Network::new()?; - let ns = NameServer::new(FQDN::ROOT, &network)?.sign()?; + let ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, &network)?.sign()?; eprintln!("KSK:\n{}", ns.key_signing_key()); eprintln!("ZSK:\n{}", ns.zone_signing_key()); @@ -387,7 +393,7 @@ mod tests { #[test] fn terminate_works() -> Result<()> { let network = Network::new()?; - let ns = NameServer::new(FQDN::ROOT, &network)?.start()?; + let ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, &network)?.start()?; let logs = ns.terminate()?; assert!(logs.contains("nsd starting")); diff --git a/packages/dns-test/src/resolver.rs b/packages/dns-test/src/resolver.rs index c6773ab7..22c5e55a 100644 --- a/packages/dns-test/src/resolver.rs +++ b/packages/dns-test/src/resolver.rs @@ -33,7 +33,8 @@ impl Resolver { "must configure at least one local root server" ); - let container = Container::run(&implementation, network)?; + let image = implementation.clone().into(); + let container = Container::run(&image, network)?; let mut hints = String::new(); for root in roots { @@ -124,7 +125,7 @@ mod tests { #[test] fn terminate_works() -> Result<()> { let network = Network::new()?; - let ns = NameServer::new(FQDN::ROOT, &network)?.start()?; + let ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, &network)?.start()?; let resolver = Resolver::start( Implementation::Unbound, &[Root::new(ns.fqdn().clone(), ns.ipv4_addr())], diff --git a/packages/dns-test/src/tshark.rs b/packages/dns-test/src/tshark.rs index d07fe7eb..f230d31d 100644 --- a/packages/dns-test/src/tshark.rs +++ b/packages/dns-test/src/tshark.rs @@ -255,7 +255,7 @@ mod tests { #[test] fn nameserver() -> Result<()> { let network = &Network::new()?; - let ns = NameServer::new(FQDN::ROOT, network)?.start()?; + let ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, network)?.start()?; let mut tshark = ns.eavesdrop()?; let client = Client::new(network)?; @@ -291,10 +291,11 @@ mod tests { #[test] fn resolver() -> Result<()> { let network = &Network::new()?; - let mut root_ns = NameServer::new(FQDN::ROOT, network)?; - let mut com_ns = NameServer::new(FQDN::COM, network)?; + let mut root_ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, network)?; + let mut com_ns = NameServer::new(Implementation::Unbound, FQDN::COM, network)?; - let mut nameservers_ns = NameServer::new(FQDN("nameservers.com.")?, network)?; + let mut nameservers_ns = + NameServer::new(Implementation::Unbound, FQDN("nameservers.com.")?, network)?; nameservers_ns .a(root_ns.fqdn().clone(), root_ns.ipv4_addr()) .a(com_ns.fqdn().clone(), com_ns.ipv4_addr());