#![cfg(all(feature = "stats", feature = "std", feature = "slab"))]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::{net::Ipv4Addr, time::Instant as StdInstant};
use std::time::Duration;
use mdns_proto::{
CollectedAnswer, Name, Query,
cache::CacheEntry,
config::{EndpointConfig, QuerySpec, ServiceSpec},
endpoint::{Endpoint, EndpointEventEntry, ServiceRoute},
event::{QueryUpdate, ServiceUpdate},
records::ServiceRecords,
transmit::Transmit,
wire::{Flags, Header, MessageBuilder, ResourceType},
};
type TestQuery = Query<StdInstant, slab::Slab<CollectedAnswer>, slab::Slab<QueryUpdate>>;
type Endp = Endpoint<
StdInstant,
rand::rngs::StdRng,
slab::Slab<CacheEntry<StdInstant>>,
slab::Slab<ServiceRoute>,
slab::Slab<TestQuery>,
slab::Slab<EndpointEventEntry>,
slab::Slab<CollectedAnswer>,
slab::Slab<QueryUpdate>,
>;
fn make_endpoint(seed: u8) -> Endp {
use rand::SeedableRng;
let rng = rand::rngs::StdRng::from_seed([seed; 32]);
Endp::try_new(EndpointConfig::new(), rng)
}
fn build_srv_response(qname: &Name) -> Vec<u8> {
let mut buf = [0u8; 512];
let header = Header::new().with_flags(Flags::new().with_response());
let mut b: MessageBuilder<'_, 0> = MessageBuilder::try_new(&mut buf, header).unwrap();
let target = Name::try_from_str("host.local.").unwrap();
b.push_srv_answer(qname, 120, 0, 0, 8080, &target, true)
.unwrap();
let n = b.finish().unwrap();
buf[..n].to_vec()
}
fn build_query(qname: &Name) -> Vec<u8> {
use mdns_proto::wire::ResourceClass;
let mut buf = [0u8; 512];
let header = Header::new();
let mut b: MessageBuilder<'_, 0> = MessageBuilder::try_new(&mut buf, header).unwrap();
b.push_question(qname, ResourceType::Srv, ResourceClass::In, false)
.unwrap();
let n = b.finish().unwrap();
buf[..n].to_vec()
}
#[test]
fn stats_services_registered() {
let mut e = make_endpoint(0);
let stype = Name::try_from_str("_ipp._tcp.local.").unwrap();
let inst = Name::try_from_str("StatsTest._ipp._tcp.local.").unwrap();
let host = Name::try_from_str("stats-host.local.").unwrap();
let mut recs = ServiceRecords::new(stype, inst, host, 631, 120);
recs.add_a(Ipv4Addr::new(192, 168, 1, 42));
let now = StdInstant::now();
let _ = e
.try_register_service::<slab::Slab<Transmit>, slab::Slab<ServiceUpdate>>(
ServiceSpec::new(recs),
now,
)
.unwrap();
let snap = e.stats();
assert!(
snap.services_registered >= 1,
"services_registered must be >= 1 after registering; got {}",
snap.services_registered
);
}
#[test]
fn stats_packets_rx_and_answers_rx() {
let mut e = make_endpoint(1);
let qname = Name::try_from_str("StatsProbe._ipp._tcp.local.").unwrap();
let now = StdInstant::now();
let _qh = e
.try_start_query(QuerySpec::new(qname.clone(), ResourceType::Srv), now)
.unwrap();
let packet = build_srv_response(&qname);
let src = "192.0.2.100:5353".parse().unwrap();
let local_ip = "192.0.2.20".parse().unwrap();
for ev in e.handle(now, src, local_ip, 0, &packet, false).unwrap() {
let _ = ev;
}
let snap = e.stats();
assert!(
snap.packets_rx >= 1,
"packets_rx must be >= 1 after handle(); got {}",
snap.packets_rx
);
assert!(
snap.answers_rx >= 1,
"answers_rx must be >= 1 after handling a response with one SRV record; got {}",
snap.answers_rx
);
}
#[test]
fn stats_questions_rx() {
let mut e = make_endpoint(2);
let qname = Name::try_from_str("StatsQRx._ipp._tcp.local.").unwrap();
let now = StdInstant::now();
let _qh = e
.try_start_query(QuerySpec::new(qname.clone(), ResourceType::Srv), now)
.unwrap();
let packet = build_query(&qname);
let src = "192.0.2.50:5353".parse().unwrap();
let local_ip = "192.0.2.20".parse().unwrap();
for ev in e.handle(now, src, local_ip, 0, &packet, false).unwrap() {
let _ = ev;
}
let snap = e.stats();
assert!(
snap.questions_rx >= 1,
"questions_rx must be >= 1 after handling an inbound query; got {}",
snap.questions_rx
);
}
#[test]
fn stats_combined_exchange() {
let mut e = make_endpoint(3);
let stype = Name::try_from_str("_http._tcp.local.").unwrap();
let inst = Name::try_from_str("StatsCombined._http._tcp.local.").unwrap();
let host = Name::try_from_str("sc-host.local.").unwrap();
let mut recs = ServiceRecords::new(stype, inst.clone(), host, 80, 120);
recs.add_a(Ipv4Addr::new(10, 0, 0, 1));
let now = StdInstant::now();
let _ = e
.try_register_service::<slab::Slab<Transmit>, slab::Slab<ServiceUpdate>>(
ServiceSpec::new(recs),
now,
)
.unwrap();
let _qh = e
.try_start_query(QuerySpec::new(inst.clone(), ResourceType::Srv), now)
.unwrap();
let ans_pkt = build_srv_response(&inst);
let src = "192.0.2.200:5353".parse().unwrap();
let local_ip = "192.0.2.20".parse().unwrap();
for ev in e.handle(now, src, local_ip, 0, &ans_pkt, false).unwrap() {
let _ = ev;
}
let snap = e.stats();
assert!(
snap.services_registered >= 1,
"services_registered: {}",
snap.services_registered
);
assert!(snap.packets_rx >= 1, "packets_rx: {}", snap.packets_rx);
assert!(snap.answers_rx >= 1, "answers_rx: {}", snap.answers_rx);
}
fn make_no_probe_endpoint() -> (
Endp,
mdns_proto::Service<StdInstant, slab::Slab<Transmit>, slab::Slab<ServiceUpdate>>,
) {
use rand::SeedableRng;
let rng = rand::rngs::StdRng::from_seed([7u8; 32]);
let cfg = EndpointConfig::new().with_probe_unique_names(false);
let mut e = Endp::try_new(cfg, rng);
let stype = Name::try_from_str("_ipp._tcp.local.").unwrap();
let inst = Name::try_from_str("TxTest._ipp._tcp.local.").unwrap();
let host = Name::try_from_str("txtest-host.local.").unwrap();
let mut recs = ServiceRecords::new(stype, inst, host, 631, 120);
recs.add_a(Ipv4Addr::new(10, 0, 1, 1));
let now = StdInstant::now();
let (_h, svc) = e
.try_register_service::<slab::Slab<Transmit>, slab::Slab<ServiceUpdate>>(
ServiceSpec::new(recs),
now,
)
.unwrap();
(e, svc)
}
fn tick(
svc: &mut mdns_proto::Service<StdInstant, slab::Slab<Transmit>, slab::Slab<ServiceUpdate>>,
now: StdInstant,
) -> (bool, StdInstant) {
let advanced = now + Duration::from_millis(1100);
svc.handle_timeout(advanced).unwrap();
let mut buf = vec![0u8; 4096];
let ok = svc.poll_transmit(advanced, &mut buf).unwrap().is_some();
(ok, advanced)
}
#[test]
fn tx_counters_stay_zero_on_all_socket_failure() {
let (e, mut svc) = make_no_probe_endpoint();
let mut now = StdInstant::now();
for _ in 0..6 {
let (ok, next) = tick(&mut svc, now);
now = next;
if ok {
svc.note_transmit_result(now, false); }
}
let snap = e.stats();
assert_eq!(
snap.probes_tx, 0,
"probes_tx must be 0 under all-socket-failure; got {}",
snap.probes_tx
);
assert_eq!(
snap.announcements_tx, 0,
"announcements_tx must be 0 under all-socket-failure; got {}",
snap.announcements_tx
);
assert_eq!(
snap.responses_tx, 0,
"responses_tx must be 0 under all-socket-failure; got {}",
snap.responses_tx
);
assert_eq!(
snap.goodbyes_tx, 0,
"goodbyes_tx must be 0 under all-socket-failure; got {}",
snap.goodbyes_tx
);
}
#[test]
fn tx_counters_advance_on_confirmed_delivery() {
use rand::SeedableRng;
let rng = rand::rngs::StdRng::from_seed([8u8; 32]);
let cfg = EndpointConfig::new(); let mut e = Endp::try_new(cfg, rng);
let stype = Name::try_from_str("_http._tcp.local.").unwrap();
let inst = Name::try_from_str("TxConfirm._http._tcp.local.").unwrap();
let host = Name::try_from_str("txconfirm-host.local.").unwrap();
let mut recs = ServiceRecords::new(stype, inst, host, 80, 120);
recs.add_a(Ipv4Addr::new(10, 0, 2, 2));
let start = StdInstant::now();
let (_h, mut svc) = e
.try_register_service::<slab::Slab<Transmit>, slab::Slab<ServiceUpdate>>(
ServiceSpec::new(recs),
start,
)
.unwrap();
let mut now = start;
for _ in 0..20 {
now += Duration::from_millis(1100);
svc.handle_timeout(now).unwrap();
let mut buf = vec![0u8; 4096];
if svc.poll_transmit(now, &mut buf).unwrap().is_some() {
svc.note_transmit_result(now, true);
}
if svc.state() == mdns_proto::ServiceState::Established {
break;
}
}
assert_eq!(
svc.state(),
mdns_proto::ServiceState::Established,
"service must reach Established within 20 ticks"
);
let snap = e.stats();
assert_eq!(
snap.probes_tx, 3,
"probes_tx must be exactly 3 after startup; got {}",
snap.probes_tx
);
assert_eq!(
snap.announcements_tx, 2,
"announcements_tx must be exactly 2 after startup; got {}",
snap.announcements_tx
);
assert_eq!(
snap.goodbyes_tx, 0,
"goodbyes_tx must be 0 before any unregister; got {}",
snap.goodbyes_tx
);
}
#[test]
fn handle_invalid_opcode_bumps_packets_dropped() {
let mut e = make_endpoint(9);
let src = "192.0.2.5:5353".parse().unwrap();
let local_ip = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let bad_opcode = [0u8, 0, 0x10, 0x00, 0, 0, 0, 0, 0, 0, 0, 0];
let before = e.stats();
let result = e.handle(now, src, local_ip, 0, &bad_opcode, false);
assert!(
matches!(result, Err(mdns_proto::HandleError::InvalidOpcode(_))),
"expected Err(InvalidOpcode)"
);
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"packets_rx must be bumped for an invalid-opcode packet"
);
assert_eq!(
after.packets_dropped,
before.packets_dropped + 1,
"packets_dropped must be bumped for an invalid-opcode packet"
);
assert_eq!(
after.parse_errors, before.parse_errors,
"parse_errors must NOT be bumped for an invalid-opcode packet"
);
}
#[test]
fn handle_invalid_rcode_bumps_packets_dropped() {
let mut e = make_endpoint(10);
let src = "192.0.2.6:5353".parse().unwrap();
let local_ip = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let bad_rcode = [0u8, 0, 0x00, 0x01, 0, 0, 0, 0, 0, 0, 0, 0];
let before = e.stats();
let result = e.handle(now, src, local_ip, 0, &bad_rcode, false);
assert!(
matches!(result, Err(mdns_proto::HandleError::InvalidResponseCode(_))),
"expected Err(InvalidResponseCode)"
);
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"packets_rx must be bumped for an invalid-rcode packet"
);
assert_eq!(
after.packets_dropped,
before.packets_dropped + 1,
"packets_dropped must be bumped for an invalid-rcode packet"
);
assert_eq!(
after.parse_errors, before.parse_errors,
"parse_errors must NOT be bumped for an invalid-rcode packet"
);
}
fn build_response_with_malformed_answer() -> Vec<u8> {
let qname = Name::try_from_str("broken._ipp._tcp.local.").unwrap();
let mut buf = [0u8; 512];
let header = Header::new().with_flags(Flags::new().with_response());
let mut b: MessageBuilder<'_, 0> = MessageBuilder::try_new(&mut buf, header).unwrap();
let target = Name::try_from_str("host.local.").unwrap();
b.push_srv_answer(&qname, 120, 0, 0, 8080, &target, true)
.unwrap();
let n = b.finish().unwrap();
let mut pkt = buf[..n].to_vec();
let truncated_len = n.saturating_sub(4);
pkt.truncate(truncated_len);
pkt
}
#[test]
fn handle_lazy_section_parse_error_bumps_parse_errors() {
let mut e = make_endpoint(11);
let qname = Name::try_from_str("broken._ipp._tcp.local.").unwrap();
let _qh = e
.try_start_query(
QuerySpec::new(qname.clone(), ResourceType::Srv),
StdInstant::now(),
)
.unwrap();
let src = "192.0.2.7:5353".parse().unwrap();
let local_ip = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let pkt = build_response_with_malformed_answer();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, &pkt, false)
.expect("a well-formed header must not fail at handle() entry");
let mut saw_parse_err = false;
for ev in route {
if ev.is_err() {
saw_parse_err = true;
}
}
assert!(
saw_parse_err,
"expected a lazy parse error from RouteEvents"
);
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"packets_rx must be bumped for a packet with a malformed record"
);
assert_eq!(
after.parse_errors,
before.parse_errors + 1,
"parse_errors must be bumped when RouteEvents yields a lazy parse error"
);
}
fn truncate_rdata(mut pkt: Vec<u8>, drop_bytes: usize) -> Vec<u8> {
let new_len = pkt.len().saturating_sub(drop_bytes);
pkt.truncate(new_len);
pkt
}
fn build_qr0_malformed_additional() -> Vec<u8> {
let qname = Name::try_from_str("mal-add.local.").unwrap();
let mut buf = [0u8; 512];
let header = Header::new(); let mut b: MessageBuilder<'_, 0> = MessageBuilder::try_new(&mut buf, header).unwrap();
b.push_a_answer(&qname, 120, Ipv4Addr::new(10, 0, 0, 1), false)
.unwrap();
let n = b.finish().unwrap();
let mut pkt = buf[..n].to_vec();
pkt[7] = 0;
pkt[11] = 1;
truncate_rdata(pkt, 3)
}
fn build_qr1_malformed_additional() -> Vec<u8> {
let qname = Name::try_from_str("mal-add.local.").unwrap();
let mut buf = [0u8; 512];
let header = Header::new().with_flags(Flags::new().with_response()); let mut b: MessageBuilder<'_, 0> = MessageBuilder::try_new(&mut buf, header).unwrap();
b.push_a_answer(&qname, 120, Ipv4Addr::new(10, 0, 0, 2), false)
.unwrap();
let n = b.finish().unwrap();
let mut pkt = buf[..n].to_vec();
pkt[7] = 0;
pkt[11] = 1;
truncate_rdata(pkt, 3)
}
fn build_qr1_malformed_answer() -> Vec<u8> {
let qname = Name::try_from_str("mal-ans.local.").unwrap();
let mut buf = [0u8; 512];
let header = Header::new().with_flags(Flags::new().with_response());
let mut b: MessageBuilder<'_, 0> = MessageBuilder::try_new(&mut buf, header).unwrap();
b.push_a_answer(&qname, 120, Ipv4Addr::new(10, 0, 0, 3), false)
.unwrap();
let n = b.finish().unwrap();
truncate_rdata(buf[..n].to_vec(), 3)
}
fn build_qr0_malformed_answer() -> Vec<u8> {
let qname = Name::try_from_str("mal-ans.local.").unwrap();
let mut buf = [0u8; 512];
let header = Header::new(); let mut b: MessageBuilder<'_, 0> = MessageBuilder::try_new(&mut buf, header).unwrap();
b.push_a_answer(&qname, 120, Ipv4Addr::new(10, 0, 0, 4), false)
.unwrap();
let n = b.finish().unwrap();
truncate_rdata(buf[..n].to_vec(), 3)
}
fn build_qr0_malformed_authority() -> Vec<u8> {
let qname = Name::try_from_str("mal-auth.local.").unwrap();
let mut buf = [0u8; 512];
let header = Header::new(); let mut b: MessageBuilder<'_, 0> = MessageBuilder::try_new(&mut buf, header).unwrap();
b.push_a_authority(&qname, 120, Ipv4Addr::new(10, 0, 0, 5))
.unwrap();
let n = b.finish().unwrap();
truncate_rdata(buf[..n].to_vec(), 3)
}
fn assert_parse_error_exactly_once(e: &mut Endp, pkt: &[u8], src_port: u16, label: &str) {
let src: std::net::SocketAddr = format!("192.0.2.99:{src_port}").parse().unwrap();
let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, pkt, false)
.expect("valid header — handle() must not fail at entry");
for ev in route {
let _ = ev; }
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"{label}: packets_rx must be bumped exactly once"
);
assert_eq!(
after.parse_errors,
before.parse_errors + 1,
"{label}: parse_errors must be bumped exactly once"
);
assert_eq!(
after.packets_dropped, before.packets_dropped,
"{label}: packets_dropped must NOT be bumped for a parse error"
);
}
#[test]
fn r10_qr0_malformed_additional_bumps_parse_errors_exactly_once() {
let mut e = make_endpoint(20);
let pkt = build_qr0_malformed_additional();
assert_parse_error_exactly_once(&mut e, &pkt, 5353, "QR=0 malformed additional");
}
#[test]
fn r10_qr1_malformed_additional_bumps_parse_errors_exactly_once() {
let mut e = make_endpoint(21);
let pkt = build_qr1_malformed_additional();
assert_parse_error_exactly_once(&mut e, &pkt, 5353, "QR=1 malformed additional");
}
#[test]
fn r10_qr1_malformed_answer_bumps_parse_errors_exactly_once() {
let mut e = make_endpoint(22);
let pkt = build_qr1_malformed_answer();
assert_parse_error_exactly_once(&mut e, &pkt, 5353, "QR=1 malformed answer");
}
#[test]
fn r10_qr0_malformed_answer_bumps_parse_errors_exactly_once() {
let mut e = make_endpoint(23);
let pkt = build_qr0_malformed_answer();
assert_parse_error_exactly_once(&mut e, &pkt, 5353, "QR=0 malformed answer");
}
#[test]
fn r10_qr0_malformed_authority_bumps_parse_errors_exactly_once() {
let mut e = make_endpoint(24);
let pkt = build_qr0_malformed_authority();
assert_parse_error_exactly_once(&mut e, &pkt, 5353, "QR=0 malformed authority");
}
#[test]
fn r10_answer_questions_false_malformed_additional_bumps_parse_errors() {
use rand::SeedableRng;
let rng = rand::rngs::StdRng::from_seed([25u8; 32]);
let cfg = EndpointConfig::new().with_answer_questions(false);
let mut e = Endp::try_new(cfg, rng);
let pkt = build_qr0_malformed_additional();
assert_parse_error_exactly_once(
&mut e,
&pkt,
5353,
"answer_questions=false malformed additional",
);
}
fn build_qr0_malformed_question() -> Vec<u8> {
let mut msg = vec![0u8; 12];
msg[5] = 1; msg.push(0x05); msg
}
fn build_qr1_malformed_question() -> Vec<u8> {
let mut msg = vec![0u8; 12];
msg[2] = 0x84; msg[5] = 1; msg.push(0x05); msg
}
fn build_qr0_well_formed_authority() -> Vec<u8> {
let qname = Name::try_from_str("auth-host.local.").unwrap();
let mut buf = [0u8; 512];
let header = mdns_proto::wire::Header::new(); let mut b: mdns_proto::wire::MessageBuilder<'_, 0> =
mdns_proto::wire::MessageBuilder::try_new(&mut buf, header).unwrap();
b.push_a_authority(&qname, 120, Ipv4Addr::new(10, 0, 0, 9))
.unwrap();
let n = b.finish().unwrap();
buf[..n].to_vec()
}
fn build_qr0_malformed_authority_for_r11() -> Vec<u8> {
build_qr0_malformed_authority()
}
fn assert_parse_error_exactly_once_port(e: &mut Endp, pkt: &[u8], src_port: u16, label: &str) {
let src: std::net::SocketAddr = format!("192.0.2.99:{src_port}").parse().unwrap();
let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, pkt, false)
.expect("valid header — handle() must not fail at entry");
for ev in route {
let _ = ev; }
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"{label}: packets_rx must be bumped exactly once"
);
assert_eq!(
after.parse_errors,
before.parse_errors + 1,
"{label}: parse_errors must be bumped exactly once"
);
assert_eq!(
after.packets_dropped, before.packets_dropped,
"{label}: packets_dropped must NOT be bumped for a parse error"
);
}
#[test]
fn r11_answer_questions_false_malformed_question_qr0_bumps_parse_errors() {
use rand::SeedableRng;
let rng = rand::rngs::StdRng::from_seed([30u8; 32]);
let cfg = EndpointConfig::new().with_answer_questions(false);
let mut e = Endp::try_new(cfg, rng);
let pkt = build_qr0_malformed_question();
assert_parse_error_exactly_once_port(
&mut e,
&pkt,
5353,
"answer_questions=false QR=0 malformed question",
);
}
#[test]
fn r11_answer_questions_false_malformed_question_qr1_bumps_parse_errors() {
use rand::SeedableRng;
let rng = rand::rngs::StdRng::from_seed([31u8; 32]);
let cfg = EndpointConfig::new().with_answer_questions(false);
let mut e = Endp::try_new(cfg, rng);
let pkt = build_qr1_malformed_question();
assert_parse_error_exactly_once_port(
&mut e,
&pkt,
5353,
"answer_questions=false QR=1 malformed question",
);
}
#[test]
fn r11_answer_questions_true_malformed_question_bumps_parse_errors_exactly_once() {
let mut e = make_endpoint(32);
let pkt = build_qr0_malformed_question();
assert_parse_error_exactly_once_port(
&mut e,
&pkt,
5353,
"answer_questions=true QR=0 malformed question",
);
}
#[test]
fn r11_non_5353_malformed_authority_bumps_parse_errors() {
let mut e = make_endpoint(33);
let pkt = build_qr0_malformed_authority_for_r11();
assert_parse_error_exactly_once_port(
&mut e,
&pkt,
40000, "non-5353 source QR=0 malformed authority",
);
}
#[test]
fn r11_5353_malformed_authority_bumps_parse_errors_exactly_once() {
let mut e = make_endpoint(34);
let pkt = build_qr0_malformed_authority_for_r11();
assert_parse_error_exactly_once_port(&mut e, &pkt, 5353, "5353 source QR=0 malformed authority");
}
#[test]
fn r11_well_formed_non_5353_authority_no_false_positive() {
let mut e = make_endpoint(35);
let pkt = build_qr0_well_formed_authority();
let src: std::net::SocketAddr = "192.0.2.99:40000".parse().unwrap(); let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, &pkt, false)
.expect("valid header");
for ev in route {
let _ = ev;
}
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"well-formed non-5353 authority: packets_rx must be bumped"
);
assert_eq!(
after.parse_errors, before.parse_errors,
"well-formed non-5353 authority: parse_errors must NOT be bumped"
);
assert_eq!(
after.packets_dropped, before.packets_dropped,
"well-formed non-5353 authority: packets_dropped must NOT be bumped \
(section-level authority suppression is not a whole-datagram drop)"
);
}
#[test]
fn r11_well_formed_packet_does_not_bump_reject_counters() {
let mut e = make_endpoint(36);
let qname = Name::try_from_str("r11-well-formed.local.").unwrap();
let pkt = build_srv_response(&qname);
let src: std::net::SocketAddr = "192.0.2.99:5353".parse().unwrap();
let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, &pkt, false)
.expect("valid header");
for ev in route {
let _ = ev;
}
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"well-formed: packets_rx must be bumped"
);
assert_eq!(
after.parse_errors, before.parse_errors,
"well-formed: parse_errors must NOT be bumped"
);
assert_eq!(
after.packets_dropped, before.packets_dropped,
"well-formed: packets_dropped must NOT be bumped"
);
}
#[test]
fn r10_well_formed_packet_does_not_bump_reject_counters() {
let mut e = make_endpoint(26);
let qname = Name::try_from_str("well-formed.local.").unwrap();
let pkt = build_srv_response(&qname);
let src: std::net::SocketAddr = "192.0.2.99:5353".parse().unwrap();
let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, &pkt, false)
.expect("valid header");
for ev in route {
let _ = ev;
}
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"well-formed: packets_rx must be bumped"
);
assert_eq!(
after.parse_errors, before.parse_errors,
"well-formed: parse_errors must NOT be bumped"
);
assert_eq!(
after.packets_dropped, before.packets_dropped,
"well-formed: packets_dropped must NOT be bumped"
);
}
#[test]
fn r12_malformed_self_loopback_bumps_dropped_not_parse_errors() {
let mut e = make_endpoint(40);
let pkt = build_qr1_malformed_answer();
let src: std::net::SocketAddr = "192.0.2.50:5353".parse().unwrap();
let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, &pkt, true)
.expect("valid header — suppression must not prevent handle() returning Ok");
for ev in route {
let _ = ev;
}
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"r12 self-loopback malformed: packets_rx must be bumped exactly once"
);
assert_eq!(
after.packets_dropped,
before.packets_dropped + 1,
"r12 self-loopback malformed: packets_dropped must be bumped (suppression)"
);
assert_eq!(
after.parse_errors, before.parse_errors,
"r12 self-loopback malformed: parse_errors must NOT be bumped \
(suppression takes precedence over section malformation)"
);
}
#[test]
fn r12_malformed_untrusted_response_bumps_dropped_not_parse_errors() {
let mut e = make_endpoint(41);
let pkt = build_qr1_malformed_answer();
let src: std::net::SocketAddr = "192.0.2.51:40000".parse().unwrap();
let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, &pkt, false)
.expect("valid header");
for ev in route {
let _ = ev;
}
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"r12 untrusted-response malformed: packets_rx must be bumped exactly once"
);
assert_eq!(
after.packets_dropped,
before.packets_dropped + 1,
"r12 untrusted-response malformed: packets_dropped must be bumped (suppression)"
);
assert_eq!(
after.parse_errors, before.parse_errors,
"r12 untrusted-response malformed: parse_errors must NOT be bumped \
(suppression takes precedence over section malformation)"
);
}
#[test]
fn r12_malformed_not_suppressed_bumps_parse_errors_not_dropped() {
let mut e = make_endpoint(42);
let pkt = build_qr1_malformed_answer();
let src: std::net::SocketAddr = "192.0.2.52:5353".parse().unwrap();
let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, &pkt, false)
.expect("valid header");
for ev in route {
let _ = ev;
}
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"r12 not-suppressed malformed: packets_rx must be bumped exactly once"
);
assert_eq!(
after.parse_errors,
before.parse_errors + 1,
"r12 not-suppressed malformed: parse_errors must be bumped"
);
assert_eq!(
after.packets_dropped, before.packets_dropped,
"r12 not-suppressed malformed: packets_dropped must NOT be bumped"
);
}
#[test]
fn r12_well_formed_suppressed_bumps_dropped_not_parse_errors() {
let mut e = make_endpoint(43);
let qname = Name::try_from_str("r12-wellformed-self.local.").unwrap();
let pkt = build_srv_response(&qname);
let src: std::net::SocketAddr = "192.0.2.53:5353".parse().unwrap();
let local_ip: std::net::IpAddr = "192.0.2.1".parse().unwrap();
let now = StdInstant::now();
let before = e.stats();
let route = e
.handle(now, src, local_ip, 0, &pkt, true)
.expect("valid header");
for ev in route {
let _ = ev;
}
let after = e.stats();
assert_eq!(
after.packets_rx,
before.packets_rx + 1,
"r12 well-formed suppressed: packets_rx must be bumped exactly once"
);
assert_eq!(
after.packets_dropped,
before.packets_dropped + 1,
"r12 well-formed suppressed: packets_dropped must be bumped (suppression)"
);
assert_eq!(
after.parse_errors, before.parse_errors,
"r12 well-formed suppressed: parse_errors must NOT be bumped"
);
}