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:
66
src/ctx.rs
66
src/ctx.rs
@@ -511,27 +511,17 @@ fn strip_dnssec_records(pkt: &mut DnsPacket) {
|
||||
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) {
|
||||
let rewrite = |rec: &mut DnsRecord| {
|
||||
if let DnsRecord::UNKNOWN {
|
||||
qtype: HTTPS_TYPE,
|
||||
data,
|
||||
..
|
||||
} = rec
|
||||
{
|
||||
let https_qtype = QueryType::HTTPS.to_num();
|
||||
pkt.for_each_record_mut(|rec| {
|
||||
if let DnsRecord::UNKNOWN { qtype, data, .. } = rec {
|
||||
if *qtype == https_qtype {
|
||||
if let Some(new_data) = crate::svcb::strip_ipv6hint(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 {
|
||||
@@ -1285,22 +1275,20 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
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
|
||||
// ipv6hint removed.
|
||||
let mut rdata = Vec::new();
|
||||
rdata.extend_from_slice(&1u16.to_be_bytes()); // priority
|
||||
rdata.push(0); // empty target (".")
|
||||
// alpn = ["h3"]
|
||||
rdata.extend_from_slice(&1u16.to_be_bytes());
|
||||
rdata.extend_from_slice(&3u16.to_be_bytes());
|
||||
rdata.extend_from_slice(&[0x02, b'h', b'3']);
|
||||
// ipv6hint = [2606:4700::1]
|
||||
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,
|
||||
]);
|
||||
// ipv6hint (20 bytes) removed.
|
||||
let rdata = crate::svcb::build_rdata(
|
||||
1,
|
||||
&[],
|
||||
&[
|
||||
(1, vec![0x02, b'h', b'3']),
|
||||
(
|
||||
6,
|
||||
vec![0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
let mut pkt = DnsPacket::new();
|
||||
pkt.header.response = true;
|
||||
@@ -1349,14 +1337,14 @@ mod tests {
|
||||
// Regression guard for the DO-bit gate in resolve_query: modifying
|
||||
// HTTPS rdata invalidates any accompanying RRSIG, so a DO=1 client
|
||||
// must receive the record untouched even when filter_aaaa is on.
|
||||
let mut rdata = Vec::new();
|
||||
rdata.extend_from_slice(&1u16.to_be_bytes());
|
||||
rdata.push(0);
|
||||
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 rdata = crate::svcb::build_rdata(
|
||||
1,
|
||||
&[],
|
||||
&[(
|
||||
6,
|
||||
vec![0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01],
|
||||
)],
|
||||
);
|
||||
|
||||
let mut pkt = DnsPacket::new();
|
||||
pkt.header.response = true;
|
||||
|
||||
@@ -85,6 +85,14 @@ impl DnsPacket {
|
||||
+ 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 {
|
||||
let mut resp = DnsPacket::new();
|
||||
resp.header.id = query.header.id;
|
||||
|
||||
34
src/svcb.rs
34
src/svcb.rs
@@ -80,13 +80,15 @@ pub fn strip_ipv6hint(rdata: &[u8]) -> Option<Vec<u8>> {
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// Build an SVCB RDATA blob from a priority, target labels, and
|
||||
/// (key, value) param pairs. Shared by `svcb` unit tests and `ctx`
|
||||
/// pipeline tests that need to seed the cache with a synthetic HTTPS RR.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Build an SVCB RDATA blob from a priority, target labels, and
|
||||
/// (key, value) param pairs. Used for constructing test vectors.
|
||||
fn build(priority: u16, target: &[&str], params: &[(u16, Vec<u8>)]) -> Vec<u8> {
|
||||
pub(crate) fn build_rdata(
|
||||
priority: u16,
|
||||
target: &[&str],
|
||||
params: &[(u16, Vec<u8>)],
|
||||
) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
out.extend_from_slice(&priority.to_be_bytes());
|
||||
for label in target {
|
||||
@@ -100,7 +102,11 @@ mod tests {
|
||||
out.extend_from_slice(value);
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn alpn_h3() -> (u16, Vec<u8>) {
|
||||
// alpn = ["h3"]: one length-prefixed ALPN id
|
||||
@@ -123,35 +129,35 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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 expected = build(1, &[], &[alpn_h3(), ipv4hint_single()]);
|
||||
let expected = build_rdata(1, &[], &[alpn_h3(), ipv4hint_single()]);
|
||||
assert_eq!(stripped, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 expected = build(1, &[], &[]);
|
||||
let expected = build_rdata(1, &[], &[]);
|
||||
assert_eq!(stripped, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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();
|
||||
assert!(stripped.starts_with(&[0x00, 0x01])); // priority
|
||||
assert_eq!(&stripped[2..6], b"\x03svc");
|
||||
|
||||
Reference in New Issue
Block a user