use byteorder::{BigEndian, ByteOrder};
use log::debug;
use std::str;
use crate::errors::{DnsError, DnsResult};
pub const DNS_PACKET_LEN_MIN: usize = 12; pub const DNS_HEADER_SIZE: usize = DNS_PACKET_LEN_MIN; pub const DNS_MAX_PACKET_SIZE: usize = 0x1600; pub const DNS_MAX_HOSTNAME_SIZE: usize = 256; pub const DNS_OFFSET_QUESTION: usize = DNS_HEADER_SIZE; pub const DNS_MAX_UDP_PACKET_SIZE: usize = 512;
pub const DNS_TYPE_A: u16 = 1;
pub const DNS_TYPE_NS: u16 = 2;
pub const DNS_TYPE_CNAME: u16 = 5;
#[allow(dead_code)]
pub const DNS_TYPE_SOA: u16 = 6;
pub const DNS_TYPE_PTR: u16 = 12;
pub const DNS_TYPE_MX: u16 = 15;
#[allow(dead_code)]
pub const DNS_TYPE_TXT: u16 = 16;
pub const DNS_TYPE_AAAA: u16 = 28;
pub const DNS_TYPE_SRV: u16 = 33;
pub const DNS_TYPE_OPT: u16 = 41;
pub const DNS_TYPE_RRSIG: u16 = 46;
pub const DNS_TYPE_ANY: u16 = 255;
pub const DNS_CLASS_IN: u16 = 1;
pub const DNS_CLASS_ANY: u16 = 255;
pub const DNS_FLAG_DO: u16 = 0x8000;
pub const EDNS_OPTION_CLIENT_SUBNET: u16 = 8;
const DNS_FLAGS_QR: u16 = 1u16 << 15; const DNS_FLAGS_AA: u16 = 1u16 << 10; const DNS_FLAGS_TC: u16 = 1u16 << 9; #[allow(dead_code)]
const DNS_FLAGS_RD: u16 = 1u16 << 8; const DNS_FLAGS_RA: u16 = 1u16 << 7; const DNS_FLAGS_CD: u16 = 1u16 << 4; #[allow(dead_code)]
const DNS_FLAGS_AD: u16 = 1u16 << 5;
pub const DNS_OPCODE_QUERY: u8 = 0; #[allow(dead_code)]
pub const DNS_OPCODE_IQUERY: u8 = 1; #[allow(dead_code)]
pub const DNS_OPCODE_STATUS: u8 = 2; #[allow(dead_code)]
pub const DNS_OPCODE_NOTIFY: u8 = 4; #[allow(dead_code)]
pub const DNS_OPCODE_UPDATE: u8 = 5;
pub fn validate_dns_packet(packet: &[u8]) -> DnsResult<usize> {
if packet.len() < DNS_HEADER_SIZE {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if packet.len() > DNS_MAX_PACKET_SIZE {
return Err(DnsError::PacketTooLarge {
size: packet.len(),
max_size: DNS_MAX_PACKET_SIZE,
});
}
if !is_response(packet) {
let op = opcode(packet);
if op != DNS_OPCODE_QUERY {
return Err(DnsError::UnsupportedOperation(format!(
"Unsupported opcode: {op}"
)));
}
}
let qd_count = qdcount(packet);
if qd_count == 0 {
return Err(DnsError::InvalidPacket(
"No questions in packet".to_string(),
));
}
if qd_count > 1 {
return Err(DnsError::InvalidPacket(
"Too many questions in packet".to_string(),
));
}
let (an_count, ns_count, ar_count) = (ancount(packet), nscount(packet), arcount(packet));
let mut offset = DNS_HEADER_SIZE;
for _ in 0..qd_count {
offset = match skip_name(packet, offset) {
Ok(new_offset) => new_offset,
Err(e) => {
return Err(DnsError::InvalidQuestion(format!(
"Invalid question name: {e}"
)));
}
};
if packet.len() < offset + 4 {
return Err(DnsError::InvalidPacket(
"Packet too short for QTYPE and QCLASS".to_string(),
));
}
let qtype_end = offset.checked_add(2).ok_or_else(|| {
DnsError::InvalidPacket("Integer overflow calculating QTYPE offset".to_string())
})?;
let qclass_end = offset.checked_add(4).ok_or_else(|| {
DnsError::InvalidPacket("Integer overflow calculating QCLASS offset".to_string())
})?;
let _qtype = BigEndian::read_u16(&packet[offset..qtype_end]);
let qclass = BigEndian::read_u16(&packet[qtype_end..qclass_end]);
if qclass != DNS_CLASS_IN && qclass != DNS_CLASS_ANY {
debug!("Unusual: DNS query with QCLASS={qclass}");
}
match qname(packet) {
Ok(name) => {
if name.len() > DNS_MAX_HOSTNAME_SIZE {
return Err(DnsError::DomainNameTooLong {
length: name.len(),
max_length: DNS_MAX_HOSTNAME_SIZE,
});
}
let labels: Vec<&[u8]> = name.split(|&c| c == b'.').collect();
for label in labels {
if label.is_empty() {
if !name.starts_with(b".") && !name.ends_with(b".") && name.len() > 1 {
return Err(DnsError::EmptyLabel);
}
continue;
}
if label.len() > 63 {
return Err(DnsError::LabelTooLong {
length: label.len(),
});
}
for (i, &c) in label.iter().enumerate() {
if !is_valid_domain_name_char(c, i, label.len()) {
let label_str = match std::str::from_utf8(label) {
Ok(s) => s.to_string(),
Err(_) => format!("{label:?}"),
};
return Err(DnsError::InvalidDomainNameCharacter {
character: c as char,
position: i,
label: label_str,
});
}
}
}
}
Err(e) => {
return Err(DnsError::InvalidQuestion(format!(
"Failed to extract query name: {e}"
)));
}
}
offset = offset.checked_add(4).ok_or_else(|| {
DnsError::InvalidPacket("Integer overflow advancing past QTYPE/QCLASS".to_string())
})?;
}
let an_count_usize = an_count as usize;
let ns_count_usize = ns_count as usize;
let ar_count_usize = ar_count as usize;
let total_rr_count = match an_count_usize.checked_add(ns_count_usize) {
Some(count) => match count.checked_add(ar_count_usize) {
Some(total) => total,
None => {
return Err(DnsError::InvalidPacket(
"Integer overflow in resource record count calculation".to_string(),
));
}
},
None => {
return Err(DnsError::InvalidPacket(
"Integer overflow in resource record count calculation".to_string(),
));
}
};
const MAX_SENSIBLE_RR_COUNT: usize = 1000;
if total_rr_count > MAX_SENSIBLE_RR_COUNT {
return Err(DnsError::InvalidPacket(format!(
"Excessive resource record count: {total_rr_count} (max {MAX_SENSIBLE_RR_COUNT})"
)));
}
if total_rr_count > 0 {
match traverse_rrs(packet, offset, total_rr_count, |rr_offset| {
if packet.len() < rr_offset + 2 {
return Err(DnsError::PacketTooShort { offset: rr_offset });
}
let _rr_type = BigEndian::read_u16(&packet[rr_offset..rr_offset + 2]);
if packet.len() < rr_offset + 8 {
return Err(DnsError::PacketTooShort { offset: rr_offset });
}
let ttl = BigEndian::read_u32(&packet[rr_offset + 4..rr_offset + 8]);
if ttl > 604800 {
debug!("Unusual: DNS record with TTL > 7 days: {ttl} seconds");
}
if packet.len() < rr_offset + 10 {
return Err(DnsError::PacketTooShort { offset: rr_offset });
}
let rdlength = BigEndian::read_u16(&packet[rr_offset + 8..rr_offset + 10]) as usize;
let rr_data_end = match rr_offset.checked_add(10) {
Some(offset_plus_10) => match offset_plus_10.checked_add(rdlength) {
Some(end_offset) => end_offset,
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating record data bounds".to_string(),
));
}
},
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating record data bounds".to_string(),
));
}
};
if packet.len() < rr_data_end {
return Err(DnsError::InvalidRecord(format!(
"Record data length ({rdlength}) exceeds packet bounds"
)));
}
Ok(())
}) {
Ok(_) => (),
Err(e) => {
return Err(DnsError::InvalidRecord(format!(
"Invalid resource record: {e}"
)));
}
}
}
let additional_offset = traverse_rrs(
packet,
offset,
an_count as usize + ns_count as usize,
|_| Ok(()),
)?;
let message_len = validate_edns_records(packet, additional_offset, ar_count as usize)?;
if let Some(edns_version) = extract_edns_version(packet)?
&& edns_version != 0
{
return Err(DnsError::UnsupportedEdnsVersion {
version: edns_version,
});
}
Ok(message_len)
}
fn validate_edns_records(packet: &[u8], mut offset: usize, arcount: usize) -> DnsResult<usize> {
let mut found_opt = false;
for _ in 0..arcount {
let owner_offset = offset;
let rr_offset = skip_name(packet, owner_offset)?;
if packet.len().saturating_sub(rr_offset) < 10 {
return Err(DnsError::PacketTooShort { offset: rr_offset });
}
if BigEndian::read_u16(&packet[rr_offset..rr_offset + 2]) == DNS_TYPE_OPT {
if packet[owner_offset] != 0 {
return Err(DnsError::InvalidEdns(
"OPT record owner must be the root name".to_string(),
));
}
if found_opt {
return Err(DnsError::InvalidEdns("Duplicate OPT RR found".to_string()));
}
found_opt = true;
}
offset = traverse_rrs(packet, owner_offset, 1, |_| Ok(()))?;
}
Ok(offset)
}
#[inline]
pub fn tid(packet: &[u8]) -> u16 {
if packet.len() < 2 {
return 0; }
BigEndian::read_u16(&packet[0..2])
}
#[inline]
pub fn qdcount(packet: &[u8]) -> u16 {
if packet.len() < 6 {
return 0; }
BigEndian::read_u16(&packet[4..6])
}
#[inline]
pub fn ancount(packet: &[u8]) -> u16 {
if packet.len() < 8 {
return 0; }
BigEndian::read_u16(&packet[6..8])
}
#[inline]
pub fn nscount(packet: &[u8]) -> u16 {
if packet.len() < 10 {
return 0; }
BigEndian::read_u16(&packet[8..10])
}
#[inline]
pub fn arcount(packet: &[u8]) -> u16 {
if packet.len() < DNS_PACKET_LEN_MIN {
return 0; }
BigEndian::read_u16(&packet[10..DNS_PACKET_LEN_MIN])
}
#[inline]
pub fn rcode(packet: &[u8]) -> u8 {
if packet.len() < 4 {
return 2; }
packet[3] & 0x0F
}
#[inline]
pub fn is_response(packet: &[u8]) -> bool {
if packet.len() < 4 {
return false; }
(BigEndian::read_u16(&packet[2..4]) & DNS_FLAGS_QR) == DNS_FLAGS_QR
}
#[inline]
pub fn opcode(packet: &[u8]) -> u8 {
if packet.len() < 3 {
return 0; }
(packet[2] >> 3) & 0x0F
}
#[inline]
#[allow(dead_code)]
pub fn is_standard_query(packet: &[u8]) -> bool {
!is_response(packet) && opcode(packet) == DNS_OPCODE_QUERY
}
#[inline]
pub fn is_truncated(packet: &[u8]) -> bool {
if packet.len() < 4 {
return false; }
(BigEndian::read_u16(&packet[2..4]) & DNS_FLAGS_TC) == DNS_FLAGS_TC
}
#[allow(dead_code)]
pub fn is_dnssec_requested(packet: &[u8]) -> DnsResult<bool> {
if packet.len() < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if is_checking_disabled(packet) {
return Ok(true);
}
is_dnssec_ok(packet)
}
pub fn is_dnssec_ok(packet: &[u8]) -> DnsResult<bool> {
if packet.len() < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let packet_len = packet.len();
if packet_len <= DNS_OFFSET_QUESTION {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
if packet_len.saturating_sub(offset) < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset = offset.checked_add(4).ok_or_else(|| {
DnsError::InvalidPacket("Integer overflow skipping QTYPE/QCLASS".to_string())
})?;
let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet));
offset = traverse_rrs(packet, offset, ancount as usize + nscount as usize, |_| {
Ok(())
})?;
let mut dnssec_requested = false;
traverse_rrs(packet, offset, arcount as usize, |offset| {
let qtype = BigEndian::read_u16(&packet[offset..]);
if qtype == DNS_TYPE_OPT {
let opt_flags = BigEndian::read_u16(&packet[offset + 6..]);
if (opt_flags & DNS_FLAG_DO) == DNS_FLAG_DO {
dnssec_requested = true;
}
}
Ok(())
})?;
Ok(dnssec_requested)
}
pub fn is_recursion_desired(packet: &[u8]) -> bool {
flags(packet) & DNS_FLAGS_RD != 0
}
pub fn is_checking_disabled(packet: &[u8]) -> bool {
flags(packet) & DNS_FLAGS_CD != 0
}
pub fn is_authenticated_data_requested(packet: &[u8]) -> bool {
flags(packet) & DNS_FLAGS_AD != 0
}
#[allow(dead_code)]
pub fn query_name(packet: &[u8]) -> DnsResult<String> {
let qname_bytes = qname(packet)?;
if qname_bytes.len() > DNS_MAX_HOSTNAME_SIZE {
return Err(DnsError::DomainNameTooLong {
length: qname_bytes.len(),
max_length: DNS_MAX_HOSTNAME_SIZE,
});
}
let qname_str = match str::from_utf8(&qname_bytes) {
Ok(s) => s,
Err(_) => {
return Err(DnsError::InvalidDomainName(
"Invalid UTF-8 in query name".to_string(),
));
}
};
if qname_str.len() > DNS_MAX_HOSTNAME_SIZE {
return Err(DnsError::DomainNameTooLong {
length: qname_str.len(),
max_length: DNS_MAX_HOSTNAME_SIZE,
});
}
Ok(qname_str.to_string())
}
pub fn query_type_class(packet: &[u8]) -> DnsResult<(u16, u16)> {
if packet.len() < DNS_HEADER_SIZE {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if qdcount(packet) == 0 {
return Err(DnsError::InvalidPacket("No question".to_string()));
}
let offset = skip_name(packet, DNS_HEADER_SIZE)?;
if packet.len() - offset < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let qtype = BigEndian::read_u16(&packet[offset..]);
let qclass = BigEndian::read_u16(&packet[offset + 2..]);
Ok((qtype, qclass))
}
pub fn questions_match(query: &[u8], response: &[u8]) -> DnsResult<bool> {
if opcode(query) != opcode(response) {
return Ok(false);
}
let query_name = qname(query)?;
let response_name = qname(response)?;
let query_fields = query_type_class(query)?;
let response_fields = query_type_class(response)?;
Ok(query_name.eq_ignore_ascii_case(&response_name) && query_fields == response_fields)
}
pub fn extract_min_ttl(packet: &[u8]) -> DnsResult<Option<u32>> {
let packet_len = packet.len();
if packet_len <= DNS_OFFSET_QUESTION {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if packet_len > DNS_MAX_PACKET_SIZE {
return Err(DnsError::PacketTooLarge {
size: packet_len,
max_size: DNS_MAX_PACKET_SIZE,
});
}
if qdcount(packet) == 0 {
return Err(DnsError::InvalidPacket("No question".to_string()));
}
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
if packet_len - offset < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset += 4;
let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet));
let total_rr_count = ancount as usize + nscount as usize + arcount as usize;
if total_rr_count == 0 {
return Ok(None);
}
let mut min_ttl: Option<u32> = None;
traverse_rrs(packet, offset, total_rr_count, |offset| {
let rr_type = BigEndian::read_u16(&packet[offset..offset + 2]);
let ttl = BigEndian::read_u32(&packet[offset + 4..offset + 8]);
if rr_type != DNS_TYPE_OPT {
min_ttl = match min_ttl {
None => Some(ttl),
Some(current_min) => Some(std::cmp::min(current_min, ttl)),
};
}
Ok(())
})?;
Ok(min_ttl)
}
pub fn change_ttl(packet: &mut [u8], new_ttl: u32) -> DnsResult<()> {
let packet_len = packet.len();
if packet_len <= DNS_OFFSET_QUESTION {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if packet_len > DNS_MAX_PACKET_SIZE {
return Err(DnsError::PacketTooLarge {
size: packet_len,
max_size: DNS_MAX_PACKET_SIZE,
});
}
if qdcount(packet) == 0 {
return Err(DnsError::InvalidPacket("No question".to_string()));
}
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
if packet_len - offset < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset += 4;
let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet));
let total_rr_count = ancount as usize + nscount as usize + arcount as usize;
if total_rr_count == 0 {
return Ok(());
}
traverse_rrs_mut(packet, offset, total_rr_count, |packet, offset| {
let rr_type = BigEndian::read_u16(&packet[offset..offset + 2]);
if rr_type != DNS_TYPE_OPT {
BigEndian::write_u32(&mut packet[offset + 4..offset + 8], new_ttl);
}
Ok(())
})?;
Ok(())
}
pub fn decrement_ttl(packet: &mut [u8], elapsed: u32) -> DnsResult<()> {
let packet_len = packet.len();
if packet_len <= DNS_OFFSET_QUESTION {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if packet_len > DNS_MAX_PACKET_SIZE {
return Err(DnsError::PacketTooLarge {
size: packet_len,
max_size: DNS_MAX_PACKET_SIZE,
});
}
if qdcount(packet) == 0 {
return Err(DnsError::InvalidPacket("No question".to_string()));
}
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
if packet_len - offset < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset += 4;
let total_rr_count =
ancount(packet) as usize + nscount(packet) as usize + arcount(packet) as usize;
traverse_rrs_mut(packet, offset, total_rr_count, |packet, offset| {
let rr_type = BigEndian::read_u16(&packet[offset..offset + 2]);
if rr_type != DNS_TYPE_OPT {
let ttl = BigEndian::read_u32(&packet[offset + 4..offset + 8]);
BigEndian::write_u32(
&mut packet[offset + 4..offset + 8],
ttl.saturating_sub(elapsed),
);
}
Ok(())
})?;
Ok(())
}
fn is_valid_domain_name_char(c: u8, position: usize, label_len: usize) -> bool {
let is_letter = c.is_ascii_lowercase() || c.is_ascii_uppercase();
let is_digit = c.is_ascii_digit();
let is_hyphen = c == b'-';
if position == 0 {
return is_letter || is_digit || c == b'_';
}
if position == label_len - 1 {
return is_letter || is_digit;
}
is_letter || is_digit || is_hyphen
}
pub fn qname(packet: &[u8]) -> DnsResult<Vec<u8>> {
let packet_len = packet.len();
if packet_len <= DNS_OFFSET_QUESTION {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if qdcount(packet) == 0 {
return Err(DnsError::InvalidPacket("No question".to_string()));
}
let mut offset = DNS_HEADER_SIZE;
let mut qname = Vec::with_capacity(DNS_MAX_HOSTNAME_SIZE);
loop {
if offset >= packet_len {
return Err(DnsError::PacketTooShort { offset: 0 });
}
match packet[offset] as usize {
label_len if label_len & 0xc0 == 0xc0 => {
if offset + 1 >= packet_len {
return Err(DnsError::PacketTooShort { offset });
}
let second_byte = packet[offset + 1] as usize;
return Err(DnsError::InvalidCompressionPointer {
offset,
pointer: ((label_len & 0x3f) << 8) | second_byte,
packet_size: packet_len,
});
}
0 => {
if qname.is_empty() {
qname.push(b'.');
}
break;
}
label_len => {
if label_len >= 0x40 {
return Err(DnsError::LabelTooLong { length: label_len });
}
if packet_len.saturating_sub(offset) <= 1 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset = offset.checked_add(1).ok_or_else(|| {
DnsError::InvalidPacket(
"Integer overflow advancing past label length byte".to_string(),
)
})?;
if packet_len.saturating_sub(offset) < label_len {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if !qname.is_empty() {
qname.push(b'.');
}
if qname.len() + label_len >= DNS_MAX_HOSTNAME_SIZE {
return Err(DnsError::DomainNameTooLong {
length: qname.len() + label_len,
max_length: DNS_MAX_HOSTNAME_SIZE,
});
}
let label_end = offset.checked_add(label_len).ok_or_else(|| {
DnsError::InvalidPacket("Integer overflow calculating label end".to_string())
})?;
if packet[offset..label_end].contains(&b'.') {
return Err(DnsError::InvalidDomainName(
"Label contains a dot".to_string(),
));
}
qname.extend_from_slice(&packet[offset..label_end]);
offset = label_end;
}
}
}
Ok(qname)
}
fn skip_name(packet: &[u8], offset: usize) -> DnsResult<usize> {
let packet_len = packet.len();
if offset >= packet_len {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let mut current_offset = offset;
let mut jumps = 0;
let mut total_name_len: usize = 0;
let mut result_offset_after_pointer: Option<usize> = None;
loop {
if current_offset >= packet_len {
return Err(DnsError::PacketTooShort {
offset: current_offset,
});
}
match packet[current_offset] as usize {
label_len if label_len & 0xc0 == 0xc0 => {
if packet_len - current_offset < 2 {
return Err(DnsError::PacketTooShort {
offset: current_offset,
});
}
jumps += 1;
if jumps > 10 {
return Err(DnsError::TooManyCompressionPointers { count: jumps });
}
if jumps == 1 {
let result_offset = match current_offset.checked_add(2) {
Some(offset) => offset,
None => {
return Err(DnsError::InvalidCompressionPointer {
offset: current_offset,
pointer: 0,
packet_size: packet_len,
});
}
};
if result_offset > packet_len {
return Err(DnsError::PacketTooShort {
offset: current_offset,
});
}
result_offset_after_pointer = Some(result_offset);
}
if current_offset + 1 >= packet_len {
return Err(DnsError::PacketTooShort {
offset: current_offset,
});
}
let second_byte = packet[current_offset + 1] as usize;
let first_part = (label_len & 0x3f) << 8;
let pointer = match first_part.checked_add(second_byte) {
Some(ptr) => ptr,
None => {
return Err(DnsError::InvalidCompressionPointer {
offset: current_offset,
pointer: first_part,
packet_size: packet_len,
});
}
};
if pointer >= packet_len {
return Err(DnsError::InvalidCompressionPointer {
offset: current_offset,
pointer,
packet_size: packet_len,
});
}
if pointer >= current_offset {
return Err(DnsError::InvalidCompressionPointer {
offset: current_offset,
pointer,
packet_size: packet_len,
});
}
current_offset = pointer;
}
0 => {
if total_name_len.saturating_add(1) > 255 {
return Err(DnsError::DomainNameTooLong {
length: total_name_len + 1,
max_length: 255,
});
}
current_offset = match current_offset.checked_add(1) {
Some(offset) => offset,
None => {
return Err(DnsError::PacketTooShort {
offset: current_offset,
});
}
};
break;
}
label_len => {
if label_len >= 0x40 {
return Err(DnsError::LabelTooLong { length: label_len });
}
let remaining_bytes = if current_offset <= packet_len {
packet_len - current_offset
} else {
return Err(DnsError::PacketTooShort {
offset: current_offset,
});
};
if remaining_bytes <= label_len {
return Err(DnsError::PacketTooShort {
offset: current_offset,
});
}
total_name_len = match label_len.checked_add(1) {
Some(len) => match total_name_len.checked_add(len) {
Some(total) => total,
None => {
return Err(DnsError::DomainNameTooLong {
length: u32::MAX as usize,
max_length: DNS_MAX_HOSTNAME_SIZE,
});
}
},
None => {
return Err(DnsError::DomainNameTooLong {
length: u32::MAX as usize,
max_length: DNS_MAX_HOSTNAME_SIZE,
});
}
};
if total_name_len > DNS_MAX_HOSTNAME_SIZE {
return Err(DnsError::DomainNameTooLong {
length: total_name_len,
max_length: DNS_MAX_HOSTNAME_SIZE,
});
}
let label_plus_1 = match label_len.checked_add(1) {
Some(len) => len,
None => {
return Err(DnsError::LabelTooLong {
length: u32::MAX as usize,
});
}
};
current_offset = match current_offset.checked_add(label_plus_1) {
Some(offset) => offset,
None => {
return Err(DnsError::PacketTooShort {
offset: current_offset,
});
}
};
}
}
}
Ok(result_offset_after_pointer.unwrap_or(current_offset))
}
fn traverse_rrs<F>(
packet: &[u8],
mut offset: usize,
rrcount: usize,
mut callback: F,
) -> DnsResult<usize>
where
F: FnMut(usize) -> DnsResult<()>,
{
let packet_len = packet.len();
for _ in 0..rrcount {
offset = skip_name(packet, offset)?;
if packet_len < offset || packet_len - offset < 10 {
return Err(DnsError::PacketTooShort { offset });
}
if offset + 8 >= packet_len {
return Err(DnsError::PacketTooShort { offset });
}
let rdlen = BigEndian::read_u16(&packet[offset + 8..offset + 10]) as usize;
let rdata_end = match offset.checked_add(10) {
Some(offset_plus_10) => match offset_plus_10.checked_add(rdlen) {
Some(end_offset) => end_offset,
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating record data bounds".to_string(),
));
}
},
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating record offset".to_string(),
));
}
};
if packet_len < rdata_end {
return Err(DnsError::InvalidRecord(
"Record length would exceed packet length".to_string(),
));
}
callback(offset)?;
let rr_type = BigEndian::read_u16(&packet[offset..offset + 2]);
match rr_type {
DNS_TYPE_NS | DNS_TYPE_CNAME | DNS_TYPE_PTR | DNS_TYPE_MX | DNS_TYPE_SRV
if rdlen > DNS_MAX_HOSTNAME_SIZE => {
debug!("Large RDATA for domain name record: {rdlen} bytes (type: {rr_type})");
}
_ => {}
}
offset = match offset.checked_add(10) {
Some(o) => match o.checked_add(rdlen) {
Some(final_offset) => final_offset,
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating next record offset".to_string(),
));
}
},
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating next record offset".to_string(),
));
}
};
}
Ok(offset)
}
fn traverse_rrs_mut<F>(
packet: &mut [u8],
mut offset: usize,
rrcount: usize,
mut callback: F,
) -> DnsResult<usize>
where
F: FnMut(&mut [u8], usize) -> DnsResult<()>,
{
let packet_len = packet.len();
for _ in 0..rrcount {
offset = skip_name(packet, offset)?;
if packet_len < offset || packet_len - offset < 10 {
return Err(DnsError::PacketTooShort { offset });
}
if offset + 8 >= packet_len {
return Err(DnsError::PacketTooShort { offset });
}
let rdlen = BigEndian::read_u16(&packet[offset + 8..offset + 10]) as usize;
let rdata_end = match offset.checked_add(10) {
Some(offset_plus_10) => match offset_plus_10.checked_add(rdlen) {
Some(end_offset) => end_offset,
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating record data bounds".to_string(),
));
}
},
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating record offset".to_string(),
));
}
};
if packet_len < rdata_end {
return Err(DnsError::InvalidRecord(
"Record length would exceed packet length".to_string(),
));
}
callback(packet, offset)?;
let rr_type = BigEndian::read_u16(&packet[offset..offset + 2]);
match rr_type {
DNS_TYPE_NS | DNS_TYPE_CNAME | DNS_TYPE_PTR | DNS_TYPE_MX | DNS_TYPE_SRV
if rdlen > DNS_MAX_HOSTNAME_SIZE => {
debug!("Large RDATA for domain name record: {rdlen} bytes (type: {rr_type})");
}
_ => {}
}
offset = match offset.checked_add(10) {
Some(o) => match o.checked_add(rdlen) {
Some(final_offset) => final_offset,
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating next record offset".to_string(),
));
}
},
None => {
return Err(DnsError::InvalidRecord(
"Integer overflow calculating next record offset".to_string(),
));
}
};
}
Ok(offset)
}
pub fn add_edns_section(packet: &mut Vec<u8>, max_payload_size: u16) -> DnsResult<()> {
if packet.len() < DNS_HEADER_SIZE {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if packet.len() > DNS_MAX_PACKET_SIZE {
return Err(DnsError::PacketTooLarge {
size: packet.len(),
max_size: DNS_MAX_PACKET_SIZE,
});
}
if extract_edns_version(packet)?.is_some() {
return Err(DnsError::InvalidEdns(
"Packet already contains an OPT record".to_string(),
));
}
let opt_rr: [u8; 11] = [
0, (DNS_TYPE_OPT >> 8) as u8, DNS_TYPE_OPT as u8, (max_payload_size >> 8) as u8, max_payload_size as u8, 0, 0, 0, 0, 0, 0, ];
if DNS_MAX_PACKET_SIZE - packet.len() < opt_rr.len() {
return Err(DnsError::PacketTooLarge {
size: packet.len(),
max_size: DNS_MAX_PACKET_SIZE,
});
}
let arcount_offset = 10; let mut arcount = BigEndian::read_u16(&packet[arcount_offset..arcount_offset + 2]);
if arcount == 0xffff {
return Err(DnsError::InvalidPacket(
"Too many additional records".to_string(),
));
}
arcount += 1;
BigEndian::write_u16(&mut packet[arcount_offset..arcount_offset + 2], arcount);
packet.extend_from_slice(&opt_rr);
Ok(())
}
pub fn add_edns_client_subnet(
packet: &mut Vec<u8>,
client_ip: &str,
prefix_v4: u8,
prefix_v6: u8,
max_payload_size: u16,
) -> DnsResult<()> {
let ip_addr = match client_ip.parse::<std::net::IpAddr>() {
Ok(addr) => addr,
Err(_) => {
debug!("Invalid client IP address for EDNS-client-subnet: {client_ip}");
return Ok(()); }
};
let mut has_opt_record = false;
let mut opt_record_offset = 0;
let mut opt_record_rdlen_offset = 0;
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
offset += 4;
let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet));
offset = traverse_rrs(packet, offset, ancount as usize + nscount as usize, |_| {
Ok(())
})?;
traverse_rrs(packet, offset, arcount as usize, |rr_offset| {
let qtype = BigEndian::read_u16(&packet[rr_offset..]);
if qtype == DNS_TYPE_OPT {
has_opt_record = true;
opt_record_offset = rr_offset;
opt_record_rdlen_offset = rr_offset + 8; }
Ok(())
})?;
if !has_opt_record {
add_edns_section(packet, max_payload_size)?;
offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
offset += 4;
offset = traverse_rrs(packet, offset, ancount as usize + nscount as usize, |_| {
Ok(())
})?;
traverse_rrs(packet, offset, arcount as usize + 1, |rr_offset| {
let qtype = BigEndian::read_u16(&packet[rr_offset..]);
if qtype == DNS_TYPE_OPT {
has_opt_record = true;
opt_record_offset = rr_offset;
opt_record_rdlen_offset = rr_offset + 8; }
Ok(())
})?;
}
if !has_opt_record {
return Err(DnsError::InvalidEdns(
"Failed to add OPT record".to_string(),
));
}
let rdlen_offset = opt_record_rdlen_offset;
let mut rdlen = BigEndian::read_u16(&packet[rdlen_offset..rdlen_offset + 2]) as usize;
let rdata_offset = rdlen_offset + 2;
let mut option_offset = rdata_offset;
while option_offset < rdata_offset + rdlen {
if rdata_offset + rdlen - option_offset < 4 {
return Err(DnsError::InvalidEdns(
"Truncated EDNS option header".to_string(),
));
}
let option_code = BigEndian::read_u16(&packet[option_offset..option_offset + 2]);
let option_len =
BigEndian::read_u16(&packet[option_offset + 2..option_offset + 4]) as usize;
let option_end = option_offset
.checked_add(4 + option_len)
.ok_or_else(|| DnsError::InvalidEdns("EDNS option length overflow".to_string()))?;
if option_end > rdata_offset + rdlen {
return Err(DnsError::InvalidEdns(
"EDNS option exceeds OPT record data".to_string(),
));
}
if option_code == EDNS_OPTION_CLIENT_SUBNET {
packet.drain(option_offset..option_end);
rdlen -= option_end - option_offset;
continue;
}
option_offset = option_end;
}
BigEndian::write_u16(&mut packet[rdlen_offset..rdlen_offset + 2], rdlen as u16);
let mut ecs_option = Vec::new();
let (family, source_prefix_length, address_bytes) = match ip_addr {
std::net::IpAddr::V4(ipv4) => {
if prefix_v4 > 32 {
return Err(DnsError::InvalidEdns(format!(
"Invalid ECS prefix length {prefix_v4} for IPv4"
)));
}
let bytes = ipv4.octets();
(1u16, prefix_v4, bytes.to_vec())
}
std::net::IpAddr::V6(ipv6) => {
if prefix_v6 > 128 {
return Err(DnsError::InvalidEdns(format!(
"Invalid ECS prefix length {prefix_v6} for IPv6"
)));
}
let bytes = ipv6.octets();
(2u16, prefix_v6, bytes.to_vec())
}
};
let address_byte_count = (source_prefix_length as usize).div_ceil(8);
let mut truncated_address = address_bytes[..address_byte_count].to_vec();
if address_byte_count > 0 && source_prefix_length % 8 != 0 {
let last_byte_index = address_byte_count - 1;
let mask = 0xffu8 << (8 - (source_prefix_length % 8));
truncated_address[last_byte_index] &= mask;
}
ecs_option.push((EDNS_OPTION_CLIENT_SUBNET >> 8) as u8);
ecs_option.push(EDNS_OPTION_CLIENT_SUBNET as u8);
let option_length = 4 + truncated_address.len(); ecs_option.push((option_length >> 8) as u8);
ecs_option.push(option_length as u8);
ecs_option.push((family >> 8) as u8);
ecs_option.push(family as u8);
ecs_option.push(source_prefix_length);
ecs_option.push(0);
ecs_option.extend_from_slice(&truncated_address);
if DNS_MAX_PACKET_SIZE.saturating_sub(packet.len()) < ecs_option.len() {
return Err(DnsError::PacketTooLarge {
size: packet.len(),
max_size: DNS_MAX_PACKET_SIZE,
});
}
let new_rdlen = rdlen + ecs_option.len();
BigEndian::write_u16(
&mut packet[rdlen_offset..rdlen_offset + 2],
new_rdlen as u16,
);
packet.splice(rdata_offset + rdlen..rdata_offset + rdlen, ecs_option);
Ok(())
}
pub fn set_edns_max_payload_size(packet: &mut Vec<u8>, max_payload_size: u16) -> DnsResult<()> {
let packet_len = packet.len();
if packet_len <= DNS_OFFSET_QUESTION {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if packet_len > DNS_MAX_PACKET_SIZE {
return Err(DnsError::PacketTooLarge {
size: packet_len,
max_size: DNS_MAX_PACKET_SIZE,
});
}
if qdcount(packet) == 0 {
return Err(DnsError::InvalidPacket("No question".to_string()));
}
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
if packet_len - offset < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset += 4;
let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet));
offset = traverse_rrs(packet, offset, ancount as usize + nscount as usize, |_| {
Ok(())
})?;
let mut edns_payload_set = false;
traverse_rrs_mut(packet, offset, arcount as usize, |packet, offset| {
let qtype = BigEndian::read_u16(&packet[offset..]);
if qtype == DNS_TYPE_OPT {
if edns_payload_set {
return Err(DnsError::InvalidEdns("Duplicate OPT RR found".to_string()));
}
BigEndian::write_u16(&mut packet[offset + 2..], max_payload_size);
edns_payload_set = true;
}
Ok(())
})?;
if !edns_payload_set {
add_edns_section(packet, max_payload_size)?;
}
Ok(())
}
pub fn extract_edns_version(packet: &[u8]) -> DnsResult<Option<u8>> {
let packet_len = packet.len();
if packet_len <= DNS_OFFSET_QUESTION {
return Ok(None);
}
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
if packet_len - offset < 4 {
return Ok(None);
}
offset += 4;
let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet));
offset = traverse_rrs(packet, offset, ancount as usize + nscount as usize, |_| {
Ok(())
})?;
let mut edns_version = None;
traverse_rrs(packet, offset, arcount as usize, |offset| {
if offset.checked_add(8).is_none_or(|sum| sum > packet_len) {
return Ok(()); }
let rr_type = BigEndian::read_u16(&packet[offset..offset + 2]);
if rr_type == DNS_TYPE_OPT {
let ttl_bytes = &packet[offset + 4..offset + 8];
edns_version = Some(ttl_bytes[1]); }
Ok(())
})?;
Ok(edns_version)
}
pub fn validate_dns_response(packet: &[u8]) -> DnsResult<()> {
validate_dns_packet(packet)?;
if !is_response(packet) {
return Err(DnsError::InvalidPacket("Not a response".to_string()));
}
let qdcount = qdcount(packet);
let rcode = rcode(packet);
if qdcount != 1 && rcode == 0 {
return Err(DnsError::InvalidPacket(
"No question in successful response".to_string(),
));
}
Ok(())
}
pub fn set_tid(packet: &mut [u8], tid: u16) -> DnsResult<()> {
if packet.len() < 2 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
packet[0] = (tid >> 8) as u8;
packet[1] = tid as u8;
Ok(())
}
#[inline]
pub fn flags(packet: &[u8]) -> u16 {
if packet.len() < 4 {
return 0; }
BigEndian::read_u16(&packet[2..4])
}
pub fn set_flags(packet: &mut [u8], flags: u16) -> DnsResult<()> {
if packet.len() < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
packet[2] = (flags >> 8) as u8;
packet[3] = flags as u8;
Ok(())
}
pub fn set_qr(packet: &mut [u8], is_response: bool) -> DnsResult<()> {
if packet.len() < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let mut flags_val = flags(packet);
if is_response {
flags_val |= DNS_FLAGS_QR; } else {
flags_val &= !DNS_FLAGS_QR; }
set_flags(packet, flags_val)
}
pub fn set_tc(packet: &mut [u8], is_truncated: bool) -> DnsResult<()> {
if packet.len() < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let mut flags_val = flags(packet);
if is_truncated {
flags_val |= DNS_FLAGS_TC; } else {
flags_val &= !DNS_FLAGS_TC; }
set_flags(packet, flags_val)
}
pub fn set_aa(packet: &mut [u8], is_authoritative: bool) -> DnsResult<()> {
if packet.len() < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let mut flags_val = flags(packet);
if is_authoritative {
flags_val |= DNS_FLAGS_AA; } else {
flags_val &= !DNS_FLAGS_AA; }
set_flags(packet, flags_val)
}
pub fn set_ra(packet: &mut [u8], recursion_available: bool) -> DnsResult<()> {
if packet.len() < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let mut flags_val = flags(packet);
if recursion_available {
flags_val |= DNS_FLAGS_RA; } else {
flags_val &= !DNS_FLAGS_RA; }
set_flags(packet, flags_val)
}
pub fn set_ad(packet: &mut [u8], authenticated_data: bool) -> DnsResult<()> {
if packet.len() < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let mut current_flags = flags(packet);
if authenticated_data {
current_flags |= DNS_FLAGS_AD;
} else {
current_flags &= !DNS_FLAGS_AD;
}
set_flags(packet, current_flags)
}
pub fn set_ancount(packet: &mut [u8], count: u16) -> DnsResult<()> {
if packet.len() < 8 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
BigEndian::write_u16(&mut packet[6..8], count);
Ok(())
}
pub fn set_nscount(packet: &mut [u8], count: u16) -> DnsResult<()> {
if packet.len() < 10 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
BigEndian::write_u16(&mut packet[8..10], count);
Ok(())
}
pub fn set_arcount(packet: &mut [u8], count: u16) -> DnsResult<()> {
if packet.len() < DNS_PACKET_LEN_MIN {
return Err(DnsError::PacketTooShort { offset: 0 });
}
BigEndian::write_u16(&mut packet[10..DNS_PACKET_LEN_MIN], count);
Ok(())
}
pub fn set_rcode(packet: &mut [u8], rcode: u8) -> DnsResult<()> {
if packet.len() < DNS_PACKET_LEN_MIN {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if rcode <= 0x0F {
packet[3] = (packet[3] & 0xF0) | (rcode & 0x0F);
Ok(())
} else {
packet[3] &= 0xF0;
Ok(())
}
}
pub fn extract_edns0_max_size(packet: &[u8]) -> DnsResult<Option<u16>> {
let packet_len = packet.len();
if packet_len < DNS_PACKET_LEN_MIN {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if packet_len > DNS_MAX_PACKET_SIZE {
return Err(DnsError::PacketTooLarge {
size: packet_len,
max_size: DNS_MAX_PACKET_SIZE,
});
}
let arcount_val = arcount(packet);
if arcount_val == 0 {
return Ok(None);
}
let qdcount_val = qdcount(packet);
if qdcount_val == 0 {
return Err(DnsError::InvalidPacket("No question".to_string()));
}
let mut offset = DNS_HEADER_SIZE;
for _ in 0..qdcount_val {
match skip_name(packet, offset) {
Ok(new_offset) => {
offset = new_offset;
if packet_len - offset < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset += 4;
}
Err(_) => {
return Ok(None);
}
}
}
let (ancount_val, nscount_val) = (ancount(packet), nscount(packet));
match traverse_rrs(
packet,
offset,
ancount_val as usize + nscount_val as usize,
|_| Ok(()),
) {
Ok(new_offset) => {
offset = new_offset;
}
Err(_) => {
return Ok(None);
}
}
let mut max_size: Option<u16> = None;
match traverse_rrs(packet, offset, arcount_val as usize, |offset| {
if packet_len - offset < 2 {
return Ok(());
}
let qtype = BigEndian::read_u16(&packet[offset..]);
if qtype == DNS_TYPE_OPT {
if packet_len - offset < 4 {
return Ok(());
}
let size = BigEndian::read_u16(&packet[offset + 2..]);
if max_size.is_none() {
max_size = Some(size);
}
}
Ok(())
}) {
Ok(_) => {}
Err(_) => {
return Ok(None);
}
}
Ok(max_size)
}
pub fn truncate_dns_packet(
packet: &[u8],
max_size: Option<usize>,
force_response: bool,
) -> DnsResult<Vec<u8>> {
if packet.len() < DNS_PACKET_LEN_MIN {
return Err(DnsError::PacketTooShort { offset: 0 });
}
if let Some(size) = max_size
&& packet.len() <= size
{
return Ok(packet.to_vec());
}
let mut truncated_data = packet.to_vec();
let mut offset = DNS_HEADER_SIZE;
let qdcount = qdcount(&truncated_data);
if qdcount == 0 {
return Err(DnsError::InvalidPacket("No question in packet".to_string()));
}
for _ in 0..qdcount {
offset = skip_name(&truncated_data, offset)?;
if truncated_data.len() - offset < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset += 4; }
truncated_data.truncate(offset);
if force_response && !is_response(&truncated_data) {
set_qr(&mut truncated_data, true)?;
}
set_tc(&mut truncated_data, true)?;
set_ancount(&mut truncated_data, 0)?;
set_nscount(&mut truncated_data, 0)?;
set_arcount(&mut truncated_data, 0)?;
Ok(truncated_data)
}
#[allow(dead_code)]
pub fn create_truncated_response(query: &[u8]) -> DnsResult<Vec<u8>> {
if is_response(query) {
return Err(DnsError::InvalidPacket(
"Expected a query, got a response".to_string(),
));
}
truncate_dns_packet(query, None, true)
}
#[allow(dead_code)]
pub fn truncate_response(response: &[u8], max_size: usize) -> DnsResult<Vec<u8>> {
if !is_response(response) {
return Err(DnsError::InvalidPacket(
"Expected a response, got a query".to_string(),
));
}
truncate_dns_packet(response, Some(max_size), false)
}
pub fn recover_question_from_response(response: &[u8]) -> DnsResult<Vec<u8>> {
if response.len() < DNS_PACKET_LEN_MIN {
return Err(DnsError::PacketTooShort { offset: 0 });
}
let mut query_data = response.to_vec();
set_qr(&mut query_data, false)?;
set_tc(&mut query_data, false)?;
let qdcount = qdcount(&query_data);
if qdcount == 0 {
return Err(DnsError::InvalidPacket(
"No question in response".to_string(),
));
}
let mut offset = DNS_HEADER_SIZE;
for _ in 0..qdcount {
offset = skip_name(&query_data, offset)?;
if query_data.len() - offset < 4 {
return Err(DnsError::PacketTooShort { offset: 0 });
}
offset = offset.checked_add(4).ok_or_else(|| {
DnsError::InvalidPacket("Integer overflow advancing past QTYPE/QCLASS".to_string())
})?;
}
query_data.truncate(offset);
set_ancount(&mut query_data, 0)?;
set_nscount(&mut query_data, 0)?;
set_arcount(&mut query_data, 0)?;
Ok(query_data)
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_query() -> Vec<u8> {
let mut packet = vec![
0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
packet.push(0x07); for b in b"example" {
packet.push(*b);
}
packet.push(0x03); for b in b"com" {
packet.push(*b);
}
packet.push(0x00);
packet.push(0x00);
packet.push(0x01);
packet.push(0x00);
packet.push(0x01);
packet
}
fn create_test_query_with_opcode(opcode: u8) -> Vec<u8> {
let mut packet = create_test_query();
packet[2] = (packet[2] & 0x87) | ((opcode & 0x0F) << 3);
packet
}
fn create_test_query_with_edns0(max_size: u16) -> Vec<u8> {
let mut packet = create_test_query();
packet[11] = 0x01;
packet.push(0x00);
packet.push(0x00);
packet.push(0x29);
packet.push((max_size >> 8) as u8);
packet.push(max_size as u8);
packet.push(0x00); packet.push(0x00); packet.push(0x00); packet.push(0x00);
packet.push(0x00);
packet.push(0x00);
packet
}
#[test]
fn test_extract_edns0_max_size() {
let query = create_test_query();
println!("Query length: {}", query.len());
let result = match extract_edns0_max_size(&query) {
Ok(r) => r,
Err(e) => {
println!("Error extracting EDNS0 max size from query: {:?}", e);
panic!("Failed to extract EDNS0 max size from query");
}
};
assert_eq!(result, None);
let query_with_edns0 = create_test_query_with_edns0(4096);
println!("Query with EDNS0 length: {}", query_with_edns0.len());
let edns0_size = extract_edns0_max_size(&query_with_edns0).unwrap();
assert_eq!(edns0_size, Some(4096));
println!("EDNS0 packet: {:?}", query_with_edns0);
assert_eq!(query_with_edns0[11], 1); assert_eq!(query_with_edns0[29], 0); assert_eq!(query_with_edns0[30], 0); assert_eq!(query_with_edns0[31], 41); assert_eq!(query_with_edns0[32], 16); assert_eq!(query_with_edns0[33], 0);
let max_size = (query_with_edns0[32] as u16) << 8 | query_with_edns0[33] as u16;
assert_eq!(max_size, 4096);
}
#[test]
fn test_change_ttl_preserves_opt_metadata() {
let mut response = create_test_query();
set_qr(&mut response, true).unwrap();
set_ancount(&mut response, 1).unwrap();
response.extend_from_slice(&[
0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x04, 192, 0, 2, 1,
]);
add_edns_section(&mut response, 1232).unwrap();
let opt_start = response.len() - 11;
response[opt_start + 5..opt_start + 9].copy_from_slice(&[0x12, 0x00, 0x80, 0x00]);
change_ttl(&mut response, 15).unwrap();
let answer_ttl_offset = 29 + 2 + 4;
assert_eq!(
&response[answer_ttl_offset..answer_ttl_offset + 4],
&[0, 0, 0, 15]
);
assert_eq!(
&response[opt_start + 5..opt_start + 9],
&[0x12, 0x00, 0x80, 0x00]
);
}
#[test]
fn test_decrement_ttl_preserves_individual_ttls_and_opt() {
let mut response = create_test_query();
set_qr(&mut response, true).unwrap();
set_ancount(&mut response, 2).unwrap();
response.extend_from_slice(&[
0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x04, 192, 0, 2, 1,
0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x04, 192, 0, 2, 2,
]);
add_edns_section(&mut response, 1232).unwrap();
let opt_start = response.len() - 11;
response[opt_start + 5..opt_start + 9].copy_from_slice(&[0x12, 0x00, 0x80, 0x00]);
decrement_ttl(&mut response, 10).unwrap();
assert_eq!(BigEndian::read_u32(&response[35..39]), 290);
assert_eq!(BigEndian::read_u32(&response[51..55]), 50);
assert_eq!(
&response[opt_start + 5..opt_start + 9],
&[0x12, 0x00, 0x80, 0x00]
);
}
#[test]
fn test_question_matching() {
let query = create_test_query();
let mut response = query.clone();
set_qr(&mut response, true).unwrap();
assert!(questions_match(&query, &response).unwrap());
let qtype_offset = response.len() - 4;
response[qtype_offset..qtype_offset + 2].copy_from_slice(&[0, 28]);
assert!(!questions_match(&query, &response).unwrap());
}
#[test]
fn test_dot_inside_label_is_rejected() {
let mut dotted = create_test_query();
dotted[12] = 11;
dotted[20] = b'.';
assert!(validate_dns_packet(&dotted).is_err());
}
#[test]
fn test_trailing_data_is_not_part_of_the_message() {
let query = create_test_query();
assert_eq!(validate_dns_packet(&query).unwrap(), query.len());
let mut with_trailing_opt = query.clone();
add_edns_section(&mut with_trailing_opt, 1232).unwrap();
set_arcount(&mut with_trailing_opt, 0).unwrap();
assert_eq!(
validate_dns_packet(&with_trailing_opt).unwrap(),
query.len()
);
}
#[test]
fn test_invalid_opt_layout_is_rejected() {
let mut duplicate = create_test_query();
add_edns_section(&mut duplicate, 1232).unwrap();
let opt = duplicate[duplicate.len() - 11..].to_vec();
duplicate.extend_from_slice(&opt);
set_arcount(&mut duplicate, 2).unwrap();
assert!(validate_dns_packet(&duplicate).is_err());
let mut named = create_test_query();
add_edns_section(&mut named, 1232).unwrap();
let opt_start = named.len() - 11;
named.splice(opt_start..=opt_start, [1, b'x', 0]);
assert!(validate_dns_packet(&named).is_err());
}
#[test]
fn test_forward_compression_pointer_is_rejected() {
let mut response = create_test_query();
set_qr(&mut response, true).unwrap();
set_ancount(&mut response, 1).unwrap();
let answer_start = response.len();
let pointer = answer_start + 2;
response.extend_from_slice(&[
0xc0 | ((pointer >> 8) as u8),
pointer as u8,
0x00,
0x01,
0x00,
0x01,
0x00,
0x00,
0x00,
0x3c,
0x00,
0x04,
192,
0,
2,
1,
]);
assert!(validate_dns_packet(&response).is_err());
}
#[test]
fn test_wire_name_length_boundary() {
fn query_with_labels(lengths: &[usize]) -> Vec<u8> {
let mut query = vec![
0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
for &length in lengths {
query.push(length as u8);
query.extend(std::iter::repeat_n(b'a', length));
}
query.extend_from_slice(&[0, 0, 1, 0, 1]);
query
}
assert!(validate_dns_packet(&query_with_labels(&[63, 63, 63, 61])).is_ok());
assert!(validate_dns_packet(&query_with_labels(&[63, 63, 63, 62])).is_err());
}
#[test]
fn test_ecs_is_replaced_and_can_be_removed() {
let mut query = create_test_query();
add_edns_section(&mut query, 1232).unwrap();
add_edns_client_subnet(&mut query, "192.0.2.129", 24, 56, 1232).unwrap();
add_edns_client_subnet(&mut query, "198.51.100.9", 24, 56, 1232).unwrap();
let ecs = extract_edns_client_subnet(&query).unwrap().unwrap();
assert_eq!(ecs.address, vec![198, 51, 100]);
remove_edns_client_subnet(&mut query).unwrap();
assert!(extract_edns_client_subnet(&query).unwrap().is_none());
assert_eq!(arcount(&query), 1);
}
#[test]
fn test_truncate_dns_packet() {
let query = create_test_query();
let truncated_response = truncate_dns_packet(&query, None, true).unwrap();
assert!(is_response(&truncated_response));
assert!(is_truncated(&truncated_response));
assert_eq!(tid(&truncated_response), 0x1234);
assert_eq!(qdcount(&truncated_response), 1);
assert_eq!(ancount(&truncated_response), 0);
assert_eq!(nscount(&truncated_response), 0);
assert_eq!(arcount(&truncated_response), 0);
let mut response = query.to_vec();
set_qr(&mut response, true).unwrap();
response.extend_from_slice(&[0; 100]);
let max_size = 50;
let truncated_response = truncate_dns_packet(&response, Some(max_size), false).unwrap();
assert!(truncated_response.len() <= max_size);
assert!(is_response(&truncated_response));
assert!(is_truncated(&truncated_response));
}
#[test]
fn test_recover_question_from_response() {
let query = create_test_query();
let truncated_response = truncate_dns_packet(&query, None, true).unwrap();
let recovered_query = recover_question_from_response(&truncated_response).unwrap();
assert!(!is_response(&recovered_query));
assert!(!is_truncated(&recovered_query));
assert_eq!(tid(&recovered_query), 0x1234);
assert_eq!(qdcount(&recovered_query), 1);
assert_eq!(ancount(&recovered_query), 0);
assert_eq!(nscount(&recovered_query), 0);
assert_eq!(arcount(&recovered_query), 0);
}
#[test]
fn test_recover_question_truncates_extra_data() {
let query = create_test_query();
let mut response = query.clone();
set_qr(&mut response, true).unwrap();
set_tc(&mut response, true).unwrap();
response.extend_from_slice(&[0u8; 32]);
let recovered_query = recover_question_from_response(&response).unwrap();
assert_eq!(recovered_query.len(), query.len());
}
#[test]
fn test_is_dnssec_requested_short_packet() {
let result = is_dnssec_requested(&[0u8; 3]);
assert!(matches!(result, Err(DnsError::PacketTooShort { .. })));
}
#[test]
fn test_opcode_validation() {
let query = create_test_query();
assert_eq!(opcode(&query), DNS_OPCODE_QUERY);
assert!(is_standard_query(&query));
let result = validate_dns_packet(&query);
assert!(result.is_ok());
let update_query = create_test_query_with_opcode(DNS_OPCODE_UPDATE);
assert_eq!(opcode(&update_query), DNS_OPCODE_UPDATE);
assert!(!is_standard_query(&update_query));
let result = validate_dns_packet(&update_query);
assert!(result.is_err());
match result {
Err(DnsError::UnsupportedOperation(msg)) => {
assert!(msg.contains("Unsupported opcode: 5"));
}
_ => panic!("Expected UnsupportedOperation error"),
}
}
#[test]
fn test_domain_name_validation() {
let valid_labels = [
"example",
"test123",
"a-valid-label",
"123valid",
"valid123",
];
for label in &valid_labels {
assert!(is_valid_domain_name_char(
label.as_bytes()[0],
0,
label.len()
));
assert!(is_valid_domain_name_char(
label.as_bytes()[label.len() - 1],
label.len() - 1,
label.len()
));
let label_bytes = label.as_bytes();
let label_len = label_bytes.len();
let mut is_valid = true;
if label_len == 0 {
is_valid = false;
}
if label_len > 63 {
is_valid = false;
}
for (i, &c) in label_bytes.iter().enumerate() {
if !is_valid_domain_name_char(c, i, label_len) {
is_valid = false;
break;
}
}
assert!(is_valid, "Label '{}' should be valid", label);
}
let invalid_labels = [
"-invalid", "invalid-", "inva@lid", "invalid label", "", ];
for label in &invalid_labels {
if !label.is_empty() {
if label.starts_with('-') {
assert!(!is_valid_domain_name_char(
label.as_bytes()[0],
0,
label.len()
));
}
if label.ends_with('-') {
assert!(!is_valid_domain_name_char(
label.as_bytes()[label.len() - 1],
label.len() - 1,
label.len()
));
}
}
let label_bytes = label.as_bytes();
let label_len = label_bytes.len();
let mut is_valid = true;
if label_len == 0 {
is_valid = false;
} else {
if label_len > 63 {
is_valid = false;
}
for (i, &c) in label_bytes.iter().enumerate() {
if !is_valid_domain_name_char(c, i, label_len) {
is_valid = false;
break;
}
}
}
assert!(!is_valid, "Label '{}' should be invalid", label);
}
}
#[test]
fn test_extract_edns_version() {
let query = create_test_query();
let version = extract_edns_version(&query).unwrap();
assert_eq!(version, None);
let mut query_with_edns = query.clone();
add_edns_section(&mut query_with_edns, 1232).unwrap();
let version = extract_edns_version(&query_with_edns).unwrap();
assert_eq!(version, Some(0));
let mut query_with_edns_v1 = create_test_query();
let opt_record: Vec<u8> = vec![
0, 0, 41, 0x04, 0xd0, 0, 1, 0, 0, 0, 0, ];
let arcount_offset = 10;
assert!(
query_with_edns_v1.len() >= arcount_offset + 2,
"Packet too short for ARCOUNT"
);
let arcount = BigEndian::read_u16(&query_with_edns_v1[arcount_offset..arcount_offset + 2]);
BigEndian::write_u16(
&mut query_with_edns_v1[arcount_offset..arcount_offset + 2],
arcount + 1,
);
query_with_edns_v1.extend_from_slice(&opt_record);
let version = extract_edns_version(&query_with_edns_v1).unwrap();
assert_eq!(version, Some(1));
}
#[test]
fn test_validate_dns_packet_with_edns_version() {
let mut query = create_test_query();
add_edns_section(&mut query, 1232).unwrap();
let result = validate_dns_packet(&query);
assert!(result.is_ok());
let mut query_with_edns_v1 = create_test_query();
let opt_record: Vec<u8> = vec![
0, 0, 41, 0x04, 0xd0, 0, 1, 0, 0, 0, 0, ];
let arcount_offset = 10;
assert!(
query_with_edns_v1.len() >= arcount_offset + 2,
"Packet too short for ARCOUNT"
);
let arcount = BigEndian::read_u16(&query_with_edns_v1[arcount_offset..arcount_offset + 2]);
BigEndian::write_u16(
&mut query_with_edns_v1[arcount_offset..arcount_offset + 2],
arcount + 1,
);
query_with_edns_v1.extend_from_slice(&opt_record);
let result = validate_dns_packet(&query_with_edns_v1);
assert!(result.is_err());
match result {
Err(DnsError::UnsupportedEdnsVersion { version }) => {
assert_eq!(version, 1);
}
_ => panic!("Expected UnsupportedEdnsVersion error"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[allow(dead_code)]
pub struct EdnsClientSubnet {
pub family: u16,
pub source_prefix_length: u8,
pub scope_prefix_length: u8,
pub address: Vec<u8>,
}
pub fn remove_edns_client_subnet(packet: &mut Vec<u8>) -> DnsResult<()> {
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
if packet.len().saturating_sub(offset) < 4 {
return Err(DnsError::PacketTooShort { offset });
}
offset += 4;
offset = traverse_rrs(
packet,
offset,
ancount(packet) as usize + nscount(packet) as usize,
|_| Ok(()),
)?;
for _ in 0..arcount(packet) {
let rr_offset = skip_name(packet, offset)?;
if packet.len().saturating_sub(rr_offset) < 10 {
return Err(DnsError::PacketTooShort { offset: rr_offset });
}
let rr_type = BigEndian::read_u16(&packet[rr_offset..rr_offset + 2]);
let mut rdlen = BigEndian::read_u16(&packet[rr_offset + 8..rr_offset + 10]) as usize;
let rdata_offset = rr_offset + 10;
if packet.len().saturating_sub(rdata_offset) < rdlen {
return Err(DnsError::InvalidRecord(
"OPT record data exceeds packet".to_string(),
));
}
if rr_type == DNS_TYPE_OPT {
let mut option_offset = rdata_offset;
while option_offset < rdata_offset + rdlen {
if rdata_offset + rdlen - option_offset < 4 {
return Err(DnsError::InvalidEdns(
"Truncated EDNS option header".to_string(),
));
}
let option_code = BigEndian::read_u16(&packet[option_offset..option_offset + 2]);
let option_len =
BigEndian::read_u16(&packet[option_offset + 2..option_offset + 4]) as usize;
let option_end = option_offset.checked_add(4 + option_len).ok_or_else(|| {
DnsError::InvalidEdns("EDNS option length overflow".to_string())
})?;
if option_end > rdata_offset + rdlen {
return Err(DnsError::InvalidEdns(
"EDNS option exceeds OPT record data".to_string(),
));
}
if option_code == EDNS_OPTION_CLIENT_SUBNET {
packet.drain(option_offset..option_end);
rdlen -= option_end - option_offset;
continue;
}
option_offset = option_end;
}
BigEndian::write_u16(&mut packet[rr_offset + 8..rr_offset + 10], rdlen as u16);
}
offset = rdata_offset + rdlen;
}
Ok(())
}
pub fn remove_out_of_bailiwick_glue(packet: &mut Vec<u8>) -> DnsResult<usize> {
let arcount = arcount(packet);
if flags(packet) & DNS_FLAGS_AA != 0
|| rcode(packet) != 0
|| qdcount(packet) != 1
|| ancount(packet) != 0
|| nscount(packet) == 0
|| arcount == 0
{
return Ok(0);
}
let qname = name_labels(packet, DNS_OFFSET_QUESTION)?;
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)? + 4;
let mut delegation_depth = None;
for _ in 0..nscount(packet) {
let record = record_at(packet, offset)?;
if record.rr_type == DNS_TYPE_NS {
let owner = name_labels(packet, offset)?;
if is_subdomain(&qname, &owner) {
delegation_depth = delegation_depth.max(Some(owner.len()));
}
}
offset = record.end;
}
let Some(depth) = delegation_depth else {
return Ok(0);
};
let bailiwick = &qname[qname.len() - depth.saturating_sub(1)..];
let additional_start = offset;
let mut filtered = packet[..additional_start].to_vec();
let mut removed = 0u16;
for _ in 0..arcount {
let record = record_at(packet, offset)?;
let is_address = match record.rr_type {
DNS_TYPE_A | DNS_TYPE_AAAA => true,
DNS_TYPE_RRSIG => {
record.end - record.header >= 12
&& matches!(
BigEndian::read_u16(&packet[record.header + 10..]),
DNS_TYPE_A | DNS_TYPE_AAAA
)
}
_ => false,
};
if record.rr_type == DNS_TYPE_OPT
|| (is_address
&& record.rr_class == DNS_CLASS_IN
&& is_subdomain(&name_labels(packet, offset)?, bailiwick))
{
copy_name(packet, offset, additional_start, &mut filtered)?;
filtered.extend_from_slice(&packet[record.header..record.end]);
} else {
removed += 1;
}
offset = record.end;
}
if removed == 0 {
return Ok(0);
}
if filtered.len() > DNS_MAX_PACKET_SIZE {
return Err(DnsError::PacketTooLarge {
size: filtered.len(),
max_size: DNS_MAX_PACKET_SIZE,
});
}
set_arcount(&mut filtered, arcount - removed)?;
*packet = filtered;
Ok(usize::from(removed))
}
struct Record {
rr_type: u16,
rr_class: u16,
header: usize,
end: usize,
}
fn record_at(packet: &[u8], offset: usize) -> DnsResult<Record> {
let header = skip_name(packet, offset)?;
if packet.len().saturating_sub(header) < 10 {
return Err(DnsError::PacketTooShort { offset: header });
}
let end = header + 10 + BigEndian::read_u16(&packet[header + 8..]) as usize;
if end > packet.len() {
return Err(DnsError::InvalidRecord(
"Record length would exceed packet length".to_string(),
));
}
Ok(Record {
rr_type: BigEndian::read_u16(&packet[header..]),
rr_class: BigEndian::read_u16(&packet[header + 2..]),
header,
end,
})
}
fn name_labels(packet: &[u8], mut offset: usize) -> DnsResult<Vec<&[u8]>> {
skip_name(packet, offset)?;
let mut labels = Vec::new();
loop {
let len = packet[offset] as usize;
if len & 0xc0 == 0xc0 {
offset = ((len & 0x3f) << 8) | packet[offset + 1] as usize;
} else if len == 0 {
return Ok(labels);
} else {
labels.push(&packet[offset + 1..=offset + len]);
offset += 1 + len;
}
}
}
fn is_subdomain(name: &[&[u8]], zone: &[&[u8]]) -> bool {
name.len() >= zone.len()
&& name[name.len() - zone.len()..]
.iter()
.zip(zone)
.all(|(a, b)| a.eq_ignore_ascii_case(b))
}
fn copy_name(packet: &[u8], mut offset: usize, limit: usize, out: &mut Vec<u8>) -> DnsResult<()> {
skip_name(packet, offset)?;
loop {
let len = packet[offset] as usize;
if len & 0xc0 == 0xc0 {
let pointer = ((len & 0x3f) << 8) | packet[offset + 1] as usize;
if name_ends_before(packet, pointer, limit) {
out.extend_from_slice(&packet[offset..offset + 2]);
return Ok(());
}
offset = pointer;
} else {
out.extend_from_slice(&packet[offset..=offset + len]);
if len == 0 {
return Ok(());
}
offset += 1 + len;
}
}
}
fn name_ends_before(packet: &[u8], mut offset: usize, limit: usize) -> bool {
loop {
if offset >= limit {
return false;
}
let len = packet[offset] as usize;
if len & 0xc0 == 0xc0 {
if offset + 1 >= limit {
return false;
}
offset = ((len & 0x3f) << 8) | packet[offset + 1] as usize;
} else if len == 0 {
return true;
} else {
offset += 1 + len;
}
}
}
pub fn edns_client_subnet_matches(query: &[u8], response: &[u8]) -> DnsResult<bool> {
let Some(response_ecs) = extract_edns_client_subnet(response)? else {
return Ok(true);
};
let Some(query_ecs) = extract_edns_client_subnet(query)? else {
return Ok(false);
};
Ok(response_ecs.family == query_ecs.family
&& response_ecs.source_prefix_length == query_ecs.source_prefix_length
&& response_ecs.address == query_ecs.address)
}
#[allow(dead_code)]
pub fn extract_edns_client_subnet(packet: &[u8]) -> DnsResult<Option<EdnsClientSubnet>> {
let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?;
offset += 4;
let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet));
offset = traverse_rrs(packet, offset, ancount as usize + nscount as usize, |_| {
Ok(())
})?;
let mut ecs_info = None;
traverse_rrs(packet, offset, arcount as usize, |rr_offset| {
let qtype = BigEndian::read_u16(&packet[rr_offset..]);
if qtype == DNS_TYPE_OPT {
let rdlen = BigEndian::read_u16(&packet[rr_offset + 8..rr_offset + 10]) as usize;
let rdata_offset = rr_offset + 10;
let rdata_end = rdata_offset + rdlen;
let mut option_offset = rdata_offset;
while option_offset < rdata_end {
if option_offset + 4 > rdata_end {
break;
}
let option_code = BigEndian::read_u16(&packet[option_offset..option_offset + 2]);
let option_len =
BigEndian::read_u16(&packet[option_offset + 2..option_offset + 4]) as usize;
option_offset += 4;
if option_offset + option_len > rdata_end {
break;
}
if option_code == EDNS_OPTION_CLIENT_SUBNET {
if option_len < 4 {
break;
}
let family = BigEndian::read_u16(&packet[option_offset..option_offset + 2]);
let source_prefix_length = packet[option_offset + 2];
let scope_prefix_length = packet[option_offset + 3];
let address_offset = option_offset + 4;
let address_len = option_len - 4;
let max_prefix = match family {
1 => 32,
2 => 128,
_ => u8::MAX,
};
if source_prefix_length > max_prefix {
break;
}
let expected_address_len = (source_prefix_length as usize).div_ceil(8);
if address_len < expected_address_len {
break;
}
let address =
packet[address_offset..address_offset + expected_address_len].to_vec();
ecs_info = Some(EdnsClientSubnet {
family,
source_prefix_length,
scope_prefix_length,
address,
});
break;
}
option_offset += option_len;
}
}
Ok(())
})?;
Ok(ecs_info)
}
#[cfg(test)]
mod overflow_tests {
use super::*;
use crate::errors::DnsError;
#[test]
fn test_offset_overflow_in_qname() {
let mut packet = vec![0u8; DNS_HEADER_SIZE + 10];
packet[4] = 0;
packet[5] = 1;
packet[DNS_HEADER_SIZE] = 64;
let result = qname(&packet);
assert!(matches!(result, Err(DnsError::LabelTooLong { .. })));
}
#[test]
fn test_offset_overflow_in_skip_name() {
let mut packet = vec![0u8; 20];
packet[4] = 0;
packet[5] = 1;
packet[12] = 63;
let result = skip_name(&packet, 12);
assert!(
result.is_err(),
"Expected error when label extends beyond packet"
);
}
#[test]
fn test_dns_packet_offset_boundary() {
let mut packet = vec![0u8; DNS_HEADER_SIZE + 5];
packet[4] = 0;
packet[5] = 1;
packet[DNS_HEADER_SIZE] = 0; packet[DNS_HEADER_SIZE + 1] = 0; packet[DNS_HEADER_SIZE + 2] = 1; packet[DNS_HEADER_SIZE + 3] = 0; packet[DNS_HEADER_SIZE + 4] = 1;
let result = validate_dns_packet(&packet);
assert!(result.is_ok());
}
#[test]
fn test_ecs_helpers_reject_malformed_packets_without_panicking() {
let root_query_with_opt = |rdlen: u16| {
let mut packet = vec![
0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x02, 0x00, 0x01, 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00, 0x00, 0x00,
];
packet.extend_from_slice(&rdlen.to_be_bytes());
packet
};
let mut packet = root_query_with_opt(0xffff);
packet.extend_from_slice(&[0x00, 0x08]);
assert!(extract_edns_client_subnet(&packet).is_err());
let padding_len = DNS_MAX_PACKET_SIZE;
let mut packet = root_query_with_opt((padding_len + 4) as u16);
packet.extend_from_slice(&[0x00, 0x0c]);
packet.extend_from_slice(&(padding_len as u16).to_be_bytes());
packet.resize(packet.len() + padding_len, 0);
assert!(matches!(
add_edns_client_subnet(&mut packet, "192.0.2.1", 24, 56, 1232),
Err(DnsError::PacketTooLarge { .. })
));
}
}
#[cfg(test)]
mod bailiwick_tests {
use super::*;
const QNAME: &str = "www.sub.EXAMPLE.com";
fn wire_name(name: &str) -> Vec<u8> {
let mut out = Vec::new();
for label in name.split('.').filter(|label| !label.is_empty()) {
out.push(label.len() as u8);
out.extend_from_slice(label.as_bytes());
}
out.push(0);
out
}
fn pointer(offset: usize) -> [u8; 2] {
(0xc000 | offset as u16).to_be_bytes()
}
fn rr(owner: &[u8], rr_type: u16, rdata: &[u8]) -> Vec<u8> {
let mut out = owner.to_vec();
out.extend_from_slice(&rr_type.to_be_bytes());
out.extend_from_slice(&DNS_CLASS_IN.to_be_bytes());
out.extend_from_slice(&300u32.to_be_bytes());
out.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
out.extend_from_slice(rdata);
out
}
fn referral(authority: &[Vec<u8>], additional: &[Vec<u8>]) -> Vec<u8> {
let mut packet = vec![0x12, 0x34, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00];
packet.extend_from_slice(&(authority.len() as u16).to_be_bytes());
packet.extend_from_slice(&(additional.len() as u16).to_be_bytes());
packet.extend_from_slice(&wire_name(QNAME));
packet.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]);
for record in authority.iter().chain(additional) {
packet.extend_from_slice(record);
}
packet
}
fn delegation(ns: &[u8]) -> Vec<u8> {
rr(&pointer(16), DNS_TYPE_NS, ns)
}
fn first_ns_data() -> usize {
referral(&[], &[]).len() + 12
}
#[test]
fn test_only_glue_under_the_parent_zone_is_kept() {
let authority = [
delegation(&wire_name("ns1.sub.example.com")),
delegation(&wire_name("ns2.example.com")),
];
let in_domain = rr(&pointer(first_ns_data()), DNS_TYPE_A, &[192, 0, 2, 1]);
let sibling = rr(&wire_name("ns2.example.com"), DNS_TYPE_AAAA, &[0x20; 16]);
let sibling_signature = rr(
&wire_name("ns2.example.com"),
DNS_TYPE_RRSIG,
&DNS_TYPE_AAAA.to_be_bytes(),
);
let unrelated = rr(&wire_name("ns.example.net"), DNS_TYPE_A, &[198, 51, 100, 1]);
let not_an_address = rr(&wire_name("ns1.sub.example.com"), DNS_TYPE_TXT, b"\x02hi");
let mut packet = referral(
&authority,
&[
in_domain.clone(),
unrelated,
sibling.clone(),
not_an_address,
sibling_signature.clone(),
],
);
let mut expected = referral(&authority, &[in_domain, sibling, sibling_signature]);
add_edns_section(&mut packet, 1232).unwrap();
add_edns_section(&mut expected, 1232).unwrap();
assert_eq!(remove_out_of_bailiwick_glue(&mut packet).unwrap(), 2);
assert_eq!(packet, expected);
}
#[test]
fn test_pointers_to_moved_records_are_expanded() {
let authority = [delegation(b"\x03ns1")];
let txt_name = pointer(referral(&authority, &[]).len());
let mut packet = referral(
&authority,
&[
rr(&wire_name("example.com"), DNS_TYPE_TXT, b"\x02hi"),
rr(&pointer(first_ns_data()), DNS_TYPE_A, &[192, 0, 2, 1]),
rr(
&[b"\x03ns2", &txt_name[..]].concat(),
DNS_TYPE_A,
&[192, 0, 2, 2],
),
],
);
assert!(validate_dns_response(&packet).is_ok());
let expected = referral(
&authority,
&[
rr(&wire_name("ns1.example.com"), DNS_TYPE_A, &[192, 0, 2, 1]),
rr(&wire_name("ns2.example.com"), DNS_TYPE_A, &[192, 0, 2, 2]),
],
);
assert_eq!(remove_out_of_bailiwick_glue(&mut packet).unwrap(), 1);
assert_eq!(packet, expected);
}
#[test]
fn test_other_responses_are_left_alone() {
let glue = || rr(&wire_name("ns.example.net"), DNS_TYPE_A, &[198, 51, 100, 1]);
let mut authoritative = referral(&[delegation(&wire_name("ns.example.net"))], &[glue()]);
set_aa(&mut authoritative, true).unwrap();
let other_name = rr(
&wire_name("other.example.com"),
DNS_TYPE_NS,
&wire_name("ns.example.net"),
);
let root = rr(&[0], DNS_TYPE_NS, &wire_name("a.root-servers.net"));
for mut packet in [
authoritative,
referral(&[other_name], &[glue()]),
referral(&[root], &[glue()]),
] {
assert_eq!(remove_out_of_bailiwick_glue(&mut packet).unwrap(), 0);
}
}
#[test]
fn test_malformed_or_oversized_results_are_errors() {
let authority = [delegation(&wire_name("ns.example.com"))];
let glue = rr(&wire_name("ns.example.net"), DNS_TYPE_A, &[198, 51, 100, 1]);
let mut truncated = referral(&authority, &[glue]);
truncated.pop();
assert!(remove_out_of_bailiwick_glue(&mut truncated).is_err());
let long_name = format!("{}example.com", "abcdefghijklmnopqrstuvwxyz.".repeat(8));
let a_long_name = [b"\x01a", &pointer(referral(&authority, &[]).len())[..]].concat();
let mut additional = vec![rr(&wire_name(&long_name), DNS_TYPE_TXT, b"\x02hi")];
additional.extend(std::iter::repeat_n(
rr(&a_long_name, DNS_TYPE_A, &[192, 0, 2, 3]),
30,
));
let mut too_large = referral(&authority, &additional);
assert!(validate_dns_response(&too_large).is_ok());
assert!(matches!(
remove_out_of_bailiwick_glue(&mut too_large),
Err(DnsError::PacketTooLarge { .. })
));
}
}