use crate::{DatagramParser, FlowSide, Timestamp};
use super::correlator::Correlator;
use super::parser::{DnsParseResult, parse_message_at};
use super::types::{DnsConfig, DnsQuery, DnsResponse};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum DnsMessage {
Query(DnsQuery),
Response(DnsResponse),
Unanswered(DnsQuery),
}
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct DnsUdpParser {
correlator: Option<Correlator<()>>,
}
impl DnsUdpParser {
pub fn new() -> Self {
Self::default()
}
pub fn with_correlation() -> Self {
Self {
correlator: Some(Correlator::new()),
}
}
pub fn with_config(config: DnsConfig) -> Self {
Self {
correlator: Some(Correlator::with_config(config)),
}
}
}
impl DatagramParser for DnsUdpParser {
type Message = DnsMessage;
fn parse(&mut self, payload: &[u8], _side: FlowSide, ts: Timestamp) -> Vec<DnsMessage> {
match parse_message_at(payload, ts) {
Ok(DnsParseResult::Query(q)) => {
if let Some(c) = &mut self.correlator {
c.record_query((), q.clone());
}
vec![DnsMessage::Query(q)]
}
Ok(DnsParseResult::Response(mut r)) => {
if let Some(c) = &mut self.correlator
&& let Some((_, elapsed)) = c.match_response(&(), r.transaction_id, ts)
{
r.elapsed = Some(elapsed);
}
vec![DnsMessage::Response(r)]
}
Err(_) => Vec::new(),
}
}
fn on_tick(&mut self, now: Timestamp) -> Vec<DnsMessage> {
match &mut self.correlator {
Some(c) => c
.sweep(now)
.into_iter()
.map(DnsMessage::Unanswered)
.collect(),
None => Vec::new(),
}
}
fn parser_kind(&self) -> &'static str {
"dns-udp"
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn build_msg(tx_id: u16, qname: &str, flags: u16) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&tx_id.to_be_bytes());
v.extend_from_slice(&flags.to_be_bytes());
v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&0u16.to_be_bytes()); v.extend_from_slice(&0u16.to_be_bytes()); v.extend_from_slice(&0u16.to_be_bytes()); for label in qname.split('.') {
v.push(label.len() as u8);
v.extend_from_slice(label.as_bytes());
}
v.push(0);
v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&1u16.to_be_bytes()); v
}
#[test]
fn parses_query() {
let mut p = DnsUdpParser::new();
let bytes = build_msg(0xabcd, "example.com", 0x0100);
let msgs = p.parse(&bytes, FlowSide::Initiator, Timestamp::default());
assert_eq!(msgs.len(), 1);
match &msgs[0] {
DnsMessage::Query(q) => assert_eq!(q.transaction_id, 0xabcd),
_ => panic!("expected Query"),
}
}
#[test]
fn malformed_returns_empty() {
let mut p = DnsUdpParser::new();
let msgs = p.parse(b"\x00", FlowSide::Initiator, Timestamp::default());
assert!(msgs.is_empty());
}
#[test]
fn correlation_sets_elapsed_on_response() {
let mut p = DnsUdpParser::with_correlation();
let q = build_msg(0x1234, "example.com", 0x0100);
p.parse(&q, FlowSide::Initiator, Timestamp::new(10, 0));
let r = build_msg(0x1234, "example.com", 0x8100);
let msgs = p.parse(&r, FlowSide::Responder, Timestamp::new(10, 500_000_000));
assert_eq!(msgs.len(), 1);
match &msgs[0] {
DnsMessage::Response(resp) => {
assert_eq!(resp.elapsed, Some(Duration::from_millis(500)));
}
_ => panic!("expected Response"),
}
}
#[test]
fn on_tick_emits_unanswered_after_timeout() {
let mut p = DnsUdpParser::with_correlation();
let q = build_msg(0x9, "slow.example", 0x0100);
p.parse(&q, FlowSide::Initiator, Timestamp::new(0, 0));
assert!(p.on_tick(Timestamp::new(5, 0)).is_empty());
let ticked = p.on_tick(Timestamp::new(60, 0));
assert_eq!(ticked.len(), 1);
assert!(matches!(&ticked[0], DnsMessage::Unanswered(uq) if uq.transaction_id == 9));
}
#[test]
fn matched_query_not_reported_unanswered() {
let mut p = DnsUdpParser::with_correlation();
let q = build_msg(0x9, "x.example", 0x0100);
p.parse(&q, FlowSide::Initiator, Timestamp::new(0, 0));
let r = build_msg(0x9, "x.example", 0x8100);
p.parse(&r, FlowSide::Responder, Timestamp::new(1, 0));
assert!(p.on_tick(Timestamp::new(60, 0)).is_empty());
}
#[test]
fn without_correlation_no_rtt_no_unanswered() {
let mut p = DnsUdpParser::new();
let q = build_msg(0x1, "x.example", 0x0100);
p.parse(&q, FlowSide::Initiator, Timestamp::new(0, 0));
let r = build_msg(0x1, "x.example", 0x8100);
let msgs = p.parse(&r, FlowSide::Responder, Timestamp::new(1, 0));
match &msgs[0] {
DnsMessage::Response(resp) => assert!(resp.elapsed.is_none()),
_ => panic!("expected Response"),
}
assert!(p.on_tick(Timestamp::new(99, 0)).is_empty());
}
}