(tests) Implement tests for DNS header parsing

* implemented 3 tests
  * valid opcodes
  * invalid opcodes
  * header too short
* fixed byteorder and bit shifting for flags
* added hexdump for main function for testing purposes
This commit is contained in:
ddidderr 2022-04-03 20:49:47 +02:00
parent cccdf5b5e9
commit 0dc41ef845
Signed by: ddidderr
GPG Key ID: 3841F1C27E6F0E14
4 changed files with 287 additions and 32 deletions

188
Cargo.lock generated
View File

@ -2,6 +2,194 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "dns-parse" name = "dns-parse"
version = "0.1.0" 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"

View File

@ -6,3 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
hexyl = "0.9"

View File

@ -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() { fn main() {
let (thread_udp, _thread_udp_tx, thread_udp_rx) = listen(); let (thread_udp, _thread_udp_tx, thread_udp_rx) = listen();
for msg in thread_udp_rx.iter() { for msg in thread_udp_rx.iter() {
print_hex(&msg);
let hdr_struct = DNSHeader::from_udp_datagram(&msg).unwrap(); let hdr_struct = DNSHeader::from_udp_datagram(&msg).unwrap();
dbg!(hdr_struct); dbg!(hdr_struct);
} }

View File

@ -11,7 +11,7 @@ use std::convert::TryInto;
/// 4 Notify [RFC1996] /// 4 Notify [RFC1996]
/// 5 Update [RFC2136] /// 5 Update [RFC2136]
/// 6-15 Unassigned /// 6-15 Unassigned
#[derive(Debug)] #[derive(Debug, PartialEq, Copy, Clone)]
pub enum DNSOpCode { pub enum DNSOpCode {
Query = 0, Query = 0,
IQuery = 1, // obsolete IQuery = 1, // obsolete
@ -20,10 +20,10 @@ pub enum DNSOpCode {
Update = 5, Update = 5,
} }
impl TryFrom<u16> for DNSOpCode { impl TryFrom<u8> for DNSOpCode {
type Error = DNSParseError; type Error = DNSParseError;
fn try_from(value: u16) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value { match value {
0 => Ok(DNSOpCode::Query), 0 => Ok(DNSOpCode::Query),
1 => Ok(DNSOpCode::IQuery), 1 => Ok(DNSOpCode::IQuery),
@ -89,10 +89,10 @@ pub enum DNSRCode {
NotZone = 10, NotZone = 10,
} }
impl TryFrom<u16> for DNSRCode { impl TryFrom<u8> for DNSRCode {
type Error = DNSParseError; type Error = DNSParseError;
fn try_from(value: u16) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value { match value {
0 => Ok(DNSRCode::NoError), 0 => Ok(DNSRCode::NoError),
1 => Ok(DNSRCode::FormErr), 1 => Ok(DNSRCode::FormErr),
@ -149,41 +149,41 @@ pub struct DNSQuery {
} }
impl DNSHeader { impl DNSHeader {
const QR_MASK: u16 = 0x0001; const QR_MASK: u8 = 0b10000000;
const OPCODE_MASK: u16 = 0x000e; const OPCODE_MASK: u8 = 0b01111000;
const AA_MASK: u16 = 0x0020; const AA_MASK: u8 = 0b00000100;
const TC_MASK: u16 = 0x0040; const TC_MASK: u8 = 0b00000010;
const RD_MASK: u16 = 0x0080; const RD_MASK: u8 = 0b00000001;
const RA_MASK: u16 = 0x0100;
const AD_MASK: u16 = 0x0400;
const CD_MASK: u16 = 0x0800;
const RCODE_MASK: u16 = 0xf000;
const OPCODE_OFFSET: u16 = 1; const RA_MASK: u8 = 0b10000000;
const RCODE_OFFSET: u16 = 11; 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<Self, DNSParseError> { pub fn from_udp_datagram(datagram: &[u8]) -> Result<Self, DNSParseError> {
if datagram.len() < 11 { if datagram.len() < 12 {
return Err(DNSParseError::DatagramTooShort); return Err(DNSParseError::DatagramTooShort);
} }
let id = u16::from_be_bytes((&datagram[..2]).try_into().unwrap()); 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 = (datagram[2] & Self::QR_MASK) != 0;
let qr = (flags & Self::QR_MASK) != 0; let opcode = DNSOpCode::try_from((datagram[2] & Self::OPCODE_MASK) >> Self::OPCODE_OFFSET)?;
let opcode = DNSOpCode::try_from((flags & Self::OPCODE_MASK) >> Self::OPCODE_OFFSET)?; let aa = (datagram[2] & Self::AA_MASK) != 0;
let aa = (flags & Self::AA_MASK) != 0; let tc = (datagram[2] & Self::TC_MASK) != 0;
let tc = (flags & Self::TC_MASK) != 0; let rd = (datagram[2] & Self::RD_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 qd_zo_count = u16::from_be_bytes((&datagram[4..6]).try_into().unwrap()); let ra = (datagram[3] & Self::RA_MASK) != 0;
let an_pr_count = u16::from_be_bytes((&datagram[6..8]).try_into().unwrap()); let ad = (datagram[3] & Self::AD_MASK) != 0;
let ns_up_count = u16::from_be_bytes((&datagram[8..10]).try_into().unwrap()); let cd = (datagram[3] & Self::CD_MASK) != 0;
let ar_count = u16::from_be_bytes((&datagram[10..12]).try_into().unwrap()); 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 { Ok(DNSHeader {
id, id,
@ -204,9 +204,67 @@ impl DNSHeader {
} }
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum DNSParseError { pub enum DNSParseError {
DatagramTooShort, DatagramTooShort,
DNSOpCodeInvalid, DNSOpCodeInvalid,
DNSRCodeInvalid, 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])
);
}
}
}