use std::io::{Read, Write};
use std::net::{SocketAddr, TcpStream, UdpSocket};
use std::time::{Duration, Instant};
const UDP_MAX_ATTEMPTS: u32 = 2;
const UDP_RETRY_DELAY: Duration = Duration::from_millis(200);
const UDP_TIMEOUT: Duration = Duration::from_secs(3);
const UDP_BUF_SIZE: usize = 4096;
const TCP_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
const TCP_IO_TIMEOUT: Duration = Duration::from_secs(5);
const DNSCRYPT_RESPONSE_MAGIC: &[u8; 8] = b"r6fnvWj8";
pub fn send_dns_query_udp(resolver_ip: SocketAddr, query: &[u8]) -> Result<Vec<u8>, String> {
let socket =
UdpSocket::bind("0.0.0.0:0").map_err(|e| format!("Failed to bind UDP socket: {e}"))?;
socket.set_read_timeout(Some(UDP_TIMEOUT)).ok();
socket.set_write_timeout(Some(UDP_TIMEOUT)).ok();
let mut buf = vec![0u8; UDP_BUF_SIZE];
let mut last_err = String::new();
for attempt in 1..=UDP_MAX_ATTEMPTS {
if let Err(e) = socket.send_to(query, resolver_ip) {
last_err = format!("UDP send failed (attempt {attempt}): {e}");
if attempt < UDP_MAX_ATTEMPTS {
std::thread::sleep(UDP_RETRY_DELAY);
}
continue;
}
let deadline = Instant::now() + UDP_TIMEOUT;
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
last_err = format!("UDP recv timed out (attempt {attempt})");
break;
}
socket.set_read_timeout(Some(remaining)).ok();
match socket.recv_from(&mut buf) {
Ok((n, src)) if src == resolver_ip => return Ok(buf[..n].to_vec()),
Ok(_) => {}
Err(e) => {
last_err = format!("UDP recv failed (attempt {attempt}): {e}");
break;
}
}
}
if attempt < UDP_MAX_ATTEMPTS {
std::thread::sleep(UDP_RETRY_DELAY);
}
}
Err(last_err)
}
pub fn send_dns_query_tcp(resolver_ip: SocketAddr, query: &[u8]) -> Result<Vec<u8>, String> {
let mut stream = TcpStream::connect_timeout(&resolver_ip, TCP_CONNECT_TIMEOUT)
.map_err(|e| format!("TCP connect failed: {e}"))?;
stream.set_read_timeout(Some(TCP_IO_TIMEOUT)).ok();
stream.set_write_timeout(Some(TCP_IO_TIMEOUT)).ok();
let len: u16 = query
.len()
.try_into()
.map_err(|_| "query too large for DNS-over-TCP framing (max 65535 bytes)".to_string())?;
let mut payload = Vec::with_capacity(2 + query.len());
payload.extend_from_slice(&len.to_be_bytes());
payload.extend_from_slice(query);
stream
.write_all(&payload)
.map_err(|e| format!("TCP write payload failed: {e}"))?;
let mut len_buf = [0u8; 2];
stream
.read_exact(&mut len_buf)
.map_err(|e| format!("TCP read len failed: {e}"))?;
let resp_len = u16::from_be_bytes(len_buf) as usize;
let mut resp_buf = vec![0u8; resp_len];
stream
.read_exact(&mut resp_buf)
.map_err(|e| format!("TCP read data failed: {e}"))?;
Ok(resp_buf)
}
pub fn send_dns_query(
resolver_ip: SocketAddr,
query: &[u8],
force_tcp: bool,
) -> Result<Vec<u8>, String> {
if force_tcp {
return send_dns_query_tcp(resolver_ip, query);
}
send_dns_query_udp(resolver_ip, query).map_or_else(
|_| send_dns_query_tcp(resolver_ip, query),
|resp| {
let is_dnscrypt = resp.len() >= 8 && resp[0..8] == *DNSCRYPT_RESPONSE_MAGIC;
let tc_bit_set = resp.len() >= 3 && (resp[2] & 0x02 != 0);
if is_dnscrypt || !tc_bit_set {
Ok(resp)
} else {
send_dns_query_tcp(resolver_ip, query)
}
},
)
}
#[cfg(feature = "tokio")]
pub async fn send_dns_query_udp_async(
resolver_ip: SocketAddr,
query: &[u8],
) -> Result<Vec<u8>, String> {
use tokio::net::UdpSocket;
use tokio::time::timeout;
let socket = UdpSocket::bind("0.0.0.0:0")
.await
.map_err(|e| format!("Failed to bind UDP socket: {e}"))?;
let mut buf = vec![0u8; UDP_BUF_SIZE];
let mut last_err = String::new();
for attempt in 1..=UDP_MAX_ATTEMPTS {
if let Err(e) = socket.send_to(query, resolver_ip).await {
last_err = format!("UDP send failed (attempt {attempt}): {e}");
if attempt < UDP_MAX_ATTEMPTS {
tokio::time::sleep(UDP_RETRY_DELAY).await;
}
continue;
}
let recv_matching = async {
loop {
match socket.recv_from(&mut buf).await {
Ok((n, src)) if src == resolver_ip => return Ok(n),
Ok(_) => {}
Err(e) => return Err(e),
}
}
};
match timeout(UDP_TIMEOUT, recv_matching).await {
Ok(Ok(n)) => return Ok(buf[..n].to_vec()),
Ok(Err(e)) => {
last_err = format!("UDP recv failed (attempt {attempt}): {e}");
if attempt < UDP_MAX_ATTEMPTS {
tokio::time::sleep(UDP_RETRY_DELAY).await;
}
}
Err(_) => {
last_err = format!("UDP query timed out (attempt {attempt})");
if attempt < UDP_MAX_ATTEMPTS {
tokio::time::sleep(UDP_RETRY_DELAY).await;
}
}
}
}
Err(last_err)
}
#[cfg(feature = "tokio")]
pub async fn send_dns_query_tcp_async(
resolver_ip: SocketAddr,
query: &[u8],
) -> Result<Vec<u8>, String> {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::timeout;
let mut stream = timeout(TCP_CONNECT_TIMEOUT, TcpStream::connect(resolver_ip))
.await
.map_err(|_| "TCP connection timeout".to_string())?
.map_err(|e| format!("TCP connect failed: {e}"))?;
let len: u16 = query
.len()
.try_into()
.map_err(|_| "query too large for DNS-over-TCP framing (max 65535 bytes)".to_string())?;
let mut payload = Vec::with_capacity(2 + query.len());
payload.extend_from_slice(&len.to_be_bytes());
payload.extend_from_slice(query);
timeout(TCP_IO_TIMEOUT, stream.write_all(&payload))
.await
.map_err(|_| "TCP write payload timeout".to_string())?
.map_err(|e| format!("TCP write payload failed: {e}"))?;
let mut len_buf = [0u8; 2];
timeout(TCP_IO_TIMEOUT, stream.read_exact(&mut len_buf))
.await
.map_err(|_| "TCP read len timeout".to_string())?
.map_err(|e| format!("TCP read len failed: {e}"))?;
let resp_len = u16::from_be_bytes(len_buf) as usize;
let mut resp_buf = vec![0u8; resp_len];
timeout(TCP_IO_TIMEOUT, stream.read_exact(&mut resp_buf))
.await
.map_err(|_| "TCP read data timeout".to_string())?
.map_err(|e| format!("TCP read data failed: {e}"))?;
Ok(resp_buf)
}
#[cfg(feature = "tokio")]
pub async fn send_dns_query_async(
resolver_ip: SocketAddr,
query: &[u8],
force_tcp: bool,
) -> Result<Vec<u8>, String> {
if force_tcp {
return send_dns_query_tcp_async(resolver_ip, query).await;
}
match send_dns_query_udp_async(resolver_ip, query).await {
Ok(resp) => {
let is_dnscrypt = resp.len() >= 8 && resp[0..8] == *DNSCRYPT_RESPONSE_MAGIC;
let tc_bit_set = resp.len() >= 3 && (resp[2] & 0x02 != 0);
if is_dnscrypt || !tc_bit_set {
Ok(resp)
} else {
send_dns_query_tcp_async(resolver_ip, query).await
}
}
Err(_) => send_dns_query_tcp_async(resolver_ip, query).await,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::net::{IpAddr, Ipv4Addr, TcpListener, UdpSocket};
use std::thread;
#[test]
fn test_fallback_udp_to_tcp() {
let udp_socket = UdpSocket::bind("127.0.0.1:0").unwrap();
let port = udp_socket.local_addr().unwrap().port();
let tcp_listener = TcpListener::bind(("127.0.0.1", port)).unwrap();
let resolver_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
thread::spawn(move || {
let mut buf = [0u8; 1024];
let (_, src) = udp_socket.recv_from(&mut buf).unwrap();
let mut resp = vec![0u8; 12];
resp[0] = buf[0];
resp[1] = buf[1];
resp[2] = 0x02;
udp_socket.send_to(&resp, src).unwrap();
});
thread::spawn(move || {
let (mut stream, _) = tcp_listener.accept().unwrap();
let mut len_buf = [0u8; 2];
stream.read_exact(&mut len_buf).unwrap();
let len = usize::from(u16::from_be_bytes(len_buf));
let mut query = vec![0u8; len];
stream.read_exact(&mut query).unwrap();
let mut resp = vec![0u8; 32];
resp[0..8].copy_from_slice(DNSCRYPT_RESPONSE_MAGIC);
let resp_len = u16::try_from(resp.len()).unwrap().to_be_bytes();
stream.write_all(&resp_len).unwrap();
stream.write_all(&resp).unwrap();
});
let query = b"test query";
let result = send_dns_query(resolver_ip, query, false).unwrap();
assert!(result.len() >= 8);
assert_eq!(&result[0..8], DNSCRYPT_RESPONSE_MAGIC);
}
#[test]
fn test_send_dns_query_force_tcp() {
let tcp_listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = tcp_listener.local_addr().unwrap().port();
let resolver_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
thread::spawn(move || {
let (mut stream, _) = tcp_listener.accept().unwrap();
let mut len_buf = [0u8; 2];
stream.read_exact(&mut len_buf).unwrap();
let len = usize::from(u16::from_be_bytes(len_buf));
let mut query = vec![0u8; len];
stream.read_exact(&mut query).unwrap();
let resp = vec![0xaa; 16];
stream
.write_all(&u16::try_from(resp.len()).unwrap().to_be_bytes())
.unwrap();
stream.write_all(&resp).unwrap();
});
let result = send_dns_query(resolver_ip, b"forced tcp query", true).unwrap();
assert_eq!(result, vec![0xaa; 16]);
}
#[test]
fn test_send_dns_query_tcp_rejects_oversized_query() {
let tcp_listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = tcp_listener.local_addr().unwrap().port();
let resolver_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
thread::spawn(move || {
let _ = tcp_listener.accept();
});
let oversized = vec![0u8; 65536];
let err = send_dns_query_tcp(resolver_ip, &oversized).unwrap_err();
assert!(err.contains("too large"));
}
#[test]
fn test_send_dns_query_udp_rejects_unmatched_source() {
let udp_socket = UdpSocket::bind("127.0.0.1:0").unwrap();
let port = udp_socket.local_addr().unwrap().port();
let resolver_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
thread::spawn(move || {
let mut buf = [0u8; 1024];
let (_, client_addr) = udp_socket.recv_from(&mut buf).unwrap();
let spoofer = UdpSocket::bind("127.0.0.1:0").unwrap();
spoofer.send_to(b"spoofed", client_addr).unwrap();
thread::sleep(Duration::from_millis(100));
let mut resp = vec![0u8; 12];
resp[0] = buf[0];
resp[1] = buf[1];
udp_socket.send_to(&resp, client_addr).unwrap();
});
let result = send_dns_query_udp(resolver_ip, b"query").unwrap();
assert_eq!(result.len(), 12);
}
#[cfg(feature = "tokio")]
mod async_tests {
use super::*;
#[tokio::test]
async fn test_async_fallback_udp_to_tcp() {
let udp_socket = UdpSocket::bind("127.0.0.1:0").unwrap();
let port = udp_socket.local_addr().unwrap().port();
let tcp_listener = TcpListener::bind(("127.0.0.1", port)).unwrap();
let resolver_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
thread::spawn(move || {
let mut buf = [0u8; 1024];
let (_, src) = udp_socket.recv_from(&mut buf).unwrap();
let mut resp = vec![0u8; 12];
resp[0] = buf[0];
resp[1] = buf[1];
resp[2] = 0x02; udp_socket.send_to(&resp, src).unwrap();
});
thread::spawn(move || {
let (mut stream, _) = tcp_listener.accept().unwrap();
let mut len_buf = [0u8; 2];
stream.read_exact(&mut len_buf).unwrap();
let len = usize::from(u16::from_be_bytes(len_buf));
let mut query = vec![0u8; len];
stream.read_exact(&mut query).unwrap();
let mut resp = vec![0u8; 32];
resp[0..8].copy_from_slice(DNSCRYPT_RESPONSE_MAGIC);
stream
.write_all(&u16::try_from(resp.len()).unwrap().to_be_bytes())
.unwrap();
stream.write_all(&resp).unwrap();
});
let result = send_dns_query_async(resolver_ip, b"test query", false)
.await
.unwrap();
assert_eq!(&result[0..8], DNSCRYPT_RESPONSE_MAGIC);
}
#[tokio::test]
async fn test_async_send_dns_query_force_tcp() {
let tcp_listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = tcp_listener.local_addr().unwrap().port();
let resolver_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
thread::spawn(move || {
let (mut stream, _) = tcp_listener.accept().unwrap();
let mut len_buf = [0u8; 2];
stream.read_exact(&mut len_buf).unwrap();
let len = usize::from(u16::from_be_bytes(len_buf));
let mut query = vec![0u8; len];
stream.read_exact(&mut query).unwrap();
let resp = vec![0xbb; 16];
stream
.write_all(&u16::try_from(resp.len()).unwrap().to_be_bytes())
.unwrap();
stream.write_all(&resp).unwrap();
});
let result = send_dns_query_async(resolver_ip, b"forced tcp query", true)
.await
.unwrap();
assert_eq!(result, vec![0xbb; 16]);
}
#[tokio::test]
async fn test_async_send_dns_query_tcp_rejects_oversized_query() {
let tcp_listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = tcp_listener.local_addr().unwrap().port();
let resolver_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
thread::spawn(move || {
let _ = tcp_listener.accept();
});
let oversized = vec![0u8; 65536];
let err = send_dns_query_tcp_async(resolver_ip, &oversized)
.await
.unwrap_err();
assert!(err.contains("too large"));
}
#[tokio::test]
async fn test_async_send_dns_query_udp_rejects_unmatched_source() {
let udp_socket = UdpSocket::bind("127.0.0.1:0").unwrap();
let port = udp_socket.local_addr().unwrap().port();
let resolver_ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
thread::spawn(move || {
let mut buf = [0u8; 1024];
let (_, client_addr) = udp_socket.recv_from(&mut buf).unwrap();
let spoofer = UdpSocket::bind("127.0.0.1:0").unwrap();
spoofer.send_to(b"spoofed", client_addr).unwrap();
thread::sleep(Duration::from_millis(100));
let mut resp = vec![0u8; 12];
resp[0] = buf[0];
resp[1] = buf[1];
udp_socket.send_to(&resp, client_addr).unwrap();
});
let result = send_dns_query_udp_async(resolver_ip, b"query")
.await
.unwrap();
assert_eq!(result.len(), 12);
}
}
}