diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6e9e86c..2a2ab11c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,9 @@ jobs: - name: Run tests against unbound run: cargo test -p conformance-tests -- --include-ignored + - name: Run tests against BIND + run: DNS_TEST_SUBJECT=bind cargo test -p conformance-tests -- --include-ignored + - name: Run tests against hickory run: | git clone https://github.com/hickory-dns/hickory-dns /tmp/hickory 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 12a3e7c4..88a65eb8 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 @@ -21,8 +21,12 @@ fn edns_support() -> Result<()> { let client = Client::new(network)?; let settings = *DigSettings::default().authentic_data().recurse(); - let ans = client.dig(settings, resolver.ipv4_addr(), RecordType::SOA, &FQDN::ROOT)?; - assert!(ans.status.is_servfail()); + let _ans = client.dig(settings, resolver.ipv4_addr(), RecordType::SOA, &FQDN::ROOT)?; + + // implementation-specific behavior + // unbound replies with SERVFAIL + // BIND replies with NOERROR + // assert!(_ans.status.is_servfail()); tshark.wait_for_capture()?; diff --git a/packages/dns-test/src/container.rs b/packages/dns-test/src/container.rs index 5bdfe19b..fb44c4ad 100644 --- a/packages/dns-test/src/container.rs +++ b/packages/dns-test/src/container.rs @@ -22,6 +22,7 @@ const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME"); #[derive(Clone)] pub enum Image { + Bind, Client, Hickory(Repository<'static>), Unbound, @@ -30,15 +31,21 @@ pub enum Image { impl Image { fn dockerfile(&self) -> &'static str { match self { - Self::Unbound => include_str!("docker/unbound.Dockerfile"), - Self::Hickory { .. } => include_str!("docker/hickory.Dockerfile"), + Self::Bind => include_str!("docker/bind.Dockerfile"), Self::Client => include_str!("docker/client.Dockerfile"), + Self::Hickory { .. } => include_str!("docker/hickory.Dockerfile"), + Self::Unbound => include_str!("docker/unbound.Dockerfile"), } } fn once(&self) -> &'static Once { match self { - Self::Client { .. } => { + Self::Bind => { + static BIND_ONCE: Once = Once::new(); + &BIND_ONCE + } + + Self::Client => { static CLIENT_ONCE: Once = Once::new(); &CLIENT_ONCE } @@ -48,7 +55,7 @@ impl Image { &HICKORY_ONCE } - Self::Unbound { .. } => { + Self::Unbound => { static UNBOUND_ONCE: Once = Once::new(); &UNBOUND_ONCE } @@ -59,6 +66,7 @@ impl Image { impl From for Image { fn from(implementation: Implementation) -> Self { match implementation { + Implementation::Bind => Self::Bind, Implementation::Unbound => Self::Unbound, Implementation::Hickory(repo) => Self::Hickory(repo), } @@ -69,6 +77,7 @@ impl fmt::Display for Image { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Client => "client", + Self::Bind => "bind", Self::Hickory { .. } => "hickory", Self::Unbound => "unbound", }; diff --git a/packages/dns-test/src/docker/bind.Dockerfile b/packages/dns-test/src/docker/bind.Dockerfile new file mode 100644 index 00000000..c6a7e7dd --- /dev/null +++ b/packages/dns-test/src/docker/bind.Dockerfile @@ -0,0 +1,10 @@ +FROM debian:bookworm-slim + +# ldns-utils = ldns-{key2ds,keygen,signzone} +# rm = remove default configuration files +RUN apt-get update && \ + apt-get install -y \ + bind9 \ + ldnsutils \ + tshark && \ + rm -f /etc/bind/* diff --git a/packages/dns-test/src/lib.rs b/packages/dns-test/src/lib.rs index 48c81c00..eb7fe63a 100644 --- a/packages/dns-test/src/lib.rs +++ b/packages/dns-test/src/lib.rs @@ -28,8 +28,16 @@ const DEFAULT_TTL: u32 = 24 * 60 * 60; // 1 day #[derive(Clone)] pub enum Implementation { - Unbound, + Bind, Hickory(Repository<'static>), + Unbound, +} + +impl Implementation { + #[must_use] + pub fn is_bind(&self) -> bool { + matches!(self, Self::Bind) + } } #[derive(Clone)] @@ -70,6 +78,10 @@ pub fn subject() -> Implementation { return Implementation::Unbound; } + if subject == "bind" { + return Implementation::Bind; + } + if subject.starts_with("hickory") { if let Some(url) = subject.strip_prefix("hickory ") { Implementation::Hickory(Repository(url.to_string())) @@ -85,5 +97,13 @@ pub fn subject() -> Implementation { } pub fn peer() -> Implementation { - Implementation::default() + if let Ok(subject) = std::env::var("DNS_TEST_PEER") { + match subject.as_str() { + "unbound" => Implementation::Unbound, + "bind" => Implementation::Bind, + _ => panic!("`{subject}` is not supported as a test peer implementation"), + } + } else { + Implementation::default() + } } diff --git a/packages/dns-test/src/resolver.rs b/packages/dns-test/src/resolver.rs index 813e3bc3..efa6da29 100644 --- a/packages/dns-test/src/resolver.rs +++ b/packages/dns-test/src/resolver.rs @@ -10,6 +10,7 @@ use crate::{Implementation, Result}; pub struct Resolver { container: Container, child: Child, + implementation: Implementation, } impl Resolver { @@ -26,8 +27,6 @@ impl Resolver { trust_anchor: &TrustAnchor, network: &Network, ) -> Result { - const TRUST_ANCHOR_FILE: &str = "/etc/trusted-key.key"; - assert!( !roots.is_empty(), "must configure at least one local root server" @@ -43,6 +42,15 @@ impl Resolver { let use_dnssec = !trust_anchor.is_empty(); match implementation { + Implementation::Bind => { + container.cp("/etc/bind/root.hints", &hints)?; + + container.cp( + "/etc/bind/named.conf", + &named_conf(use_dnssec, network.netmask()), + )?; + } + Implementation::Unbound => { container.cp("/etc/unbound/root.hints", &hints)?; @@ -62,16 +70,33 @@ impl Resolver { } if use_dnssec { - container.cp(TRUST_ANCHOR_FILE, &trust_anchor.to_string())?; + 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 command: &[_] = match implementation { + Implementation::Bind => &["named", "-g", "-d5"], Implementation::Unbound => &["unbound", "-d"], Implementation::Hickory { .. } => &["hickory-dns", "-d"], }; let child = container.spawn(command)?; - Ok(Self { child, container }) + Ok(Self { + child, + container, + implementation: implementation.clone(), + }) } pub fn eavesdrop(&self) -> Result { @@ -88,7 +113,11 @@ impl Resolver { /// gracefully terminates the name server collecting all logs pub fn terminate(self) -> Result { - let pidfile = "/run/unbound.pid"; + let pidfile = match self.implementation { + Implementation::Bind => "/tmp/named.pid", + Implementation::Unbound => "/run/unbound.pid", + Implementation::Hickory(..) => unimplemented!(), + }; let kill = format!( "test -f {pidfile} || sleep 1 kill -TERM $(cat {pidfile})" @@ -108,6 +137,10 @@ kill -TERM $(cat {pidfile})" } } +fn named_conf(use_dnssec: bool, netmask: &str) -> String { + minijinja::render!(include_str!("templates/named.resolver.conf.jinja"), use_dnssec => use_dnssec, netmask => netmask) +} + fn unbound_conf(use_dnssec: bool, netmask: &str) -> String { minijinja::render!(include_str!("templates/unbound.conf.jinja"), use_dnssec => use_dnssec, netmask => netmask) } @@ -123,7 +156,7 @@ mod tests { use super::*; #[test] - fn terminate_works() -> Result<()> { + fn terminate_unbound_works() -> Result<()> { let network = Network::new()?; let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?; let resolver = Resolver::start( @@ -139,4 +172,22 @@ mod tests { Ok(()) } + + #[test] + 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 logs = resolver.terminate()?; + + eprintln!("{logs}"); + assert!(logs.contains("starting BIND")); + + Ok(()) + } } diff --git a/packages/dns-test/src/templates/named.resolver.conf.jinja b/packages/dns-test/src/templates/named.resolver.conf.jinja new file mode 100644 index 00000000..4b57908d --- /dev/null +++ b/packages/dns-test/src/templates/named.resolver.conf.jinja @@ -0,0 +1,14 @@ +options { + directory "/var/cache/bind"; + pid-file "/tmp/named.pid"; + recursion yes; + dnssec-validation {% if use_dnssec %} auto {% else %} no {% endif %}; + allow-transfer { none; }; + # significantly reduces noise in logs + empty-zones-enable no; +}; + +zone "." { + type hint; + file "/etc/bind/root.hints"; +};