2
0
mirror of https://github.com/jedisct1/encrypted-dns-server synced 2024-11-12 13:10:44 +00:00

Preliminary support for Anonymized DNS

This commit is contained in:
Frank Denis 2019-10-13 22:34:46 +02:00
parent 8aae5ac52c
commit 9db26ba20b
6 changed files with 154 additions and 42 deletions

View File

@ -1,2 +0,0 @@
pub const ANONYMIZED_DNSCRYPT_QUERY_MAGIC: [u8; 10] =
[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00];

82
src/anonymized_dns.rs Normal file
View File

@ -0,0 +1,82 @@
use crate::*;
use byteorder::{BigEndian, ByteOrder};
use failure::ensure;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::sync::Arc;
use tokio::net::UdpSocket;
pub const ANONYMIZED_DNSCRYPT_QUERY_MAGIC: [u8; 10] =
[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00];
pub const ANONYMIZED_DNSCRYPT_OVERHEAD: usize = 16 + 2;
pub async fn handle_anonymized_dns(
globals: Arc<Globals>,
client_ctx: ClientCtx,
encrypted_packet: &[u8],
) -> Result<(), Error> {
ensure!(
encrypted_packet.len() > ANONYMIZED_DNSCRYPT_OVERHEAD,
"Short packet"
);
let ip_bin = &encrypted_packet[..16];
let ip_v6 = Ipv6Addr::new(
BigEndian::read_u16(&ip_bin[0..2]),
BigEndian::read_u16(&ip_bin[2..4]),
BigEndian::read_u16(&ip_bin[4..6]),
BigEndian::read_u16(&ip_bin[6..8]),
BigEndian::read_u16(&ip_bin[8..10]),
BigEndian::read_u16(&ip_bin[10..12]),
BigEndian::read_u16(&ip_bin[12..14]),
BigEndian::read_u16(&ip_bin[14..16]),
);
let ip = match ip_v6.to_ipv4() {
Some(ip_v4) => IpAddr::V4(ip_v4),
None => IpAddr::V6(ip_v6),
};
#[cfg(feature = "metrics")]
globals.varz.anonymized_queries.inc();
ensure!(ip.is_global(), "Forbidden upstream address");
let port = BigEndian::read_u16(&encrypted_packet[16..18]);
ensure!([443].contains(&port), "Forbidden upstream port");
let upstream_address = SocketAddr::new(ip, port);
ensure!(
!globals.listen_addrs.contains(&upstream_address)
&& globals.external_addr != upstream_address,
"Would be relaying to self"
);
let encrypted_packet = &encrypted_packet[ANONYMIZED_DNSCRYPT_OVERHEAD..];
let encrypted_packet_len = encrypted_packet.len();
ensure!(
encrypted_packet_len >= DNSCRYPT_UDP_QUERY_MIN_SIZE
&& encrypted_packet_len <= DNSCRYPT_UDP_QUERY_MAX_SIZE,
"Unexpected encapsulated query length"
);
ensure!(
encrypted_packet_len > 8 && [0u8, 0, 0, 0, 0, 0, 0, 1] != encrypted_packet[..8],
"Protocol confusion with QUIC"
);
let mut ext_socket = UdpSocket::bind(&globals.external_addr).await?;
ext_socket.connect(&upstream_address).await?;
ext_socket.send(&encrypted_packet).await?;
let mut response = vec![0u8; DNSCRYPT_UDP_RESPONSE_MAX_SIZE];
loop {
let fut = ext_socket.recv_from(&mut response[..]);
let (response_len, response_addr) = fut.await?;
if response_addr == upstream_address
&& (DNSCRYPT_UDP_RESPONSE_MIN_SIZE..=DNSCRYPT_UDP_RESPONSE_MAX_SIZE)
.contains(&response_len)
&& response[..DNSCRYPT_RESPONSE_MAGIC_SIZE] == DNSCRYPT_RESPONSE_MAGIC
{
response.truncate(response_len);
break;
}
}
#[cfg(feature = "metrics")]
globals.varz.anonymized_responses.inc();
respond_to_query(client_ctx, response).await
}

View File

@ -21,6 +21,8 @@ pub const DNSCRYPT_QUERY_MIN_OVERHEAD: usize =
DNSCRYPT_QUERY_HEADER_SIZE + DNSCRYPT_MAC_SIZE + DNSCRYPT_QUERY_MIN_PADDING_SIZE;
pub const DNSCRYPT_RESPONSE_MAGIC_SIZE: usize = 8;
pub const DNSCRYPT_RESPONSE_MAGIC: [u8; DNSCRYPT_RESPONSE_MAGIC_SIZE] =
[0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38];
pub const DNSCRYPT_RESPONSE_NONCE_SIZE: usize = DNSCRYPT_FULL_NONCE_SIZE;
pub const DNSCRYPT_RESPONSE_HEADER_SIZE: usize =
DNSCRYPT_RESPONSE_MAGIC_SIZE + DNSCRYPT_RESPONSE_NONCE_SIZE;
@ -103,7 +105,7 @@ pub fn encrypt(
max_packet_size: usize,
) -> Result<Vec<u8>, Error> {
let mut wrapped_packet = Vec::with_capacity(DNS_MAX_PACKET_SIZE);
wrapped_packet.extend_from_slice(&[0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38]);
wrapped_packet.extend_from_slice(&DNSCRYPT_RESPONSE_MAGIC);
wrapped_packet.extend_from_slice(nonce);
ensure!(
max_packet_size >= wrapped_packet.len(),

View File

@ -1,4 +1,4 @@
use crate::anomymized_dns::*;
use crate::anonymized_dns::*;
use crate::config::*;
use crate::crypto::*;
use crate::dnscrypt::*;

View File

@ -2,6 +2,7 @@
#![allow(clippy::type_complexity)]
#![allow(clippy::cognitive_complexity)]
#![allow(dead_code)]
#![feature(ip)]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
@ -22,7 +23,7 @@ extern crate serde_big_array;
#[macro_use]
extern crate prometheus;
mod anomymized_dns;
mod anonymized_dns;
mod blacklist;
mod cache;
mod config;
@ -38,6 +39,7 @@ mod resolver;
#[cfg(feature = "metrics")]
mod varz;
use anonymized_dns::*;
use blacklist::*;
use cache::*;
use config::*;
@ -78,18 +80,18 @@ use tokio::runtime::Runtime;
use tokio::sync::oneshot;
#[derive(Debug)]
struct UdpClientCtx {
pub struct UdpClientCtx {
net_udp_socket: std::net::UdpSocket,
client_addr: SocketAddr,
}
#[derive(Debug)]
struct TcpClientCtx {
pub struct TcpClientCtx {
client_connection: TcpStream,
}
#[derive(Debug)]
enum ClientCtx {
pub enum ClientCtx {
Udp(UdpClientCtx),
Tcp(TcpClientCtx),
}
@ -111,28 +113,7 @@ fn maybe_truncate_response(
Ok(response)
}
async fn respond_to_query(
client_ctx: ClientCtx,
packet: Vec<u8>,
response: Vec<u8>,
original_packet_size: usize,
shared_key: Option<SharedKey>,
nonce: Option<[u8; DNSCRYPT_FULL_NONCE_SIZE]>,
) -> Result<(), Error> {
ensure!(dns::is_response(&response), "Packet is not a response");
let max_response_size = match client_ctx {
ClientCtx::Udp(_) => original_packet_size,
ClientCtx::Tcp(_) => DNSCRYPT_TCP_RESPONSE_MAX_SIZE,
};
let response = match &shared_key {
None => response,
Some(shared_key) => dnscrypt::encrypt(
maybe_truncate_response(&client_ctx, packet, response, original_packet_size)?,
shared_key,
nonce.as_ref().unwrap(),
max_response_size,
)?,
};
pub async fn respond_to_query(client_ctx: ClientCtx, response: Vec<u8>) -> Result<(), Error> {
match client_ctx {
ClientCtx::Udp(client_ctx) => {
let net_udp_socket = client_ctx.net_udp_socket;
@ -155,12 +136,51 @@ async fn respond_to_query(
Ok(())
}
async fn encrypt_and_respond_to_query(
client_ctx: ClientCtx,
packet: Vec<u8>,
response: Vec<u8>,
original_packet_size: usize,
shared_key: Option<SharedKey>,
nonce: Option<[u8; DNSCRYPT_FULL_NONCE_SIZE]>,
) -> Result<(), Error> {
ensure!(dns::is_response(&response), "Packet is not a response");
let max_response_size = match client_ctx {
ClientCtx::Udp(_) => original_packet_size,
ClientCtx::Tcp(_) => DNSCRYPT_TCP_RESPONSE_MAX_SIZE,
};
let response = match &shared_key {
None => response,
Some(shared_key) => dnscrypt::encrypt(
maybe_truncate_response(&client_ctx, packet, response, original_packet_size)?,
shared_key,
nonce.as_ref().unwrap(),
max_response_size,
)?,
};
respond_to_query(client_ctx, response).await
}
async fn handle_client_query(
globals: Arc<Globals>,
client_ctx: ClientCtx,
encrypted_packet: Vec<u8>,
) -> Result<(), Error> {
let original_packet_size = encrypted_packet.len();
ensure!(
original_packet_size >= DNSCRYPT_QUERY_MIN_OVERHEAD + DNS_HEADER_SIZE,
"Short packet"
);
debug_assert!(DNSCRYPT_QUERY_MIN_OVERHEAD > ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len());
if encrypted_packet[..ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()] == ANONYMIZED_DNSCRYPT_QUERY_MAGIC
{
return handle_anonymized_dns(
globals,
client_ctx,
&encrypted_packet[ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()..],
)
.await;
}
let mut dnscrypt_encryption_params_set = vec![];
for params in &**globals.dnscrypt_encryption_params_set.read() {
dnscrypt_encryption_params_set.push((*params).clone())
@ -175,7 +195,7 @@ async fn handle_client_query(
&globals.provider_name,
&dnscrypt_encryption_params_set,
)? {
return respond_to_query(
return encrypt_and_respond_to_query(
client_ctx,
packet,
synth_packet,
@ -195,7 +215,7 @@ async fn handle_client_query(
"Question expected, but got a response instead"
);
let response = resolver::get_cached_response_or_resolve(&globals, &mut packet).await?;
respond_to_query(
encrypt_and_respond_to_query(
client_ctx,
packet,
response,
@ -601,16 +621,12 @@ fn main() -> Result<(), Error> {
{
if let Some(metrics_config) = config.metrics {
runtime.spawn(
metrics::prometheus_service(
globals.varz.clone(),
metrics_config.clone(),
runtime.clone(),
)
.map_err(|e| {
error!("Unable to start the metrics service: [{}]", e);
std::process::exit(1);
})
.map(|_| ()),
metrics::prometheus_service(globals.varz.clone(), metrics_config, runtime.clone())
.map_err(|e| {
error!("Unable to start the metrics service: [{}]", e);
std::process::exit(1);
})
.map(|_| ()),
);
}
}

View File

@ -7,6 +7,8 @@ pub struct StartInstant(pub Instant);
pub struct Inner {
pub start_instant: StartInstant,
pub uptime: Gauge,
pub anonymized_queries: Counter,
pub anonymized_responses: Counter,
pub client_queries: Gauge,
pub client_queries_udp: Counter,
pub client_queries_tcp: Counter,
@ -39,6 +41,18 @@ impl Inner {
labels! {"handler" => "all",}
))
.unwrap(),
anonymized_queries: register_counter!(opts!(
"encrypted_dns_anonymized_queries",
"Number of anomymized queries received",
labels! {"handler" => "all",}
))
.unwrap(),
anonymized_responses: register_counter!(opts!(
"encrypted_dns_anonymized_responses",
"Number of anomymized responses received",
labels! {"handler" => "all",}
))
.unwrap(),
client_queries: register_gauge!(opts!(
"encrypted_dns_client_queries",
"Number of client queries received",