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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"minijinja",
|
"minijinja",
|
||||||
|
"serde",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -8,4 +8,5 @@ license = "MIT or Apache 2.0"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
minijinja = "1.0.12"
|
minijinja = "1.0.12"
|
||||||
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.9.0"
|
||||||
|
@ -114,6 +114,7 @@ impl Container {
|
|||||||
Ok(child)
|
Ok(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO cache this to avoid calling `docker inspect` every time
|
||||||
pub fn ip_addr(&self) -> Result<String> {
|
pub fn ip_addr(&self) -> Result<String> {
|
||||||
let mut command = Command::new("docker");
|
let mut command = Command::new("docker");
|
||||||
command
|
command
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub use crate::authoritative_name_server::AuthoritativeNameServer;
|
pub use crate::authoritative_name_server::AuthoritativeNameServer;
|
||||||
|
pub use crate::recursive_resolver::RecursiveResolver;
|
||||||
|
|
||||||
pub type Error = Box<dyn std::error::Error>;
|
pub type Error = Box<dyn std::error::Error>;
|
||||||
pub type Result<T> = core::result::Result<T, 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 authoritative_name_server;
|
||||||
mod container;
|
mod container;
|
||||||
|
mod recursive_resolver;
|
||||||
|
|
||||||
pub enum Domain<'a> {
|
pub enum Domain<'a> {
|
||||||
Root,
|
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.
|
{%- for root in roots -%}
|
||||||
primary.root-server.com. 3600000 A {{ root_ns_ip_addr }}
|
. 3600000 NS {{ root.name }}
|
||||||
|
{{ root.name }} 3600000 A {{ root.ip_addr }}
|
||||||
|
{% endfor %}
|
||||||
|
Loading…
Reference in New Issue
Block a user