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

Commit

Permalink
support using BIND in the Resolver role
Browse files Browse the repository at this point in the history
  • Loading branch information
japaric committed Feb 29, 2024
1 parent 49c89f7 commit 755a417
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 14 deletions.
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!(),
};
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";
};

0 comments on commit 755a417

Please sign in to comment.