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 Resolver role #27

Merged
merged 2 commits into from
Mar 11, 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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;

Expand Down
17 changes: 13 additions & 4 deletions packages/dns-test/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME");

#[derive(Clone)]
pub enum Image {
Bind,
Client,
Hickory(Repository<'static>),
Unbound,
Expand All @@ -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
}
Expand All @@ -48,7 +55,7 @@ impl Image {
&HICKORY_ONCE
}

Self::Unbound { .. } => {
Self::Unbound => {
static UNBOUND_ONCE: Once = Once::new();
&UNBOUND_ONCE
}
Expand All @@ -59,6 +66,7 @@ impl Image {
impl From<Implementation> for Image {
fn from(implementation: Implementation) -> Self {
match implementation {
Implementation::Bind => Self::Bind,
Implementation::Unbound => Self::Unbound,
Implementation::Hickory(repo) => Self::Hickory(repo),
}
Expand All @@ -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",
};
Expand Down
10 changes: 10 additions & 0 deletions packages/dns-test/src/docker/bind.Dockerfile
Original file line number Diff line number Diff line change
@@ -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/*
24 changes: 22 additions & 2 deletions packages/dns-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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()))
Expand All @@ -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()
}
}
63 changes: 57 additions & 6 deletions packages/dns-test/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{Implementation, Result};
pub struct Resolver {
container: Container,
child: Child,
implementation: Implementation,
}

impl Resolver {
Expand All @@ -26,8 +27,6 @@ impl Resolver {
trust_anchor: &TrustAnchor,
network: &Network,
) -> Result<Self> {
const TRUST_ANCHOR_FILE: &str = "/etc/trusted-key.key";

assert!(
!roots.is_empty(),
"must configure at least one local root server"
Expand All @@ -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)?;

Expand All @@ -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<Tshark> {
Expand All @@ -88,7 +113,11 @@ impl Resolver {

/// gracefully terminates the name server collecting all logs
pub fn terminate(self) -> Result<String> {
let pidfile = "/run/unbound.pid";
let pidfile = match self.implementation {
Implementation::Bind => "/tmp/named.pid",
Implementation::Unbound => "/run/unbound.pid",
Implementation::Hickory(..) => unimplemented!(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this will not cause an issue, because hickory is not used as a name server yet?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct

I was thinking of a different match statement so this answer was not correct. this unimplemented! is not a problem per se because no conformance test uses terminate. terminate is meant to be used to debug issues in tests, i.e. tests not behaving as expected. in the case of hickory, terminate simply has not yet been implemented but I'm currently working on it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently working on it

implemented in #42

};
let kill = format!(
"test -f {pidfile} || sleep 1
kill -TERM $(cat {pidfile})"
Expand All @@ -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)
}
Expand All @@ -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(
Expand All @@ -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(())
}
}
14 changes: 14 additions & 0 deletions packages/dns-test/src/templates/named.resolver.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 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";
};