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:
parent
8aae5ac52c
commit
9db26ba20b
@ -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
82
src/anonymized_dns.rs
Normal 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
|
||||
}
|
@ -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(),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::anomymized_dns::*;
|
||||
use crate::anonymized_dns::*;
|
||||
use crate::config::*;
|
||||
use crate::crypto::*;
|
||||
use crate::dnscrypt::*;
|
||||
|
92
src/main.rs
92
src/main.rs
@ -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(|_| ()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
14
src/varz.rs
14
src/varz.rs
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user