use flowscope::dns::{DnsParseResult, DnsRcode, parse_message};
fn build_a_query(tx_id: u16, qname: &str) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&tx_id.to_be_bytes());
v.extend_from_slice(&0x0100u16.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()); encode_qname(&mut v, qname);
v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&1u16.to_be_bytes()); v
}
fn build_a_response(tx_id: u16, qname: &str, addr: [u8; 4]) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&tx_id.to_be_bytes());
v.extend_from_slice(&0x8180u16.to_be_bytes()); v.extend_from_slice(&1u16.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()); encode_qname(&mut v, qname);
v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&1u16.to_be_bytes());
encode_qname(&mut v, qname);
v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&1u16.to_be_bytes()); v.extend_from_slice(&60u32.to_be_bytes()); v.extend_from_slice(&4u16.to_be_bytes()); v.extend_from_slice(&addr);
v
}
fn build_nxdomain(tx_id: u16, qname: &str) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&tx_id.to_be_bytes());
v.extend_from_slice(&0x8183u16.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());
encode_qname(&mut v, qname);
v.extend_from_slice(&1u16.to_be_bytes());
v.extend_from_slice(&1u16.to_be_bytes());
v
}
fn encode_qname(buf: &mut Vec<u8>, name: &str) {
for label in name.split('.') {
buf.push(label.len() as u8);
buf.extend_from_slice(label.as_bytes());
}
buf.push(0);
}
#[test]
fn parses_a_query() {
let bytes = build_a_query(0x1234, "example.com");
match parse_message(&bytes).unwrap() {
DnsParseResult::Query(q) => {
assert_eq!(q.transaction_id, 0x1234);
assert_eq!(q.questions.len(), 1);
assert_eq!(q.questions[0].name, "example.com");
assert!(q.flags.is_recursion_desired());
assert!(!q.flags.is_response());
}
_ => panic!("expected Query"),
}
}
#[test]
fn parses_a_response_with_address() {
use flowscope::dns::DnsRdata;
let bytes = build_a_response(0x5678, "rust-lang.org", [192, 0, 2, 1]);
match parse_message(&bytes).unwrap() {
DnsParseResult::Response(r) => {
assert_eq!(r.transaction_id, 0x5678);
assert_eq!(r.rcode, DnsRcode::NoError);
assert!(r.flags.is_response());
assert_eq!(r.answers.len(), 1);
match &r.answers[0].data {
DnsRdata::A(addr) => {
assert_eq!(addr.octets(), [192, 0, 2, 1]);
}
other => panic!("expected A, got {other:?}"),
}
}
_ => panic!("expected Response"),
}
}
#[test]
fn parses_nxdomain() {
let bytes = build_nxdomain(0x9abc, "does-not-exist.invalid");
match parse_message(&bytes).unwrap() {
DnsParseResult::Response(r) => {
assert_eq!(r.rcode, DnsRcode::NXDomain);
assert_eq!(r.answers.len(), 0);
}
_ => panic!("expected Response"),
}
}
#[test]
fn malformed_returns_error() {
assert!(parse_message(b"").is_err());
assert!(parse_message(b"\x00\x00").is_err()); }
#[test]
fn correlator_matches_query_response() {
use flowscope::Timestamp;
use flowscope::dns::Correlator;
let mut c = Correlator::<u32>::new();
let q_bytes = build_a_query(42, "example.com");
let q = match flowscope::dns::parse_message_at(&q_bytes, Timestamp::new(0, 0)).unwrap() {
DnsParseResult::Query(q) => q,
_ => unreachable!(),
};
c.record_query(7u32, q);
assert_eq!(c.pending_len(), 1);
let matched = c.match_response(&7u32, 42, Timestamp::new(1, 500_000_000));
assert!(matched.is_some());
let (_q, elapsed) = matched.unwrap();
assert_eq!(elapsed.as_secs(), 1);
assert_eq!(elapsed.subsec_millis(), 500);
assert_eq!(c.pending_len(), 0);
}
#[test]
fn correlator_orphan_response() {
use flowscope::Timestamp;
use flowscope::dns::Correlator;
let mut c = Correlator::<u32>::new();
let matched = c.match_response(&7u32, 999, Timestamp::new(0, 0));
assert!(matched.is_none());
}
#[test]
fn correlator_sweep_flags_unanswered() {
use flowscope::Timestamp;
use flowscope::dns::Correlator;
let mut c = Correlator::<u32>::new();
let q_bytes = build_a_query(99, "slow.example");
let q = match flowscope::dns::parse_message_at(&q_bytes, Timestamp::new(0, 0)).unwrap() {
DnsParseResult::Query(q) => q,
_ => unreachable!(),
};
c.record_query(7u32, q);
let expired = c.sweep(Timestamp::new(31, 0));
assert_eq!(expired.len(), 1);
assert_eq!(expired[0].transaction_id, 99);
}