diff --git a/Cargo.toml b/Cargo.toml index 42c3104..82802b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dqy" +name = "dnslib" edition = "2021" version = "0.5.2" #:version authors = ["Alain Viguier "] @@ -14,6 +14,10 @@ categories = ["command-line-utilities"] license = "MIT" rust-version = "1.82.0" +[lib] +name = "dnslib" +path = "src/lib.rs" + [dependencies] base16 = "0.2.1" base64 = "0.21.5" @@ -63,3 +67,7 @@ unnecessary_cast = "allow" [[bin]] name = "certgen" path = "src/certgen.rs" + +# [[bin]] +# name = "dqy" +# path = "src/main.rs" diff --git a/src/args.rs b/src/args.rs index 63685e4..cc4ffeb 100644 --- a/src/args.rs +++ b/src/args.rs @@ -14,13 +14,14 @@ use log::trace; use rustc_version_runtime::version; use simplelog::*; +use dnslib::dns::rfc::domain::DomainName; +use dnslib::dns::rfc::{flags::BitFlags, qclass::QClass, qtype::QType}; +use dnslib::error::Error; +use dnslib::transport::network::{IPVersion, Protocol}; +use dnslib::transport::{endpoint::EndPoint, TransportOptions}; + use crate::cli_options::{DnsProtocolOptions, EdnsOptions}; -use crate::dns::rfc::domain::DomainName; -use crate::dns::rfc::{flags::BitFlags, qclass::QClass, qtype::QType}; -use crate::error::Error; use crate::show::{DisplayOptions, DumpOptions}; -use crate::transport::network::{IPVersion, Protocol}; -use crate::transport::{endpoint::EndPoint, TransportOptions}; // value of the environment variable for flags if any const ENV_FLAGS: &str = "DQY_FLAGS"; @@ -60,7 +61,7 @@ pub struct CliOptions { } impl FromStr for CliOptions { - type Err = crate::error::Error; + type Err = dnslib::error::Error; fn from_str(s: &str) -> std::result::Result { let args: Vec<_> = s.split_ascii_whitespace().map(|a| a.to_string()).collect(); @@ -80,7 +81,7 @@ impl CliOptions { } } - pub fn options(args: &[String]) -> crate::error::Result { + pub fn options(args: &[String]) -> dnslib::error::Result { // save all cli options into a structure let mut options = CliOptions::default(); @@ -1012,7 +1013,7 @@ fn validate_qtypes(s: &str) -> std::result::Result { } // Initialize write logger: either create it or use it -fn init_write_logger(logfile: &PathBuf, level: log::LevelFilter) -> crate::error::Result<()> { +fn init_write_logger(logfile: &PathBuf, level: log::LevelFilter) -> dnslib::error::Result<()> { if level == log::LevelFilter::Off { return Ok(()); } @@ -1039,7 +1040,7 @@ fn init_write_logger(logfile: &PathBuf, level: log::LevelFilter) -> crate::error } // Initialize terminal logger -fn init_term_logger(level: log::LevelFilter) -> crate::error::Result<()> { +fn init_term_logger(level: log::LevelFilter) -> dnslib::error::Result<()> { if level == log::LevelFilter::Off { return Ok(()); } @@ -1051,7 +1052,7 @@ fn init_term_logger(level: log::LevelFilter) -> crate::error::Result<()> { #[cfg(test)] mod tests { use super::*; - use crate::dns::rfc::domain::ROOT; + use dnslib::dns::rfc::domain::ROOT; #[test] fn _split_args() { diff --git a/src/cli_options.rs b/src/cli_options.rs index e78cc8a..bbc4341 100644 --- a/src/cli_options.rs +++ b/src/cli_options.rs @@ -3,11 +3,10 @@ use std::net::SocketAddr; use log::trace; -use crate::args::CliOptions; -use crate::dns::rfc::domain::ROOT; -use crate::dns::rfc::opt::cookie::COOKIE; -use crate::dns::rfc::opt::zoneversion::ZONEVERSION; -use crate::dns::rfc::{ +use dnslib::dns::rfc::domain::ROOT; +use dnslib::dns::rfc::opt::cookie::COOKIE; +use dnslib::dns::rfc::opt::zoneversion::ZONEVERSION; +use dnslib::dns::rfc::{ domain::{DomainName, ROOT_DOMAIN}, opt::{ //dau_dhu_n3u::{EdnsKeyTag, DAU, DHU, N3U}, @@ -21,6 +20,8 @@ use crate::dns::rfc::{ resource_record::OPT, }; +use crate::args::CliOptions; + // DNSSEC OK const DNSSEC_FLAG: u16 = 0x8000; diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..a8d9f95 --- /dev/null +++ b/src/display.rs @@ -0,0 +1,40 @@ +// titles when displaying headers: build a map giving for each title its colored version +use colored::*; +use std::collections::HashMap; +use std::sync::LazyLock; + +type ColoredTitles = HashMap; + +pub static TITLES: LazyLock = LazyLock::new(|| { + const COLOR: Color = Color::BrightCyan; + + // local helper + fn insert_title(h: &mut ColoredTitles, title: &str, color: Color) { + h.insert(title.to_string(), title.color(color)); + } + + // init new hmap + let mut h = HashMap::new(); + + // add all titles + insert_title(&mut h, "qname", COLOR); + insert_title(&mut h, "qtype", COLOR); + insert_title(&mut h, "qclass", COLOR); + insert_title(&mut h, "name", COLOR); + insert_title(&mut h, "type", COLOR); + insert_title(&mut h, "payload", COLOR); + insert_title(&mut h, "rcode", COLOR); + insert_title(&mut h, "version", COLOR); + insert_title(&mut h, "flags", COLOR); + + h +}); + +pub fn header_section(text: &str, length: Option) -> ColoredString { + let s = if let Some(l) = length { + format!("{:) { - // print out Query if requested - if display_options.show_question { - self.query.show(display_options, length); - } - - self.response.show(display_options, length); - } -} - //─────────────────────────────────────────────────────────────────────────────────── // convenient struct for holding all messages //─────────────────────────────────────────────────────────────────────────────────── @@ -118,65 +107,3 @@ impl fmt::Display for MessageList { Ok(()) } } - -impl ShowAll for MessageList { - fn show_all(&self, display_options: &mut DisplayOptions, info: QueryInfo) { - //─────────────────────────────────────────────────────────────────────────────────── - // JSON - //─────────────────────────────────────────────────────────────────────────────────── - if display_options.json_pretty { - let j = serde_json::json!({ - "messages": self, - "info": info - }); - println!("{}", serde_json::to_string_pretty(&j).unwrap()); - return; - } - - //─────────────────────────────────────────────────────────────────────────────────── - // JSON pretty - //─────────────────────────────────────────────────────────────────────────────────── - if display_options.json { - let j = serde_json::json!({ - "messages": self, - "info": info - }); - println!("{}", serde_json::to_string(&j).unwrap()); - return; - } - - //─────────────────────────────────────────────────────────────────────────────────── - // fancy print out when only one message - //─────────────────────────────────────────────────────────────────────────────────── - if self.len() == 1 { - // we only have 1 message - let msg = &self[0]; - let resp = msg.response(); - - // when we only have one message, we print out a dig-like info - display_options.sho_resp_header = true; - display_options.show_headers = true; - display_options.show_all = true; - - resp.show(display_options, None); - - // print out stats - println!("{}", header_section("STATS", None)); - println!("{}", info); - } - //─────────────────────────────────────────────────────────────────────────────────── - // when several messages, just print out the ANSWER - //─────────────────────────────────────────────────────────────────────────────────── - else { - let max_length = self.max_length(); - - for msg in self.iter() { - msg.show(display_options, max_length); - } - - if display_options.stats { - println!("{}", info); - } - } - } -} diff --git a/src/dns/rfc/a.rs b/src/dns/rfc/a.rs index ddbf990..7156bb1 100644 --- a/src/dns/rfc/a.rs +++ b/src/dns/rfc/a.rs @@ -8,7 +8,7 @@ use serde::Serialize; // A resource record #[allow(clippy::upper_case_acronyms)] #[derive(Debug, PartialEq, FromNetwork, Serialize)] -pub(super) struct A(pub Ipv4Addr); +pub struct A(pub Ipv4Addr); impl Default for A { fn default() -> Self { diff --git a/src/dns/rfc/afsdb.rs b/src/dns/rfc/afsdb.rs index 6937206..eeeb2a9 100644 --- a/src/dns/rfc/afsdb.rs +++ b/src/dns/rfc/afsdb.rs @@ -8,7 +8,7 @@ use super::domain::DomainName; #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork, Serialize)] -pub(super) struct AFSDB { +pub struct AFSDB { subtype: u16, hostname: DomainName, } diff --git a/src/dns/rfc/apl.rs b/src/dns/rfc/apl.rs index fb4cf40..6e63f0b 100644 --- a/src/dns/rfc/apl.rs +++ b/src/dns/rfc/apl.rs @@ -82,7 +82,7 @@ impl Serialize for InnerAPL { //─────────────────────────────────────────────────────────────────────────────────── #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, Serialize)] -pub(super) struct APL { +pub struct APL { #[serde(skip_serializing)] rd_length: u16, apl: Vec, diff --git a/src/dns/rfc/caa.rs b/src/dns/rfc/caa.rs index a3a0aef..01d22a7 100644 --- a/src/dns/rfc/caa.rs +++ b/src/dns/rfc/caa.rs @@ -20,7 +20,7 @@ use crate::{dns::buffer::Buffer, new_rd_length}; // +----------------+----------------+.....+----------------+ #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork, Serialize)] -pub(super) struct CAA { +pub struct CAA { // transmistted through RR deserialization #[serde(skip_serializing)] #[from_network(ignore)] diff --git a/src/dns/rfc/char_string.rs b/src/dns/rfc/char_string.rs index bebbc8c..f9b8899 100644 --- a/src/dns/rfc/char_string.rs +++ b/src/dns/rfc/char_string.rs @@ -20,6 +20,11 @@ impl CharacterString { pub fn len(&self) -> u8 { self.length } + + #[inline] + pub fn is_empty(&self) -> bool { + self.length == 0 + } } impl DataLength for CharacterString { diff --git a/src/dns/rfc/dnskey.rs b/src/dns/rfc/dnskey.rs index b8480bf..70845b2 100644 --- a/src/dns/rfc/dnskey.rs +++ b/src/dns/rfc/dnskey.rs @@ -20,7 +20,7 @@ use super::algorithm::DNSSECAlgorithmTypes; #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork)] #[from_network(TryFrom)] -pub(super) struct DNSKEY { +pub struct DNSKEY { #[from_network(ignore)] rd_length: u16, diff --git a/src/dns/rfc/domain.rs b/src/dns/rfc/domain.rs index 899ccf2..785d364 100644 --- a/src/dns/rfc/domain.rs +++ b/src/dns/rfc/domain.rs @@ -3,14 +3,12 @@ use std::io::{Cursor, Result}; use std::ops::Deref; use std::slice::Iter; -use colored::Colorize; use log::trace; use serde::{Serialize, Serializer}; use type2network::{FromNetworkOrder, ToNetworkOrder}; use type2network_derive::ToNetwork; use crate::error::{self, Dns, Error}; -use crate::show::ToColor; pub const ROOT_DOMAIN: DomainName = DomainName { labels: vec![] }; pub const ROOT: &str = "."; @@ -29,7 +27,7 @@ impl Label { self.0.len() } - pub fn size(&self) -> usize { + pub fn _size(&self) -> usize { self.0.len() + 1 } @@ -292,13 +290,6 @@ impl fmt::Debug for DomainName { } } -impl ToColor for DomainName { - fn to_color(&self) -> colored::ColoredString { - self.to_string().bright_green() - // self.to_string().truecolor(NAME_COLOR.0, NAME_COLOR.1, NAME_COLOR.2) - } -} - // Convert from a ref impl<'a> TryFrom<&'a DomainName> for DomainName { type Error = Error; diff --git a/src/dns/rfc/eui48.rs b/src/dns/rfc/eui48.rs index dd4a40a..b0ce84f 100644 --- a/src/dns/rfc/eui48.rs +++ b/src/dns/rfc/eui48.rs @@ -11,7 +11,7 @@ use type2network_derive::FromNetwork; // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ #[derive(Debug, Default, FromNetwork)] -pub(super) struct EUI48([u8; 6]); +pub struct EUI48([u8; 6]); impl fmt::Display for EUI48 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/dns/rfc/eui64.rs b/src/dns/rfc/eui64.rs index 3f96927..559a1fe 100644 --- a/src/dns/rfc/eui64.rs +++ b/src/dns/rfc/eui64.rs @@ -11,7 +11,7 @@ use type2network_derive::FromNetwork; // | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ #[derive(Debug, Default, FromNetwork)] -pub(super) struct EUI64(u64); +pub struct EUI64(u64); impl fmt::Display for EUI64 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/dns/rfc/flags.rs b/src/dns/rfc/flags.rs index adb5740..cd124f8 100644 --- a/src/dns/rfc/flags.rs +++ b/src/dns/rfc/flags.rs @@ -15,7 +15,7 @@ use crate::error::{Dns, Error}; // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // |QR| Opcode |AA|TC|RD|RA|Z |AD|CD| RCODE | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ -#[derive(Debug, Default, PartialEq, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Serialize)] pub struct Flags { pub(super) qr: PacketType, // A one bit field that specifies whether this message is a query (0), or a response (1). pub(super) op_code: OpCode, // A four bit field that specifies kind of query in this @@ -48,6 +48,12 @@ pub struct Flags { //6-15 Reserved for future use. } +impl Flags { + pub fn set_response_code(&mut self, rc: ResponseCode) { + self.response_code = rc; + } +} + #[derive(Debug, PartialEq, Clone, Serialize)] pub struct BitFlags { pub authorative_answer: bool, // Authoritative Answer - this bit is valid in responses, diff --git a/src/dns/rfc/header.rs b/src/dns/rfc/header.rs index 3b76662..d69e208 100644 --- a/src/dns/rfc/header.rs +++ b/src/dns/rfc/header.rs @@ -7,7 +7,7 @@ use type2network_derive::{FromNetwork, ToNetwork}; use rand::Rng; use serde::Serialize; -use super::{flags::Flags, opcode::OpCode, packet_type::PacketType}; +use super::{flags::Flags, opcode::OpCode, packet_type::PacketType, response_code::ResponseCode}; // 1 1 1 1 1 1 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 @@ -24,7 +24,7 @@ use super::{flags::Flags, opcode::OpCode, packet_type::PacketType}; // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // | ARCOUNT | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ -#[derive(Debug, ToNetwork, FromNetwork, Serialize)] +#[derive(Debug, Clone, ToNetwork, FromNetwork, Serialize)] pub struct Header { pub id: u16, // A 16 bit identifier assigned by the program that // generates any kind of query. This identifier is copied @@ -46,6 +46,10 @@ impl Header { pub fn set_id(&mut self, id: u16) { self.id = id; } + + pub fn set_response_code(&mut self, rc: ResponseCode) { + self.flags.set_response_code(rc); + } } impl Default for Header { diff --git a/src/dns/rfc/kx.rs b/src/dns/rfc/kx.rs index 4e8ba89..b3853da 100644 --- a/src/dns/rfc/kx.rs +++ b/src/dns/rfc/kx.rs @@ -14,7 +14,7 @@ use super::domain::DomainName; // / / // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ #[derive(Debug, Default, FromNetwork, Serialize)] -pub(super) struct KX { +pub struct KX { preference: u16, exchanger: DomainName, } diff --git a/src/dns/rfc/naptr.rs b/src/dns/rfc/naptr.rs index 72f593f..d00299e 100644 --- a/src/dns/rfc/naptr.rs +++ b/src/dns/rfc/naptr.rs @@ -25,7 +25,7 @@ use super::{char_string::CharacterString, domain::DomainName}; // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork, Serialize)] -pub(super) struct NAPTR { +pub struct NAPTR { order: u16, preference: u16, flags: CharacterString, diff --git a/src/dns/rfc/nsec.rs b/src/dns/rfc/nsec.rs index 144c92b..ce8eb6f 100644 --- a/src/dns/rfc/nsec.rs +++ b/src/dns/rfc/nsec.rs @@ -19,7 +19,7 @@ use super::{domain::DomainName, type_bitmaps::TypeBitMaps}; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork, Serialize)] -pub(super) struct NSEC { +pub struct NSEC { // transmistted through RR deserialization #[from_network(ignore)] rd_length: u16, diff --git a/src/dns/rfc/nsec3.rs b/src/dns/rfc/nsec3.rs index 3495a87..b66598e 100644 --- a/src/dns/rfc/nsec3.rs +++ b/src/dns/rfc/nsec3.rs @@ -90,7 +90,7 @@ mod tests { resp.deserialize_from(&mut resp_buffer) .map_err(|_| Error::Dns(Dns::CantDeserialize))?; - let answer = resp.authority.unwrap(); + let answer = resp.authority().as_ref().unwrap(); for (i, a) in answer.iter().enumerate() { match i { diff --git a/src/dns/rfc/openpgpkey.rs b/src/dns/rfc/openpgpkey.rs index b81a585..b1ee2bd 100644 --- a/src/dns/rfc/openpgpkey.rs +++ b/src/dns/rfc/openpgpkey.rs @@ -10,7 +10,7 @@ use crate::{dns::buffer::Buffer, new_rd_length}; //------------------------------------------------------------------------------------- #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork)] -pub(super) struct OPENPGPKEY { +pub struct OPENPGPKEY { // transmistted through RR deserialization #[from_network(ignore)] rd_length: u16, diff --git a/src/dns/rfc/qtype.rs b/src/dns/rfc/qtype.rs index 47e07e4..fbe889e 100644 --- a/src/dns/rfc/qtype.rs +++ b/src/dns/rfc/qtype.rs @@ -1,12 +1,9 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use colored::Colorize; use enum_from::{EnumDisplay, EnumFromStr, EnumTryFrom}; use serde::Serialize; use type2network::{FromNetworkOrder, ToNetworkOrder}; use type2network_derive::{FromNetwork, ToNetwork}; -use crate::show::ToColor; - #[allow(clippy::unnecessary_cast)] // https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.2 #[derive( @@ -115,12 +112,6 @@ pub enum QType { TYPE(u16), } -impl ToColor for QType { - fn to_color(&self) -> colored::ColoredString { - self.to_string().bright_blue() - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/dns/rfc/query.rs b/src/dns/rfc/query.rs index 9009af6..7e0ae2b 100644 --- a/src/dns/rfc/query.rs +++ b/src/dns/rfc/query.rs @@ -1,6 +1,6 @@ use std::fmt; use std::fs::File; -use std::io::Write; +use std::io::{Cursor, Write}; use std::path::PathBuf; use colored::Colorize; @@ -8,11 +8,11 @@ use log::{debug, trace}; use serde::Serialize; use tokio::io::AsyncWriteExt; -use type2network::ToNetworkOrder; -use type2network_derive::ToNetwork; +use type2network::{FromNetworkOrder, ToNetworkOrder}; +use type2network_derive::{FromNetwork, ToNetwork}; use crate::error::{Dns, Error, Result}; -use crate::show::{header_section, DisplayOptions, Show}; +use crate::header_section; use crate::transport::network::Messenger; use super::{ @@ -22,16 +22,18 @@ use super::{ const DEFAULT_BUFSIZE: u16 = 4096; +#[non_exhaustive] #[derive(Debug, ToNetwork, Serialize)] pub enum MetaRR { OPT(OPT), } +#[allow(unreachable_patterns)] impl fmt::Display for MetaRR { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { MetaRR::OPT(opt) => write!(f, "{}", opt), - //_ => unimplemented!("Meta RR other than OPT not implemented"), + _ => unimplemented!("Meta RR other than OPT not implemented"), } } } @@ -44,7 +46,20 @@ impl Default for MetaRR { } } -#[derive(Debug, Default, ToNetwork, Serialize)] +#[allow(unreachable_patterns)] +impl<'a> FromNetworkOrder<'a> for MetaRR { + fn deserialize_from(&mut self, buffer: &mut Cursor<&'a [u8]>) -> std::io::Result<()> { + match self { + MetaRR::OPT(opt) => opt.deserialize_from(buffer)?, + _ => unimplemented!("Meta RR other than OPT not implemented"), + } + + // if a pointer, get pointer value and call + Ok(()) + } +} + +#[derive(Debug, Default, ToNetwork, FromNetwork, Serialize)] pub struct Query { #[serde(skip_serializing)] pub length: Option, // length in case of TCP/TLS transport (https://datatracker.ietf.org/doc/html/rfc1035#section-4.2.2) @@ -102,7 +117,7 @@ impl Query { // Send the query through the wire pub fn send(&mut self, trp: &mut T, save_path: &Option) -> Result { - // convert to network bytes + // convert to network Querybytes let mut buffer: Vec = Vec::new(); let message_size = self .serialize_to(&mut buffer) @@ -199,15 +214,6 @@ impl fmt::Display for Query { } } -impl Show for Query { - fn show(&self, display_options: &DisplayOptions, _length: Option) { - // print out Query if requested - if display_options.show_question { - println!("{}", self); - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/dns/rfc/question.rs b/src/dns/rfc/question.rs index 476099a..b200847 100644 --- a/src/dns/rfc/question.rs +++ b/src/dns/rfc/question.rs @@ -5,10 +5,8 @@ use type2network_derive::{FromNetwork, ToNetwork}; use serde::Serialize; -use crate::{ - dns::rfc::{domain::DomainName, qclass::QClass, qtype::QType}, - show::TITLES, -}; +use crate::dns::rfc::{domain::DomainName, qclass::QClass, qtype::QType}; +use crate::TITLES; // Question structure: https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.2 // 1 1 1 1 1 1 @@ -22,7 +20,7 @@ use crate::{ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ // | QCLASS | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ -#[derive(Debug, Default, PartialEq, ToNetwork, FromNetwork, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, ToNetwork, FromNetwork, Serialize)] pub struct Question { pub qname: DomainName, pub qtype: QType, diff --git a/src/dns/rfc/rdata.rs b/src/dns/rfc/rdata.rs index fc2d283..5f3eadb 100644 --- a/src/dns/rfc/rdata.rs +++ b/src/dns/rfc/rdata.rs @@ -3,14 +3,13 @@ use std::{ io::Cursor, }; -use colored::{ColoredString, Colorize}; use log::trace; use serde::Serialize; use type2network::ToNetworkOrder; use type2network::FromNetworkOrder; -use crate::{dns::buffer::Buffer, show::ToColor}; +use crate::dns::buffer::Buffer; use super::{ a::A, @@ -59,7 +58,7 @@ use super::{ #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Serialize)] #[serde(tag = "type", content = "rdata")] -pub(super) enum RData { +pub enum RData { // RData definitions A(A), AAAA(AAAA), @@ -295,9 +294,3 @@ impl fmt::Display for RData { } } } - -impl ToColor for RData { - fn to_color(&self) -> ColoredString { - self.to_string().bright_yellow() - } -} diff --git a/src/dns/rfc/resource_record.rs b/src/dns/rfc/resource_record.rs index a1d09b6..0347c6b 100644 --- a/src/dns/rfc/resource_record.rs +++ b/src/dns/rfc/resource_record.rs @@ -1,19 +1,25 @@ use std::{fmt, io::Cursor, net::IpAddr}; -use colored::Colorize; use serde::Serialize; use type2network::{FromNetworkOrder, ToNetworkOrder}; use type2network_derive::{FromNetwork, ToNetwork}; use super::domain::ROOT_DOMAIN; use super::opt::OptionDataValue; -// use super::opt::opt_rr::OPT; use super::{domain::DomainName, qclass::QClass, qtype::QType, rdata::RData}; use crate::dns::rfc::opt::opt_rr::{OptOption, OptionList}; -use crate::show::{DisplayOptions, ToColor, TITLES}; use log::{debug, trace}; +macro_rules! getter { + ($fname:ident, $type:ty) => { + #[inline] + pub fn $fname(&self) -> $type { + self.$fname + } + }; +} + // 4.1.3. Resource record format // The answer, authority, and additional sections all share the same @@ -60,6 +66,11 @@ pub struct RegularClassTtl { pub(super) ttl: u32, } +impl RegularClassTtl { + getter!(class, QClass); + getter!(ttl, u32); +} + // Case of OPT RR // https://www.rfc-editor.org/rfc/rfc6891#section-6.1.3 // +0 (MSB) +1 (LSB) @@ -70,10 +81,17 @@ pub struct RegularClassTtl { // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ #[derive(Debug, Default, PartialEq, ToNetwork, FromNetwork)] pub struct OptPayload { - pub(super) payload: u16, - pub(super) extended_rcode: u8, - pub(super) version: u8, - pub(super) flags: u16, + payload: u16, + extended_rcode: u8, + version: u8, + flags: u16, +} + +impl OptPayload { + getter!(payload, u16); + getter!(extended_rcode, u8); + getter!(version, u8); + getter!(flags, u16); } // CLASS & TTL vary if RR is OPT or not @@ -122,18 +140,7 @@ impl fmt::Debug for OptOrClassTtl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { OptOrClassTtl::Regular(x) => write!(f, "{:<10} {:<10}", x.class.to_string(), x.ttl), - OptOrClassTtl::Opt(x) => write!( - f, - "{}:{} {}:{} {}:{} {}:{}", - TITLES["payload"], - x.payload, - TITLES["rcode"], - x.extended_rcode, - TITLES["version"], - x.version, - TITLES["flags"], - x.flags - ), + OptOrClassTtl::Opt(x) => write!(f, "{} {} {} {}", x.payload, x.extended_rcode, x.version, x.flags), } } } @@ -165,7 +172,7 @@ impl Serialize for OptOrClassTtl { } // a new type definition for printing out TTL as days, hours, minutes and seconds -struct Ttl(u32); +pub struct Ttl(u32); impl fmt::Display for Ttl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -192,9 +199,9 @@ impl fmt::Display for Ttl { } } -impl ToColor for Ttl { - fn to_color(&self) -> colored::ColoredString { - self.to_string().bright_red() +impl From for Ttl { + fn from(v: u32) -> Self { + Self(v) } } @@ -215,30 +222,23 @@ pub struct ResourceRecord { // cached. For example, SOA records are always distributed // with a zero TTL to prohibit caching. Zero values can // also be used for extremely volatile data. - pub(super) rd_length: u16, // an unsigned 16 bit integer that specifies the length in octets of the RDATA field. + rd_length: u16, // an unsigned 16 bit integer that specifies the length in octets of the RDATA field. #[serde(flatten)] - pub(super) r_data: RData, + pub r_data: RData, // a variable length string of octets that describes the // resource. The format of this information varies // according to the TYPE and CLASS of the resource record. } -// standard lengths for displaying and aligning a RR -const NAME_DISPLAY_LENGTH: usize = 28; -const TYPE_DISPLAY_LENGTH: usize = 10; -const LENGTH_DISPLAY_LENGTH: usize = 5; -const CLASS_DISPLAY_LENGTH: usize = 4; -const TTL_INT_DISPLAY_LENGTH: usize = 7; -const TTL_STRING_DISPLAY_LENGTH: usize = 12; -const PAYLOAD_DISPLAY_LENGTH: usize = 5; -const EXTCODE_DISPLAY_LENGTH: usize = 5; -const VERSION_DISPLAY_LENGTH: usize = 5; -const FLAGS_DISPLAY_LENGTH: usize = 5; - // don't use Show trait to provide extra length used to align output // use this function impl ResourceRecord { + #[inline] + pub fn rd_length(&self) -> u16 { + self.rd_length + } + // return the domain name when rr is NS pub fn ns_name(&self) -> Option { if self.r#type == QType::NS { @@ -266,106 +266,11 @@ impl ResourceRecord { } None } - - fn display(&self, fmt: &str, raw_ttl: bool, name_length: usize, puny: bool) { - for f in fmt.split(",") { - match f.trim() { - // except OPT - "name" => { - // print punycodes - if puny { - print!("{: print!("{: print!("{: { - if let Some(r) = self.opt_or_class_ttl.regular() { - print!("{: { - if let Some(r) = self.opt_or_class_ttl.regular() { - if raw_ttl { - print!("{: print!("{}", self.r_data.to_color()), - - // OPT specific data - "payload" => { - if let Some(r) = self.opt_or_class_ttl.opt() { - print!("{: { - if let Some(r) = self.opt_or_class_ttl.opt() { - print!("{: { - if let Some(r) = self.opt_or_class_ttl.opt() { - print!("EDNS{: { - if let Some(r) = self.opt_or_class_ttl.opt() { - print!("{: (), - } - } - } - - pub(super) fn show(&self, display_options: &DisplayOptions, length: Option) { - let name_length = length.unwrap_or(NAME_DISPLAY_LENGTH); - - // formatting display - if !display_options.fmt.is_empty() { - self.display( - &display_options.fmt, - display_options.raw_ttl, - name_length, - display_options.puny, - ); - println!(); - return; - } - - // other options - if display_options.short { - println!("{}", self.r_data.to_color()); - } else if self.r#type != QType::OPT { - const ALL_FIELDS: &str = "name,type,class,ttl,length,rdata"; - self.display(ALL_FIELDS, display_options.raw_ttl, name_length, display_options.puny); - println!(); - } else { - const ALL_FIELDS: &str = "name,type,length,payload,extcode,version,flags,length,rdata"; - self.display(ALL_FIELDS, display_options.raw_ttl, name_length, display_options.puny); - println!(); - } - } } //─────────────────────────────────────opt_or_class_ttl────────────────────────────────────────────── // OPT is a special case of RR -//─────────────────────────────────────────────────────────────────────────────────── +//─────────────────────────────────────────────────────────────────────────────────────────────────── pub type OPT = ResourceRecord; impl OPT { @@ -408,11 +313,7 @@ impl OPT { impl fmt::Debug for OPT { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}:{} {}:{} {:?}", - TITLES["name"], self.name, TITLES["type"], self.r#type, self.opt_or_class_ttl - )?; + write!(f, "{} {} {:?}", self.name, self.r#type, self.opt_or_class_ttl)?; Ok(()) } } diff --git a/src/dns/rfc/response.rs b/src/dns/rfc/response.rs index ec3f1d2..7ba7eb7 100644 --- a/src/dns/rfc/response.rs +++ b/src/dns/rfc/response.rs @@ -7,15 +7,16 @@ use log::{debug, trace}; use serde::Serialize; use tokio::io::AsyncWriteExt; -use type2network::FromNetworkOrder; +use type2network::{FromNetworkOrder, ToNetworkOrder}; +use type2network_derive::ToNetwork; +use super::query::Query; use super::{ domain::DomainName, header::Header, qtype::QType, question::Question, resource_record::ResourceRecord, rrlist::RRList, }; use crate::dns::rfc::response_code::ResponseCode; use crate::error::{Dns, Error}; -use crate::show::{header_section, DisplayOptions, Show}; use crate::transport::network::Messenger; pub enum ResponseSection { @@ -24,17 +25,27 @@ pub enum ResponseSection { Additional, } -#[derive(Debug, Default, Serialize)] +#[derive(Debug, Default, Serialize, ToNetwork)] pub struct Response { pub header: Header, pub question: Question, pub answer: Option, - pub(super) authority: Option, - pub(super) additional: Option, + authority: Option, + additional: Option, } // hide internal fields impl Response { + #[inline] + pub fn authority(&self) -> &Option { + &self.authority + } + + #[inline] + pub fn additional(&self) -> &Option { + &self.additional + } + #[inline] pub fn rcode(&self) -> ResponseCode { self.header.flags.response_code @@ -60,6 +71,11 @@ impl Response { self.header.flags.bitflags.authorative_answer } + #[inline] + pub fn set_response_code(&mut self, rc: ResponseCode) { + self.header.set_response_code(rc); + } + // referral response means no answer #[inline] pub fn is_referral(&self) -> bool { @@ -223,6 +239,21 @@ impl Response { } } +impl From<&Query> for Response { + fn from(q: &Query) -> Self { + // copy header and question from query + let mut r = Response { + header: q.header.clone(), + question: q.question.clone(), + ..Default::default() + }; + + r.header.ar_count = 0; + + r + } +} + impl fmt::Display for Response { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // print out anwser, authority, additional if any @@ -278,56 +309,6 @@ impl<'a> FromNetworkOrder<'a> for Response { } } -impl Show for Response { - fn show(&self, display_options: &DisplayOptions, max_length: Option) { - // const HEADER_LENGTH: usize = 80; - - //─────────────────────────────────────────────────────────────────────────────────── - // Response HEADER - //─────────────────────────────────────────────────────────────────────────────────── - if display_options.sho_resp_header { - println!("{}", header_section("Response HEADER", None)); - println!("{}\n", self.header); - } - - //─────────────────────────────────────────────────────────────────────────────────── - // ANSWER - //─────────────────────────────────────────────────────────────────────────────────── - if self.header.an_count > 0 { - debug_assert!(self.answer.is_some()); - - if display_options.show_headers { - println!("{}", header_section("ANSWER", None)); - } - self.answer.as_ref().unwrap().show(display_options, max_length); - } - - //─────────────────────────────────────────────────────────────────────────────────── - // AUTHORATIVE - //─────────────────────────────────────────────────────────────────────────────────── - if self.header.ns_count > 0 && display_options.show_all { - debug_assert!(self.authority.is_some()); - - if display_options.show_headers { - println!("\n{}", header_section("AUTHORATIVE", None)); - } - self.authority.as_ref().unwrap().show(display_options, max_length); - } - - //─────────────────────────────────────────────────────────────────────────────────── - // ADDITIONAL - //─────────────────────────────────────────────────────────────────────────────────── - if self.header.ar_count > 0 && display_options.show_all { - debug_assert!(self.additional.is_some()); - - if display_options.show_headers { - println!("\n{}", header_section("ADDITIONAL", None)); - } - self.additional.as_ref().unwrap().show(display_options, max_length); - } - } -} - #[cfg(test)] mod tests { @@ -379,7 +360,7 @@ mod tests { assert_eq!(answer.r#type, QType::A); assert!(matches!(&answer.opt_or_class_ttl, OptOrClassTtl::Regular(x) if x.class == QClass::IN)); assert!(matches!(&answer.opt_or_class_ttl, OptOrClassTtl::Regular(x) if x.ttl == 119)); - assert_eq!(answer.rd_length, 4); + assert_eq!(answer.rd_length(), 4); // assert!( // matches!(answer.r_data, RData::A(A(addr)) if Ipv4Addr::from(addr) == Ipv4Addr::new(172,217,18,36)) @@ -425,9 +406,9 @@ mod tests { // //assert_eq!(ans.ttl.as_ref(), Left(&172800)); // } - assert_eq!(answer[0].rd_length, 14); + assert_eq!(answer[0].rd_length(), 14); for i in 1..8 { - assert_eq!(answer[i].rd_length, 4); + assert_eq!(answer[i].rd_length(), 4); } assert!(matches!(&answer[0].r_data, RData::NS(ns) if ns.to_string() == "c.hkirc.net.hk.")); @@ -447,8 +428,8 @@ mod tests { assert_eq!(format!("{}", add.name), "."); assert_eq!(add.r#type, QType::OPT); - assert!(matches!(&add.opt_or_class_ttl, OptOrClassTtl::Opt(x) if x.payload == 1232)); - assert_eq!(add.rd_length, 0); + assert!(matches!(&add.opt_or_class_ttl, OptOrClassTtl::Opt(x) if x.payload() == 1232)); + assert_eq!(add.rd_length(), 0); Ok(()) } diff --git a/src/dns/rfc/rp.rs b/src/dns/rfc/rp.rs index 949ff51..42c2396 100644 --- a/src/dns/rfc/rp.rs +++ b/src/dns/rfc/rp.rs @@ -8,7 +8,7 @@ use serde::Serialize; use super::domain::DomainName; #[derive(Debug, Default, FromNetwork, Serialize)] -pub(super) struct RP { +pub struct RP { mbox: DomainName, hostname: DomainName, } diff --git a/src/dns/rfc/rrlist.rs b/src/dns/rfc/rrlist.rs index a0227e1..1fbcebf 100644 --- a/src/dns/rfc/rrlist.rs +++ b/src/dns/rfc/rrlist.rs @@ -6,13 +6,13 @@ use std::{fmt, net::IpAddr, ops::Deref}; use rand::seq::IteratorRandom; use serde::Serialize; -use type2network::FromNetworkOrder; -use type2network_derive::FromNetwork; +use type2network::{FromNetworkOrder, ToNetworkOrder}; +use type2network_derive::{FromNetwork, ToNetwork}; use super::{domain::DomainName, qtype::QType, resource_record::ResourceRecord}; -use crate::show::{DisplayOptions, Show}; +// use crate::show::{DisplayOptions, Show}; -#[derive(Debug, Default, FromNetwork, Serialize)] +#[derive(Debug, Default, FromNetwork, ToNetwork, Serialize)] pub struct RRList(Vec); impl RRList { @@ -78,26 +78,6 @@ impl fmt::Display for RRList { } } -impl Show for RRList { - fn show(&self, display_options: &DisplayOptions, _: Option) { - let max_length = if display_options.align_names { - self.max_length() - } else { - None - }; - - for rr in &self.0 { - // don't display OPT if not requested - // if rr.r#type == QType::OPT && !display_options.show_opt { - // continue; - // } else { - // rr.show(display_options, max_length); - // } - rr.show(display_options, max_length); - } - } -} - #[cfg(test)] mod tests { @@ -122,12 +102,12 @@ mod tests { // no anwser is response => this is a referral assert!(resp.is_referral()); - assert!(resp.authority.is_some()); - let auth = resp.authority.unwrap(); + assert!(resp.authority().is_some()); + let auth = resp.authority().as_ref().unwrap(); assert_eq!(auth.len(), 13); - assert!(resp.additional.is_some()); - let add = resp.additional.unwrap(); + assert!(resp.additional().is_some()); + let add = resp.additional().as_ref().unwrap(); assert_eq!(add.len(), 27); let ip = add.ip_address(&QType::A, "l.gtld-servers.net.").unwrap(); diff --git a/src/dns/rfc/tlsa.rs b/src/dns/rfc/tlsa.rs index 2527ec9..7eb05c7 100644 --- a/src/dns/rfc/tlsa.rs +++ b/src/dns/rfc/tlsa.rs @@ -16,7 +16,7 @@ use crate::{dns::buffer::Buffer, new_rd_length}; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork)] -pub(super) struct TLSA { +pub struct TLSA { #[from_network(ignore)] rd_length: u16, diff --git a/src/dns/rfc/uri.rs b/src/dns/rfc/uri.rs index 8ba9d52..1378d16 100644 --- a/src/dns/rfc/uri.rs +++ b/src/dns/rfc/uri.rs @@ -17,7 +17,7 @@ use crate::{dns::buffer::Buffer, new_rd_length}; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork)] -pub(super) struct URI { +pub struct URI { #[from_network(ignore)] rd_length: u16, diff --git a/src/dns/rfc/zonemd.rs b/src/dns/rfc/zonemd.rs index 09306a6..3bca58f 100644 --- a/src/dns/rfc/zonemd.rs +++ b/src/dns/rfc/zonemd.rs @@ -20,7 +20,7 @@ use crate::new_rd_length; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Default, FromNetwork)] -pub(super) struct ZONEMD { +pub struct ZONEMD { #[from_network(ignore)] rd_length: u16, diff --git a/src/handlebars.rs b/src/handlebars.rs index a4419fe..6dfca6e 100644 --- a/src/handlebars.rs +++ b/src/handlebars.rs @@ -2,8 +2,8 @@ use handlebars::*; use serde::Serialize; -use crate::dns::message::MessageList; use crate::QueryInfo; +use dnslib::dns::message::MessageList; // custom helper handlebars_helper!(ljust: |length: usize, x: String| format!("{:; + +pub static TITLES: LazyLock = LazyLock::new(|| { + const COLOR: Color = Color::BrightCyan; + + // local helper + fn insert_title(h: &mut ColoredTitles, title: &str, color: Color) { + h.insert(title.to_string(), title.color(color)); + } + + // init new hmap + let mut h = HashMap::new(); + + // add all titles + insert_title(&mut h, "qname", COLOR); + insert_title(&mut h, "qtype", COLOR); + insert_title(&mut h, "qclass", COLOR); + insert_title(&mut h, "name", COLOR); + insert_title(&mut h, "type", COLOR); + insert_title(&mut h, "payload", COLOR); + insert_title(&mut h, "rcode", COLOR); + insert_title(&mut h, "version", COLOR); + insert_title(&mut h, "flags", COLOR); + + h +}); + +pub fn header_section(text: &str, length: Option) -> ColoredString { + let s = if let Some(l) = length { + format!("{:( info: Option<&mut QueryInfo>, transport: &mut T, options: &CliOptions, -) -> error::Result { +) -> dnslib::error::Result { // BUFFER_SIZE is the size of the buffer used to received data let messages = DnsProtocol::sync_process_request(options, transport, BUFFER_SIZE)?; @@ -76,7 +69,7 @@ fn get_messages_using_sync_transport( //─────────────────────────────────────────────────────────────────────────────────── // send all QTypes to domain and get responses for each query. //─────────────────────────────────────────────────────────────────────────────────── -pub fn get_messages(info: Option<&mut QueryInfo>, options: &CliOptions) -> error::Result { +pub fn get_messages(info: Option<&mut QueryInfo>, options: &CliOptions) -> dnslib::error::Result { info!( "qtype={:?} domain='{}' resolver=<{}>", options.protocol.qtype, options.protocol.domain_name, options.transport.endpoint @@ -137,7 +130,7 @@ fn main() -> ExitCode { // core of processing //─────────────────────────────────────────────────────────────────────────────────── #[allow(unused_assignments)] -fn run() -> error::Result<()> { +fn run() -> dnslib::error::Result<()> { let now = Instant::now(); init_root_map(); diff --git a/src/protocol.rs b/src/protocol.rs index 99def6b..82b5053 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -2,13 +2,13 @@ use std::path::PathBuf; use log::{debug, info}; -use crate::dns::{ - message::{Message, MessageList}, - rfc::{qtype::QType, query::Query, response::Response}, +use dnslib::{ + dns::message::{Message, MessageList}, + dns::rfc::{qtype::QType, query::Query, response::Response}, + transport::network::{Messenger, Protocol}, + transport::tcp::TcpProtocol, }; -use crate::error::{self}; -use crate::transport::network::{Messenger, Protocol}; -use crate::transport::tcp::TcpProtocol; + use crate::{args::CliOptions, cli_options::FromOptions}; // a unit struct with gathers all high level functions @@ -18,7 +18,7 @@ impl DnsProtocol { //─────────────────────────────────────────────────────────────────────────────────── // send the query to the resolver //─────────────────────────────────────────────────────────────────────────────────── - fn send_query(options: &CliOptions, qt: &QType, trp: &mut T) -> error::Result { + fn send_query(options: &CliOptions, qt: &QType, trp: &mut T) -> dnslib::error::Result { // it's safe to unwrap here, see from_options() for Query let mut query = Query::from_options(options, qt).unwrap(); @@ -41,7 +41,7 @@ impl DnsProtocol { //─────────────────────────────────────────────────────────────────────────────────── // send the query to the resolver, async version //─────────────────────────────────────────────────────────────────────────────────── - async fn asend_query(options: &CliOptions, qt: &QType, trp: &mut T) -> error::Result { + async fn asend_query(options: &CliOptions, qt: &QType, trp: &mut T) -> dnslib::error::Result { // it's safe to unwrap here, see from_options() for Query let mut query = Query::from_options(options, qt).unwrap(); @@ -69,7 +69,7 @@ impl DnsProtocol { trp: &mut T, buffer: &mut [u8], save_path: &Option, - ) -> crate::error::Result { + ) -> dnslib::error::Result { let mut response = Response::default(); let _ = response.recv(trp, buffer, save_path)?; @@ -84,7 +84,7 @@ impl DnsProtocol { trp: &mut T, buffer: &mut [u8], save_path: &Option, - ) -> crate::error::Result { + ) -> dnslib::error::Result { let mut response = Response::default(); let _ = response.arecv(trp, buffer, save_path).await?; @@ -98,7 +98,7 @@ impl DnsProtocol { options: &CliOptions, trp: &mut T, buffer_size: usize, - ) -> crate::error::Result { + ) -> dnslib::error::Result { // we'll have the same number of messages than the number of types to query let mut messages = Vec::with_capacity(options.protocol.qtype.len()); let mut buffer = vec![0u8; buffer_size]; @@ -137,7 +137,7 @@ impl DnsProtocol { options: &CliOptions, trp: &mut T, buffer_size: usize, - ) -> crate::error::Result { + ) -> dnslib::error::Result { // we'll have the same number of messages than the number of types to query let mut messages = Vec::with_capacity(options.protocol.qtype.len()); let mut buffer = vec![0u8; buffer_size]; diff --git a/src/show.rs b/src/show.rs index 6727050..36065de 100644 --- a/src/show.rs +++ b/src/show.rs @@ -2,9 +2,19 @@ use std::fmt; use std::fmt::Display; use std::path::PathBuf; +use colored::Colorize; +use dnslib::dns::rfc::domain::DomainName; +use dnslib::dns::rfc::query::Query; +use dnslib::dns::rfc::response::Response; +use dnslib::dns::rfc::rrlist::RRList; +use dnslib::header_section; use serde::Serialize; -use crate::transport::NetworkInfo; +use dnslib::dns::message::{Message, MessageList}; +use dnslib::dns::rfc::qtype::QType; +use dnslib::dns::rfc::rdata::RData; +use dnslib::dns::rfc::resource_record::{ResourceRecord, Ttl}; +use dnslib::transport::NetworkInfo; //─────────────────────────────────────────────────────────────────────────────────── // Gather some information which might be useful for the user @@ -109,53 +119,312 @@ pub struct DumpOptions { pub trait Show: Display { fn show(&self, display_options: &DisplayOptions, length: Option); } + +impl Show for Response { + fn show(&self, display_options: &DisplayOptions, max_length: Option) { + // const HEADER_LENGTH: usize = 80; + + //─────────────────────────────────────────────────────────────────────────────────── + // Response HEADER + //─────────────────────────────────────────────────────────────────────────────────── + if display_options.sho_resp_header { + println!("{}", header_section("Response HEADER", None)); + println!("{}\n", self.header); + } + + //─────────────────────────────────────────────────────────────────────────────────── + // ANSWER + //─────────────────────────────────────────────────────────────────────────────────── + if self.header.an_count > 0 { + debug_assert!(self.answer.is_some()); + + if display_options.show_headers { + println!("{}", header_section("ANSWER", None)); + } + self.answer.as_ref().unwrap().show(display_options, max_length); + } + + //─────────────────────────────────────────────────────────────────────────────────── + // AUTHORATIVE + //─────────────────────────────────────────────────────────────────────────────────── + if self.header.ns_count > 0 && display_options.show_all { + debug_assert!(self.authority().is_some()); + + if display_options.show_headers { + println!("\n{}", header_section("AUTHORATIVE", None)); + } + self.authority().as_ref().unwrap().show(display_options, max_length); + } + + //─────────────────────────────────────────────────────────────────────────────────── + // ADDITIONAL + //─────────────────────────────────────────────────────────────────────────────────── + if self.header.ar_count > 0 && display_options.show_all { + debug_assert!(self.additional().is_some()); + + if display_options.show_headers { + println!("\n{}", header_section("ADDITIONAL", None)); + } + self.additional().as_ref().unwrap().show(display_options, max_length); + } + } +} + +impl Show for RRList { + fn show(&self, display_options: &DisplayOptions, _: Option) { + let max_length = if display_options.align_names { + self.max_length() + } else { + None + }; + + for rr in self.iter() { + // don't display OPT if not requested + // if rr.r#type == QType::OPT && !display_options.show_opt { + // continue; + // } else { + // rr.show(display_options, max_length); + // } + rr.show(display_options, max_length); + } + } +} + +impl Show for Query { + fn show(&self, display_options: &DisplayOptions, _length: Option) { + // print out Query if requested + if display_options.show_question { + println!("{}", self); + } + } +} + +impl Show for Message { + fn show(&self, display_options: &DisplayOptions, length: Option) { + // print out Query if requested + if display_options.show_question { + self.query.show(display_options, length); + } + + self.response.show(display_options, length); + } +} + +// standard lengths for displaying and aligning a RR +const NAME_DISPLAY_LENGTH: usize = 28; +const TYPE_DISPLAY_LENGTH: usize = 10; +const LENGTH_DISPLAY_LENGTH: usize = 5; +const CLASS_DISPLAY_LENGTH: usize = 4; +const TTL_INT_DISPLAY_LENGTH: usize = 7; +const TTL_STRING_DISPLAY_LENGTH: usize = 12; +const PAYLOAD_DISPLAY_LENGTH: usize = 5; +const EXTCODE_DISPLAY_LENGTH: usize = 5; +const VERSION_DISPLAY_LENGTH: usize = 5; +const FLAGS_DISPLAY_LENGTH: usize = 5; + +fn display(rr: &ResourceRecord, fmt: &str, raw_ttl: bool, name_length: usize, puny: bool) { + for f in fmt.split(",") { + match f.trim() { + // except OPT + "name" => { + // print punycodes + if puny { + print!("{: print!("{: print!("{: { + if let Some(r) = rr.opt_or_class_ttl.regular() { + print!("{: { + if let Some(r) = rr.opt_or_class_ttl.regular() { + if raw_ttl { + print!("{: print!("{}", rr.r_data.to_color()), + + // OPT specific data + "payload" => { + if let Some(r) = rr.opt_or_class_ttl.opt() { + print!("{: { + if let Some(r) = rr.opt_or_class_ttl.opt() { + print!("{: { + if let Some(r) = rr.opt_or_class_ttl.opt() { + print!("EDNS{: { + if let Some(r) = rr.opt_or_class_ttl.opt() { + print!("{: (), + } + } +} + +impl Show for ResourceRecord { + fn show(&self, display_options: &DisplayOptions, length: Option) { + let name_length = length.unwrap_or(NAME_DISPLAY_LENGTH); + + // formatting display + if !display_options.fmt.is_empty() { + display( + self, + &display_options.fmt, + display_options.raw_ttl, + name_length, + display_options.puny, + ); + println!(); + return; + } + + // other options + if display_options.short { + println!("{}", self.r_data.to_color()); + } else if self.r#type != QType::OPT { + const ALL_FIELDS: &str = "name,type,class,ttl,length,rdata"; + display( + self, + ALL_FIELDS, + display_options.raw_ttl, + name_length, + display_options.puny, + ); + println!(); + } else { + const ALL_FIELDS: &str = "name,type,length,payload,extcode,version,flags,length,rdata"; + display( + self, + ALL_FIELDS, + display_options.raw_ttl, + name_length, + display_options.puny, + ); + println!(); + } + } +} + pub trait ShowAll: Display { fn show_all(&self, display_options: &mut DisplayOptions, info: QueryInfo); } +impl ShowAll for MessageList { + fn show_all(&self, display_options: &mut DisplayOptions, info: QueryInfo) { + //─────────────────────────────────────────────────────────────────────────────────── + // JSON + //─────────────────────────────────────────────────────────────────────────────────── + if display_options.json_pretty { + let j = serde_json::json!({ + "messages": self, + "info": info + }); + println!("{}", serde_json::to_string_pretty(&j).unwrap()); + return; + } + + //─────────────────────────────────────────────────────────────────────────────────── + // JSON pretty + //─────────────────────────────────────────────────────────────────────────────────── + if display_options.json { + let j = serde_json::json!({ + "messages": self, + "info": info + }); + println!("{}", serde_json::to_string(&j).unwrap()); + return; + } + + //─────────────────────────────────────────────────────────────────────────────────── + // fancy print out when only one message + //─────────────────────────────────────────────────────────────────────────────────── + if self.len() == 1 { + // we only have 1 message + let msg = &self[0]; + let resp = msg.response(); + + // when we only have one message, we print out a dig-like info + display_options.sho_resp_header = true; + display_options.show_headers = true; + display_options.show_all = true; + + resp.show(display_options, None); + + // print out stats + println!("{}", header_section("STATS", None)); + println!("{}", info); + } + //─────────────────────────────────────────────────────────────────────────────────── + // when several messages, just print out the ANSWER + //─────────────────────────────────────────────────────────────────────────────────── + else { + let max_length = self.max_length(); + + for msg in self.iter() { + msg.show(display_options, max_length); + } + + if display_options.stats { + println!("{}", info); + } + } + } +} + pub trait ToColor: Display { fn to_color(&self) -> colored::ColoredString; } -// pub const NAME_COLOR: (u8, u8, u8) = (100, 100, 100); - -// titles when displaying headers: build a map giving for each title its colored version -use colored::*; -use std::collections::HashMap; -use std::sync::LazyLock; - -type ColoredTitles = HashMap; +impl ToColor for RData { + fn to_color(&self) -> colored::ColoredString { + self.to_string().bright_yellow() + } +} -pub static TITLES: LazyLock = LazyLock::new(|| { - const COLOR: Color = Color::BrightCyan; +impl ToColor for Ttl { + fn to_color(&self) -> colored::ColoredString { + self.to_string().bright_red() + } +} - // local helper - fn insert_title(h: &mut ColoredTitles, title: &str, color: Color) { - h.insert(title.to_string(), title.color(color)); +impl ToColor for QType { + fn to_color(&self) -> colored::ColoredString { + self.to_string().bright_blue() } +} - // init new hmap - let mut h = HashMap::new(); - - // add all titles - insert_title(&mut h, "qname", COLOR); - insert_title(&mut h, "qtype", COLOR); - insert_title(&mut h, "qclass", COLOR); - insert_title(&mut h, "name", COLOR); - insert_title(&mut h, "type", COLOR); - insert_title(&mut h, "payload", COLOR); - insert_title(&mut h, "rcode", COLOR); - insert_title(&mut h, "version", COLOR); - insert_title(&mut h, "flags", COLOR); - - h -}); - -pub fn header_section(text: &str, length: Option) -> ColoredString { - let s = if let Some(l) = length { - format!("{: colored::ColoredString { + self.to_string().bright_green() + // self.to_string().truecolor(NAME_COLOR.0, NAME_COLOR.1, NAME_COLOR.2) + } } + +// pub const NAME_COLOR: (u8, u8, u8) = (100, 100, 100); diff --git a/src/trace.rs b/src/trace.rs index 156f3da..3258dcb 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -1,14 +1,15 @@ use log::trace; +use dnslib::dns::rfc::domain::ROOT; +use dnslib::dns::rfc::{domain::ROOT_DOMAIN, qtype::QType}; +use dnslib::error::*; +use dnslib::transport::{endpoint::EndPoint, root_servers::get_root_server}; + use crate::args::CliOptions; -use crate::dns::rfc::domain::ROOT; -use crate::dns::rfc::{domain::ROOT_DOMAIN, qtype::QType}; -use crate::error::{Dns, Error}; use crate::get_messages; use crate::show::Show; -use crate::transport::{endpoint::EndPoint, root_servers::get_root_server}; -pub fn trace_resolution(options: &mut CliOptions) -> crate::error::Result<()> { +pub fn trace_resolution(options: &mut CliOptions) -> dnslib::error::Result<()> { trace!("tracing started"); // save original options @@ -53,7 +54,7 @@ pub fn trace_resolution(options: &mut CliOptions) -> crate::error::Result<()> { println!(); // did we find the ip address for the domain we asked for ? - if let Some(ip) = resp.ip_address(&orig_qt, &options.protocol.domain_name) { + if let Some(_ip) = resp.ip_address(&orig_qt, &options.protocol.domain_name) { // println!("!!! found ip={}", ip); return Ok(()); }