From 24610ae3fe759de22dff1fbcdc164fed937f52e9 Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Sun, 19 Apr 2026 07:49:35 +0300 Subject: [PATCH 1/2] feat(question): add SVCB, LOC, NAPTR variants to QueryType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Logs were printing UNKNOWN(64), UNKNOWN(29), UNKNOWN(35) for SVCB, LOC, and NAPTR — three RR types that have been registered for years and show up in the wild (notably SVCB via RFC 9462 DDR clients querying _dns.resolver.arpa). Adds the variants and replaces the SVCB_QTYPE u16 const introduced in #119 with QueryType::SVCB.to_num(), matching the HTTPS path. Closes #114. --- src/ctx.rs | 11 +++++------ src/question.rs | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/ctx.rs b/src/ctx.rs index 23b1014..71e81c9 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -511,13 +511,12 @@ fn strip_dnssec_records(pkt: &mut DnsPacket) { pkt.resources.retain(|r| !is_dnssec_record(r)); } -const SVCB_QTYPE: u16 = 64; - fn strip_svcb_ipv6_hints(pkt: &mut DnsPacket) { let https_qtype = QueryType::HTTPS.to_num(); + let svcb_qtype = QueryType::SVCB.to_num(); pkt.for_each_record_mut(|rec| { if let DnsRecord::UNKNOWN { qtype, data, .. } = rec { - if *qtype == https_qtype || *qtype == SVCB_QTYPE { + if *qtype == https_qtype || *qtype == svcb_qtype { if let Some(new_data) = crate::svcb::strip_ipv6hint(data) { *data = new_data; } @@ -1307,7 +1306,7 @@ mod tests { let mut svcb_pkt = pkt.clone(); svcb_pkt.questions[0].name = "svc.test".to_string(); - svcb_pkt.questions[0].qtype = QueryType::UNKNOWN(64); + svcb_pkt.questions[0].qtype = QueryType::SVCB; if let DnsRecord::UNKNOWN { domain, qtype, .. } = &mut svcb_pkt.answers[0] { *domain = "svc.test".to_string(); *qtype = 64; @@ -1322,12 +1321,12 @@ mod tests { ctx.cache .write() .unwrap() - .insert("svc.test", QueryType::UNKNOWN(64), &svcb_pkt); + .insert("svc.test", QueryType::SVCB, &svcb_pkt); let ctx = Arc::new(ctx); for (name, qtype, label) in [ ("hints.test", QueryType::HTTPS, "HTTPS"), - ("svc.test", QueryType::UNKNOWN(64), "SVCB"), + ("svc.test", QueryType::SVCB, "SVCB"), ] { let (resp, path) = resolve_in_test(&ctx, name, qtype).await; assert_eq!(path, QueryPath::Cached, "{label}"); diff --git a/src/question.rs b/src/question.rs index dc23dd1..9523339 100644 --- a/src/question.rs +++ b/src/question.rs @@ -12,13 +12,16 @@ pub enum QueryType { MX, // 15 TXT, // 16 AAAA, // 28 + LOC, // 29 SRV, // 33 + NAPTR, // 35 DS, // 43 RRSIG, // 46 NSEC, // 47 DNSKEY, // 48 NSEC3, // 50 OPT, // 41 (EDNS0 pseudo-type) + SVCB, // 64 HTTPS, // 65 } @@ -34,13 +37,16 @@ impl QueryType { QueryType::MX => 15, QueryType::TXT => 16, QueryType::AAAA => 28, + QueryType::LOC => 29, QueryType::SRV => 33, + QueryType::NAPTR => 35, QueryType::OPT => 41, QueryType::DS => 43, QueryType::RRSIG => 46, QueryType::NSEC => 47, QueryType::DNSKEY => 48, QueryType::NSEC3 => 50, + QueryType::SVCB => 64, QueryType::HTTPS => 65, } } @@ -55,13 +61,16 @@ impl QueryType { 15 => QueryType::MX, 16 => QueryType::TXT, 28 => QueryType::AAAA, + 29 => QueryType::LOC, 33 => QueryType::SRV, + 35 => QueryType::NAPTR, 41 => QueryType::OPT, 43 => QueryType::DS, 46 => QueryType::RRSIG, 47 => QueryType::NSEC, 48 => QueryType::DNSKEY, 50 => QueryType::NSEC3, + 64 => QueryType::SVCB, 65 => QueryType::HTTPS, _ => QueryType::UNKNOWN(num), } @@ -77,13 +86,16 @@ impl QueryType { QueryType::MX => "MX", QueryType::TXT => "TXT", QueryType::AAAA => "AAAA", + QueryType::LOC => "LOC", QueryType::SRV => "SRV", + QueryType::NAPTR => "NAPTR", QueryType::OPT => "OPT", QueryType::DS => "DS", QueryType::RRSIG => "RRSIG", QueryType::NSEC => "NSEC", QueryType::DNSKEY => "DNSKEY", QueryType::NSEC3 => "NSEC3", + QueryType::SVCB => "SVCB", QueryType::HTTPS => "HTTPS", QueryType::UNKNOWN(_) => "UNKNOWN", } @@ -99,12 +111,15 @@ impl QueryType { "MX" => Some(QueryType::MX), "TXT" => Some(QueryType::TXT), "AAAA" => Some(QueryType::AAAA), + "LOC" => Some(QueryType::LOC), "SRV" => Some(QueryType::SRV), + "NAPTR" => Some(QueryType::NAPTR), "DS" => Some(QueryType::DS), "RRSIG" => Some(QueryType::RRSIG), "DNSKEY" => Some(QueryType::DNSKEY), "NSEC" => Some(QueryType::NSEC), "NSEC3" => Some(QueryType::NSEC3), + "SVCB" => Some(QueryType::SVCB), "HTTPS" => Some(QueryType::HTTPS), _ => None, } From 5725f94ff34684c9ebb7681dafc091e9940f51ee Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Sun, 19 Apr 2026 08:01:18 +0300 Subject: [PATCH 2/2] refactor(question): collapse QueryType impls behind define_qtypes! macro Adding a record type used to require 5 edits across the file (enum variant, to_num, from_num, as_str, parse_str). The macro takes a single (variant, num, str) tuple per type and generates the enum plus all four methods. UNKNOWN(u16) stays hand-coded since it carries data and can't sit in the table. src/question.rs: 156 lines -> 92 lines, no behavior change. --- src/question.rs | 179 ++++++++++++++++-------------------------------- 1 file changed, 58 insertions(+), 121 deletions(-) diff --git a/src/question.rs b/src/question.rs index 9523339..fbb3fef 100644 --- a/src/question.rs +++ b/src/question.rs @@ -1,129 +1,66 @@ use crate::buffer::BytePacketBuffer; use crate::Result; -#[derive(PartialEq, Eq, Debug, Clone, Hash, Copy)] -pub enum QueryType { - UNKNOWN(u16), - A, // 1 - NS, // 2 - CNAME, // 5 - SOA, // 6 - PTR, // 12 - MX, // 15 - TXT, // 16 - AAAA, // 28 - LOC, // 29 - SRV, // 33 - NAPTR, // 35 - DS, // 43 - RRSIG, // 46 - NSEC, // 47 - DNSKEY, // 48 - NSEC3, // 50 - OPT, // 41 (EDNS0 pseudo-type) - SVCB, // 64 - HTTPS, // 65 +macro_rules! define_qtypes { + ( $( $variant:ident = $num:literal, $str:literal ),* $(,)? ) => { + #[derive(PartialEq, Eq, Debug, Clone, Hash, Copy)] + pub enum QueryType { + UNKNOWN(u16), + $( $variant, )* + } + + impl QueryType { + pub fn to_num(&self) -> u16 { + match *self { + QueryType::UNKNOWN(x) => x, + $( QueryType::$variant => $num, )* + } + } + + pub fn from_num(num: u16) -> QueryType { + match num { + $( $num => QueryType::$variant, )* + _ => QueryType::UNKNOWN(num), + } + } + + pub fn as_str(&self) -> &'static str { + match self { + QueryType::UNKNOWN(_) => "UNKNOWN", + $( QueryType::$variant => $str, )* + } + } + + pub fn parse_str(s: &str) -> Option { + match s.to_ascii_uppercase().as_str() { + $( $str => Some(QueryType::$variant), )* + _ => None, + } + } + } + }; } -impl QueryType { - pub fn to_num(&self) -> u16 { - match *self { - QueryType::UNKNOWN(x) => x, - QueryType::A => 1, - QueryType::NS => 2, - QueryType::CNAME => 5, - QueryType::SOA => 6, - QueryType::PTR => 12, - QueryType::MX => 15, - QueryType::TXT => 16, - QueryType::AAAA => 28, - QueryType::LOC => 29, - QueryType::SRV => 33, - QueryType::NAPTR => 35, - QueryType::OPT => 41, - QueryType::DS => 43, - QueryType::RRSIG => 46, - QueryType::NSEC => 47, - QueryType::DNSKEY => 48, - QueryType::NSEC3 => 50, - QueryType::SVCB => 64, - QueryType::HTTPS => 65, - } - } - - pub fn from_num(num: u16) -> QueryType { - match num { - 1 => QueryType::A, - 2 => QueryType::NS, - 5 => QueryType::CNAME, - 6 => QueryType::SOA, - 12 => QueryType::PTR, - 15 => QueryType::MX, - 16 => QueryType::TXT, - 28 => QueryType::AAAA, - 29 => QueryType::LOC, - 33 => QueryType::SRV, - 35 => QueryType::NAPTR, - 41 => QueryType::OPT, - 43 => QueryType::DS, - 46 => QueryType::RRSIG, - 47 => QueryType::NSEC, - 48 => QueryType::DNSKEY, - 50 => QueryType::NSEC3, - 64 => QueryType::SVCB, - 65 => QueryType::HTTPS, - _ => QueryType::UNKNOWN(num), - } - } - - pub fn as_str(&self) -> &'static str { - match self { - QueryType::A => "A", - QueryType::NS => "NS", - QueryType::CNAME => "CNAME", - QueryType::SOA => "SOA", - QueryType::PTR => "PTR", - QueryType::MX => "MX", - QueryType::TXT => "TXT", - QueryType::AAAA => "AAAA", - QueryType::LOC => "LOC", - QueryType::SRV => "SRV", - QueryType::NAPTR => "NAPTR", - QueryType::OPT => "OPT", - QueryType::DS => "DS", - QueryType::RRSIG => "RRSIG", - QueryType::NSEC => "NSEC", - QueryType::DNSKEY => "DNSKEY", - QueryType::NSEC3 => "NSEC3", - QueryType::SVCB => "SVCB", - QueryType::HTTPS => "HTTPS", - QueryType::UNKNOWN(_) => "UNKNOWN", - } - } - - pub fn parse_str(s: &str) -> Option { - match s.to_ascii_uppercase().as_str() { - "A" => Some(QueryType::A), - "NS" => Some(QueryType::NS), - "CNAME" => Some(QueryType::CNAME), - "SOA" => Some(QueryType::SOA), - "PTR" => Some(QueryType::PTR), - "MX" => Some(QueryType::MX), - "TXT" => Some(QueryType::TXT), - "AAAA" => Some(QueryType::AAAA), - "LOC" => Some(QueryType::LOC), - "SRV" => Some(QueryType::SRV), - "NAPTR" => Some(QueryType::NAPTR), - "DS" => Some(QueryType::DS), - "RRSIG" => Some(QueryType::RRSIG), - "DNSKEY" => Some(QueryType::DNSKEY), - "NSEC" => Some(QueryType::NSEC), - "NSEC3" => Some(QueryType::NSEC3), - "SVCB" => Some(QueryType::SVCB), - "HTTPS" => Some(QueryType::HTTPS), - _ => None, - } - } +define_qtypes! { + A = 1, "A", + NS = 2, "NS", + CNAME = 5, "CNAME", + SOA = 6, "SOA", + PTR = 12, "PTR", + MX = 15, "MX", + TXT = 16, "TXT", + AAAA = 28, "AAAA", + LOC = 29, "LOC", + SRV = 33, "SRV", + NAPTR = 35, "NAPTR", + OPT = 41, "OPT", + DS = 43, "DS", + RRSIG = 46, "RRSIG", + NSEC = 47, "NSEC", + DNSKEY = 48, "DNSKEY", + NSEC3 = 50, "NSEC3", + SVCB = 64, "SVCB", + HTTPS = 65, "HTTPS", } #[derive(Debug, Clone, PartialEq, Eq)]