diff --git a/packages/dns-test/examples/explore.rs b/packages/dns-test/examples/explore.rs index f92dd4fc..6d4c0427 100644 --- a/packages/dns-test/examples/explore.rs +++ b/packages/dns-test/examples/explore.rs @@ -3,9 +3,9 @@ use std::net::Ipv4Addr; use std::sync::mpsc; use dns_test::client::Client; -use dns_test::name_server::NameServer; +use dns_test::name_server::{Graph, NameServer, Sign}; use dns_test::record::RecordType; -use dns_test::{Network, Resolver, Result, TrustAnchor, FQDN}; +use dns_test::{Network, Resolver, Result, FQDN}; fn main() -> Result<()> { let args = Args::from_env()?; @@ -14,49 +14,16 @@ fn main() -> Result<()> { let peer = &dns_test::PEER; println!("building docker image..."); - let mut root_ns = NameServer::new(peer, FQDN::ROOT, &network)?; + let leaf_ns = NameServer::new(peer, FQDN("mydomain.com.")?, &network)?; println!("DONE"); println!("setting up name servers..."); - let mut com_ns = NameServer::new(peer, FQDN::COM, &network)?; - - let mut nameservers_ns = NameServer::new(peer, FQDN("nameservers.com.")?, &network)?; - nameservers_ns.add(root_ns.a()).add(com_ns.a()); - - let nameservers_ns = if args.dnssec { - let nameservers_ns = nameservers_ns.sign()?; - com_ns.add(nameservers_ns.ds().clone()); - nameservers_ns.start()? - } else { - nameservers_ns.start()? - }; - - com_ns.referral_nameserver(&nameservers_ns); - - let com_ns = if args.dnssec { - let com_ns = com_ns.sign()?; - root_ns.add(com_ns.ds().clone()); - com_ns.start()? - } else { - com_ns.start()? - }; - - root_ns.referral_nameserver(&com_ns); - - let mut trust_anchor = TrustAnchor::empty(); - let root_ns = if args.dnssec { - let root_ns = root_ns.sign()?; - let root_ksk = root_ns.key_signing_key(); - let root_zsk = root_ns.zone_signing_key(); - - trust_anchor.add(root_ksk.clone()); - trust_anchor.add(root_zsk.clone()); - - root_ns.start()? - } else { - root_ns.start()? - }; - + let sign = if args.dnssec { Sign::Yes } else { Sign::No }; + let Graph { + root, + trust_anchor, + nameservers, + } = Graph::build(leaf_ns, sign)?; println!("DONE"); let client = Client::new(&network)?; @@ -68,40 +35,29 @@ fn main() -> Result<()> { Ipv4Addr::new(127, 0, 0, 1), RecordType::SOA, &FQDN::ROOT, - &trust_anchor, + trust_anchor.as_ref().unwrap(), )?; } println!("building docker image..."); - let resolver = Resolver::new(&network, root_ns.root_hint()) - .trust_anchor(&trust_anchor) - .start(&dns_test::SUBJECT)?; + let mut builder = Resolver::new(&network, root); + if let Some(trust_anchor) = trust_anchor { + builder.trust_anchor(&trust_anchor); + } + let resolver = builder.start(&dns_test::SUBJECT)?; println!("DONE\n\n"); let (tx, rx) = mpsc::channel(); ctrlc::set_handler(move || tx.send(()).expect("could not forward signal"))?; - println!(". (root) name server's IP address: {}", root_ns.ipv4_addr()); - println!( - "attach to this container with: `docker exec -it {} bash`\n", - root_ns.container_id() - ); - - println!("com. name server's IP address: {}", com_ns.ipv4_addr()); - println!( - "attach to this container with: `docker exec -it {} bash`\n", - com_ns.container_id() - ); - - println!( - "nameservers.com. name server's IP address: {}", - nameservers_ns.ipv4_addr() - ); - println!( - "attach to this container with: `docker exec -it {} bash`\n", - nameservers_ns.container_id() - ); + for ns in &nameservers { + println!("{} name server's IP address: {}", ns.zone(), ns.ipv4_addr()); + println!( + "attach to this container with: `docker exec -it {} bash`\n", + ns.container_id() + ); + } let resolver_addr = resolver.ipv4_addr(); println!("resolver's IP address: {resolver_addr}",); diff --git a/packages/dns-test/src/name_server.rs b/packages/dns-test/src/name_server.rs index 9ac28dc5..4db1c0c3 100644 --- a/packages/dns-test/src/name_server.rs +++ b/packages/dns-test/src/name_server.rs @@ -19,7 +19,6 @@ 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)), } @@ -36,46 +35,54 @@ impl Graph { /// /// a non-empty `TrustAnchor` is returned only when `Sign::Yes` or `Sign::AndAmend` is used pub fn build(leaf: NameServer, sign: Sign) -> Result { - // 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"); + assert_eq!(2, leaf.zone().num_labels(), "not yet implemented"); + assert_eq!(Some(FQDN::COM), leaf.zone().parent(), "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(), - )?; + let network = leaf.container.network().clone(); + let implementation = leaf.implementation.clone(); - leaf.add(nameserver.a()); + let (mut nameservers_ns, leaf) = if leaf.zone() != &FQDN::NAMESERVERS { + let nameservers_ns = NameServer::new(&implementation, FQDN::NAMESERVERS, &network)?; + (nameservers_ns, Some(leaf)) + } else { + (leaf, None) + }; + + // the nameserver covering `FQDN::NAMESERVERS` needs A records about all the nameservers in the graph + let mut nameservers = vec![]; + while let Some(parent) = zone.parent() { + let nameserver = NameServer::new(&implementation, parent.clone(), &network)?; + + nameservers_ns.add(nameserver.a()); nameservers.push(nameserver); zone = parent; } + drop((network, implementation)); - // XXX will not hold when `leaf` is not authoritative over `nameservers.com.` - assert_eq!(3, nameservers.len()); + if let Some(leaf) = leaf { + nameservers.insert(0, leaf); + } + nameservers.insert(0, nameservers_ns); // 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_nameserver(child); + // the nameservers are sorted leaf-most zone first but siblings may be next to each other + // for each child (e.g. `nameservers.com.`), do a linear search for its parent (`com.`) + for index in 1..nameservers.len() { + let (left, right) = nameservers.split_at_mut(index); + let child = left.last_mut().unwrap(); + for maybe_parent in right { + if Some(maybe_parent.zone()) == child.zone().parent().as_ref() { + let parent = maybe_parent; + parent.referral_nameserver(child); + break; + } + } } - let root = nameservers.last().unwrap(); - let root = Root::new(root.fqdn().clone(), root.ipv4_addr()); + let root = nameservers.last().unwrap().root_hint(); // start name servers let (nameservers, trust_anchor) = match sign { @@ -96,15 +103,22 @@ impl Graph { }; let mut running = vec![]; - let mut child_ds = None; + let mut children_ds = vec![]; + let mut children_num_labels = 0; let len = nameservers.len(); for (index, mut nameserver) in nameservers.into_iter().enumerate() { - if let Some(ds) = child_ds.take() { - nameserver.add(ds); + if !children_ds.is_empty() { + let is_parent = nameserver.zone().num_labels() + 1 == children_num_labels; + if is_parent { + for ds in children_ds.drain(..) { + nameserver.add(ds); + } + } } let mut nameserver = nameserver.sign()?; - child_ds = Some(nameserver.ds().clone()); + children_ds.push(nameserver.ds().clone()); + children_num_labels = nameserver.zone().num_labels(); if let Some(mutate) = maybe_mutate { let zone = nameserver.zone().clone(); mutate(&zone, &mut nameserver.signed_zone_file_mut().records); @@ -153,7 +167,7 @@ impl NameServer { /// the zone pub fn new(implementation: &Implementation, zone: FQDN, network: &Network) -> Result { let ns_count = ns_count(); - let nameserver = primary_ns(ns_count); + let nameserver = primary_ns(ns_count, &zone); let image = implementation.clone().into(); let container = Container::run(&image, network)?; @@ -161,7 +175,7 @@ impl NameServer { zone: zone.clone(), ttl: DEFAULT_TTL, nameserver: nameserver.clone(), - admin: admin_ns(ns_count), + admin: admin_ns(ns_count, &zone), settings: SoaSettings::default(), }; let mut zone_file = ZoneFile::new(soa); @@ -439,12 +453,22 @@ pub struct Running { child: Child, } -fn primary_ns(ns_count: usize) -> FQDN { - FQDN(format!("primary{ns_count}.nameservers.com.")).unwrap() +fn primary_ns(ns_count: usize, zone: &FQDN) -> FQDN { + FQDN(format!("primary{ns_count}.{}", expand_zone(zone))).unwrap() } -fn admin_ns(ns_count: usize) -> FQDN { - FQDN(format!("admin{ns_count}.nameservers.com.")).unwrap() +fn admin_ns(ns_count: usize, zone: &FQDN) -> FQDN { + FQDN(format!("admin{ns_count}.{}", expand_zone(zone))).unwrap() +} + +fn expand_zone(zone: &FQDN) -> String { + if zone == &FQDN::ROOT { + "nameservers.com.".to_string() + } else if zone.num_labels() == 1 { + format!("nameservers.{}", zone.as_str()) + } else { + zone.to_string() + } } #[cfg(test)]