Added DNS-over-HTTPS support for forwarded queries.

pull/156/head
Revertron 3 years ago
parent eed0adad0c
commit 08328c95fe

842
Cargo.lock generated

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"

@ -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();

@ -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::*;

@ -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,

@ -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 {

@ -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")]
}
Loading…
Cancel
Save