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"
|
sqlite = "0.26.0"
|
||||||
uuid = { version = "0.8.2", features = ["serde", "v4"] }
|
uuid = { version = "0.8.2", features = ["serde", "v4"] }
|
||||||
mio = { version = "0.7.13", features = ["os-poll", "net"] }
|
mio = { version = "0.7.13", features = ["os-poll", "net"] }
|
||||||
|
ureq = "2.2"
|
||||||
|
dnsclient = "0.1"
|
||||||
|
lru = "0.6"
|
||||||
derive_more = "0.99.16"
|
derive_more = "0.99.16"
|
||||||
lazy_static = "1.4.0"
|
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
|
# How many threads to spawn by DNS server
|
||||||
threads = 50
|
threads = 50
|
||||||
# AdGuard DNS servers to filter ads and trackers
|
# 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
|
# 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 file support (resolve local names or block ads)
|
||||||
#hosts = ["system", "adblock.txt"]
|
#hosts = ["system", "adblock.txt"]
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ impl DnsFilter for BlockchainFilter {
|
|||||||
subdomain = String::from(parts[2]);
|
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 data = self.context.lock().unwrap().chain.get_domain_info(&search);
|
||||||
let zone = parts[0].to_owned();
|
let zone = parts[0].to_owned();
|
||||||
|
|||||||
+136
-6
@@ -1,20 +1,25 @@
|
|||||||
//! client for sending DNS queries to other servers
|
//! client for sending DNS queries to other servers
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::{Write, Read};
|
||||||
use std::marker::{Send, Sync};
|
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::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::mpsc::{channel, Sender};
|
use std::sync::mpsc::{channel, Sender};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::thread::{sleep, Builder};
|
use std::thread::{sleep, Builder};
|
||||||
use std::time::Duration as SleepDuration;
|
use std::time::Duration as SleepDuration;
|
||||||
|
|
||||||
use chrono::*;
|
use chrono::*;
|
||||||
use derive_more::{Display, Error, From};
|
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::netutil::{read_packet_length, write_packet_length};
|
||||||
use crate::dns::protocol::{DnsPacket, DnsQuestion, QueryType};
|
use crate::dns::protocol::{DnsPacket, DnsQuestion, QueryType};
|
||||||
|
use dnsclient::UpstreamServer;
|
||||||
|
use lru::LruCache;
|
||||||
|
|
||||||
#[derive(Debug, Display, From, Error)]
|
#[derive(Debug, Display, From, Error)]
|
||||||
pub enum ClientError {
|
pub enum ClientError {
|
||||||
@@ -38,7 +43,7 @@ pub trait DnsClient {
|
|||||||
/// The UDP client
|
/// The UDP client
|
||||||
///
|
///
|
||||||
/// This includes a fair bit of synchronization due to the stateless nature of UDP.
|
/// 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
|
/// 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,
|
/// 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.
|
/// 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);
|
return Ok(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Truncated response - resending as TCP");
|
info!("Truncated response - resending as TCP");
|
||||||
self.send_tcp_query(qname, qtype, server, recursive)
|
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)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+13
-8
@@ -7,7 +7,7 @@ use derive_more::{Display, Error, From};
|
|||||||
|
|
||||||
use crate::dns::authority::Authority;
|
use crate::dns::authority::Authority;
|
||||||
use crate::dns::cache::SynchronizedCache;
|
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::filter::DnsFilter;
|
||||||
use crate::dns::resolve::{DnsResolver, ForwardingDnsResolver, RecursiveDnsResolver};
|
use crate::dns::resolve::{DnsResolver, ForwardingDnsResolver, RecursiveDnsResolver};
|
||||||
|
|
||||||
@@ -44,7 +44,8 @@ pub struct ServerContext {
|
|||||||
pub authority: Authority,
|
pub authority: Authority,
|
||||||
pub cache: SynchronizedCache,
|
pub cache: SynchronizedCache,
|
||||||
pub filters: Vec<Box<dyn DnsFilter + Sync + Send>>,
|
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 dns_listen: String,
|
||||||
pub api_port: u16,
|
pub api_port: u16,
|
||||||
pub resolve_strategy: ResolveStrategy,
|
pub resolve_strategy: ResolveStrategy,
|
||||||
@@ -58,18 +59,19 @@ pub struct ServerContext {
|
|||||||
|
|
||||||
impl Default for ServerContext {
|
impl Default for ServerContext {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ServerContext::new()
|
ServerContext::new(String::from("0.0.0.0:53"), Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerContext {
|
impl ServerContext {
|
||||||
pub fn new() -> ServerContext {
|
pub fn new(dns_listen: String, bootstraps: Vec<String>) -> ServerContext {
|
||||||
ServerContext {
|
ServerContext {
|
||||||
authority: Authority::new(),
|
authority: Authority::new(),
|
||||||
cache: SynchronizedCache::new(),
|
cache: SynchronizedCache::new(),
|
||||||
filters: Vec::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))),
|
||||||
dns_listen: String::from("0.0.0.0:53"),
|
doh_client: Box::new(HttpsDnsClient::new(bootstraps)),
|
||||||
|
dns_listen,
|
||||||
api_port: 5380,
|
api_port: 5380,
|
||||||
resolve_strategy: ResolveStrategy::Recursive,
|
resolve_strategy: ResolveStrategy::Recursive,
|
||||||
allow_recursive: true,
|
allow_recursive: true,
|
||||||
@@ -83,7 +85,9 @@ impl ServerContext {
|
|||||||
|
|
||||||
pub fn initialize(&mut self) -> Result<()> {
|
pub fn initialize(&mut self) -> Result<()> {
|
||||||
// Start UDP client thread
|
// Start UDP client thread
|
||||||
self.client.run()?;
|
self.old_client.run()?;
|
||||||
|
// Start DoH client
|
||||||
|
self.doh_client.run()?;
|
||||||
|
|
||||||
// Load authority data
|
// Load authority data
|
||||||
self.authority.load()?;
|
self.authority.load()?;
|
||||||
@@ -117,7 +121,8 @@ pub mod tests {
|
|||||||
authority: Authority::new(),
|
authority: Authority::new(),
|
||||||
cache: SynchronizedCache::new(),
|
cache: SynchronizedCache::new(),
|
||||||
filters: Vec::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"),
|
dns_listen: String::from("0.0.0.0:53"),
|
||||||
api_port: 5380,
|
api_port: 5380,
|
||||||
resolve_strategy: ResolveStrategy::Recursive,
|
resolve_strategy: ResolveStrategy::Recursive,
|
||||||
|
|||||||
+12
-2
@@ -87,7 +87,13 @@ impl DnsResolver for ForwardingDnsResolver {
|
|||||||
let mut random = rand::thread_rng();
|
let mut random = rand::thread_rng();
|
||||||
let upstream = self.upstreams.iter().choose(&mut random).unwrap();
|
let upstream = self.upstreams.iter().choose(&mut random).unwrap();
|
||||||
let result = match self.context.cache.lookup(qname, qtype) {
|
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
|
Some(packet) => packet
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,7 +156,7 @@ impl DnsResolver for RecursiveDnsResolver {
|
|||||||
let ns_copy = ns.clone();
|
let ns_copy = ns.clone();
|
||||||
|
|
||||||
let server = format!("{}:{}", ns_copy.as_str(), 53);
|
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 we've got an actual answer, we're done!
|
||||||
if !response.answers.is_empty() && response.header.rescode == ResultCode::NOERROR {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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
|
/// Creates DNS-context with all needed settings
|
||||||
fn create_server_context(context: Arc<Mutex<Context>>, settings: &Settings) -> Arc<ServerContext> {
|
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.allow_recursive = true;
|
||||||
server_context.dns_listen = settings.dns.listen.clone();
|
|
||||||
server_context.resolve_strategy = match settings.dns.forwarders.is_empty() {
|
server_context.resolve_strategy = match settings.dns.forwarders.is_empty() {
|
||||||
true => ResolveStrategy::Recursive,
|
true => ResolveStrategy::Recursive,
|
||||||
false => ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() }
|
false => ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() }
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ fn setup_logger(opt_matches: &Matches) {
|
|||||||
}
|
}
|
||||||
let config = ConfigBuilder::new()
|
let config = ConfigBuilder::new()
|
||||||
.add_filter_ignore_str("mio::poll")
|
.add_filter_ignore_str("mio::poll")
|
||||||
|
.add_filter_ignore_str("rustls::client")
|
||||||
|
.add_filter_ignore_str("ureq::")
|
||||||
.set_thread_level(LevelFilter::Off)
|
.set_thread_level(LevelFilter::Off)
|
||||||
.set_location_level(LevelFilter::Off)
|
.set_location_level(LevelFilter::Off)
|
||||||
.set_target_level(LevelFilter::Error)
|
.set_target_level(LevelFilter::Error)
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ pub struct Dns {
|
|||||||
#[serde(default = "default_threads")]
|
#[serde(default = "default_threads")]
|
||||||
pub threads: usize,
|
pub threads: usize,
|
||||||
pub forwarders: Vec<String>,
|
pub forwarders: Vec<String>,
|
||||||
|
#[serde(default = "default_dns_bootstraps")]
|
||||||
|
pub bootstraps: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub hosts: Vec<String>
|
pub hosts: Vec<String>
|
||||||
}
|
}
|
||||||
@@ -77,6 +79,7 @@ impl Default for Dns {
|
|||||||
listen: String::from("127.0.0.1:53"),
|
listen: String::from("127.0.0.1:53"),
|
||||||
threads: 20,
|
threads: 20,
|
||||||
forwarders: vec![String::from("94.140.14.14:53"), String::from("94.140.15.15:53")],
|
forwarders: vec![String::from("94.140.14.14:53"), String::from("94.140.15.15:53")],
|
||||||
|
bootstraps: default_dns_bootstraps(),
|
||||||
hosts: Vec::new()
|
hosts: Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,3 +141,7 @@ fn default_key_files() -> Vec<String> {
|
|||||||
String::from("key5.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