Added DNS-over-HTTPS support for forwarded queries.

pull/154/head
Revertron 3 years ago
parent eed0adad0c
commit 65bdbd677a

252
Cargo.lock generated

@ -101,6 +101,7 @@ dependencies = [
"thread-priority",
"tinyfiledialogs",
"toml",
"ureq",
"uuid",
"web-view",
"winapi",
@ -183,6 +184,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426"
[[package]]
name = "bumpalo"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -250,6 +257,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "chunked_transfer"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]]
name = "cipher"
version = "0.2.5"
@ -402,6 +415,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "gdk-pixbuf-sys"
version = "0.10.0"
@ -585,6 +608,17 @@ dependencies = [
"digest",
]
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "itoa"
version = "0.4.7"
@ -600,6 +634,15 @@ dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4bf49d50e2961077d9c99f4b7997d770a1114f087c3c2e0069b36c13fc2979d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -621,6 +664,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mio"
version = "0.7.13"
@ -692,6 +741,12 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -726,6 +781,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877630b3de15c0b64cc52f659345724fbf6bdad9bd9566699fc53688f3c34a34"
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
@ -868,6 +929,21 @@ dependencies = [
"rand_core 0.6.2",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustc_version"
version = "0.3.3"
@ -877,12 +953,35 @@ dependencies = [
"semver",
]
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "sct"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "semver"
version = "0.11.0"
@ -996,6 +1095,12 @@ dependencies = [
"system-deps",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "sqlite"
version = "0.26.0"
@ -1147,6 +1252,21 @@ dependencies = [
"libc",
]
[[package]]
name = "tinyvec"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.8"
@ -1168,6 +1288,21 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-bidi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
[[package]]
name = "unicode-normalization"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
@ -1196,6 +1331,40 @@ dependencies = [
"subtle",
]
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "ureq"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3131cd6cb18488da91da1d10ed31e966f453c06b65bf010d35638456976a3fd7"
dependencies = [
"base64",
"chunked_transfer",
"log",
"once_cell",
"rustls",
"url",
"webpki",
"webpki-roots",
]
[[package]]
name = "url"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "urlencoding"
version = "1.1.1"
@ -1236,6 +1405,70 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe8dc78e2326ba5f845f4b5bf548401604fa20b1dd1d365fb73b6c1d6364041"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44468aa53335841d9d6b6c023eaab07c0cd4bddbcfdee3e2bb1e8d2cb8069fef"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0195807922713af1e67dc66132c7328206ed9766af3858164fb583eedc25fbad"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29"
[[package]]
name = "web-sys"
version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224b2f6b67919060055ef1a67807367c2066ed520c3862cc013d26cf893a783c"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "web-view"
version = "0.7.3"
@ -1270,6 +1503,25 @@ dependencies = [
"soup-sys",
]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
dependencies = [
"webpki",
]
[[package]]
name = "webview-sys"
version = "0.6.2"

@ -38,6 +38,7 @@ rand-old = { package = "rand", version = "0.7.0" } # For ed25519-dalek
sqlite = "0.26.0"
uuid = { version = "0.8.2", features = ["serde", "v4"] }
mio = { version = "0.7.13", features = ["os-poll", "net"] }
ureq = "2.2"
derive_more = "0.99.16"
lazy_static = "1.4.0"

@ -23,9 +23,9 @@ listen = "127.0.0.1:53"
# How many threads to spawn by DNS server
threads = 50
# AdGuard DNS servers to filter ads and trackers
forwarders = ["94.140.14.14:53", "94.140.15.15:53"]
forwarders = ["https://dns.adguard.com/dns-query", "94.140.14.14:53", "94.140.15.15:53"]
# Cloudflare servers
#forwarders = ["1.1.1.1:53", "1.0.0.1:53"]
#forwarders = ["https://cloudflare-dns.com/dns-query", "1.1.1.1:53", "1.0.0.1:53"]
# Hosts file support (resolve local names or block ads)
#hosts = ["system", "adblock.txt"]

@ -1,6 +1,6 @@
//! client for sending DNS queries to other servers
use std::io::Write;
use std::io::{Write, Read};
use std::marker::{Send, Sync};
use std::net::{SocketAddr, TcpStream, ToSocketAddrs, UdpSocket};
use std::sync::atomic::{AtomicUsize, Ordering};
@ -12,7 +12,10 @@ use std::time::Duration as SleepDuration;
use chrono::*;
use derive_more::{Display, Error, From};
use crate::dns::buffer::{BytePacketBuffer, PacketBuffer, StreamPacketBuffer};
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use crate::dns::buffer::{BytePacketBuffer, PacketBuffer, StreamPacketBuffer, VectorPacketBuffer};
use crate::dns::netutil::{read_packet_length, write_packet_length};
use crate::dns::protocol::{DnsPacket, DnsQuestion, QueryType};
@ -38,7 +41,7 @@ pub trait DnsClient {
/// The UDP client
///
/// This includes a fair bit of synchronization due to the stateless nature of UDP.
/// When many queries are sent in parallell, the response packets can come back
/// When many queries are sent in parallel, the response packets can come back
/// in any order. For that reason, we fire off replies on the sending thread, but
/// handle replies on a single thread. A channel is created for every response,
/// and the caller will block on the channel until the a response is received.
@ -337,11 +340,100 @@ impl DnsClient for DnsNetworkClient {
return Ok(packet);
}
println!("Truncated response - resending as TCP");
info!("Truncated response - resending as TCP");
self.send_tcp_query(qname, qtype, server, recursive)
}
}
pub struct HttpsDnsClient {
agent: ureq::Agent,
/// Counter for assigning packet ids
seq: AtomicUsize,
}
impl HttpsDnsClient {
pub(crate) fn new() -> Self {
let client_name = format!("ALFIS/{}", env!("CARGO_PKG_VERSION"));
let agent = ureq::AgentBuilder::new()
.user_agent(&client_name)
.timeout(std::time::Duration::from_secs(3))
.build();
Self { agent, seq: AtomicUsize::new(1) }
}
}
impl DnsClient for HttpsDnsClient {
fn get_sent_count(&self) -> usize {
// No statistics for now
0
}
fn get_failed_count(&self) -> usize {
// No statistics for now
0
}
fn run(&self) -> Result<()> {
debug!("Started DoH client");
Ok(())
}
fn send_query(&self, qname: &str, qtype: QueryType, doh_url: &str, recursive: bool) -> Result<DnsPacket> {
// Create DnsPacket
let mut packet = DnsPacket::new();
packet.header.id = self.seq.fetch_add(1, Ordering::SeqCst) as u16;
if packet.header.id + 1 == 0xFFFF {
let _ = self.seq.compare_exchange(0xFFFF, 0, Ordering::SeqCst, Ordering::SeqCst);
}
packet.header.questions = 1;
packet.header.recursion_desired = recursive;
packet.questions.push(DnsQuestion::new(String::from(qname), qtype));
let mut req_buffer = VectorPacketBuffer::new();
packet.write(&mut req_buffer, 512).expect("Preparing DnsPacket failed!");
let response = self.agent
.post(doh_url)
.set("Content-Type", "application/dns-message")
.send_bytes(&req_buffer.buffer.as_slice());
match response {
Ok(response) => {
trace!("Response: Code {}, Type: {}, Headers: {:?}", response.status(), response.content_type(), response.headers_names());
match response.status() {
200 => {
match response.header("Content-Length") {
None => warn!("No 'Content-Length' header in DoH response!"),
Some(str) => {
match str.parse::<usize>() {
Ok(size) => {
let mut bytes: Vec<u8> = Vec::with_capacity(size);
response.into_reader()
.take(4096)
.read_to_end(&mut bytes)?;
let mut buffer = VectorPacketBuffer::new();
buffer.buffer.extend_from_slice(&bytes.as_slice());
if let Ok(packet) = DnsPacket::from_buffer(&mut buffer) {
trace!("Packet parsed successfully: {:?}", &packet);
return Ok(packet);
}
warn!("Error parsing DoH result!");
}
Err(e) => warn!("Error parsing 'Content-Length' in DoH response! {}", e)
}
}
}
}
_ => warn!("Error getting DoH response")
}
}
Err(e) => warn!("DoH error: {}", &e.to_string())
}
Err(ClientError::LookupFailed)
}
}
#[cfg(test)]
pub mod tests {
use super::*;

@ -7,7 +7,7 @@ use derive_more::{Display, Error, From};
use crate::dns::authority::Authority;
use crate::dns::cache::SynchronizedCache;
use crate::dns::client::{DnsClient, DnsNetworkClient};
use crate::dns::client::{DnsClient, DnsNetworkClient, HttpsDnsClient};
use crate::dns::filter::DnsFilter;
use crate::dns::resolve::{DnsResolver, ForwardingDnsResolver, RecursiveDnsResolver};
@ -44,7 +44,8 @@ pub struct ServerContext {
pub authority: Authority,
pub cache: SynchronizedCache,
pub filters: Vec<Box<dyn DnsFilter + Sync + Send>>,
pub client: Box<dyn DnsClient + Sync + Send>,
pub old_client: Box<dyn DnsClient + Sync + Send>,
pub doh_client: Box<dyn DnsClient + Sync + Send>,
pub dns_listen: String,
pub api_port: u16,
pub resolve_strategy: ResolveStrategy,
@ -68,7 +69,8 @@ impl ServerContext {
authority: Authority::new(),
cache: SynchronizedCache::new(),
filters: Vec::new(),
client: Box::new(DnsNetworkClient::new(10000 + (rand::random::<u16>() % 20000))),
old_client: Box::new(DnsNetworkClient::new(10000 + (rand::random::<u16>() % 20000))),
doh_client: Box::new(HttpsDnsClient::new()),
dns_listen: String::from("0.0.0.0:53"),
api_port: 5380,
resolve_strategy: ResolveStrategy::Recursive,
@ -83,7 +85,9 @@ impl ServerContext {
pub fn initialize(&mut self) -> Result<()> {
// Start UDP client thread
self.client.run()?;
self.old_client.run()?;
// Start DoH client
self.doh_client.run()?;
// Load authority data
self.authority.load()?;
@ -117,7 +121,8 @@ pub mod tests {
authority: Authority::new(),
cache: SynchronizedCache::new(),
filters: Vec::new(),
client: Box::new(DnsStubClient::new(callback)),
old_client: Box::new(DnsStubClient::new(callback)),
doh_client: Box::new(HttpsDnsClient::new()),
dns_listen: String::from("0.0.0.0:53"),
api_port: 5380,
resolve_strategy: ResolveStrategy::Recursive,

@ -87,7 +87,13 @@ impl DnsResolver for ForwardingDnsResolver {
let mut random = rand::thread_rng();
let upstream = self.upstreams.iter().choose(&mut random).unwrap();
let result = match self.context.cache.lookup(qname, qtype) {
None => self.context.client.send_query(qname, qtype, upstream, true)?,
None => {
if is_url(upstream) {
self.context.doh_client.send_query(qname, qtype, upstream, true)?
} else {
self.context.old_client.send_query(qname, qtype, upstream, true)?
}
},
Some(packet) => packet
};
@ -150,7 +156,7 @@ impl DnsResolver for RecursiveDnsResolver {
let ns_copy = ns.clone();
let server = format!("{}:{}", ns_copy.as_str(), 53);
let response = self.context.client.send_query(qname, qtype.clone(), &server, false)?;
let response = self.context.old_client.send_query(qname, qtype.clone(), &server, false)?;
// If we've got an actual answer, we're done!
if !response.answers.is_empty() && response.header.rescode == ResultCode::NOERROR {
@ -198,6 +204,10 @@ impl DnsResolver for RecursiveDnsResolver {
}
}
fn is_url(url: &str) -> bool {
url.starts_with("https://")
}
#[cfg(test)]
mod tests {

Loading…
Cancel
Save