diff --git a/src/api.rs b/src/api.rs
index c3dc324..04a81bf 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -410,14 +410,8 @@ async fn forward_query_for_diagnose(
timeout: std::time::Duration,
) -> (bool, String) {
use crate::packet::DnsPacket;
- use crate::question::DnsQuestion;
- let mut query = DnsPacket::new();
- query.header.id = 0xBEEF;
- query.header.recursion_desired = true;
- query
- .questions
- .push(DnsQuestion::new(domain.to_string(), QueryType::A));
+ let query = DnsPacket::query(0xBEEF, domain, QueryType::A);
match forward_query(&query, upstream, timeout).await {
Ok(resp) => (
diff --git a/src/ctx.rs b/src/ctx.rs
index fbddb15..b21e20b 100644
--- a/src/ctx.rs
+++ b/src/ctx.rs
@@ -93,18 +93,13 @@ pub async fn handle_query(
} else if qname == "localhost" || qname.ends_with(".localhost") {
// RFC 6761: .localhost always resolves to loopback
let mut resp = DnsPacket::response_from(&query, ResultCode::NOERROR);
- match qtype {
- QueryType::AAAA => resp.answers.push(DnsRecord::AAAA {
- domain: qname.clone(),
- addr: std::net::Ipv6Addr::LOCALHOST,
- ttl: 300,
- }),
- _ => resp.answers.push(DnsRecord::A {
- domain: qname.clone(),
- addr: std::net::Ipv4Addr::LOCALHOST,
- ttl: 300,
- }),
- }
+ resp.answers.push(sinkhole_record(
+ &qname,
+ qtype,
+ std::net::Ipv4Addr::LOCALHOST,
+ std::net::Ipv6Addr::LOCALHOST,
+ 300,
+ ));
(resp, QueryPath::Local, DnssecStatus::Indeterminate)
} else if is_special_use_domain(&qname) {
// RFC 6761/8880: private PTR, DDR, NAT64 — answer locally
@@ -130,38 +125,24 @@ pub async fn handle_query(
.unwrap_or(std::net::Ipv4Addr::LOCALHOST)
}
};
+ let v6 = if resolve_ip == std::net::Ipv4Addr::LOCALHOST {
+ std::net::Ipv6Addr::LOCALHOST
+ } else {
+ resolve_ip.to_ipv6_mapped()
+ };
let mut resp = DnsPacket::response_from(&query, ResultCode::NOERROR);
- match qtype {
- QueryType::AAAA => resp.answers.push(DnsRecord::AAAA {
- domain: qname.clone(),
- addr: if resolve_ip == std::net::Ipv4Addr::LOCALHOST {
- std::net::Ipv6Addr::LOCALHOST
- } else {
- resolve_ip.to_ipv6_mapped()
- },
- ttl: 300,
- }),
- _ => resp.answers.push(DnsRecord::A {
- domain: qname.clone(),
- addr: resolve_ip,
- ttl: 300,
- }),
- }
+ resp.answers
+ .push(sinkhole_record(&qname, qtype, resolve_ip, v6, 300));
(resp, QueryPath::Local, DnssecStatus::Indeterminate)
} else if ctx.blocklist.read().unwrap().is_blocked(&qname) {
let mut resp = DnsPacket::response_from(&query, ResultCode::NOERROR);
- match qtype {
- QueryType::AAAA => resp.answers.push(DnsRecord::AAAA {
- domain: qname.clone(),
- addr: std::net::Ipv6Addr::UNSPECIFIED,
- ttl: 60,
- }),
- _ => resp.answers.push(DnsRecord::A {
- domain: qname.clone(),
- addr: std::net::Ipv4Addr::UNSPECIFIED,
- ttl: 60,
- }),
- }
+ resp.answers.push(sinkhole_record(
+ &qname,
+ qtype,
+ std::net::Ipv4Addr::UNSPECIFIED,
+ std::net::Ipv6Addr::UNSPECIFIED,
+ 60,
+ ));
(resp, QueryPath::Blocked, DnssecStatus::Indeterminate)
} else if let Some(records) = ctx.zone_map.get(qname.as_str()).and_then(|m| m.get(&qtype)) {
let mut resp = DnsPacket::response_from(&query, ResultCode::NOERROR);
@@ -383,6 +364,27 @@ fn is_special_use_domain(qname: &str) -> bool {
qname == "local" || qname.ends_with(".local")
}
+fn sinkhole_record(
+ domain: &str,
+ qtype: QueryType,
+ v4: std::net::Ipv4Addr,
+ v6: std::net::Ipv6Addr,
+ ttl: u32,
+) -> DnsRecord {
+ match qtype {
+ QueryType::AAAA => DnsRecord::AAAA {
+ domain: domain.to_string(),
+ addr: v6,
+ ttl,
+ },
+ _ => DnsRecord::A {
+ domain: domain.to_string(),
+ addr: v4,
+ ttl,
+ },
+ }
+}
+
enum Disposition {
Leader(broadcast::Sender