use std::net::Ipv4Addr;
const HEADER_LEN: usize = 12;
const TYPE_A: u16 = 1;
const CLASS_IN: u16 = 1;
pub fn build_query(id: u16, name: &str) -> Vec<u8> {
let mut m = Vec::with_capacity(name.len() + 18);
m.extend_from_slice(&id.to_be_bytes());
m.extend_from_slice(&[0x01, 0x00]); m.extend_from_slice(&[0x00, 0x01]); m.extend_from_slice(&[0, 0, 0, 0, 0, 0]); for label in name.split('.') {
if label.is_empty() || label.len() > 63 {
continue;
}
m.push(label.len() as u8);
m.extend_from_slice(label.as_bytes());
}
m.push(0); m.extend_from_slice(&TYPE_A.to_be_bytes());
m.extend_from_slice(&CLASS_IN.to_be_bytes());
m
}
pub fn question_name(msg: &[u8]) -> Option<String> {
if msg.len() < HEADER_LEN {
return None;
}
let qdcount = u16::from_be_bytes([msg[4], msg[5]]);
if qdcount == 0 {
return None;
}
let (name, _) = read_name(msg, HEADER_LEN)?;
Some(name)
}
pub fn question(msg: &[u8]) -> Option<(String, u16, u16)> {
if msg.len() < HEADER_LEN {
return None;
}
let qdcount = u16::from_be_bytes([msg[4], msg[5]]);
if qdcount == 0 {
return None;
}
let (name, pos) = read_name(msg, HEADER_LEN)?;
if pos + 4 > msg.len() {
return None;
}
let qtype = u16::from_be_bytes([msg[pos], msg[pos + 1]]);
let qclass = u16::from_be_bytes([msg[pos + 2], msg[pos + 3]]);
Some((name, qtype, qclass))
}
pub fn min_ttl(msg: &[u8]) -> Option<u32> {
if msg.len() < HEADER_LEN {
return None;
}
let qdcount = u16::from_be_bytes([msg[4], msg[5]]);
let ancount = u16::from_be_bytes([msg[6], msg[7]]);
let mut pos = HEADER_LEN;
for _ in 0..qdcount {
pos = skip_name(msg, pos)?;
pos += 4;
if pos > msg.len() {
return None;
}
}
let mut min: Option<u32> = None;
for _ in 0..ancount {
pos = skip_name(msg, pos)?;
if pos + 10 > msg.len() {
break;
}
let ttl = u32::from_be_bytes([msg[pos + 4], msg[pos + 5], msg[pos + 6], msg[pos + 7]]);
let rdlength = u16::from_be_bytes([msg[pos + 8], msg[pos + 9]]) as usize;
pos += 10 + rdlength;
min = Some(min.map_or(ttl, |m| m.min(ttl)));
}
min
}
pub fn a_records(msg: &[u8]) -> Vec<Ipv4Addr> {
let mut out = Vec::new();
if msg.len() < HEADER_LEN {
return out;
}
let qdcount = u16::from_be_bytes([msg[4], msg[5]]);
let ancount = u16::from_be_bytes([msg[6], msg[7]]);
let mut pos = HEADER_LEN;
for _ in 0..qdcount {
pos = match skip_name(msg, pos) {
Some(p) => p,
None => return out,
};
pos += 4;
if pos > msg.len() {
return out;
}
}
for _ in 0..ancount {
pos = match skip_name(msg, pos) {
Some(p) => p,
None => return out,
};
if pos + 10 > msg.len() {
return out;
}
let rtype = u16::from_be_bytes([msg[pos], msg[pos + 1]]);
let rclass = u16::from_be_bytes([msg[pos + 2], msg[pos + 3]]);
let rdlength = u16::from_be_bytes([msg[pos + 8], msg[pos + 9]]) as usize;
pos += 10;
if pos + rdlength > msg.len() {
return out;
}
if rtype == TYPE_A && rclass == CLASS_IN && rdlength == 4 {
out.push(Ipv4Addr::new(
msg[pos],
msg[pos + 1],
msg[pos + 2],
msg[pos + 3],
));
}
pos += rdlength;
}
out
}
fn read_name(msg: &[u8], start: usize) -> Option<(String, usize)> {
let mut labels: Vec<String> = Vec::new();
let mut pos = start;
let mut jumped = false;
let mut after_ptr = start;
let mut budget = msg.len();
loop {
if pos >= msg.len() || budget == 0 {
return None;
}
budget -= 1;
let len = msg[pos];
match len & 0xC0 {
0x00 => {
if len == 0 {
pos += 1;
if !jumped {
after_ptr = pos;
}
break;
}
let l = len as usize;
let s = pos + 1;
let e = s + l;
if e > msg.len() {
return None;
}
labels.push(String::from_utf8_lossy(&msg[s..e]).to_ascii_lowercase());
pos = e;
}
0xC0 => {
if pos + 1 >= msg.len() {
return None;
}
let ptr = (((len & 0x3F) as usize) << 8) | msg[pos + 1] as usize;
if !jumped {
after_ptr = pos + 2;
jumped = true;
}
if ptr >= msg.len() {
return None;
}
pos = ptr;
}
_ => return None, }
}
Some((labels.join("."), after_ptr))
}
fn skip_name(msg: &[u8], start: usize) -> Option<usize> {
let mut pos = start;
loop {
if pos >= msg.len() {
return None;
}
let len = msg[pos];
match len & 0xC0 {
0x00 => {
if len == 0 {
return Some(pos + 1);
}
pos += 1 + len as usize;
}
0xC0 => {
return if pos + 1 < msg.len() {
Some(pos + 2)
} else {
None
};
}
_ => return None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn query(name: &str) -> Vec<u8> {
let mut m = vec![0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0];
for label in name.split('.') {
m.push(label.len() as u8);
m.extend_from_slice(label.as_bytes());
}
m.push(0);
m.extend_from_slice(&TYPE_A.to_be_bytes());
m.extend_from_slice(&CLASS_IN.to_be_bytes());
m
}
#[test]
fn reads_question_name() {
assert_eq!(
question_name(&query("www.google.com")).as_deref(),
Some("www.google.com")
);
assert_eq!(
question_name(&query("EXAMPLE.com")).as_deref(),
Some("example.com")
);
assert_eq!(question_name(b"short"), None);
}
fn response_with(q: &[u8], records: &[([u8; 4], u32)]) -> Vec<u8> {
let mut m = q.to_vec();
m[2] = 0x81;
m[3] = 0x80;
m[6] = (records.len() >> 8) as u8;
m[7] = records.len() as u8;
for (ip, ttl) in records {
m.extend_from_slice(&[0xC0, 0x0C]); m.extend_from_slice(&TYPE_A.to_be_bytes());
m.extend_from_slice(&CLASS_IN.to_be_bytes());
m.extend_from_slice(&ttl.to_be_bytes());
m.extend_from_slice(&4u16.to_be_bytes());
m.extend_from_slice(ip);
}
m
}
#[test]
fn build_query_round_trips() {
let q = build_query(0xABCD, "www.example.com");
assert_eq!(&q[0..2], &[0xAB, 0xCD]); let (name, qtype, qclass) = question(&q).unwrap();
assert_eq!(name, "www.example.com");
assert_eq!((qtype, qclass), (TYPE_A, CLASS_IN));
}
#[test]
fn reads_question_tuple() {
let (name, qtype, qclass) = question(&query("a.b.example.com")).unwrap();
assert_eq!(name, "a.b.example.com");
assert_eq!((qtype, qclass), (TYPE_A, CLASS_IN));
assert!(question(b"short").is_none());
}
#[test]
fn min_ttl_picks_smallest() {
let m = response_with(
&query("example.com"),
&[([1, 2, 3, 4], 300), ([5, 6, 7, 8], 60)],
);
assert_eq!(min_ttl(&m), Some(60));
assert_eq!(min_ttl(&query("example.com")), None); }
#[test]
fn extracts_a_records_with_compression() {
let mut m = query("example.com");
m[2] = 0x81; m[3] = 0x80; m[6] = 0x00;
m[7] = 0x02; for ip in [[93, 184, 216, 34], [1, 2, 3, 4]] {
m.extend_from_slice(&[0xC0, 0x0C]); m.extend_from_slice(&TYPE_A.to_be_bytes());
m.extend_from_slice(&CLASS_IN.to_be_bytes());
m.extend_from_slice(&300u32.to_be_bytes()); m.extend_from_slice(&4u16.to_be_bytes()); m.extend_from_slice(&ip);
}
let ips = a_records(&m);
assert_eq!(
ips,
vec![Ipv4Addr::new(93, 184, 216, 34), Ipv4Addr::new(1, 2, 3, 4)]
);
}
#[test]
fn ignores_non_a_and_malformed() {
assert!(a_records(&query("example.com")).is_empty()); assert!(a_records(b"").is_empty());
assert!(a_records(b"\x00\x00\x00\x00\xff\xff\xff\xff").is_empty()); }
}