initial RecursiveResolver API
This commit is contained in:
parent
9101bb1046
commit
60ecfeca5e
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -25,6 +25,7 @@ name = "dnssec-tests"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"minijinja",
|
||||
"serde",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
|
|
|
@ -8,4 +8,5 @@ license = "MIT or Apache 2.0"
|
|||
|
||||
[dependencies]
|
||||
minijinja = "1.0.12"
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
tempfile = "3.9.0"
|
||||
|
|
|
@ -114,6 +114,7 @@ impl Container {
|
|||
Ok(child)
|
||||
}
|
||||
|
||||
// TODO cache this to avoid calling `docker inspect` every time
|
||||
pub fn ip_addr(&self) -> Result<String> {
|
||||
let mut command = Command::new("docker");
|
||||
command
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub use crate::authoritative_name_server::AuthoritativeNameServer;
|
||||
pub use crate::recursive_resolver::RecursiveResolver;
|
||||
|
||||
pub type Error = Box<dyn std::error::Error>;
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
@ -7,6 +8,7 @@ const CHMOD_RW_EVERYONE: &str = "666";
|
|||
|
||||
mod authoritative_name_server;
|
||||
mod container;
|
||||
mod recursive_resolver;
|
||||
|
||||
pub enum Domain<'a> {
|
||||
Root,
|
||||
|
|
123
src/recursive_resolver.rs
Normal file
123
src/recursive_resolver.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use std::process::Child;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::container::Container;
|
||||
use crate::{Result, CHMOD_RW_EVERYONE};
|
||||
|
||||
pub struct RecursiveResolver {
|
||||
container: Container,
|
||||
child: Child,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RootServer {
|
||||
name: String,
|
||||
ip_addr: String,
|
||||
}
|
||||
|
||||
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> {
|
||||
let container = Container::run()?;
|
||||
|
||||
container.cp(
|
||||
"/etc/unbound/root.hints",
|
||||
&root_hints(root_servers),
|
||||
CHMOD_RW_EVERYONE,
|
||||
)?;
|
||||
|
||||
let child = container.spawn(&["unbound", "-d"])?;
|
||||
|
||||
Ok(Self { child, container })
|
||||
}
|
||||
|
||||
pub fn ip_addr(&self) -> Result<String> {
|
||||
self.container.ip_addr()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RecursiveResolver {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.child.kill();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::AuthoritativeNameServer;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_resolve() -> Result<()> {
|
||||
let root_ns = AuthoritativeNameServer::start(crate::Domain::Root)?;
|
||||
let roots = &[RootServer {
|
||||
name: "my.root-server.com".to_string(),
|
||||
ip_addr: root_ns.ip_addr()?,
|
||||
}];
|
||||
let resolver = RecursiveResolver::start(roots)?;
|
||||
let resolver_ip_addr = resolver.ip_addr()?;
|
||||
|
||||
let container = Container::run()?;
|
||||
let output = container.exec(&["dig", &format!("@{}", resolver_ip_addr), "example.com"])?;
|
||||
|
||||
let stdout = core::str::from_utf8(&output.stdout)?;
|
||||
assert!(stdout.contains("status: NOERROR"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_hints_template_works() {
|
||||
let expected = [
|
||||
("a.root-server.com", "172.17.0.1"),
|
||||
("b.root-server.com", "172.17.0.2"),
|
||||
];
|
||||
|
||||
let roots = expected
|
||||
.iter()
|
||||
.map(|(ns_name, ip_addr)| RootServer {
|
||||
name: ns_name.to_string(),
|
||||
ip_addr: ip_addr.to_string(),
|
||||
})
|
||||
.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, ip_addr);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +1,4 @@
|
|||
. 3600000 NS primary.root-server.com.
|
||||
primary.root-server.com. 3600000 A {{ root_ns_ip_addr }}
|
||||
{%- for root in roots -%}
|
||||
. 3600000 NS {{ root.name }}
|
||||
{{ root.name }} 3600000 A {{ root.ip_addr }}
|
||||
{% endfor %}
|
||||
|
|
Loading…
Reference in New Issue
Block a user