refactor: dedupe HTTPS_TYPE, record-walk, and test rdata builder

- Drop `const HTTPS_TYPE: u16 = 65;` in favor of `QueryType::HTTPS.to_num()`
  at the single call site — avoids a fresh magic number alongside the
  existing enum mapping in question.rs.
- Add `DnsPacket::for_each_record_mut` so `strip_https_ipv6_hints` stops
  hand-rolling the answers/authorities/resources walk; future section
  rewrites go through the same helper.
- Promote the SVCB test-rdata builder from `svcb::tests` to module scope
  as `pub(crate) #[cfg(test)] fn build_rdata`, and reuse it in the two
  pipeline tests in ctx.rs — kills ~20 lines of byte-fiddling and keeps
  one RDATA-construction code path.
This commit is contained in:
Razvan Dimescu
2026-04-19 05:58:47 +03:00
parent 22dd3cd222
commit 61ea2e510d
3 changed files with 68 additions and 66 deletions

View File

@@ -511,27 +511,17 @@ fn strip_dnssec_records(pkt: &mut DnsPacket) {
pkt.resources.retain(|r| !is_dnssec_record(r)); pkt.resources.retain(|r| !is_dnssec_record(r));
} }
/// HTTPS RR type code (RFC 9460). Numa stores HTTPS/SVCB records as
/// `DnsRecord::UNKNOWN { qtype: 65, .. }` since it doesn't have a
/// dedicated variant.
const HTTPS_TYPE: u16 = 65;
fn strip_https_ipv6_hints(pkt: &mut DnsPacket) { fn strip_https_ipv6_hints(pkt: &mut DnsPacket) {
let rewrite = |rec: &mut DnsRecord| { let https_qtype = QueryType::HTTPS.to_num();
if let DnsRecord::UNKNOWN { pkt.for_each_record_mut(|rec| {
qtype: HTTPS_TYPE, if let DnsRecord::UNKNOWN { qtype, data, .. } = rec {
data, if *qtype == https_qtype {
..
} = rec
{
if let Some(new_data) = crate::svcb::strip_ipv6hint(data) { if let Some(new_data) = crate::svcb::strip_ipv6hint(data) {
*data = new_data; *data = new_data;
} }
} }
}; }
pkt.answers.iter_mut().for_each(rewrite); });
pkt.authorities.iter_mut().for_each(rewrite);
pkt.resources.iter_mut().for_each(rewrite);
} }
fn is_special_use_domain(qname: &str) -> bool { fn is_special_use_domain(qname: &str) -> bool {
@@ -1285,22 +1275,20 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn pipeline_filter_aaaa_strips_ipv6hint_from_https() { async fn pipeline_filter_aaaa_strips_ipv6hint_from_https() {
// Build an HTTPS record (type 65) with ipv6hint (key 6). Cache it, // Build an HTTPS record (type 65) with alpn + ipv6hint, cache it,
// then query with filter_aaaa on — the returned rdata must have // then query with filter_aaaa on — the returned rdata must have
// ipv6hint removed. // ipv6hint (20 bytes) removed.
let mut rdata = Vec::new(); let rdata = crate::svcb::build_rdata(
rdata.extend_from_slice(&1u16.to_be_bytes()); // priority 1,
rdata.push(0); // empty target (".") &[],
// alpn = ["h3"] &[
rdata.extend_from_slice(&1u16.to_be_bytes()); (1, vec![0x02, b'h', b'3']),
rdata.extend_from_slice(&3u16.to_be_bytes()); (
rdata.extend_from_slice(&[0x02, b'h', b'3']); 6,
// ipv6hint = [2606:4700::1] vec![0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01],
rdata.extend_from_slice(&6u16.to_be_bytes()); ),
rdata.extend_from_slice(&16u16.to_be_bytes()); ],
rdata.extend_from_slice(&[ );
0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01,
]);
let mut pkt = DnsPacket::new(); let mut pkt = DnsPacket::new();
pkt.header.response = true; pkt.header.response = true;
@@ -1349,14 +1337,14 @@ mod tests {
// Regression guard for the DO-bit gate in resolve_query: modifying // Regression guard for the DO-bit gate in resolve_query: modifying
// HTTPS rdata invalidates any accompanying RRSIG, so a DO=1 client // HTTPS rdata invalidates any accompanying RRSIG, so a DO=1 client
// must receive the record untouched even when filter_aaaa is on. // must receive the record untouched even when filter_aaaa is on.
let mut rdata = Vec::new(); let rdata = crate::svcb::build_rdata(
rdata.extend_from_slice(&1u16.to_be_bytes()); 1,
rdata.push(0); &[],
rdata.extend_from_slice(&6u16.to_be_bytes()); &[(
rdata.extend_from_slice(&16u16.to_be_bytes()); 6,
rdata.extend_from_slice(&[ vec![0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01],
0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, )],
]); );
let mut pkt = DnsPacket::new(); let mut pkt = DnsPacket::new();
pkt.header.response = true; pkt.header.response = true;

View File

@@ -85,6 +85,14 @@ impl DnsPacket {
+ self.edns.as_ref().map_or(0, |e| e.options.capacity()) + self.edns.as_ref().map_or(0, |e| e.options.capacity())
} }
/// Apply `f` to every record in the three RR sections (answers,
/// authorities, resources). Does not touch questions or edns.
pub fn for_each_record_mut(&mut self, mut f: impl FnMut(&mut DnsRecord)) {
self.answers.iter_mut().for_each(&mut f);
self.authorities.iter_mut().for_each(&mut f);
self.resources.iter_mut().for_each(&mut f);
}
pub fn response_from(query: &DnsPacket, rescode: crate::header::ResultCode) -> DnsPacket { pub fn response_from(query: &DnsPacket, rescode: crate::header::ResultCode) -> DnsPacket {
let mut resp = DnsPacket::new(); let mut resp = DnsPacket::new();
resp.header.id = query.header.id; resp.header.id = query.header.id;

View File

@@ -80,13 +80,15 @@ pub fn strip_ipv6hint(rdata: &[u8]) -> Option<Vec<u8>> {
Some(out) Some(out)
} }
#[cfg(test)]
mod tests {
use super::*;
/// Build an SVCB RDATA blob from a priority, target labels, and /// Build an SVCB RDATA blob from a priority, target labels, and
/// (key, value) param pairs. Used for constructing test vectors. /// (key, value) param pairs. Shared by `svcb` unit tests and `ctx`
fn build(priority: u16, target: &[&str], params: &[(u16, Vec<u8>)]) -> Vec<u8> { /// pipeline tests that need to seed the cache with a synthetic HTTPS RR.
#[cfg(test)]
pub(crate) fn build_rdata(
priority: u16,
target: &[&str],
params: &[(u16, Vec<u8>)],
) -> Vec<u8> {
let mut out = Vec::new(); let mut out = Vec::new();
out.extend_from_slice(&priority.to_be_bytes()); out.extend_from_slice(&priority.to_be_bytes());
for label in target { for label in target {
@@ -102,6 +104,10 @@ mod tests {
out out
} }
#[cfg(test)]
mod tests {
use super::*;
fn alpn_h3() -> (u16, Vec<u8>) { fn alpn_h3() -> (u16, Vec<u8>) {
// alpn = ["h3"]: one length-prefixed ALPN id // alpn = ["h3"]: one length-prefixed ALPN id
(1, vec![0x02, b'h', b'3']) (1, vec![0x02, b'h', b'3'])
@@ -123,35 +129,35 @@ mod tests {
#[test] #[test]
fn strips_ipv6hint_and_keeps_other_params() { fn strips_ipv6hint_and_keeps_other_params() {
let rdata = build(1, &[], &[alpn_h3(), ipv4hint_single(), ipv6hint_single()]); let rdata = build_rdata(1, &[], &[alpn_h3(), ipv4hint_single(), ipv6hint_single()]);
let stripped = strip_ipv6hint(&rdata).expect("ipv6hint present → stripped"); let stripped = strip_ipv6hint(&rdata).expect("ipv6hint present → stripped");
let expected = build(1, &[], &[alpn_h3(), ipv4hint_single()]); let expected = build_rdata(1, &[], &[alpn_h3(), ipv4hint_single()]);
assert_eq!(stripped, expected); assert_eq!(stripped, expected);
} }
#[test] #[test]
fn no_ipv6hint_returns_none() { fn no_ipv6hint_returns_none() {
let rdata = build(1, &[], &[alpn_h3(), ipv4hint_single()]); let rdata = build_rdata(1, &[], &[alpn_h3(), ipv4hint_single()]);
assert!(strip_ipv6hint(&rdata).is_none()); assert!(strip_ipv6hint(&rdata).is_none());
} }
#[test] #[test]
fn alias_mode_empty_params_returns_none() { fn alias_mode_empty_params_returns_none() {
let rdata = build(0, &["example", "com"], &[]); let rdata = build_rdata(0, &["example", "com"], &[]);
assert!(strip_ipv6hint(&rdata).is_none()); assert!(strip_ipv6hint(&rdata).is_none());
} }
#[test] #[test]
fn only_ipv6hint_yields_empty_param_section() { fn only_ipv6hint_yields_empty_param_section() {
let rdata = build(1, &[], &[ipv6hint_single()]); let rdata = build_rdata(1, &[], &[ipv6hint_single()]);
let stripped = strip_ipv6hint(&rdata).expect("ipv6hint present → stripped"); let stripped = strip_ipv6hint(&rdata).expect("ipv6hint present → stripped");
let expected = build(1, &[], &[]); let expected = build_rdata(1, &[], &[]);
assert_eq!(stripped, expected); assert_eq!(stripped, expected);
} }
#[test] #[test]
fn preserves_target_name() { fn preserves_target_name() {
let rdata = build(1, &["svc", "example", "net"], &[ipv6hint_single()]); let rdata = build_rdata(1, &["svc", "example", "net"], &[ipv6hint_single()]);
let stripped = strip_ipv6hint(&rdata).unwrap(); let stripped = strip_ipv6hint(&rdata).unwrap();
assert!(stripped.starts_with(&[0x00, 0x01])); // priority assert!(stripped.starts_with(&[0x00, 0x01])); // priority
assert_eq!(&stripped[2..6], b"\x03svc"); assert_eq!(&stripped[2..6], b"\x03svc");