From c98a202f806f4aeb02f44698e8eafc3f05c361bb Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 21 Sep 2019 00:53:20 +0200 Subject: [PATCH] Add a simple built-in DNS cache (TTL is not handled yet) --- Cargo.toml | 2 +- encrypted-dns.toml | 5 +++++ src/config.rs | 1 + src/dns.rs | 12 ++++++++++++ src/globals.rs | 8 +++++++- src/main.rs | 30 ++++++++++++++++++++++++++++++ 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bafc378..a51ed43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ rand = "0.7.2" serde = "1.0.101" serde_derive = "1.0.101" serde-big-array = "0.1.5" -siphasher = "0.3.0" +siphasher = "0.3.1" tokio = "=0.2.0-alpha.4" tokio-net = "=0.2.0-alpha.4" toml = "0.5.3" diff --git a/encrypted-dns.toml b/encrypted-dns.toml index b37ec81..a4d37bb 100644 --- a/encrypted-dns.toml +++ b/encrypted-dns.toml @@ -50,6 +50,11 @@ udp_max_active_connections = 1000 tcp_max_active_connections = 100 +## DNS cache capacity + +cache_capacity = 10000 + + ## User name to drop privileges to, when started as root. # user = "nobody" diff --git a/src/config.rs b/src/config.rs index 8cff57a..95f4e24 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,6 +30,7 @@ pub struct Config { pub tcp_timeout: u32, pub udp_max_active_connections: u32, pub tcp_max_active_connections: u32, + pub cache_capacity: usize, pub user: Option, pub group: Option, pub chroot: Option, diff --git a/src/dns.rs b/src/dns.rs index 8ceb519..3579662 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -18,6 +18,18 @@ const DNS_TYPE_OPT: u16 = 41; const DNS_TYPE_TXT: u16 = 16; const DNS_CLASS_INET: u16 = 1; +const DNS_RCODE_SERVFAIL: u8 = 2; + +#[inline] +pub fn rcode(packet: &[u8]) -> u8 { + packet[3] & 0x0f +} + +#[inline] +pub fn rcode_servfail(packet: &[u8]) -> bool { + rcode(packet) == DNS_RCODE_SERVFAIL +} + #[inline] pub fn qdcount(packet: &[u8]) -> u16 { BigEndian::read_u16(&packet[4..]) diff --git a/src/globals.rs b/src/globals.rs index 33650b8..d53e2a4 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,7 +1,9 @@ use crate::crypto::*; use crate::dnscrypt_certs::*; +use clockpro_cache::ClockProCache; use parking_lot::{Mutex, RwLock}; +use siphasher::sip128::SipHasher13; use std::collections::vec_deque::VecDeque; use std::net::SocketAddr; use std::path::PathBuf; @@ -11,7 +13,8 @@ use std::time::Duration; use tokio::runtime::Runtime; use tokio::sync::oneshot; -#[derive(Debug)] +#[derive(Derivative)] +#[derivative(Debug)] pub struct Globals { pub runtime: Arc, pub state_file: PathBuf, @@ -31,4 +34,7 @@ pub struct Globals { pub udp_active_connections: Arc>>>, pub tcp_active_connections: Arc>>>, pub key_cache_capacity: usize, + pub hasher: SipHasher13, + #[derivative(Debug = "ignore")] + pub cache: Arc>>>, } diff --git a/src/main.rs b/src/main.rs index 608fb59..71c9059 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ use globals::*; use byteorder::{BigEndian, ByteOrder}; use clap::Arg; +use clockpro_cache::ClockProCache; use dnsstamps::{InformalProperty, WithInformalProperty}; use failure::{bail, ensure}; use futures::join; @@ -44,9 +45,11 @@ use parking_lot::Mutex; use parking_lot::RwLock; use privdrop::PrivDrop; use rand::prelude::*; +use siphasher::sip128::{Hasher128, SipHasher13}; use std::collections::vec_deque::VecDeque; use std::convert::TryFrom; use std::fs::File; +use std::hash::Hasher; use std::io::prelude::*; use std::mem; use std::net::SocketAddr; @@ -140,6 +143,21 @@ async fn respond_to_query( async fn resolve(globals: &Globals, mut packet: &mut Vec) -> Result, Error> { let original_tid = dns::tid(&packet); + + dns::set_tid(&mut packet, 0); + let mut hasher = globals.hasher.clone(); + hasher.write(&packet); + let packet_hash = hasher.finish128().as_u128(); + let cached_response = { + match globals.cache.lock().get(&packet_hash) { + None => None, + Some(response) => Some((*response).clone()), + } + }; + if let Some(mut cached_response) = cached_response { + dns::set_tid(&mut cached_response, original_tid); + return Ok(cached_response); + } let tid = random(); dns::set_tid(&mut packet, tid); let mut ext_socket = UdpSocket::bind(&globals.external_addr).await?; @@ -189,6 +207,9 @@ async fn resolve(globals: &Globals, mut packet: &mut Vec) -> Result, "Unexpected query name in the response" ); } + if !dns::rcode_servfail(&response) { + globals.cache.lock().insert(packet_hash, response.clone()); + } dns::set_tid(&mut response, original_tid); Ok(response) } @@ -435,6 +456,7 @@ fn main() -> Result<(), Error> { let runtime = Arc::new(runtime_builder.build()?); let key_cache_capacity = config.dnscrypt.key_cache_capacity; + let cache_capacity = config.cache_capacity; let state_file = &config.state_file; if let Some(secret_key_path) = matches.value_of("import-from-dnscrypt-wrapper") { @@ -497,6 +519,12 @@ fn main() -> Result<(), Error> { .map(Arc::new) .collect::>(); + let (sh_k0, sh_k1) = rand::thread_rng().gen(); + let hasher = SipHasher13::new_with_keys(sh_k0, sh_k1); + + let cache = ClockProCache::new(cache_capacity) + .map_err(|e| format_err!("Unable to create the DNS cache: [{}]", e))?; + let globals = Arc::new(Globals { runtime: runtime.clone(), state_file: state_file.to_path_buf(), @@ -522,6 +550,8 @@ fn main() -> Result<(), Error> { config.tcp_max_active_connections as _, ))), key_cache_capacity, + hasher, + cache: Arc::new(Mutex::new(cache)), }); let updater = DNSCryptEncryptionParamsUpdater::new(globals.clone()); runtime.spawn(updater.run());