From 3864de1951a244e55f42a4374a7deaad6061a723 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 7 Dec 2019 19:51:37 +0100 Subject: [PATCH] Add the ability to return synthetic response for undelegated TLDs --- example-encrypted-dns.toml | 8 ++++++++ src/config.rs | 1 + src/dns.rs | 23 ++++++++++++++++++++++- src/globals.rs | 1 + src/main.rs | 13 ++++++++++++- src/resolver.rs | 7 +++++++ undelegated.txt | 38 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 undelegated.txt diff --git a/example-encrypted-dns.toml b/example-encrypted-dns.toml index bd13903..f0edcae 100644 --- a/example-encrypted-dns.toml +++ b/example-encrypted-dns.toml @@ -170,6 +170,14 @@ key_cache_capacity = 10000 # domain_blacklist = "/etc/domain_blacklist.txt" +## List of undelegated TLDs +## This is the list of nonexistent TLDs that queries are frequently observed for, +## but will never resolve to anything. The server will immediately return a +## synthesized NXDOMAIN response instead of hitting root servers. + +# undelegated_list = "/etc/undelegated.txt" + + ######################### # Metrics # diff --git a/src/config.rs b/src/config.rs index 49c36d3..680fed0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,6 +48,7 @@ pub struct ListenAddrConfig { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct FilteringConfig { pub domain_blacklist: Option, + pub undelegated_list: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/src/dns.rs b/src/dns.rs index c3194ec..1765a4f 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -214,6 +214,10 @@ pub fn normalize_qname(packet: &mut [u8]) -> Result<(), Error> { Ok(()) } +pub fn qname_tld(qname: &[u8]) -> &[u8] { + qname.rsplit(|c| *c == b'.').next().unwrap_or_default() +} + pub fn recase_qname(packet: &mut [u8], qname: &[u8]) -> Result<(), Error> { debug_assert!(std::usize::MAX > 0xffff); ensure!(qdcount(packet) == 1, "Unexpected query count"); @@ -457,7 +461,7 @@ pub fn serve_certificates<'t>( Ok(Some(packet)) } -pub fn serve_truncated(client_packet: Vec) -> Result, Error> { +pub fn serve_truncated_response(client_packet: Vec) -> Result, Error> { ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); ensure!(qdcount(&client_packet) == 1, "No question"); ensure!( @@ -474,6 +478,23 @@ pub fn serve_truncated(client_packet: Vec) -> Result, Error> { Ok(packet) } +pub fn serve_nxdomain_response(client_packet: Vec) -> Result, Error> { + ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); + ensure!(qdcount(&client_packet) == 1, "No question"); + ensure!( + !is_response(&client_packet), + "Question expected, but got a response instead" + ); + let offset = skip_name(&client_packet, DNS_HEADER_SIZE)?; + let mut packet = client_packet; + ensure!(packet.len() - offset >= 4, "Short packet"); + packet.truncate(offset + 4); + an_ns_ar_count_clear(&mut packet); + authoritative_response(&mut packet); + set_rcode_nxdomain(&mut packet); + Ok(packet) +} + pub fn serve_blocked_response(client_packet: Vec) -> Result, Error> { ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); ensure!(qdcount(&client_packet) == 1, "No question"); diff --git a/src/globals.rs b/src/globals.rs index 16871fd..48ae930 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -41,6 +41,7 @@ pub struct Globals { pub cache: Cache, pub cert_cache: Cache, pub blacklist: Option, + pub undelegated_list: Option, pub anonymized_dns_enabled: bool, pub anonymized_dns_allowed_ports: Vec, pub anonymized_dns_allow_non_reserved_ports: bool, diff --git a/src/main.rs b/src/main.rs index ac89f6a..70b0652 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,7 +102,7 @@ fn maybe_truncate_response( if encrypted_response_min_len > original_packet_size || encrypted_response_min_len > DNSCRYPT_UDP_RESPONSE_MAX_SIZE { - return Ok(dns::serve_truncated(packet)?); + return Ok(dns::serve_truncated_response(packet)?); } } Ok(response) @@ -618,6 +618,16 @@ fn main() -> Result<(), Error> { .map_err(|e| anyhow!("Unable to load the blacklist [{:?}]: [{}]", path, e))?, ), }; + let undelegated_list = match config.filtering.undelegated_list { + None => None, + Some(path) => Some(BlackList::load(&path).map_err(|e| { + anyhow!( + "Unable to load the list of undelegated TLDs [{:?}]: [{}]", + path, + e + ) + })?), + }; let ( anonymized_dns_enabled, anonymized_dns_allowed_ports, @@ -662,6 +672,7 @@ fn main() -> Result<(), Error> { cache, cert_cache, blacklist, + undelegated_list, anonymized_dns_enabled, anonymized_dns_allowed_ports, anonymized_dns_allow_non_reserved_ports, diff --git a/src/resolver.rs b/src/resolver.rs index 1fec08c..d03ac16 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -170,6 +170,13 @@ pub async fn get_cached_response_or_resolve( return dns::serve_blocked_response(packet.to_vec()); } } + if let Some(undelegated_list) = &globals.undelegated_list { + if undelegated_list.find(dns::qname_tld(&packet_qname)) { + #[cfg(feature = "metrics")] + globals.varz.client_queries_rcode_nxdomain.inc(); + return dns::serve_nxdomain_response(packet.to_vec()); + } + } let original_tid = dns::tid(&packet); dns::set_tid(&mut packet, 0); dns::normalize_qname(&mut packet)?; diff --git a/undelegated.txt b/undelegated.txt new file mode 100644 index 0000000..3c38e9d --- /dev/null +++ b/undelegated.txt @@ -0,0 +1,38 @@ +1 +airdream +api +bbrouter +belkin +blinkap +corp +davolink +dearmyrouter +dhcp +dlink +domain +envoy +grp +gw== +home +hub +internal +intra +invalid +ksyun +loc +local +localdomain +localnet +modem +mynet +myrouter +novalocal +openstacklocal +priv +prv +router +telus +totolink +wlan_ap +workgroup +zghjccbob3n0