diff --git a/Cargo.toml b/Cargo.toml index f0e7c00..a673eac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,7 @@ rust-version = "1.74.0" [dependencies] bytes = "1" clap = { version = "4", features = ["derive", "unstable-doc"] } -chrono = { version = "0.4.38", features = [ "alloc", "clock" ] } domain = { git="https://github.com/NLnetLabs/domain.git", branch="message-for-slice", features = ["resolv", "unstable-client-transport"]} -tempfile = "3.1.0" tokio = { version = "1.33", features = ["rt-multi-thread"] } diff --git a/doc/dnsi-query.1 b/doc/dnsi-query.1 deleted file mode 100644 index 7be4224..0000000 --- a/doc/dnsi-query.1 +++ /dev/null @@ -1,167 +0,0 @@ -.TH "dnsi-query" "1" "NLnet Labs" - -.SH NAME -dnsi-query - Send a query to a name server - -.SH SYNOPSIS -.B dnsi query -[\fIoptions\fR] -.I query_name -[\fIquery_type\fR] - -.SH DESCRIPTION -The -.B dnsi query -command sends a DNS query to a name server and prints out the response. The -server can either be selected or the resolver configured in the system will -be used. - -The query will be for the domain name given by -.IR query_name . -If -.I query_type -is given, it provides the record type to be queried for. If it is missing, -the record type -.B A -is used. - -A specific name server and port can be selected through the -.B --server -and -.B --port -options. If the latter is missing, the default port is used. If no server -is given, the system’s default servers configured in -.I /etc/resolv.conf -are tried in order and the first received response is printed. - -If neither the -.B --tcp -or -.B --udp -options are given, the query is first sent over UDP. If a response is -received but it indicates that it had to be truncated to fit, it is repeated -over TCP. If one of the options is given, then only this transport -protocol is used. In particular, if -.B --udp -is given, a truncated answer is accepted and printed. - -The output format can be selected through the -.B --format -option. If it is missing, output similar to that of -.BR dig (1) -is used. - -.SH OPTIONS -.TP -.B -s\fR \fIaddr_or_host\fR, \fB--server\fR \fIaddr_or_host -Specifies the name server to send the query to. If present, the query will be -sent to the server given. If a host name is used, the name is resolved using -the system resolver and the query is sent to the resulting addresses and the -first received response is printed. - -If this option is missing, -is given, the system’s default servers configured in -.I /etc/resolv.conf -are tried in order and the first received response is printed. - -.TP -.B --p\fR \fIport\fR, \fB--port\fR \fIport -Specifies the port to use when connecting to the name server. If missing, the -default port will be used. This is 53 for UDP and TCP. - -.TP -.BR -4 ,\ --ipv4 -Indicates that only IPv4 should be used. - -.TP -.BR -6 ,\ --ipv6 -Indicates that only IPv4 should be used. - -.TP -.BR -t ,\ --tcp -Specifies that only TCP should be used. - -.TP -.BR -u ,\ --udp -Specifies that only UDP should be used and the resulting answer be printed -even if it had to be truncated. - -.TP -.BI --timeout \ seconds -Sets the time after sending a query before a server is considered -non-responsive if an answer is not received. - -The -.I -seconds -value can be given with decimal fractions, e.g., 0.2. - -.TP -.BI --retries \ number -The number of times a query is retried over UDP after timing out before a -server is considered non-responsive. This value is ignored for transport -protocols other than UDP. - -.TP -.BI --udp-payload-size \ bytes -Sets the accepted UDP payload size announced in the query to server. If this -option is missing, the default size of 1232 bytes is used. The value is -ignored for transport protocols other than UDP. - -.TP -.B --no-rd -Specifies that the "recursion desired" flag, or RD flag for short, should -not be set in the query. If this option is missing, the flag will be set. - -The flag indicates to a resolver that it should try and gather all necessary -information from their authoritative name servers to create a complete answer -to the query. This process is called "recursion." - -.TP -.BR -f ,\ --force -Requests that no sanity checks are done and the query is to be sent as -specified. - -Normally, -.B dnsi query -will refuse to do zone transfer queries with query type AXFR or IXFR -because they require special processing. With this option, you can force -it to send these queries, anyway. - -.TP -.B --verify -Requests to compare the received response to the response provided -by one of the authoritative name servers. - -If the resource records in the -answer section differ, a diff between them is provided. Records that appear -in the answer section of the received response only are prefixed with a plus, -records that appard in the answer section of the authoritative response are -prefixed with a minus. - -The records in the authority and additional sections are not compared. - -This option is intended for zones that provide the same answer on all servers -and only one authoritative response is acquired and considered. If the zone's -name servers provided differing answers, re-running the command thus may -result in different output. - -.TP -.BI --format \ format -Selects the data format in which the response should be printed. The -following formats are currently supported: -.RS -.TP -.B dig -The response and some additional information are printed in a format -similar to what -.BR dig (1) -produces. - -This is currently the default format if the option is missing. -.RE - -.TP -.BR -h ,\ --help -Prints some help information. - diff --git a/doc/dnsi.1 b/doc/dnsi.1 deleted file mode 100644 index 661798d..0000000 --- a/doc/dnsi.1 +++ /dev/null @@ -1,26 +0,0 @@ -.TH "dnsi" "1" "NLnet Labs" - -.SH NAME -dnsi - inspect the DNS - -.SH SYNOPSIS -.B dnsi -[] - -[] - -.SH DESCRIPTION -Inspect the Domain Name System (DNS) in various ways. - -.B dnsi -provides a number of commands that perform various tasks related to the DNS. - -Please consult the manual pages for these individual commands for more -information. - -.SH IDNS COMMANDS - -.PP -\fBdnsi-query\fR(1) -.RS 4 -Send a query to a name server. diff --git a/src/bin/dnsi.rs b/src/bin/dnsi.rs deleted file mode 100644 index 02762ae..0000000 --- a/src/bin/dnsi.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! The _dnsi_ binary. - -use clap::Parser; -use domain_tools::dnsi; - -fn main() { - if let Err(err) = dnsi::Args::parse().execute() { - eprintln!("{}", err); - } -} - diff --git a/src/dnsi/args.rs b/src/dnsi/args.rs deleted file mode 100644 index f9fc2b0..0000000 --- a/src/dnsi/args.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Global configuration. - -use super::commands::Commands; -use super::error::Error; - - -#[derive(Clone, Debug, clap::Parser)] -pub struct Args { - #[command(subcommand)] - command: Commands, -} - -impl Args { - pub fn execute(self) -> Result<(), Error> { - self.command.execute() - } -} - diff --git a/src/dnsi/client.rs b/src/dnsi/client.rs deleted file mode 100644 index 6b19b53..0000000 --- a/src/dnsi/client.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! The DNS client for _dnsi._ - -use std::fmt; -use std::net::SocketAddr; -use std::time::Duration; -use bytes::Bytes; -use chrono::{DateTime, Local, TimeDelta}; -use domain::base::message::Message; -use domain::base::message_builder::MessageBuilder; -use domain::base::name::ToName; -use domain::base::question::Question; -use domain::net::client::{dgram, stream}; -use domain::net::client::protocol::UdpConnect; -use domain::net::client::request::{RequestMessage, SendRequest}; -use domain::resolv::stub::conf; -use tokio::net::TcpStream; -use crate::dnsi::error::Error; - - -//------------ Client -------------------------------------------------------- - -/// The DNS client used by _dnsi._ -#[derive(Clone, Debug)] -pub struct Client { - servers: Vec, -} - -impl Client { - /// Creates a client using the system configuration. - pub fn system() -> Self { - let conf = conf::ResolvConf::default(); - Self { - servers: conf.servers.iter().map(|server| { - Server { - addr: server.addr, - transport: server.transport.into(), - timeout: server.request_timeout, - retries: u8::try_from(conf.options.attempts).unwrap_or(2), - udp_payload_size: server.udp_payload_size, - } - }).collect(), - } - } - - pub fn with_servers(servers: Vec) -> Self { - Self { servers } - } - - pub async fn query>>( - &self, question: Q - ) -> Result { - let mut res = MessageBuilder::new_vec(); - - res.header_mut().set_rd(true); - res.header_mut().set_random_id(); - - let mut res = res.question(); - res.push(question.into()).unwrap(); - - self.request(RequestMessage::new(res)).await - } - - pub async fn request( - &self, request: RequestMessage> - ) -> Result { - let mut servers = self.servers.as_slice(); - while let Some((server, tail)) = servers.split_first() { - match self.request_server(request.clone(), server).await { - Ok(answer) => return Ok(answer), - Err(err) => { - if tail.is_empty() { - return Err(err) - } - } - } - servers = tail; - } - unreachable!() - } - - pub async fn request_server( - &self, request: RequestMessage>, server: &Server, - ) -> Result { - match server.transport { - Transport::Udp => self.request_udp(request, server).await, - Transport::UdpTcp => self.request_udptcp(request, server).await, - Transport::Tcp => self.request_tcp(request, server).await, - } - } - - pub async fn request_udptcp( - &self, request: RequestMessage>, server: &Server, - ) -> Result { - let answer = self.request_udp(request.clone(), server).await?; - if answer.message.header().tc() { - self.request_tcp(request, server).await - } - else { - Ok(answer) - } - } - - pub async fn request_udp( - &self, request: RequestMessage>, server: &Server, - ) -> Result { - let mut stats = Stats::new(server.addr, Protocol::Udp); - let conn = dgram::Connection::with_config( - UdpConnect::new(server.addr), - Self::dgram_config(server) - ); - let message = conn.send_request(request).get_response().await?; - stats.finalize(); - Ok(Answer { message, stats }) - } - - pub async fn request_tcp( - &self, request: RequestMessage>, server: &Server, - ) -> Result { - let mut stats = Stats::new(server.addr, Protocol::Tcp); - let socket = TcpStream::connect(server.addr).await?; - let (conn, tran) = stream::Connection::with_config( - socket, Self::stream_config(server) - ); - tokio::spawn(tran.run()); - let message = conn.send_request(request).get_response().await?; - stats.finalize(); - Ok(Answer { message, stats }) - } - - fn dgram_config(server: &Server) -> dgram::Config { - let mut res = dgram::Config::new(); - res.set_read_timeout(server.timeout); - res.set_max_retries(server.retries); - res.set_udp_payload_size(Some(server.udp_payload_size)); - res - } - - fn stream_config(server: &Server) -> stream::Config { - let mut res = stream::Config::new(); - res.set_response_timeout(server.timeout); - res - } -} - - -//------------ Server -------------------------------------------------------- - -#[derive(Clone, Debug)] -pub struct Server { - pub addr: SocketAddr, - pub transport: Transport, - pub timeout: Duration, - pub retries: u8, - pub udp_payload_size: u16, -} - - -//------------ Transport ----------------------------------------------------- - -#[derive(Clone, Copy, Debug)] -pub enum Transport { - Udp, - UdpTcp, - Tcp, -} - -impl From for Transport { - fn from(transport: conf::Transport) -> Self { - match transport { - conf::Transport::UdpTcp => Transport::UdpTcp, - conf::Transport::Tcp => Transport::Tcp, - } - } -} - - -//------------ Answer -------------------------------------------------------- - -/// An answer for a query. -pub struct Answer { - message: Message, - stats: Stats, -} - -impl Answer { - pub fn stats(&self) -> Stats { - self.stats - } - - pub fn message(&self) -> &Message { - &self.message - } - - pub fn msg_slice(&self) -> Message<&[u8]> { - self.message.for_slice_ref() - } -} - -impl AsRef> for Answer { - fn as_ref(&self) -> &Message { - &self.message - } -} - - -//------------ Stats --------------------------------------------------------- - -#[derive(Clone, Copy, Debug)] -pub struct Stats { - pub start: DateTime, - pub duration: TimeDelta, - pub server_addr: SocketAddr, - pub server_proto: Protocol, -} - -impl Stats { - fn new(server_addr: SocketAddr, server_proto: Protocol) -> Self { - Stats { - start: Local::now(), - duration: Default::default(), - server_addr, - server_proto, - } - } - - fn finalize(&mut self) { - self.duration = Local::now() - self.start; - } -} - - -//------------ Protocol ------------------------------------------------------ - -#[derive(Clone, Copy, Debug)] -pub enum Protocol { - Udp, - Tcp, -} - -impl fmt::Display for Protocol { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str( - match *self { - Protocol::Udp => "UDP", - Protocol::Tcp => "TCP", - } - ) - } -} - diff --git a/src/dnsi/commands/help.rs b/src/dnsi/commands/help.rs deleted file mode 100644 index bee63ca..0000000 --- a/src/dnsi/commands/help.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! The help command of _dnsi._ - -use std::io::Write; -use std::process::Command; -use tempfile::NamedTempFile; -use crate::dnsi::error::Error; - - -//------------ Help ---------------------------------------------------------- - -#[derive(Clone, Debug, clap::Args)] -pub struct Help { - /// The command to show the man page for - #[arg(value_name="COMMAND")] - command: Option, -} - -impl Help { - pub fn execute(self) -> Result<(), Error> { - let page = match self.command.as_deref() { - None => Self::IDNS_1, - Some("query") => Self::IDNS_QUERY_1, - Some(command) => { - return Err(format!("Unknown command '{}'.", command).into()); - } - }; - - let mut file = NamedTempFile::new().map_err(|err| { - format!( - "Can't display man page: \ - Failed to create temporary file: {}.", - err - ) - })?; - file.write_all(page).map_err(|err| { - format!( - "Can't display man page: \ - Failed to write to temporary file: {}.", - err - ) - })?; - let _ = Command::new("man").arg(file.path()).status().map_err(|err| { - format!("Failed to run man: {}", err) - })?; - Ok(()) - } -} - -impl Help { - const IDNS_1: &'static [u8] = include_bytes!("../../../doc/dnsi.1"); - const IDNS_QUERY_1: &'static [u8] = include_bytes!( - "../../../doc/dnsi-query.1" - ); -} - diff --git a/src/dnsi/commands/mod.rs b/src/dnsi/commands/mod.rs deleted file mode 100644 index da31f42..0000000 --- a/src/dnsi/commands/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! The various commands of _idsn._ - -pub mod help; -pub mod query; - - -use super::error::Error; - - -#[derive(Clone, Debug, clap::Subcommand)] -pub enum Commands { - /// Query the DNS. - Query(self::query::Query), - - /// Show the manual pages. - Man(self::help::Help), -} - -impl Commands { - pub fn execute(self) -> Result<(), Error> { - match self { - Self::Query(query) => query.execute(), - Self::Man(help) => help.execute(), - } - } -} - diff --git a/src/dnsi/commands/query.rs b/src/dnsi/commands/query.rs deleted file mode 100644 index f9d6f0d..0000000 --- a/src/dnsi/commands/query.rs +++ /dev/null @@ -1,452 +0,0 @@ -//! The query command of _dnsi._ - -use std::fmt; -use std::collections::HashSet; -use std::net::{IpAddr, SocketAddr}; -use std::str::FromStr; -use std::time::Duration; -use bytes::Bytes; -use domain::base::iana::{Class, Rtype}; -use domain::base::message::Message; -use domain::base::message_builder::MessageBuilder; -use domain::base::name::{Name, ParsedName, ToName, UncertainName}; -use domain::base::rdata::RecordData; -use domain::net::client::request::RequestMessage; -use domain::rdata::{AllRecordData, Ns, Soa}; -use domain::resolv::stub::StubResolver; -use domain::resolv::stub::conf::ResolvConf; -use crate::dnsi::client::{Answer, Client, Server, Transport}; -use crate::dnsi::error::Error; -use crate::dnsi::output::OutputFormat; - - -//------------ Query --------------------------------------------------------- - -#[derive(Clone, Debug, clap::Args)] -pub struct Query { - /// The name of the resource records to look up - #[arg(value_name="QUERY_NAME")] - qname: Name>, - - /// The record type to look up - #[arg(value_name="QUERY_TYPE", default_value = "A")] - qtype: Rtype, - - /// The server to send the query to. System servers used if missing - #[arg(short, long, value_name="ADDR_OR_HOST")] - server: Option, - - /// The port of the server to send query to. - #[arg(short = 'p', long = "port", requires = "server")] - port: Option, - - /// Use only IPv4 for communication. - #[arg(short = '4', long, conflicts_with = "ipv6")] - ipv4: bool, - - /// Use only IPv6 for communication. - #[arg(short = '6', long, conflicts_with = "ipv4")] - ipv6: bool, - - /// Use only TCP. - #[arg(short, long)] - tcp: bool, - - /// Use only UDP. - #[arg(short, long)] - udp: bool, - - /// Set the timeout for a query. - #[arg(long, value_name="SECONDS")] - timeout: Option, - - /// Set the number of retries over UDP. - #[arg(long)] - retries: Option, - - /// Set the advertised UDP payload size. - #[arg(long)] - udp_payload_size: Option, - - /// Unset the RD flag in the request. - #[arg(long)] - no_rd: bool, - - /// Disable all sanity checks. - #[arg(long, short = 'f')] - force: bool, - - /// Verify the answer against an authoritative server. - #[arg(long)] - verify: bool, - - /// Select the output format. - #[arg(long = "format", default_value = "dig")] - output_format: OutputFormat, -} - -/// # Executing the command -/// -impl Query { - pub fn execute(self) -> Result<(), Error> { - #[allow(clippy::collapsible_if)] // There may be more later ... - if !self.force { - if self.qtype == Rtype::AXFR || self.qtype == Rtype::IXFR { - return Err( - "AXFR and IXFR query types invoke zone transfer which \ - may result in a sequence\n\ - of responses but only the first is shown \ - by the 'query' command.\n\ - Please use the 'xfr' command for zone transfer.\n\ - (Use --force to query anyway.)".into() - ); - } - } - - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(self.async_execute()) - } - - pub async fn async_execute(self) -> Result<(), Error> { - let client = match self.server { - Some(ServerName::Name(ref host)) => self.host_server(host).await?, - Some(ServerName::Addr(addr)) => self.addr_server(addr), - None => self.system_server(), - }; - - let answer = client.request(self.create_request()).await?; - self.output_format.print(&answer)?; - if self.verify { - let auth_answer = self.auth_answer().await?; - if let Some(diff) = Self::diff_answers( - auth_answer.message(), answer.message() - )? { - println!("\n;; Authoritative ANSWER does not match."); - println!( - ";; Difference of ANSWER with authoritative server {}:", - auth_answer.stats().server_addr - ); - self.output_diff(diff); - } - else { - println!("\n;; Authoritative ANSWER matches."); - } - } - Ok(()) - } -} - -/// # Configuration -/// -impl Query { - fn timeout(&self) -> Duration { - Duration::from_secs_f32(self.timeout.unwrap_or(5.)) - } - - fn retries(&self) -> u8 { - self.retries.unwrap_or(2) - } - - fn udp_payload_size(&self) -> u16 { - self.udp_payload_size.unwrap_or(1232) - } -} - - -/// # Resolving the server set -/// -impl Query { - /// Resolves a provided server name. - async fn host_server( - &self, server: &UncertainName>, - ) -> Result { - let resolver = StubResolver::default(); - let answer = match server { - UncertainName::Absolute(name) => { - resolver.lookup_host(name).await - } - UncertainName::Relative(name) => { - resolver.search_host(name).await - } - }.map_err(|err| err.to_string())?; - - let mut servers = Vec::new(); - for addr in answer.iter() { - if (addr.is_ipv4() && self.ipv6) || (addr.is_ipv6() && self.ipv4) { - continue - } - servers.push(Server { - addr: SocketAddr::new(addr, self.port.unwrap_or(53)), - transport: self.transport(), - timeout: self.timeout(), - retries: self.retries.unwrap_or(2), - udp_payload_size: self.udp_payload_size.unwrap_or(1232), - }); - } - Ok(Client::with_servers(servers)) - } - - /// Resolves a provided server name. - fn addr_server(&self, addr: IpAddr) -> Client { - Client::with_servers(vec![ - Server { - addr: SocketAddr::new(addr, self.port.unwrap_or(53)), - transport: self.transport(), - timeout: self.timeout(), - retries: self.retries(), - udp_payload_size: self.udp_payload_size() - } - ]) - } - - /// Creates a client based on the system defaults. - fn system_server(&self) -> Client { - let conf = ResolvConf::default(); - Client::with_servers( - conf.servers.iter().map(|server| { - Server { - addr: server.addr, - transport: self.transport(), - timeout: server.request_timeout, - retries: u8::try_from(conf.options.attempts).unwrap_or(2), - udp_payload_size: server.udp_payload_size, - } - }).collect() - ) - } - - fn transport(&self) -> Transport { - if self.udp { - Transport::Udp - } - else if self.tcp { - Transport::Tcp - } - else { - Transport::UdpTcp - } - } -} - -/// # Create the actual query -/// -impl Query { - /// Creates a new request message. - fn create_request(&self) -> RequestMessage> { - let mut res = MessageBuilder::new_vec(); - - res.header_mut().set_rd(!self.no_rd); - - let mut res = res.question(); - res.push((&self.qname, self.qtype)).unwrap(); - - RequestMessage::new(res) - } -} - -/// # Get an authoritative answer -impl Query { - async fn auth_answer(&self) -> Result { - let servers = { - let resolver = StubResolver::new(); - let apex = self.get_apex(&resolver).await?; - let ns_set = self.get_ns_set(&apex, &resolver).await?; - self.get_ns_addrs(&ns_set, &resolver).await? - }; - Client::with_servers(servers).query((&self.qname, self.qtype)).await - } - - /// Tries to determine the apex of the zone the requested records live in. - async fn get_apex( - &self, resolv: &StubResolver - ) -> Result>, Error> { - // Ask for the SOA record for the qname. - let response = resolv.query((&self.qname, Rtype::SOA)).await?; - - // The SOA record is in the answer section if the qname is the apex - // or in the authority section with the apex as the owner name - // otherwise. - let mut answer = response.answer()?.limit_to_in::>(); - if let Some(soa) = answer.next() { - let soa = soa?; - if *soa.owner() == self.qname { - return Ok(self.qname.clone()) - } - // Strange SOA in the answer section, let’s continue with - // the authority section. - } - - let mut authority = answer.next_section()?.unwrap() - .limit_to_in::>(); - if let Some(soa) = authority.next() { - let soa = soa?; - return Ok(soa.owner().to_name()) - } - - Err("no SOA record".into()) - } - - /// Tries to find the NS set for the given apex name. - async fn get_ns_set( - &self, apex: &Name>, resolv: &StubResolver - ) -> Result>>, Error> { - let response = resolv.query((apex, Rtype::NS)).await?; - let mut res = Vec::new(); - for record in response.answer()?.limit_to_in::>() { - let record = record?; - if *record.owner() != apex { - continue; - } - res.push(record.data().nsdname().to_name()); - } - - // We could technically get the A and AAAA records from the additional - // section, but we’re going to ask anyway, so: meh. - - Ok(res) - } - - /// Tries to get all the addresses for all the name servers. - async fn get_ns_addrs( - &self, ns_set: &[Name>], resolv: &StubResolver - ) -> Result, Error> { - let mut res = HashSet::new(); - for ns in ns_set { - for addr in resolv.lookup_host(ns).await?.iter() { - res.insert(addr); - } - } - Ok( - res.into_iter().map(|addr| { - Server { - addr: SocketAddr::new(addr, 53), - transport: Transport::UdpTcp, - timeout: self.timeout(), - retries: self.retries(), - udp_payload_size: self.udp_payload_size(), - } - }).collect() - ) - } - - /// Produces a diff between two answer sections. - /// - /// Returns `Ok(None)` if the two answer sections are identical apart from - /// the TTLs. - #[allow(clippy::mutable_key_type)] - fn diff_answers( - left: &Message, right: &Message - ) -> Result>, Error> { - // Put all the answers into a two hashsets. - let left = left.answer()?.into_records::>( - ).filter_map( - Result::ok - ).map(|record| { - let class = record.class(); - let (name, data) = record.into_owner_and_data(); - (name, class, data) - }).collect::>(); - - let right = right.answer()?.into_records::>( - ).filter_map( - Result::ok - ).map(|record| { - let class = record.class(); - let (name, data) = record.into_owner_and_data(); - (name, class, data) - }).collect::>(); - - let mut diff = left.intersection(&right).cloned().map(|item| { - (Action::Unchanged, item) - }).collect::>(); - let size = diff.len(); - - diff.extend(left.difference(&right).cloned().map(|item| { - (Action::Removed, item) - })); - - diff.extend(right.difference(&left).cloned().map(|item| { - (Action::Added, item) - })); - - diff.sort_by(|left, right| left.1.cmp(&right.1)); - - if size == diff.len() { - Ok(None) - } - else { - Ok(Some(diff)) - } - } - - /// Prints the content of a diff. - fn output_diff(&self, diff: Vec) { - for item in diff { - println!( - "{}{} {} {} {}", - item.0, item.1.0, item.1.1, item.1.2.rtype(), item.1.2 - ); - } - } -} - - -//------------ ServerName --------------------------------------------------- - -#[derive(Clone, Debug)] -enum ServerName { - Name(UncertainName>), - Addr(IpAddr), -} - -impl FromStr for ServerName { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - if let Ok(addr) = IpAddr::from_str(s) { - Ok(ServerName::Addr(addr)) - } - else { - UncertainName::from_str(s).map(Self::Name).map_err(|_| - "illegal host name" - ) - } - } -} - - -//------------ Action -------------------------------------------------------- - -#[derive(Clone, Copy, Debug)] -enum Action { - Added, - Removed, - Unchanged, -} - -impl fmt::Display for Action { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str( - match *self { - Self::Added => "+ ", - Self::Removed => "- ", - Self::Unchanged => " ", - } - ) - } -} - - -//----------- DiffItem ------------------------------------------------------- - -type DiffItem = ( - Action, - ( - ParsedName, - Class, - AllRecordData> - ) -); diff --git a/src/dnsi/error.rs b/src/dnsi/error.rs deleted file mode 100644 index 5024fb4..0000000 --- a/src/dnsi/error.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Error handling. - -use std::{error, fmt, io}; -use std::borrow::Cow; -use domain::base::wire::ParseError; -use domain::net::client::request; - - -//------------ Error --------------------------------------------------------- - -#[derive(Clone, Debug)] -pub struct Error { - message: Cow<'static, str>, -} - -impl From<&'static str> for Error { - fn from(message: &'static str) -> Self { - Self { message: Cow::Borrowed(message) } - } -} - -impl From for Error { - fn from(message: String) -> Self { - Self { message: Cow::Owned(message) } - } -} - -impl From for Error { - fn from(err: io::Error) -> Self { - Self::from(err.to_string()) - } -} - -impl From for Error { - fn from(_err: ParseError) -> Self { - Self::from("message parse error") - } -} - -impl From for Error { - fn from(err: request::Error) -> Self { - Self::from(err.to_string()) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.message, f) - } -} - -impl error::Error for Error { } diff --git a/src/dnsi/mod.rs b/src/dnsi/mod.rs deleted file mode 100644 index 6dab6e9..0000000 --- a/src/dnsi/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! The actual implementation of _dnsi._ - -pub use self::args::Args; - -pub mod args; -pub mod client; -pub mod error; -pub mod commands; -pub mod output; - diff --git a/src/dnsi/output/dig.rs b/src/dnsi/output/dig.rs deleted file mode 100644 index 675d461..0000000 --- a/src/dnsi/output/dig.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! An output format compatible with dig. - -use std::io; -use domain::base::iana::Rtype; -use domain::base::opt::AllOptData; -use domain::rdata::AllRecordData; -use crate::dnsi::client::Answer; - -//------------ write --------------------------------------------------------- - -pub fn write( - answer: &Answer, target: &mut impl io::Write -) -> Result<(), io::Error> { - let msg = answer.msg_slice(); - - // Header - let header = msg.header(); - let counts = msg.header_counts(); - - writeln!(target, - ";; ->>HEADER<<- opcode: {}, rcode: {}, id: {}", - header.opcode(), header.rcode(), header.id() - )?; - write!(target, ";; flags: {}", header.flags())?; - writeln!(target, - "; QUERY: {}, ANSWER: {}, AUTHORITY: {}, ADDITIONAL: {}", - counts.qdcount(), counts.ancount(), counts.nscount(), counts.arcount() - )?; - - let opt = msg.opt(); // We need it further down ... - - if let Some(opt) = opt.as_ref() { - writeln!(target, "\n;; OPT PSEUDOSECTION:")?; - writeln!(target, - "; EDNS: version {}; flags: {}; udp: {}", - opt.version(), opt.dnssec_ok(), opt.udp_payload_size() - )?; - for option in opt.opt().iter::>() { - use AllOptData::*; - - match option { - Ok(opt) => match opt { - Nsid(nsid) => writeln!(target, "; NSID: {}", nsid)?, - Dau(dau) => writeln!(target, "; DAU: {}", dau)?, - Dhu(dhu) => writeln!(target, "; DHU: {}", dhu)?, - N3u(n3u) => writeln!(target, "; N3U: {}", n3u)?, - Expire(expire) => { - writeln!(target, "; EXPIRE: {}", expire)? - } - TcpKeepalive(opt) => { - writeln!(target, "; TCPKEEPALIVE: {}", opt)? - } - Padding(padding) => { - writeln!(target, "; PADDING: {}", padding)? - } - ClientSubnet(opt) => { - writeln!(target, "; CLIENTSUBNET: {}", opt)? - } - Cookie(cookie) => { - writeln!(target, "; COOKIE: {}", cookie)? - } - Chain(chain) => { - writeln!(target, "; CHAIN: {}", chain)? - } - KeyTag(keytag) => { - writeln!(target, "; KEYTAG: {}", keytag)? - } - ExtendedError(extendederror) => { - writeln!(target, "; EDE: {}", extendederror)? - } - Other(other) => { - writeln!(target, "; {}", other.code())?; - } - _ => writeln!(target, "Unknown OPT")?, - } - Err(err) => { - writeln!(target, "; ERROR: bad option: {}.", err)?; - } - } - } - } - - // Question - let questions = msg.question(); - if counts.qdcount() > 0 { - write!(target, ";; QUESTION SECTION:")?; - for item in questions { - match item { - Ok(item) => writeln!(target, "; {}", item)?, - Err(err) => { - writeln!(target, "; ERROR: bad question: {}.", err)?; - return Ok(()) - } - } - } - } - - /* Answer */ - let mut section = match questions.answer() { - Ok(section) => section.limit_to::>(), - Err(err) => { - writeln!(target, "; ERROR: bad question: {}.", err)?; - return Ok(()) - } - }; - if counts.ancount() > 0 { - writeln!(target, "\n;; ANSWER SECTION:")?; - for item in section.by_ref() { - match item { - Ok(item) => writeln!(target, "{}", item)?, - Err(err) => { - writeln!(target, "; Error: bad record: {}.", err)?; - return Ok(()) - } - } - } - } - - // Authority - let mut section = match section.next_section() { - Ok(section) => section.unwrap().limit_to::>(), - Err(err) => { - writeln!(target, "; ERROR: bad record: {}.", err)?; - return Ok(()) - } - }; - if counts.nscount() > 0 { - writeln!(target, "\n;; AUTHORITY SECTION:")?; - for item in section.by_ref() { - match item { - Ok(item) => writeln!(target, "{}", item)?, - Err(err) => { - writeln!(target, "; Error: bad record: {}.", err)?; - return Ok(()) - } - } - } - } - - // Additional - let section = match section.next_section() { - Ok(section) => section.unwrap().limit_to::>(), - Err(err) => { - writeln!(target, "; ERROR: bad record: {}.", err)?; - return Ok(()) - } - }; - if counts.arcount() > 1 || (opt.is_none() && counts.arcount() > 0) { - writeln!(target, "\n;; ADDITIONAL SECTION:")?; - for item in section { - match item { - Ok(item) => { - if item.rtype() != Rtype::OPT { - writeln!(target, "{}", item)? - } - } - Err(err) => { - writeln!(target, "; Error: bad record: {}.", err)?; - return Ok(()) - } - } - } - } - - // Stats - let stats = answer.stats(); - writeln!(target, - "\n;; Query time: {} msec", - stats.duration.num_milliseconds() - )?; - writeln!(target, - ";; SERVER: {}#{} ({})", - stats.server_addr.ip(), stats.server_addr.port(), - stats.server_proto - )?; - writeln!(target, - ";; WHEN: {}", - stats.start.format("%a %b %d %H:%M:%S %Z %Y") - )?; - writeln!(target, ";; MSG SIZE rcvd: {}", msg.as_slice().len())?; - - Ok(()) -} - diff --git a/src/dnsi/output/mod.rs b/src/dnsi/output/mod.rs deleted file mode 100644 index 0008da9..0000000 --- a/src/dnsi/output/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Message output formats. - -mod dig; - - -use std::io; -use clap::ValueEnum; -use super::client::Answer; - -//------------ OutputFormat -------------------------------------------------- - -#[derive(Clone, Copy, Debug, ValueEnum)] -pub enum OutputFormat { - /// Similar to dig. - Dig -} - -impl OutputFormat { - pub fn write( - self, msg: &Answer, target: &mut impl io::Write - ) -> Result<(), io::Error> { - match self { - Self::Dig => self::dig::write(msg, target) - } - } - - pub fn print( - self, msg: &Answer, - ) -> Result<(), io::Error> { - self.write(msg, &mut io::stdout().lock()) - } -} - diff --git a/src/lib.rs b/src/lib.rs index 4b61fd9..8f60218 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ //! The actual implementation of the tools. -pub mod dnsi;