From c22c3b1dbbe170369b83fe04a4fc8dc1bbb0f088 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2020 04:56:56 +0000 Subject: [PATCH 01/30] Update prometheus requirement from 0.7 to 0.8 Updates the requirements on [prometheus](https://github.com/pingcap/rust-prometheus) to permit the latest version. - [Release notes](https://github.com/pingcap/rust-prometheus/releases) - [Changelog](https://github.com/tikv/rust-prometheus/blob/master/CHANGELOG.md) - [Commits](https://github.com/pingcap/rust-prometheus/compare/v0.7.0...v0.8.0) Signed-off-by: dependabot-preview[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4f3534e..d237c3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ toml = "0.5" [dependencies.prometheus] optional = true -version = "0.7" +version = "0.8" default_features = false features = ["process"] From e3693e25f14c0c0e011627e51d36025d18a7a746 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 9 Mar 2020 23:03:16 +0100 Subject: [PATCH 02/30] Trigger a new build --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6589f8e..f5630c7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ All of these can be served simultaneously, on the same port (usually port 443). ### Option 1: precompiled binary for Linux Download the Encrypted DNS Server -[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/433868705/artifacts/1517465). +[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/433868705/artifacts/1517465) And make the application executable: From ac91ea4ca9fd9de502da05d36cb8b0a86a0c14d4 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 9 Mar 2020 23:26:55 +0100 Subject: [PATCH 03/30] Update binaries --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f5630c7..625c220 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ All of these can be served simultaneously, on the same port (usually port 443). ### Option 1: precompiled binary for Linux Download the Encrypted DNS Server -[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/433868705/artifacts/1517465) +[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/509803151/artifacts/2710804) And make the application executable: @@ -32,7 +32,7 @@ chmod +x encrypted-dns Nothing else has to be installed. It doesn't require any external dependencies. -A [Debian package](https://github.com/jedisct1/encrypted-dns-server/suites/433868705/artifacts/1517464) +A [Debian package](https://github.com/jedisct1/encrypted-dns-server/suites/509803151/artifacts/2710803) for Linux/x86_64 is also available. In this package, the example configuration file can be found in `/usr/share/doc/encrypted-dns/`. From 691129eec28ea220e2f3441fbbbbb728a58ec93c Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 11 Mar 2020 16:59:38 +0100 Subject: [PATCH 04/30] Use derivative 2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d237c3a..44f62a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ clap = { version="2.33", default-features = false, features=["wrap_help", "night clockpro-cache = "0.1.8" coarsetime = "0.1.12" daemonize-simple = "0.1.4" -derivative = "1.0.3" +derivative = "2.0.2" dnsstamps = "0.1.3" env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } futures = { version = "0.3", features = ["async-await"] } From d5b06a665368267618d1df1ba28aeb3295f315ce Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 20 Mar 2020 10:43:54 +0100 Subject: [PATCH 05/30] Implement access control --- README.md | 4 ++ example-encrypted-dns.toml | 21 ++++++++++ src/config.rs | 7 ++++ src/dns.rs | 85 +++++++++++++++++++++++++++++++++++--- src/globals.rs | 1 + src/main.rs | 14 +++++++ 6 files changed, 126 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 625c220..a3e2a58 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,10 @@ Putting it in a directory that is only readable by the super-user is not a bad i Domains can be filtered directly by the proxy, see the `[filtering]` section of the configuration file. +## Access control + +Access control can be enabled in the `[access_control]` section and configured with the `query_meta` configuration value of `dnscrypt-proxy`. + ## Prometheus metrics Prometheus metrics can optionally be enabled in order to monitor performance, cache efficiency, and more. diff --git a/example-encrypted-dns.toml b/example-encrypted-dns.toml index a8226e2..099ed54 100644 --- a/example-encrypted-dns.toml +++ b/example-encrypted-dns.toml @@ -221,3 +221,24 @@ allow_non_reserved_ports = false # Blacklisted upstream IP addresses blacklisted_ips = [ "93.184.216.34" ] + + + + +################################ +# Access control # +################################ + +[access_control] + +# Enabled access control + +enabled = false + +# Only allow access to client queries including one of these random tokens +# Tokens can be configured in the `query_meta` section of `dnscrypt-proxy` as +# `query_meta = ["token:..."]` -- Replace ... with the token to use by the client. +# Example: `query_meta = ["token:Y2oHkDJNHz"]` + +tokens = ["Y2oHkDJNHz", "G5zY3J5cHQtY", "C5zZWN1cmUuZG5z"] + diff --git a/src/config.rs b/src/config.rs index a59249a..8a1e3e6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,6 +9,12 @@ use std::net::{IpAddr, SocketAddr}; use std::path::{Path, PathBuf}; use tokio::prelude::*; +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AccessControlConfig { + pub enabled: bool, + pub tokens: Vec, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AnonymizedDNSConfig { pub enabled: bool, @@ -79,6 +85,7 @@ pub struct Config { #[cfg(feature = "metrics")] pub metrics: Option, pub anonymized_dns: Option, + pub access_control: Option, } impl Config { diff --git a/src/dns.rs b/src/dns.rs index 2b92cd4..3c41721 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -104,6 +104,12 @@ fn arcount_inc(packet: &mut [u8]) -> Result<(), Error> { Ok(()) } +#[inline] +fn arcount_clear(packet: &mut [u8]) -> Result<(), Error> { + BigEndian::write_u16(&mut packet[10..], 0); + Ok(()) +} + #[inline] pub fn an_ns_ar_count_clear(packet: &mut [u8]) { packet[6..12].iter_mut().for_each(|x| *x = 0); @@ -312,13 +318,13 @@ fn traverse_rrs Result<(), Error>>( for _ in 0..rrcount { offset = skip_name(packet, offset)?; ensure!(packet_len - offset >= 10, "Short packet"); - cb(offset)?; let rdlen = BigEndian::read_u16(&packet[offset + 8..]) as usize; - offset += 10; ensure!( - packet_len - offset >= rdlen, + packet_len - offset >= 10 + rdlen, "Record length would exceed packet length" ); + cb(offset)?; + offset += 10; offset += rdlen; } Ok(offset) @@ -334,13 +340,13 @@ fn traverse_rrs_mut Result<(), Error>>( for _ in 0..rrcount { offset = skip_name(packet, offset)?; ensure!(packet_len - offset >= 10, "Short packet"); - cb(packet, offset)?; let rdlen = BigEndian::read_u16(&packet[offset + 8..]) as usize; - offset += 10; ensure!( - packet_len - offset >= rdlen, + packet_len - offset >= 10 + rdlen, "Record length would exceed packet length" ); + cb(packet, offset)?; + offset += 10; offset += rdlen; } Ok(offset) @@ -502,6 +508,73 @@ pub fn qtype_qclass(packet: &[u8]) -> Result<(u16, u16), Error> { Ok((qtype, qclass)) } +fn parse_txt_rrdata Result<(), Error>>( + rrdata: &[u8], + mut cb: F, +) -> Result<(), Error> { + let rrdata_len = rrdata.len(); + let mut offset = 0; + while offset < rrdata_len { + let part_len = rrdata[offset] as usize; + if part_len == 0 { + break; + } + ensure!(rrdata_len - offset > part_len, "Short TXT RR data"); + offset += 1; + let part_bin = &rrdata[offset..offset + part_len]; + let part = std::str::from_utf8(part_bin)?; + cb(part)?; + offset += part_len; + } + Ok(()) +} + +pub fn query_meta(packet: &mut Vec) -> Result, Error> { + let packet_len = packet.len(); + ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); + ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); + ensure!(qdcount(packet) == 1, "No question"); + let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?; + assert!(offset > DNS_OFFSET_QUESTION); + ensure!(packet_len - offset >= 4, "Short packet"); + offset += 4; + let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet)); + offset = traverse_rrs( + packet, + offset, + ancount as usize + nscount as usize, + |_offset| Ok(()), + )?; + let mut token = None; + traverse_rrs(packet, offset, arcount as _, |mut offset| { + let qtype = BigEndian::read_u16(&packet[offset..]); + let qclass = BigEndian::read_u16(&packet[offset + 2..]); + if qtype != DNS_TYPE_TXT || qclass != DNS_CLASS_INET { + return Ok(()); + } + let len = BigEndian::read_u16(&packet[offset + 8..]) as usize; + offset += 10; + ensure!(packet_len - offset >= len, "Short packet"); + let rrdata = &packet[offset..offset + len]; + parse_txt_rrdata(rrdata, |txt| { + if txt.len() < 7 || !txt.starts_with("token:") { + return Ok(()); + } + ensure!(token.is_none(), "Duplicate token"); + let found_token = &txt[6..]; + let found_token = found_token.to_owned(); + token = Some(found_token); + Ok(()) + })?; + Ok(()) + })?; + if token.is_some() { + arcount_clear(packet)?; + packet.truncate(offset); + } + Ok(token) +} + 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"); diff --git a/src/globals.rs b/src/globals.rs index 21e36ce..beb25ce 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -48,6 +48,7 @@ pub struct Globals { pub anonymized_dns_allowed_ports: Vec, pub anonymized_dns_allow_non_reserved_ports: bool, pub anonymized_dns_blacklisted_ips: Vec, + pub access_control_tokens: Option>, #[cfg(feature = "metrics")] #[derivative(Debug = "ignore")] pub varz: Varz, diff --git a/src/main.rs b/src/main.rs index b9c2b8d..d341a7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -218,6 +218,12 @@ async fn handle_client_query( !dns::is_response(&packet), "Question expected, but got a response instead" ); + if let Some(tokens) = &globals.access_control_tokens { + match query_meta(&mut packet)? { + None => bail!("No access token"), + Some(token) => ensure!(tokens.contains(&token), "Access token not found"), + } + } let response = resolver::get_cached_response_or_resolve(&globals, &mut packet).await?; encrypt_and_respond_to_query( globals, @@ -652,6 +658,13 @@ fn main() -> Result<(), Error> { anonymized_dns.blacklisted_ips, ), }; + let access_control_tokens = match config.access_control { + None => None, + Some(access_control) => match access_control.enabled { + false => None, + true => Some(access_control.tokens), + }, + }; let runtime_handle = runtime.handle(); let globals = Arc::new(Globals { runtime_handle: runtime_handle.clone(), @@ -689,6 +702,7 @@ fn main() -> Result<(), Error> { anonymized_dns_allowed_ports, anonymized_dns_allow_non_reserved_ports, anonymized_dns_blacklisted_ips, + access_control_tokens, #[cfg(feature = "metrics")] varz: Varz::default(), }); From 5ebd3939815922adc00199f3ddfcb69f89a6d633 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 20 Mar 2020 10:54:21 +0100 Subject: [PATCH 06/30] Clippify --- src/config.rs | 11 +++-------- src/main.rs | 7 ++----- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8a1e3e6..4c952ed 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,8 +2,7 @@ use crate::crypto::*; use crate::dnscrypt_certs::*; use crate::errors::*; -use std::fs::File; -use std::io::prelude::*; +use std::fs; use std::mem; use std::net::{IpAddr, SocketAddr}; use std::path::{Path, PathBuf}; @@ -98,9 +97,7 @@ impl Config { } pub fn from_path>(path: P) -> Result { - let mut fd = File::open(path)?; - let mut toml = String::new(); - fd.read_to_string(&mut toml)?; + let toml = fs::read_to_string(path)?; Config::from_string(&toml) } } @@ -142,9 +139,7 @@ impl State { } pub fn from_file>(path: P, key_cache_capacity: usize) -> Result { - let mut fp = File::open(path.as_ref())?; - let mut state_bin = vec![]; - fp.read_to_end(&mut state_bin)?; + let state_bin = fs::read(path)?; let mut state: State = toml::from_slice(&state_bin)?; for params_set in &mut state.dnscrypt_encryption_params_set { params_set.add_key_cache(key_cache_capacity); diff --git a/src/main.rs b/src/main.rs index d341a7b..9526803 100644 --- a/src/main.rs +++ b/src/main.rs @@ -659,11 +659,8 @@ fn main() -> Result<(), Error> { ), }; let access_control_tokens = match config.access_control { - None => None, - Some(access_control) => match access_control.enabled { - false => None, - true => Some(access_control.tokens), - }, + Some(access_control) if access_control.enabled => Some(access_control.tokens), + _ => None, }; let runtime_handle = runtime.handle(); let globals = Arc::new(Globals { From b9361a8711d818d5881c96b7d059c57b4af2c4c1 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 20 Mar 2020 10:56:26 +0100 Subject: [PATCH 07/30] Fail open if the tokens list is empty --- src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 9526803..e62bc46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -659,7 +659,9 @@ fn main() -> Result<(), Error> { ), }; let access_control_tokens = match config.access_control { - Some(access_control) if access_control.enabled => Some(access_control.tokens), + Some(access_control) if access_control.enabled && !access_control.tokens.is_empty() => { + Some(access_control.tokens) + } _ => None, }; let runtime_handle = runtime.handle(); From 792f82fa35c00bf1b819cd6d68c8aaa3a97f3453 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 20 Mar 2020 11:09:39 +0100 Subject: [PATCH 08/30] Print something when access control is enabled --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index e62bc46..6d06248 100644 --- a/src/main.rs +++ b/src/main.rs @@ -660,6 +660,7 @@ fn main() -> Result<(), Error> { }; let access_control_tokens = match config.access_control { Some(access_control) if access_control.enabled && !access_control.tokens.is_empty() => { + info!("Access control enabled"); Some(access_control.tokens) } _ => None, From dd86579c37306436812299b30be32317848eaf3c Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 20 Mar 2020 11:11:44 +0100 Subject: [PATCH 09/30] Bump --- Cargo.toml | 4 ++-- example-encrypted-dns.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44f62a3..6171848 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "encrypted-dns" -version = "0.3.12" +version = "0.3.13" authors = ["Frank Denis "] edition = "2018" description = "A modern encrypted DNS server (DNSCrypt v2, Anonymized DNSCrypt, DoH)" @@ -36,7 +36,7 @@ serde = "1.0" serde_derive = "1.0" serde-big-array = "0.2" siphasher = "0.3" -tokio = { version = "0.2.11", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } +tokio = { version = "0.2.13", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5" [dependencies.prometheus] diff --git a/example-encrypted-dns.toml b/example-encrypted-dns.toml index 099ed54..c44df81 100644 --- a/example-encrypted-dns.toml +++ b/example-encrypted-dns.toml @@ -231,7 +231,7 @@ blacklisted_ips = [ "93.184.216.34" ] [access_control] -# Enabled access control +# Enable access control enabled = false From 4b20d77bf35f89131c6f9e6885c47b2221960464 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 20 Mar 2020 15:06:29 +0100 Subject: [PATCH 10/30] Update precompiled packages --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a3e2a58..5017503 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ All of these can be served simultaneously, on the same port (usually port 443). ### Option 1: precompiled binary for Linux Download the Encrypted DNS Server -[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/509803151/artifacts/2710804) +[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/534845774/artifacts/3155816) And make the application executable: @@ -32,7 +32,7 @@ chmod +x encrypted-dns Nothing else has to be installed. It doesn't require any external dependencies. -A [Debian package](https://github.com/jedisct1/encrypted-dns-server/suites/509803151/artifacts/2710803) +A [Debian package](https://github.com/jedisct1/encrypted-dns-server/suites/534845774/artifacts/3155815) for Linux/x86_64 is also available. In this package, the example configuration file can be found in `/usr/share/doc/encrypted-dns/`. From 1e88e19228b1037dbf18732aa25f318dc3b40b22 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 21 Mar 2020 18:02:03 +0100 Subject: [PATCH 11/30] Disable parking_lot for tokio --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6171848..b5e03f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ serde = "1.0" serde_derive = "1.0" serde-big-array = "0.2" siphasher = "0.3" -tokio = { version = "0.2.13", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } +tokio = { version = "0.2.13", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream"] } toml = "0.5" [dependencies.prometheus] From c93a03d824029125805c1e1239cd64cb10b626e4 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 21 Mar 2020 18:04:21 +0100 Subject: [PATCH 12/30] Bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b5e03f9..3db9db1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "encrypted-dns" -version = "0.3.13" +version = "0.3.14" authors = ["Frank Denis "] edition = "2018" description = "A modern encrypted DNS server (DNSCrypt v2, Anonymized DNSCrypt, DoH)" From aa392cd6530e78dfde87841362432ffe4cebcfbd Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sun, 22 Mar 2020 22:33:29 +0100 Subject: [PATCH 13/30] Revert "Disable parking_lot for tokio" This reverts commit 1e88e19228b1037dbf18732aa25f318dc3b40b22. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3db9db1..5015b87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ serde = "1.0" serde_derive = "1.0" serde-big-array = "0.2" siphasher = "0.3" -tokio = { version = "0.2.13", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream"] } +tokio = { version = "0.2.13", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5" [dependencies.prometheus] From 673d2c5c354983de29d9faa8d6e6390004513237 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 1 Apr 2020 23:50:42 +0200 Subject: [PATCH 14/30] Update deps to force a tokio update --- Cargo.toml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5015b87..780cda8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,36 +12,36 @@ categories = ["asynchronous", "network-programming","command-line-utilities"] readme = "README.md" [dependencies] -anyhow = "1.0" -byteorder = "1.3" -clap = { version="2.33", default-features = false, features=["wrap_help", "nightly"] } +anyhow = "1.0.28" +byteorder = "1.3.4" +clap = { version = "2.33.0", default-features = false, features = ["wrap_help", "nightly"] } clockpro-cache = "0.1.8" coarsetime = "0.1.12" daemonize-simple = "0.1.4" -derivative = "2.0.2" +derivative = "2.1.0" dnsstamps = "0.1.3" env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } -futures = { version = "0.3", features = ["async-await"] } -hyper = { version = "0.13", default_features = false, optional = true } -ipext = "0.1" -jemallocator = "0.3" -libsodium-sys-stable="1.19" +futures = { version = "0.3.4", features = ["async-await"] } +hyper = { version = "0.13.4", default_features = false, optional = true } +ipext = "0.1.0" +jemallocator = "0.3.2" +libsodium-sys-stable= "1.19.4" log = { version = "0.4.8", features = ["std", "release_max_level_debug"] } net2 = "0.2.33" -parking_lot = "0.10" +parking_lot = "0.10.0" privdrop = "0.3.4" -rand = "0.7" -rustc-hash = "1" -serde = "1.0" -serde_derive = "1.0" -serde-big-array = "0.2" -siphasher = "0.3" +rand = "0.7.3" +rustc-hash = "1.1.0" +serde = "1.0.105" +serde_derive = "1.0.105" +serde-big-array = "0.2.0" +siphasher = "0.3.2" tokio = { version = "0.2.13", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } -toml = "0.5" +toml = "0.5.6" [dependencies.prometheus] optional = true -version = "0.8" +version = "0.8.0" default_features = false features = ["process"] From 23c54cb96fc6bb54892a58ba565bff49244d59f8 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 1 Apr 2020 23:51:05 +0200 Subject: [PATCH 15/30] Bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 780cda8..7aa6a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "encrypted-dns" -version = "0.3.14" +version = "0.3.15" authors = ["Frank Denis "] edition = "2018" description = "A modern encrypted DNS server (DNSCrypt v2, Anonymized DNSCrypt, DoH)" From 864da4ec40cfa7f08655ae5c62659dd0df577550 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Thu, 2 Apr 2020 00:05:10 +0200 Subject: [PATCH 16/30] Update precompiled binaries --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5017503..28d9f0d 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ All of these can be served simultaneously, on the same port (usually port 443). ### Option 1: precompiled binary for Linux Download the Encrypted DNS Server -[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/534845774/artifacts/3155816) +[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/565691183/artifacts/3739886) And make the application executable: @@ -32,7 +32,7 @@ chmod +x encrypted-dns Nothing else has to be installed. It doesn't require any external dependencies. -A [Debian package](https://github.com/jedisct1/encrypted-dns-server/suites/534845774/artifacts/3155815) +A [Debian package](https://github.com/jedisct1/encrypted-dns-server/suites/565691183/artifacts/3739885) for Linux/x86_64 is also available. In this package, the example configuration file can be found in `/usr/share/doc/encrypted-dns/`. From a8c9611b0a975e236f0ada5923602431ee5c82d0 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Thu, 2 Apr 2020 20:34:26 +0200 Subject: [PATCH 17/30] Update tokio dep due to a regression in the previous version --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7aa6a98..1f0d085 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ clockpro-cache = "0.1.8" coarsetime = "0.1.12" daemonize-simple = "0.1.4" derivative = "2.1.0" -dnsstamps = "0.1.3" +dnsstamps = "0.1.4" env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } futures = { version = "0.3.4", features = ["async-await"] } hyper = { version = "0.13.4", default_features = false, optional = true } @@ -36,7 +36,7 @@ serde = "1.0.105" serde_derive = "1.0.105" serde-big-array = "0.2.0" siphasher = "0.3.2" -tokio = { version = "0.2.13", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } +tokio = { version = "0.2.15", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5.6" [dependencies.prometheus] From 53fc0fbfd979c059a100d3dc96371e3f579d0a5c Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 10 Apr 2020 10:07:00 +0200 Subject: [PATCH 18/30] Require tokio 0.2.17 This tokio version fixes a panic that could happen under load. --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f0d085..c8f89b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "encrypted-dns" -version = "0.3.15" +version = "0.3.16" authors = ["Frank Denis "] edition = "2018" description = "A modern encrypted DNS server (DNSCrypt v2, Anonymized DNSCrypt, DoH)" @@ -32,11 +32,11 @@ parking_lot = "0.10.0" privdrop = "0.3.4" rand = "0.7.3" rustc-hash = "1.1.0" -serde = "1.0.105" -serde_derive = "1.0.105" +serde = "1.0.106" +serde_derive = "1.0.106" serde-big-array = "0.2.0" siphasher = "0.3.2" -tokio = { version = "0.2.15", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } +tokio = { version = "0.2.17", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5.6" [dependencies.prometheus] From 9f6e54307d80d2c97aaf45844e26abd9d1037c57 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 20 Apr 2020 15:16:08 +0200 Subject: [PATCH 19/30] Update deps --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c8f89b4..88ee93c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,17 +18,17 @@ clap = { version = "2.33.0", default-features = false, features = ["wrap_help", clockpro-cache = "0.1.8" coarsetime = "0.1.12" daemonize-simple = "0.1.4" -derivative = "2.1.0" +derivative = "2.1.1" dnsstamps = "0.1.4" env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } futures = { version = "0.3.4", features = ["async-await"] } -hyper = { version = "0.13.4", default_features = false, optional = true } +hyper = { version = "0.13.5", default_features = false, optional = true } ipext = "0.1.0" jemallocator = "0.3.2" libsodium-sys-stable= "1.19.4" log = { version = "0.4.8", features = ["std", "release_max_level_debug"] } net2 = "0.2.33" -parking_lot = "0.10.0" +parking_lot = "0.10.2" privdrop = "0.3.4" rand = "0.7.3" rustc-hash = "1.1.0" @@ -36,7 +36,7 @@ serde = "1.0.106" serde_derive = "1.0.106" serde-big-array = "0.2.0" siphasher = "0.3.2" -tokio = { version = "0.2.17", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } +tokio = { version = "0.2.18", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5.6" [dependencies.prometheus] From 2a96c5f985f905d0d7d988fbccfeb1dc008e9f07 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 20 Apr 2020 15:44:42 +0200 Subject: [PATCH 20/30] dafuq --- src/dns.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 ++- src/resolver.rs | 15 ++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/dns.rs b/src/dns.rs index 3c41721..e767f9d 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -2,6 +2,7 @@ use crate::dnscrypt_certs::*; use crate::errors::*; use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::sync::Arc; pub const DNS_MAX_HOSTNAME_SIZE: usize = 256; @@ -620,3 +621,47 @@ pub fn serve_blocked_response(client_packet: Vec) -> Result, Error> packet.extend_from_slice(hinfo_rdata); Ok(packet) } + +pub fn serve_a_response(client_packet: Vec, ip: Ipv4Addr) -> 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); + ancount_inc(&mut packet)?; + packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; + packet.write_u16::(DNS_TYPE_A)?; + packet.write_u16::(DNS_CLASS_INET)?; + packet.write_u32::(60)?; + packet.extend_from_slice(&ip.octets()); + Ok(packet) +} + +pub fn serve_aaaa_response(client_packet: Vec, ip: Ipv6Addr) -> 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); + ancount_inc(&mut packet)?; + packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; + packet.write_u16::(DNS_TYPE_AAAA)?; + packet.write_u16::(DNS_CLASS_INET)?; + packet.write_u32::(60)?; + packet.extend_from_slice(&ip.octets()); + Ok(packet) +} diff --git a/src/main.rs b/src/main.rs index 6d06248..9050b90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -224,7 +224,8 @@ async fn handle_client_query( Some(token) => ensure!(tokens.contains(&token), "Access token not found"), } } - let response = resolver::get_cached_response_or_resolve(&globals, &mut packet).await?; + let response = + resolver::get_cached_response_or_resolve(&globals, &client_ctx, &mut packet).await?; encrypt_and_respond_to_query( globals, client_ctx, diff --git a/src/resolver.rs b/src/resolver.rs index f0758a9..1f447aa 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -2,12 +2,13 @@ use crate::cache::*; use crate::dns::{self, *}; use crate::errors::*; use crate::globals::*; +use crate::ClientCtx; use byteorder::{BigEndian, ByteOrder}; use rand::prelude::*; use siphasher::sip128::Hasher128; use std::hash::Hasher; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use tokio::net::{TcpStream, UdpSocket}; use tokio::prelude::*; @@ -164,9 +165,21 @@ pub async fn resolve( pub async fn get_cached_response_or_resolve( globals: &Globals, + client_ctx: &ClientCtx, mut packet: &mut Vec, ) -> Result, Error> { let packet_qname = dns::qname(&packet)?; + if &packet_qname == b"my.ip" { + let client_ip = match client_ctx { + ClientCtx::Udp(u) => u.client_addr, + ClientCtx::Tcp(t) => t.client_connection.peer_addr()?, + } + .ip(); + return match client_ip { + IpAddr::V4(ip) => serve_a_response(packet.to_vec(), ip), + IpAddr::V6(ip) => serve_aaaa_response(packet.to_vec(), ip), + }; + } if let Some(blacklist) = &globals.blacklist { if blacklist.find(&packet_qname) { #[cfg(feature = "metrics")] From 75166216b93b4d3ee47337155b0ec05b809b0756 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 20 Apr 2020 16:24:18 +0200 Subject: [PATCH 21/30] Add my_ip feature --- example-encrypted-dns.toml | 5 +++++ src/blacklist.rs | 4 ++-- src/config.rs | 1 + src/dns.rs | 46 +++++++++++++++----------------------- src/globals.rs | 1 + src/main.rs | 1 + src/resolver.rs | 19 ++++++++-------- 7 files changed, 37 insertions(+), 40 deletions(-) diff --git a/example-encrypted-dns.toml b/example-encrypted-dns.toml index c44df81..6fb46fa 100644 --- a/example-encrypted-dns.toml +++ b/example-encrypted-dns.toml @@ -114,6 +114,11 @@ daemonize = false # chroot = "/var/empty" +## Queries sent to that name will return the client IP address. +## This can be very useful for debugging, or to check that relaying works. + +my_ip = "my.ip" + #################################### # DNSCrypt settings # diff --git a/src/blacklist.rs b/src/blacklist.rs index 5b9d99b..0c163c0 100644 --- a/src/blacklist.rs +++ b/src/blacklist.rs @@ -43,7 +43,7 @@ impl BlackList { while line.ends_with('.') { line = &line[..line.len() - 1]; } - let qname = line.as_bytes().to_vec().to_ascii_lowercase(); + let qname = line.as_bytes().to_ascii_lowercase(); if qname.is_empty() { bail!("Unexpected blacklist rule at line {}", line_nb) } @@ -53,7 +53,7 @@ impl BlackList { } pub fn find(&self, qname: &[u8]) -> bool { - let qname = qname.to_vec().to_ascii_lowercase(); + let qname = qname.to_ascii_lowercase(); let mut qname = qname.as_slice(); let map = &self.inner.map; let mut iterations = self.max_iterations; diff --git a/src/config.rs b/src/config.rs index 4c952ed..ac23033 100644 --- a/src/config.rs +++ b/src/config.rs @@ -81,6 +81,7 @@ pub struct Config { pub daemonize: bool, pub pid_file: Option, pub log_file: Option, + pub my_ip: Option, #[cfg(feature = "metrics")] pub metrics: Option, pub anonymized_dns: Option, diff --git a/src/dns.rs b/src/dns.rs index e767f9d..448cf02 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -2,7 +2,7 @@ use crate::dnscrypt_certs::*; use crate::errors::*; use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; -use std::net::{Ipv4Addr, Ipv6Addr}; +use std::net::IpAddr; use std::sync::Arc; pub const DNS_MAX_HOSTNAME_SIZE: usize = 256; @@ -622,7 +622,7 @@ pub fn serve_blocked_response(client_packet: Vec) -> Result, Error> Ok(packet) } -pub fn serve_a_response(client_packet: Vec, ip: Ipv4Addr) -> Result, Error> { +pub fn serve_ip_response(client_packet: Vec, ip: IpAddr, ttl: u32) -> Result, Error> { ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); ensure!(qdcount(&client_packet) == 1, "No question"); ensure!( @@ -637,31 +637,21 @@ pub fn serve_a_response(client_packet: Vec, ip: Ipv4Addr) -> Result, authoritative_response(&mut packet); ancount_inc(&mut packet)?; packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; - packet.write_u16::(DNS_TYPE_A)?; - packet.write_u16::(DNS_CLASS_INET)?; - packet.write_u32::(60)?; - packet.extend_from_slice(&ip.octets()); - Ok(packet) -} - -pub fn serve_aaaa_response(client_packet: Vec, ip: Ipv6Addr) -> 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); - ancount_inc(&mut packet)?; - packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; - packet.write_u16::(DNS_TYPE_AAAA)?; - packet.write_u16::(DNS_CLASS_INET)?; - packet.write_u32::(60)?; - packet.extend_from_slice(&ip.octets()); + match ip { + IpAddr::V4(ip) => { + packet.write_u16::(DNS_TYPE_A)?; + packet.write_u16::(DNS_CLASS_INET)?; + packet.write_u32::(ttl)?; + packet.write_u16::(4)?; + packet.extend_from_slice(&ip.octets()); + } + IpAddr::V6(ip) => { + packet.write_u16::(DNS_TYPE_AAAA)?; + packet.write_u16::(DNS_CLASS_INET)?; + packet.write_u32::(ttl)?; + packet.write_u16::(16)?; + packet.extend_from_slice(&ip.octets()); + } + }; Ok(packet) } diff --git a/src/globals.rs b/src/globals.rs index beb25ce..14ebf52 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -49,6 +49,7 @@ pub struct Globals { pub anonymized_dns_allow_non_reserved_ports: bool, pub anonymized_dns_blacklisted_ips: Vec, pub access_control_tokens: Option>, + pub my_ip: Option>, #[cfg(feature = "metrics")] #[derivative(Debug = "ignore")] pub varz: Varz, diff --git a/src/main.rs b/src/main.rs index 9050b90..980e396 100644 --- a/src/main.rs +++ b/src/main.rs @@ -704,6 +704,7 @@ fn main() -> Result<(), Error> { anonymized_dns_allow_non_reserved_ports, anonymized_dns_blacklisted_ips, access_control_tokens, + my_ip: config.my_ip.map(|ip| ip.as_bytes().to_ascii_lowercase()), #[cfg(feature = "metrics")] varz: Varz::default(), }); diff --git a/src/resolver.rs b/src/resolver.rs index 1f447aa..b3c9157 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -8,7 +8,7 @@ use byteorder::{BigEndian, ByteOrder}; use rand::prelude::*; use siphasher::sip128::Hasher128; use std::hash::Hasher; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use tokio::net::{TcpStream, UdpSocket}; use tokio::prelude::*; @@ -169,16 +169,15 @@ pub async fn get_cached_response_or_resolve( mut packet: &mut Vec, ) -> Result, Error> { let packet_qname = dns::qname(&packet)?; - if &packet_qname == b"my.ip" { - let client_ip = match client_ctx { - ClientCtx::Udp(u) => u.client_addr, - ClientCtx::Tcp(t) => t.client_connection.peer_addr()?, + if let Some(my_ip) = &globals.my_ip { + if &packet_qname.to_ascii_lowercase() == my_ip { + let client_ip = match client_ctx { + ClientCtx::Udp(u) => u.client_addr, + ClientCtx::Tcp(t) => t.client_connection.peer_addr()?, + } + .ip(); + return serve_ip_response(packet.to_vec(), client_ip, 1); } - .ip(); - return match client_ip { - IpAddr::V4(ip) => serve_a_response(packet.to_vec(), ip), - IpAddr::V6(ip) => serve_aaaa_response(packet.to_vec(), ip), - }; } if let Some(blacklist) = &globals.blacklist { if blacklist.find(&packet_qname) { From 83a2957b0a99e744ae908b565f7feddb75df7d3f Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 20 Apr 2020 16:24:36 +0200 Subject: [PATCH 22/30] Bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 88ee93c..a52bb69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "encrypted-dns" -version = "0.3.16" +version = "0.3.17" authors = ["Frank Denis "] edition = "2018" description = "A modern encrypted DNS server (DNSCrypt v2, Anonymized DNSCrypt, DoH)" From 4a7f6abe07281b90edd21fd4c6f88eb11024483d Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 20 Apr 2020 18:41:59 +0200 Subject: [PATCH 23/30] Update precompiled binaries --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28d9f0d..3055802 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ All of these can be served simultaneously, on the same port (usually port 443). ### Option 1: precompiled binary for Linux Download the Encrypted DNS Server -[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/565691183/artifacts/3739886) +[precompiled application for Linux (x86_64)](https://github.com/jedisct1/encrypted-dns-server/suites/612418335/artifacts/4698672) And make the application executable: @@ -32,7 +32,7 @@ chmod +x encrypted-dns Nothing else has to be installed. It doesn't require any external dependencies. -A [Debian package](https://github.com/jedisct1/encrypted-dns-server/suites/565691183/artifacts/3739885) +A [Debian package](https://github.com/jedisct1/encrypted-dns-server/suites/612418335/artifacts/4698671) for Linux/x86_64 is also available. In this package, the example configuration file can be found in `/usr/share/doc/encrypted-dns/`. From dd1b550ef9c98787c063df7946dc973e019d4c1c Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 24 Apr 2020 22:56:29 +0200 Subject: [PATCH 24/30] Add decreasing TTLs with jitter when a TTL becomes low Fixes #33 --- example-encrypted-dns.toml | 7 +++++++ src/cache.rs | 10 +++++++--- src/config.rs | 1 + src/dns.rs | 22 ++++++++++++++++++++++ src/globals.rs | 1 + src/main.rs | 1 + src/resolver.rs | 6 ++++++ 7 files changed, 45 insertions(+), 3 deletions(-) diff --git a/example-encrypted-dns.toml b/example-encrypted-dns.toml index 6fb46fa..4ef1c3d 100644 --- a/example-encrypted-dns.toml +++ b/example-encrypted-dns.toml @@ -83,6 +83,13 @@ cache_ttl_max = 86400 cache_ttl_error = 600 +## DNS cache: to avoid bursts of traffic when an RRSET expires, +## introduce jitter to the decreasing TTL sent to clients when the +## actual TTL goes below that value. + +client_ttl_jitter = 60 + + ## Run as a background process daemonize = false diff --git a/src/cache.rs b/src/cache.rs index 62ec5a6..9f10b46 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -30,6 +30,10 @@ impl CachedResponse { pub fn has_expired(&self) -> bool { Instant::recent() > self.expiry } + + pub fn ttl(&self) -> u32 { + (self.expiry - Instant::recent()).as_secs() as _ + } } #[derive(Clone, Derivative)] @@ -37,9 +41,9 @@ impl CachedResponse { pub struct Cache { #[derivative(Debug = "ignore")] cache: Arc>>, - ttl_min: u32, - ttl_max: u32, - ttl_error: u32, + pub ttl_min: u32, + pub ttl_max: u32, + pub ttl_error: u32, } impl Cache { diff --git a/src/config.rs b/src/config.rs index ac23033..b88acb8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -82,6 +82,7 @@ pub struct Config { pub pid_file: Option, pub log_file: Option, pub my_ip: Option, + pub client_ttl_jitter: Option, #[cfg(feature = "metrics")] pub metrics: Option, pub anonymized_dns: Option, diff --git a/src/dns.rs b/src/dns.rs index 448cf02..719817b 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -381,6 +381,28 @@ pub fn min_ttl(packet: &[u8], min_ttl: u32, max_ttl: u32, failure_ttl: u32) -> R Ok(found_min_ttl) } +pub fn set_ttl(packet: &mut [u8], ttl: u32) -> Result<(), Error> { + let packet_len = packet.len(); + ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); + ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); + ensure!(qdcount(packet) == 1, "No question"); + let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?; + assert!(offset > DNS_OFFSET_QUESTION); + ensure!(packet_len - offset > 4, "Short packet"); + offset += 4; + let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet)); + let rrcount = ancount as usize + nscount as usize + arcount as usize; + offset = traverse_rrs_mut(packet, offset, rrcount, |packet, offset| { + let qtype = BigEndian::read_u16(&packet[offset..]); + if qtype != DNS_TYPE_OPT { + BigEndian::write_u32(&mut packet[offset + 4..], ttl) + } + Ok(()) + })?; + ensure!(packet_len == offset, "Garbage after packet"); + Ok(()) +} + fn add_edns_section(packet: &mut Vec, max_payload_size: u16) -> Result<(), Error> { let opt_rr: [u8; 11] = [ 0, diff --git a/src/globals.rs b/src/globals.rs index 14ebf52..e8a3fb0 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -49,6 +49,7 @@ pub struct Globals { pub anonymized_dns_allow_non_reserved_ports: bool, pub anonymized_dns_blacklisted_ips: Vec, pub access_control_tokens: Option>, + pub client_ttl_jitter: u32, pub my_ip: Option>, #[cfg(feature = "metrics")] #[derivative(Debug = "ignore")] diff --git a/src/main.rs b/src/main.rs index 980e396..5322d2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -705,6 +705,7 @@ fn main() -> Result<(), Error> { anonymized_dns_blacklisted_ips, access_control_tokens, my_ip: config.my_ip.map(|ip| ip.as_bytes().to_ascii_lowercase()), + client_ttl_jitter: config.client_ttl_jitter.unwrap_or(60), #[cfg(feature = "metrics")] varz: Varz::default(), }); diff --git a/src/resolver.rs b/src/resolver.rs index b3c9157..b851cd3 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -226,7 +226,13 @@ pub async fn get_cached_response_or_resolve( #[cfg(feature = "metrics")] globals.varz.client_queries_cached.inc(); cached_response.set_tid(original_tid); + let mut ttl = cached_response.ttl(); + if ttl < globals.client_ttl_jitter { + let jitter = rand::thread_rng().gen::() % globals.client_ttl_jitter; + ttl = globals.client_ttl_jitter.saturating_add(jitter); + } let mut response = cached_response.into_response(); + dns::set_ttl(&mut response, ttl)?; dns::recase_qname(&mut response, &packet_qname)?; return Ok(response); } From 6dde6e20d42a6612b62d2eef646b31a3f0fda819 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 24 Apr 2020 22:56:59 +0200 Subject: [PATCH 25/30] Update deps --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a52bb69..5b06f85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ futures = { version = "0.3.4", features = ["async-await"] } hyper = { version = "0.13.5", default_features = false, optional = true } ipext = "0.1.0" jemallocator = "0.3.2" -libsodium-sys-stable= "1.19.4" +libsodium-sys-stable= "1.19.5" log = { version = "0.4.8", features = ["std", "release_max_level_debug"] } net2 = "0.2.33" parking_lot = "0.10.2" @@ -35,7 +35,7 @@ rustc-hash = "1.1.0" serde = "1.0.106" serde_derive = "1.0.106" serde-big-array = "0.2.0" -siphasher = "0.3.2" +siphasher = "0.3.3" tokio = { version = "0.2.18", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5.6" From 38f46220729a0da97461aecb11ec762ba3d60a42 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 4 May 2020 04:48:53 +0000 Subject: [PATCH 26/30] Update serde-big-array requirement from 0.2.0 to 0.3.0 Updates the requirements on [serde-big-array](https://github.com/est31/serde-big-array) to permit the latest version. - [Release notes](https://github.com/est31/serde-big-array/releases) - [Commits](https://github.com/est31/serde-big-array/compare/v0.2.0...v0.3.0) Signed-off-by: dependabot-preview[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5b06f85..c2df533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ rand = "0.7.3" rustc-hash = "1.1.0" serde = "1.0.106" serde_derive = "1.0.106" -serde-big-array = "0.2.0" +serde-big-array = "0.3.0" siphasher = "0.3.3" tokio = { version = "0.2.18", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5.6" From 04fdf73046eebe46f5da164955c62f2eecfd147c Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 4 May 2020 08:54:08 +0200 Subject: [PATCH 27/30] Use specific lengths for big arrays --- Cargo.toml | 6 +++--- src/crypto.rs | 2 +- src/dnscrypt_certs.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2df533..fb9558a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ anyhow = "1.0.28" byteorder = "1.3.4" clap = { version = "2.33.0", default-features = false, features = ["wrap_help", "nightly"] } clockpro-cache = "0.1.8" -coarsetime = "0.1.12" +coarsetime = "0.1.13" daemonize-simple = "0.1.4" derivative = "2.1.1" dnsstamps = "0.1.4" @@ -27,7 +27,7 @@ ipext = "0.1.0" jemallocator = "0.3.2" libsodium-sys-stable= "1.19.5" log = { version = "0.4.8", features = ["std", "release_max_level_debug"] } -net2 = "0.2.33" +net2 = "0.2.34" parking_lot = "0.10.2" privdrop = "0.3.4" rand = "0.7.3" @@ -36,7 +36,7 @@ serde = "1.0.106" serde_derive = "1.0.106" serde-big-array = "0.3.0" siphasher = "0.3.3" -tokio = { version = "0.2.18", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } +tokio = { version = "0.2.20", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5.6" [dependencies.prometheus] diff --git a/src/crypto.rs b/src/crypto.rs index 2a347cd..cbaad55 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -23,7 +23,7 @@ impl Signature { } } -big_array! { BigArray; } +big_array! { BigArray; crypto_sign_SECRETKEYBYTES as usize } #[derive(Serialize, Deserialize, Derivative, Clone)] #[derivative(Default)] diff --git a/src/dnscrypt_certs.rs b/src/dnscrypt_certs.rs index a98f8b0..0b0aead 100644 --- a/src/dnscrypt_certs.rs +++ b/src/dnscrypt_certs.rs @@ -38,7 +38,7 @@ impl DNSCryptCertInner { } } -big_array! { BigArray; } +big_array! { BigArray; 64 } #[derive(Derivative, Serialize, Deserialize)] #[derivative(Debug, Default, Clone)] From 561ebd07f4666c4814c38a4d04b610079c9c31f4 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Tue, 5 May 2020 17:27:28 +0200 Subject: [PATCH 28/30] client_ttl_jitter -> client_ttl_holdon --- example-encrypted-dns.toml | 8 ++++---- src/cache.rs | 16 +++++++++++++++- src/config.rs | 2 +- src/globals.rs | 2 +- src/main.rs | 2 +- src/resolver.rs | 8 +++++--- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/example-encrypted-dns.toml b/example-encrypted-dns.toml index 4ef1c3d..d992640 100644 --- a/example-encrypted-dns.toml +++ b/example-encrypted-dns.toml @@ -83,11 +83,11 @@ cache_ttl_max = 86400 cache_ttl_error = 600 -## DNS cache: to avoid bursts of traffic when an RRSET expires, -## introduce jitter to the decreasing TTL sent to clients when the -## actual TTL goes below that value. +## DNS cache: to avoid bursts of traffic for popular queries when an +## RRSET expires, hold a TTL received from an upstream server for +## `client_ttl_holdon` seconds before decreasing it in client responses. -client_ttl_jitter = 60 +client_ttl_holdon = 60 ## Run as a background process diff --git a/src/cache.rs b/src/cache.rs index 9f10b46..d9da4b7 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -9,6 +9,7 @@ use std::sync::Arc; pub struct CachedResponse { response: Vec, expiry: Instant, + original_ttl: u32, } impl CachedResponse { @@ -16,24 +17,37 @@ impl CachedResponse { let ttl = dns::min_ttl(&response, cache.ttl_min, cache.ttl_max, cache.ttl_error) .unwrap_or(cache.ttl_error); let expiry = Instant::recent() + Duration::from_secs(u64::from(ttl)); - CachedResponse { response, expiry } + CachedResponse { + response, + expiry, + original_ttl: ttl, + } } + #[inline] pub fn set_tid(&mut self, tid: u16) { dns::set_tid(&mut self.response, tid) } + #[inline] pub fn into_response(self) -> Vec { self.response } + #[inline] pub fn has_expired(&self) -> bool { Instant::recent() > self.expiry } + #[inline] pub fn ttl(&self) -> u32 { (self.expiry - Instant::recent()).as_secs() as _ } + + #[inline] + pub fn original_ttl(&self) -> u32 { + self.original_ttl + } } #[derive(Clone, Derivative)] diff --git a/src/config.rs b/src/config.rs index b88acb8..192949a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -82,7 +82,7 @@ pub struct Config { pub pid_file: Option, pub log_file: Option, pub my_ip: Option, - pub client_ttl_jitter: Option, + pub client_ttl_holdon: Option, #[cfg(feature = "metrics")] pub metrics: Option, pub anonymized_dns: Option, diff --git a/src/globals.rs b/src/globals.rs index e8a3fb0..c855fdc 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -49,7 +49,7 @@ pub struct Globals { pub anonymized_dns_allow_non_reserved_ports: bool, pub anonymized_dns_blacklisted_ips: Vec, pub access_control_tokens: Option>, - pub client_ttl_jitter: u32, + pub client_ttl_holdon: u32, pub my_ip: Option>, #[cfg(feature = "metrics")] #[derivative(Debug = "ignore")] diff --git a/src/main.rs b/src/main.rs index 5322d2c..fbcfb61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -705,7 +705,7 @@ fn main() -> Result<(), Error> { anonymized_dns_blacklisted_ips, access_control_tokens, my_ip: config.my_ip.map(|ip| ip.as_bytes().to_ascii_lowercase()), - client_ttl_jitter: config.client_ttl_jitter.unwrap_or(60), + client_ttl_holdon: config.client_ttl_holdon.unwrap_or(60), #[cfg(feature = "metrics")] varz: Varz::default(), }); diff --git a/src/resolver.rs b/src/resolver.rs index b851cd3..83374d9 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -7,6 +7,7 @@ use crate::ClientCtx; use byteorder::{BigEndian, ByteOrder}; use rand::prelude::*; use siphasher::sip128::Hasher128; +use std::cmp; use std::hash::Hasher; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use tokio::net::{TcpStream, UdpSocket}; @@ -226,11 +227,12 @@ pub async fn get_cached_response_or_resolve( #[cfg(feature = "metrics")] globals.varz.client_queries_cached.inc(); cached_response.set_tid(original_tid); + let original_ttl = cached_response.original_ttl(); let mut ttl = cached_response.ttl(); - if ttl < globals.client_ttl_jitter { - let jitter = rand::thread_rng().gen::() % globals.client_ttl_jitter; - ttl = globals.client_ttl_jitter.saturating_add(jitter); + if ttl.saturating_add(globals.client_ttl_holdon) > original_ttl { + ttl = original_ttl; } + ttl = cmp::max(1, ttl); let mut response = cached_response.into_response(); dns::set_ttl(&mut response, ttl)?; dns::recase_qname(&mut response, &packet_qname)?; From 56ca4c58479aff17cc197e8d1b7abcbedb75d4f7 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Tue, 5 May 2020 23:32:37 +0200 Subject: [PATCH 29/30] Remove nightly feature from clap --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fb9558a..e7d6e21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" [dependencies] anyhow = "1.0.28" byteorder = "1.3.4" -clap = { version = "2.33.0", default-features = false, features = ["wrap_help", "nightly"] } +clap = { version = "2.33.0", default-features = false, features = ["wrap_help"] } clockpro-cache = "0.1.8" coarsetime = "0.1.13" daemonize-simple = "0.1.4" From 7bdfaba63c8af7fc4b792ee01ac736b7e118263c Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 18 May 2020 16:23:30 +0200 Subject: [PATCH 30/30] Update Prometheus and friends --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e7d6e21..cda76b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,16 +12,16 @@ categories = ["asynchronous", "network-programming","command-line-utilities"] readme = "README.md" [dependencies] -anyhow = "1.0.28" +anyhow = "1.0.31" byteorder = "1.3.4" -clap = { version = "2.33.0", default-features = false, features = ["wrap_help"] } +clap = { version = "2.33.1", default-features = false, features = ["wrap_help"] } clockpro-cache = "0.1.8" coarsetime = "0.1.13" daemonize-simple = "0.1.4" derivative = "2.1.1" dnsstamps = "0.1.4" env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } -futures = { version = "0.3.4", features = ["async-await"] } +futures = { version = "0.3.5", features = ["async-await"] } hyper = { version = "0.13.5", default_features = false, optional = true } ipext = "0.1.0" jemallocator = "0.3.2" @@ -32,16 +32,16 @@ parking_lot = "0.10.2" privdrop = "0.3.4" rand = "0.7.3" rustc-hash = "1.1.0" -serde = "1.0.106" -serde_derive = "1.0.106" +serde = "1.0.110" +serde_derive = "1.0.110" serde-big-array = "0.3.0" siphasher = "0.3.3" -tokio = { version = "0.2.20", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } +tokio = { version = "0.2.21", features = ["fs", "rt-threaded", "time", "tcp", "udp", "stream", "parking_lot"] } toml = "0.5.6" [dependencies.prometheus] optional = true -version = "0.8.0" +version = "0.9.0" default_features = false features = ["process"]