revamp zone file generation
This commit is contained in:
parent
7e9f63d85e
commit
984a05e873
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -25,7 +25,6 @@ name = "dnssec-tests"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"minijinja",
|
||||
"serde",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
|
|
|
@ -4,9 +4,6 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
license = "MIT or Apache 2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
minijinja = "1.0.12"
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
tempfile = "3.9.0"
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use std::net::Ipv4Addr;
|
||||
use std::process::Child;
|
||||
|
||||
use crate::{container::Container, Domain, Result, CHMOD_RW_EVERYONE};
|
||||
use crate::container::Container;
|
||||
use crate::record::{self, Referral, SoaSettings, Zone};
|
||||
use crate::{Domain, Result, CHMOD_RW_EVERYONE};
|
||||
|
||||
pub struct AuthoritativeNameServer {
|
||||
pub struct AuthoritativeNameServer<'a> {
|
||||
child: Child,
|
||||
container: Container,
|
||||
zone: Zone<'a>,
|
||||
}
|
||||
|
||||
impl AuthoritativeNameServer {
|
||||
pub fn start(domain: Domain) -> Result<Self> {
|
||||
impl<'a> AuthoritativeNameServer<'a> {
|
||||
pub fn start(domain: Domain<'a>, referrals: &[Referral<'a>]) -> Result<Self> {
|
||||
let container = Container::run()?;
|
||||
|
||||
// for PID file
|
||||
|
@ -17,46 +20,48 @@ impl AuthoritativeNameServer {
|
|||
|
||||
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)?;
|
||||
container.cp("/etc/nsd/nsd.conf", &nsd_conf(&domain), CHMOD_RW_EVERYONE)?;
|
||||
|
||||
let zone_file_contents = if domain.is_root() {
|
||||
root_zone()
|
||||
} else {
|
||||
tld_zone(domain)
|
||||
let ns_count = crate::nameserver_count();
|
||||
let ns = Domain(format!("primary.ns{ns_count}.com."))?;
|
||||
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_file_contents, CHMOD_RW_EVERYONE)?;
|
||||
container.cp(zone_path, &zone.to_string(), CHMOD_RW_EVERYONE)?;
|
||||
|
||||
let child = container.spawn(&["nsd", "-d"])?;
|
||||
|
||||
Ok(Self { child, container })
|
||||
Ok(Self {
|
||||
child,
|
||||
container,
|
||||
zone,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ipv4_addr(&self) -> Ipv4Addr {
|
||||
self.container.ipv4_addr()
|
||||
}
|
||||
|
||||
pub fn nameserver(&self) -> &Domain<'a> {
|
||||
&self.zone.soa.ns
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AuthoritativeNameServer {
|
||||
impl Drop for AuthoritativeNameServer<'_> {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.child.kill();
|
||||
}
|
||||
}
|
||||
|
||||
fn tld_zone(domain: Domain) -> String {
|
||||
assert!(!domain.is_root());
|
||||
|
||||
minijinja::render!(
|
||||
include_str!("templates/tld.zone.jinja"),
|
||||
tld => domain.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
fn root_zone() -> String {
|
||||
minijinja::render!(include_str!("templates/root.zone.jinja"),)
|
||||
}
|
||||
|
||||
fn nsd_conf(domain: Domain) -> String {
|
||||
fn nsd_conf(domain: &Domain) -> String {
|
||||
minijinja::render!(
|
||||
include_str!("templates/nsd.conf.jinja"),
|
||||
domain => domain.as_str()
|
||||
|
@ -68,8 +73,8 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tld_setup() -> Result<()> {
|
||||
let tld_ns = AuthoritativeNameServer::start(Domain("com.")?)?;
|
||||
fn tld_ns() -> Result<()> {
|
||||
let tld_ns = AuthoritativeNameServer::start(Domain("com.")?, &[])?;
|
||||
let ip_addr = tld_ns.ipv4_addr();
|
||||
|
||||
let client = Container::run()?;
|
||||
|
@ -83,8 +88,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn root_setup() -> Result<()> {
|
||||
let root_ns = AuthoritativeNameServer::start(Domain::ROOT)?;
|
||||
fn root_ns() -> Result<()> {
|
||||
let root_ns = AuthoritativeNameServer::start(Domain::ROOT, &[])?;
|
||||
let ip_addr = root_ns.ipv4_addr();
|
||||
|
||||
let client = Container::run()?;
|
||||
|
@ -96,4 +101,27 @@ mod tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_ns_with_referral() -> Result<()> {
|
||||
let expected_ip_addr = Ipv4Addr::new(172, 17, 200, 1);
|
||||
let root_ns = AuthoritativeNameServer::start(
|
||||
Domain::ROOT,
|
||||
&[Referral {
|
||||
domain: Domain("com.")?,
|
||||
ipv4_addr: expected_ip_addr,
|
||||
ns: Domain("primary.tld-server.com.")?,
|
||||
}],
|
||||
)?;
|
||||
let ip_addr = root_ns.ipv4_addr();
|
||||
|
||||
let client = Container::run()?;
|
||||
let output = client.output(&["dig", &format!("@{ip_addr}"), "NS", "com."])?;
|
||||
|
||||
assert!(output.status.success());
|
||||
eprintln!("{}", output.stdout);
|
||||
assert!(output.stdout.contains("status: NOERROR"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ impl Container {
|
|||
|
||||
let output: Output = checked_output(&mut command)?.try_into()?;
|
||||
let id = output.stdout;
|
||||
dbg!(&id);
|
||||
|
||||
let ipv4_addr = get_ipv4_addr(&id)?;
|
||||
|
||||
|
@ -183,7 +182,6 @@ fn get_ipv4_addr(container_id: &str) -> Result<Ipv4Addr> {
|
|||
}
|
||||
|
||||
let ipv4_addr = str::from_utf8(&output.stdout)?.trim().to_string();
|
||||
dbg!(&ipv4_addr);
|
||||
|
||||
Ok(ipv4_addr.parse()?)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
use core::fmt;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone)]
|
||||
pub struct Domain<'a> {
|
||||
inner: &'a str,
|
||||
inner: Cow<'a, str>,
|
||||
}
|
||||
|
||||
// TODO likely needs further validation
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Domain(input: &str) -> Result<Domain<'_>> {
|
||||
pub fn Domain<'a>(input: impl Into<Cow<'a, str>>) -> Result<Domain<'a>> {
|
||||
let input = input.into();
|
||||
if !input.ends_with('.') {
|
||||
return Err("domain must end with a `.`".into());
|
||||
}
|
||||
|
@ -20,13 +24,32 @@ pub fn Domain(input: &str) -> Result<Domain<'_>> {
|
|||
}
|
||||
|
||||
impl<'a> Domain<'a> {
|
||||
pub const ROOT: Domain<'static> = Domain { inner: "." };
|
||||
pub const ROOT: Domain<'static> = Domain {
|
||||
inner: Cow::Borrowed("."),
|
||||
};
|
||||
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.inner == "."
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'a str {
|
||||
self.inner
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn into_owned(self) -> Domain<'static> {
|
||||
let owned = match self.inner {
|
||||
Cow::Borrowed(borrowed) => borrowed.to_string(),
|
||||
Cow::Owned(owned) => owned,
|
||||
};
|
||||
|
||||
Domain {
|
||||
inner: Cow::Owned(owned),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Domain<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.inner)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::atomic::{self, AtomicUsize};
|
||||
|
||||
pub use crate::authoritative_name_server::AuthoritativeNameServer;
|
||||
pub use crate::domain::Domain;
|
||||
pub use crate::recursive_resolver::RecursiveResolver;
|
||||
|
@ -10,4 +12,10 @@ const CHMOD_RW_EVERYONE: &str = "666";
|
|||
mod authoritative_name_server;
|
||||
pub mod container;
|
||||
mod domain;
|
||||
pub mod record;
|
||||
mod recursive_resolver;
|
||||
|
||||
fn nameserver_count() -> usize {
|
||||
static COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
COUNT.fetch_add(1, atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
|
293
src/record.rs
Normal file
293
src/record.rs
Normal file
|
@ -0,0 +1,293 @@
|
|||
//! DNS records in BIND syntax
|
||||
//!
|
||||
//! Note that the `@` syntax is not used to avoid relying on the order of the records
|
||||
|
||||
use core::fmt;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::Domain;
|
||||
|
||||
pub struct Zone<'a> {
|
||||
pub origin: Domain<'a>,
|
||||
pub ttl: u32,
|
||||
pub soa: Soa<'a>,
|
||||
pub records: Vec<Record<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Zone<'a> {
|
||||
/// Convenience constructor that uses "reasonable" defaults
|
||||
pub fn new(origin: Domain<'a>, soa: Soa<'a>) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
ttl: 1800,
|
||||
soa,
|
||||
records: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends a record
|
||||
pub fn record(&mut self, record: impl Into<Record<'a>>) {
|
||||
self.records.push(record.into())
|
||||
}
|
||||
|
||||
/// Appends a NS + A record pair
|
||||
pub fn referral(&mut self, referral: &Referral<'a>) {
|
||||
let Referral {
|
||||
domain,
|
||||
ipv4_addr,
|
||||
ns,
|
||||
} = referral;
|
||||
|
||||
self.record(Ns {
|
||||
domain: domain.clone(),
|
||||
ns: ns.clone(),
|
||||
});
|
||||
self.record(A {
|
||||
domain: domain.clone(),
|
||||
ipv4_addr: *ipv4_addr,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Zone<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self {
|
||||
origin,
|
||||
ttl,
|
||||
soa,
|
||||
records,
|
||||
} = self;
|
||||
|
||||
writeln!(f, "$ORIGIN {origin}")?;
|
||||
writeln!(f, "$TTL {ttl}")?;
|
||||
writeln!(f, "{soa}")?;
|
||||
|
||||
for record in records {
|
||||
writeln!(f, "{record}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Referral<'a> {
|
||||
pub domain: Domain<'a>,
|
||||
pub ipv4_addr: Ipv4Addr,
|
||||
pub ns: Domain<'a>,
|
||||
}
|
||||
|
||||
pub struct Root<'a> {
|
||||
pub ipv4_addr: Ipv4Addr,
|
||||
pub ns: Domain<'a>,
|
||||
pub ttl: u32,
|
||||
}
|
||||
|
||||
impl<'a> Root<'a> {
|
||||
/// Convenience constructor that uses "reasonable" defaults
|
||||
pub fn new(ns: Domain<'a>, ipv4_addr: Ipv4Addr) -> Self {
|
||||
Self {
|
||||
ipv4_addr,
|
||||
ns,
|
||||
ttl: 3600000, // 1000 hours
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Root<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self { ipv4_addr, ns, ttl } = self;
|
||||
|
||||
writeln!(f, ".\t{ttl}\tNS\t{ns}")?;
|
||||
write!(f, "{ns}\t{ttl}\tA\t{ipv4_addr}")
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Record<'a> {
|
||||
A(A<'a>),
|
||||
Ns(Ns<'a>),
|
||||
}
|
||||
|
||||
impl<'a> From<A<'a>> for Record<'a> {
|
||||
fn from(v: A<'a>) -> Self {
|
||||
Self::A(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Ns<'a>> for Record<'a> {
|
||||
fn from(v: Ns<'a>) -> Self {
|
||||
Self::Ns(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Record<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Record::A(a) => a.fmt(f),
|
||||
Record::Ns(ns) => ns.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct A<'a> {
|
||||
pub domain: Domain<'a>,
|
||||
pub ipv4_addr: Ipv4Addr,
|
||||
}
|
||||
|
||||
impl fmt::Display for A<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self { domain, ipv4_addr } = self;
|
||||
|
||||
write!(f, "{domain}\tIN\tA\t{ipv4_addr}")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ns<'a> {
|
||||
pub domain: Domain<'a>,
|
||||
pub ns: Domain<'a>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Ns<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self { domain, ns } = self;
|
||||
|
||||
write!(f, "{domain}\tIN\tNS\t{ns}")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Soa<'a> {
|
||||
pub domain: Domain<'a>,
|
||||
pub ns: Domain<'a>,
|
||||
pub admin: Domain<'a>,
|
||||
pub settings: SoaSettings,
|
||||
}
|
||||
|
||||
impl fmt::Display for Soa<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self {
|
||||
domain,
|
||||
ns,
|
||||
admin,
|
||||
settings,
|
||||
} = self;
|
||||
|
||||
write!(f, "{domain}\tIN\tSOA\t{ns}\t{admin}\t{settings}")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SoaSettings {
|
||||
pub serial: u32,
|
||||
pub refresh: u32,
|
||||
pub retry: u32,
|
||||
pub expire: u32,
|
||||
pub minimum: u32,
|
||||
}
|
||||
|
||||
impl Default for SoaSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
serial: 2024010101,
|
||||
refresh: 1800, // 30 minutes
|
||||
retry: 900, // 15 minutes
|
||||
expire: 604800, // 1 week
|
||||
minimum: 86400, // 1 day
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SoaSettings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self {
|
||||
serial,
|
||||
refresh,
|
||||
retry,
|
||||
expire,
|
||||
minimum,
|
||||
} = self;
|
||||
|
||||
write!(f, "( {serial} {refresh} {retry} {expire} {minimum} )")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Result;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_to_string() -> Result<()> {
|
||||
let expected = "e.gtld-servers.net. IN A 192.12.94.30";
|
||||
let a = example_a()?;
|
||||
assert_eq!(expected, a.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ns_to_string() -> Result<()> {
|
||||
let expected = "com. IN NS e.gtld-servers.net.";
|
||||
let ns = example_ns()?;
|
||||
assert_eq!(expected, ns.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_to_string() -> Result<()> {
|
||||
let expected = ". 3600000 NS a.root-servers.net.
|
||||
a.root-servers.net. 3600000 A 198.41.0.4";
|
||||
let root = Root::new(Domain("a.root-servers.net.")?, Ipv4Addr::new(198, 41, 0, 4));
|
||||
assert_eq!(expected, root.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn soa_to_string() -> Result<()> {
|
||||
let expected =
|
||||
". IN SOA a.root-servers.net. nstld.verisign-grs.com. ( 2024010101 1800 900 604800 86400 )";
|
||||
let soa = example_soa()?;
|
||||
assert_eq!(expected, soa.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zone_file_to_string() -> Result<()> {
|
||||
let expected = "$ORIGIN .
|
||||
$TTL 1800
|
||||
. IN SOA a.root-servers.net. nstld.verisign-grs.com. ( 2024010101 1800 900 604800 86400 )
|
||||
com. IN NS e.gtld-servers.net.
|
||||
e.gtld-servers.net. IN A 192.12.94.30
|
||||
";
|
||||
let mut zone = Zone::new(Domain::ROOT, example_soa()?);
|
||||
zone.record(example_ns()?);
|
||||
zone.record(example_a()?);
|
||||
|
||||
assert_eq!(expected, zone.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn example_a() -> Result<A<'static>> {
|
||||
Ok(A {
|
||||
domain: Domain("e.gtld-servers.net.")?,
|
||||
ipv4_addr: Ipv4Addr::new(192, 12, 94, 30),
|
||||
})
|
||||
}
|
||||
|
||||
fn example_ns() -> Result<Ns<'static>> {
|
||||
Ok(Ns {
|
||||
domain: Domain("com.")?,
|
||||
ns: Domain("e.gtld-servers.net.")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn example_soa() -> Result<Soa<'static>> {
|
||||
Ok(Soa {
|
||||
domain: Domain(".")?,
|
||||
ns: Domain("a.root-servers.net.")?,
|
||||
admin: Domain("nstld.verisign-grs.com.")?,
|
||||
settings: SoaSettings::default(),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
use core::fmt::Write;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::process::Child;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::record::Root;
|
||||
use crate::{Result, CHMOD_RW_EVERYONE};
|
||||
|
||||
pub struct RecursiveResolver {
|
||||
|
@ -11,28 +11,16 @@ pub struct RecursiveResolver {
|
|||
child: Child,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RootServer {
|
||||
name: String,
|
||||
ip_addr: Ipv4Addr,
|
||||
}
|
||||
|
||||
fn root_hints(roots: &[RootServer]) -> String {
|
||||
minijinja::render!(
|
||||
include_str!("templates/root.hints.jinja"),
|
||||
roots => roots
|
||||
)
|
||||
}
|
||||
|
||||
impl RecursiveResolver {
|
||||
pub fn start(root_servers: &[RootServer]) -> Result<Self> {
|
||||
pub fn start(roots: &[Root]) -> Result<Self> {
|
||||
let container = Container::run()?;
|
||||
|
||||
container.cp(
|
||||
"/etc/unbound/root.hints",
|
||||
&root_hints(root_servers),
|
||||
CHMOD_RW_EVERYONE,
|
||||
)?;
|
||||
let mut hints = String::new();
|
||||
for root in roots {
|
||||
writeln!(hints, "{root}").unwrap();
|
||||
}
|
||||
|
||||
container.cp("/etc/unbound/root.hints", &hints, CHMOD_RW_EVERYONE)?;
|
||||
|
||||
let child = container.spawn(&["unbound", "-d"])?;
|
||||
|
||||
|
@ -52,18 +40,24 @@ impl Drop for RecursiveResolver {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{AuthoritativeNameServer, Domain};
|
||||
use crate::{record::Referral, AuthoritativeNameServer, Domain};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore = "FIXME"]
|
||||
fn can_resolve() -> Result<()> {
|
||||
let root_ns = AuthoritativeNameServer::start(Domain::ROOT)?;
|
||||
let roots = &[RootServer {
|
||||
name: "my.root-server.com".to_string(),
|
||||
ip_addr: root_ns.ipv4_addr(),
|
||||
}];
|
||||
let tld_ns = AuthoritativeNameServer::start(Domain("com.")?, &[])?;
|
||||
|
||||
let root_ns = AuthoritativeNameServer::start(
|
||||
Domain::ROOT,
|
||||
&[Referral {
|
||||
domain: Domain("com.")?,
|
||||
ipv4_addr: tld_ns.ipv4_addr(),
|
||||
ns: tld_ns.nameserver().clone(),
|
||||
}],
|
||||
)?;
|
||||
|
||||
let roots = &[Root::new(root_ns.nameserver().clone(), root_ns.ipv4_addr())];
|
||||
let resolver = RecursiveResolver::start(roots)?;
|
||||
let resolver_ip_addr = resolver.ipv4_addr();
|
||||
|
||||
|
@ -71,56 +65,11 @@ mod tests {
|
|||
let output =
|
||||
container.output(&["dig", &format!("@{}", resolver_ip_addr), "example.com"])?;
|
||||
|
||||
eprintln!("{}", output.stdout);
|
||||
|
||||
assert!(output.status.success());
|
||||
assert!(output.stdout.contains("status: NOERROR"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_hints_template_works() {
|
||||
let expected = [
|
||||
("a.root-server.com", Ipv4Addr::new(172, 17, 0, 1)),
|
||||
("b.root-server.com", Ipv4Addr::new(172, 17, 0, 2)),
|
||||
];
|
||||
|
||||
let roots = expected
|
||||
.iter()
|
||||
.map(|(ns_name, ip_addr)| RootServer {
|
||||
name: ns_name.to_string(),
|
||||
ip_addr: *ip_addr,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let hints = root_hints(&roots);
|
||||
|
||||
eprintln!("{hints}");
|
||||
let lines = hints.lines().collect::<Vec<_>>();
|
||||
|
||||
for (lines, (expected_ns_name, expected_ip_addr)) in lines.chunks(2).zip(expected) {
|
||||
let [ns_record, a_record] = lines.try_into().unwrap();
|
||||
|
||||
// block to avoid shadowing
|
||||
{
|
||||
let [domain, _ttl, record_type, ns_name] = ns_record
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(".", domain);
|
||||
assert_eq!("NS", record_type);
|
||||
assert_eq!(expected_ns_name, ns_name);
|
||||
}
|
||||
|
||||
let [ns_name, _ttl, record_type, ip_addr] = a_record
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
assert_eq!(expected_ns_name, ns_name);
|
||||
assert_eq!("A", record_type);
|
||||
assert_eq!(expected_ip_addr.to_string(), ip_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{%- for root in roots -%}
|
||||
. 3600000 NS {{ root.name }}
|
||||
{{ root.name }} 3600000 A {{ root.ip_addr }}
|
||||
{% endfor %}
|
|
@ -1,12 +0,0 @@
|
|||
$ORIGIN .
|
||||
$TTL 1800
|
||||
@ IN SOA primary.root-server.com admin.root-server.com (
|
||||
2014010100 ; Serial
|
||||
10800 ; Refresh (3 hours)
|
||||
900 ; Retry (15 minutes)
|
||||
604800 ; Expire (1 week)
|
||||
86400 ; Minimum (1 day)
|
||||
)
|
||||
@ IN NS primary.root-server.com
|
||||
|
||||
; TODO referral
|
|
@ -1,12 +0,0 @@
|
|||
$ORIGIN {{ tld }}
|
||||
$TTL 1800
|
||||
@ IN SOA primary.tld-server.{{ tld }} admin.tld-server.{{ tld }} (
|
||||
2014010100 ; Serial
|
||||
10800 ; Refresh (3 hours)
|
||||
900 ; Retry (15 minutes)
|
||||
604800 ; Expire (1 week)
|
||||
86400 ; Minimum (1 day)
|
||||
)
|
||||
@ IN NS primary.tld-server.{{ tld }}
|
||||
|
||||
; intentionally blank
|
Loading…
Reference in New Issue
Block a user