From 7cf99b610ac18f4a8ec14fdb51430165a6d68410 Mon Sep 17 00:00:00 2001 From: Andy Pack Date: Mon, 29 Jan 2024 22:11:12 +0000 Subject: [PATCH] parsing questions back and forth --- Cargo.lock | 68 +---------- dnstp/Cargo.toml | 2 +- dnstp/src/dns_question.rs | 243 +++++++++++++++++++++++++++++++++++++- 3 files changed, 243 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e24a6c8..f1d1f2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,16 +130,7 @@ name = "dnstplib" version = "0.1.0" dependencies = [ "log", - "url", -] - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", + "urlencoding", ] [[package]] @@ -148,16 +139,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "itoa" version = "1.0.10" @@ -185,12 +166,6 @@ dependencies = [ "libc", ] -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - [[package]] name = "powerfmt" version = "0.2.0" @@ -303,27 +278,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -331,24 +285,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "urlencoding" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf8parse" diff --git a/dnstp/Cargo.toml b/dnstp/Cargo.toml index ce9307c..408b1dd 100644 --- a/dnstp/Cargo.toml +++ b/dnstp/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" [dependencies] log = "0.4.20" -url = "2.5.0" \ No newline at end of file +urlencoding = "2.1.3" \ No newline at end of file diff --git a/dnstp/src/dns_question.rs b/dnstp/src/dns_question.rs index d67540b..def4b54 100644 --- a/dnstp/src/dns_question.rs +++ b/dnstp/src/dns_question.rs @@ -1,4 +1,5 @@ -use url::form_urlencoded; +use std::ops::Sub; +use urlencoding::{encode, decode}; #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] enum QType { @@ -17,6 +18,29 @@ enum QType { SRV = 33 } +impl TryFrom for QType { + type Error = (); + + fn try_from(v: u8) -> Result { + match v { + x if x == QType::A as u8 => Ok(QType::A), + x if x == QType::NS as u8 => Ok(QType::NS), + x if x == QType::CNAME as u8 => Ok(QType::CNAME), + x if x == QType::SOA as u8 => Ok(QType::SOA), + x if x == QType::WKS as u8 => Ok(QType::WKS), + x if x == QType::PTR as u8 => Ok(QType::PTR), + x if x == QType::HINFO as u8 => Ok(QType::HINFO), + x if x == QType::MINFO as u8 => Ok(QType::MINFO), + x if x == QType::MX as u8 => Ok(QType::MX), + x if x == QType::TXT as u8 => Ok(QType::TXT), + x if x == QType::RP as u8 => Ok(QType::RP), + x if x == QType::AAAA as u8 => Ok(QType::AAAA), + x if x == QType::SRV as u8 => Ok(QType::SRV), + _ => Err(()), + } + } +} + #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] enum QClass { Internet = 1, @@ -24,6 +48,20 @@ enum QClass { Hesiod = 4, } +impl TryFrom for QClass { + type Error = (); + + fn try_from(v: u8) -> Result { + match v { + x if x == QClass::Internet as u8 => Ok(QClass::Internet), + x if x == QClass::Chaos as u8 => Ok(QClass::Chaos), + x if x == QClass::Hesiod as u8 => Ok(QClass::Hesiod), + _ => Err(()), + } + } +} + +#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)] struct DNSQuestion { qname: String, qtype: QType, @@ -42,16 +80,15 @@ impl DNSQuestion { pub fn to_bytes(&self) -> Vec { - let mut ret: Vec = vec!(); + let mut ret: Vec = Vec::with_capacity(self.qname.len() + 2 + 3); for part in self.qname.split(".") { - let encoded_string: String = form_urlencoded::byte_serialize(part.as_bytes()).collect(); + let encoded_string = encode(part); let count = encoded_string.len(); ret.push(count as u8); - ret.reserve(count); - for x in encoded_string.into_bytes() { + for x in encoded_string.bytes() { ret.push(x); }; } @@ -63,4 +100,200 @@ impl DNSQuestion { ret } +} + +pub fn questions_from_bytes(bytes: Vec, total_questions: u8) -> Result, ()> +{ + if (bytes.len() < 4) + { + return Err(()); + } + + let mut questions: Vec = Vec::with_capacity(total_questions as usize); + let mut current_query: Option> = None; + + let mut current_length: Option = None; + let mut remaining_length: Box = Box::from(0); + let mut current_qtype: Option = None; + let mut current_qclass: Option = None; + let mut trailers_reached = false; + + for byte in bytes { + match current_length { + None => { // next question, init lengths + current_length = Some(byte); + remaining_length = Box::from(byte); + current_query = Some(Vec::with_capacity(10)); + } + Some(_) => { + if byte == 0 { + trailers_reached = true; + continue + } + + if *remaining_length == 0 && !trailers_reached { + current_query.as_mut().unwrap().push('.' as u8); + current_length = Some(byte); + remaining_length = Box::from(byte); + } + else if trailers_reached { // trailer fields + match current_qtype { + None => { + current_qtype = Some(byte); + } + Some(qtype_b) => { + match current_qclass { + None => { + current_qclass = Some(byte); + } + Some(qclass_b) => { + + match (qtype_b.try_into(), qclass_b.try_into()) { + (Ok(qtype), Ok(qclass)) => { + questions.push(DNSQuestion { + qname: String::from_utf8(current_query.unwrap()).unwrap(), + qtype, + qclass + }); + + current_length = Some(byte); + remaining_length = Box::from(byte); + current_query = Some(Vec::with_capacity(10)); + current_qtype = None; + current_qclass = None; + trailers_reached = false; + } + _ => { + return Err(()); + } + } + } + } + } + } + } + else + { + current_query.as_mut().unwrap().push(byte); + *remaining_length = remaining_length.sub(1); + } + } + } + } + + match (current_qtype, current_qclass) { + (Some(qtype), Some(qclass)) => { + match (qtype.try_into(), qclass.try_into()) { + (Ok(qtype), Ok(qclass)) => { + questions.push(DNSQuestion { + qname: String::from_utf8(current_query.unwrap()).unwrap(), + qtype, + qclass + }); + } + _ => { + return Err(()); + } + } + } + _ => { + return Err(()); + } + } + + Ok(questions) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn one_question_back_and_forth() { + let q = DNSQuestion { + qname: "google.com".to_string(), + qclass: QClass::Internet, + qtype: QType::A + }; + + let q_bytes = q.to_bytes(); + + let q_reconstructed = questions_from_bytes(q_bytes, 1).unwrap(); + + assert_eq!(q.qname, q_reconstructed[0].qname); + assert_eq!(q.qclass, q_reconstructed[0].qclass); + assert_eq!(q.qtype, q_reconstructed[0].qtype); + } + + #[test] + fn two_questions_back_and_forth() { + let q = DNSQuestion { + qname: "google.com".to_string(), + qclass: QClass::Internet, + qtype: QType::A + }; + + let q2 = DNSQuestion { + qname: "duck.com".to_string(), + qclass: QClass::Internet, + qtype: QType::AAAA + }; + + let mut q_bytes = q.to_bytes(); + let mut q2_bytes = q2.to_bytes(); + + q_bytes.append(&mut q2_bytes); + + let q_reconstructed = questions_from_bytes(q_bytes, 2).unwrap(); + + assert_eq!(q.qname, q_reconstructed[0].qname); + assert_eq!(q.qclass, q_reconstructed[0].qclass); + assert_eq!(q.qtype, q_reconstructed[0].qtype); + + assert_eq!(q2.qname, q_reconstructed[1].qname); + assert_eq!(q2.qclass, q_reconstructed[1].qclass); + assert_eq!(q2.qtype, q_reconstructed[1].qtype); + } + + #[test] + fn three_questions_back_and_forth() { + let q = DNSQuestion { + qname: "google.com".to_string(), + qclass: QClass::Internet, + qtype: QType::A + }; + + let q2 = DNSQuestion { + qname: "duck.com".to_string(), + qclass: QClass::Internet, + qtype: QType::AAAA + }; + + let q3 = DNSQuestion { + qname: "facebook.com".to_string(), + qclass: QClass::Hesiod, + qtype: QType::CNAME + }; + + let mut q_bytes = q.to_bytes(); + let mut q2_bytes = q2.to_bytes(); + let mut q3_bytes = q3.to_bytes(); + + q_bytes.append(&mut q2_bytes); + q_bytes.append(&mut q3_bytes); + + let q_reconstructed = questions_from_bytes(q_bytes, 2).unwrap(); + + assert_eq!(q.qname, q_reconstructed[0].qname); + assert_eq!(q.qclass, q_reconstructed[0].qclass); + assert_eq!(q.qtype, q_reconstructed[0].qtype); + + assert_eq!(q2.qname, q_reconstructed[1].qname); + assert_eq!(q2.qclass, q_reconstructed[1].qclass); + assert_eq!(q2.qtype, q_reconstructed[1].qtype); + + assert_eq!(q3.qname, q_reconstructed[2].qname); + assert_eq!(q3.qclass, q_reconstructed[2].qclass); + assert_eq!(q3.qtype, q_reconstructed[2].qtype); + } } \ No newline at end of file