Merge pull request #27 from ferrous-systems/ja-bind-impl
support using BIND in the Resolver role
This commit is contained in:
commit
d25cc923ec
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -30,6 +30,9 @@ jobs:
|
|||||||
- name: Run tests against unbound
|
- name: Run tests against unbound
|
||||||
run: cargo test -p conformance-tests -- --include-ignored
|
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
|
- name: Run tests against hickory
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/hickory-dns/hickory-dns /tmp/hickory
|
git clone https://github.com/hickory-dns/hickory-dns /tmp/hickory
|
||||||
|
@ -21,8 +21,12 @@ fn edns_support() -> Result<()> {
|
|||||||
|
|
||||||
let client = Client::new(network)?;
|
let client = Client::new(network)?;
|
||||||
let settings = *DigSettings::default().authentic_data().recurse();
|
let settings = *DigSettings::default().authentic_data().recurse();
|
||||||
let ans = client.dig(settings, resolver.ipv4_addr(), RecordType::SOA, &FQDN::ROOT)?;
|
let _ans = client.dig(settings, resolver.ipv4_addr(), RecordType::SOA, &FQDN::ROOT)?;
|
||||||
assert!(ans.status.is_servfail());
|
|
||||||
|
// implementation-specific behavior
|
||||||
|
// unbound replies with SERVFAIL
|
||||||
|
// BIND replies with NOERROR
|
||||||
|
// assert!(_ans.status.is_servfail());
|
||||||
|
|
||||||
tshark.wait_for_capture()?;
|
tshark.wait_for_capture()?;
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME");
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Image {
|
pub enum Image {
|
||||||
|
Bind,
|
||||||
Client,
|
Client,
|
||||||
Hickory(Repository<'static>),
|
Hickory(Repository<'static>),
|
||||||
Unbound,
|
Unbound,
|
||||||
@ -30,15 +31,21 @@ pub enum Image {
|
|||||||
impl Image {
|
impl Image {
|
||||||
fn dockerfile(&self) -> &'static str {
|
fn dockerfile(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Unbound => include_str!("docker/unbound.Dockerfile"),
|
Self::Bind => include_str!("docker/bind.Dockerfile"),
|
||||||
Self::Hickory { .. } => include_str!("docker/hickory.Dockerfile"),
|
|
||||||
Self::Client => include_str!("docker/client.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 {
|
fn once(&self) -> &'static Once {
|
||||||
match self {
|
match self {
|
||||||
Self::Client { .. } => {
|
Self::Bind => {
|
||||||
|
static BIND_ONCE: Once = Once::new();
|
||||||
|
&BIND_ONCE
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Client => {
|
||||||
static CLIENT_ONCE: Once = Once::new();
|
static CLIENT_ONCE: Once = Once::new();
|
||||||
&CLIENT_ONCE
|
&CLIENT_ONCE
|
||||||
}
|
}
|
||||||
@ -48,7 +55,7 @@ impl Image {
|
|||||||
&HICKORY_ONCE
|
&HICKORY_ONCE
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Unbound { .. } => {
|
Self::Unbound => {
|
||||||
static UNBOUND_ONCE: Once = Once::new();
|
static UNBOUND_ONCE: Once = Once::new();
|
||||||
&UNBOUND_ONCE
|
&UNBOUND_ONCE
|
||||||
}
|
}
|
||||||
@ -59,6 +66,7 @@ impl Image {
|
|||||||
impl From<Implementation> for Image {
|
impl From<Implementation> for Image {
|
||||||
fn from(implementation: Implementation) -> Self {
|
fn from(implementation: Implementation) -> Self {
|
||||||
match implementation {
|
match implementation {
|
||||||
|
Implementation::Bind => Self::Bind,
|
||||||
Implementation::Unbound => Self::Unbound,
|
Implementation::Unbound => Self::Unbound,
|
||||||
Implementation::Hickory(repo) => Self::Hickory(repo),
|
Implementation::Hickory(repo) => Self::Hickory(repo),
|
||||||
}
|
}
|
||||||
@ -69,6 +77,7 @@ impl fmt::Display for Image {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let s = match self {
|
let s = match self {
|
||||||
Self::Client => "client",
|
Self::Client => "client",
|
||||||
|
Self::Bind => "bind",
|
||||||
Self::Hickory { .. } => "hickory",
|
Self::Hickory { .. } => "hickory",
|
||||||
Self::Unbound => "unbound",
|
Self::Unbound => "unbound",
|
||||||
};
|
};
|
||||||
|
10
packages/dns-test/src/docker/bind.Dockerfile
Normal file
10
packages/dns-test/src/docker/bind.Dockerfile
Normal file
@ -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/*
|
@ -28,8 +28,16 @@ const DEFAULT_TTL: u32 = 24 * 60 * 60; // 1 day
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Implementation {
|
pub enum Implementation {
|
||||||
Unbound,
|
Bind,
|
||||||
Hickory(Repository<'static>),
|
Hickory(Repository<'static>),
|
||||||
|
Unbound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Implementation {
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_bind(&self) -> bool {
|
||||||
|
matches!(self, Self::Bind)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -70,6 +78,10 @@ pub fn subject() -> Implementation {
|
|||||||
return Implementation::Unbound;
|
return Implementation::Unbound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if subject == "bind" {
|
||||||
|
return Implementation::Bind;
|
||||||
|
}
|
||||||
|
|
||||||
if subject.starts_with("hickory") {
|
if subject.starts_with("hickory") {
|
||||||
if let Some(url) = subject.strip_prefix("hickory ") {
|
if let Some(url) = subject.strip_prefix("hickory ") {
|
||||||
Implementation::Hickory(Repository(url.to_string()))
|
Implementation::Hickory(Repository(url.to_string()))
|
||||||
@ -85,5 +97,13 @@ pub fn subject() -> Implementation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn peer() -> 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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use crate::{Implementation, Result};
|
|||||||
pub struct Resolver {
|
pub struct Resolver {
|
||||||
container: Container,
|
container: Container,
|
||||||
child: Child,
|
child: Child,
|
||||||
|
implementation: Implementation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolver {
|
impl Resolver {
|
||||||
@ -26,8 +27,6 @@ impl Resolver {
|
|||||||
trust_anchor: &TrustAnchor,
|
trust_anchor: &TrustAnchor,
|
||||||
network: &Network,
|
network: &Network,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
const TRUST_ANCHOR_FILE: &str = "/etc/trusted-key.key";
|
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
!roots.is_empty(),
|
!roots.is_empty(),
|
||||||
"must configure at least one local root server"
|
"must configure at least one local root server"
|
||||||
@ -43,6 +42,15 @@ impl Resolver {
|
|||||||
|
|
||||||
let use_dnssec = !trust_anchor.is_empty();
|
let use_dnssec = !trust_anchor.is_empty();
|
||||||
match implementation {
|
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 => {
|
Implementation::Unbound => {
|
||||||
container.cp("/etc/unbound/root.hints", &hints)?;
|
container.cp("/etc/unbound/root.hints", &hints)?;
|
||||||
|
|
||||||
@ -62,16 +70,33 @@ impl Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if use_dnssec {
|
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 {
|
let command: &[_] = match implementation {
|
||||||
|
Implementation::Bind => &["named", "-g", "-d5"],
|
||||||
Implementation::Unbound => &["unbound", "-d"],
|
Implementation::Unbound => &["unbound", "-d"],
|
||||||
Implementation::Hickory { .. } => &["hickory-dns", "-d"],
|
Implementation::Hickory { .. } => &["hickory-dns", "-d"],
|
||||||
};
|
};
|
||||||
let child = container.spawn(command)?;
|
let child = container.spawn(command)?;
|
||||||
|
|
||||||
Ok(Self { child, container })
|
Ok(Self {
|
||||||
|
child,
|
||||||
|
container,
|
||||||
|
implementation: implementation.clone(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eavesdrop(&self) -> Result<Tshark> {
|
pub fn eavesdrop(&self) -> Result<Tshark> {
|
||||||
@ -88,7 +113,11 @@ impl Resolver {
|
|||||||
|
|
||||||
/// gracefully terminates the name server collecting all logs
|
/// gracefully terminates the name server collecting all logs
|
||||||
pub fn terminate(self) -> Result<String> {
|
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!(
|
let kill = format!(
|
||||||
"test -f {pidfile} || sleep 1
|
"test -f {pidfile} || sleep 1
|
||||||
kill -TERM $(cat {pidfile})"
|
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 {
|
fn unbound_conf(use_dnssec: bool, netmask: &str) -> String {
|
||||||
minijinja::render!(include_str!("templates/unbound.conf.jinja"), use_dnssec => use_dnssec, netmask => netmask)
|
minijinja::render!(include_str!("templates/unbound.conf.jinja"), use_dnssec => use_dnssec, netmask => netmask)
|
||||||
}
|
}
|
||||||
@ -123,7 +156,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn terminate_works() -> Result<()> {
|
fn terminate_unbound_works() -> Result<()> {
|
||||||
let network = Network::new()?;
|
let network = Network::new()?;
|
||||||
let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
|
let ns = NameServer::new(&Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
|
||||||
let resolver = Resolver::start(
|
let resolver = Resolver::start(
|
||||||
@ -139,4 +172,22 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
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
packages/dns-test/src/templates/named.resolver.conf.jinja
Normal file
14
packages/dns-test/src/templates/named.resolver.conf.jinja
Normal file
@ -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";
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user