diff --git a/site/dashboard.html b/site/dashboard.html index 6b89b45..ca746c3 100644 --- a/site/dashboard.html +++ b/site/dashboard.html @@ -215,6 +215,7 @@ body { min-width: 2px; } .path-bar-fill.forward { background: var(--amber); } +.path-bar-fill.recursive { background: var(--cyan); } .path-bar-fill.cached { background: var(--teal); } .path-bar-fill.local { background: var(--violet); } .path-bar-fill.override { background: var(--emerald); } @@ -278,6 +279,7 @@ body { font-weight: 500; } .path-tag.FORWARD { background: rgba(192, 98, 58, 0.12); color: var(--amber-dim); } +.path-tag.RECURSIVE { background: rgba(74, 124, 138, 0.12); color: var(--cyan); } .path-tag.CACHED { background: rgba(107, 124, 78, 0.12); color: var(--teal-dim); } .path-tag.LOCAL { background: rgba(100, 116, 139, 0.12); color: var(--violet-dim); } .path-tag.OVERRIDE { background: rgba(82, 122, 82, 0.12); color: var(--emerald); } @@ -709,6 +711,7 @@ function formatRemaining(secs) { const PATH_DEFS = [ { key: 'forwarded', label: 'Forward', cls: 'forward' }, + { key: 'recursive', label: 'Recursive', cls: 'recursive' }, { key: 'cached', label: 'Cached', cls: 'cached' }, { key: 'local', label: 'Local', cls: 'local' }, { key: 'overridden', label: 'Override', cls: 'override' }, diff --git a/src/ctx.rs b/src/ctx.rs index 0ffaa20..644eb39 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -83,6 +83,26 @@ pub async fn handle_query( let mut resp = DnsPacket::response_from(&query, ResultCode::NOERROR); resp.answers.push(record); (resp, QueryPath::Overridden, DnssecStatus::Indeterminate) + } 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, QueryPath::Local, DnssecStatus::Indeterminate) + } else if is_special_use_domain(&qname) { + // RFC 6761/8880: private PTR, DDR, NAT64 — answer locally + let resp = special_use_response(&query, &qname, qtype); + (resp, QueryPath::Local, DnssecStatus::Indeterminate) } else if !ctx.proxy_tld_suffix.is_empty() && (qname.ends_with(&ctx.proxy_tld_suffix) || qname == ctx.proxy_tld) { @@ -315,3 +335,66 @@ fn strip_dnssec_records(pkt: &mut DnsPacket) { pkt.authorities.retain(|r| !is_dnssec_record(r)); pkt.resources.retain(|r| !is_dnssec_record(r)); } + +fn is_special_use_domain(qname: &str) -> bool { + if qname.ends_with(".in-addr.arpa") { + // RFC 6303: private + loopback + link-local reverse DNS + if qname.ends_with(".10.in-addr.arpa") + || qname.ends_with(".168.192.in-addr.arpa") + || qname.ends_with(".127.in-addr.arpa") + || qname.ends_with(".254.169.in-addr.arpa") + || qname.ends_with(".0.in-addr.arpa") + || qname.contains("_dns-sd._udp") + { + return true; + } + // 172.16-31.x.x (RFC 1918) — check each /16 reverse zone + for octet in 16..=31u8 { + let suffix = format!(".{}.172.in-addr.arpa", octet); + if qname.ends_with(&suffix) { + return true; + } + } + return false; + } + // DDR (RFC 9462) + if qname == "_dns.resolver.arpa" || qname.ends_with("._dns.resolver.arpa") { + return true; + } + // NAT64 (RFC 8880) + qname == "ipv4only.arpa" +} + +fn special_use_response(query: &DnsPacket, qname: &str, qtype: QueryType) -> DnsPacket { + use std::net::{Ipv4Addr, Ipv6Addr}; + if qname == "ipv4only.arpa" { + // RFC 8880: well-known NAT64 addresses + let mut resp = DnsPacket::response_from(query, ResultCode::NOERROR); + let domain = qname.to_string(); + match qtype { + QueryType::A => { + resp.answers.push(DnsRecord::A { + domain: domain.clone(), + addr: Ipv4Addr::new(192, 0, 0, 170), + ttl: 300, + }); + resp.answers.push(DnsRecord::A { + domain, + addr: Ipv4Addr::new(192, 0, 0, 171), + ttl: 300, + }); + } + QueryType::AAAA => { + resp.answers.push(DnsRecord::AAAA { + domain, + addr: Ipv6Addr::new(0x0064, 0xff9b, 0, 0, 0, 0, 0xc000, 0x00aa), + ttl: 300, + }); + } + _ => {} + } + resp + } else { + DnsPacket::response_from(query, ResultCode::NXDOMAIN) + } +}