From cb23cef343883e7862bf38da93db925f7dc59cfd Mon Sep 17 00:00:00 2001 From: Brendan Ball Date: Sun, 4 Feb 2024 14:18:45 +0100 Subject: [PATCH] parse message --- Cargo.lock | 56 +++++++++++++ dns_decode/Cargo.toml | 3 +- dns_decode/src/lib.rs | 1 + dns_decode/src/message.rs | 131 ++++++++++++++++++++++++++++++ dns_decode/src/message_header.rs | 6 +- dns_decode/src/query.rs | 2 +- dns_decode/src/resource_record.rs | 11 +-- 7 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 dns_decode/src/message.rs diff --git a/Cargo.lock b/Cargo.lock index 3d4e009..2d601ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ version = "0.1.0" dependencies = [ "hex", "nom", + "thiserror", ] [[package]] @@ -41,3 +42,58 @@ dependencies = [ "memchr", "minimal-lexical", ] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/dns_decode/Cargo.toml b/dns_decode/Cargo.toml index d9df2f4..5b9e77b 100644 --- a/dns_decode/Cargo.toml +++ b/dns_decode/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" [dependencies] nom = "7.1.3" -hex = "0.4.3" \ No newline at end of file +hex = "0.4.3" +thiserror = "1.0.56" \ No newline at end of file diff --git a/dns_decode/src/lib.rs b/dns_decode/src/lib.rs index 0be2205..ff94e2b 100644 --- a/dns_decode/src/lib.rs +++ b/dns_decode/src/lib.rs @@ -1,3 +1,4 @@ +pub mod message; pub mod message_header; pub mod query; pub mod resource_record; diff --git a/dns_decode/src/message.rs b/dns_decode/src/message.rs new file mode 100644 index 0000000..dd2f804 --- /dev/null +++ b/dns_decode/src/message.rs @@ -0,0 +1,131 @@ +use crate::{message_header::*, query::*, resource_record::*}; +use nom::{multi::count, IResult}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ParseError { + #[error("unknown parse error")] + Unknown, +} + +#[derive(Debug, PartialEq)] +pub struct Message { + pub header: MessageHeader, + pub queries: Vec, + pub answers: Vec, +} + +impl TryFrom<&[u8]> for Message { + type Error = ParseError; + + fn try_from(input: &[u8]) -> Result { + // TODO improve error reporting + let (_, m) = message(input).map_err(|_op| ParseError::Unknown)?; + Ok(m) + } +} + +fn message(input: &[u8]) -> IResult<&[u8], Message> { + let (input, header) = message_header(input)?; + let (input, queries) = count(query, header.query_count as usize)(input)?; + let (input, answers) = count(resource_record, header.answer_count as usize)(input)?; + + Ok(( + input, + Message { + header, + queries, + answers, + }, + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::Ipv4Addr; + + #[test] + fn test_message_request() { + let message_bytes = + hex::decode("690601000001000000000000076578616d706c6503636f6d0000010001").unwrap(); + let result = message(&message_bytes); + + assert_eq!( + result, + Ok(( + &b""[..], + Message { + header: MessageHeader { + message_id: 0x6906, + flags: Flags { + qr: QR::Query, + opcode: Opcode::Query, + aa: AuthoritativeAnswer::NonAuthoritative, + truncated: Truncated::NotTruncated, + recursion_desired: RecursionDesired::Desired, + recursion_available: RecursionAvailable::NotAvailable, + rcode: Rcode::NoError, + }, + query_count: 1, + answer_count: 0, + name_server_count: 0, + additional_count: 0, + }, + queries: vec![Query { + name: vec![String::from("example"), String::from("com")], + query_type: QueryType::A, + query_class: QueryClass::Internet, + }], + answers: vec![] + } + )) + ); + } + + #[test] + fn test_message_response() { + let message_bytes = hex::decode( + "690681800001000100000000076578616d706c6503636f6d0000010001c00c0001000100005a0200045db8d822" + ) + .unwrap(); + let result = message(&message_bytes); + + assert_eq!( + result, + Ok(( + &b""[..], + Message { + header: MessageHeader { + message_id: 0x6906, + flags: Flags { + qr: QR::Response, + opcode: Opcode::Query, + aa: AuthoritativeAnswer::NonAuthoritative, + truncated: Truncated::NotTruncated, + recursion_desired: RecursionDesired::Desired, + recursion_available: RecursionAvailable::Available, + rcode: Rcode::NoError, + }, + query_count: 1, + answer_count: 1, + name_server_count: 0, + additional_count: 0, + }, + queries: vec![Query { + name: vec![String::from("example"), String::from("com")], + query_type: QueryType::A, + query_class: QueryClass::Internet, + }], + answers: vec![ResourceRecord { + name: Name::Pointer(49164), + resource_type: ResourceType::A, + resource_class: ResourceClass::Internet, + ttl: 23042, + rdata: ResourceData::A(Ipv4Addr::from(0x5db8d822)) + }] + } + )) + ); + } +} diff --git a/dns_decode/src/message_header.rs b/dns_decode/src/message_header.rs index c2f4f5e..016bdb1 100644 --- a/dns_decode/src/message_header.rs +++ b/dns_decode/src/message_header.rs @@ -180,7 +180,7 @@ fn dns_flags(input: &[u8]) -> IResult<&[u8], Flags> { bits::<_, _, Error<(&[u8], usize)>, _, _>(dns_flags_inner)(input) } -fn dns_message(input: &[u8]) -> IResult<&[u8], MessageHeader> { +pub fn message_header(input: &[u8]) -> IResult<&[u8], MessageHeader> { let (input, message_id) = be_u16(input)?; let (input, flags) = dns_flags(input)?; let (input, query_count) = be_u16(input)?; @@ -208,7 +208,7 @@ mod tests { fn message_header_query() { let dns_message_bytes = hex::decode("690601000001000000000000").unwrap(); - let result = dns_message(&dns_message_bytes); + let result = message_header(&dns_message_bytes); assert_eq!( result, Ok(( @@ -237,7 +237,7 @@ mod tests { fn message_header_response() { let dns_message_bytes = hex::decode("690681800001000100000000").unwrap(); - let result = dns_message(&dns_message_bytes); + let result = message_header(&dns_message_bytes); assert_eq!( result, Ok(( diff --git a/dns_decode/src/query.rs b/dns_decode/src/query.rs index ab8635b..3885668 100644 --- a/dns_decode/src/query.rs +++ b/dns_decode/src/query.rs @@ -80,7 +80,7 @@ fn name(input: &[u8]) -> IResult<&[u8], Vec> { Ok((input, labels)) } -fn query(input: &[u8]) -> IResult<&[u8], Query> { +pub fn query(input: &[u8]) -> IResult<&[u8], Query> { let (input, name) = name(input)?; let (input, query_type) = be_u16(input)?; let (input, query_class) = be_u16(input)?; diff --git a/dns_decode/src/resource_record.rs b/dns_decode/src/resource_record.rs index 3a73d8c..aea3d0b 100644 --- a/dns_decode/src/resource_record.rs +++ b/dns_decode/src/resource_record.rs @@ -1,12 +1,9 @@ use nom::{ - bytes::complete::{tag, take}, - combinator::iterator, - error::{Error, ErrorKind, ParseError}, multi::length_value, - number::complete::{be_u128, be_u16, be_u32, be_u8}, - Compare, Err, IResult, InputIter, InputLength, InputTake, Slice, + number::complete::{be_u128, be_u16, be_u32}, + IResult, InputIter, InputLength, Slice, }; -use std::ops::{Range, RangeFrom, RangeTo}; +use std::ops::RangeFrom; use std::{ convert::Into, net::{Ipv4Addr, Ipv6Addr}, @@ -125,7 +122,7 @@ where } } -fn resource_record(input: &[u8]) -> IResult<&[u8], ResourceRecord> { +pub fn resource_record(input: &[u8]) -> IResult<&[u8], ResourceRecord> { let (input, name) = name(input)?; let (input, resource_type) = be_u16(input)?; let (input, resource_class) = be_u16(input)?;