Skip to content
This repository has been archived by the owner on Jun 7, 2024. It is now read-only.

support using BIND in the NameServer role #28

Merged
merged 2 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
108 changes: 90 additions & 18 deletions packages/dns-test/src/name_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use crate::{Implementation, Result, DEFAULT_TTL, FQDN};

pub struct NameServer<State> {
container: Container,
zone_file: ZoneFile,
implementation: Implementation,
state: State,
zone_file: ZoneFile,
}

impl NameServer<Stopped> {
Expand All @@ -28,12 +29,17 @@ impl NameServer<Stopped> {
/// the zone
pub fn new(implementation: &Implementation, zone: FQDN, network: &Network) -> Result<Self> {
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(),
Expand All @@ -45,10 +51,12 @@ impl NameServer<Stopped> {
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,
})
Expand Down Expand Up @@ -76,11 +84,13 @@ impl NameServer<Stopped> {
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();

Expand Down Expand Up @@ -108,7 +118,6 @@ impl NameServer<Stopped> {
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()?;
Expand All @@ -117,6 +126,7 @@ impl NameServer<Stopped> {

Ok(NameServer {
container,
implementation,
zone_file,
state: Signed {
ds,
Expand All @@ -133,28 +143,45 @@ impl NameServer<Stopped> {
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 {
Expand All @@ -172,20 +199,40 @@ impl NameServer<Signed> {
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 },
})
Expand Down Expand Up @@ -220,7 +267,13 @@ impl NameServer<Running> {

/// gracefully terminates the name server collecting all logs
pub fn terminate(self) -> Result<String> {
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!(
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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()?;
Expand All @@ -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(())
}
}
14 changes: 14 additions & 0 deletions packages/dns-test/src/templates/named.name-server.conf.jinja
Original file line number Diff line number Diff line change
@@ -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";
};
2 changes: 1 addition & 1 deletion packages/dns-test/src/templates/nsd.conf.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ remote-control:

zone:
name: {{ fqdn }}
zonefile: /etc/nsd/zones/main.zone
zonefile: /etc/zones/main.zone