From 65bdbd677acd6265e74f5b0b2fba1aeba1b7fd2a Mon Sep 17 00:00:00 2001 From: Revertron Date: Sun, 5 Sep 2021 19:05:30 +0200 Subject: [PATCH 1/2] Added DNS-over-HTTPS support for forwarded queries. --- Cargo.lock | 252 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + alfis.toml | 4 +- src/dns/client.rs | 100 +++++++++++++++++- src/dns/context.rs | 15 ++- src/dns/resolve.rs | 14 ++- 6 files changed, 373 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8c59ec..4e2d127 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 9f12325..8adb42c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/alfis.toml b/alfis.toml index 267e9de..fa0d8c5 100644 --- a/alfis.toml +++ b/alfis.toml @@ -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"] diff --git a/src/dns/client.rs b/src/dns/client.rs index 7ce7520..4a7cc7e 100644 --- a/src/dns/client.rs +++ b/src/dns/client.rs @@ -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 { + // 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::() { + Ok(size) => { + let mut bytes: Vec = 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::*; diff --git a/src/dns/context.rs b/src/dns/context.rs index 2fce375..43f18bc 100644 --- a/src/dns/context.rs +++ b/src/dns/context.rs @@ -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>, - pub client: Box, + pub old_client: Box, + pub doh_client: Box, 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::() % 20000))), + old_client: Box::new(DnsNetworkClient::new(10000 + (rand::random::() % 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, diff --git a/src/dns/resolve.rs b/src/dns/resolve.rs index efbae04..4444560 100644 --- a/src/dns/resolve.rs +++ b/src/dns/resolve.rs @@ -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 { From d0f95c58e93522184fdab4a3fa9f46decfcfa1d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 03:04:18 +0000 Subject: [PATCH 2/2] Bump num-bigint from 0.4.1 to 0.4.2 Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.4.1 to 0.4.2. - [Release notes](https://github.com/rust-num/num-bigint/releases) - [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md) - [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.1...num-bigint-0.4.2) --- updated-dependencies: - dependency-name: num-bigint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e2d127..1581691 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -703,9 +703,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e97c412795abf6c24ba30055a8f20642ea57ca12875220b854cfa501bf1e48" +checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" dependencies = [ "autocfg", "num-integer", diff --git a/Cargo.toml b/Cargo.toml index 8adb42c..4080a71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ serde_json = "1.0.67" bincode = "1.3.3" serde_cbor = "0.11.2" base64 = "0.13.0" -num-bigint = "0.4.1" +num-bigint = "0.4.2" num-traits = "0.2.14" chrono = { version = "0.4.19", features = ["serde"] } rand = { version = "0.8.4", package = "rand" }