Implement support for server-side blacklists

pull/5/head
Frank Denis 5 years ago
parent 80e70eace4
commit 518f0ce17d

@ -89,3 +89,7 @@ The proxy creates and updates a file named `encrypted-dns.state` by default. Tha
Do not delete the file, unless you want to change parameters (such as the provider name), and keep it secret, or the keys will be lost. Do not delete the file, unless you want to change parameters (such as the provider name), and keep it secret, or the keys will be lost.
Putting it in a directory that is only readable by the super-user is not a bad idea. Putting it in a directory that is only readable by the super-user is not a bad idea.
## Filtering
Domains can be filtered directly by the proxy, see the `[filtering]` section of the configuration file.

@ -150,3 +150,13 @@ key_cache_capacity = 10000
## Where to prooxy TLS connections to (e.g. DoH server) ## Where to prooxy TLS connections to (e.g. DoH server)
# upstream_addr = "127.0.0.1:4343" # upstream_addr = "127.0.0.1:4343"
#######################################
# Server-side filtering #
#######################################
[filtering]
# domain_blacklist = "/etc/domain_blacklist.txt"

@ -0,0 +1,76 @@
use crate::errors::*;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::sync::Arc;
const MAX_ITERATIONS: usize = 5;
#[derive(Debug)]
struct BlackListInner {
map: HashMap<Vec<u8>, ()>,
}
#[derive(Clone, Debug)]
pub struct BlackList {
inner: Arc<BlackListInner>,
max_iterations: usize,
}
impl BlackList {
pub fn new(map: HashMap<Vec<u8>, ()>, max_iterations: usize) -> Self {
let inner = Arc::new(BlackListInner { map });
BlackList {
inner,
max_iterations,
}
}
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let mut map = HashMap::new();
let fp = BufReader::new(File::open(path)?);
for (line_nb, line) in fp.lines().enumerate() {
let line = line?;
let mut line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
while line.starts_with("*.") {
line = &line[2..];
}
while line.ends_with('.') {
line = &line[..line.len() - 1];
}
let qname = line.as_bytes().to_vec().to_ascii_lowercase();
if qname.is_empty() {
bail!(format_err!("Unexpected blacklist rule at line {}", line_nb))
}
map.insert(qname, ());
}
Ok(BlackList::new(map, MAX_ITERATIONS))
}
pub fn find(&self, qname: &[u8]) -> bool {
let qname = qname.to_vec().to_ascii_lowercase();
let mut qname = qname.as_slice();
let map = &self.inner.map;
let mut iterations = self.max_iterations;
while qname.len() >= 4 && iterations > 0 {
if map.contains_key(qname) {
return true;
}
let mut it = qname.splitn(2, |x| *x == b'.');
if it.next().is_none() {
break;
}
qname = match it.next() {
None => break,
Some(qname) => qname,
};
iterations -= 1;
}
false
}
}

@ -29,6 +29,11 @@ pub struct ListenAddrConfig {
pub external: SocketAddr, pub external: SocketAddr,
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct FilteringConfig {
pub domain_blacklist: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Config { pub struct Config {
pub listen_addrs: Vec<ListenAddrConfig>, pub listen_addrs: Vec<ListenAddrConfig>,
@ -46,6 +51,7 @@ pub struct Config {
pub user: Option<String>, pub user: Option<String>,
pub group: Option<String>, pub group: Option<String>,
pub chroot: Option<String>, pub chroot: Option<String>,
pub filtering: FilteringConfig,
pub dnscrypt: DNSCryptConfig, pub dnscrypt: DNSCryptConfig,
pub tls: TLSConfig, pub tls: TLSConfig,
pub daemonize: bool, pub daemonize: bool,

@ -372,3 +372,18 @@ pub fn serve_truncated(client_packet: Vec<u8>) -> Result<Vec<u8>, Error> {
truncate(&mut packet); truncate(&mut packet);
Ok(packet) Ok(packet)
} }
pub fn serve_empty_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");
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);
authoritative_response(&mut packet);
Ok(packet)
}

@ -1,3 +1,4 @@
use crate::blacklist::*;
use crate::cache::*; use crate::cache::*;
use crate::crypto::*; use crate::crypto::*;
use crate::dnscrypt_certs::*; use crate::dnscrypt_certs::*;
@ -35,4 +36,5 @@ pub struct Globals {
pub key_cache_capacity: usize, pub key_cache_capacity: usize,
pub hasher: SipHasher13, pub hasher: SipHasher13,
pub cache: Cache, pub cache: Cache,
pub blacklist: Option<BlackList>,
} }

@ -1,5 +1,6 @@
#![allow(clippy::assertions_on_constants)] #![allow(clippy::assertions_on_constants)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
#![allow(clippy::cognitive_complexity)]
#![allow(dead_code)] #![allow(dead_code)]
#[global_allocator] #[global_allocator]
@ -18,6 +19,7 @@ extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate serde_big_array; extern crate serde_big_array;
mod blacklist;
mod cache; mod cache;
mod config; mod config;
mod crypto; mod crypto;
@ -28,6 +30,7 @@ mod errors;
mod globals; mod globals;
mod resolver; mod resolver;
use blacklist::*;
use cache::*; use cache::*;
use config::*; use config::*;
use crypto::*; use crypto::*;
@ -521,7 +524,13 @@ fn main() -> Result<(), Error> {
config.cache_ttl_max, config.cache_ttl_max,
config.cache_ttl_error, config.cache_ttl_error,
); );
let blacklist = match config.filtering.domain_blacklist {
None => None,
Some(path) => Some(
BlackList::load(&path)
.map_err(|e| format_err!("Unable to load the blacklist [{:?}]: [{}]", path, e))?,
),
};
let globals = Arc::new(Globals { let globals = Arc::new(Globals {
runtime: runtime.clone(), runtime: runtime.clone(),
state_file: state_file.to_path_buf(), state_file: state_file.to_path_buf(),
@ -549,6 +558,7 @@ fn main() -> Result<(), Error> {
key_cache_capacity, key_cache_capacity,
hasher, hasher,
cache, cache,
blacklist,
}); });
let updater = DNSCryptEncryptionParamsUpdater::new(globals.clone()); let updater = DNSCryptEncryptionParamsUpdater::new(globals.clone());
if !state_is_new { if !state_is_new {

@ -13,6 +13,12 @@ use tokio::prelude::*;
use tokio_net::driver::Handle; use tokio_net::driver::Handle;
pub async fn resolve(globals: &Globals, mut packet: &mut Vec<u8>) -> Result<Vec<u8>, Error> { pub async fn resolve(globals: &Globals, mut packet: &mut Vec<u8>) -> Result<Vec<u8>, Error> {
let packet_qname = dns::qname(&packet)?;
if let Some(blacklist) = &globals.blacklist {
if blacklist.find(&packet_qname) {
return dns::serve_empty_response(packet.to_vec());
}
}
let original_tid = dns::tid(&packet); let original_tid = dns::tid(&packet);
dns::set_tid(&mut packet, 0); dns::set_tid(&mut packet, 0);
let mut hasher = globals.hasher; let mut hasher = globals.hasher;
@ -59,7 +65,7 @@ pub async fn resolve(globals: &Globals, mut packet: &mut Vec<u8>) -> Result<Vec<
if response_addr == globals.upstream_addr if response_addr == globals.upstream_addr
&& response_len >= DNS_HEADER_SIZE && response_len >= DNS_HEADER_SIZE
&& dns::tid(&response) == tid && dns::tid(&response) == tid
&& dns::qname(&packet)? == dns::qname(&response)? && packet_qname == dns::qname(&response)?
{ {
break; break;
} }
@ -98,7 +104,7 @@ pub async fn resolve(globals: &Globals, mut packet: &mut Vec<u8>) -> Result<Vec<
ext_socket.read_exact(&mut response).await?; ext_socket.read_exact(&mut response).await?;
ensure!(dns::tid(&response) == tid, "Unexpected transaction ID"); ensure!(dns::tid(&response) == tid, "Unexpected transaction ID");
ensure!( ensure!(
dns::qname(&packet)? == dns::qname(&response)?, packet_qname == dns::qname(&response)?,
"Unexpected query name in the response" "Unexpected query name in the response"
); );
} }

Loading…
Cancel
Save