From 31ce22025fbf11c44c73076fa91aa083599233f9 Mon Sep 17 00:00:00 2001 From: dandyvica Date: Sat, 30 Nov 2024 20:19:30 +0100 Subject: [PATCH] added new OPT codes --- src/args.rs | 11 +- src/cli_options.rs | 35 +++-- src/dns/buffer.rs | 9 +- src/dns/rfc/cert.rs | 4 +- src/dns/rfc/dhcid.rs | 4 +- src/dns/rfc/dnskey.rs | 4 +- src/dns/rfc/ds.rs | 2 +- src/dns/rfc/hip.rs | 8 +- src/dns/rfc/ipseckey.rs | 4 +- src/dns/rfc/mod.rs | 2 +- src/dns/rfc/nsec3.rs | 2 +- src/dns/rfc/openpgpkey.rs | 4 +- src/dns/rfc/opt/cookie.rs | 2 +- src/dns/rfc/opt/dau_dhu_n3u.rs | 4 +- src/dns/rfc/opt/extended.rs | 50 +++++- src/dns/rfc/opt/mod.rs | 22 ++- src/dns/rfc/opt/nsid.rs | 19 +-- src/dns/rfc/opt/opt_rr.rs | 229 ++++++++++++++-------------- src/dns/rfc/opt/padding.rs | 6 +- src/dns/rfc/opt/report_chanel.rs | 43 ++++++ src/dns/rfc/opt/zoneversion.rs | 62 ++++++++ src/dns/rfc/query.rs | 6 +- src/dns/rfc/rdata.rs | 32 ++-- src/dns/rfc/resource_record.rs | 60 +++++--- src/dns/rfc/response.rs | 15 +- src/dns/rfc/{rrset.rs => rrlist.rs} | 12 +- src/dns/rfc/rrsig.rs | 4 +- src/error.rs | 7 +- src/protocol.rs | 3 - src/transport/mod.rs | 1 - 30 files changed, 426 insertions(+), 240 deletions(-) create mode 100644 src/dns/rfc/opt/report_chanel.rs create mode 100644 src/dns/rfc/opt/zoneversion.rs rename src/dns/rfc/{rrset.rs => rrlist.rs} (96%) diff --git a/src/args.rs b/src/args.rs index cdaa149..9a034e1 100644 --- a/src/args.rs +++ b/src/args.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; -use clap::{error, Arg, ArgAction, Command}; +use clap::{Arg, ArgAction, Command}; use http::*; use log::{debug, trace}; use regex::Regex; @@ -451,6 +451,14 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY .value_parser(clap::value_parser!(u16)) .help_heading("EDNS options") ) + .arg( + Arg::new("zoneversion") + .long("zoneversion") + .long_help("Sets the EDNS ZONEVERSION option in the OPT record.") + .long_help("Sets the EDNS NSID option in the OPT record.") + .action(ArgAction::SetTrue) + .help_heading("EDNS options") + ) //─────────────────────────────────────────────────────────────────────────────────── // Display options //─────────────────────────────────────────────────────────────────────────────────── @@ -809,6 +817,7 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY options.edns.no_opt = matches.get_flag("no-opt"); options.edns.dnssec = matches.get_flag("dnssec"); options.edns.nsid = matches.get_flag("nsid"); + options.edns.zoneversion = matches.get_flag("zoneversion"); options.edns.padding = matches.get_one::("padding").copied(); options.edns.dau = matches.get_many::("dau").map(|v| v.copied().collect::>()); diff --git a/src/cli_options.rs b/src/cli_options.rs index ccdcda8..a6d06ba 100644 --- a/src/cli_options.rs +++ b/src/cli_options.rs @@ -5,10 +5,11 @@ use log::trace; use crate::args::CliOptions; use crate::dns::rfc::domain::ROOT; +use crate::dns::rfc::opt::zoneversion::ZONEVERSION; use crate::dns::rfc::{ domain::{DomainName, ROOT_DOMAIN}, opt::{ - dau_dhu_n3u::{EdnsKeyTag, DAU, DHU, N3U}, + //dau_dhu_n3u::{EdnsKeyTag, DAU, DHU, N3U}, nsid::NSID, //opt_rr::OPT, padding::Padding, @@ -63,6 +64,9 @@ pub struct EdnsOptions { // add NSID option if true pub nsid: bool, + // add ZONEVERSION option if true + pub zoneversion: bool, + // padding if the form of +padding=20 pub padding: Option, @@ -145,21 +149,26 @@ impl FromOptions for OPT { opt.add_option(Padding::new(len)); } - // DAU, DHU & N3U - if let Some(list) = &edns.dau { - opt.add_option(DAU::from(list.as_slice())); - } - if let Some(list) = &edns.dhu { - opt.add_option(DHU::from(list.as_slice())); - } - if let Some(list) = &edns.n3u { - opt.add_option(N3U::from(list.as_slice())); + // ZONEVERSION + if edns.zoneversion { + opt.add_option(ZONEVERSION::default()); } + // DAU, DHU & N3U + // if let Some(list) = &edns.dau { + // opt.add_option(DAU::from(list.as_slice())); + // } + // if let Some(list) = &edns.dhu { + // opt.add_option(DHU::from(list.as_slice())); + // } + // if let Some(list) = &edns.n3u { + // opt.add_option(N3U::from(list.as_slice())); + // } + // edns-key-tag - if let Some(list) = &edns.keytag { - opt.add_option(EdnsKeyTag::from(list.as_slice())); - } + // if let Some(list) = &edns.keytag { + // opt.add_option(EdnsKeyTag::from(list.as_slice())); + // } Some(opt) } diff --git a/src/dns/buffer.rs b/src/dns/buffer.rs index b79318a..b3b940b 100644 --- a/src/dns/buffer.rs +++ b/src/dns/buffer.rs @@ -22,12 +22,12 @@ impl Buffer { // } // some RRs need to convert the raw data into b16 or b64 hexa strings - pub fn to_b64(&self) -> String { + pub fn to_base64(&self) -> String { general_purpose::STANDARD.encode(self.as_ref()) } // some RRs need to convert the raw data into b16 or b64 hexa strings - pub fn to_b16(&self) -> String { + pub fn to_base16(&self) -> String { base16::encode_upper(&self.as_ref()) } @@ -35,6 +35,11 @@ impl Buffer { pub fn to_hex(&self) -> String { format!("{:?}", self) } + + // fancier display + pub fn display(&self) -> String { + format!("0x{:?} \"{}\"", self, self) + } } impl Deref for Buffer { diff --git a/src/dns/rfc/cert.rs b/src/dns/rfc/cert.rs index 0ff5092..dd0aaab 100644 --- a/src/dns/rfc/cert.rs +++ b/src/dns/rfc/cert.rs @@ -63,7 +63,7 @@ impl fmt::Display for CERT { self.certificate_type, self.key_tag, self.algorithm, - self.certificate.to_b64() + self.certificate.to_base64() ) } } @@ -77,7 +77,7 @@ impl Serialize for CERT { seq.serialize_entry("flags", &self.certificate_type.to_string())?; seq.serialize_entry("key_tag", &self.key_tag)?; seq.serialize_entry("algorithm", &self.algorithm)?; - seq.serialize_entry("certificate", &self.certificate.to_b64())?; + seq.serialize_entry("certificate", &self.certificate.to_base64())?; seq.end() } } diff --git a/src/dns/rfc/dhcid.rs b/src/dns/rfc/dhcid.rs index 74999bb..c5e2c9a 100644 --- a/src/dns/rfc/dhcid.rs +++ b/src/dns/rfc/dhcid.rs @@ -24,7 +24,7 @@ new_rd_length!(DHCID); impl fmt::Display for DHCID { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.data.to_b64()) + write!(f, "{}", self.data.to_base64()) } } @@ -33,7 +33,7 @@ impl Serialize for DHCID { where S: Serializer, { - serializer.serialize_str(&self.data.to_b64()) + serializer.serialize_str(&self.data.to_base64()) } } diff --git a/src/dns/rfc/dnskey.rs b/src/dns/rfc/dnskey.rs index 4e1063b..1c7ce70 100644 --- a/src/dns/rfc/dnskey.rs +++ b/src/dns/rfc/dnskey.rs @@ -78,7 +78,7 @@ impl fmt::Display for DNSKEY { self.flags, self.protocol, self.algorithm, - self.key.to_b64() + self.key.to_base64() ) } } @@ -97,7 +97,7 @@ impl Serialize for DNSKEY { seq.serialize_entry("flags", &self.flags)?; seq.serialize_entry("protocol", &self.protocol)?; seq.serialize_entry("algorithm", &self.algorithm.to_string())?; - seq.serialize_entry("key", &self.key.to_b64())?; + seq.serialize_entry("key", &self.key.to_base64())?; seq.end() } } diff --git a/src/dns/rfc/ds.rs b/src/dns/rfc/ds.rs index d8931af..94bfec7 100644 --- a/src/dns/rfc/ds.rs +++ b/src/dns/rfc/ds.rs @@ -57,7 +57,7 @@ impl Serialize for DS { seq.serialize_entry("key_tag", &self.key_tag)?; seq.serialize_entry("algorithm", &self.algorithm.to_string())?; seq.serialize_entry("digest_type", &self.digest_type)?; - seq.serialize_entry("digest", &self.digest.to_b64())?; + seq.serialize_entry("digest", &self.digest.to_base64())?; seq.end() } } diff --git a/src/dns/rfc/hip.rs b/src/dns/rfc/hip.rs index aea03d4..4f5e878 100644 --- a/src/dns/rfc/hip.rs +++ b/src/dns/rfc/hip.rs @@ -59,8 +59,8 @@ impl fmt::Display for HIP { f, "{} {} {}", self.pk_algorithm, - self.hit.to_b16(), - self.public_key.to_b64() + self.hit.to_base16(), + self.public_key.to_base64() ) } } @@ -74,8 +74,8 @@ impl Serialize for HIP { { let mut seq = serializer.serialize_map(Some(3))?; seq.serialize_entry("pk_algorithm", &self.pk_algorithm)?; - seq.serialize_entry("hit", &self.hit.to_b16())?; - seq.serialize_entry("public_key", &self.public_key.to_b64())?; + seq.serialize_entry("hit", &self.hit.to_base16())?; + seq.serialize_entry("public_key", &self.public_key.to_base64())?; seq.end() } } diff --git a/src/dns/rfc/ipseckey.rs b/src/dns/rfc/ipseckey.rs index 9ce474c..59baeb4 100644 --- a/src/dns/rfc/ipseckey.rs +++ b/src/dns/rfc/ipseckey.rs @@ -83,7 +83,7 @@ impl fmt::Display for IPSECKEY { self.gateway_type, self.algorithm, self.gateway, - self.public_key.to_b64() + self.public_key.to_base64() ) } } @@ -100,7 +100,7 @@ impl Serialize for IPSECKEY { seq.serialize_entry("gateway_type", &self.gateway_type)?; seq.serialize_entry("algorithm", &self.algorithm)?; seq.serialize_entry("gateway", &self.gateway.to_string())?; - seq.serialize_entry("public_key", &self.public_key.to_b64())?; + seq.serialize_entry("public_key", &self.public_key.to_base64())?; seq.end() } } diff --git a/src/dns/rfc/mod.rs b/src/dns/rfc/mod.rs index 74daff4..45d6c68 100644 --- a/src/dns/rfc/mod.rs +++ b/src/dns/rfc/mod.rs @@ -45,7 +45,7 @@ pub mod query; pub mod rdata; pub mod response; pub mod rp; -pub mod rrset; +pub mod rrlist; pub mod rrsig; pub mod soa; pub mod srv; diff --git a/src/dns/rfc/nsec3.rs b/src/dns/rfc/nsec3.rs index 8ef4f9b..3495a87 100644 --- a/src/dns/rfc/nsec3.rs +++ b/src/dns/rfc/nsec3.rs @@ -8,7 +8,7 @@ use serde::Serialize; use crate::{ dns::buffer::{serialize_buffer, Buffer}, - error::{Dns, Error}, + // error::{Dns, Error}, new_rd_length, }; diff --git a/src/dns/rfc/openpgpkey.rs b/src/dns/rfc/openpgpkey.rs index b2b2889..b81a585 100644 --- a/src/dns/rfc/openpgpkey.rs +++ b/src/dns/rfc/openpgpkey.rs @@ -24,7 +24,7 @@ new_rd_length!(OPENPGPKEY); impl fmt::Display for OPENPGPKEY { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.key.to_b64()) + write!(f, "{}", self.key.to_base64()) } } @@ -35,7 +35,7 @@ impl Serialize for OPENPGPKEY { where S: Serializer, { - serializer.serialize_str(&self.key.to_b64()) + serializer.serialize_str(&self.key.to_base64()) } } diff --git a/src/dns/rfc/opt/cookie.rs b/src/dns/rfc/opt/cookie.rs index 2ace8a9..00617f4 100644 --- a/src/dns/rfc/opt/cookie.rs +++ b/src/dns/rfc/opt/cookie.rs @@ -3,7 +3,7 @@ use type2network_derive::ToNetwork; use serde::Serialize; -// Cookie: https://www.rfc-editor.org/rfc/rfc5001.html +// Cookie: https://www.rfc-editor.org/rfc/rfc7873 #[derive(Debug, Default, ToNetwork, Serialize)] pub struct COOKIE { client_cookie: Vec, diff --git a/src/dns/rfc/opt/dau_dhu_n3u.rs b/src/dns/rfc/opt/dau_dhu_n3u.rs index 71b5827..286d004 100644 --- a/src/dns/rfc/opt/dau_dhu_n3u.rs +++ b/src/dns/rfc/opt/dau_dhu_n3u.rs @@ -6,8 +6,8 @@ use serde::Serialize; use crate::{opt_code, opt_data}; use super::{ - opt_rr::{OptionCode, OptOptionData}, - OptionData, + opt_rr::{OptionData, OptionCode}, + OptionDataValue, }; // useful macro to auto define DAU, DHU & N3U which are the same diff --git a/src/dns/rfc/opt/extended.rs b/src/dns/rfc/opt/extended.rs index f9597da..f1ccc38 100644 --- a/src/dns/rfc/opt/extended.rs +++ b/src/dns/rfc/opt/extended.rs @@ -1,13 +1,57 @@ -use type2network::{FromNetworkOrder, ToNetworkOrder}; -use type2network_derive::{FromNetwork, ToNetwork}; +use std::fmt; + +use type2network::ToNetworkOrder; +use type2network_derive::ToNetwork; use serde::Serialize; use crate::dns::buffer::Buffer; // https://www.rfc-editor.org/rfc/rfc7871 -#[derive(Debug, Default, ToNetwork, FromNetwork, Serialize)] +#[derive(Debug, Default, ToNetwork, Serialize)] pub struct Extended { pub(super) info_code: u16, pub(super) extra_text: Buffer, } + +impl From<(u16, Buffer)> for Extended { + fn from(x: (u16, Buffer)) -> Self { + Self { + info_code: x.0, + extra_text: x.1, + } + } +} + +impl fmt::Display for Extended { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.info_code { + 1 => write!(f, "Other"), + 2 => write!(f, "Unsupported DNSKEY Algorithm"), + 3 => write!(f, "Unsupported DS Digest Type"), + 4 => write!(f, "Stale Answer"), + 5 => write!(f, "Forged Answer"), + 6 => write!(f, "DNSSEC Indeterminate"), + 7 => write!(f, "DNSSEC Bogus"), + 8 => write!(f, "Signature Expired"), + 9 => write!(f, "Signature Not Yet Valid"), + 10 => write!(f, "DNSKEY Missing"), + 11 => write!(f, "RRSIGs Missing"), + 12 => write!(f, "No Zone Key Bit Set"), + 13 => write!(f, "NSEC Missing"), + 14 => write!(f, "Cached Error"), + 15 => write!(f, "Not Ready"), + 16 => write!(f, "Blocked"), + 17 => write!(f, "Censored"), + 18 => write!(f, "Filtered"), + 19 => write!(f, "Prohibited"), + 20 => write!(f, "Stale NXDOMAIN Answer"), + 21 => write!(f, "Not Authoritative"), + 22 => write!(f, "Not Supported"), + 23 => write!(f, "No Reachable Authority"), + 24 => write!(f, "Network Error"), + 25 => write!(f, "Invalid Data"), + _ => write!(f, "extended code {} not yet assigned", self.info_code), + } + } +} diff --git a/src/dns/rfc/opt/mod.rs b/src/dns/rfc/opt/mod.rs index ffd87ea..b88cd6c 100644 --- a/src/dns/rfc/opt/mod.rs +++ b/src/dns/rfc/opt/mod.rs @@ -15,17 +15,19 @@ // 65001-65534 Reserved for Local/Experimental Use [RFC6891] // 65535 Reserved for future expansion [RFC6891] -use self::opt_rr::{OptionCode, OptOptionData}; +use self::opt_rr::{OptionCode, OptionData}; pub mod client_subnet; pub mod cookie; -pub mod dau_dhu_n3u; +//pub mod dau_dhu_n3u; pub mod extended; pub mod nsid; pub mod opt_rr; pub mod padding; +pub mod report_chanel; +pub mod zoneversion; -pub trait OptionData { +pub trait OptionDataValue { // return the option code for the option data fn code(&self) -> OptionCode; @@ -36,13 +38,12 @@ pub trait OptionData { } // return the option data enum arm - fn data(self) -> OptOptionData; + fn data(self) -> Option; } // macro helpers to define code() et data() easily #[macro_export] macro_rules! opt_code { - // to deserialize "simple" structs (like A) ($opt:ident) => { fn code(&self) -> OptionCode { OptionCode::$opt @@ -52,10 +53,15 @@ macro_rules! opt_code { #[macro_export] macro_rules! opt_data { - // to deserialize "simple" structs (like A) ($opt:ident) => { - fn data(self) -> OptOptionData { - OptOptionData::$opt(self) + fn data(self) -> Option { + Some(OptionData::$opt(self)) + } + }; + + () => { + fn data(self) -> Option { + None } }; } diff --git a/src/dns/rfc/opt/nsid.rs b/src/dns/rfc/opt/nsid.rs index 183fe93..661daf0 100644 --- a/src/dns/rfc/opt/nsid.rs +++ b/src/dns/rfc/opt/nsid.rs @@ -9,8 +9,8 @@ use crate::{opt_code, opt_data}; use serde::Serialize; use super::{ - opt_rr::{OptionCode, OptOptionData}, - OptionData, + opt_rr::{OptionCode, OptionData}, + OptionDataValue, }; // NSID: https://www.rfc-editor.org/rfc/rfc5001.html @@ -25,20 +25,15 @@ impl From for NSID { impl fmt::Display for NSID { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.0.is_some() { - let buf = self.0.as_ref().unwrap(); - write!(f, "0x{:?} ", buf)?; - - f.write_str("\"")?; - write!(f, "{}", buf)?; - f.write_str("\"")?; + if let Some(b) = &self.0 { + write!(f, "{}", b.display())? } Ok(()) } } -impl OptionData for NSID { +impl OptionDataValue for NSID { // return the option code for the option data opt_code!(NSID); @@ -47,6 +42,6 @@ impl OptionData for NSID { 0 } - // return the option data enum arm - opt_data!(NSID); + // return None + opt_data!(); } diff --git a/src/dns/rfc/opt/opt_rr.rs b/src/dns/rfc/opt/opt_rr.rs index 8cd128f..72ba89f 100644 --- a/src/dns/rfc/opt/opt_rr.rs +++ b/src/dns/rfc/opt/opt_rr.rs @@ -1,4 +1,8 @@ -use std::{fmt, io::Cursor}; +use std::{ + fmt, + io::Cursor, + ops::{Deref, DerefMut}, +}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; @@ -9,22 +13,23 @@ use type2network_derive::{FromNetwork, ToNetwork}; use serde::Serialize; -use crate::{ - dns::buffer::Buffer, - dns::rfc::{ +use crate::dns::{ + buffer::Buffer, + rfc::{ + domain::DomainName, opt::nsid::NSID, - qtype::QType, - // resource_record::{OptClassTtl, OptOrElse, ResourceRecord}, + //qtype::QType, // resource_record::{OptClassTtl, OptOrElse, ResourceRecord}, }, }; use super::{ + //client_subnet::ClientSubnet, client_subnet::ClientSubnet, cookie::COOKIE, - dau_dhu_n3u::{EdnsKeyTag, DAU, DHU, N3U}, extended::Extended, padding::Padding, - OptionData, + report_chanel::ReportChannel, + zoneversion::{ZONEVERSION, ZV}, }; // This OPT is the one which is sent in the query (additional record) @@ -38,92 +43,6 @@ use super::{ // | RDLEN | u_int16_t | length of all RDATA | // | RDATA | octet stream | {attribute,value} pairs | // +------------+--------------+------------------------------+ -#[allow(non_camel_case_types)] -//pub(super) type OPT_OPTIONS = Vec; - -// // OPT is a special (weird) case of RR -// #[derive(Debug, Default, ToNetwork, Serialize)] -// pub struct OPT { -// pub name: u8, -// pub r#type: QType, -// pub payload: u16, -// pub extended_rcode: u8, -// pub version: u8, -// pub flags: u16, -// pub rd_length: u16, -// pub options: Vec, -// } - -// impl OPT { -// pub fn new(bufsize: u16) -> Self { -// Self { -// r#type: QType::OPT, -// payload: bufsize, -// ..Default::default() -// } -// } - -// // set DNSSEC bit to 1 -// pub fn set_dnssec(&mut self) { -// self.flags = 0x8000; -// } - -// pub fn add_option(&mut self, data: T) { -// // build the option structure -// let option = OptOption { -// code: data.code(), -// length: data.len(), -// data: data.data(), -// }; - -// trace!("OPTION:{:?}", option); - -// // add in the list of options -// self.rd_length += 4 + option.length; -// self.options.push(option); -// } - -// //─────────────────────────────────────────────────────────────────────────────────── -// // builder pattern for adding lots of options to OPT RR -// //─────────────────────────────────────────────────────────────────────────────────── -// // pub fn build(bufsize: u16) -> Self { -// // Self::new(bufsize) -// // } - -// // pub fn with_option(mut self, data: T) -> Self { -// // self.add_option(data); -// // self -// // } - -// // #[allow(clippy::field_reassign_with_default)] -// // pub fn set_edns_nsid(&mut self) { -// // let mut nsid = OptOption::default(); -// // nsid.code = OptOptionCode::NSID; - -// // self.push_option(nsid); -// // } -// } - -// https://www.rfc-editor.org/rfc/rfc6891#section-6.1.3 -// +0 (MSB) +1 (LSB) -// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ -// 0: | EXTENDED-RCODE | VERSION | -// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ -// 2: | DO| Z | -// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ -//#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, ToNetwork, FromNetwork, Serialize)] -// pub struct OptTTL { -// pub(super) extended_rcode: u8, -// pub(super) version: u8, -// pub(super) flags: u16, -// } - -// impl fmt::Display for OptTTL { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// write!(f, "({} {} {})", self.extended_rcode, self.version, self.flags) -// } -// } - // https://www.rfc-editor.org/rfc/rfc6891#section-6.1.2 // +0 (MSB) +1 (LSB) // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ @@ -139,12 +58,16 @@ use super::{ pub struct OptOption { pub(crate) code: OptionCode, pub(crate) length: u16, - pub(crate) data: OptOptionData, + pub(crate) data: Option, } impl fmt::Display for OptOption { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} {} {}", self.code, self.length, self.data) + if let Some(data) = &self.data { + write!(f, "{} {} {}", self.code, self.length, data) + } else { + write!(f, "{} {}", self.code, self.length) + } } } @@ -158,27 +81,50 @@ impl<'a> FromNetworkOrder<'a> for OptOption { OptionCode::NSID => { let mut buf: Buffer = Buffer::with_capacity(self.length); buf.deserialize_from(buffer)?; - self.data = OptOptionData::NSID(NSID::from(buf)); + self.data = Some(OptionData::NSID(NSID::from(buf))); } OptionCode::Padding => { let mut buf: Buffer = Buffer::with_capacity(self.length); buf.deserialize_from(buffer)?; - self.data = OptOptionData::Padding(Padding::from(buf)); + self.data = Some(OptionData::Padding(Padding::from(buf))); + } + OptionCode::Extended => { + let mut info_code = 0u16; + info_code.deserialize_from(buffer)?; + let mut buf: Buffer = Buffer::with_capacity(self.length - 2); + buf.deserialize_from(buffer)?; + self.data = Some(OptionData::Extended(Extended::from((info_code, buf)))); + } + OptionCode::ReportChannel => { + let mut agent_domain = DomainName::default(); + agent_domain.deserialize_from(buffer)?; + self.data = Some(OptionData::ReportChanel(ReportChannel::from(agent_domain))); + } + OptionCode::ZONEVERSION => { + println!("inside ZV"); + let mut zv = ZV::default(); + zv.label_count.deserialize_from(buffer)?; + zv.r#type.deserialize_from(buffer)?; + let mut buf: Buffer = Buffer::with_capacity(self.length - 2); + buf.deserialize_from(buffer)?; + self.data = Some(OptionData::ZONEVERSION(ZONEVERSION::from(zv))); } OptionCode::EdnsClientSubnet => { let mut subnet = ClientSubnet::default(); subnet.address = Buffer::with_capacity(self.length - 4); subnet.deserialize_from(buffer)?; - self.data = OptOptionData::ClientSubnet(subnet); - } - OptionCode::Extended => { - let mut extended = Extended::default(); - extended.extra_text = Buffer::with_capacity(self.length - 2); - extended.deserialize_from(buffer)?; - self.data = OptOptionData::Extended(extended); + self.data = Some(OptionData::ClientSubnet(subnet)); } + // OptionCode::Extended => { + // let mut extended = Extended::default(); + // extended.extra_text = Buffer::with_capacity(self.length - 2); + // extended.deserialize_from(buffer)?; + // self.data = OptOptionData::Extended(extended); + // } _ => unimplemented!("option code <{}> is not yet implemented", self.code), } + + trace!("OptOption deserialize: {:#?}", self); Ok(()) } } @@ -205,37 +151,88 @@ pub enum OptionCode { Extended = 15, // DNS Error Standard [RFC8914] EdnsClientTag = 16, // Optional [draft-bellis-dnsop-edns-tags] EdnsServerTag = 17, // Optional [draft-bellis-dnsop-edns-tags] + ReportChannel = 18, + ZONEVERSION = 19, Umbrella = 20292, // Ident Optional [https://developer.cisco.com/docs/cloud-security/#!integrating-network-devices/rdata-description][Cisco_CIE_DNS_team] DeviceID = 26946, // Optional [https://developer.cisco.com/docs/cloud-security/#!network-devices-getting-started/response-codes][Cisco_CIE_DNS_team] } #[derive(Debug, ToNetwork, Serialize)] -pub enum OptOptionData { +pub enum OptionData { NSID(NSID), COOKIE(COOKIE), Padding(Padding), ClientSubnet(ClientSubnet), - DAU(DAU), - DHU(DHU), - N3U(N3U), - EdnsKeyTag(EdnsKeyTag), + // DAU(DAU), + // DHU(DHU), + // N3U(N3U), + // EdnsKeyTag(EdnsKeyTag), Extended(Extended), + ReportChanel(ReportChannel), + ZONEVERSION(ZONEVERSION), } -impl Default for OptOptionData { +impl Default for OptionData { fn default() -> Self { - OptOptionData::NSID(NSID::default()) + OptionData::NSID(NSID::default()) } } -impl fmt::Display for OptOptionData { +impl fmt::Display for OptionData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - OptOptionData::NSID(n) => write!(f, "{}", n)?, - OptOptionData::Padding(p) => write!(f, "{}", p)?, - OptOptionData::ClientSubnet(p) => write!(f, "{} {}", p.family, p.address)?, + OptionData::NSID(n) => write!(f, "{}", n)?, + OptionData::Padding(p) => write!(f, "{}", p)?, + OptionData::Extended(p) => write!(f, "{}", p)?, + OptionData::ClientSubnet(p) => write!(f, "{} {}", p.family, p.address)?, + OptionData::ReportChanel(p) => write!(f, "{}", p)?, + OptionData::ZONEVERSION(p) => write!(f, "{}", p)?, _ => unimplemented!("EDNS option not yet implemented"), } Ok(()) } } + +// list of options which are appended to the RDATA for the OPT RR +#[derive(Debug, Default, Serialize, ToNetwork, FromNetwork)] +pub struct OptionList(Vec); + +impl OptionList { + pub fn new(v: Vec) -> Self { + Self(v) + } +} + +impl Deref for OptionList { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for OptionList { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +// IntoIterator to benefit from already defined iterator on Vec +impl<'a> IntoIterator for &'a OptionList { + type Item = &'a OptOption; + type IntoIter = std::slice::Iter<'a, OptOption>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl fmt::Display for OptionList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for opt in &self.0 { + write!(f, "<{}>", opt)?; + } + + Ok(()) + } +} diff --git a/src/dns/rfc/opt/padding.rs b/src/dns/rfc/opt/padding.rs index b0276ab..b55fed6 100644 --- a/src/dns/rfc/opt/padding.rs +++ b/src/dns/rfc/opt/padding.rs @@ -8,8 +8,8 @@ use serde::Serialize; use crate::{dns::buffer::Buffer, opt_code, opt_data}; use super::{ - opt_rr::{OptionCode, OptOptionData}, - OptionData, + opt_rr::{OptionCode, OptionData}, + OptionDataValue, }; // https://www.rfc-editor.org/rfc/rfc7830.html @@ -46,7 +46,7 @@ impl fmt::Display for Padding { } } -impl OptionData for Padding { +impl OptionDataValue for Padding { // return the option code for the option data opt_code!(Padding); diff --git a/src/dns/rfc/opt/report_chanel.rs b/src/dns/rfc/opt/report_chanel.rs new file mode 100644 index 0000000..55eb9b8 --- /dev/null +++ b/src/dns/rfc/opt/report_chanel.rs @@ -0,0 +1,43 @@ +use std::fmt; + +use type2network::ToNetworkOrder; +use type2network_derive::ToNetwork; + +use crate::dns::rfc::domain::DomainName; +use crate::{opt_code, opt_data}; + +use serde::Serialize; + +use super::{ + opt_rr::{OptionCode, OptionData}, + OptionDataValue, +}; + +// ReportChanel: https://www.rfc-editor.org/rfc/rfc9567.html +#[derive(Debug, Default, ToNetwork, Serialize)] +pub struct ReportChannel(DomainName); + +impl From for ReportChannel { + fn from(dn: DomainName) -> Self { + Self(dn) + } +} + +impl fmt::Display for ReportChannel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl OptionDataValue for ReportChannel { + // return the option code for the option data + opt_code!(ReportChannel); + + // return option data length + fn len(&self) -> u16 { + 0 + } + + // return None + opt_data!(); +} diff --git a/src/dns/rfc/opt/zoneversion.rs b/src/dns/rfc/opt/zoneversion.rs new file mode 100644 index 0000000..3e41bb9 --- /dev/null +++ b/src/dns/rfc/opt/zoneversion.rs @@ -0,0 +1,62 @@ +use std::fmt; + +use type2network::ToNetworkOrder; +use type2network_derive::ToNetwork; + +use crate::dns::buffer::Buffer; +use crate::{opt_code, opt_data}; + +use serde::Serialize; + +use super::{ + opt_rr::{OptionCode, OptionData}, + OptionDataValue, +}; + +// ZONEVERSION: https://www.rfc-editor.org/rfc/rfc9660.html +#[derive(Debug, Default, ToNetwork, Serialize)] +pub struct ZV { + pub label_count: u8, + pub r#type: u8, + pub version: Buffer, +} + +#[derive(Debug, Default, ToNetwork, Serialize)] +pub struct ZONEVERSION(pub Option); + +impl ZONEVERSION { + pub fn new() -> Self { + Self(Some(ZV::default())) + } +} + +impl From for ZONEVERSION { + fn from(zv: ZV) -> Self { + Self(Some(zv)) + } +} + +impl fmt::Display for ZONEVERSION { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(zv) = &self.0 { + write!(f, "{}", zv.label_count)?; + write!(f, "{}", zv.r#type)?; + write!(f, "{}", zv.version.display())?; + } + + Ok(()) + } +} + +impl OptionDataValue for ZONEVERSION { + // return the option code for the option data + opt_code!(ZONEVERSION); + + // return option data length + fn len(&self) -> u16 { + 0 + } + + // return None + opt_data!(); +} diff --git a/src/dns/rfc/query.rs b/src/dns/rfc/query.rs index c633f12..f5707df 100644 --- a/src/dns/rfc/query.rs +++ b/src/dns/rfc/query.rs @@ -13,8 +13,8 @@ use crate::error::{Dns, Error, Result}; use crate::transport::network::Messenger; use super::{ - domain::DomainName, flags::BitFlags, header::Header, resource_record::OPT, qclass::QClass, qtype::QType, - question::Question, + domain::DomainName, flags::BitFlags, header::Header, qclass::QClass, qtype::QType, question::Question, + resource_record::OPT, }; #[derive(Debug, ToNetwork, Serialize)] @@ -88,8 +88,6 @@ impl Query { // Send the query through the wire pub fn send(&mut self, trp: &mut T, save_path: &Option) -> Result { - trace!("==================== {:?}", self); - // convert to network bytes let mut buffer: Vec = Vec::new(); let message_size = self diff --git a/src/dns/rfc/rdata.rs b/src/dns/rfc/rdata.rs index e6678fe..7dff524 100644 --- a/src/dns/rfc/rdata.rs +++ b/src/dns/rfc/rdata.rs @@ -9,7 +9,6 @@ use serde::Serialize; use type2network::ToNetworkOrder; use type2network::FromNetworkOrder; -use type2network_derive::FromNetwork; use crate::{dns::buffer::Buffer, show::ToColor}; @@ -39,7 +38,7 @@ use super::{ nsec3::NSEC3, nsec3param::NSEC3PARAM, openpgpkey::OPENPGPKEY, - opt::opt_rr::OptOption, + opt::opt_rr::{OptOption, OptionList}, ptr::PTR, qtype::QType, rp::RP, @@ -54,9 +53,6 @@ use super::{ zonemd::ZONEMD, }; -#[derive(Debug, Serialize)] -struct OptionList(Vec); - #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Serialize)] #[serde(tag = "type", content = "rdata")] @@ -92,7 +88,7 @@ pub(super) enum RData { NSEC3(NSEC3), NSEC3PARAM(NSEC3PARAM), OPENPGPKEY(OPENPGPKEY), - OPT(Vec), + OPT(OptionList), PTR(PTR), RP(RP), RRSIG(RRSIG), @@ -175,7 +171,7 @@ impl RData { v.push(option); } - Ok(RData::OPT(v)) + Ok(RData::OPT(OptionList::new(v))) } QType::PTR => get_rr!(buffer, PTR, RData::PTR), QType::RP => get_rr!(buffer, RP, RData::RP), @@ -205,9 +201,14 @@ impl Default for RData { } } +// on serializing, only OPT is necessary to serialize impl ToNetworkOrder for RData { - fn serialize_to(&self, _buffer: &mut Vec) -> std::io::Result { - Ok(0) + fn serialize_to(&self, buffer: &mut Vec) -> std::io::Result { + if let RData::OPT(opt) = self { + opt.serialize_to(buffer) + } else { + Ok(0) + } } } @@ -246,12 +247,13 @@ impl fmt::Display for RData { RData::NSEC3(a) => write!(f, "{}", a), RData::NSEC3PARAM(a) => write!(f, "{}", a), RData::OPENPGPKEY(a) => write!(f, "{}", a), - RData::OPT(a) => { - for opt in a { - write!(f, "{}", opt)?; - } - Ok(()) - } + RData::OPT(a) => write!(f, "{}", a), + //{ + // for opt in a { + // write!(f, "{}", opt)?; + // } + // Ok(()) + // } RData::PTR(a) => write!(f, "{}", a), RData::RP(a) => write!(f, "{}", a), RData::RRSIG(a) => write!(f, "{}", a), diff --git a/src/dns/rfc/resource_record.rs b/src/dns/rfc/resource_record.rs index b81744b..9244475 100644 --- a/src/dns/rfc/resource_record.rs +++ b/src/dns/rfc/resource_record.rs @@ -1,16 +1,15 @@ use std::{fmt, io::Cursor, net::IpAddr}; -use bytes::buf; use colored::Colorize; use serde::Serialize; use type2network::{FromNetworkOrder, ToNetworkOrder}; use type2network_derive::{FromNetwork, ToNetwork}; use super::domain::ROOT_DOMAIN; -use super::opt::OptionData; +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; +use crate::dns::rfc::opt::opt_rr::{OptOption, OptionList}; use crate::show::{DisplayOptions, ToColor}; use log::{debug, trace}; @@ -212,6 +211,10 @@ const LENGTH: usize = 5; const CLASS: usize = 4; const TTL_INT: usize = 7; const TTL_STRING: usize = 12; +const PAYLOAD: usize = 5; +const EXTCODE: usize = 5; +const VERSION: usize = 5; +const FLAGS: usize = 5; // don't use Show trait to provide extra length used to align output // use this function @@ -244,7 +247,6 @@ impl ResourceRecord { None } - // local function to print out RR fn display(&self, fmt: &str, raw_ttl: bool, name_length: usize) { for f in fmt.split(",") { match f.trim() { @@ -266,6 +268,28 @@ impl ResourceRecord { } } "rdata" => 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!("{: (), } } @@ -285,15 +309,18 @@ impl ResourceRecord { if display_options.short { println!("{}", self.r_data.to_color()); } else if self.r#type != QType::OPT { - const ALL_FIELDS: &str = "name,type,length,class,ttl,length,rdata"; + const ALL_FIELDS: &str = "name,type,class,ttl,length,rdata"; self.display(ALL_FIELDS, display_options.raw_ttl, name_length); println!(); } else { - if let RData::OPT(opt) = &self.r_data { - for option in opt { - println!("{}", option); - } - } + const ALL_FIELDS: &str = "name,type,length,payload,extcode,version,flags,length,rdata"; + // if let RData::OPT(opt) = &self.r_data { + // for option in opt { + // println!("{}", option); + // } + // } + self.display(ALL_FIELDS, display_options.raw_ttl, name_length); + println!(); } } } @@ -314,12 +341,12 @@ impl OPT { r#type: QType::OPT, opt_or_class_ttl: OptOrClassTtl::Opt(opt_payload), rd_length: 0, - r_data: RData::OPT(Vec::new()), // no options added yet + r_data: RData::OPT(OptionList::default()), // no options added yet } } - // add another option in OPT record - pub fn add_option(&mut self, data: T) { + // add another option in OPT record? Only valid for queries + pub fn add_option(&mut self, data: T) { // build the option structure let option = OptOption { code: data.code(), @@ -334,10 +361,9 @@ impl OPT { if let RData::OPT(opt) = &mut self.r_data { opt.push(option); } - } + } } - impl fmt::Display for ResourceRecord { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -345,8 +371,6 @@ impl fmt::Display for ResourceRecord { "{:<28} {:<10} {} {:<10}", self.name.to_string(), self.r#type.to_string(), - // self.class.to_string(), - // self.ttl.to_string(), self.opt_or_class_ttl, self.rd_length )?; @@ -401,7 +425,7 @@ impl<'a> FromNetworkOrder<'a> for ResourceRecord { // a specific processing when OPT record has no options (rd_length == 0) // because by default RData enum is UNKNOWN else if self.r#type == QType::OPT { - self.r_data = RData::OPT(Vec::new()); + self.r_data = RData::OPT(OptionList::default()); } Ok(()) diff --git a/src/dns/rfc/response.rs b/src/dns/rfc/response.rs index ea95ce2..6c27d2f 100644 --- a/src/dns/rfc/response.rs +++ b/src/dns/rfc/response.rs @@ -5,7 +5,8 @@ use serde::Serialize; use type2network::FromNetworkOrder; use super::{ - domain::DomainName, header::Header, qtype::QType, question::Question, resource_record::ResourceRecord, rrset::RRSet, + 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}; @@ -22,9 +23,9 @@ pub enum ResponseSection { pub struct Response { pub header: Header, pub question: Question, - pub answer: Option, - pub(super) authority: Option, - pub(super) additional: Option, + pub answer: Option, + pub(super) authority: Option, + pub(super) additional: Option, } // hide internal fields @@ -209,17 +210,17 @@ impl<'a> FromNetworkOrder<'a> for Response { // vector to the number received if self.header.an_count > 0 { // self.answer = Some(Vec::with_capacity(self.header.an_count as usize)); - self.answer = Some(RRSet::with_capacity(self.header.an_count as usize)); + self.answer = Some(RRList::with_capacity(self.header.an_count as usize)); self.answer.deserialize_from(buffer)?; } if self.header.ns_count > 0 { - self.authority = Some(RRSet::with_capacity(self.header.ns_count as usize)); + self.authority = Some(RRList::with_capacity(self.header.ns_count as usize)); self.authority.deserialize_from(buffer)?; } if self.header.ar_count > 0 { - self.additional = Some(RRSet::with_capacity(self.header.ar_count as usize)); + self.additional = Some(RRList::with_capacity(self.header.ar_count as usize)); self.additional.deserialize_from(buffer)?; } diff --git a/src/dns/rfc/rrset.rs b/src/dns/rfc/rrlist.rs similarity index 96% rename from src/dns/rfc/rrset.rs rename to src/dns/rfc/rrlist.rs index b365acb..445a8e2 100644 --- a/src/dns/rfc/rrset.rs +++ b/src/dns/rfc/rrlist.rs @@ -13,10 +13,10 @@ use super::{domain::DomainName, qtype::QType, resource_record::ResourceRecord}; use crate::show::{DisplayOptions, Show}; #[derive(Debug, Default, FromNetwork, Serialize)] -pub struct RRSet(Vec); +pub struct RRList(Vec); -impl RRSet { - // necessery for deserialization +impl RRList { + // necessary for deserialization pub fn with_capacity(capa: usize) -> Self { Self(Vec::with_capacity(capa)) } @@ -48,7 +48,7 @@ impl RRSet { } } -impl Deref for RRSet { +impl Deref for RRList { type Target = Vec; fn deref(&self) -> &Self::Target { @@ -56,7 +56,7 @@ impl Deref for RRSet { } } -impl fmt::Display for RRSet { +impl fmt::Display for RRList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for rr in &self.0 { writeln!(f, "{}", rr)?; @@ -66,7 +66,7 @@ impl fmt::Display for RRSet { } } -impl Show for RRSet { +impl Show for RRList { fn show(&self, display_options: &DisplayOptions, max_length: Option) { // let max_length = if display_options.align_names { // self.max_length() diff --git a/src/dns/rfc/rrsig.rs b/src/dns/rfc/rrsig.rs index 14225f0..18e03ee 100644 --- a/src/dns/rfc/rrsig.rs +++ b/src/dns/rfc/rrsig.rs @@ -68,7 +68,7 @@ impl fmt::Display for RRSIG { self.sign_expiration, self.sign_inception, self.key_tag, - self.signature.to_b64() + self.signature.to_base64() ) } } @@ -90,7 +90,7 @@ impl Serialize for RRSIG { seq.serialize_entry("sign_inception", &self.sign_inception.to_string())?; seq.serialize_entry("name", &self.name)?; - seq.serialize_entry("signature", &self.signature.to_b64())?; + seq.serialize_entry("signature", &self.signature.to_base64())?; seq.end() } } diff --git a/src/error.rs b/src/error.rs index 0ec49c3..a3f0e6f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,17 +1,12 @@ //! A dedicated error for all possible errors in DNS queries: I/O, DNS packet unconsistencies, etc -use std::fmt::Display; -use std::net::{AddrParseError, SocketAddr}; -use std::num::ParseIntError; +use std::net::AddrParseError; use std::path::PathBuf; use std::process::ExitCode; -use std::str; use std::time::Duration; use std::{fmt, io}; use thiserror::Error; -use crate::dns::rfc::response_code::ResponseCode; - /// A specific custom `Result` for all functions pub type Result = std::result::Result; diff --git a/src/protocol.rs b/src/protocol.rs index 4ad84d5..98305f5 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,6 +1,3 @@ -use std::fs::File; -use std::io::Write; - use log::{debug, info}; use crate::error::{self, Error}; diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 373af19..d5b47b9 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,7 +1,6 @@ use std::fmt::Debug; use std::io::{ErrorKind, Read}; use std::net::{SocketAddr, TcpStream, ToSocketAddrs}; -use std::path::PathBuf; use std::time::Duration; use endpoint::EndPoint;