Merge pull request #19 from japaric/ja-dns-test-peer

add `dns_test::peer` and use it to initialize `NameServer`
This commit is contained in:
Андрей Листочкин (Andrei Listochkin) 2024-02-23 13:59:25 +00:00 committed by GitHub
commit 9f03274589
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 119 additions and 79 deletions

View File

@ -12,10 +12,11 @@ fn can_resolve() -> Result<()> {
let needle_fqdn = FQDN("example.nameservers.com.")?;
let network = Network::new()?;
let mut root_ns = NameServer::new(FQDN::ROOT, &network)?;
let mut com_ns = NameServer::new(FQDN::COM, &network)?;
let mut root_ns = NameServer::new(dns_test::peer(), FQDN::ROOT, &network)?;
let mut com_ns = NameServer::new(dns_test::peer(), FQDN::COM, &network)?;
let mut nameservers_ns = NameServer::new(FQDN("nameservers.com.")?, &network)?;
let mut nameservers_ns =
NameServer::new(dns_test::peer(), FQDN("nameservers.com.")?, &network)?;
nameservers_ns
.a(root_ns.fqdn().clone(), root_ns.ipv4_addr())
.a(com_ns.fqdn().clone(), com_ns.ipv4_addr())
@ -68,10 +69,11 @@ fn nxdomain() -> Result<()> {
let needle_fqdn = FQDN("unicorn.nameservers.com.")?;
let network = Network::new()?;
let mut root_ns = NameServer::new(FQDN::ROOT, &network)?;
let mut com_ns = NameServer::new(FQDN::COM, &network)?;
let mut root_ns = NameServer::new(dns_test::peer(), FQDN::ROOT, &network)?;
let mut com_ns = NameServer::new(dns_test::peer(), FQDN::COM, &network)?;
let mut nameservers_ns = NameServer::new(FQDN("nameservers.com.")?, &network)?;
let mut nameservers_ns =
NameServer::new(dns_test::peer(), FQDN("nameservers.com.")?, &network)?;
nameservers_ns
.a(root_ns.fqdn().clone(), root_ns.ipv4_addr())
.a(com_ns.fqdn().clone(), com_ns.ipv4_addr());

View File

@ -9,7 +9,7 @@ use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN};
#[ignore]
fn edns_support() -> Result<()> {
let network = &Network::new()?;
let ns = NameServer::new(FQDN::ROOT, network)?.start()?;
let ns = NameServer::new(dns_test::peer(), FQDN::ROOT, network)?.start()?;
let resolver = Resolver::start(
dns_test::subject(),
&[Root::new(ns.fqdn().clone(), ns.ipv4_addr())],

View File

@ -11,7 +11,7 @@ use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN};
#[test]
fn can_validate_without_delegation() -> Result<()> {
let network = Network::new()?;
let mut ns = NameServer::new(FQDN::ROOT, &network)?;
let mut ns = NameServer::new(dns_test::peer(), FQDN::ROOT, &network)?;
ns.a(ns.fqdn().clone(), ns.ipv4_addr());
let ns = ns.sign()?;
@ -55,10 +55,11 @@ fn can_validate_with_delegation() -> Result<()> {
let needle_fqdn = FQDN("example.nameservers.com.")?;
let network = Network::new()?;
let mut root_ns = NameServer::new(FQDN::ROOT, &network)?;
let mut com_ns = NameServer::new(FQDN::COM, &network)?;
let mut root_ns = NameServer::new(dns_test::peer(), FQDN::ROOT, &network)?;
let mut com_ns = NameServer::new(dns_test::peer(), FQDN::COM, &network)?;
let mut nameservers_ns = NameServer::new(FQDN("nameservers.com.")?, &network)?;
let mut nameservers_ns =
NameServer::new(dns_test::peer(), FQDN("nameservers.com.")?, &network)?;
nameservers_ns
.a(root_ns.fqdn().clone(), root_ns.ipv4_addr())
.a(com_ns.fqdn().clone(), com_ns.ipv4_addr())

View File

@ -1,10 +1,10 @@
use core::str::FromStr;
use std::net::Ipv4Addr;
use crate::container::{Container, Network};
use crate::container::{Container, Image, Network};
use crate::record::{Record, RecordType};
use crate::trust_anchor::TrustAnchor;
use crate::{Error, Implementation, Result, FQDN};
use crate::{Error, Result, FQDN};
pub struct Client {
inner: Container,
@ -13,7 +13,7 @@ pub struct Client {
impl Client {
pub fn new(network: &Network) -> Result<Self> {
Ok(Self {
inner: Container::run(&Implementation::Unbound, network)?,
inner: Container::run(&Image::Client, network)?,
})
}

View File

@ -1,17 +1,17 @@
mod network;
use core::str;
use core::{fmt, str};
use std::net::Ipv4Addr;
use std::process::{self, ChildStdout, ExitStatus};
use std::process::{Command, Stdio};
use std::sync::atomic::AtomicUsize;
use std::sync::{atomic, Arc};
use std::sync::{atomic, Arc, Once};
use std::{env, fs};
use tempfile::{NamedTempFile, TempDir};
pub use crate::container::network::Network;
use crate::{Error, Implementation, Result};
use crate::{Error, Implementation, Repository, Result};
#[derive(Clone)]
pub struct Container {
@ -20,16 +20,72 @@ pub struct Container {
const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME");
#[derive(Clone)]
pub enum Image {
Client,
Hickory(Repository<'static>),
Unbound,
}
impl Image {
fn dockerfile(&self) -> &'static str {
match self {
Self::Unbound => include_str!("docker/unbound.Dockerfile"),
Self::Hickory { .. } => include_str!("docker/hickory.Dockerfile"),
Self::Client => include_str!("docker/client.Dockerfile"),
}
}
fn once(&self) -> &'static Once {
match self {
Self::Client { .. } => {
static CLIENT_ONCE: Once = Once::new();
&CLIENT_ONCE
}
Self::Hickory { .. } => {
static HICKORY_ONCE: Once = Once::new();
&HICKORY_ONCE
}
Self::Unbound { .. } => {
static UNBOUND_ONCE: Once = Once::new();
&UNBOUND_ONCE
}
}
}
}
impl From<Implementation> for Image {
fn from(implementation: Implementation) -> Self {
match implementation {
Implementation::Unbound => Self::Unbound,
Implementation::Hickory(repo) => Self::Hickory(repo),
}
}
}
impl fmt::Display for Image {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Client => "client",
Self::Hickory { .. } => "hickory",
Self::Unbound => "unbound",
};
f.write_str(s)
}
}
impl Container {
/// Starts the container in a "parked" state
pub fn run(implementation: &Implementation, network: &Network) -> Result<Self> {
pub fn run(image: &Image, network: &Network) -> Result<Self> {
// TODO make this configurable and support hickory & bind
let dockerfile = implementation.dockerfile();
let dockerfile = image.dockerfile();
let docker_build_dir = TempDir::new()?;
let docker_build_dir = docker_build_dir.path();
fs::write(docker_build_dir.join("Dockerfile"), dockerfile)?;
let image_tag = format!("{PACKAGE_NAME}-{implementation}");
let image_tag = format!("{PACKAGE_NAME}-{image}");
let mut command = Command::new("docker");
command
@ -37,13 +93,13 @@ impl Container {
.arg(&image_tag)
.arg(docker_build_dir);
let repo = if let Implementation::Hickory(repo) = implementation {
let repo = if let Image::Hickory(repo) = image {
Some(repo)
} else {
None
};
implementation.once().call_once(|| {
image.once().call_once(|| {
if let Some(repo) = repo {
let mut cp_r = Command::new("git");
cp_r.args([
@ -66,7 +122,7 @@ impl Container {
let mut command = Command::new("docker");
let pid = process::id();
let count = container_count();
let name = format!("{PACKAGE_NAME}-{implementation}-{pid}-{count}");
let name = format!("{PACKAGE_NAME}-{image}-{pid}-{count}");
command
.args([
"run",
@ -334,7 +390,7 @@ mod tests {
#[test]
fn run_works() -> Result<()> {
let network = Network::new()?;
let container = Container::run(&Implementation::Unbound, &network)?;
let container = Container::run(&Image::Client, &network)?;
let output = container.output(&["true"])?;
assert!(output.status.success());
@ -345,7 +401,7 @@ mod tests {
#[test]
fn ipv4_addr_works() -> Result<()> {
let network = Network::new()?;
let container = Container::run(&Implementation::Unbound, &network)?;
let container = Container::run(&Image::Client, &network)?;
let ipv4_addr = container.ipv4_addr();
let output = container.output(&["ping", "-c1", &format!("{ipv4_addr}")])?;
@ -357,7 +413,7 @@ mod tests {
#[test]
fn cp_works() -> Result<()> {
let network = Network::new()?;
let container = Container::run(&Implementation::Unbound, &network)?;
let container = Container::run(&Image::Client, &network)?;
let path = "/tmp/somefile";
let contents = "hello";

View File

@ -113,7 +113,7 @@ fn network_count() -> usize {
#[cfg(test)]
mod tests {
use crate::{container::Container, Implementation};
use crate::container::{Container, Image};
use super::*;
@ -146,7 +146,7 @@ mod tests {
let network = Network::new().expect("Failed to create network");
let network_name = network.name().to_string();
let container =
Container::run(&Implementation::Unbound, &network).expect("Failed to start container");
Container::run(&Image::Client, &network).expect("Failed to start container");
assert!(exists_network(&network_name));
drop(network);

View File

@ -0,0 +1,8 @@
FROM debian:bookworm-slim
# dnsutils = dig & delv
# iputils-ping = ping
RUN apt-get update && \
apt-get install -y \
dnsutils \
iputils-ping

View File

@ -1,12 +1,8 @@
FROM debian:bookworm-slim
# dnsutils = dig & delv
# iputils-ping = ping
# ldns-utils = ldns-{key2ds,keygen,signzone}
RUN apt-get update && \
apt-get install -y \
dnsutils \
iputils-ping \
ldnsutils \
nsd \
tshark \

View File

@ -1,9 +1,7 @@
//! A test framework for all things DNS
use core::fmt;
use std::borrow::Cow;
use std::path::Path;
use std::sync::Once;
use url::Url;
@ -57,45 +55,12 @@ pub fn Repository(input: impl Into<Cow<'static, str>>) -> Repository<'static> {
Repository { inner: input }
}
impl Implementation {
fn dockerfile(&self) -> &'static str {
match self {
Implementation::Unbound => include_str!("docker/unbound.Dockerfile"),
Implementation::Hickory { .. } => include_str!("docker/hickory.Dockerfile"),
}
}
fn once(&self) -> &'static Once {
match self {
Implementation::Unbound => {
static UNBOUND_ONCE: Once = Once::new();
&UNBOUND_ONCE
}
Implementation::Hickory { .. } => {
static HICKORY_ONCE: Once = Once::new();
&HICKORY_ONCE
}
}
}
}
impl Default for Implementation {
fn default() -> Self {
Self::Unbound
}
}
impl fmt::Display for Implementation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Implementation::Unbound => "unbound",
Implementation::Hickory { .. } => "hickory",
};
f.write_str(s)
}
}
pub fn subject() -> Implementation {
if let Ok(subject) = std::env::var("DNS_TEST_SUBJECT") {
if subject == "unbound" {
@ -115,3 +80,7 @@ pub fn subject() -> Implementation {
Implementation::default()
}
}
pub fn peer() -> Implementation {
Implementation::default()
}

View File

@ -25,7 +25,12 @@ impl<'a> NameServer<'a, Stopped> {
/// - one SOA record, with the primary name server field set to this name server's FQDN
/// - one NS record, with this name server's FQDN set as the only available name server for
/// the zone
pub fn new(zone: FQDN<'a>, network: &Network) -> Result<Self> {
pub fn new(implementation: Implementation, zone: FQDN<'a>, network: &Network) -> Result<Self> {
assert!(
matches!(implementation, Implementation::Unbound),
"currently only `unbound` (`nsd`) can be used as a `NameServer`"
);
let ns_count = ns_count();
let nameserver = primary_ns(ns_count);
@ -42,8 +47,9 @@ impl<'a> NameServer<'a, Stopped> {
nameserver: nameserver.clone(),
});
let image = implementation.into();
Ok(Self {
container: Container::run(&Implementation::Unbound, network)?,
container: Container::run(&image, network)?,
zone_file,
state: Stopped,
})
@ -301,7 +307,7 @@ mod tests {
#[test]
fn simplest() -> Result<()> {
let network = Network::new()?;
let tld_ns = NameServer::new(FQDN::COM, &network)?.start()?;
let tld_ns = NameServer::new(Implementation::Unbound, FQDN::COM, &network)?.start()?;
let ip_addr = tld_ns.ipv4_addr();
let client = Client::new(&network)?;
@ -322,7 +328,7 @@ mod tests {
fn with_referral() -> Result<()> {
let network = Network::new()?;
let expected_ip_addr = Ipv4Addr::new(172, 17, 200, 1);
let mut root_ns = NameServer::new(FQDN::ROOT, &network)?;
let mut root_ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, &network)?;
root_ns.referral(
FQDN::COM,
FQDN("primary.tld-server.com.")?,
@ -351,7 +357,7 @@ mod tests {
#[test]
fn signed() -> Result<()> {
let network = Network::new()?;
let ns = NameServer::new(FQDN::ROOT, &network)?.sign()?;
let ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, &network)?.sign()?;
eprintln!("KSK:\n{}", ns.key_signing_key());
eprintln!("ZSK:\n{}", ns.zone_signing_key());
@ -387,7 +393,7 @@ mod tests {
#[test]
fn terminate_works() -> Result<()> {
let network = Network::new()?;
let ns = NameServer::new(FQDN::ROOT, &network)?.start()?;
let ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
let logs = ns.terminate()?;
assert!(logs.contains("nsd starting"));

View File

@ -33,7 +33,8 @@ impl Resolver {
"must configure at least one local root server"
);
let container = Container::run(&implementation, network)?;
let image = implementation.clone().into();
let container = Container::run(&image, network)?;
let mut hints = String::new();
for root in roots {
@ -124,7 +125,7 @@ mod tests {
#[test]
fn terminate_works() -> Result<()> {
let network = Network::new()?;
let ns = NameServer::new(FQDN::ROOT, &network)?.start()?;
let ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, &network)?.start()?;
let resolver = Resolver::start(
Implementation::Unbound,
&[Root::new(ns.fqdn().clone(), ns.ipv4_addr())],

View File

@ -255,7 +255,7 @@ mod tests {
#[test]
fn nameserver() -> Result<()> {
let network = &Network::new()?;
let ns = NameServer::new(FQDN::ROOT, network)?.start()?;
let ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, network)?.start()?;
let mut tshark = ns.eavesdrop()?;
let client = Client::new(network)?;
@ -291,10 +291,11 @@ mod tests {
#[test]
fn resolver() -> Result<()> {
let network = &Network::new()?;
let mut root_ns = NameServer::new(FQDN::ROOT, network)?;
let mut com_ns = NameServer::new(FQDN::COM, network)?;
let mut root_ns = NameServer::new(Implementation::Unbound, FQDN::ROOT, network)?;
let mut com_ns = NameServer::new(Implementation::Unbound, FQDN::COM, network)?;
let mut nameservers_ns = NameServer::new(FQDN("nameservers.com.")?, network)?;
let mut nameservers_ns =
NameServer::new(Implementation::Unbound, FQDN("nameservers.com.")?, network)?;
nameservers_ns
.a(root_ns.fqdn().clone(), root_ns.ipv4_addr())
.a(com_ns.fqdn().clone(), com_ns.ipv4_addr());