diff --git a/Cargo.toml b/Cargo.toml index 2fc7513..b8a0143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dns-message-parser" -version = "0.8.0" +version = "0.9.0" authors = ["LinkTed "] edition = "2018" readme = "README.md" @@ -28,7 +28,7 @@ hex = "0.4" thiserror = "2" [dev-dependencies] -criterion = "0.5" +criterion = "0.6" [[bench]] name = "message" diff --git a/README.md b/README.md index 347dbb8..76a78e4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A library to encode and decode DNS packets ([RFC1035](https://tools.ietf.org/htm Add this to your `Cargo.toml`: ```toml [dependencies] -dns-message-parser = "0.7.0" +dns-message-parser = "0.9.0" ``` ## Example diff --git a/src/decode/decoder.rs b/src/decode/decoder.rs index 35f30dc..62aad8c 100644 --- a/src/decode/decoder.rs +++ b/src/decode/decoder.rs @@ -29,12 +29,12 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { } } - pub(super) fn new_main_offset(&self, offset: usize) -> Decoder<'static, 'static> { + pub(super) fn new_main_offset(&self, offset: u16) -> Decoder<'static, 'static> { let main = self.get_main(); Decoder { parent: None, bytes: main.bytes.clone(), - offset, + offset: offset as usize, } } @@ -57,6 +57,14 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { } } + pub(super) fn remaining(&self) -> DecodeResult { + let bytes_len = self.bytes.len(); + match bytes_len.checked_sub(self.offset) { + Some(remaining) => Ok(remaining), + None => Err(DecodeError::NotEnoughBytes(bytes_len, self.offset)), + } + } + pub(super) fn finished(self) -> DecodeResult<()> { match self.is_finished()? { true => Ok(()), diff --git a/src/decode/domain_name.rs b/src/decode/domain_name.rs index 33c63da..e73ef59 100644 --- a/src/decode/domain_name.rs +++ b/src/decode/domain_name.rs @@ -1,7 +1,7 @@ use crate::{ decode::Decoder, domain_name::DOMAIN_NAME_MAX_RECURSION, DecodeError, DecodeResult, DomainName, }; -use std::{collections::HashSet, str::from_utf8, usize}; +use std::{collections::HashSet, str::from_utf8}; const COMPRESSION_BITS: u8 = 0b1100_0000; const COMPRESSION_BITS_REV: u8 = 0b0011_1111; @@ -12,67 +12,75 @@ const fn is_compressed(length: u8) -> bool { } #[inline] -const fn get_offset(length_1: u8, length_2: u8) -> usize { - (((length_1 & COMPRESSION_BITS_REV) as usize) << 8) | length_2 as usize +const fn get_offset(length_1: u8, length_2: u8) -> u16 { + (((length_1 & COMPRESSION_BITS_REV) as u16) << 8) | length_2 as u16 +} + +enum DomainNameLength { + Compressed(u16), + Label(u8), } impl<'a, 'b: 'a> Decoder<'a, 'b> { + fn domain_name_length(&mut self) -> DecodeResult { + let length = self.u8()?; + if is_compressed(length) { + let offset = self.u8()?; + Ok(DomainNameLength::Compressed(get_offset(length, offset))) + } else { + Ok(DomainNameLength::Label(length)) + } + } + pub(super) fn domain_name(&mut self) -> DecodeResult { let mut domain_name = DomainName::default(); - let mut length = self.u8()?; - while length != 0 { - if is_compressed(length) { - let mut recursions = HashSet::new(); - self.domain_name_recursion(&mut domain_name, &mut recursions, length)?; - return Ok(domain_name); - } else { - length = self.domain_name_label(&mut domain_name, length)?; + loop { + match self.domain_name_length()? { + DomainNameLength::Compressed(offset) => { + self.domain_name_recursion(&mut domain_name, offset)?; + return Ok(domain_name); + } + DomainNameLength::Label(0) => return Ok(domain_name), + DomainNameLength::Label(length) => { + self.domain_name_label(&mut domain_name, length)? + } } } - Ok(domain_name) } - fn domain_name_label(&mut self, domain_name: &mut DomainName, length: u8) -> DecodeResult { + fn domain_name_label(&mut self, domain_name: &mut DomainName, length: u8) -> DecodeResult<()> { let buffer = self.read(length as usize)?; let label = from_utf8(buffer.as_ref())?; let label = label.parse()?; domain_name.append_label(label)?; - self.u8() + Ok(()) } - fn domain_name_recursion( - &mut self, - domain_name: &mut DomainName, - recursions: &mut HashSet, - mut length: u8, - ) -> DecodeResult<()> { - let mut buffer = self.u8()?; - let mut offset = get_offset(length, buffer); + fn domain_name_recursion(&self, domain_name: &mut DomainName, offset: u16) -> DecodeResult<()> { let mut decoder = self.new_main_offset(offset); + let mut recursions = HashSet::new(); - length = decoder.u8()?; - - while length != 0 { - if is_compressed(length) { - buffer = decoder.u8()?; - offset = get_offset(length, buffer); - if recursions.insert(offset) { - let recursions_len = recursions.len(); - if recursions_len > DOMAIN_NAME_MAX_RECURSION { - return Err(DecodeError::MaxRecursion(recursions_len)); + loop { + match decoder.domain_name_length()? { + DomainNameLength::Compressed(offset) => { + if recursions.insert(offset) { + let recursions_len = recursions.len(); + if recursions_len > DOMAIN_NAME_MAX_RECURSION { + return Err(DecodeError::MaxRecursion(recursions_len)); + } + } else { + return Err(DecodeError::EndlessRecursion(offset)); } - } else { - return Err(DecodeError::EndlessRecursion(offset)); + + decoder.offset = offset as usize; + } + DomainNameLength::Label(0) => return Ok(()), + DomainNameLength::Label(length) => { + decoder.domain_name_label(domain_name, length)?; } - decoder.offset = offset as usize; - length = decoder.u8()?; - } else { - length = decoder.domain_name_label(domain_name, length)?; } } - - Ok(()) } } diff --git a/src/decode/error.rs b/src/decode/error.rs index dbf562e..dcfe06b 100644 --- a/src/decode/error.rs +++ b/src/decode/error.rs @@ -1,4 +1,4 @@ -use crate::rr::edns::CookieError; +use crate::rr::edns::{CookieError, ExtendedDNSErrorExtraTextError}; use crate::rr::{AddressError, Class, ISDNError, PSDNAddressError, TagError, Type}; use crate::{Dns, DomainName, DomainNameError, LabelError}; use hex::FromHexError; @@ -69,6 +69,10 @@ pub enum DecodeError { CookieError(#[from] CookieError), #[error("Could not decode AddressNumber: {0}")] EcsAddressNumber(u16), + #[error("Could not decode EcsAddressNumber: {0}")] + ExtendedDNSErrorCodes(u16), + #[error("Could not decode ExtendedDNSErrorExtraText: {0}")] + ExtendedDNSErrorExtraTextError(#[from] ExtendedDNSErrorExtraTextError), #[error("The IPv4 Address is too big: {0}")] EcsTooBigIpv4Address(usize), #[error("The IPv6 Address is too big: {0}")] @@ -90,7 +94,7 @@ pub enum DecodeError { #[error("Could not decode the domain name, the because maximum recursion is reached: {0}")] MaxRecursion(usize), #[error("Could not decode the domain name, because an endless recursion was detected: {0}")] - EndlessRecursion(usize), + EndlessRecursion(u16), #[error("The are remaining bytes, which was not parsed")] RemainingBytes(usize, Dns), #[error("Padding is not zero: {0}")] diff --git a/src/decode/helpers.rs b/src/decode/helpers.rs index 4b56450..3ba9687 100644 --- a/src/decode/helpers.rs +++ b/src/decode/helpers.rs @@ -27,8 +27,12 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { Ok(buffer.get_u64()) } - pub(super) fn string(&mut self) -> DecodeResult { + pub(super) fn string_with_len(&mut self) -> DecodeResult { let length = self.u8()? as usize; + self.string(length) + } + + pub(super) fn string(&mut self, length: usize) -> DecodeResult { let buffer = self.read(length)?; let string = from_utf8(buffer.as_ref())?; Ok(String::from(string)) diff --git a/src/decode/rr/draft_ietf_dnsop_svcb_https.rs b/src/decode/rr/draft_ietf_dnsop_svcb_https.rs index ea83542..82c97e8 100644 --- a/src/decode/rr/draft_ietf_dnsop_svcb_https.rs +++ b/src/decode/rr/draft_ietf_dnsop_svcb_https.rs @@ -51,8 +51,7 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { /// Decode a single service parameter /// /// Parameters: - /// - `service_parameter_key` - the IANA-controlled numeric identifier as defined in section - /// 14.3 of the RFC + /// - `service_parameter_key` - the IANA-controlled numeric identifier as defined in section 14.3 of the RFC /// /// Returns: /// - `Ok(ServiceParameter)` - if there were no issues decoding the value @@ -72,7 +71,7 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { 1 => { let mut alpn_ids = vec![]; while !self.is_finished()? { - alpn_ids.push(self.string()?); + alpn_ids.push(self.string_with_len()?); } ServiceParameter::ALPN { alpn_ids } } diff --git a/src/decode/rr/edns/mod.rs b/src/decode/rr/edns/mod.rs index a556863..8742058 100644 --- a/src/decode/rr/edns/mod.rs +++ b/src/decode/rr/edns/mod.rs @@ -2,3 +2,4 @@ mod rfc_6891; mod rfc_7830; mod rfc_7871; mod rfc_7873; +mod rfc_8914; diff --git a/src/decode/rr/edns/rfc_6891.rs b/src/decode/rr/edns/rfc_6891.rs index d2b95b9..cc5d6dd 100644 --- a/src/decode/rr/edns/rfc_6891.rs +++ b/src/decode/rr/edns/rfc_6891.rs @@ -25,13 +25,16 @@ impl<'a, 'b: 'a> Decoder<'b, 'b> { fn rr_edns_option(&'a mut self) -> DecodeResult { let edns_option_code = self.rr_edns_option_code()?; let edns_option_length = self.u16()?; - let mut ends_option_data = self.sub(edns_option_length)?; + let mut edns_option_data = self.sub(edns_option_length)?; let edns_option = match edns_option_code { - EDNSOptionCode::ECS => EDNSOption::ECS(ends_option_data.rr_edns_ecs()?), - EDNSOptionCode::Cookie => EDNSOption::Cookie(ends_option_data.rr_edns_cookie()?), - EDNSOptionCode::Padding => EDNSOption::Padding(ends_option_data.rr_edns_padding()?), + EDNSOptionCode::ECS => EDNSOption::ECS(edns_option_data.rr_edns_ecs()?), + EDNSOptionCode::Cookie => EDNSOption::Cookie(edns_option_data.rr_edns_cookie()?), + EDNSOptionCode::Padding => EDNSOption::Padding(edns_option_data.rr_edns_padding()?), + EDNSOptionCode::ExtendedDnsError => { + EDNSOption::ExtendedDNSErrors(edns_option_data.rr_edns_extended_dns_errors()?) + } }; - ends_option_data.finished()?; + edns_option_data.finished()?; Ok(edns_option) } diff --git a/src/decode/rr/edns/rfc_8914.rs b/src/decode/rr/edns/rfc_8914.rs new file mode 100644 index 0000000..fd209cd --- /dev/null +++ b/src/decode/rr/edns/rfc_8914.rs @@ -0,0 +1,34 @@ +use crate::decode::Decoder; +use crate::rr::edns::{ExtendedDNSErrorCodes, ExtendedDNSErrorExtraText, ExtendedDNSErrors}; +use crate::{DecodeError, DecodeResult}; +use std::convert::TryFrom; + +impl<'a, 'b: 'a> Decoder<'a, 'b> { + fn rr_edns_extended_dns_error_codes(&mut self) -> DecodeResult { + let buffer = self.u16()?; + match ExtendedDNSErrorCodes::try_from(buffer) { + Ok(edns_extended_dns_errors_codes) => Ok(edns_extended_dns_errors_codes), + Err(buffer) => Err(DecodeError::ExtendedDNSErrorCodes(buffer)), + } + } + + fn rr_edns_extended_dns_errors_extra_text( + &mut self, + ) -> DecodeResult { + let extended_dns_errors_extra_text = + ExtendedDNSErrorExtraText::try_from(self.string(self.remaining()?)?)?; + Ok(extended_dns_errors_extra_text) + } + + pub(super) fn rr_edns_extended_dns_errors(&mut self) -> DecodeResult { + println!("AA"); + let info_code = self.rr_edns_extended_dns_error_codes()?; + println!("AA"); + let extra_text = self.rr_edns_extended_dns_errors_extra_text()?; + println!("AA {0}", extra_text.as_ref().is_empty()); + Ok(ExtendedDNSErrors { + info_code, + extra_text, + }) + } +} diff --git a/src/decode/rr/rfc_1035.rs b/src/decode/rr/rfc_1035.rs index e28565f..9c26bf5 100644 --- a/src/decode/rr/rfc_1035.rs +++ b/src/decode/rr/rfc_1035.rs @@ -85,8 +85,8 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { pub(super) fn rr_hinfo(&mut self, header: Header) -> DecodeResult { let class = header.get_class()?; - let cpu = self.string()?; - let os = self.string()?; + let cpu = self.string_with_len()?; + let os = self.string_with_len()?; let hinfo = HINFO { domain_name: header.domain_name, ttl: header.ttl, @@ -105,7 +105,7 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { let class = header.get_class()?; let mut strings = Vec::new(); while !self.is_finished()? { - strings.push(self.string()?); + strings.push(self.string_with_len()?); } let strings = strings.try_into().map_err(|_| DecodeError::TXTEmpty)?; let txt = TXT { diff --git a/src/decode/rr/rfc_1183.rs b/src/decode/rr/rfc_1183.rs index 3bacc7d..57fbb91 100644 --- a/src/decode/rr/rfc_1183.rs +++ b/src/decode/rr/rfc_1183.rs @@ -31,7 +31,7 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { pub(super) fn rr_x25(&mut self, header: Header) -> DecodeResult { let class = header.get_class()?; - let psdn_address = self.string()?; + let psdn_address = self.string_with_len()?; let psdn_address = PSDNAddress::try_from(psdn_address)?; let x25 = X25 { @@ -45,13 +45,13 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { pub(super) fn rr_isdn(&mut self, header: Header) -> DecodeResult { let class = header.get_class()?; - let isdn_address = self.string()?; + let isdn_address = self.string_with_len()?; let isdn_address = ISDNAddress::try_from(isdn_address)?; let sa = if self.is_finished()? { None } else { - let sa = self.string()?; + let sa = self.string_with_len()?; let sa = SA::try_from(sa)?; Some(sa) }; diff --git a/src/decode/rr/rfc_1712.rs b/src/decode/rr/rfc_1712.rs index 218925e..9458fd1 100644 --- a/src/decode/rr/rfc_1712.rs +++ b/src/decode/rr/rfc_1712.rs @@ -8,19 +8,19 @@ impl<'a, 'b: 'a> Decoder<'a, 'b> { let class = header.get_class()?; // TODO String value check - let longitude = self.string()?; + let longitude = self.string_with_len()?; let longitude_len = longitude.len(); if !(1..=256).contains(&longitude_len) { return Err(DecodeError::GPOS); } - let latitude = self.string()?; + let latitude = self.string_with_len()?; let latitude_len = latitude.len(); if !(1..=256).contains(&latitude_len) { return Err(DecodeError::GPOS); } - let altitude = self.string()?; + let altitude = self.string_with_len()?; let altitude_len = altitude.len(); if !(1..=256).contains(&altitude_len) { return Err(DecodeError::GPOS); diff --git a/src/decode/rr/rfc_8659.rs b/src/decode/rr/rfc_8659.rs index 9064dbd..70e75e8 100644 --- a/src/decode/rr/rfc_8659.rs +++ b/src/decode/rr/rfc_8659.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; impl<'a, 'b: 'a> Decoder<'a, 'b> { fn rr_caa_tag(&mut self) -> DecodeResult { - let tag = self.string()?; + let tag = self.string_with_len()?; let tag = Tag::try_from(tag)?; Ok(tag) } diff --git a/src/decode/tests.rs b/src/decode/tests.rs index cea3f6a..5c43498 100644 --- a/src/decode/tests.rs +++ b/src/decode/tests.rs @@ -31,7 +31,7 @@ fn u32_error() { fn string_error_1() { let bytes = Bytes::copy_from_slice(&b""[..]); let mut decoder = Decoder::main(bytes); - let result = decoder.string(); + let result = decoder.string_with_len(); assert_eq!(result, Err(DecodeError::NotEnoughBytes(0, 1))); } @@ -39,7 +39,7 @@ fn string_error_1() { fn string_error_2() { let bytes = Bytes::copy_from_slice(&b"\x0f\x41\x42"[..]); let mut decoder = Decoder::main(bytes); - let result = decoder.string(); + let result = decoder.string_with_len(); assert_eq!(result, Err(DecodeError::NotEnoughBytes(3, 16))); } @@ -47,7 +47,7 @@ fn string_error_2() { fn string_error_3() { let bytes = Bytes::copy_from_slice(&b"\x02\x00\xff"[..]); let mut decoder = Decoder::main(bytes); - let result = decoder.string(); + let result = decoder.string_with_len(); let bytes = Bytes::copy_from_slice(&b"\x02\x00\xff"[..]); let excepted = from_utf8(&bytes[1..]).unwrap_err(); assert_eq!(result, Err(DecodeError::Utf8Error(excepted))); diff --git a/src/encode/domain_name.rs b/src/encode/domain_name.rs index 02e4803..214f413 100644 --- a/src/encode/domain_name.rs +++ b/src/encode/domain_name.rs @@ -33,7 +33,7 @@ impl Encoder { #[inline] fn label(&mut self, label: &Label) -> EncodeResult { let index = self.get_offset()?; - self.string(label.as_ref())?; + self.string_with_len(label.as_ref())?; Ok(index) } @@ -67,14 +67,14 @@ impl Encoder { domain_name_index.insert(domain_name, index); } } - self.string("")?; + self.string_with_len("")?; self.merge_domain_name_index(domain_name_index, 0)?; Ok(()) } } impl DomainName { - fn iter(&self) -> DomainNameIter { + fn iter(&self) -> DomainNameIter<'_> { DomainNameIter { labels: &self.0 } } } diff --git a/src/encode/helpers.rs b/src/encode/helpers.rs index 3b2e008..3540c8e 100644 --- a/src/encode/helpers.rs +++ b/src/encode/helpers.rs @@ -68,13 +68,17 @@ impl Encoder { self.u8(octets[15]); } - pub(super) fn string(&mut self, s: &str) -> EncodeResult<()> { + pub(super) fn string_with_len(&mut self, s: &str) -> EncodeResult<()> { let length = s.len(); if length > 255 { return Err(EncodeError::String(length)); } self.u8(length as u8); + self.string(s) + } + + pub(super) fn string(&mut self, s: &str) -> EncodeResult<()> { self.bytes.extend_from_slice(s.as_bytes()); Ok(()) @@ -101,3 +105,10 @@ impl Encoder { } } } + +#[test] +fn string_with_len() { + let mut encoder = Encoder::default(); + let result = encoder.string_with_len("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + assert_eq!(result, Err(EncodeError::String(256))); +} diff --git a/src/encode/rr/draft_ietf_dnsop_svcb_https.rs b/src/encode/rr/draft_ietf_dnsop_svcb_https.rs index 61bdcea..1155473 100644 --- a/src/encode/rr/draft_ietf_dnsop_svcb_https.rs +++ b/src/encode/rr/draft_ietf_dnsop_svcb_https.rs @@ -44,7 +44,7 @@ impl Encoder { } ServiceParameter::ALPN { alpn_ids } => { for alpn_id in alpn_ids { - self.string(alpn_id)?; + self.string_with_len(alpn_id)?; } } ServiceParameter::NO_DEFAULT_ALPN => {} diff --git a/src/encode/rr/edns/mod.rs b/src/encode/rr/edns/mod.rs index a556863..8742058 100644 --- a/src/encode/rr/edns/mod.rs +++ b/src/encode/rr/edns/mod.rs @@ -2,3 +2,4 @@ mod rfc_6891; mod rfc_7830; mod rfc_7871; mod rfc_7873; +mod rfc_8914; diff --git a/src/encode/rr/edns/rfc_6891.rs b/src/encode/rr/edns/rfc_6891.rs index 4a46e08..908350f 100644 --- a/src/encode/rr/edns/rfc_6891.rs +++ b/src/encode/rr/edns/rfc_6891.rs @@ -24,6 +24,9 @@ impl Encoder { EDNSOption::ECS(ecs) => self.rr_edns_ecs(ecs)?, EDNSOption::Cookie(cookie) => self.rr_edns_cookie(cookie)?, EDNSOption::Padding(padding) => self.rr_edns_padding(padding), + EDNSOption::ExtendedDNSErrors(extended_dns_errors) => { + self.rr_edns_extended_dns_errors(extended_dns_errors)? + } } Ok(()) } diff --git a/src/encode/rr/edns/rfc_8914.rs b/src/encode/rr/edns/rfc_8914.rs new file mode 100644 index 0000000..7439f79 --- /dev/null +++ b/src/encode/rr/edns/rfc_8914.rs @@ -0,0 +1,16 @@ +use crate::encode::Encoder; +use crate::rr::edns::{EDNSOptionCode, ExtendedDNSErrors}; +use crate::EncodeResult; + +impl Encoder { + pub(super) fn rr_edns_extended_dns_errors( + &mut self, + extended_dns_errors: &ExtendedDNSErrors, + ) -> EncodeResult<()> { + self.rr_edns_option_code(&EDNSOptionCode::ExtendedDnsError); + let length_index = self.create_length_index(); + self.u16(extended_dns_errors.info_code as u16); + self.string(extended_dns_errors.extra_text.as_ref())?; + self.set_length_index(length_index) + } +} diff --git a/src/encode/rr/rfc_1035.rs b/src/encode/rr/rfc_1035.rs index cc244d0..3c89108 100644 --- a/src/encode/rr/rfc_1035.rs +++ b/src/encode/rr/rfc_1035.rs @@ -65,8 +65,8 @@ impl Encoder { self.rr_class(&hinfo.class); self.u32(hinfo.ttl); let length_index = self.create_length_index(); - self.string(&hinfo.cpu)?; - self.string(&hinfo.os)?; + self.string_with_len(&hinfo.cpu)?; + self.string_with_len(&hinfo.os)?; self.set_length_index(length_index) } @@ -81,7 +81,7 @@ impl Encoder { self.u32(txt.ttl); let length_index = self.create_length_index(); for string in txt.strings.iter() { - self.string(string)?; + self.string_with_len(string)?; } self.set_length_index(length_index) } diff --git a/src/encode/rr/rfc_1183.rs b/src/encode/rr/rfc_1183.rs index a46658b..3709bde 100644 --- a/src/encode/rr/rfc_1183.rs +++ b/src/encode/rr/rfc_1183.rs @@ -22,7 +22,7 @@ impl Encoder { } fn rr_x25_psdn_address(&mut self, psdn_address: &PSDNAddress) -> EncodeResult<()> { - self.string(psdn_address) + self.string_with_len(psdn_address) } pub(super) fn rr_x25(&mut self, x25: &X25) -> EncodeResult<()> { @@ -37,12 +37,12 @@ impl Encoder { #[inline] fn rr_isdn_address(&mut self, isdn_address: &ISDNAddress) -> EncodeResult<()> { - self.string(isdn_address) + self.string_with_len(isdn_address) } #[inline] fn rr_isdn_sa(&mut self, sa: &SA) -> EncodeResult<()> { - self.string(sa) + self.string_with_len(sa) } pub(super) fn rr_isdn(&mut self, isdn: &ISDN) -> EncodeResult<()> { diff --git a/src/encode/rr/rfc_1712.rs b/src/encode/rr/rfc_1712.rs index efaedcc..9b722c1 100644 --- a/src/encode/rr/rfc_1712.rs +++ b/src/encode/rr/rfc_1712.rs @@ -9,9 +9,9 @@ impl Encoder { self.rr_class(&gpos.class); self.u32(gpos.ttl); let length_index = self.create_length_index(); - self.string(&gpos.longitude)?; - self.string(&gpos.latitude)?; - self.string(&gpos.altitude)?; + self.string_with_len(&gpos.longitude)?; + self.string_with_len(&gpos.latitude)?; + self.string_with_len(&gpos.altitude)?; self.set_length_index(length_index) } } diff --git a/src/encode/rr/rfc_8659.rs b/src/encode/rr/rfc_8659.rs index bdce7f7..e18b5e1 100644 --- a/src/encode/rr/rfc_8659.rs +++ b/src/encode/rr/rfc_8659.rs @@ -4,7 +4,7 @@ use crate::EncodeResult; impl Encoder { fn rr_caa_tag(&mut self, tag: &Tag) -> EncodeResult<()> { - self.string(tag.as_ref()) + self.string_with_len(tag.as_ref()) } pub(super) fn rr_caa(&mut self, caa: &CAA) -> EncodeResult<()> { diff --git a/src/rr/edns/mod.rs b/src/rr/edns/mod.rs index b41bcf8..7a7dd60 100644 --- a/src/rr/edns/mod.rs +++ b/src/rr/edns/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod rfc_6891; mod rfc_7830; mod rfc_7871; mod rfc_7873; +mod rfc_8914; //pub use rfc_6891::OPT; pub use rfc_6891::{EDNSOption, EDNSOptionCode, EDNS_DNSSEC_MASK}; @@ -11,3 +12,7 @@ pub use rfc_7873::{ Cookie, CookieError, CLIENT_COOKIE_LENGTH, MAXIMUM_SERVER_COOKIE_LENGTH, MINIMUM_SERVER_COOKIE_LENGTH, }; +pub use rfc_8914::{ + ExtendedDNSErrorCodes, ExtendedDNSErrorExtraText, ExtendedDNSErrorExtraTextError, + ExtendedDNSErrors, +}; diff --git a/src/rr/edns/rfc_6891.rs b/src/rr/edns/rfc_6891.rs index 1af535e..203c4cc 100644 --- a/src/rr/edns/rfc_6891.rs +++ b/src/rr/edns/rfc_6891.rs @@ -1,4 +1,4 @@ -use super::{Cookie, Padding, ECS}; +use super::{Cookie, ExtendedDNSErrors, Padding, ECS}; use std::fmt::{Display, Formatter, Result as FmtResult}; pub const EDNS_DNSSEC_MASK: u8 = 0x80; @@ -10,6 +10,7 @@ try_from_enum_to_integer! { ECS = 0x00008, Cookie = 0x000a, Padding = 0x000c, + ExtendedDnsError = 0x000f, } } @@ -18,6 +19,7 @@ pub enum EDNSOption { ECS(ECS), Cookie(Cookie), Padding(Padding), + ExtendedDNSErrors(ExtendedDNSErrors), } impl Display for EDNSOption { @@ -26,6 +28,7 @@ impl Display for EDNSOption { EDNSOption::ECS(ecs) => ecs.fmt(f), EDNSOption::Cookie(cookie) => cookie.fmt(f), EDNSOption::Padding(padding) => padding.fmt(f), + EDNSOption::ExtendedDNSErrors(extended_dns_errors) => extended_dns_errors.fmt(f), } } } diff --git a/src/rr/edns/rfc_8914.rs b/src/rr/edns/rfc_8914.rs new file mode 100644 index 0000000..8ed5d40 --- /dev/null +++ b/src/rr/edns/rfc_8914.rs @@ -0,0 +1,186 @@ +use std::{ + convert::TryFrom, + fmt::{Display, Formatter, Result as FmtResult}, +}; +use thiserror::Error; + +pub const EXTENDED_DNS_ERROR_EXTRA_TEXT_ERROR: usize = + u16::MAX as usize - std::mem::size_of::(); + +try_from_enum_to_integer! { + #[repr(u16)] + /// The [Extended DNS Error Codes] field in the [Extended DNS Errors]. + /// + /// [type]: https://tools.ietf.org/html/rfc8914#section-5.2-3 + /// [Extended DNS Errors]: crate::rr::edns::ExtendedDNSErrors + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum ExtendedDNSErrorCodes { + /// The [Other] type. + /// + /// [Other]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-0-o + Other = 0, + /// The [Unsupported DNSKEY Algorithm] type. + /// + /// [Unsupported DNSKEY Algorithm]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-1-u + UnsupportedDNSKEYAlgorithm = 1, + /// The [Unsupported DS Digest Type] type. + /// + /// [Unsupported DS Digest Type]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-2-u + UnsupportedDSDigestType = 2, + /// The [Stale Answer] type. + /// + /// [Stale Answer]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-3-s + StaleAnswer = 3, + /// The [Forged Answer] type. + /// + /// [Forged Answer]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-4-f + ForgedAnswer = 4, + /// The [DNSSEC Indeterminate] type. + /// + /// [DNSSEC Indeterminate]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-5-d + DNSSECIndeterminate = 5, + /// The [DNSSEC Bogus] type. + /// + /// [DNSSEC Bogus]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-6-d + DNSSECBogus = 6, + /// The [Signature Expired] type. + /// + /// [Signature Expired]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-7-s + SignatureExpired = 7, + /// The [Signature Not Yet Valid] type. + /// + /// [Signature Not Yet Valid]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-8-s + SignatureNotYetValid = 8, + /// The [DNSKEY Missing] type. + /// + /// [DNSKEY Missing]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-9-d + DNSKEYMissing = 9, + /// The [RRSIGs Missing] type. + /// + /// [RRSIGs Missing]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-10- + RRSIGsMissing = 10, + /// The [No Zone Key Bit Set] type. + /// + /// [No Zone Key Bit Set]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-11- + NoZoneKeyBitSet = 11, + /// The [NSEC Missing] type. + /// + /// [NSEC Missing]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-12- + NSECMissing = 12, + /// The [Cached Error] type. + /// + /// [Cached Error]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-13- + CachedError = 13, + /// The [Not Ready] type. + /// + /// [Not Ready]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-14- + NotReady = 14, + /// The [Blocked] type. + /// + /// [Blocked]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-15- + Blocked = 15, + /// The [Censored] type. + /// + /// [Censored]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-16- + Censored = 16, + /// The [Filtered] type. + /// + /// [Filtered]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-17- + Filtered = 17, + /// The [Prohibited] type. + /// + /// [Prohibited]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-18- + Prohibited = 18, + /// The [Stale NXDOMAIN Answer] type. + /// + /// [Stale NXDOMAIN Answer]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-19- + StaleNXDomainAnswer = 19, + /// The [Not Authoritative] type. + /// + /// [Not Authoritative]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-20- + NotAuthoritative = 20, + /// The [Not Supported] type. + /// + /// [Not Supported]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-21- + NotSupported = 21, + /// The [No Reachable Authority] type. + /// + /// [No Reachable Authority]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-22- + NoReachableAuthority = 22, + /// The [Network Error] type. + /// + /// [Network Error]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-23- + NetworkError = 23, + /// The [Invalid Data] type. + /// + /// [Invalid Data]: https://datatracker.ietf.org/doc/html/rfc8914#name-extended-dns-error-code-24- + InvalidData = 24, + } +} + +#[derive(Debug, PartialEq, Clone, Eq, Hash)] +pub struct ExtendedDNSErrorExtraText { + inner: String, +} + +impl Display for ExtendedDNSErrorExtraText { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "{}", self.inner) + } +} + +impl AsRef for ExtendedDNSErrorExtraText { + fn as_ref(&self) -> &str { + &self.inner + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)] +pub enum ExtendedDNSErrorExtraTextError { + #[error("Text too big {0}")] + TooBig(usize), +} + +impl TryFrom<&str> for ExtendedDNSErrorExtraText { + type Error = ExtendedDNSErrorExtraTextError; + + fn try_from(text: &str) -> Result { + let len = text.len(); + if len <= EXTENDED_DNS_ERROR_EXTRA_TEXT_ERROR { + Ok(ExtendedDNSErrorExtraText { + inner: text.to_owned(), + }) + } else { + Err(ExtendedDNSErrorExtraTextError::TooBig(len)) + } + } +} + +impl TryFrom for ExtendedDNSErrorExtraText { + type Error = ExtendedDNSErrorExtraTextError; + + fn try_from(text: String) -> Result { + let len = text.len(); + if len <= EXTENDED_DNS_ERROR_EXTRA_TEXT_ERROR { + Ok(ExtendedDNSErrorExtraText { inner: text }) + } else { + Err(ExtendedDNSErrorExtraTextError::TooBig(len)) + } + } +} + +#[derive(Debug, PartialEq, Clone, Eq, Hash)] +pub struct ExtendedDNSErrors { + pub info_code: ExtendedDNSErrorCodes, + pub extra_text: ExtendedDNSErrorExtraText, +} + +impl Display for ExtendedDNSErrors { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!( + f, + "Extended DNS Errors {} {}", + self.info_code, self.extra_text + ) + } +} diff --git a/tests/decode.rs b/tests/decode.rs index 0226901..3ec170f 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -1,3 +1,5 @@ +use std::{convert::TryFrom, str::FromStr}; + use bytes::Bytes; use dns_message_parser::Dns; @@ -24,3 +26,66 @@ fn response() { let dns = decode_msg(&msg[..]); assert!(dns.is_response()); } + +#[test] +fn example_net_edns_ede_forged() { + let msg = b"\x16\x5a\x81\x83\x00\x01\x00\x00\x00\x00\x00\x02\x07\x65\x78\x61\x6d\x70\x6c\x65\ + \x03\x6e\x65\x74\x00\x00\x01\x00\x01\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x6f\x72\x67\x00\x00\x06\x00\x01\x00\x09\ + \x3a\x80\x00\x1e\xc0\x1d\x05\x65\x6d\x61\x69\x6c\xc0\x1d\x00\x00\x00\x02\x00\x09\x3a\x80\x00\x01\x51\x80\x00\x24\ + \xea\x00\x00\x09\x3a\x80\x00\x00\x29\x04\xd0\x00\x00\x00\x00\x00\x08\x00\x0f\x00\x04\x00\x12\x41\x41"; + let dns = decode_msg(&msg[..]); + assert_eq!( + dns, + dns_message_parser::Dns { + id: 5722, + flags: dns_message_parser::Flags { + qr: true, + opcode: dns_message_parser::Opcode::Query, + aa: false, + tc: false, + rd: true, + ra: true, + ad: false, + cd: false, + rcode: dns_message_parser::RCode::NXDomain + }, + questions: vec![dns_message_parser::question::Question { + domain_name: dns_message_parser::DomainName::from_str("example.net").unwrap(), + q_class: dns_message_parser::question::QClass::IN, + q_type: dns_message_parser::question::QType::A + }], + answers: vec![], + authorities: vec![], + additionals: vec![ + dns_message_parser::rr::RR::SOA(dns_message_parser::rr::SOA { + domain_name: dns_message_parser::DomainName::from_str("example.org").unwrap(), + ttl: 604800, + class: dns_message_parser::rr::Class::IN, + m_name: dns_message_parser::DomainName::from_str("example.org").unwrap(), + r_name: dns_message_parser::DomainName::from_str("email.example.org").unwrap(), + serial: 2, + refresh: 604800, + retry: 86400, + expire: 2419200, + min_ttl: 604800 + }), + dns_message_parser::rr::RR::OPT(dns_message_parser::rr::OPT { + requestor_payload_size: 1232, + extend_rcode: 0, + version: 0, + dnssec: false, + edns_options: vec![ + dns_message_parser::rr::edns::EDNSOption::ExtendedDNSErrors( + dns_message_parser::rr::edns::ExtendedDNSErrors { + info_code: + dns_message_parser::rr::edns::ExtendedDNSErrorCodes::Prohibited, + extra_text: + dns_message_parser::rr::edns::ExtendedDNSErrorExtraText::try_from("AA").unwrap(), + } + ) + ] + }) + ] + } + ); +} diff --git a/tests/decode_encode_decode.rs b/tests/decode_encode_decode.rs index b0efce3..43195a7 100644 --- a/tests/decode_encode_decode.rs +++ b/tests/decode_encode_decode.rs @@ -741,3 +741,30 @@ fn caa_example_org_response() { \x00\x0a\x80\x03\x74\x61\x67\x56\x41\x4c\x55\x45"; decode_encode_decode(&msg[..]); } + +#[test] +fn example_net_edns_ede_blocked() { + let msg = b"\xba\x59\x81\x83\x00\x01\x00\x00\x00\x00\x00\x02\x07\x65\x78\x61\x6d\x70\x6c\x65\ + \x03\x6e\x65\x74\x00\x00\x01\x00\x01\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x6f\x72\x67\x00\x00\x06\x00\x01\x00\x09\ + \x3a\x80\x00\x1e\xc0\x1d\x05\x65\x6d\x61\x69\x6c\xc0\x1d\x00\x00\x00\x02\x00\x09\x3a\x80\x00\x01\x51\x80\x00\x24\ + \xea\x00\x00\x09\x3a\x80\x00\x00\x29\x04\xd0\x00\x00\x00\x00\x00\x06\x00\x0f\x00\x02\x00\x0f"; + decode_encode_decode(&msg[..]); +} + +#[test] +fn example_net_edns_ede_censored() { + let msg = b"\x73\xf3\x81\x83\x00\x01\x00\x00\x00\x00\x00\x02\x07\x65\x78\x61\x6d\x70\x6c\x65\ + \x03\x6e\x65\x74\x00\x00\x01\x00\x01\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x6f\x72\x67\x00\x00\x06\x00\x01\x00\x09\ + \x3a\x80\x00\x1e\xc0\x1d\x05\x65\x6d\x61\x69\x6c\xc0\x1d\x00\x00\x00\x02\x00\x09\x3a\x80\x00\x01\x51\x80\x00\x24\ + \xea\x00\x00\x09\x3a\x80\x00\x00\x29\x04\xd0\x00\x00\x00\x00\x00\x06\x00\x0f\x00\x02\x00\x11"; + decode_encode_decode(&msg[..]); +} + +#[test] +fn example_net_edns_ede_forged() { + let msg = b"\x16\x5a\x81\x83\x00\x01\x00\x00\x00\x00\x00\x02\x07\x65\x78\x61\x6d\x70\x6c\x65\ + \x03\x6e\x65\x74\x00\x00\x01\x00\x01\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x6f\x72\x67\x00\x00\x06\x00\x01\x00\x09\ + \x3a\x80\x00\x1e\xc0\x1d\x05\x65\x6d\x61\x69\x6c\xc0\x1d\x00\x00\x00\x02\x00\x09\x3a\x80\x00\x01\x51\x80\x00\x24\ + \xea\x00\x00\x09\x3a\x80\x00\x00\x29\x04\xd0\x00\x00\x00\x00\x00\x06\x00\x0f\x00\x02\x00\x12"; + decode_encode_decode(&msg[..]); +} diff --git a/tests/display.rs b/tests/display.rs index 35e4d7b..7e4dfe8 100644 --- a/tests/display.rs +++ b/tests/display.rs @@ -1,14 +1,17 @@ use dns_message_parser::{ question::{QClass, QType, Question}, - rr::edns::{Cookie, EDNSOption, Padding, ECS}, rr::{ + edns::{ + Cookie, EDNSOption, ExtendedDNSErrorCodes, ExtendedDNSErrorExtraText, + ExtendedDNSErrors, Padding, ECS, + }, APItem, Address, AlgorithmType, Class, DigestType, ISDNAddress, PSDNAddress, SSHFPAlgorithm, SSHFPType, ServiceBinding, ServiceParameter, Tag, A, AAAA, APL, CAA, CNAME, DNAME, DNSKEY, DS, EID, EUI48, EUI64, GPOS, HINFO, ISDN, KX, L32, L64, LP, MB, MD, MF, MG, MINFO, MR, MX, NID, NIMLOC, NS, OPT, PTR, PX, RP, RR, RT, SA, SOA, SRV, SSHFP, TXT, URI, X25, }, - {Dns, Flags, Opcode, RCode}, + Dns, Flags, Opcode, RCode, }; use std::{ collections::BTreeSet, @@ -1083,3 +1086,22 @@ fn dns() { additionals [example.org. 3600 IN A 10.0.0.10, ]", ); } + +#[test] +fn rr_opt_extended_dns_errors() { + let extended_dns_errors = ExtendedDNSErrors { + info_code: ExtendedDNSErrorCodes::UnsupportedDNSKEYAlgorithm, + extra_text: ExtendedDNSErrorExtraText::try_from("TEST").unwrap(), + }; + let rr = RR::OPT(OPT { + requestor_payload_size: 1024, + dnssec: false, + version: 0, + extend_rcode: 0, + edns_options: vec![EDNSOption::ExtendedDNSErrors(extended_dns_errors)], + }); + check_output( + &rr, + ". OPT 1024 0 0 false Extended DNS Errors UnsupportedDNSKEYAlgorithm TEST", + ); +}