(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:
parent
cccdf5b5e9
commit
0dc41ef845
188
Cargo.lock
generated
188
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
122
src/proto.rs
122
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<u16> for DNSOpCode {
|
||||
impl TryFrom<u8> for DNSOpCode {
|
||||
type Error = DNSParseError;
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(DNSOpCode::Query),
|
||||
1 => Ok(DNSOpCode::IQuery),
|
||||
@ -89,10 +89,10 @@ pub enum DNSRCode {
|
||||
NotZone = 10,
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for DNSRCode {
|
||||
impl TryFrom<u8> for DNSRCode {
|
||||
type Error = DNSParseError;
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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<Self, DNSParseError> {
|
||||
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])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user