From 95cea6882541f4d44d4cc8926a137c9d17384287 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 29 Feb 2024 17:38:06 +0100 Subject: [PATCH 1/2] support BIND in the NameServer role --- packages/dns-test/src/name_server.rs | 108 +++++++++++++++--- .../templates/named.name-server.conf.jinja | 14 +++ .../dns-test/src/templates/nsd.conf.jinja | 2 +- 3 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 packages/dns-test/src/templates/named.name-server.conf.jinja diff --git a/packages/dns-test/src/name_server.rs b/packages/dns-test/src/name_server.rs index e5732c2..81ce4d7 100644 --- a/packages/dns-test/src/name_server.rs +++ b/packages/dns-test/src/name_server.rs @@ -9,8 +9,9 @@ use crate::{Implementation, Result, DEFAULT_TTL, FQDN}; pub struct NameServer { container: Container, - zone_file: ZoneFile, + implementation: Implementation, state: State, + zone_file: ZoneFile, } impl NameServer { @@ -28,12 +29,17 @@ impl NameServer { /// the zone pub fn new(implementation: &Implementation, zone: FQDN, network: &Network) -> Result { assert!( - matches!(implementation, Implementation::Unbound), - "currently only `unbound` (`nsd`) can be used as a `NameServer`" + matches!( + implementation, + Implementation::Unbound | Implementation::Bind + ), + "currently only `unbound` (`nsd`) and BIND can be used as a `NameServer`" ); let ns_count = ns_count(); let nameserver = primary_ns(ns_count); + let image = implementation.clone().into(); + let container = Container::run(&image, network)?; let soa = SOA { zone: zone.clone(), @@ -45,10 +51,12 @@ impl NameServer { let mut zone_file = ZoneFile::new(soa); zone_file.add(Record::ns(zone, nameserver.clone())); + // BIND requires that `nameserver` has an A record + zone_file.add(Record::a(nameserver.clone(), container.ipv4_addr())); - let image = implementation.clone().into(); Ok(Self { - container: Container::run(&image, network)?, + container, + implementation: implementation.clone(), zone_file, state: Stopped, }) @@ -76,11 +84,13 @@ impl NameServer { let Self { container, zone_file, + implementation, state: _, } = self; container.status_ok(&["mkdir", "-p", ZONES_DIR])?; - container.cp("/etc/nsd/zones/main.zone", &zone_file.to_string())?; + let zone_file_path = zone_file_path(); + container.cp(&zone_file_path, &zone_file.to_string())?; let zone = zone_file.origin(); @@ -108,7 +118,6 @@ impl NameServer { let key2ds = format!("cd {ZONES_DIR} && ldns-key2ds -n -2 {ZONE_FILENAME}.signed"); let ds: DS = container.stdout(&["sh", "-c", &key2ds])?.parse()?; - let zone_file_path = zone_file_path(); let signed: ZoneFile = container .stdout(&["cat", &format!("{zone_file_path}.signed")])? .parse()?; @@ -117,6 +126,7 @@ impl NameServer { Ok(NameServer { container, + implementation, zone_file, state: Signed { ds, @@ -133,28 +143,45 @@ impl NameServer { let Self { container, zone_file, + implementation, state: _, } = self; - // for PID file - container.status_ok(&["mkdir", "-p", "/run/nsd/"])?; + let origin = zone_file.origin(); + let (path, contents, cmd_args) = match &implementation { + Implementation::Bind => ( + "/etc/bind/named.conf", + named_conf(origin), + &["named", "-g", "-d5"][..], + ), - container.cp("/etc/nsd/nsd.conf", &nsd_conf(zone_file.origin()))?; + Implementation::Unbound => { + // for PID file + container.status_ok(&["mkdir", "-p", "/run/nsd/"])?; + + ("/etc/nsd/nsd.conf", nsd_conf(origin), &["nsd", "-d"][..]) + } + + Implementation::Hickory(_) => unreachable!(), + }; + + container.cp(path, &contents)?; container.status_ok(&["mkdir", "-p", ZONES_DIR])?; container.cp(&zone_file_path(), &zone_file.to_string())?; - let child = container.spawn(&["nsd", "-d"])?; + let child = container.spawn(cmd_args)?; Ok(NameServer { container, + implementation, zone_file, state: Running { child }, }) } } -const ZONES_DIR: &str = "/etc/nsd/zones"; +const ZONES_DIR: &str = "/etc/zones"; const ZONE_FILENAME: &str = "main.zone"; fn zone_file_path() -> String { @@ -172,20 +199,40 @@ impl NameServer { let Self { container, zone_file, + implementation, state, } = self; - // for PID file - container.status_ok(&["mkdir", "-p", "/run/nsd/"])?; + let (conf_path, conf_contents, cmd_args) = match implementation { + Implementation::Bind => ( + "/etc/bind/named.conf", + named_conf(zone_file.origin()), + &["named", "-g", "-d5"][..], + ), + + Implementation::Unbound => { + // for PID file + container.status_ok(&["mkdir", "-p", "/run/nsd/"])?; + + ( + "/etc/nsd/nsd.conf", + nsd_conf(zone_file.origin()), + &["nsd", "-d"][..], + ) + } + + Implementation::Hickory(..) => unreachable!(), + }; - container.cp("/etc/nsd/nsd.conf", &nsd_conf(zone_file.origin()))?; + container.cp(conf_path, &conf_contents)?; container.cp(&zone_file_path(), &state.signed.to_string())?; - let child = container.spawn(&["nsd", "-d"])?; + let child = container.spawn(cmd_args)?; Ok(NameServer { container, + implementation, zone_file, state: Running { child }, }) @@ -220,7 +267,13 @@ impl NameServer { /// gracefully terminates the name server collecting all logs pub fn terminate(self) -> Result { - let pidfile = "/run/nsd/nsd.pid"; + let pidfile = match &self.implementation { + Implementation::Bind => "/tmp/named.pid", + + Implementation::Unbound => "/run/nsd/nsd.pid", + + Implementation::Hickory(_) => unreachable!(), + }; // if `terminate` is called right after `start` NSD may not have had the chance to create // the PID file so if it doesn't exist wait for a bit before invoking `kill` let kill = format!( @@ -286,6 +339,13 @@ fn admin_ns(ns_count: usize) -> FQDN { FQDN(format!("admin{ns_count}.nameservers.com.")).unwrap() } +fn named_conf(fqdn: &FQDN) -> String { + minijinja::render!( + include_str!("templates/named.name-server.conf.jinja"), + fqdn => fqdn.as_str() + ) +} + fn nsd_conf(fqdn: &FQDN) -> String { minijinja::render!( include_str!("templates/nsd.conf.jinja"), @@ -375,7 +435,7 @@ mod tests { } #[test] - fn terminate_works() -> Result<()> { + fn terminate_nsd_works() -> Result<()> { let network = Network::new()?; let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?; let logs = ns.terminate()?; @@ -384,4 +444,16 @@ mod tests { Ok(()) } + + #[test] + fn terminate_named_works() -> Result<()> { + let network = Network::new()?; + let ns = NameServer::new(&Implementation::Bind, FQDN::ROOT, &network)?.start()?; + let logs = ns.terminate()?; + + eprintln!("{logs}"); + assert!(logs.contains("starting BIND")); + + Ok(()) + } } diff --git a/packages/dns-test/src/templates/named.name-server.conf.jinja b/packages/dns-test/src/templates/named.name-server.conf.jinja new file mode 100644 index 0000000..3a829ef --- /dev/null +++ b/packages/dns-test/src/templates/named.name-server.conf.jinja @@ -0,0 +1,14 @@ +options { + directory "/var/cache/bind"; + pid-file "/tmp/named.pid"; + recursion no; + dnssec-validation no; + allow-transfer { none; }; + # significantly reduces noise in logs + empty-zones-enable no; +}; + +zone "{{ fqdn }}" IN { + type primary; + file "/etc/zones/main.zone"; +}; diff --git a/packages/dns-test/src/templates/nsd.conf.jinja b/packages/dns-test/src/templates/nsd.conf.jinja index 2a7541a..36dea4f 100644 --- a/packages/dns-test/src/templates/nsd.conf.jinja +++ b/packages/dns-test/src/templates/nsd.conf.jinja @@ -3,4 +3,4 @@ remote-control: zone: name: {{ fqdn }} - zonefile: /etc/nsd/zones/main.zone + zonefile: /etc/zones/main.zone From 811e0ae55f468a474ebfc2d8e00f944c8ff09105 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 29 Feb 2024 18:22:07 +0100 Subject: [PATCH 2/2] CI: test unbound (subject) against BIND (peers) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a2ab11..e8f7942 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: run: cargo test -p dns-test -- --include-ignored - name: Run tests against unbound - run: cargo test -p conformance-tests -- --include-ignored + run: DNS_TEST_PEER=bind cargo test -p conformance-tests -- --include-ignored - name: Run tests against BIND run: DNS_TEST_SUBJECT=bind cargo test -p conformance-tests -- --include-ignored