fix(resolver): skip ipv6hint strip for DO-bit clients
Modifying HTTPS rdata invalidates any accompanying RRSIG, so a DNSSEC- validating downstream would reject the response as Bogus. Gate the strip on !client_do, matching the existing DNSSEC-records strip. Adds a regression test that catches the gate being removed: builds a query with EDNS DO=1, asserts the HTTPS rdata round-trips untouched.
This commit is contained in:
71
src/ctx.rs
71
src/ctx.rs
@@ -347,8 +347,10 @@ pub async fn resolve_query(
|
|||||||
|
|
||||||
// filter_aaaa: also strip ipv6hint from HTTPS/SVCB answers so modern
|
// filter_aaaa: also strip ipv6hint from HTTPS/SVCB answers so modern
|
||||||
// browsers (Chrome ≥103 etc.) don't receive v6 address hints via the
|
// browsers (Chrome ≥103 etc.) don't receive v6 address hints via the
|
||||||
// HTTPS record path that bypasses AAAA entirely.
|
// HTTPS record path that bypasses AAAA entirely. Gated on !client_do
|
||||||
if ctx.filter_aaaa {
|
// because modifying rdata invalidates any accompanying RRSIG — a DO-bit
|
||||||
|
// validator downstream would reject the response as Bogus.
|
||||||
|
if ctx.filter_aaaa && !client_do {
|
||||||
strip_https_ipv6_hints(&mut response);
|
strip_https_ipv6_hints(&mut response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1342,6 +1344,71 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn pipeline_filter_aaaa_preserves_ipv6hint_for_dnssec_clients() {
|
||||||
|
// 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 mut pkt = DnsPacket::new();
|
||||||
|
pkt.header.response = true;
|
||||||
|
pkt.header.rescode = ResultCode::NOERROR;
|
||||||
|
pkt.questions.push(crate::question::DnsQuestion {
|
||||||
|
name: "hints.test".to_string(),
|
||||||
|
qtype: QueryType::HTTPS,
|
||||||
|
});
|
||||||
|
pkt.answers.push(DnsRecord::UNKNOWN {
|
||||||
|
domain: "hints.test".to_string(),
|
||||||
|
qtype: 65,
|
||||||
|
data: rdata.clone(),
|
||||||
|
ttl: 300,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut ctx = crate::testutil::test_ctx().await;
|
||||||
|
ctx.filter_aaaa = true;
|
||||||
|
ctx.cache
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.insert("hints.test", QueryType::HTTPS, &pkt);
|
||||||
|
let ctx = Arc::new(ctx);
|
||||||
|
|
||||||
|
// Build a query with EDNS DO bit set — can't use resolve_in_test
|
||||||
|
// because it constructs a plain query without EDNS.
|
||||||
|
let mut query = DnsPacket::query(0xBEEF, "hints.test", QueryType::HTTPS);
|
||||||
|
query.edns = Some(crate::packet::EdnsOpt {
|
||||||
|
do_bit: true,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let mut buf = BytePacketBuffer::new();
|
||||||
|
query.write(&mut buf).unwrap();
|
||||||
|
let raw = &buf.buf[..buf.pos];
|
||||||
|
let src: SocketAddr = "127.0.0.1:1234".parse().unwrap();
|
||||||
|
|
||||||
|
let (resp_buf, _) = resolve_query(query, raw, src, &ctx, Transport::Udp)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut resp_parse_buf = BytePacketBuffer::from_bytes(resp_buf.filled());
|
||||||
|
let resp = DnsPacket::from_buffer(&mut resp_parse_buf).unwrap();
|
||||||
|
|
||||||
|
match &resp.answers[0] {
|
||||||
|
DnsRecord::UNKNOWN { data, .. } => {
|
||||||
|
assert_eq!(
|
||||||
|
data, &rdata,
|
||||||
|
"ipv6hint must be preserved for DO-bit clients"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
other => panic!("expected UNKNOWN record, got {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn pipeline_blocklist_sinkhole() {
|
async fn pipeline_blocklist_sinkhole() {
|
||||||
let ctx = crate::testutil::test_ctx().await;
|
let ctx = crate::testutil::test_ctx().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user