(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:
2022-04-03 20:49:47 +02:00
parent cccdf5b5e9
commit 0dc41ef845
4 changed files with 287 additions and 32 deletions

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() {
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);
}

View File

@ -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])
);
}
}
}