Merge branch 'master' of github.com:jedisct1/rust-dnscrypt-server

* 'master' of github.com:jedisct1/rust-dnscrypt-server: (30 commits)
  Update Prometheus and friends
  Remove nightly feature from clap
  client_ttl_jitter -> client_ttl_holdon
  Use specific lengths for big arrays
  Update serde-big-array requirement from 0.2.0 to 0.3.0
  Update deps
  Add decreasing TTLs with jitter when a TTL becomes low
  Update precompiled binaries
  Bump
  Add my_ip feature
  dafuq
  Update deps
  Require tokio 0.2.17
  Update tokio dep due to a regression in the previous version
  Update precompiled binaries
  Bump
  Update deps to force a tokio update
  Revert "Disable parking_lot for tokio"
  Bump
  Disable parking_lot for tokio
  ...
pull/38/head
Frank Denis 4 years ago
commit 6a19db5edf

@ -1,6 +1,6 @@
[package]
name = "encrypted-dns"
version = "0.3.12"
version = "0.3.17"
authors = ["Frank Denis <github@pureftpd.org>"]
edition = "2018"
description = "A modern encrypted DNS server (DNSCrypt v2, Anonymized DNSCrypt, DoH)"
@ -12,20 +12,20 @@ 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.31"
byteorder = "1.3.4"
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", 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.5", 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.5"
log = { version = "0.4.8", features = ["std", "release_max_level_debug"] }
socket2 = "0.3"
parking_lot = "0.10"

@ -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/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/433868705/artifacts/1517464)
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/`.
@ -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.

@ -83,6 +83,13 @@ cache_ttl_max = 86400
cache_ttl_error = 600
## 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_holdon = 60
## Run as a background process
daemonize = false
@ -114,6 +121,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 #
@ -221,3 +233,24 @@ allow_non_reserved_ports = false
# Blacklisted upstream IP addresses
blacklisted_ips = [ "93.184.216.34" ]
################################
# Access control #
################################
[access_control]
# Enable 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"]

@ -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;

@ -9,6 +9,7 @@ use std::sync::Arc;
pub struct CachedResponse {
response: Vec<u8>,
expiry: Instant,
original_ttl: u32,
}
impl CachedResponse {
@ -16,20 +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<u8> {
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)]
@ -37,9 +55,9 @@ impl CachedResponse {
pub struct Cache {
#[derivative(Debug = "ignore")]
cache: Arc<Mutex<ClockProCache<u128, CachedResponse>>>,
ttl_min: u32,
ttl_max: u32,
ttl_error: u32,
pub ttl_min: u32,
pub ttl_max: u32,
pub ttl_error: u32,
}
impl Cache {

@ -2,13 +2,18 @@ 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};
use tokio::prelude::*;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AccessControlConfig {
pub enabled: bool,
pub tokens: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AnonymizedDNSConfig {
pub enabled: bool,
@ -76,9 +81,12 @@ pub struct Config {
pub daemonize: bool,
pub pid_file: Option<PathBuf>,
pub log_file: Option<PathBuf>,
pub my_ip: Option<String>,
pub client_ttl_holdon: Option<u32>,
#[cfg(feature = "metrics")]
pub metrics: Option<MetricsConfig>,
pub anonymized_dns: Option<AnonymizedDNSConfig>,
pub access_control: Option<AccessControlConfig>,
}
impl Config {
@ -91,9 +99,7 @@ impl Config {
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Config, Error> {
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)
}
}
@ -135,9 +141,7 @@ impl State {
}
pub fn from_file<P: AsRef<Path>>(path: P, key_cache_capacity: usize) -> Result<Self, Error> {
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);

@ -23,7 +23,7 @@ impl Signature {
}
}
big_array! { BigArray; }
big_array! { BigArray; crypto_sign_SECRETKEYBYTES as usize }
#[derive(Serialize, Deserialize, Derivative, Clone)]
#[derivative(Default)]

@ -2,6 +2,7 @@ use crate::dnscrypt_certs::*;
use crate::errors::*;
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use std::net::IpAddr;
use std::sync::Arc;
pub const DNS_MAX_HOSTNAME_SIZE: usize = 256;
@ -104,6 +105,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 +319,13 @@ fn traverse_rrs<F: FnMut(usize) -> 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 +341,13 @@ fn traverse_rrs_mut<F: FnMut(&mut [u8], usize) -> 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)
@ -374,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<u8>, max_payload_size: u16) -> Result<(), Error> {
let opt_rr: [u8; 11] = [
0,
@ -502,6 +531,73 @@ pub fn qtype_qclass(packet: &[u8]) -> Result<(u16, u16), Error> {
Ok((qtype, qclass))
}
fn parse_txt_rrdata<F: FnMut(&str) -> 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<u8>) -> Result<Option<String>, 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<u8>) -> Result<Vec<u8>, Error> {
ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet");
ensure!(qdcount(&client_packet) == 1, "No question");
@ -547,3 +643,37 @@ pub fn serve_blocked_response(client_packet: Vec<u8>) -> Result<Vec<u8>, Error>
packet.extend_from_slice(hinfo_rdata);
Ok(packet)
}
pub fn serve_ip_response(client_packet: Vec<u8>, ip: IpAddr, ttl: u32) -> Result<Vec<u8>, 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::<BigEndian>(0xc000 + DNS_HEADER_SIZE as u16)?;
match ip {
IpAddr::V4(ip) => {
packet.write_u16::<BigEndian>(DNS_TYPE_A)?;
packet.write_u16::<BigEndian>(DNS_CLASS_INET)?;
packet.write_u32::<BigEndian>(ttl)?;
packet.write_u16::<BigEndian>(4)?;
packet.extend_from_slice(&ip.octets());
}
IpAddr::V6(ip) => {
packet.write_u16::<BigEndian>(DNS_TYPE_AAAA)?;
packet.write_u16::<BigEndian>(DNS_CLASS_INET)?;
packet.write_u32::<BigEndian>(ttl)?;
packet.write_u16::<BigEndian>(16)?;
packet.extend_from_slice(&ip.octets());
}
};
Ok(packet)
}

@ -38,7 +38,7 @@ impl DNSCryptCertInner {
}
}
big_array! { BigArray; }
big_array! { BigArray; 64 }
#[derive(Derivative, Serialize, Deserialize)]
#[derivative(Debug, Default, Clone)]

@ -48,6 +48,9 @@ pub struct Globals {
pub anonymized_dns_allowed_ports: Vec<u16>,
pub anonymized_dns_allow_non_reserved_ports: bool,
pub anonymized_dns_blacklisted_ips: Vec<IpAddr>,
pub access_control_tokens: Option<Vec<String>>,
pub client_ttl_holdon: u32,
pub my_ip: Option<Vec<u8>>,
#[cfg(feature = "metrics")]
#[derivative(Debug = "ignore")]
pub varz: Varz,

@ -218,7 +218,14 @@ async fn handle_client_query(
!dns::is_response(&packet),
"Question expected, but got a response instead"
);
let response = resolver::get_cached_response_or_resolve(&globals, &mut packet).await?;
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, &client_ctx, &mut packet).await?;
encrypt_and_respond_to_query(
globals,
client_ctx,
@ -702,6 +709,13 @@ fn main() -> Result<(), Error> {
anonymized_dns.blacklisted_ips,
),
};
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,
};
let runtime_handle = runtime.handle();
let globals = Arc::new(Globals {
runtime_handle: runtime_handle.clone(),
@ -739,6 +753,9 @@ fn main() -> Result<(), Error> {
anonymized_dns_allowed_ports,
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()),
client_ttl_holdon: config.client_ttl_holdon.unwrap_or(60),
#[cfg(feature = "metrics")]
varz: Varz::default(),
});

@ -2,10 +2,12 @@ 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::cmp;
use std::hash::Hasher;
use std::net::{Ipv6Addr, SocketAddr, SocketAddrV6};
use tokio::net::{TcpStream, UdpSocket};
@ -226,9 +228,20 @@ pub async fn resolve(
pub async fn get_cached_response_or_resolve(
globals: &Globals,
client_ctx: &ClientCtx,
mut packet: &mut Vec<u8>,
) -> Result<Vec<u8>, Error> {
let packet_qname = dns::qname(&packet)?;
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);
}
}
if let Some(blacklist) = &globals.blacklist {
if blacklist.find(&packet_qname) {
#[cfg(feature = "metrics")]
@ -276,7 +289,14 @@ 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.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)?;
return Ok(response);
}

Loading…
Cancel
Save