diff --git a/Cargo.lock b/Cargo.lock index 8e94b7b..6261d05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,194 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "term_size", + "textwrap", + "unicode-width", +] + [[package]] name = "dns-parse" version = "0.1.0" +dependencies = [ + "hexyl", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hexyl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f823e021c22ebf3e1437685a9f3ceb2b93adb723e23dca2e4e38b47d3d49e447" +dependencies = [ + "ansi_term", + "anyhow", + "atty", + "clap", + "libc", + "thiserror", +] + +[[package]] +name = "libc" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "term_size", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 664b4ef..659ae25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +hexyl = "0.9" diff --git a/src/main.rs b/src/main.rs index 20c1e42..7410bae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,10 +32,18 @@ fn listen() -> ( ) } +fn print_hex(bytes: &[u8]) { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let mut hxp = hexyl::Printer::new(&mut stdout, true, hexyl::BorderStyle::None, false); + let _ = hxp.print_all(bytes); +} + fn main() { let (thread_udp, _thread_udp_tx, thread_udp_rx) = listen(); for msg in thread_udp_rx.iter() { + print_hex(&msg); let hdr_struct = DNSHeader::from_udp_datagram(&msg).unwrap(); dbg!(hdr_struct); } diff --git a/src/proto.rs b/src/proto.rs index 290b9a1..6dd256f 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -11,7 +11,7 @@ use std::convert::TryInto; /// 4 Notify [RFC1996] /// 5 Update [RFC2136] /// 6-15 Unassigned -#[derive(Debug)] +#[derive(Debug, PartialEq, Copy, Clone)] pub enum DNSOpCode { Query = 0, IQuery = 1, // obsolete @@ -20,10 +20,10 @@ pub enum DNSOpCode { Update = 5, } -impl TryFrom for DNSOpCode { +impl TryFrom for DNSOpCode { type Error = DNSParseError; - fn try_from(value: u16) -> Result { + fn try_from(value: u8) -> Result { match value { 0 => Ok(DNSOpCode::Query), 1 => Ok(DNSOpCode::IQuery), @@ -89,10 +89,10 @@ pub enum DNSRCode { NotZone = 10, } -impl TryFrom for DNSRCode { +impl TryFrom for DNSRCode { type Error = DNSParseError; - fn try_from(value: u16) -> Result { + fn try_from(value: u8) -> Result { match value { 0 => Ok(DNSRCode::NoError), 1 => Ok(DNSRCode::FormErr), @@ -149,41 +149,41 @@ pub struct DNSQuery { } impl DNSHeader { - const QR_MASK: u16 = 0x0001; - const OPCODE_MASK: u16 = 0x000e; - const AA_MASK: u16 = 0x0020; - const TC_MASK: u16 = 0x0040; - const RD_MASK: u16 = 0x0080; - const RA_MASK: u16 = 0x0100; - const AD_MASK: u16 = 0x0400; - const CD_MASK: u16 = 0x0800; - const RCODE_MASK: u16 = 0xf000; + const QR_MASK: u8 = 0b10000000; + const OPCODE_MASK: u8 = 0b01111000; + const AA_MASK: u8 = 0b00000100; + const TC_MASK: u8 = 0b00000010; + const RD_MASK: u8 = 0b00000001; - const OPCODE_OFFSET: u16 = 1; - const RCODE_OFFSET: u16 = 11; + const RA_MASK: u8 = 0b10000000; + const AD_MASK: u8 = 0b00100000; + const CD_MASK: u8 = 0b00010000; + const RCODE_MASK: u8 = 0b00001111; + + const OPCODE_OFFSET: u8 = 3; pub fn from_udp_datagram(datagram: &[u8]) -> Result { - if datagram.len() < 11 { + if datagram.len() < 12 { return Err(DNSParseError::DatagramTooShort); } let id = u16::from_be_bytes((&datagram[..2]).try_into().unwrap()); - let flags = u16::from_be_bytes((&datagram[2..4]).try_into().unwrap()); - let qr = (flags & Self::QR_MASK) != 0; - let opcode = DNSOpCode::try_from((flags & Self::OPCODE_MASK) >> Self::OPCODE_OFFSET)?; - let aa = (flags & Self::AA_MASK) != 0; - let tc = (flags & Self::TC_MASK) != 0; - let rd = (flags & Self::RD_MASK) != 0; - let ra = (flags & Self::RA_MASK) != 0; - let ad = (flags & Self::AD_MASK) != 0; - let cd = (flags & Self::CD_MASK) != 0; - let rcode = DNSRCode::try_from((flags & Self::RCODE_MASK) >> Self::RCODE_OFFSET)?; + let qr = (datagram[2] & Self::QR_MASK) != 0; + let opcode = DNSOpCode::try_from((datagram[2] & Self::OPCODE_MASK) >> Self::OPCODE_OFFSET)?; + let aa = (datagram[2] & Self::AA_MASK) != 0; + let tc = (datagram[2] & Self::TC_MASK) != 0; + let rd = (datagram[2] & Self::RD_MASK) != 0; - let qd_zo_count = u16::from_be_bytes((&datagram[4..6]).try_into().unwrap()); - let an_pr_count = u16::from_be_bytes((&datagram[6..8]).try_into().unwrap()); - let ns_up_count = u16::from_be_bytes((&datagram[8..10]).try_into().unwrap()); - let ar_count = u16::from_be_bytes((&datagram[10..12]).try_into().unwrap()); + let ra = (datagram[3] & Self::RA_MASK) != 0; + let ad = (datagram[3] & Self::AD_MASK) != 0; + let cd = (datagram[3] & Self::CD_MASK) != 0; + let rcode = DNSRCode::try_from(datagram[3] & Self::RCODE_MASK)?; + + let qd_zo_count = u16::from_be_bytes((datagram[4..6]).try_into().unwrap()); + let an_pr_count = u16::from_be_bytes((datagram[6..8]).try_into().unwrap()); + let ns_up_count = u16::from_be_bytes((datagram[8..10]).try_into().unwrap()); + let ar_count = u16::from_be_bytes((datagram[10..12]).try_into().unwrap()); Ok(DNSHeader { id, @@ -204,9 +204,67 @@ impl DNSHeader { } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum DNSParseError { DatagramTooShort, DNSOpCodeInvalid, DNSRCodeInvalid, } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn parse_dns_header_too_short() { + // minimal header with 1 byte missing + let dns_query = [ + 0x13, 0x37, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let parse_result = DNSHeader::from_udp_datagram(&dns_query); + assert_eq!(parse_result.err().unwrap(), DNSParseError::DatagramTooShort); + } + + #[test] + fn parse_dns_header_opcode_invalid() { + let mut dns_query = [ + 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let invalid_opcodes = [3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + + for opcode in invalid_opcodes { + dns_query[2] = opcode << 3; + let parse_result = DNSHeader::from_udp_datagram(&dns_query); + assert_eq!( + parse_result.err(), + Some(DNSParseError::DNSOpCodeInvalid), + "query: {:?}, opcode: {}", + dns_query, + opcode + ); + } + } + + #[test] + fn parse_dns_header_opcode_valid() { + let mut dns_query = [ + 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let valid_opcodes = [0, 1, 2, 4, 5]; + let valid_parsed_opcodes = [ + DNSOpCode::Query, + DNSOpCode::IQuery, + DNSOpCode::Status, + DNSOpCode::Notify, + DNSOpCode::Update, + ]; + + for (idx, opcode) in valid_opcodes.iter().enumerate() { + dns_query[2] = opcode << 3; + let parse_result = DNSHeader::from_udp_datagram(&dns_query); + assert_eq!( + parse_result.map(|x| x.opcode), + Ok(valid_parsed_opcodes[idx]) + ); + } + } +}