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