make resolution test work

This commit is contained in:
Jorge Aparicio 2024-02-05 19:21:52 +01:00
parent 984a05e873
commit d13186e404
4 changed files with 172 additions and 40 deletions

View File

@ -7,3 +7,6 @@ license = "MIT or Apache 2.0"
[dependencies] [dependencies]
minijinja = "1.0.12" minijinja = "1.0.12"
tempfile = "3.9.0" tempfile = "3.9.0"
[lib]
doctest = false

View File

@ -12,40 +12,26 @@ pub struct AuthoritativeNameServer<'a> {
} }
impl<'a> AuthoritativeNameServer<'a> { impl<'a> AuthoritativeNameServer<'a> {
pub fn start(domain: Domain<'a>, referrals: &[Referral<'a>]) -> Result<Self> { /// Spins up a container in a parked state where the name server is not running yet
let container = Container::run()?; pub fn reserve() -> Result<StoppedAuthoritativeNameServer> {
// for PID file
container.status_ok(&["mkdir", "-p", "/run/nsd/"])?;
container.status_ok(&["mkdir", "-p", "/etc/nsd/zones"])?;
let zone_path = "/etc/nsd/zones/main.zone";
container.cp("/etc/nsd/nsd.conf", &nsd_conf(&domain), CHMOD_RW_EVERYONE)?;
let ns_count = crate::nameserver_count(); let ns_count = crate::nameserver_count();
let ns = Domain(format!("primary.ns{ns_count}.com."))?; let nameserver = primary_ns(ns_count);
let soa = record::Soa {
domain: domain.clone(),
ns,
admin: Domain(format!("admin.ns{ns_count}.com."))?,
settings: SoaSettings::default(),
};
let mut zone = Zone::new(domain, soa);
for referral in referrals {
zone.referral(referral)
}
container.cp(zone_path, &zone.to_string(), CHMOD_RW_EVERYONE)?; Ok(StoppedAuthoritativeNameServer {
container: Container::run()?,
let child = container.spawn(&["nsd", "-d"])?; nameserver,
ns_count,
Ok(Self {
child,
container,
zone,
}) })
} }
pub fn start(
domain: Domain<'a>,
referrals: &[Referral<'a>],
a_records: &[record::A<'a>],
) -> Result<Self> {
Self::reserve()?.start(domain, referrals, a_records)
}
pub fn ipv4_addr(&self) -> Ipv4Addr { pub fn ipv4_addr(&self) -> Ipv4Addr {
self.container.ipv4_addr() self.container.ipv4_addr()
} }
@ -53,6 +39,10 @@ impl<'a> AuthoritativeNameServer<'a> {
pub fn nameserver(&self) -> &Domain<'a> { pub fn nameserver(&self) -> &Domain<'a> {
&self.zone.soa.ns &self.zone.soa.ns
} }
pub fn zone(&self) -> &Zone<'a> {
&self.zone
}
} }
impl Drop for AuthoritativeNameServer<'_> { impl Drop for AuthoritativeNameServer<'_> {
@ -61,6 +51,85 @@ impl Drop for AuthoritativeNameServer<'_> {
} }
} }
fn primary_ns(ns_count: usize) -> Domain<'static> {
Domain(format!("primary{ns_count}.nameservers.com.")).unwrap()
}
fn admin_ns(ns_count: usize) -> Domain<'static> {
Domain(format!("admin{ns_count}.nameservers.com.")).unwrap()
}
pub struct StoppedAuthoritativeNameServer {
container: Container,
nameserver: Domain<'static>,
ns_count: usize,
}
impl StoppedAuthoritativeNameServer {
pub fn ipv4_addr(&self) -> Ipv4Addr {
self.container.ipv4_addr()
}
pub fn nameserver(&self) -> &Domain<'static> {
&self.nameserver
}
pub fn start<'a>(
self,
domain: Domain<'a>,
referrals: &[Referral<'a>],
a_records: &[record::A<'a>],
) -> Result<AuthoritativeNameServer<'a>> {
let Self {
container,
nameserver,
ns_count,
} = self;
// for PID file
container.status_ok(&["mkdir", "-p", "/run/nsd/"])?;
container.status_ok(&["mkdir", "-p", "/etc/nsd/zones"])?;
let zone_path = "/etc/nsd/zones/main.zone";
container.cp("/etc/nsd/nsd.conf", &nsd_conf(&domain), CHMOD_RW_EVERYONE)?;
let soa = record::Soa {
domain: domain.clone(),
ns: nameserver.clone(),
admin: admin_ns(ns_count),
settings: SoaSettings::default(),
};
let mut zone = Zone::new(domain.clone(), soa);
zone.record(record::Ns {
domain: domain.clone(),
ns: nameserver,
});
zone.record(record::A {
domain,
ipv4_addr: container.ipv4_addr(),
});
for referral in referrals {
zone.referral(referral)
}
for a in a_records {
zone.record(a.clone())
}
container.cp(zone_path, &zone.to_string(), CHMOD_RW_EVERYONE)?;
let child = container.spawn(&["nsd", "-d"])?;
Ok(AuthoritativeNameServer {
child,
container,
zone,
})
}
}
fn nsd_conf(domain: &Domain) -> String { fn nsd_conf(domain: &Domain) -> String {
minijinja::render!( minijinja::render!(
include_str!("templates/nsd.conf.jinja"), include_str!("templates/nsd.conf.jinja"),
@ -74,7 +143,7 @@ mod tests {
#[test] #[test]
fn tld_ns() -> Result<()> { fn tld_ns() -> Result<()> {
let tld_ns = AuthoritativeNameServer::start(Domain("com.")?, &[])?; let tld_ns = AuthoritativeNameServer::start(Domain("com.")?, &[], &[])?;
let ip_addr = tld_ns.ipv4_addr(); let ip_addr = tld_ns.ipv4_addr();
let client = Container::run()?; let client = Container::run()?;
@ -89,7 +158,7 @@ mod tests {
#[test] #[test]
fn root_ns() -> Result<()> { fn root_ns() -> Result<()> {
let root_ns = AuthoritativeNameServer::start(Domain::ROOT, &[])?; let root_ns = AuthoritativeNameServer::start(Domain::ROOT, &[], &[])?;
let ip_addr = root_ns.ipv4_addr(); let ip_addr = root_ns.ipv4_addr();
let client = Container::run()?; let client = Container::run()?;
@ -112,6 +181,7 @@ mod tests {
ipv4_addr: expected_ip_addr, ipv4_addr: expected_ip_addr,
ns: Domain("primary.tld-server.com.")?, ns: Domain("primary.tld-server.com.")?,
}], }],
&[],
)?; )?;
let ip_addr = root_ns.ipv4_addr(); let ip_addr = root_ns.ipv4_addr();

View File

@ -43,7 +43,7 @@ impl<'a> Zone<'a> {
ns: ns.clone(), ns: ns.clone(),
}); });
self.record(A { self.record(A {
domain: domain.clone(), domain: ns.clone(),
ipv4_addr: *ipv4_addr, ipv4_addr: *ipv4_addr,
}); });
} }
@ -128,6 +128,7 @@ impl fmt::Display for Record<'_> {
} }
} }
#[derive(Clone)]
pub struct A<'a> { pub struct A<'a> {
pub domain: Domain<'a>, pub domain: Domain<'a>,
pub ipv4_addr: Ipv4Addr, pub ipv4_addr: Ipv4Addr,

View File

@ -40,36 +40,94 @@ impl Drop for RecursiveResolver {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{record::Referral, AuthoritativeNameServer, Domain}; use crate::{
record::{self, Referral},
AuthoritativeNameServer, Domain,
};
use super::*; use super::*;
#[test] #[test]
fn can_resolve() -> Result<()> { fn can_resolve() -> Result<()> {
let tld_ns = AuthoritativeNameServer::start(Domain("com.")?, &[])?; let expected_ipv4_addr = Ipv4Addr::new(1, 2, 3, 4);
let needle = Domain("example.nameservers.com.")?;
let root_ns = AuthoritativeNameServer::reserve()?;
let com_ns = AuthoritativeNameServer::reserve()?;
let root_ns = AuthoritativeNameServer::start( let nameservers_domain = Domain("nameservers.com.")?;
let nameservers_ns = AuthoritativeNameServer::start(
nameservers_domain.clone(),
&[],
&[
record::A {
domain: root_ns.nameserver().clone(),
ipv4_addr: root_ns.ipv4_addr(),
},
record::A {
domain: com_ns.nameserver().clone(),
ipv4_addr: com_ns.ipv4_addr(),
},
record::A {
domain: needle.clone(),
ipv4_addr: expected_ipv4_addr,
},
],
)?;
eprintln!("nameservers.com.zone:\n{}", nameservers_ns.zone());
let com_domain = Domain("com.")?;
let com_ns = com_ns.start(
com_domain.clone(),
&[Referral {
domain: nameservers_domain,
ipv4_addr: nameservers_ns.ipv4_addr(),
ns: nameservers_ns.nameserver().clone(),
}],
&[],
)?;
eprintln!("com.zone:\n{}", com_ns.zone());
let root_ns = root_ns.start(
Domain::ROOT, Domain::ROOT,
&[Referral { &[Referral {
domain: Domain("com.")?, domain: com_domain,
ipv4_addr: tld_ns.ipv4_addr(), ipv4_addr: com_ns.ipv4_addr(),
ns: tld_ns.nameserver().clone(), ns: com_ns.nameserver().clone(),
}], }],
&[],
)?; )?;
eprintln!("root.zone:\n{}", root_ns.zone());
let roots = &[Root::new(root_ns.nameserver().clone(), root_ns.ipv4_addr())]; let roots = &[Root::new(root_ns.nameserver().clone(), root_ns.ipv4_addr())];
let resolver = RecursiveResolver::start(roots)?; let resolver = RecursiveResolver::start(roots)?;
let resolver_ip_addr = resolver.ipv4_addr(); let resolver_ip_addr = resolver.ipv4_addr();
let container = Container::run()?; let container = Container::run()?;
let output = let output = container.output(&[
container.output(&["dig", &format!("@{}", resolver_ip_addr), "example.com"])?; "dig",
&format!("@{}", resolver_ip_addr),
&needle.to_string(),
])?;
eprintln!("{}", output.stdout); eprintln!("{}", output.stdout);
assert!(output.status.success()); assert!(output.status.success());
assert!(output.stdout.contains("status: NOERROR")); assert!(output.stdout.contains("status: NOERROR"));
let mut found = false;
let needle = needle.to_string();
for line in output.stdout.lines() {
if line.starts_with(&needle) {
found = true;
assert!(line.ends_with(&expected_ipv4_addr.to_string()));
}
}
assert!(found);
Ok(()) Ok(())
} }
} }