From 1dc7ce9a05d0cd1addfcc58e51fdfed68f30583d Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 2 Sep 2019 23:02:23 +0200 Subject: [PATCH] up --- Cargo.toml | 23 ++++ README.md | 3 + src/config.rs | 0 src/crypto.rs | 88 ++++++++++++ src/dns.rs | 307 ++++++++++++++++++++++++++++++++++++++++++ src/dnscrypt_certs.rs | 77 +++++++++++ src/errors.rs | 13 ++ src/globals.rs | 26 ++++ src/main.rs | 109 +++++++++++++++ 9 files changed, 646 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/config.rs create mode 100644 src/crypto.rs create mode 100644 src/dns.rs create mode 100644 src/dnscrypt_certs.rs create mode 100644 src/errors.rs create mode 100644 src/globals.rs create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d63b589 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "rust-dnscrypt-server" +version = "0.1.0" +authors = ["Frank Denis "] +edition = "2018" + +[dependencies] +bincode = "1.1.4" +byteorder = "1.3.2" +clap = { version="2.33.0", features=["suggestions", "wrap_help", "nightly"] } +derivative = "1.0.3" +env_logger = "0.6.2" +failure = "0.1.5" +futures-preview = { version = "=0.3.0-alpha.18", features = ["compat", "async-await", "nightly", "io-compat", "cfg-target-has-atomic"] } +jemallocator = "0.3.2" +libsodium-sys="0.2.3" +log = "0.4.8" +tokio = "=0.2.0-alpha.4" + +[profile.release] +lto = true +panic = "abort" +opt-level = 3 diff --git a/README.md b/README.md index 54b0451..a310db9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # rust-dnscrypt-server + A new DNSCrypt server proxy implementation in Rust. + +# *** WIP - Nothing to see yet *** diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..175b04f --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,88 @@ +use crate::errors::*; + +use libsodium_sys::*; +use std::ptr; + +#[derive(Derivative)] +#[derivative(Default)] +pub struct Signature( + #[derivative(Default(value = "[0u8; crypto_sign_BYTES as usize]"))] + [u8; crypto_sign_BYTES as usize], +); + +impl Signature { + pub fn as_bytes(&self) -> &[u8; crypto_sign_BYTES as usize] { + &self.0 + } + + pub fn from_bytes(bytes: [u8; crypto_sign_BYTES as usize]) -> Self { + Signature(bytes) + } +} + +#[derive(Derivative)] +#[derivative(Default)] +pub struct SignSK( + #[derivative(Default(value = "[0u8; crypto_sign_SECRETKEYBYTES as usize]"))] + [u8; crypto_sign_SECRETKEYBYTES as usize], +); + +impl SignSK { + pub fn as_bytes(&self) -> &[u8; crypto_sign_SECRETKEYBYTES as usize] { + &self.0 + } + + pub fn from_bytes(bytes: [u8; crypto_sign_SECRETKEYBYTES as usize]) -> Self { + SignSK(bytes) + } + + pub fn sign(&self, bytes: &[u8]) -> Signature { + let mut signature = Signature::default(); + let ret = unsafe { + crypto_sign_detached( + signature.0.as_mut_ptr(), + ptr::null_mut(), + bytes.as_ptr(), + bytes.len() as _, + self.as_bytes().as_ptr(), + ) + }; + assert_eq!(ret, 0); + signature + } +} + +#[derive(Debug, Default)] +pub struct SignPK([u8; crypto_sign_PUBLICKEYBYTES as usize]); + +impl SignPK { + pub fn as_bytes(&self) -> &[u8; crypto_sign_PUBLICKEYBYTES as usize] { + &self.0 + } + + pub fn from_bytes(bytes: [u8; crypto_sign_PUBLICKEYBYTES as usize]) -> Self { + SignPK(bytes) + } +} + +#[derive(Derivative)] +#[derivative(Debug, Default)] +pub struct SignKeyPair { + #[derivative(Debug = "ignore")] + pub sk: SignSK, + pub pk: SignPK, +} + +impl SignKeyPair { + pub fn new() -> Self { + let mut kp = SignKeyPair::default(); + unsafe { crypto_sign_keypair(kp.pk.0.as_mut_ptr(), kp.sk.0.as_mut_ptr()) }; + kp + } +} + +pub fn init() -> Result<(), Error> { + let res = unsafe { sodium_init() }; + ensure!(res >= 0, "Unable to initialize libsodium"); + Ok(()) +} diff --git a/src/dns.rs b/src/dns.rs new file mode 100644 index 0000000..288e30a --- /dev/null +++ b/src/dns.rs @@ -0,0 +1,307 @@ +use crate::dnscrypt_certs::*; +use crate::errors::*; + +use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; + +const DNS_MAX_HOSTNAME_LEN: usize = 256; +const DNS_MAX_INDIRECTIONS: usize = 16; +const DNS_HEADER_SIZE: usize = 12; +const DNS_OFFSET_FLAGS: usize = 2; +const DNS_FLAGS_TC: u16 = 2u16 << 8; +const DNS_FLAGS_QR: u16 = 128u16 << 8; +const DNS_FLAGS_RA: u16 = 128; +const DNS_MAX_PACKET_SIZE: usize = 65_533; +const DNS_OFFSET_QUESTION: usize = DNS_HEADER_SIZE; +const DNS_TYPE_OPT: u16 = 41; +const DNS_TYPE_TXT: u16 = 16; +const DNS_CLASS_INET: u16 = 1; + +#[inline] +fn qdcount(packet: &[u8]) -> u16 { + BigEndian::read_u16(&packet[4..]) +} + +#[inline] +fn ancount(packet: &[u8]) -> u16 { + BigEndian::read_u16(&packet[6..]) +} + +fn ancount_inc(packet: &mut [u8]) -> Result<(), Error> { + let mut ancount = ancount(packet); + ensure!(ancount < 0xffff, "Too many answer records"); + ancount += 1; + BigEndian::write_u16(&mut packet[6..], ancount); + Ok(()) +} + +#[inline] +fn nscount(packet: &[u8]) -> u16 { + BigEndian::read_u16(&packet[8..]) +} + +#[inline] +fn arcount(packet: &[u8]) -> u16 { + BigEndian::read_u16(&packet[10..]) +} + +fn arcount_inc(packet: &mut [u8]) -> Result<(), Error> { + let mut arcount = arcount(packet); + ensure!(arcount < 0xffff, "Too many additional records"); + arcount += 1; + BigEndian::write_u16(&mut packet[10..], arcount); + Ok(()) +} + +#[inline] +pub fn authoritative_response(packet: &mut [u8]) { + let current_flags = BigEndian::read_u16(&packet[DNS_OFFSET_FLAGS..]); + BigEndian::write_u16( + &mut packet[DNS_OFFSET_FLAGS..], + current_flags | DNS_FLAGS_QR | DNS_FLAGS_RA, + ); +} + +#[inline] +pub fn truncate(packet: &mut [u8]) { + let current_flags = BigEndian::read_u16(&packet[DNS_OFFSET_FLAGS..]); + BigEndian::write_u16( + &mut packet[DNS_OFFSET_FLAGS..], + current_flags | DNS_FLAGS_TC | DNS_FLAGS_QR | DNS_FLAGS_RA, + ); +} + +pub fn qname(packet: &[u8]) -> Result, Error> { + assert!(std::usize::MAX > 0xffff); + ensure!(qdcount(packet) == 1, "Unexpected query count"); + let packet_len = packet.len(); + let mut offset = DNS_HEADER_SIZE; + let mut qname = Vec::with_capacity(DNS_MAX_HOSTNAME_LEN); + let mut indirections = 0; + loop { + ensure!(offset < packet_len, "Short packet"); + match packet[offset] as usize { + label_len if label_len & 0xc0 == 0xc0 => { + ensure!(packet_len - offset > 1, "Short packet"); + let new_offset = (BigEndian::read_u16(&packet[offset..]) & 0x3fff) as usize; + indirections += 1; + ensure!( + new_offset >= DNS_HEADER_SIZE + && new_offset != offset + && indirections < DNS_MAX_INDIRECTIONS, + "Too many indirections" + ); + offset = new_offset; + } + 0 => { + if qname.is_empty() { + qname.push(b'.') + } + break; + } + label_len => { + ensure!(packet_len - offset > 1, "Short packet"); + offset += 1; + ensure!(packet_len - offset > label_len, "Short packet"); + if !qname.is_empty() { + qname.push(b'.') + } + ensure!( + qname.len() < DNS_MAX_HOSTNAME_LEN - label_len, + "Name too long" + ); + qname.extend_from_slice(&packet[offset..offset + label_len]); + offset += label_len; + } + } + } + Ok(qname) +} + +fn skip_name(packet: &[u8], offset: usize) -> Result { + let packet_len = packet.len(); + ensure!(offset < packet_len - 1, "Short packet"); + let mut qname_len: usize = 0; + let mut offset = offset; + loop { + let label_len = match packet[offset] as usize { + label_len if label_len & 0xc0 == 0xc0 => { + ensure!(packet_len - offset >= 2, "Incomplete offset"); + offset += 2; + break; + } + label_len => label_len, + } as usize; + ensure!( + packet_len - offset - 1 > label_len, + "Malformed packet with an out-of-bounds name" + ); + qname_len += label_len + 1; + ensure!(qname_len <= DNS_MAX_HOSTNAME_LEN, "Name too long"); + offset += label_len + 1; + if label_len == 0 { + break; + } + } + Ok(offset) +} + +fn traverse_rrs Result<(), Error>>( + packet: &[u8], + mut offset: usize, + rrcount: u16, + mut cb: F, +) -> Result { + let packet_len = packet.len(); + 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, + "Record length would exceed packet length" + ); + offset += rdlen; + } + Ok(offset) +} + +fn traverse_rrs_mut Result<(), Error>>( + packet: &mut [u8], + mut offset: usize, + rrcount: u16, + mut cb: F, +) -> Result { + let packet_len = packet.len(); + 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, + "Record length would exceed packet length" + ); + offset += rdlen; + } + Ok(offset) +} + +pub fn min_ttl(packet: &[u8], min_ttl: u32, max_ttl: u32, failure_ttl: u32) -> Result { + ensure!(qdcount(packet) == 1, "Unsupported number of questions"); + let packet_len = packet.len(); + ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); + ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); + 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 + nscount + arcount; + let mut found_min_ttl = if rrcount > 0 { max_ttl } else { failure_ttl }; + + offset = traverse_rrs(packet, offset, rrcount, |offset| { + let qtype = BigEndian::read_u16(&packet[offset..]); + let ttl = BigEndian::read_u32(&packet[offset + 4..]); + if qtype != DNS_TYPE_OPT && ttl < found_min_ttl { + found_min_ttl = ttl; + } + Ok(()) + })?; + if found_min_ttl < min_ttl { + found_min_ttl = min_ttl; + } + ensure!(packet_len == offset, "Garbage after packet"); + Ok(found_min_ttl) +} + +fn add_edns_section(packet: &mut Vec, max_payload_size: u16) -> Result<(), Error> { + let opt_rr: [u8; 11] = [ + 0, + (DNS_TYPE_OPT >> 8) as u8, + DNS_TYPE_OPT as u8, + (max_payload_size >> 8) as u8, + max_payload_size as u8, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + ensure!( + DNS_MAX_PACKET_SIZE - packet.len() >= opt_rr.len(), + "Packet would be too large to add a new record" + ); + arcount_inc(packet)?; + packet.extend(&opt_rr); + Ok(()) +} + +pub fn set_edns_max_payload_size(packet: &mut Vec, max_payload_size: u16) -> Result<(), Error> { + ensure!(qdcount(packet) == 1, "Unsupported number of questions"); + let packet_len = packet.len(); + ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); + ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); + + 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 + nscount, |_offset| Ok(()))?; + let mut edns_payload_set = false; + + traverse_rrs_mut(packet, offset, arcount, |packet, offset| { + let qtype = BigEndian::read_u16(&packet[offset..]); + if qtype == DNS_TYPE_OPT { + ensure!(!edns_payload_set, "Duplicate OPT RR found"); + BigEndian::write_u16(&mut packet[offset + 2..], max_payload_size); + edns_payload_set = true; + } + Ok(()) + })?; + if edns_payload_set { + return Ok(()); + } + add_edns_section(packet, max_payload_size)?; + Ok(()) +} + +pub fn serve_certificates<'t>( + client_packet: &[u8], + expected_qname: &str, + dnscrypt_certs: impl IntoIterator, +) -> Result>, Error> { + let offset = skip_name(client_packet, DNS_HEADER_SIZE)?; + ensure!(client_packet.len() - offset >= 4, "Short packet"); + let qtype = BigEndian::read_u16(&client_packet[offset..]); + let qclass = BigEndian::read_u16(&client_packet[offset + 2..]); + if qtype != DNS_TYPE_TXT || qclass != DNS_CLASS_INET { + return Ok(None); + } + let qname_v = qname(&client_packet)?; + let qname = std::str::from_utf8(&qname_v)?; + if !qname.eq_ignore_ascii_case(expected_qname) { + return Ok(None); + } + let mut packet = (&client_packet[..offset + 4]).to_vec(); + authoritative_response(&mut packet); + + for dnscrypt_cert in dnscrypt_certs { + let cert_bin = dnscrypt_cert.as_bytes(); + ensure!(cert_bin.len() <= 0xff, "Certificate too long"); + ancount_inc(&mut packet)?; + packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; + packet.write_u16::(DNS_TYPE_TXT)?; + packet.write_u16::(DNS_CLASS_INET)?; + packet.write_u32::(28800)?; + packet.write_u16::(1 + cert_bin.len() as u16)?; + packet.write_u8(cert_bin.len() as u8)?; + packet.extend_from_slice(&cert_bin[..]); + ensure!(packet.len() < DNS_MAX_PACKET_SIZE, "Packet too large"); + } + Ok(Some(packet)) +} diff --git a/src/dnscrypt_certs.rs b/src/dnscrypt_certs.rs new file mode 100644 index 0000000..74342fa --- /dev/null +++ b/src/dnscrypt_certs.rs @@ -0,0 +1,77 @@ +use crate::crypto::*; + +use byteorder::{BigEndian, ByteOrder}; +use std::mem; +use std::slice; +use std::time::SystemTime; + +fn now() -> u32 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() as u32 +} + +#[derive(Debug, Default)] +#[repr(C, packed)] +pub struct DNSCryptCertInner { + resolver_pk: [u8; 32], + client_magic: [u8; 8], + serial: [u8; 8], + ts_start: [u8; 4], + ts_end: [u8; 4], +} + +impl DNSCryptCertInner { + pub fn as_bytes(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of_val(self)) } + } +} + +#[derive(Derivative)] +#[derivative(Debug, Default)] +#[repr(C, packed)] +pub struct DNSCryptCert { + cert_magic: [u8; 4], + es_version: [u8; 2], + minor_version: [u8; 2], + #[derivative(Debug = "ignore", Default(value = "[0u8; 64]"))] + signature: [u8; 64], + inner: DNSCryptCertInner, +} + +impl DNSCryptCert { + pub fn new(resolver_kp: &SignKeyPair) -> Self { + let ts_start = now(); + let ts_end = ts_start + 86400; + + let mut dnscrypt_cert = DNSCryptCert::default(); + + let dnscrypt_cert_inner = &mut dnscrypt_cert.inner; + dnscrypt_cert_inner + .resolver_pk + .copy_from_slice(resolver_kp.pk.as_bytes()); + dnscrypt_cert_inner + .client_magic + .copy_from_slice(&dnscrypt_cert_inner.resolver_pk[..8]); + BigEndian::write_u64(&mut dnscrypt_cert_inner.serial, 1); + BigEndian::write_u32(&mut dnscrypt_cert_inner.ts_start, ts_start); + BigEndian::write_u32(&mut dnscrypt_cert_inner.ts_end, ts_end); + + BigEndian::write_u32(&mut dnscrypt_cert.cert_magic, 0x44_4e_53_43); + BigEndian::write_u16(&mut dnscrypt_cert.es_version, 2); + BigEndian::write_u16(&mut dnscrypt_cert.minor_version, 0); + + dnscrypt_cert.signature.copy_from_slice( + resolver_kp + .sk + .sign(dnscrypt_cert_inner.as_bytes()) + .as_bytes(), + ); + dnscrypt_cert + } + + pub fn as_bytes(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of_val(self)) } + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..9ac3ea3 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,13 @@ +pub use failure::{bail, ensure, Error}; +use std::io; +use std::net::AddrParseError; + +#[derive(Debug, Fail)] +pub enum ProxyError { + #[fail(display = "Internal error: [{}]", _0)] + InternalError(String), + #[fail(display = "I/O error: [{}]", _0)] + Io(#[cause] io::Error), + #[fail(display = "Unable to parse address: [{}]", _0)] + AddrParseError(#[cause] AddrParseError), +} diff --git a/src/globals.rs b/src/globals.rs new file mode 100644 index 0000000..64b8914 --- /dev/null +++ b/src/globals.rs @@ -0,0 +1,26 @@ +use crate::crypto::*; +use crate::dnscrypt_certs::*; + +use std::sync::Arc; +use tokio::runtime::Runtime; + +#[derive(Debug)] +pub struct Globals { + pub runtime: Arc, + pub resolver_kp: SignKeyPair, + pub dnscrypt_certs: Vec, +} + +impl Globals { + pub fn new( + runtime: Arc, + resolver_kp: SignKeyPair, + dnscrypt_certs: Vec, + ) -> Self { + Globals { + runtime, + resolver_kp, + dnscrypt_certs, + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..15a4a4c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,109 @@ +#![allow(clippy::assertions_on_constants)] +#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(dead_code)] + +#[global_allocator] +static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; + +#[macro_use] +extern crate clap; +#[macro_use] +extern crate derivative; +#[macro_use] +extern crate failure; + +mod config; +mod crypto; +mod dns; +mod dnscrypt_certs; +mod errors; +mod globals; + +use crypto::*; +use dns::*; +use dnscrypt_certs::*; +use errors::*; +use globals::*; + +use byteorder::{BigEndian, ByteOrder}; +use clap::Arg; +use failure::{bail, ensure}; +use futures::prelude::*; +use futures::{FutureExt, StreamExt}; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::{TcpListener, UdpSocket}; +use tokio::prelude::*; +use tokio::runtime::Runtime; + +const DNSCRYPT_QUERY_MIN_SIZE: usize = 12; +const DNSCRYPT_QUERY_MAX_SIZE: usize = 512; + +async fn tcp_acceptor(globals: Arc, tcp_listener: TcpListener) -> Result<(), Error> { + let mut tcp_listener = tcp_listener.incoming(); + while let Some(client) = tcp_listener.next().await { + let mut client = match client { + Ok(client) => client, + Err(_) => continue, + }; + let mut binlen = [0u8, 0]; + client.read_exact(&mut binlen).await?; + let packet_len = BigEndian::read_u16(&binlen) as usize; + ensure!( + (DNSCRYPT_QUERY_MIN_SIZE..=DNSCRYPT_QUERY_MAX_SIZE).contains(&packet_len), + "Unexpected query size" + ); + let mut packet = vec![0u8; packet_len]; + client.read_exact(&mut packet).await?; + dbg!(packet); + } + Ok(()) +} + +async fn udp_acceptor(globals: Arc, mut udp_listener: UdpSocket) -> Result<(), Error> { + loop { + let mut packet = vec![0u8; DNSCRYPT_QUERY_MAX_SIZE]; + let (packet_len, client_addr) = udp_listener.recv_from(&mut packet).await?; + dbg!(&packet); + let mut packet = &mut packet[..packet_len]; + if let Some(synth_packet) = + serve_certificates(&packet, "2.dnscrypt.example.com", &globals.dnscrypt_certs)? + { + let _ = udp_listener.send_to(&synth_packet, client_addr).await; + continue; + } + truncate(&mut packet); + let _ = udp_listener.send_to(&packet, client_addr).await; + } +} + +async fn start(globals: Arc, runtime: Arc) -> Result<(), Error> { + let socket_addr: SocketAddr = "127.0.0.1:5300".parse()?; + let tcp_listener = TcpListener::bind(&socket_addr).await?; + let udp_listener = UdpSocket::bind(&socket_addr).await?; + runtime.spawn(tcp_acceptor(globals.clone(), tcp_listener).map(|_| {})); + runtime.spawn(udp_acceptor(globals.clone(), udp_listener).map(|_| {})); + Ok(()) +} + +fn main() -> Result<(), Error> { + env_logger::init(); + crypto::init()?; + + let _matches = app_from_crate!().get_matches(); + + let resolver_kp = SignKeyPair::new(); + let dnscrypt_cert = DNSCryptCert::new(&resolver_kp); + + let runtime = Arc::new(Runtime::new()?); + let globals = Arc::new(Globals::new( + runtime.clone(), + resolver_kp, + vec![dnscrypt_cert], + )); + runtime.spawn(start(globals, runtime.clone()).map(|_| ())); + runtime.block_on(future::pending::<()>()); + + Ok(()) +}