Added DNS-over-HTTPS support for forwarded queries.
This commit is contained in:
Generated
+825
-17
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,9 @@ 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"
|
||||
dnsclient = "0.1"
|
||||
lru = "0.6"
|
||||
derive_more = "0.99.16"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
|
||||
+5
-2
@@ -23,9 +23,12 @@ 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"]
|
||||
|
||||
# Bootstrap DNS-servers to resolve domains of DoH providers
|
||||
bootstraps = ["9.9.9.9:53", "94.140.14.140:53"]
|
||||
|
||||
# Hosts file support (resolve local names or block ads)
|
||||
#hosts = ["system", "adblock.txt"]
|
||||
|
||||
@@ -44,7 +44,7 @@ impl DnsFilter for BlockchainFilter {
|
||||
subdomain = String::from(parts[2]);
|
||||
}
|
||||
}
|
||||
trace!("Searching record type '{:?}', name '{}' for domain '{}'", &qtype, &subdomain, &search);
|
||||
//trace!("Searching record type '{:?}', name '{}' for domain '{}'", &qtype, &subdomain, &search);
|
||||
|
||||
let data = self.context.lock().unwrap().chain.get_domain_info(&search);
|
||||
let zone = parts[0].to_owned();
|
||||
|
||||
+136
-6
@@ -1,20 +1,25 @@
|
||||
//! 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::net::{SocketAddr, TcpStream, ToSocketAddrs, UdpSocket, IpAddr};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::{sleep, Builder};
|
||||
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};
|
||||
use dnsclient::UpstreamServer;
|
||||
use lru::LruCache;
|
||||
|
||||
#[derive(Debug, Display, From, Error)]
|
||||
pub enum ClientError {
|
||||
@@ -38,7 +43,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 +342,136 @@ 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 fn new(bootstraps: Vec<String>) -> Self {
|
||||
let client_name = format!("ALFIS/{}", env!("CARGO_PKG_VERSION"));
|
||||
let servers = bootstraps
|
||||
.iter()
|
||||
.filter_map(|addr| addr.parse().ok())
|
||||
.map(|addr: SocketAddr| UpstreamServer::new(addr.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
trace!("Using bootstraps: {:?}", &servers);
|
||||
|
||||
let dns_client = dnsclient::sync::DNSClient::new(servers);
|
||||
let cache: LruCache<String, Vec<SocketAddr>> = LruCache::new(10);
|
||||
let cache = RwLock::new(cache);
|
||||
|
||||
let agent = ureq::AgentBuilder::new()
|
||||
.user_agent(&client_name)
|
||||
.timeout(std::time::Duration::from_secs(3))
|
||||
.resolver(move |addr: &str| {
|
||||
let addr = match addr.find(":") {
|
||||
Some(index) => addr[0..index].to_string(),
|
||||
None => addr.to_string()
|
||||
};
|
||||
trace!("Resolving {}", addr);
|
||||
if let Some(addrs) = cache.write().unwrap().get(&addr) {
|
||||
trace!("Found bootstrap ip in cache");
|
||||
return Ok(addrs.clone());
|
||||
}
|
||||
|
||||
let mut result: Vec<IpAddr> = Vec::new();
|
||||
if let Ok(addrs) = dns_client.query_a(&addr) {
|
||||
result.extend(addrs.into_iter().map(|ip| IpAddr::V4(ip)))
|
||||
}
|
||||
if let Ok(addrs) = dns_client.query_aaaa(&addr) {
|
||||
result.extend(addrs.into_iter().map(|ip| IpAddr::V6(ip)));
|
||||
}
|
||||
|
||||
let addrs = result
|
||||
.into_iter()
|
||||
.map(|ip| SocketAddr::new(ip, 443))
|
||||
.collect::<Vec<_>>();
|
||||
trace!("Resolved addresses: {:?}", &addrs);
|
||||
cache.write().unwrap().put(addr, addrs.clone());
|
||||
Ok(addrs)
|
||||
})
|
||||
.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) => {
|
||||
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) {
|
||||
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::*;
|
||||
|
||||
+13
-8
@@ -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,
|
||||
@@ -58,18 +59,19 @@ pub struct ServerContext {
|
||||
|
||||
impl Default for ServerContext {
|
||||
fn default() -> Self {
|
||||
ServerContext::new()
|
||||
ServerContext::new(String::from("0.0.0.0:53"), Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerContext {
|
||||
pub fn new() -> ServerContext {
|
||||
pub fn new(dns_listen: String, bootstraps: Vec<String>) -> ServerContext {
|
||||
ServerContext {
|
||||
authority: Authority::new(),
|
||||
cache: SynchronizedCache::new(),
|
||||
filters: Vec::new(),
|
||||
client: Box::new(DnsNetworkClient::new(10000 + (rand::random::<u16>() % 20000))),
|
||||
dns_listen: String::from("0.0.0.0:53"),
|
||||
old_client: Box::new(DnsNetworkClient::new(10000 + (rand::random::<u16>() % 20000))),
|
||||
doh_client: Box::new(HttpsDnsClient::new(bootstraps)),
|
||||
dns_listen,
|
||||
api_port: 5380,
|
||||
resolve_strategy: ResolveStrategy::Recursive,
|
||||
allow_recursive: true,
|
||||
@@ -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,
|
||||
|
||||
+12
-2
@@ -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 {
|
||||
|
||||
|
||||
+1
-2
@@ -31,9 +31,8 @@ pub fn start_dns_server(context: &Arc<Mutex<Context>>, settings: &Settings) {
|
||||
|
||||
/// Creates DNS-context with all needed settings
|
||||
fn create_server_context(context: Arc<Mutex<Context>>, settings: &Settings) -> Arc<ServerContext> {
|
||||
let mut server_context = ServerContext::new();
|
||||
let mut server_context = ServerContext::new(settings.dns.listen.clone(), settings.dns.bootstraps.clone());
|
||||
server_context.allow_recursive = true;
|
||||
server_context.dns_listen = settings.dns.listen.clone();
|
||||
server_context.resolve_strategy = match settings.dns.forwarders.is_empty() {
|
||||
true => ResolveStrategy::Recursive,
|
||||
false => ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() }
|
||||
|
||||
@@ -227,6 +227,8 @@ fn setup_logger(opt_matches: &Matches) {
|
||||
}
|
||||
let config = ConfigBuilder::new()
|
||||
.add_filter_ignore_str("mio::poll")
|
||||
.add_filter_ignore_str("rustls::client")
|
||||
.add_filter_ignore_str("ureq::")
|
||||
.set_thread_level(LevelFilter::Off)
|
||||
.set_location_level(LevelFilter::Off)
|
||||
.set_target_level(LevelFilter::Error)
|
||||
|
||||
@@ -67,6 +67,8 @@ pub struct Dns {
|
||||
#[serde(default = "default_threads")]
|
||||
pub threads: usize,
|
||||
pub forwarders: Vec<String>,
|
||||
#[serde(default = "default_dns_bootstraps")]
|
||||
pub bootstraps: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub hosts: Vec<String>
|
||||
}
|
||||
@@ -77,6 +79,7 @@ impl Default for Dns {
|
||||
listen: String::from("127.0.0.1:53"),
|
||||
threads: 20,
|
||||
forwarders: vec![String::from("94.140.14.14:53"), String::from("94.140.15.15:53")],
|
||||
bootstraps: default_dns_bootstraps(),
|
||||
hosts: Vec::new()
|
||||
}
|
||||
}
|
||||
@@ -137,4 +140,8 @@ fn default_key_files() -> Vec<String> {
|
||||
String::from("key4.toml"),
|
||||
String::from("key5.toml"),
|
||||
]
|
||||
}
|
||||
|
||||
fn default_dns_bootstraps() -> Vec<String> {
|
||||
vec![String::from("9.9.9.9:53"), String::from("94.140.14.140:53")]
|
||||
}
|
||||
Reference in New Issue
Block a user