use std::io;
use std::net::{IpAddr, SocketAddr, UdpSocket};
use std::time::Duration;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum RecordType {
A,
Aaaa,
}
impl RecordType {
fn qtype(self) -> u16 {
match self {
RecordType::A => 1,
RecordType::Aaaa => 28,
}
}
}
pub mod wire {
use super::RecordType;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub fn build_query(id: u16, name: &str, rtype: RecordType) -> Option<Vec<u8>> {
let qname = encode_name(name)?;
let mut pkt = Vec::with_capacity(12 + qname.len() + 4);
pkt.extend_from_slice(&id.to_be_bytes());
pkt.push(0x01); pkt.push(0x00); pkt.extend_from_slice(&1u16.to_be_bytes()); pkt.extend_from_slice(&0u16.to_be_bytes()); pkt.extend_from_slice(&0u16.to_be_bytes()); pkt.extend_from_slice(&0u16.to_be_bytes()); pkt.extend_from_slice(&qname);
pkt.extend_from_slice(&rtype.qtype().to_be_bytes()); pkt.extend_from_slice(&1u16.to_be_bytes()); Some(pkt)
}
pub fn encode_name(name: &str) -> Option<Vec<u8>> {
let name = name.strip_suffix('.').unwrap_or(name);
let mut buf = Vec::with_capacity(name.len() + 2);
if !name.is_empty() {
for part in name.split('.') {
if part.len() > 63 {
return None;
}
buf.push(part.len() as u8);
buf.extend_from_slice(part.as_bytes());
}
}
buf.push(0); Some(buf)
}
pub fn parse_response(data: &[u8], expected_id: u16) -> Result<Vec<IpAddr>, &'static str> {
if data.len() < 12 {
return Err("response too short");
}
let id = u16::from_be_bytes([data[0], data[1]]);
if id != expected_id {
return Err("transaction ID mismatch");
}
let flags = u16::from_be_bytes([data[2], data[3]]);
if flags & 0x8000 == 0 {
return Err("not a response");
}
if flags & 0x000F != 0 {
return Err("DNS error rcode");
}
let qdcount = u16::from_be_bytes([data[4], data[5]]);
let ancount = u16::from_be_bytes([data[6], data[7]]);
let mut off = 12;
for _ in 0..qdcount {
off = skip_name(data, off).ok_or("malformed question")?;
if off + 4 > data.len() {
return Err("truncated question");
}
off += 4; }
let mut out = Vec::new();
for _ in 0..ancount {
off = match skip_name(data, off) {
Some(o) => o,
None => break,
};
if off + 10 > data.len() {
break;
}
let rtype = u16::from_be_bytes([data[off], data[off + 1]]);
let rdlength = u16::from_be_bytes([data[off + 8], data[off + 9]]) as usize;
off += 10;
if off + rdlength > data.len() {
break;
}
match (rtype, rdlength) {
(1, 4) => {
out.push(IpAddr::V4(Ipv4Addr::new(
data[off],
data[off + 1],
data[off + 2],
data[off + 3],
)));
}
(28, 16) => {
let mut b = [0u8; 16];
b.copy_from_slice(&data[off..off + 16]);
out.push(IpAddr::V6(Ipv6Addr::from(b)));
}
_ => {}
}
off += rdlength;
}
Ok(out)
}
pub fn skip_name(data: &[u8], mut off: usize) -> Option<usize> {
loop {
if off >= data.len() {
return None;
}
let l = data[off] as usize;
if l == 0 {
return Some(off + 1);
}
if l & 0xC0 == 0xC0 {
return Some(off + 2); }
off += 1 + l;
}
}
}
#[derive(Debug, Clone)]
pub struct ResolverConfig {
pub servers: Vec<SocketAddr>,
pub timeout: Duration,
}
impl Default for ResolverConfig {
fn default() -> Self {
ResolverConfig {
servers: Vec::new(),
timeout: Duration::from_secs(5),
}
}
}
#[derive(Debug, Clone)]
pub struct Resolver {
cfg: ResolverConfig,
}
impl Resolver {
pub fn new(cfg: ResolverConfig) -> Resolver {
Resolver { cfg }
}
pub fn from_servers(servers: impl IntoIterator<Item = IpAddr>) -> Resolver {
Resolver {
cfg: ResolverConfig {
servers: servers
.into_iter()
.map(|ip| SocketAddr::new(ip, 53))
.collect(),
timeout: Duration::from_secs(5),
},
}
}
pub fn resolve(&self, name: &str) -> io::Result<Vec<IpAddr>> {
if let Ok(ip) = name.parse::<IpAddr>() {
return Ok(vec![ip]);
}
let mut all = self.query(name, RecordType::A)?;
if let Ok(v6) = self.query(name, RecordType::Aaaa) {
all.extend(v6);
}
if all.is_empty() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("no addresses found for {name}"),
));
}
Ok(all)
}
pub fn query(&self, name: &str, rtype: RecordType) -> io::Result<Vec<IpAddr>> {
if self.cfg.servers.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"no DNS servers configured",
));
}
let mut last_err = io::Error::other("no servers tried");
for server in &self.cfg.servers {
match self.query_one(*server, name, rtype) {
Ok(addrs) => return Ok(addrs),
Err(e) => last_err = e,
}
}
Err(last_err)
}
fn query_one(
&self,
server: SocketAddr,
name: &str,
rtype: RecordType,
) -> io::Result<Vec<IpAddr>> {
let id = crate::rand::u32() as u16;
let query = wire::build_query(id, name, rtype)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "label too long"))?;
let bind = if server.is_ipv6() {
"[::]:0"
} else {
"0.0.0.0:0"
};
let sock = UdpSocket::bind(bind)?;
sock.set_read_timeout(Some(self.cfg.timeout))?;
sock.send_to(&query, server)?;
let mut buf = [0u8; 1500];
loop {
let (n, _from) = sock.recv_from(&mut buf)?;
match wire::parse_response(&buf[..n], id) {
Ok(addrs) => return Ok(addrs),
Err(_) => continue, }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
#[test]
fn encode_name_basic() {
let e = wire::encode_name("example.com").unwrap();
assert_eq!(e, b"\x07example\x03com\x00");
assert_eq!(wire::encode_name("example.com."), Some(e));
}
#[test]
fn encode_name_rejects_long_label() {
let long = "a".repeat(64);
assert!(wire::encode_name(&long).is_none());
}
#[test]
fn build_query_shape() {
let q = wire::build_query(0x1234, "a.com", RecordType::A).unwrap();
assert_eq!(&q[0..2], &[0x12, 0x34]);
assert_eq!(q[2], 0x01); assert_eq!(&q[4..6], &[0, 1]); assert_eq!(&q[q.len() - 4..], &[0, 1, 0, 1]);
}
#[test]
fn parse_a_record_response() {
let id: u16 = 0xBEEF;
let mut r = Vec::new();
r.extend_from_slice(&id.to_be_bytes());
r.extend_from_slice(&0x8180u16.to_be_bytes()); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&0u16.to_be_bytes()); r.extend_from_slice(&0u16.to_be_bytes()); r.extend_from_slice(&wire::encode_name("a.com").unwrap());
r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&[0xC0, 0x0C]);
r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&300u32.to_be_bytes()); r.extend_from_slice(&4u16.to_be_bytes()); r.extend_from_slice(&[1, 2, 3, 4]);
let ips = wire::parse_response(&r, id).unwrap();
assert_eq!(ips, vec![IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))]);
}
#[test]
fn parse_rejects_wrong_id() {
let r = vec![0u8; 12];
assert!(wire::parse_response(&r, 0x1234).is_err());
}
#[test]
fn resolve_ip_literal_is_passthrough() {
let r = Resolver::from_servers([]);
assert_eq!(
r.resolve("8.8.8.8").unwrap(),
vec![IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))]
);
}
#[test]
fn resolve_loopback_server_roundtrip() {
let server = UdpSocket::bind("127.0.0.1:0").unwrap();
let server_addr = server.local_addr().unwrap();
std::thread::spawn(move || {
let mut buf = [0u8; 1500];
let (n, from) = server.recv_from(&mut buf).unwrap();
let id = u16::from_be_bytes([buf[0], buf[1]]);
let mut resp = Vec::new();
resp.extend_from_slice(&id.to_be_bytes());
resp.extend_from_slice(&0x8180u16.to_be_bytes());
resp.extend_from_slice(&1u16.to_be_bytes()); resp.extend_from_slice(&1u16.to_be_bytes()); resp.extend_from_slice(&0u16.to_be_bytes());
resp.extend_from_slice(&0u16.to_be_bytes());
resp.extend_from_slice(&buf[12..n]);
resp.extend_from_slice(&[0xC0, 0x0C]);
resp.extend_from_slice(&1u16.to_be_bytes());
resp.extend_from_slice(&1u16.to_be_bytes());
resp.extend_from_slice(&60u32.to_be_bytes());
resp.extend_from_slice(&4u16.to_be_bytes());
resp.extend_from_slice(&[127, 0, 0, 1]);
server.send_to(&resp, from).unwrap();
});
let r = Resolver::new(ResolverConfig {
servers: vec![server_addr],
timeout: Duration::from_secs(2),
});
let ips = r.query("anything.test", RecordType::A).unwrap();
assert_eq!(ips, vec![IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))]);
}
}