add name_server::Graph
This commit is contained in:
parent
95f94e2c7b
commit
7bb6b9439c
|
@ -157,7 +157,7 @@ impl Container {
|
|||
id,
|
||||
name,
|
||||
ipv4_addr,
|
||||
_network: network.clone(),
|
||||
network: network.clone(),
|
||||
};
|
||||
Ok(Self {
|
||||
inner: Arc::new(inner),
|
||||
|
@ -250,6 +250,10 @@ impl Container {
|
|||
pub fn id(&self) -> &str {
|
||||
&self.inner.id
|
||||
}
|
||||
|
||||
pub(crate) fn network(&self) -> &Network {
|
||||
&self.inner.network
|
||||
}
|
||||
}
|
||||
|
||||
fn verbose_docker_build() -> bool {
|
||||
|
@ -282,7 +286,7 @@ struct Inner {
|
|||
id: String,
|
||||
// TODO probably also want the IPv6 address
|
||||
ipv4_addr: Ipv4Addr,
|
||||
_network: Network,
|
||||
network: Network,
|
||||
}
|
||||
|
||||
/// NOTE unlike `std::process::Child`, the drop implementation of this type will `kill` the
|
||||
|
|
|
@ -33,6 +33,10 @@ impl FQDN {
|
|||
inner: Cow::Borrowed("com."),
|
||||
};
|
||||
|
||||
pub const NAMESERVERS: FQDN = FQDN {
|
||||
inner: Cow::Borrowed("nameservers.com."),
|
||||
};
|
||||
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.inner == "."
|
||||
}
|
||||
|
@ -51,6 +55,28 @@ impl FQDN {
|
|||
inner: Cow::Owned(owned),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<FQDN> {
|
||||
let (fragment, parent) = self.inner.split_once('.').unwrap();
|
||||
|
||||
if fragment.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let parent = if parent.is_empty() {
|
||||
FQDN::ROOT
|
||||
} else {
|
||||
FQDN(parent.to_string()).unwrap()
|
||||
};
|
||||
Some(parent)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn num_labels(&self) -> usize {
|
||||
self.inner
|
||||
.split('.')
|
||||
.filter(|label| !label.is_empty())
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FQDN {
|
||||
|
@ -72,3 +98,37 @@ impl fmt::Display for FQDN {
|
|||
f.write_str(&self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parent() -> Result<()> {
|
||||
let mut fqdn = FQDN("example.nameservers.com.")?;
|
||||
assert_eq!(3, fqdn.num_labels());
|
||||
|
||||
let parent = fqdn.parent();
|
||||
assert_eq!(
|
||||
Some("nameservers.com."),
|
||||
parent.as_ref().map(|fqdn| fqdn.as_str())
|
||||
);
|
||||
fqdn = parent.unwrap();
|
||||
assert_eq!(2, fqdn.num_labels());
|
||||
|
||||
let parent = fqdn.parent();
|
||||
assert_eq!(Some(FQDN::COM), parent);
|
||||
fqdn = parent.unwrap();
|
||||
assert_eq!(1, fqdn.num_labels());
|
||||
|
||||
let parent = fqdn.parent();
|
||||
assert_eq!(Some(FQDN::ROOT), parent);
|
||||
fqdn = parent.unwrap();
|
||||
assert_eq!(0, fqdn.num_labels());
|
||||
|
||||
let parent = fqdn.parent();
|
||||
assert!(parent.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,135 @@ use crate::container::{Child, Container, Network};
|
|||
use crate::implementation::{Config, Role};
|
||||
use crate::record::{self, Record, SoaSettings, DS, SOA};
|
||||
use crate::tshark::Tshark;
|
||||
use crate::zone_file::{self, ZoneFile};
|
||||
use crate::{Implementation, Result, DEFAULT_TTL, FQDN};
|
||||
use crate::zone_file::{self, Root, ZoneFile};
|
||||
use crate::{Implementation, Result, TrustAnchor, DEFAULT_TTL, FQDN};
|
||||
|
||||
pub struct Graph {
|
||||
pub nameservers: Vec<NameServer<Running>>,
|
||||
pub root: Root,
|
||||
pub trust_anchor: Option<TrustAnchor>,
|
||||
}
|
||||
|
||||
/// Whether to sign the zone files
|
||||
pub enum Sign<'a> {
|
||||
No,
|
||||
Yes,
|
||||
/// Signs the zone files and then modifies the records produced by the signing process
|
||||
// XXX if captures are needed use `&dyn Fn(..)` instead of a function pointer
|
||||
AndAmend(&'a dyn Fn(&FQDN, &mut Vec<Record>)),
|
||||
}
|
||||
|
||||
impl Graph {
|
||||
/// Builds up a minimal DNS graph from `leaf` up to a root name server and returns all the
|
||||
/// name servers in the graph
|
||||
///
|
||||
/// All new name servers will share the `Implementation` of `leaf`.
|
||||
///
|
||||
/// The returned name servers are sorted from leaf zone to root zone.
|
||||
///
|
||||
/// both `Sign::Yes` and `Sign::AndAmend` will add a DS record with the hash of the child's
|
||||
/// key to the parent's zone file
|
||||
///
|
||||
/// a non-empty `TrustAnchor` is returned only when `Sign::Yes` or `Sign::AndAmend` is used
|
||||
pub fn build(leaf: NameServer<Stopped>, sign: Sign) -> Result<Self> {
|
||||
// TODO if `leaf` is not authoritative over `nameservers.com.`, we would need two "lines" to
|
||||
// root. for example, if `leaf` is authoritative over `example.net.` we would need these two
|
||||
// lines:
|
||||
// - `nameservers.com.`, `com.`, `.` to cover the `primaryNNN.nameservers.com.` domains that
|
||||
// `NameServer` implicitly uses
|
||||
// - `example.net.`, `net.`, `.` to cover the requested `leaf` name server
|
||||
assert_eq!(&FQDN::NAMESERVERS, leaf.zone(), "not yet implemented");
|
||||
|
||||
// first pass: create nameservers for parent zones
|
||||
let mut zone = leaf.zone().clone();
|
||||
let mut nameservers = vec![leaf];
|
||||
while let Some(parent) = zone.parent() {
|
||||
let leaf = &mut nameservers[0];
|
||||
let nameserver = NameServer::new(
|
||||
&leaf.implementation,
|
||||
parent.clone(),
|
||||
leaf.container.network(),
|
||||
)?;
|
||||
|
||||
leaf.add(Record::a(nameserver.fqdn().clone(), nameserver.ipv4_addr()));
|
||||
nameservers.push(nameserver);
|
||||
|
||||
zone = parent;
|
||||
}
|
||||
|
||||
// XXX will not hold when `leaf` is not authoritative over `nameservers.com.`
|
||||
assert_eq!(3, nameservers.len());
|
||||
|
||||
// second pass: add referrals from parent to child
|
||||
// `windows_mut` is not a thing in `core::iter` so use indexing as a workaround
|
||||
for index in 0..nameservers.len() - 1 {
|
||||
let [child, parent] = &mut nameservers[index..][..2] else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
parent.referral(
|
||||
child.zone().clone(),
|
||||
child.fqdn().clone(),
|
||||
child.ipv4_addr(),
|
||||
);
|
||||
}
|
||||
|
||||
let root = nameservers.last().unwrap();
|
||||
let root = Root::new(root.fqdn().clone(), root.ipv4_addr());
|
||||
|
||||
// start name servers
|
||||
let (nameservers, trust_anchor) = match sign {
|
||||
Sign::No => (
|
||||
nameservers
|
||||
.into_iter()
|
||||
.map(|nameserver| nameserver.start())
|
||||
.collect::<Result<_>>()?,
|
||||
None,
|
||||
),
|
||||
|
||||
_ => {
|
||||
let mut trust_anchor = TrustAnchor::empty();
|
||||
let maybe_mutate = match sign {
|
||||
Sign::No => unreachable!(),
|
||||
Sign::Yes => None,
|
||||
Sign::AndAmend(f) => Some(f),
|
||||
};
|
||||
|
||||
let mut running = vec![];
|
||||
let mut child_ds = None;
|
||||
let len = nameservers.len();
|
||||
for (index, mut nameserver) in nameservers.into_iter().enumerate() {
|
||||
if let Some(ds) = child_ds.take() {
|
||||
nameserver.add(ds);
|
||||
}
|
||||
|
||||
let mut nameserver = nameserver.sign()?;
|
||||
child_ds = Some(nameserver.ds().clone());
|
||||
if let Some(mutate) = maybe_mutate {
|
||||
let zone = nameserver.zone().clone();
|
||||
mutate(&zone, &mut nameserver.signed_zone_file_mut().records);
|
||||
}
|
||||
|
||||
if index == len - 1 {
|
||||
// the last nameserver covers `.`
|
||||
trust_anchor.add(nameserver.key_signing_key().clone());
|
||||
trust_anchor.add(nameserver.zone_signing_key().clone());
|
||||
}
|
||||
|
||||
running.push(nameserver.start()?);
|
||||
}
|
||||
|
||||
(running, Some(trust_anchor))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Graph {
|
||||
nameservers,
|
||||
root,
|
||||
trust_anchor,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NameServer<State> {
|
||||
container: Container,
|
||||
|
|
Loading…
Reference in New Issue
Block a user