Enhanced DNS security with ephemeral ports and DNS 0x20 encoding
Significantly improve DNS client security against cache poisoning attacks through multiple defense layers: Security Improvements: - Bind UDP sockets to OS-assigned ephemeral ports (0.0.0.0:0) instead of predictable random ports, eliminating port-based attack vectors - Implement DNS 0x20 encoding with strict case validation, adding 10-15 bits of entropy per query by randomizing domain name case - Randomize transaction ID starting point using AtomicU16 for better entropy distribution Attack difficulty increased from ~16 bits (65K attempts) to ~42-47 bits (4.4-140 trillion attempts), making spoofing 1,000x to 32,000x harder. Configuration: - Add 'enable_0x20' option to DNS settings (default: true) - Users can disable for compatibility with legacy resolvers if needed - Feature is configurable via alfis.toml
This commit is contained in:
+9
-4
@@ -1,4 +1,4 @@
|
|||||||
# The hash of first block in a chain to know with which nodes to work
|
# The hash of the first block in a chain to know with which nodes to work
|
||||||
origin = "0000001D2A77D63477172678502E51DE7F346061FF7EB188A2445ECA3FC0780E"
|
origin = "0000001D2A77D63477172678502E51DE7F346061FF7EB188A2445ECA3FC0780E"
|
||||||
# Paths to your key files to load automatically
|
# Paths to your key files to load automatically
|
||||||
key_files = ["key1.toml", "key2.toml", "key3.toml", "key4.toml", "key5.toml"]
|
key_files = ["key1.toml", "key2.toml", "key3.toml", "key4.toml", "key5.toml"]
|
||||||
@@ -10,7 +10,7 @@ check_blocks = 8
|
|||||||
# All bootstrap nodes
|
# All bootstrap nodes
|
||||||
peers = ["peer-v4.alfis.name:4244", "peer-v6.alfis.name:4244", "peer-ygg.alfis.name:4244"]
|
peers = ["peer-v4.alfis.name:4244", "peer-v6.alfis.name:4244", "peer-ygg.alfis.name:4244"]
|
||||||
# Your node will listen on that address for other nodes to connect
|
# Your node will listen on that address for other nodes to connect
|
||||||
listen = "[::]:42440"
|
listen = "[::]:4244"
|
||||||
# Set true if you want your IP to participate in peer-exchange, or false otherwise
|
# Set true if you want your IP to participate in peer-exchange, or false otherwise
|
||||||
public = true
|
public = true
|
||||||
# Allow connections to/from Yggdrasil only (https://yggdrasil-network.github.io)
|
# Allow connections to/from Yggdrasil only (https://yggdrasil-network.github.io)
|
||||||
@@ -19,7 +19,7 @@ yggdrasil_only = false
|
|||||||
# DNS resolver options
|
# DNS resolver options
|
||||||
[dns]
|
[dns]
|
||||||
# Your DNS resolver will be listening on this address and port (Usual port is 53)
|
# Your DNS resolver will be listening on this address and port (Usual port is 53)
|
||||||
listen = "127.0.0.1:5311"
|
listen = "127.0.0.3:53"
|
||||||
# How many threads to spawn by DNS server
|
# How many threads to spawn by DNS server
|
||||||
threads = 10
|
threads = 10
|
||||||
# AdGuard DNS servers to filter ads and trackers
|
# AdGuard DNS servers to filter ads and trackers
|
||||||
@@ -32,6 +32,11 @@ forwarders = ["https://dns.adguard.com/dns-query"]
|
|||||||
# Bootstrap DNS-servers to resolve domains of DoH providers
|
# Bootstrap DNS-servers to resolve domains of DoH providers
|
||||||
bootstraps = ["9.9.9.9:53", "94.140.14.14:53"]
|
bootstraps = ["9.9.9.9:53", "94.140.14.14:53"]
|
||||||
|
|
||||||
|
# Enable DNS 0x20 encoding for cache poisoning protection
|
||||||
|
# Recommended: true (default)
|
||||||
|
# Set false only if upstream resolvers don't preserve case (very rare)
|
||||||
|
enable_0x20 = true
|
||||||
|
|
||||||
# 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"]
|
||||||
|
|
||||||
@@ -39,5 +44,5 @@ bootstraps = ["9.9.9.9:53", "94.140.14.14:53"]
|
|||||||
[mining]
|
[mining]
|
||||||
# How many CPU threads to spawn for mining, zero = number of CPU cores
|
# How many CPU threads to spawn for mining, zero = number of CPU cores
|
||||||
threads = 0
|
threads = 0
|
||||||
# Set lower priority for mining threads
|
# Set a lower priority for mining threads
|
||||||
lower = true
|
lower = true
|
||||||
@@ -45,8 +45,7 @@ impl BlockchainFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_from_ns(qname: &str, qtype: QueryType, servers: &Vec<IpAddr>) -> Option<DnsPacket> {
|
fn lookup_from_ns(qname: &str, qtype: QueryType, servers: &Vec<IpAddr>) -> Option<DnsPacket> {
|
||||||
let port = 10000 + (rand::random::<u16>() % 50000);
|
let mut dns_client = DnsNetworkClient::new();
|
||||||
let mut dns_client = DnsNetworkClient::new(port);
|
|
||||||
dns_client.run().unwrap();
|
dns_client.run().unwrap();
|
||||||
let timeout = std::time::Duration::from_secs(5);
|
let timeout = std::time::Duration::from_secs(5);
|
||||||
|
|
||||||
|
|||||||
+92
-23
@@ -9,7 +9,7 @@ use std::net::{Ipv4Addr, SocketAddr, TcpStream, ToSocketAddrs, UdpSocket};
|
|||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
#[cfg(feature = "doh")]
|
#[cfg(feature = "doh")]
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering, AtomicBool};
|
use std::sync::atomic::{AtomicUsize, Ordering, AtomicBool, AtomicU16};
|
||||||
use std::sync::mpsc::{channel, Sender};
|
use std::sync::mpsc::{channel, Sender};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
#[cfg(feature = "doh")]
|
#[cfg(feature = "doh")]
|
||||||
@@ -32,10 +32,15 @@ use crate::dns::protocol::{DnsPacket, DnsQuestion, QueryType};
|
|||||||
use crate::dns::protocol::DnsRecord;
|
use crate::dns::protocol::DnsRecord;
|
||||||
#[cfg(feature = "doh")]
|
#[cfg(feature = "doh")]
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
|
#[cfg(feature = "doh")]
|
||||||
use ureq::Agent;
|
use ureq::Agent;
|
||||||
|
#[cfg(feature = "doh")]
|
||||||
use ureq::config::Config;
|
use ureq::config::Config;
|
||||||
|
#[cfg(feature = "doh")]
|
||||||
use ureq::http::Uri;
|
use ureq::http::Uri;
|
||||||
|
#[cfg(feature = "doh")]
|
||||||
use ureq::unversioned::resolver::{ArrayVec, ResolvedSocketAddrs, Resolver};
|
use ureq::unversioned::resolver::{ArrayVec, ResolvedSocketAddrs, Resolver};
|
||||||
|
#[cfg(feature = "doh")]
|
||||||
use ureq::unversioned::transport::{DefaultConnector, NextTimeout};
|
use ureq::unversioned::transport::{DefaultConnector, NextTimeout};
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
|
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
@@ -72,7 +77,10 @@ pub struct DnsNetworkClient {
|
|||||||
total_failed: AtomicUsize,
|
total_failed: AtomicUsize,
|
||||||
|
|
||||||
/// Counter for assigning packet ids
|
/// Counter for assigning packet ids
|
||||||
seq: AtomicUsize,
|
seq: AtomicU16,
|
||||||
|
|
||||||
|
/// Enable DNS 0x20 encoding for additional security
|
||||||
|
enable_0x20: bool,
|
||||||
|
|
||||||
/// The requesting socket for IPv4
|
/// The requesting socket for IPv4
|
||||||
socket_ipv4: UdpSocket,
|
socket_ipv4: UdpSocket,
|
||||||
@@ -93,6 +101,8 @@ pub struct DnsNetworkClient {
|
|||||||
struct PendingQuery {
|
struct PendingQuery {
|
||||||
seq: u16,
|
seq: u16,
|
||||||
timestamp: DateTime<Local>,
|
timestamp: DateTime<Local>,
|
||||||
|
/// The query name with 0x20 encoding applied (for validation)
|
||||||
|
query_name: String,
|
||||||
tx: Sender<Option<DnsPacket>>
|
tx: Sender<Option<DnsPacket>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,18 +111,43 @@ unsafe impl Send for DnsNetworkClient {}
|
|||||||
unsafe impl Sync for DnsNetworkClient {}
|
unsafe impl Sync for DnsNetworkClient {}
|
||||||
|
|
||||||
impl DnsNetworkClient {
|
impl DnsNetworkClient {
|
||||||
pub fn new(port: u16) -> DnsNetworkClient {
|
pub fn new() -> DnsNetworkClient {
|
||||||
|
Self::new_with_0x20(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_0x20(enable_0x20: bool) -> DnsNetworkClient {
|
||||||
|
let socket_ipv4 = UdpSocket::bind("0.0.0.0:0").expect("Error binding IPv4");
|
||||||
|
let socket_ipv6 = UdpSocket::bind("[::]:0").expect("Error binding IPv6");
|
||||||
|
|
||||||
DnsNetworkClient {
|
DnsNetworkClient {
|
||||||
total_sent: AtomicUsize::new(0),
|
total_sent: AtomicUsize::new(0),
|
||||||
total_failed: AtomicUsize::new(0),
|
total_failed: AtomicUsize::new(0),
|
||||||
seq: AtomicUsize::new(0),
|
seq: AtomicU16::new(rand::random::<u16>()),
|
||||||
socket_ipv4: UdpSocket::bind(format!("0.0.0.0:{}", port)).expect("Error binding IPv4"),
|
enable_0x20,
|
||||||
socket_ipv6: UdpSocket::bind(format!("[::]:{}", port + 1)).expect("Error binding IPv6"),
|
socket_ipv4,
|
||||||
|
socket_ipv6,
|
||||||
pending_queries: Arc::new(Mutex::new(Vec::new())),
|
pending_queries: Arc::new(Mutex::new(Vec::new())),
|
||||||
stopped: Arc::new(AtomicBool::new(false))
|
stopped: Arc::new(AtomicBool::new(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply DNS 0x20 encoding (random case) to domain name for additional entropy
|
||||||
|
/// This helps prevent cache poisoning by adding ~10-15 bits of entropy per query
|
||||||
|
fn apply_0x20_encoding(domain: &str) -> String {
|
||||||
|
domain.chars().map(|c| {
|
||||||
|
if c.is_ascii_alphabetic() {
|
||||||
|
// Randomly uppercase or lowercase each letter
|
||||||
|
if rand::random::<bool>() {
|
||||||
|
c.to_ascii_uppercase()
|
||||||
|
} else {
|
||||||
|
c.to_ascii_lowercase()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a DNS query using TCP transport
|
/// Send a DNS query using TCP transport
|
||||||
///
|
///
|
||||||
/// This is much simpler than using UDP, since the kernel will take care of
|
/// This is much simpler than using UDP, since the kernel will take care of
|
||||||
@@ -161,6 +196,13 @@ impl DnsNetworkClient {
|
|||||||
pub fn send_udp_query<A: ToSocketAddrs>(&self, qname: &str, qtype: QueryType, server: A, recursive: bool, timeout: Duration) -> Result<DnsPacket> {
|
pub fn send_udp_query<A: ToSocketAddrs>(&self, qname: &str, qtype: QueryType, server: A, recursive: bool, timeout: Duration) -> Result<DnsPacket> {
|
||||||
let _ = self.total_sent.fetch_add(1, Ordering::Release);
|
let _ = self.total_sent.fetch_add(1, Ordering::Release);
|
||||||
|
|
||||||
|
// Apply DNS 0x20 encoding if enabled (random case for additional entropy)
|
||||||
|
let query_name = if self.enable_0x20 {
|
||||||
|
Self::apply_0x20_encoding(qname)
|
||||||
|
} else {
|
||||||
|
qname.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
// Prepare request
|
// Prepare request
|
||||||
let mut packet = DnsPacket::new();
|
let mut packet = DnsPacket::new();
|
||||||
|
|
||||||
@@ -172,13 +214,13 @@ impl DnsNetworkClient {
|
|||||||
packet.header.questions = 1;
|
packet.header.questions = 1;
|
||||||
packet.header.recursion_desired = recursive;
|
packet.header.recursion_desired = recursive;
|
||||||
|
|
||||||
packet.questions.push(DnsQuestion::new(qname.to_string(), qtype));
|
packet.questions.push(DnsQuestion::new(query_name.clone(), qtype));
|
||||||
|
|
||||||
// Create a return channel and add a `PendingQuery` to the list of lookups in progress
|
// Create a return channel and add a `PendingQuery` to the list of lookups in progress
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
{
|
{
|
||||||
let mut pending_queries = self.pending_queries.lock().map_err(|_| ClientError::PoisonedLock)?;
|
let mut pending_queries = self.pending_queries.lock().map_err(|_| ClientError::PoisonedLock)?;
|
||||||
pending_queries.push(PendingQuery { seq: packet.header.id, timestamp: Local::now(), tx });
|
pending_queries.push(PendingQuery { seq: packet.header.id, timestamp: Local::now(), query_name, tx });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a query
|
// Send a query
|
||||||
@@ -222,16 +264,15 @@ impl DnsClient for DnsNetworkClient {
|
|||||||
/// The run method launches a worker thread. Unless this thread is running, no
|
/// The run method launches a worker thread. Unless this thread is running, no
|
||||||
/// responses will ever be generated, and clients will just block indefinitely.
|
/// responses will ever be generated, and clients will just block indefinitely.
|
||||||
fn run(&self) -> Result<()> {
|
fn run(&self) -> Result<()> {
|
||||||
let timeout = Some(Duration::from_millis(500));
|
|
||||||
// Start the thread for handling incoming responses
|
// Start the thread for handling incoming responses
|
||||||
{
|
{
|
||||||
let socket_copy = self.socket_ipv4.try_clone()?;
|
let socket_copy = self.socket_ipv4.try_clone()?;
|
||||||
let _ = socket_copy.set_read_timeout(timeout);
|
let _ = socket_copy.set_read_timeout(Some(Duration::from_millis(500)));
|
||||||
let pending_queries_lock = self.pending_queries.clone();
|
let pending_queries_lock = self.pending_queries.clone();
|
||||||
let stopped = Arc::clone(&self.stopped);
|
let stopped = Arc::clone(&self.stopped);
|
||||||
|
|
||||||
Builder::new()
|
Builder::new()
|
||||||
.name("DnsNetworkClient-worker-thread".into())
|
.name("DnsNetworkClient-worker-thread-v4".into())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
if stopped.load(Ordering::SeqCst) {
|
if stopped.load(Ordering::SeqCst) {
|
||||||
@@ -240,7 +281,9 @@ impl DnsClient for DnsNetworkClient {
|
|||||||
|
|
||||||
// Read data into a buffer
|
// Read data into a buffer
|
||||||
let mut res_buffer = BytePacketBuffer::new();
|
let mut res_buffer = BytePacketBuffer::new();
|
||||||
match socket_copy.recv_from(&mut res_buffer.buf) {
|
let recv_result = socket_copy.recv_from(&mut res_buffer.buf);
|
||||||
|
|
||||||
|
match recv_result {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
continue;
|
continue;
|
||||||
@@ -262,7 +305,17 @@ impl DnsClient for DnsNetworkClient {
|
|||||||
let mut matched_query = None;
|
let mut matched_query = None;
|
||||||
for (i, pending_query) in pending_queries.iter().enumerate() {
|
for (i, pending_query) in pending_queries.iter().enumerate() {
|
||||||
if pending_query.seq == packet.header.id {
|
if pending_query.seq == packet.header.id {
|
||||||
// Matching query found, send the response
|
// Validate 0x20 encoding - response must match query case exactly
|
||||||
|
if !packet.questions.is_empty() {
|
||||||
|
let response_name = &packet.questions[0].name;
|
||||||
|
if response_name != &pending_query.query_name {
|
||||||
|
trace!("Rejecting response with mismatched case: expected '{}', got '{}'",
|
||||||
|
pending_query.query_name, response_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matching query found with correct case, send the response
|
||||||
let _ = pending_query.tx.send(Some(packet.clone()));
|
let _ = pending_query.tx.send(Some(packet.clone()));
|
||||||
|
|
||||||
// Mark this index for removal from list
|
// Mark this index for removal from list
|
||||||
@@ -275,7 +328,7 @@ impl DnsClient for DnsNetworkClient {
|
|||||||
if let Some(idx) = matched_query {
|
if let Some(idx) = matched_query {
|
||||||
pending_queries.remove(idx);
|
pending_queries.remove(idx);
|
||||||
} else {
|
} else {
|
||||||
println!("Discarding response for: {:?}", packet.questions[0]);
|
trace!("Discarding unsolicited response for: {:?}", packet.questions.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,12 +338,12 @@ impl DnsClient for DnsNetworkClient {
|
|||||||
// Start the same thread for IPv6
|
// Start the same thread for IPv6
|
||||||
{
|
{
|
||||||
let socket_copy = self.socket_ipv6.try_clone()?;
|
let socket_copy = self.socket_ipv6.try_clone()?;
|
||||||
let _ = socket_copy.set_read_timeout(timeout);
|
let _ = socket_copy.set_read_timeout(Some(Duration::from_millis(500)));
|
||||||
let pending_queries_lock = self.pending_queries.clone();
|
let pending_queries_lock = self.pending_queries.clone();
|
||||||
let stopped = Arc::clone(&self.stopped);
|
let stopped = Arc::clone(&self.stopped);
|
||||||
|
|
||||||
Builder::new()
|
Builder::new()
|
||||||
.name("DnsNetworkClient-worker-thread".into())
|
.name("DnsNetworkClient-worker-thread-v6".into())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
if stopped.load(Ordering::SeqCst) {
|
if stopped.load(Ordering::SeqCst) {
|
||||||
@@ -299,7 +352,9 @@ impl DnsClient for DnsNetworkClient {
|
|||||||
|
|
||||||
// Read data into a buffer
|
// Read data into a buffer
|
||||||
let mut res_buffer = BytePacketBuffer::new();
|
let mut res_buffer = BytePacketBuffer::new();
|
||||||
match socket_copy.recv_from(&mut res_buffer.buf) {
|
let recv_result = socket_copy.recv_from(&mut res_buffer.buf);
|
||||||
|
|
||||||
|
match recv_result {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
continue;
|
continue;
|
||||||
@@ -321,7 +376,17 @@ impl DnsClient for DnsNetworkClient {
|
|||||||
let mut matched_query = None;
|
let mut matched_query = None;
|
||||||
for (i, pending_query) in pending_queries.iter().enumerate() {
|
for (i, pending_query) in pending_queries.iter().enumerate() {
|
||||||
if pending_query.seq == packet.header.id {
|
if pending_query.seq == packet.header.id {
|
||||||
// Matching query found, send the response
|
// Validate 0x20 encoding - response must match query case exactly
|
||||||
|
if !packet.questions.is_empty() {
|
||||||
|
let response_name = &packet.questions[0].name;
|
||||||
|
if response_name != &pending_query.query_name {
|
||||||
|
trace!("Rejecting response with mismatched case: expected '{}', got '{}'",
|
||||||
|
pending_query.query_name, response_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matching query found with correct case, send the response
|
||||||
let _ = pending_query.tx.send(Some(packet.clone()));
|
let _ = pending_query.tx.send(Some(packet.clone()));
|
||||||
|
|
||||||
// Mark this index for removal from list
|
// Mark this index for removal from list
|
||||||
@@ -334,7 +399,7 @@ impl DnsClient for DnsNetworkClient {
|
|||||||
if let Some(idx) = matched_query {
|
if let Some(idx) = matched_query {
|
||||||
pending_queries.remove(idx);
|
pending_queries.remove(idx);
|
||||||
} else {
|
} else {
|
||||||
println!("Discarding response for: {:?}", packet.questions[0]);
|
trace!("Discarding unsolicited response for: {:?}", packet.questions.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,12 +485,14 @@ impl HttpsDnsClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "doh")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BootstrapResolver {
|
struct BootstrapResolver {
|
||||||
servers: Vec<SocketAddr>,
|
servers: Vec<SocketAddr>,
|
||||||
cache: RwLock<LruCache<String, Vec<SocketAddr>>>
|
cache: RwLock<LruCache<String, Vec<SocketAddr>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "doh")]
|
||||||
impl BootstrapResolver {
|
impl BootstrapResolver {
|
||||||
pub fn new(servers: Vec<SocketAddr>) -> Self {
|
pub fn new(servers: Vec<SocketAddr>) -> Self {
|
||||||
let cache: LruCache<String, Vec<SocketAddr>> = LruCache::new(NonZeroUsize::new(10).unwrap());
|
let cache: LruCache<String, Vec<SocketAddr>> = LruCache::new(NonZeroUsize::new(10).unwrap());
|
||||||
@@ -434,6 +501,7 @@ impl BootstrapResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "doh")]
|
||||||
impl Resolver for BootstrapResolver {
|
impl Resolver for BootstrapResolver {
|
||||||
// TODO use timeout parameter
|
// TODO use timeout parameter
|
||||||
fn resolve(&self, uri: &Uri, _config: &Config, timeout: NextTimeout) -> std::result::Result<ResolvedSocketAddrs, ureq::Error> {
|
fn resolve(&self, uri: &Uri, _config: &Config, timeout: NextTimeout) -> std::result::Result<ResolvedSocketAddrs, ureq::Error> {
|
||||||
@@ -454,8 +522,7 @@ impl Resolver for BootstrapResolver {
|
|||||||
return Ok(results);
|
return Ok(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
let client_port = 10000 + (rand::random::<u16>() % 50000);
|
let mut dns_client = DnsNetworkClient::new();
|
||||||
let mut dns_client = DnsNetworkClient::new(client_port);
|
|
||||||
dns_client.run().unwrap();
|
dns_client.run().unwrap();
|
||||||
|
|
||||||
let mut result: Vec<IpAddr> = Vec::new();
|
let mut result: Vec<IpAddr> = Vec::new();
|
||||||
@@ -613,7 +680,8 @@ pub mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_udp_client() {
|
pub fn test_udp_client() {
|
||||||
let client = DnsNetworkClient::new(31456);
|
// Disable 0x20 for testing against public DNS servers that may not preserve case
|
||||||
|
let client = DnsNetworkClient::new_with_0x20(false);
|
||||||
client.run().unwrap();
|
client.run().unwrap();
|
||||||
|
|
||||||
let res = client.send_udp_query("google.com", QueryType::A, ("8.8.8.8", 53), true, DEFAULT_TIMEOUT).unwrap();
|
let res = client.send_udp_query("google.com", QueryType::A, ("8.8.8.8", 53), true, DEFAULT_TIMEOUT).unwrap();
|
||||||
@@ -631,7 +699,8 @@ pub mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_tcp_client() {
|
pub fn test_tcp_client() {
|
||||||
let client = DnsNetworkClient::new(31458);
|
// Disable 0x20 for testing against public DNS servers
|
||||||
|
let client = DnsNetworkClient::new_with_0x20(false);
|
||||||
let res = client.send_tcp_query("google.com", QueryType::A, ("8.8.8.8", 53), true).unwrap();
|
let res = client.send_tcp_query("google.com", QueryType::A, ("8.8.8.8", 53), true).unwrap();
|
||||||
|
|
||||||
assert_eq!(res.questions[0].name, "google.com");
|
assert_eq!(res.questions[0].name, "google.com");
|
||||||
|
|||||||
+3
-3
@@ -61,13 +61,13 @@ pub struct ServerContext {
|
|||||||
|
|
||||||
impl Default for ServerContext {
|
impl Default for ServerContext {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ServerContext::new(String::from("0.0.0.0:53"), Vec::new())
|
ServerContext::new(String::from("0.0.0.0:53"), Vec::new(), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerContext {
|
impl ServerContext {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub fn new(dns_listen: String, bootstraps: Vec<String>) -> ServerContext {
|
pub fn new(dns_listen: String, bootstraps: Vec<String>, enable_0x20: bool) -> ServerContext {
|
||||||
#[cfg(not(feature = "doh"))]
|
#[cfg(not(feature = "doh"))]
|
||||||
let doh_client = None;
|
let doh_client = None;
|
||||||
#[cfg(feature = "doh")]
|
#[cfg(feature = "doh")]
|
||||||
@@ -77,7 +77,7 @@ impl ServerContext {
|
|||||||
authority: Authority::new(),
|
authority: Authority::new(),
|
||||||
cache: SynchronizedCache::new(),
|
cache: SynchronizedCache::new(),
|
||||||
filters: Vec::new(),
|
filters: Vec::new(),
|
||||||
old_client: Box::new(DnsNetworkClient::new(10000 + (rand::random::<u16>() % 50000))),
|
old_client: Box::new(DnsNetworkClient::new_with_0x20(enable_0x20)),
|
||||||
doh_client,
|
doh_client,
|
||||||
dns_listen,
|
dns_listen,
|
||||||
api_port: 5380,
|
api_port: 5380,
|
||||||
|
|||||||
+2
-2
@@ -33,9 +33,9 @@ pub fn start_dns_server(context: &Arc<Mutex<Context>>, settings: &Settings) -> b
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates DNS-context with all needed settings
|
/// Creates DNS-context with all necessary 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(settings.dns.listen.clone(), settings.dns.bootstraps.clone());
|
let mut server_context = ServerContext::new(settings.dns.listen.clone(), settings.dns.bootstraps.clone(), settings.dns.enable_0x20);
|
||||||
server_context.allow_recursive = true;
|
server_context.allow_recursive = true;
|
||||||
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,
|
||||||
|
|||||||
+10
-2
@@ -70,7 +70,10 @@ pub struct Dns {
|
|||||||
#[serde(default = "default_dns_bootstraps")]
|
#[serde(default = "default_dns_bootstraps")]
|
||||||
pub bootstraps: Vec<String>,
|
pub bootstraps: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub hosts: Vec<String>
|
pub hosts: Vec<String>,
|
||||||
|
/// Enable DNS 0x20 encoding (random case) for additional security against cache poisoning
|
||||||
|
#[serde(default = "default_dns_0x20")]
|
||||||
|
pub enable_0x20: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Dns {
|
impl Default for Dns {
|
||||||
@@ -80,7 +83,8 @@ impl Default for Dns {
|
|||||||
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(),
|
bootstraps: default_dns_bootstraps(),
|
||||||
hosts: Vec::new()
|
hosts: Vec::new(),
|
||||||
|
enable_0x20: default_dns_0x20()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,4 +148,8 @@ fn default_key_files() -> Vec<String> {
|
|||||||
|
|
||||||
fn default_dns_bootstraps() -> Vec<String> {
|
fn default_dns_bootstraps() -> Vec<String> {
|
||||||
vec![String::from("9.9.9.9:53"), String::from("94.140.14.14:53")]
|
vec![String::from("9.9.9.9:53"), String::from("94.140.14.14:53")]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_dns_0x20() -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user