From 6c57cbb95ecec206998b94930f6b04bf8bfeb2c2 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 16 Oct 2024 11:39:29 +0200 Subject: [PATCH 01/19] start work on notify --- Cargo.toml | 3 +- src/commands/mod.rs | 6 ++ src/commands/notify.rs | 192 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 src/commands/notify.rs diff --git a/Cargo.toml b/Cargo.toml index c9d0445..f760e11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,9 @@ edition = "2021" [dependencies] clap = { version = "4", features = ["derive"] } -domain = "0.10.1" +domain = { version = "0.10.1", git = "https://github.com/NLnetLabs/domain.git", features = ["tsig", "zonefile", "bytes", "validate", "resolv"] } # for implementation of nsec3 hash until domain has it stabilized octseq = { version = "0.5.1", features = ["std"] } ring = { version = "0.17" } +tokio = "1.40.0" diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 2445c86..6b84959 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,6 +2,7 @@ pub mod help; pub mod nsec3hash; +pub mod notify; use super::error::Error; @@ -11,6 +12,10 @@ pub enum Command { #[command(name = "nsec3-hash")] Nsec3Hash(self::nsec3hash::Nsec3Hash), + /// Sends a NOTIFY message to DNS servers + #[command(name = "notify")] + Notify(self::notify::Notify), + /// Show the manual pages Help(self::help::Help), } @@ -19,6 +24,7 @@ impl Command { pub fn execute(self) -> Result<(), Error> { match self { Self::Nsec3Hash(nsec3hash) => nsec3hash.execute(), + Self::Notify(notify) => notify.execute(), Self::Help(help) => help.execute(), } } diff --git a/src/commands/notify.rs b/src/commands/notify.rs new file mode 100644 index 0000000..ffe538d --- /dev/null +++ b/src/commands/notify.rs @@ -0,0 +1,192 @@ +use std::{net::SocketAddr, str::FromStr}; + +use clap::builder::ValueParser; +use domain::{ + base::{ + iana::{Class, Opcode}, + MessageBuilder, Name, Question, Record, Rtype, Serial, Ttl, + }, + dep::octseq::Array, + rdata::{tsig::Time48, Soa}, + tsig::{Algorithm, ClientTransaction, Key, KeyName}, + utils::{base16, base64}, + resolv::stub::StubResolver, +}; + +use crate::error::Error; + +#[derive(Clone, Debug)] +struct TSigInfo { + name: KeyName, + key: Vec, + algorithm: Algorithm, +} + +impl FromStr for TSigInfo { + type Err = Error; + + fn from_str(s: &str) -> Result { + // TODO: better error messages + let Some((mut name, rest)) = s.split_once(':') else { + return Err("invalid tsig string".into()); + }; + + let mut key; + let mut algorithm; + if let Some((k, a)) = s.split_once(':') { + key = k; + algorithm = a; + } else { + key = rest; + // This is different from ldns's default of + // hmac-md5.sig-alg.reg.int but we don't support that algorithm. + algorithm = "hmac-sha512"; + } + + // With dig TSIG keys are also specified with -y, + // but out format is: -y + // and dig's is: -y [hmac:]name:key + // + // When we detect an unknown tsig algorithm in algo, + // but a known algorithm in name, we cane assume dig + // order was used. + // + // We can correct this by checking whether the name contains a valid + // algorithm while the name doesn't. + if Algorithm::from_str(algorithm).is_err() && Algorithm::from_str(name).is_ok() { + (name, key, algorithm) = (key, algorithm, name); + } + + let algorithm = Algorithm::from_str(algorithm) + .map_err(|_| format!("Unsupported TSIG algorithm: {algorithm}"))?; + + let key = base64::decode(key).map_err(|e| format!("TSIG key is invalid base64: {e}"))?; + + let name = + Name::>::from_str(name).map_err(|e| format!("TSIG name is invalid: {e}"))?; + + Ok(TSigInfo { + name, + key, + algorithm, + }) + } +} + +#[derive(Clone, Debug, clap::Args)] +pub struct Notify { + #[arg(short = 'z', required = true)] + zone: Name>, + + #[arg(short = 'I')] + source_address: (), + + #[arg(short = 's')] + soa_version: Option, + + #[arg(short = 'y', long = "tsig", value_parser = ValueParser::new(TSigInfo::from_str))] + tsig: Option, + + #[arg(short = 'p', long = "port")] + port: u16, + + #[arg(short = 'd', long = "debug")] + debug: bool, + + #[arg(short = 'r', long = "retries")] + retries: usize, + + #[arg()] + servers: Vec, +} + +impl Notify { + pub fn execute(&self) -> Result<(), Error> { + let runtime = tokio::runtime::Runtime::new().unwrap(); + runtime.block_on(self.run()) + } + + async fn run(&self) -> Result<(), Error> { + let mut msg = MessageBuilder::new_vec(); + + let header = msg.header_mut(); + header.set_opcode(Opcode::NOTIFY); + header.set_aa(true); + header.set_random_id(); + + let mut msg = msg.question(); + let question = Question::new(&self.zone, Rtype::SOA, Class::IN); + msg.push(question) + .map_err(|e| format!("Could not create question section: {e}"))?; + + let mut msg = msg.answer(); + if let Some(soa_version) = self.soa_version { + let soa = Record::new( + &self.zone, + Class::IN, + Ttl::from_secs(3600), + Soa::new( + Name::root_vec(), + Name::root_vec(), + Serial(soa_version), + Ttl::ZERO, + Ttl::ZERO, + Ttl::ZERO, + Ttl::ZERO, + ), + ); + msg.push(soa) + .map_err(|e| format!("Could not add SOA record: {e}"))?; + } + + let mut msg = msg.additional(); + + if let Some(tsig) = &self.tsig { + let key = Key::new(tsig.algorithm, &tsig.key, tsig.name.clone(), None, None) + .map_err(|e| format!("TSIG key is invalid: {e}"))?; + + // ldns does not seem to validate anything coming in + let _transaction = ClientTransaction::request(key, &mut msg, Time48::now()); + } + + let msg = msg.finish(); + + if self.debug { + println!("# Sending packet:\n"); + todo!() + } + + if self.debug { + println!("Hexdump of notify packet:\n"); + println!("{}", base16::encode_display(&msg)); + } + + let resolver = StubResolver::new(); + + for server in &self.servers { + if self.debug { + println!("# sending to {}", server); + } + + let Ok(name) = Name::>::from_str(server) else { + eprintln!("Invalid domain name \"{server}\", skipping."); + continue; + }; + + let Ok(hosts) = resolver.lookup_host(name).await else { + eprintln!("blabla"); + continue; + }; + + for socket in hosts.port_iter(self.port) { + self.notify_host(socket, &msg, server).await?; + } + } + + Ok(()) + } + + async fn notify_host(&self, socket: SocketAddr, msg: &[u8], server: &str) -> Result<(), Error> { + todo!() + } +} From 133ac3c8c8c5ea44c49025d8f7a7d900c185b169 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 16 Oct 2024 17:35:12 +0200 Subject: [PATCH 02/19] continue on notify --- Cargo.lock | 1049 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/commands/notify.rs | 53 +- 3 files changed, 1091 insertions(+), 13 deletions(-) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7aaef69 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1049 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "cc" +version = "1.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dnst" +version = "0.1.0" +dependencies = [ + "clap", + "domain", + "octseq", + "ring", + "tokio", +] + +[[package]] +name = "domain" +version = "0.10.3" +source = "git+https://github.com/NLnetLabs/domain.git#4663a8cfaf2ed4249c5b10df351b2751af3306c0" +dependencies = [ + "bytes", + "futures-util", + "moka", + "octseq", + "rand", + "ring", + "serde", + "smallvec", + "time", + "tokio", + "tracing", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "moka" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +dependencies = [ + "async-lock", + "async-trait", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener", + "futures-util", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "octseq" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126c3ca37c9c44cec575247f43a3e4374d8927684f129d2beeb0d2cef262fe12" +dependencies = [ + "bytes", + "serde", + "smallvec", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-cpuid" +version = "11.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index f760e11..7f98b93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] clap = { version = "4", features = ["derive"] } -domain = { version = "0.10.1", git = "https://github.com/NLnetLabs/domain.git", features = ["tsig", "zonefile", "bytes", "validate", "resolv"] } +domain = { version = "0.10.3", git = "https://github.com/NLnetLabs/domain.git", features = ["tsig", "zonefile", "bytes", "validate", "resolv", "net", "unstable-client-transport"] } # for implementation of nsec3 hash until domain has it stabilized octseq = { version = "0.5.1", features = ["std"] } diff --git a/src/commands/notify.rs b/src/commands/notify.rs index ffe538d..6e6fa27 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -1,16 +1,21 @@ use std::{net::SocketAddr, str::FromStr}; -use clap::builder::ValueParser; +use clap::{builder::ValueParser, ArgAction}; use domain::{ base::{ iana::{Class, Opcode}, - MessageBuilder, Name, Question, Record, Rtype, Serial, Ttl, + Message, MessageBuilder, Name, Question, Record, Rtype, Serial, Ttl, }, dep::octseq::Array, + net::client::{ + dgram, + protocol::UdpConnect, + request::{RequestMessage, SendRequest}, + }, rdata::{tsig::Time48, Soa}, + resolv::stub::StubResolver, tsig::{Algorithm, ClientTransaction, Key, KeyName}, utils::{base16, base64}, - resolv::stub::StubResolver, }; use crate::error::Error; @@ -75,27 +80,38 @@ impl FromStr for TSigInfo { #[derive(Clone, Debug, clap::Args)] pub struct Notify { + /// The zone #[arg(short = 'z', required = true)] zone: Name>, - #[arg(short = 'I')] + /// Source address to query from + #[arg(short = 'I', required = false)] source_address: (), + /// SOA version number to include #[arg(short = 's')] soa_version: Option, + /// A base64 tsig key and optional algorithm to include #[arg(short = 'y', long = "tsig", value_parser = ValueParser::new(TSigInfo::from_str))] tsig: Option, - #[arg(short = 'p', long = "port")] + /// Port to use to send the packet + #[arg(short = 'p', long = "port", default_value = "53")] port: u16, + /// Print debug information #[arg(short = 'd', long = "debug")] debug: bool, - #[arg(short = 'r', long = "retries")] + /// Max number of retries + #[arg(short = 'r', long = "retries", default_value = "15")] retries: usize, + // Hidden extra argument for `-?` to trigger help, which ldns supports. + #[arg(short = '?', action = ArgAction::Help, hide = true)] + compatible_help: (), + #[arg()] servers: Vec, } @@ -153,14 +169,14 @@ impl Notify { if self.debug { println!("# Sending packet:\n"); - todo!() + // todo!() } if self.debug { println!("Hexdump of notify packet:\n"); println!("{}", base16::encode_display(&msg)); } - + let resolver = StubResolver::new(); for server in &self.servers { @@ -172,9 +188,9 @@ impl Notify { eprintln!("Invalid domain name \"{server}\", skipping."); continue; }; - - let Ok(hosts) = resolver.lookup_host(name).await else { - eprintln!("blabla"); + + let Ok(hosts) = resolver.lookup_host(&name).await else { + eprintln!("Could not resolve host \"{name}\", skipping."); continue; }; @@ -187,6 +203,19 @@ impl Notify { } async fn notify_host(&self, socket: SocketAddr, msg: &[u8], server: &str) -> Result<(), Error> { - todo!() + let mut config = dgram::Config::new(); + config.set_max_retries(self.retries as u8); + let connection = dgram::Connection::with_config(UdpConnect::new(socket), config); + + let msg = msg.to_vec(); + let req = RequestMessage::new(Message::from_octets(msg).unwrap()).unwrap(); + let mut req = SendRequest::send_request(&connection, req); + + let resp = req.get_response().await.unwrap(); + + println!("# reply from {server}:"); + println!("{resp:?}"); + + Ok(()) } } From b5979bd45ef0c7e5052fb63e98aa3c9527904574 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 24 Oct 2024 13:25:14 +0200 Subject: [PATCH 03/19] various fixes to notify --- Cargo.toml | 2 + src/commands/mod.rs | 8 ++- src/commands/notify.rs | 136 ++++++++++++++++++++++++++++++----------- 3 files changed, 109 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7f98b93..a2c2b2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] +bytes = "1.8.0" +chrono = "0.4.38" clap = { version = "4", features = ["derive"] } domain = { version = "0.10.3", git = "https://github.com/NLnetLabs/domain.git", features = ["tsig", "zonefile", "bytes", "validate", "resolv", "net", "unstable-client-transport"] } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6b84959..b03d513 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,8 +1,8 @@ //! The command of _dnst_. pub mod help; -pub mod nsec3hash; pub mod notify; +pub mod nsec3hash; use super::error::Error; @@ -12,7 +12,11 @@ pub enum Command { #[command(name = "nsec3-hash")] Nsec3Hash(self::nsec3hash::Nsec3Hash), - /// Sends a NOTIFY message to DNS servers + /// Send a NOTIFY packet to DNS servers + /// + /// This tells them that an updated zone is available at the master servers. It can perform TSIG + /// signatures and it can add a SOA serial number of the updated zone. If a server already has + /// that serial number it will disregard the message. #[command(name = "notify")] Notify(self::notify::Notify), diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 6e6fa27..32ac972 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -1,5 +1,8 @@ -use std::{net::SocketAddr, str::FromStr}; +use core::fmt; +use std::{net::SocketAddr, str::FromStr, time::{Duration, Instant}}; +use bytes::Bytes; +use chrono::{DateTime, Local, TimeDelta}; use clap::{builder::ValueParser, ArgAction}; use domain::{ base::{ @@ -11,10 +14,11 @@ use domain::{ dgram, protocol::UdpConnect, request::{RequestMessage, SendRequest}, + tsig, }, - rdata::{tsig::Time48, Soa}, + rdata::Soa, resolv::stub::StubResolver, - tsig::{Algorithm, ClientTransaction, Key, KeyName}, + tsig::{Algorithm, Key, KeyName}, utils::{base16, base64}, }; @@ -27,6 +31,28 @@ struct TSigInfo { algorithm: Algorithm, } +// Clippy complains about the unread fields but they are used for display +#[allow(dead_code)] +struct Response { + msg: Message, + when: DateTime, + server: Option, + time: TimeDelta, +} + +impl> fmt::Display for Response { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.msg.display_dig_style())?; + writeln!(f, ";; Query time: {} msec", self.time.num_milliseconds())?; + if let Some(server) = self.server { + writeln!(f, ";; Server: {}#{}", server.ip(), server.port())?; + } + writeln!(f, ";; WHEN: {}", self.when.format("%a %b %d %H:%M:%S %Z %Y"))?; + writeln!(f, ";; MSG SIZE rcvd: {}", self.msg.as_slice().len())?; + Ok(()) + } +} + impl FromStr for TSigInfo { type Err = Error; @@ -112,6 +138,7 @@ pub struct Notify { #[arg(short = '?', action = ArgAction::Help, hide = true)] compatible_help: (), + /// DNS servers to send packet to #[arg()] servers: Vec, } @@ -133,7 +160,7 @@ impl Notify { let mut msg = msg.question(); let question = Question::new(&self.zone, Rtype::SOA, Class::IN); msg.push(question) - .map_err(|e| format!("Could not create question section: {e}"))?; + .map_err(|e| format!("could not create question section: {e}"))?; let mut msg = msg.answer(); if let Some(soa_version) = self.soa_version { @@ -152,70 +179,109 @@ impl Notify { ), ); msg.push(soa) - .map_err(|e| format!("Could not add SOA record: {e}"))?; + .map_err(|e| format!("could not add SOA record: {e}"))?; } - let mut msg = msg.additional(); - - if let Some(tsig) = &self.tsig { - let key = Key::new(tsig.algorithm, &tsig.key, tsig.name.clone(), None, None) - .map_err(|e| format!("TSIG key is invalid: {e}"))?; + let msg = msg.additional(); - // ldns does not seem to validate anything coming in - let _transaction = ClientTransaction::request(key, &mut msg, Time48::now()); - } + let tsig = self + .tsig + .as_ref() + .map(|tsig| { + Key::new(tsig.algorithm, &tsig.key, tsig.name.clone(), None, None) + .map_err(|e| format!("TSIG key is invalid: {e}")) + }) + .transpose()?; - let msg = msg.finish(); + let msg = msg.into_message(); - if self.debug { - println!("# Sending packet:\n"); - // todo!() - } + println!("# Sending packet:"); + println!("{}", msg.display_dig_style()); if self.debug { - println!("Hexdump of notify packet:\n"); + println!("Hexdump of notify packet:"); println!("{}", base16::encode_display(&msg)); } let resolver = StubResolver::new(); for server in &self.servers { - if self.debug { - println!("# sending to {}", server); - } + println!("# sending to {}", server); let Ok(name) = Name::>::from_str(server) else { - eprintln!("Invalid domain name \"{server}\", skipping."); + eprintln!("warning: invalid domain name \"{server}\", skipping."); continue; }; let Ok(hosts) = resolver.lookup_host(&name).await else { - eprintln!("Could not resolve host \"{name}\", skipping."); + eprintln!("warning: could not resolve host \"{name}\", skipping."); continue; }; + if hosts.is_empty() { + eprintln!("skipping bad address: {name}: Name or service not known"); + continue; + } + for socket in hosts.port_iter(self.port) { - self.notify_host(socket, &msg, server).await?; + let resp = match &tsig { + Some(tsig) => { + self.notify_host_with_tsig(socket, msg.clone(), tsig.clone()) + .await + } + None => self.notify_host_without_tsig(socket, msg.clone()).await, + }; + + match resp { + Ok(resp) => { + println!("# reply from {server} at {socket}:"); + println!("{resp}"); + }, + Err(e) => { + eprintln!("{e}"); + } + } } } Ok(()) } - async fn notify_host(&self, socket: SocketAddr, msg: &[u8], server: &str) -> Result<(), Error> { + async fn notify_host_with_tsig( + &self, + socket: SocketAddr, + msg: Message>, + key: Key, + ) -> Result, Error> { let mut config = dgram::Config::new(); config.set_max_retries(self.retries as u8); let connection = dgram::Connection::with_config(UdpConnect::new(socket), config); + let connection = tsig::Connection::new(key, connection); + + let req = RequestMessage::new(msg).unwrap(); + let mut req = connection.send_request(req); + + let when = Local::now(); + let msg = req.get_response().await.map_err(|e| format!("warning: could not send message to {socket}: {e}"))?; + let time2 = Local::now(); + Ok(Response { msg, when, server: Some(socket), time: time2 - when }) + } - let msg = msg.to_vec(); - let req = RequestMessage::new(Message::from_octets(msg).unwrap()).unwrap(); - let mut req = SendRequest::send_request(&connection, req); - - let resp = req.get_response().await.unwrap(); - - println!("# reply from {server}:"); - println!("{resp:?}"); + async fn notify_host_without_tsig( + &self, + socket: SocketAddr, + msg: Message>, + ) -> Result, Error> { + let mut config = dgram::Config::new(); + config.set_max_retries(self.retries as u8); + let connection = dgram::Connection::with_config(UdpConnect::new(socket), config); - Ok(()) + let req = RequestMessage::new(msg).unwrap(); + let mut req = SendRequest::send_request(&connection, req); + + let when = Local::now(); + let msg = req.get_response().await.map_err(|e| format!("warning: could not send message to {socket}: {e}"))?; + let time2 = Local::now(); + Ok(Response { msg, when, server: Some(socket), time: time2 - when }) } } From 5cc4f19d12fe758f1eaf0a04fd13067d1c29b5c2 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 30 Oct 2024 12:18:58 +0100 Subject: [PATCH 04/19] notify: incorporate feedback from @mozzieongit --- src/commands/notify.rs | 109 +++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 36 deletions(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 32ac972..02bb4c5 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -1,9 +1,9 @@ use core::fmt; -use std::{net::SocketAddr, str::FromStr, time::{Duration, Instant}}; +use std::{net::SocketAddr, str::FromStr}; use bytes::Bytes; use chrono::{DateTime, Local, TimeDelta}; -use clap::{builder::ValueParser, ArgAction}; +use clap::builder::ValueParser; use domain::{ base::{ iana::{Class, Opcode}, @@ -47,7 +47,11 @@ impl> fmt::Display for Response { if let Some(server) = self.server { writeln!(f, ";; Server: {}#{}", server.ip(), server.port())?; } - writeln!(f, ";; WHEN: {}", self.when.format("%a %b %d %H:%M:%S %Z %Y"))?; + writeln!( + f, + ";; WHEN: {}", + self.when.format("%a %b %d %H:%M:%S %Z %Y") + )?; writeln!(f, ";; MSG SIZE rcvd: {}", self.msg.as_slice().len())?; Ok(()) } @@ -64,7 +68,7 @@ impl FromStr for TSigInfo { let mut key; let mut algorithm; - if let Some((k, a)) = s.split_once(':') { + if let Some((k, a)) = rest.split_once(':') { key = k; algorithm = a; } else { @@ -75,11 +79,11 @@ impl FromStr for TSigInfo { } // With dig TSIG keys are also specified with -y, - // but out format is: -y - // and dig's is: -y [hmac:]name:key + // but our format is: + // and dig's is: [hmac:]name:key // // When we detect an unknown tsig algorithm in algo, - // but a known algorithm in name, we cane assume dig + // but a known algorithm in name, we can assume dig // order was used. // // We can correct this by checking whether the name contains a valid @@ -119,7 +123,12 @@ pub struct Notify { soa_version: Option, /// A base64 tsig key and optional algorithm to include - #[arg(short = 'y', long = "tsig", value_parser = ValueParser::new(TSigInfo::from_str))] + #[arg( + short = 'y', + long = "tsig", + value_parser = ValueParser::new(TSigInfo::from_str), + value_name = "name:key[:algo]", + )] tsig: Option, /// Port to use to send the packet @@ -134,12 +143,8 @@ pub struct Notify { #[arg(short = 'r', long = "retries", default_value = "15")] retries: usize, - // Hidden extra argument for `-?` to trigger help, which ldns supports. - #[arg(short = '?', action = ArgAction::Help, hide = true)] - compatible_help: (), - /// DNS servers to send packet to - #[arg()] + #[arg(required = true)] servers: Vec, } @@ -208,6 +213,16 @@ impl Notify { for server in &self.servers { println!("# sending to {}", server); + // The specified server might be an IP address. In ldns, this case is + // handled by `getaddrinfo`, but we have to do it ourselves. + // We parse it as an IP address and then send it to the one socket we + // can. + if let Ok(addr) = server.parse() { + let socket = SocketAddr::new(addr, self.port); + self.notify_host(socket, msg.clone(), server, &tsig).await; + continue; + } + let Ok(name) = Name::>::from_str(server) else { eprintln!("warning: invalid domain name \"{server}\", skipping."); continue; @@ -224,29 +239,36 @@ impl Notify { } for socket in hosts.port_iter(self.port) { - let resp = match &tsig { - Some(tsig) => { - self.notify_host_with_tsig(socket, msg.clone(), tsig.clone()) - .await - } - None => self.notify_host_without_tsig(socket, msg.clone()).await, - }; - - match resp { - Ok(resp) => { - println!("# reply from {server} at {socket}:"); - println!("{resp}"); - }, - Err(e) => { - eprintln!("{e}"); - } - } + self.notify_host(socket, msg.clone(), server, &tsig).await; } } Ok(()) } + async fn notify_host( + &self, + socket: SocketAddr, + msg: Message>, + server: &str, + tsig: &Option, + ) { + let resp = match &tsig { + Some(tsig) => self.notify_host_with_tsig(socket, msg, tsig.clone()).await, + None => self.notify_host_without_tsig(socket, msg).await, + }; + + match resp { + Ok(resp) => { + println!("# reply from {server} at {socket}:"); + println!("{resp}"); + } + Err(e) => { + eprintln!("{e}"); + } + } + } + async fn notify_host_with_tsig( &self, socket: SocketAddr, @@ -260,11 +282,19 @@ impl Notify { let req = RequestMessage::new(msg).unwrap(); let mut req = connection.send_request(req); - + let when = Local::now(); - let msg = req.get_response().await.map_err(|e| format!("warning: could not send message to {socket}: {e}"))?; + let msg = req + .get_response() + .await + .map_err(|e| format!("warning: could not send message to {socket}: {e}"))?; let time2 = Local::now(); - Ok(Response { msg, when, server: Some(socket), time: time2 - when }) + Ok(Response { + msg, + when, + server: Some(socket), + time: time2 - when, + }) } async fn notify_host_without_tsig( @@ -278,10 +308,17 @@ impl Notify { let req = RequestMessage::new(msg).unwrap(); let mut req = SendRequest::send_request(&connection, req); - + let when = Local::now(); - let msg = req.get_response().await.map_err(|e| format!("warning: could not send message to {socket}: {e}"))?; + let msg = req.get_response().await.map_err(|e| { + format!("warning: reply was not received or erroneous from {socket}: {e}") + })?; let time2 = Local::now(); - Ok(Response { msg, when, server: Some(socket), time: time2 - when }) + Ok(Response { + msg, + when, + server: Some(socket), + time: time2 - when, + }) } } From f30fd58cafc46c6697f9d2110e88210ea751c476 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 30 Oct 2024 12:21:16 +0100 Subject: [PATCH 05/19] notify: update outdated terminology --- src/commands/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b03d513..925db87 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -14,7 +14,7 @@ pub enum Command { /// Send a NOTIFY packet to DNS servers /// - /// This tells them that an updated zone is available at the master servers. It can perform TSIG + /// This tells them that an updated zone is available at the primaries. It can perform TSIG /// signatures and it can add a SOA serial number of the updated zone. If a server already has /// that serial number it will disregard the message. #[command(name = "notify")] From 5d1fe6b965eb794c838d10eb8093126f0f0ce446 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 30 Oct 2024 12:22:19 +0100 Subject: [PATCH 06/19] notify: fix up comment --- src/commands/notify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 02bb4c5..0793e6a 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -87,7 +87,7 @@ impl FromStr for TSigInfo { // order was used. // // We can correct this by checking whether the name contains a valid - // algorithm while the name doesn't. + // algorithm while the algorithm doesn't. if Algorithm::from_str(algorithm).is_err() && Algorithm::from_str(name).is_ok() { (name, key, algorithm) = (key, algorithm, name); } From fc7cfe8804ed8ae825d1d2b5e46caff80078b4a2 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 30 Oct 2024 14:26:49 +0100 Subject: [PATCH 07/19] update: fix up error message Co-authored-by: Jannik --- src/commands/notify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 0793e6a..1a64d58 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -287,7 +287,7 @@ impl Notify { let msg = req .get_response() .await - .map_err(|e| format!("warning: could not send message to {socket}: {e}"))?; + .map_err(|e| format!("warning: reply was not received or erroneous from {socket}: {e}"))?; let time2 = Local::now(); Ok(Response { msg, From c256bb16fab798dc4f76a143204af9f5c0454636 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 30 Oct 2024 14:31:15 +0100 Subject: [PATCH 08/19] notify: temporarily remove -I because it's not supported --- src/commands/notify.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 1a64d58..54eb8e8 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -114,10 +114,12 @@ pub struct Notify { #[arg(short = 'z', required = true)] zone: Name>, - /// Source address to query from - #[arg(short = 'I', required = false)] - source_address: (), - + // The -I option is supported by ldns but is not available in domain yet. + // It requires creating a connection from a UdpSocket (or similar). + // /// Source address to query from + // #[arg(short = 'I', required = false)] + // source_address: (), + // /// SOA version number to include #[arg(short = 's')] soa_version: Option, @@ -284,10 +286,9 @@ impl Notify { let mut req = connection.send_request(req); let when = Local::now(); - let msg = req - .get_response() - .await - .map_err(|e| format!("warning: reply was not received or erroneous from {socket}: {e}"))?; + let msg = req.get_response().await.map_err(|e| { + format!("warning: reply was not received or erroneous from {socket}: {e}") + })?; let time2 = Local::now(); Ok(Response { msg, From 21023bd350c5a95b9717c05c47a154914bb68aa0 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 31 Oct 2024 11:05:12 +0100 Subject: [PATCH 09/19] notify: imports compliant with domain's CONTRIBUTING.md --- src/commands/notify.rs | 75 +++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 54eb8e8..f5b742d 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -4,23 +4,18 @@ use std::{net::SocketAddr, str::FromStr}; use bytes::Bytes; use chrono::{DateTime, Local, TimeDelta}; use clap::builder::ValueParser; -use domain::{ - base::{ - iana::{Class, Opcode}, - Message, MessageBuilder, Name, Question, Record, Rtype, Serial, Ttl, - }, - dep::octseq::Array, - net::client::{ - dgram, - protocol::UdpConnect, - request::{RequestMessage, SendRequest}, - tsig, - }, - rdata::Soa, - resolv::stub::StubResolver, - tsig::{Algorithm, Key, KeyName}, - utils::{base16, base64}, +use domain::base::iana::{Class, Opcode}; +use domain::base::{ + Message, MessageBuilder, Name, Question, Record, Rtype, Serial, Ttl, }; +use domain::dep::octseq::Array; +use domain::net::client::protocol::UdpConnect; +use domain::net::client::request::{RequestMessage, SendRequest}; +use domain::net::client::{dgram, tsig}; +use domain::rdata::Soa; +use domain::resolv::stub::StubResolver; +use domain::tsig::{Algorithm, Key, KeyName}; +use domain::utils::{base16, base64}; use crate::error::Error; @@ -88,17 +83,21 @@ impl FromStr for TSigInfo { // // We can correct this by checking whether the name contains a valid // algorithm while the algorithm doesn't. - if Algorithm::from_str(algorithm).is_err() && Algorithm::from_str(name).is_ok() { + if Algorithm::from_str(algorithm).is_err() + && Algorithm::from_str(name).is_ok() + { (name, key, algorithm) = (key, algorithm, name); } - let algorithm = Algorithm::from_str(algorithm) - .map_err(|_| format!("Unsupported TSIG algorithm: {algorithm}"))?; + let algorithm = Algorithm::from_str(algorithm).map_err(|_| { + format!("Unsupported TSIG algorithm: {algorithm}") + })?; - let key = base64::decode(key).map_err(|e| format!("TSIG key is invalid base64: {e}"))?; + let key = base64::decode(key) + .map_err(|e| format!("TSIG key is invalid base64: {e}"))?; - let name = - Name::>::from_str(name).map_err(|e| format!("TSIG name is invalid: {e}"))?; + let name = Name::>::from_str(name) + .map_err(|e| format!("TSIG name is invalid: {e}"))?; Ok(TSigInfo { name, @@ -195,8 +194,14 @@ impl Notify { .tsig .as_ref() .map(|tsig| { - Key::new(tsig.algorithm, &tsig.key, tsig.name.clone(), None, None) - .map_err(|e| format!("TSIG key is invalid: {e}")) + Key::new( + tsig.algorithm, + &tsig.key, + tsig.name.clone(), + None, + None, + ) + .map_err(|e| format!("TSIG key is invalid: {e}")) }) .transpose()?; @@ -226,17 +231,23 @@ impl Notify { } let Ok(name) = Name::>::from_str(server) else { - eprintln!("warning: invalid domain name \"{server}\", skipping."); + eprintln!( + "warning: invalid domain name \"{server}\", skipping." + ); continue; }; let Ok(hosts) = resolver.lookup_host(&name).await else { - eprintln!("warning: could not resolve host \"{name}\", skipping."); + eprintln!( + "warning: could not resolve host \"{name}\", skipping." + ); continue; }; if hosts.is_empty() { - eprintln!("skipping bad address: {name}: Name or service not known"); + eprintln!( + "skipping bad address: {name}: Name or service not known" + ); continue; } @@ -256,7 +267,9 @@ impl Notify { tsig: &Option, ) { let resp = match &tsig { - Some(tsig) => self.notify_host_with_tsig(socket, msg, tsig.clone()).await, + Some(tsig) => { + self.notify_host_with_tsig(socket, msg, tsig.clone()).await + } None => self.notify_host_without_tsig(socket, msg).await, }; @@ -279,7 +292,8 @@ impl Notify { ) -> Result, Error> { let mut config = dgram::Config::new(); config.set_max_retries(self.retries as u8); - let connection = dgram::Connection::with_config(UdpConnect::new(socket), config); + let connection = + dgram::Connection::with_config(UdpConnect::new(socket), config); let connection = tsig::Connection::new(key, connection); let req = RequestMessage::new(msg).unwrap(); @@ -305,7 +319,8 @@ impl Notify { ) -> Result, Error> { let mut config = dgram::Config::new(); config.set_max_retries(self.retries as u8); - let connection = dgram::Connection::with_config(UdpConnect::new(socket), config); + let connection = + dgram::Connection::with_config(UdpConnect::new(socket), config); let req = RequestMessage::new(msg).unwrap(); let mut req = SendRequest::send_request(&connection, req); From 7a41ff50eb803a3407d5ff0ae8203d43d45f0299 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 4 Nov 2024 14:09:56 +0100 Subject: [PATCH 10/19] notify: improve error message for TSIG parsing --- src/commands/notify.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index f5b742d..560403a 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -56,9 +56,8 @@ impl FromStr for TSigInfo { type Err = Error; fn from_str(s: &str) -> Result { - // TODO: better error messages let Some((mut name, rest)) = s.split_once(':') else { - return Err("invalid tsig string".into()); + return Err("should contain at least one `:`".into()); }; let mut key; From 53c7b53b0f47438e2125f536b78220abe21f0f3c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 20 Nov 2024 15:32:22 +0100 Subject: [PATCH 11/19] start testing notify! --- Cargo.lock | 562 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +- src/commands/key2ds.rs | 6 +- src/commands/mod.rs | 8 + src/commands/notify.rs | 352 ++++++++++++++++++++++---- src/env/fake.rs | 61 ++++- src/env/mod.rs | 40 ++- src/env/real.rs | 19 ++ src/lib.rs | 3 +- 9 files changed, 994 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4aa3a33..48b9363 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.20" @@ -87,6 +96,34 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -207,12 +244,45 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "deranged" version = "0.3.11" @@ -242,13 +312,23 @@ name = "domain" version = "0.10.3" source = "git+https://github.com/NLnetLabs/domain.git#8f6e44385a927ab1225ea06ee4e2cdc594ef48b6" dependencies = [ + "arc-swap", "bytes", + "chrono", + "futures-util", "hashbrown", + "libc", + "moka", "octseq", "rand", "ring", "serde", + "siphasher", + "smallvec", "time", + "tokio", + "tracing", + "tracing-subscriber", ] [[package]] @@ -261,12 +341,70 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -299,6 +437,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -337,6 +481,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lexopt" version = "0.3.0" @@ -355,12 +505,31 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" @@ -376,6 +545,52 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "moka" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +dependencies = [ + "async-lock", + "async-trait", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener", + "futures-util", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -408,6 +623,7 @@ checksum = "126c3ca37c9c44cec575247f43a3e4374d8927684f129d2beeb0d2cef262fe12" dependencies = [ "bytes", "serde", + "smallvec", ] [[package]] @@ -416,12 +632,53 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "pin-project-lite" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "powerfmt" version = "0.2.0" @@ -446,6 +703,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quote" version = "1.0.37" @@ -485,6 +757,68 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw-cpuid" +version = "11.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "ring" version = "0.17.8" @@ -506,6 +840,15 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.41" @@ -519,6 +862,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.215" @@ -539,12 +894,52 @@ dependencies = [ "syn", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -568,6 +963,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tempfile" version = "3.14.0" @@ -581,6 +982,36 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.36" @@ -607,9 +1038,93 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + [[package]] name = "unicode-ident" version = "1.0.14" @@ -628,6 +1143,21 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -689,6 +1219,38 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index ecaf1fe..fd1c153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,15 @@ path = "src/bin/ldns.rs" [dependencies] bytes = "1.8.0" chrono = "0.4.38" -# domain = { version = "0.10.3", git = "https://github.com/NLnetLabs/domain.git", features = ["tsig", "zonefile", "bytes", "validate", "resolv", "net", "unstable-client-transport"] } clap = { version = "4.3.4", features = ["derive"] } domain = { version = "0.10.3", git = "https://github.com/NLnetLabs/domain.git", features = [ "zonefile", "bytes", "unstable-validate", + "net", + "unstable-client-transport", + "tsig", + "resolv", ] } lexopt = "0.3.0" tokio = "1.40.0" @@ -27,3 +30,4 @@ ring = { version = "0.17" } [dev-dependencies] tempfile = "3.14.0" +domain = { version = "*", git = "https://github.com/NLnetLabs/domain.git", features = ["unstable-stelline"] } \ No newline at end of file diff --git a/src/commands/key2ds.rs b/src/commands/key2ds.rs index b10f7bc..68392d9 100644 --- a/src/commands/key2ds.rs +++ b/src/commands/key2ds.rs @@ -248,9 +248,9 @@ mod test { use super::Key2ds; #[track_caller] - fn parse(args: FakeCmd) -> Key2ds { - let res = args.parse(); - let Command::Key2ds(x) = res.unwrap().command else { + fn parse(cmd: FakeCmd) -> Key2ds { + let res = cmd.parse().unwrap(); + let Command::Key2ds(x) = res.command else { panic!("Not a Key2ds!"); }; x diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 4f0d451..0ce3d42 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -9,6 +9,7 @@ use std::ffi::{OsStr, OsString}; use std::str::FromStr; use key2ds::Key2ds; +use notify::Notify; use nsec3hash::Nsec3Hash; use crate::env::Env; @@ -16,6 +17,7 @@ use crate::Args; use super::error::Error; +#[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, clap::Subcommand)] pub enum Command { /// Print the NSEC3 hash of a given domain name @@ -86,6 +88,12 @@ impl From for Command { } } +impl From for Command { + fn from(val: Notify) -> Self { + Command::Notify(val) + } +} + /// Utility function to parse an [`OsStr`] with a custom function fn parse_os_with(opt: &str, val: &OsStr, f: impl Fn(&str) -> Result) -> Result where diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 560403a..5298d76 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -5,21 +5,20 @@ use bytes::Bytes; use chrono::{DateTime, Local, TimeDelta}; use clap::builder::ValueParser; use domain::base::iana::{Class, Opcode}; -use domain::base::{ - Message, MessageBuilder, Name, Question, Record, Rtype, Serial, Ttl, -}; +use domain::base::{Message, MessageBuilder, Name, Question, Record, Rtype, Serial, Ttl}; use domain::dep::octseq::Array; -use domain::net::client::protocol::UdpConnect; use domain::net::client::request::{RequestMessage, SendRequest}; use domain::net::client::{dgram, tsig}; use domain::rdata::Soa; -use domain::resolv::stub::StubResolver; use domain::tsig::{Algorithm, Key, KeyName}; use domain::utils::{base16, base64}; +use crate::env::Env; use crate::error::Error; -#[derive(Clone, Debug)] +use super::LdnsCommand; + +#[derive(Clone, Debug, PartialEq, Eq)] struct TSigInfo { name: KeyName, key: Vec, @@ -82,21 +81,17 @@ impl FromStr for TSigInfo { // // We can correct this by checking whether the name contains a valid // algorithm while the algorithm doesn't. - if Algorithm::from_str(algorithm).is_err() - && Algorithm::from_str(name).is_ok() - { + if Algorithm::from_str(algorithm).is_err() && Algorithm::from_str(name).is_ok() { (name, key, algorithm) = (key, algorithm, name); } - let algorithm = Algorithm::from_str(algorithm).map_err(|_| { - format!("Unsupported TSIG algorithm: {algorithm}") - })?; + let algorithm = Algorithm::from_str(algorithm) + .map_err(|_| format!("Unsupported TSIG algorithm: {algorithm}"))?; - let key = base64::decode(key) - .map_err(|e| format!("TSIG key is invalid base64: {e}"))?; + let key = base64::decode(key).map_err(|e| format!("TSIG key is invalid base64: {e}"))?; - let name = Name::>::from_str(name) - .map_err(|e| format!("TSIG name is invalid: {e}"))?; + let name = + Name::>::from_str(name).map_err(|e| format!("TSIG name is invalid: {e}"))?; Ok(TSigInfo { name, @@ -106,10 +101,10 @@ impl FromStr for TSigInfo { } } -#[derive(Clone, Debug, clap::Args)] +#[derive(Clone, Debug, clap::Args, PartialEq, Eq)] pub struct Notify { /// The zone - #[arg(short = 'z', required = true)] + #[arg(short = 'z', long = "zone", required = true)] zone: Name>, // The -I option is supported by ldns but is not available in domain yet. @@ -148,13 +143,40 @@ pub struct Notify { servers: Vec, } +const LDNS_HELP: &str = "\ +usage: ldns-notify [other options] -z zone +Ldns notify utility + + Supported options: + -z zone The zone + -I
source address to query from + -s version SOA version number to include + -y specify named base64 tsig key, and optional an + algorithm (defaults to hmac-md5.sig-alg.reg.int) + -p port port to use to send to + -v Print version information + -d Print verbose debug information + -r num max number of retries (15) + -h Print this help information + +Report bugs to \ +"; + +impl LdnsCommand for Notify { + const HELP: &'static str = LDNS_HELP; + + fn parse_ldns>(_args: I) -> Result { + todo!() + } +} + impl Notify { - pub fn execute(&self) -> Result<(), Error> { + pub fn execute(&self, env: impl Env) -> Result<(), Error> { let runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.block_on(self.run()) + runtime.block_on(self.run(env)) } - async fn run(&self) -> Result<(), Error> { + async fn run(&self, env: impl Env) -> Result<(), Error> { let mut msg = MessageBuilder::new_vec(); let header = msg.header_mut(); @@ -193,31 +215,25 @@ impl Notify { .tsig .as_ref() .map(|tsig| { - Key::new( - tsig.algorithm, - &tsig.key, - tsig.name.clone(), - None, - None, - ) - .map_err(|e| format!("TSIG key is invalid: {e}")) + Key::new(tsig.algorithm, &tsig.key, tsig.name.clone(), None, None) + .map_err(|e| format!("TSIG key is invalid: {e}")) }) .transpose()?; let msg = msg.into_message(); - println!("# Sending packet:"); - println!("{}", msg.display_dig_style()); + writeln!(env.stdout(), "# Sending packet:"); + writeln!(env.stdout(), "{}", msg.display_dig_style()); if self.debug { - println!("Hexdump of notify packet:"); - println!("{}", base16::encode_display(&msg)); + writeln!(env.stdout(), "Hexdump of notify packet:"); + writeln!(env.stdout(), "{}", base16::encode_display(&msg)); } - let resolver = StubResolver::new(); + let resolver = env.stub_resolver().await; for server in &self.servers { - println!("# sending to {}", server); + writeln!(env.stdout(), "# sending to {}", server); // The specified server might be an IP address. In ldns, this case is // handled by `getaddrinfo`, but we have to do it ourselves. @@ -225,33 +241,38 @@ impl Notify { // can. if let Ok(addr) = server.parse() { let socket = SocketAddr::new(addr, self.port); - self.notify_host(socket, msg.clone(), server, &tsig).await; + self.notify_host(&env, socket, msg.clone(), server, &tsig) + .await; continue; } let Ok(name) = Name::>::from_str(server) else { - eprintln!( + writeln!( + env.stderr(), "warning: invalid domain name \"{server}\", skipping." ); continue; }; let Ok(hosts) = resolver.lookup_host(&name).await else { - eprintln!( + writeln!( + env.stderr(), "warning: could not resolve host \"{name}\", skipping." ); continue; }; if hosts.is_empty() { - eprintln!( + writeln!( + env.stderr(), "skipping bad address: {name}: Name or service not known" ); continue; } for socket in hosts.port_iter(self.port) { - self.notify_host(socket, msg.clone(), server, &tsig).await; + self.notify_host(&env, socket, msg.clone(), server, &tsig) + .await; } } @@ -260,6 +281,7 @@ impl Notify { async fn notify_host( &self, + env: &impl Env, socket: SocketAddr, msg: Message>, server: &str, @@ -267,32 +289,33 @@ impl Notify { ) { let resp = match &tsig { Some(tsig) => { - self.notify_host_with_tsig(socket, msg, tsig.clone()).await + self.notify_host_with_tsig(env, socket, msg, tsig.clone()) + .await } - None => self.notify_host_without_tsig(socket, msg).await, + None => self.notify_host_without_tsig(env, socket, msg).await, }; match resp { Ok(resp) => { - println!("# reply from {server} at {socket}:"); - println!("{resp}"); + writeln!(env.stdout(), "# reply from {server} at {socket}:"); + writeln!(env.stdout(), "{resp}"); } Err(e) => { - eprintln!("{e}"); + writeln!(env.stdout(), "{e}"); } } } async fn notify_host_with_tsig( &self, + env: impl Env, socket: SocketAddr, msg: Message>, key: Key, ) -> Result, Error> { let mut config = dgram::Config::new(); config.set_max_retries(self.retries as u8); - let connection = - dgram::Connection::with_config(UdpConnect::new(socket), config); + let connection = dgram::Connection::with_config(env.dgram(socket), config); let connection = tsig::Connection::new(key, connection); let req = RequestMessage::new(msg).unwrap(); @@ -313,13 +336,13 @@ impl Notify { async fn notify_host_without_tsig( &self, + env: impl Env, socket: SocketAddr, msg: Message>, ) -> Result, Error> { let mut config = dgram::Config::new(); config.set_max_retries(self.retries as u8); - let connection = - dgram::Connection::with_config(UdpConnect::new(socket), config); + let connection = dgram::Connection::with_config(env.dgram(socket), config); let req = RequestMessage::new(msg).unwrap(); let mut req = SendRequest::send_request(&connection, req); @@ -337,3 +360,234 @@ impl Notify { }) } } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use domain::{base::Name, tsig::Algorithm, utils::base64}; + + use crate::{ + commands::{notify::TSigInfo, Command}, + env::fake::FakeCmd, + }; + + use super::Notify; + + #[track_caller] + fn parse(cmd: FakeCmd) -> Notify { + let res = cmd.parse().unwrap(); + let Command::Notify(x) = res.command else { + panic!("not a notify!"); + }; + x + } + + #[test] + fn dnst_parse() { + let cmd = FakeCmd::new(["dnst", "notify"]); + + cmd.parse().unwrap_err(); + cmd.args(["--zone", "example.test"]).parse().unwrap_err(); + cmd.args(["--zone=example.test"]).parse().unwrap_err(); + cmd.args(["-z", "example.test"]).parse().unwrap_err(); + cmd.args(["-zexample.test"]).parse().unwrap_err(); + + let base = Notify { + zone: Name::from_str("example.test").unwrap(), + soa_version: None, + tsig: None, + port: 53, + debug: false, + retries: 15, + servers: vec!["some.example.test".into()], + }; + + // Create a command with some arguments that we reuse for some tests + let cmd2 = cmd.args(["-z", "example.test", "some.example.test"]); + + let res = parse(cmd2.clone()); + assert_eq!(res, base); + + for arg in ["-p", "--port"] { + let res = parse(cmd2.args([arg, "10"])); + assert_eq!( + res, + Notify { + port: 10, + ..base.clone() + } + ); + } + + let res = parse(cmd2.args(["-s", "10"])); + assert_eq!( + res, + Notify { + soa_version: Some(10), + ..base.clone() + } + ); + + for arg in ["-y", "--tsig"] { + let res = parse(cmd2.args([arg, "somekey:1234"])); + assert_eq!( + res, + Notify { + tsig: Some(TSigInfo { + name: "somekey".parse().unwrap(), + key: base64::decode("1234").unwrap(), + algorithm: Algorithm::Sha512, + }), + ..base.clone() + } + ); + } + } + + #[test] + #[ignore = "not implemented yet"] + fn ldns_parse() { + let cmd = FakeCmd::new(["ldns-notify"]); + + cmd.parse().unwrap_err(); + + // Shouldn't work at all + cmd.args(["--zone", "example.test"]).parse().unwrap_err(); + cmd.args(["--zone=example.test"]).parse().unwrap_err(); + + // Missing servers + cmd.args(["-z", "example.test"]).parse().unwrap_err(); + cmd.args(["-zexample.test"]).parse().unwrap_err(); + + let base = Notify { + zone: Name::from_str("example.test").unwrap(), + soa_version: None, + tsig: None, + port: 53, + debug: false, + retries: 15, + servers: vec!["some.example.test".into()], + }; + + // Create a command with some arguments that we reuse for some tests + let cmd2 = cmd.args(["-z", "example.test", "some.example.test"]); + + let res = parse(cmd2.clone()); + assert_eq!(res, base); + + let res = parse(cmd2.args(["-p", "10"])); + assert_eq!( + res, + Notify { + port: 10, + ..base.clone() + } + ); + + let res = parse(cmd2.args(["-s", "10"])); + assert_eq!( + res, + Notify { + soa_version: Some(10), + ..base.clone() + } + ); + + let res = parse(cmd2.args(["-y", "somekey:1234"])); + assert_eq!( + res, + Notify { + tsig: Some(TSigInfo { + name: "somekey".parse().unwrap(), + key: base64::decode("1234").unwrap(), + algorithm: Algorithm::Sha512, + }), + ..base.clone() + } + ); + } + + #[test] + fn with_zone_and_ip() { + let rpl = " + CONFIG_END + + SCENARIO_BEGIN + + RANGE_BEGIN 0 100 + + ENTRY_BEGIN + ADJUST copy_id + REPLY QR + SECTION QUESTION + nlnetlabs.test SOA + SECTION ANSWER + success.test 10 A 2.2.2.2 + ENTRY_END + + RANGE_END + + SCENARIO_END + "; + + let cmd = FakeCmd::new(["dnst", "notify", "-z", "nlnetlabs.test", "1.1.1.1"]) + .stelline(rpl.as_bytes(), "notify.rpl"); + + let res = cmd.run(); + assert_eq!(res.exit_code, 0); + assert!(res.stdout.contains("success.test")); + assert_eq!(res.stderr, ""); + } + + #[test] + fn with_zone_and_domain_name() { + let rpl = " + CONFIG_END + + SCENARIO_BEGIN + + RANGE_BEGIN 0 100 + + ENTRY_BEGIN + MATCH question + ADJUST copy_id copy_query + REPLY QR RD RA NOERROR + SECTION QUESTION + example.test. IN A + SECTION ANSWER + example.test. IN A 1.1.1.1 + ENTRY_END + + ENTRY_BEGIN + MATCH question + ADJUST copy_id copy_query + REPLY QR RD RA NOERROR + SECTION QUESTION + example.test. IN AAAA + ENTRY_END + + ENTRY_BEGIN + MATCH question + ADJUST copy_id + REPLY QR + SECTION QUESTION + nlnetlabs.test SOA + SECTION ANSWER + success.test IN 10 A 2.2.2.2 + ENTRY_END + + RANGE_END + + SCENARIO_END + "; + + let cmd = FakeCmd::new(["dnst", "notify", "-z", "nlnetlabs.test", "example.test"]) + .stelline(rpl.as_bytes(), "notify.rpl"); + + let res = cmd.run(); + assert_eq!(res.exit_code, 0); + assert!(res.stdout.contains("success.test")); + assert_eq!(res.stderr, ""); + } +} diff --git a/src/env/fake.rs b/src/env/fake.rs index 0715ee5..03b0e19 100644 --- a/src/env/fake.rs +++ b/src/env/fake.rs @@ -1,9 +1,17 @@ use std::borrow::Cow; use std::ffi::OsString; -use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::Mutex; +use std::{fmt, io}; + +use domain::net::client::dgram; +use domain::net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend}; +use domain::resolv::stub::conf::ResolvConf; +use domain::resolv::StubResolver; +use domain::stelline::client::CurrStepValue; +use domain::stelline::dgram::Dgram; +use domain::stelline::parse_stelline::{self, Stelline}; use crate::{error::Error, parse_args, run, Args}; @@ -19,6 +27,7 @@ pub struct FakeCmd { /// The command to run, including `argv[0]` cmd: Vec, cwd: Option, + stelline: Option, } /// The result of running a [`FakeCmd`] @@ -41,8 +50,8 @@ pub struct FakeEnv { /// The mocked stderr pub stderr: FakeStream, - // pub stelline: Option, - // pub curr_step_value: Option>, + + pub stelline: Option<(Stelline, Arc)>, } impl Env for FakeEnv { @@ -64,6 +73,35 @@ impl Env for FakeEnv { None => path.as_ref().into(), } } + + fn dgram( + &self, + _addr: std::net::SocketAddr, + ) -> impl AsyncConnect + + Clone + + Send + + Sync + + 'static { + if let Some((stelline, step)) = &self.stelline { + Dgram::new(stelline.clone(), step.clone()) + } else { + panic!("Tried making a stelline connection without setting up stelline") + } + } + + async fn stub_resolver_from_conf(&self, mut config: ResolvConf) -> StubResolver { + let Some((stelline, step)) = &self.stelline else { + panic!("Tried making a stelline connection without setting up stelline") + }; + + config.servers = vec![]; + let resolver = StubResolver::from_conf(config); + resolver.add_connection(Box::new(dgram::Connection::new(Dgram::new( + stelline.clone(), + step.clone(), + )))).await; + resolver + } } impl FakeCmd { @@ -74,6 +112,7 @@ impl FakeCmd { Self { cmd: cmd.into_iter().map(Into::into).collect(), cwd: None, + stelline: None, } } @@ -84,6 +123,13 @@ impl FakeCmd { } } + pub fn stelline(&self, file: impl fmt::Debug + io::Read, name: impl ToString) -> Self { + Self { + stelline: Some(parse_stelline::parse_file(file, name)), + ..self.clone() + } + } + /// Add arguments to a clone of the [`FakeCmd`] /// /// ```rust,ignore @@ -100,10 +146,15 @@ impl FakeCmd { /// Parse the arguments of this [`FakeCmd`] and return the result pub fn parse(&self) -> Result { + debug_assert!( + self.stelline.is_none(), + "We shouldn't need Stelline for argument parsing" + ); let env = FakeEnv { cmd: self.clone(), stdout: Default::default(), stderr: Default::default(), + stelline: None, }; parse_args(env) } @@ -114,6 +165,10 @@ impl FakeCmd { cmd: self.clone(), stdout: Default::default(), stderr: Default::default(), + stelline: self + .stelline + .clone() + .map(|s| (s, Arc::new(CurrStepValue::new()))), }; let exit_code = run(&env); diff --git a/src/env/mod.rs b/src/env/mod.rs index dd62dcb..62bbede 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::ffi::OsString; use std::fmt; +use std::net::SocketAddr; use std::path::Path; mod real; @@ -8,15 +9,10 @@ mod real; #[cfg(test)] pub mod fake; +use domain::{net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend}, resolv::{stub::conf::ResolvConf, StubResolver}}; pub use real::RealEnv; pub trait Env { - // /// Make a network connection - // fn make_connection(&self); - - // /// Make a new [`StubResolver`] - // fn make_stub_resolver(&self); - /// Get an iterator over the command line arguments passed to the program /// /// Equivalent to [`std::env::args_os`] @@ -36,6 +32,23 @@ pub trait Env { // fn stdin(&self) -> impl io::Read; fn in_cwd<'a>(&self, path: &'a impl AsRef) -> Cow<'a, Path>; + + fn dgram( + &self, + socket: SocketAddr, + ) -> impl AsyncConnect + + Clone + + Send + + Sync + + 'static; + + #[allow(async_fn_in_trait)] + async fn stub_resolver(&self) -> StubResolver { + self.stub_resolver_from_conf(ResolvConf::default()).await + } + + #[allow(async_fn_in_trait)] + async fn stub_resolver_from_conf(&self, config: ResolvConf) -> StubResolver; } /// A type with an infallible `write_fmt` method for use with [`write!`] macros @@ -81,4 +94,19 @@ impl Env for &E { fn in_cwd<'a>(&self, path: &'a impl AsRef) -> Cow<'a, Path> { (**self).in_cwd(path) } + + fn dgram( + &self, + socket: SocketAddr, + ) -> impl AsyncConnect + + Clone + + Send + + Sync + + 'static { + (**self).dgram(socket) + } + + async fn stub_resolver_from_conf(&self, config: ResolvConf) -> StubResolver { + (**self).stub_resolver_from_conf(config).await + } } diff --git a/src/env/real.rs b/src/env/real.rs index 26c01aa..caaf5c1 100644 --- a/src/env/real.rs +++ b/src/env/real.rs @@ -3,6 +3,10 @@ use std::fmt; use std::io; use std::path::Path; +use domain::net::client::protocol::UdpConnect; +use domain::resolv::stub::conf::ResolvConf; +use domain::resolv::StubResolver; + use super::Env; use super::Stream; @@ -25,6 +29,21 @@ impl Env for RealEnv { fn in_cwd<'a>(&self, path: &'a impl AsRef) -> std::borrow::Cow<'a, std::path::Path> { path.as_ref().into() } + + fn dgram( + &self, + addr: std::net::SocketAddr, + ) -> impl domain::net::client::protocol::AsyncConnect + + Clone + + Send + + Sync + + 'static { + UdpConnect::new(addr) + } + + async fn stub_resolver_from_conf(&self, config: ResolvConf) -> StubResolver { + StubResolver::from_conf(config) + } } struct FmtWriter(T); diff --git a/src/lib.rs b/src/lib.rs index 48e4075..9a9b314 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use std::ffi::OsString; use std::path::Path; use clap::Parser; -use commands::{key2ds::Key2ds, nsec3hash::Nsec3Hash, LdnsCommand}; +use commands::{key2ds::Key2ds, notify::Notify, nsec3hash::Nsec3Hash, LdnsCommand}; use env::Env; use error::Error; @@ -28,6 +28,7 @@ pub fn try_ldns_compatibility>( let res = match binary_name { "ldns-key2ds" => Key2ds::parse_ldns_args(args_iter), "ldns-nsec3-hash" => Nsec3Hash::parse_ldns_args(args_iter), + "ldns-notify" => Notify::parse_ldns_args(args_iter), _ => return Ok(None), }; From f0308ff001e5770a1ba118cf448248ab10864c3e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 21 Nov 2024 15:15:26 +0100 Subject: [PATCH 12/19] stelline tests for notify --- src/commands/notify.rs | 85 ++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 5298d76..4ccd82f 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -1,5 +1,6 @@ use core::fmt; -use std::{net::SocketAddr, str::FromStr}; +use std::net::SocketAddr; +use std::str::FromStr; use bytes::Bytes; use chrono::{DateTime, Local, TimeDelta}; @@ -363,6 +364,7 @@ impl Notify { #[cfg(test)] mod tests { + use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use domain::{base::Name, tsig::Algorithm, utils::base64}; @@ -508,6 +510,44 @@ mod tests { ); } + fn entries_for_name(name: &str, v4: &[Ipv4Addr], v6: &[Ipv6Addr]) -> String { + let v4 = v4 + .iter() + .map(|a| format!("{name} IN 10 A {a}")) + .collect::>() + .join("\n"); + + let v6 = v6 + .iter() + .map(|a| format!("{name} IN 10 AAAA {a}")) + .collect::>() + .join("\n"); + + format!( + " + ENTRY_BEGIN + MATCH question + ADJUST copy_id copy_query + REPLY QR RD RA NOERROR + SECTION QUESTION + {name} IN A + SECTION ANSWER + {v4} + ENTRY_END + + ENTRY_BEGIN + MATCH question + ADJUST copy_id copy_query + REPLY QR RD RA NOERROR + SECTION QUESTION + {name} IN AAAA + SECTION ANSWER + {v6} + ENTRY_END + " + ) + } + #[test] fn with_zone_and_ip() { let rpl = " @@ -521,9 +561,9 @@ mod tests { ADJUST copy_id REPLY QR SECTION QUESTION - nlnetlabs.test SOA + nlnetlabs.test SOA SECTION ANSWER - success.test 10 A 2.2.2.2 + success.test 10 A 2.2.2.2 ENTRY_END RANGE_END @@ -542,31 +582,21 @@ mod tests { #[test] fn with_zone_and_domain_name() { - let rpl = " + let foo = entries_for_name("foo.test", &[Ipv4Addr::new(1, 2, 3, 4)], &[]); + let bar = entries_for_name("bar.test", &[], &[]); + + let rpl = format!( + " CONFIG_END SCENARIO_BEGIN RANGE_BEGIN 0 100 - - ENTRY_BEGIN - MATCH question - ADJUST copy_id copy_query - REPLY QR RD RA NOERROR - SECTION QUESTION - example.test. IN A - SECTION ANSWER - example.test. IN A 1.1.1.1 - ENTRY_END - ENTRY_BEGIN - MATCH question - ADJUST copy_id copy_query - REPLY QR RD RA NOERROR - SECTION QUESTION - example.test. IN AAAA - ENTRY_END + {foo} + {bar} + ENTRY_BEGIN MATCH question ADJUST copy_id @@ -580,14 +610,21 @@ mod tests { RANGE_END SCENARIO_END - "; + " + ); - let cmd = FakeCmd::new(["dnst", "notify", "-z", "nlnetlabs.test", "example.test"]) + let cmd = FakeCmd::new(["dnst", "notify", "-z", "nlnetlabs.test", "foo.test"]) .stelline(rpl.as_bytes(), "notify.rpl"); let res = cmd.run(); - assert_eq!(res.exit_code, 0); assert!(res.stdout.contains("success.test")); assert_eq!(res.stderr, ""); + + let cmd = FakeCmd::new(["dnst", "notify", "-z", "nlnetlabs.test", "bar.test"]) + .stelline(rpl.as_bytes(), "notify.rpl"); + + let res = cmd.run(); + assert_eq!(res.exit_code, 0); + assert!(res.stderr.contains("Name or service not known")); } } From d03a9640aefe46d880bfb49a3d74a768fcbd4f02 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 21 Nov 2024 15:37:32 +0100 Subject: [PATCH 13/19] notify: implement ldns argument parsing --- src/commands/notify.rs | 80 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index 4ccd82f..e159969 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -13,11 +13,12 @@ use domain::net::client::{dgram, tsig}; use domain::rdata::Soa; use domain::tsig::{Algorithm, Key, KeyName}; use domain::utils::{base16, base64}; +use lexopt::Arg; use crate::env::Env; use crate::error::Error; -use super::LdnsCommand; +use super::{parse_os, LdnsCommand}; #[derive(Clone, Debug, PartialEq, Eq)] struct TSigInfo { @@ -166,8 +167,70 @@ Report bugs to \ impl LdnsCommand for Notify { const HELP: &'static str = LDNS_HELP; - fn parse_ldns>(_args: I) -> Result { - todo!() + fn parse_ldns>(args: I) -> Result { + let mut zone = None; + let mut soa_version = None; + let mut tsig = None; + let mut port = 53; + let mut debug = false; + let mut retries = 15; + let mut servers = Vec::new(); + + let mut parser = lexopt::Parser::from_args(args); + + while let Some(arg) = parser.next()? { + match arg { + Arg::Short('z') => { + let val = parser.value()?; + zone = Some(parse_os("zone (-z)", &val)?); + } + Arg::Short('I') => todo!(), + Arg::Short('s') => { + let val = parser.value()?; + soa_version = Some(parse_os("soa version (-s)", &val)?); + } + Arg::Short('y') => { + let val = parser.value()?; + tsig = Some(parse_os("tsig key (-y)", &val)?); + } + Arg::Short('p') => { + let val = parser.value()?; + port = parse_os("port (-p)", &val)?; + } + Arg::Short('v') => todo!(), + Arg::Short('d') => debug = true, + Arg::Short('r') => { + let val = parser.value()?; + retries = parse_os("retries (-r)", &val)?; + } + Arg::Short('h') => todo!(), + Arg::Short(x) => return Err(format!("Invalid short option: -{x}").into()), + Arg::Long(x) => { + return Err(format!("Long options are not supported, but `--{x}` given").into()) + } + Arg::Value(x) => { + servers.push(parse_os("server", &x)?); + } + } + } + + let Some(zone) = zone else { + return Err("Missing zone name argument".into()); + }; + + if servers.is_empty() { + return Err("Missing servers".into()); + } + + Ok(Notify { + zone, + soa_version, + tsig, + port, + debug, + retries, + servers, + }) } } @@ -448,7 +511,6 @@ mod tests { } #[test] - #[ignore = "not implemented yet"] fn ldns_parse() { let cmd = FakeCmd::new(["ldns-notify"]); @@ -462,6 +524,13 @@ mod tests { cmd.args(["-z", "example.test"]).parse().unwrap_err(); cmd.args(["-zexample.test"]).parse().unwrap_err(); + // Create a command with some arguments that we reuse for some tests + let cmd2 = cmd.args(["-z", "example.test", "some.example.test"]); + + // Invalid numbers + cmd2.args(["-p", "blabla"]).parse().unwrap_err(); + cmd2.args(["-r", "blabla"]).parse().unwrap_err(); + let base = Notify { zone: Name::from_str("example.test").unwrap(), soa_version: None, @@ -472,9 +541,6 @@ mod tests { servers: vec!["some.example.test".into()], }; - // Create a command with some arguments that we reuse for some tests - let cmd2 = cmd.args(["-z", "example.test", "some.example.test"]); - let res = parse(cmd2.clone()); assert_eq!(res, base); From c48b2ce9fb5de807a6ab95ba6c1829a57e2b171d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 21 Nov 2024 16:01:40 +0100 Subject: [PATCH 14/19] fix CI --- .github/workflows/ci.yml | 2 +- Cargo.toml | 1 + src/env/real.rs | 19 +++++++++++-------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f8dbc5..4e1584c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [1.78.0, stable, beta, nightly] + rust: [1.79.0, stable, beta, nightly] steps: - name: Checkout repository uses: actions/checkout@v1 diff --git a/Cargo.toml b/Cargo.toml index fd1c153..0218e05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "dnst" version = "0.1.0" edition = "2021" default-run = "dnst" +rust-version = "1.79" [[bin]] name = "ldns" diff --git a/src/env/real.rs b/src/env/real.rs index caaf5c1..42de3f5 100644 --- a/src/env/real.rs +++ b/src/env/real.rs @@ -3,6 +3,9 @@ use std::fmt; use std::io; use std::path::Path; +use domain::net::client::protocol::AsyncConnect; +use domain::net::client::protocol::AsyncDgramRecv; +use domain::net::client::protocol::AsyncDgramSend; use domain::net::client::protocol::UdpConnect; use domain::resolv::stub::conf::ResolvConf; use domain::resolv::StubResolver; @@ -31,16 +34,16 @@ impl Env for RealEnv { } fn dgram( - &self, - addr: std::net::SocketAddr, - ) -> impl domain::net::client::protocol::AsyncConnect - + Clone - + Send - + Sync - + 'static { + &self, + addr: std::net::SocketAddr, + ) -> impl AsyncConnect + + Clone + + Send + + Sync + + 'static { UdpConnect::new(addr) } - + async fn stub_resolver_from_conf(&self, config: ResolvConf) -> StubResolver { StubResolver::from_conf(config) } From d14213373cb9636c89f9b5a61dbe178993a4f04c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 21 Nov 2024 16:04:48 +0100 Subject: [PATCH 15/19] cargo fmt --- src/commands/mod.rs | 4 ++-- src/env/fake.rs | 10 ++++++---- src/env/mod.rs | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 0ce3d42..9308bda 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,8 +1,8 @@ //! The command of _dnst_. pub mod help; -pub mod notify; pub mod key2ds; +pub mod notify; pub mod nsec3hash; use std::ffi::{OsStr, OsString}; @@ -31,7 +31,7 @@ pub enum Command { /// that serial number it will disregard the message. #[command(name = "notify")] Notify(self::notify::Notify), - + /// Generate a DS RR from the DNSKEYS in keyfile /// /// The following file will be created for each key: diff --git a/src/env/fake.rs b/src/env/fake.rs index 03b0e19..df1c4b9 100644 --- a/src/env/fake.rs +++ b/src/env/fake.rs @@ -96,10 +96,12 @@ impl Env for FakeEnv { config.servers = vec![]; let resolver = StubResolver::from_conf(config); - resolver.add_connection(Box::new(dgram::Connection::new(Dgram::new( - stelline.clone(), - step.clone(), - )))).await; + resolver + .add_connection(Box::new(dgram::Connection::new(Dgram::new( + stelline.clone(), + step.clone(), + )))) + .await; resolver } } diff --git a/src/env/mod.rs b/src/env/mod.rs index 62bbede..f17f5cd 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -9,7 +9,8 @@ mod real; #[cfg(test)] pub mod fake; -use domain::{net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend}, resolv::{stub::conf::ResolvConf, StubResolver}}; +use domain::net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend}; +use domain::resolv::{stub::conf::ResolvConf, StubResolver}; pub use real::RealEnv; pub trait Env { @@ -105,7 +106,7 @@ impl Env for &E { + 'static { (**self).dgram(socket) } - + async fn stub_resolver_from_conf(&self, config: ResolvConf) -> StubResolver { (**self).stub_resolver_from_conf(config).await } From daed978096da93fe9bb455da9ffd960a579288ac Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 21 Nov 2024 16:06:41 +0100 Subject: [PATCH 16/19] update last occurrence of rust 1.78 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e1584c..e427d77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: - name: Install Rust uses: hecrj/setup-rust-action@v2 with: - rust-version: "1.78.0" + rust-version: "1.79.0" - name: Install nightly Rust run: rustup install nightly - name: Check with minimal-versions From 05cfaa42077b2a7f3b7f193d1c329237f5ac4c45 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 22 Nov 2024 12:45:07 +0100 Subject: [PATCH 17/19] fix lazy_static to 1.0.2 for minimal-versions --- Cargo.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0218e05..871bcd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,11 @@ tokio = "1.40.0" octseq = { version = "0.5.2", features = ["std"] } ring = { version = "0.17" } +# This is a workaround. lazy_static 1.0.0 fails to compile, but sharded-slab +# still uses it. And sharded-slab is used by tracing-subscriber, which is +# used by domain, which is used by us. +_unused_lazy_static = { package = "lazy_static", version = "1.0.2" } + [dev-dependencies] tempfile = "3.14.0" -domain = { version = "*", git = "https://github.com/NLnetLabs/domain.git", features = ["unstable-stelline"] } \ No newline at end of file +domain = { version = "*", git = "https://github.com/NLnetLabs/domain.git", features = ["unstable-stelline"] } From 48aa34a122709ee54f4fced7dac56cac606ead3a Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 22 Nov 2024 15:25:39 +0100 Subject: [PATCH 18/19] bring up to date with initial-nsec3-hash --- Cargo.lock | 2 +- src/commands/nsec3hash.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c472344..5419273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,7 +310,7 @@ dependencies = [ [[package]] name = "domain" version = "0.10.3" -source = "git+https://github.com/NLnetLabs/domain.git?branch=initial-nsec3-generation#e1c1db8e4103eed5f69d77d7b88581298cc00818" +source = "git+https://github.com/NLnetLabs/domain.git?branch=initial-nsec3-generation#250b52eeb9f6b0801b5c04d14e6674f96e774246" dependencies = [ "arc-swap", "bytes", diff --git a/src/commands/nsec3hash.rs b/src/commands/nsec3hash.rs index 852b49b..2edb876 100644 --- a/src/commands/nsec3hash.rs +++ b/src/commands/nsec3hash.rs @@ -190,6 +190,7 @@ mod tests { cmd: FakeCmd::new(["unused"]), stdout: FakeStream::default(), stderr: FakeStream::default(), + stelline: None, }; // We don't test all permutations as that would take too long (~20 seconds) From e53b3d50715a220c39466a8019c914397f899af5 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 22 Nov 2024 15:54:05 +0100 Subject: [PATCH 19/19] imports more in domain style --- src/commands/notify.rs | 11 ++++++----- src/env/real.rs | 5 +---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/commands/notify.rs b/src/commands/notify.rs index e159969..121eecd 100644 --- a/src/commands/notify.rs +++ b/src/commands/notify.rs @@ -430,12 +430,13 @@ mod tests { use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; - use domain::{base::Name, tsig::Algorithm, utils::base64}; + use domain::base::Name; + use domain::tsig::Algorithm; + use domain::utils::base64; - use crate::{ - commands::{notify::TSigInfo, Command}, - env::fake::FakeCmd, - }; + use crate::commands::notify::TSigInfo; + use crate::commands::Command; + use crate::env::fake::FakeCmd; use super::Notify; diff --git a/src/env/real.rs b/src/env/real.rs index 42de3f5..5b9e5b3 100644 --- a/src/env/real.rs +++ b/src/env/real.rs @@ -3,10 +3,7 @@ use std::fmt; use std::io; use std::path::Path; -use domain::net::client::protocol::AsyncConnect; -use domain::net::client::protocol::AsyncDgramRecv; -use domain::net::client::protocol::AsyncDgramSend; -use domain::net::client::protocol::UdpConnect; +use domain::net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend, UdpConnect}; use domain::resolv::stub::conf::ResolvConf; use domain::resolv::StubResolver;