(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:
@ -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])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user