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;
#[allow(dead_code)]
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;
#[allow(dead_code)]
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_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<()> {
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}"
)));
}
}
}
if let Some(edns_version) = extract_edns_version(packet)? {
if edns_version != 0 {
return Err(DnsError::UnsupportedEdnsVersion {
version: edns_version,
});
}
}
Ok(())
}
#[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
}
pub fn is_dnssec_requested(packet: &[u8]) -> DnsResult<bool> {
let flags = BigEndian::read_u16(&packet[2..4]);
if (flags & DNS_FLAGS_CD) == DNS_FLAGS_CD {
return Ok(true);
}
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 + 4..]);
if (opt_flags & DNS_FLAG_DO) == DNS_FLAG_DO {
dnssec_requested = true;
}
}
Ok(())
})?;
Ok(dnssec_requested)
}
#[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 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| {
BigEndian::write_u32(&mut packet[offset + 4..offset + 8], new_ttl);
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 || 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())
})?;
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;
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,
});
}
current_offset = result_offset;
break;
}
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,
});
}
current_offset = pointer;
}
0 => {
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(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 });
}
callback(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(),
));
}
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 });
}
callback(packet, 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(),
));
}
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<()> {
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 rdlen = BigEndian::read_u16(&packet[rdlen_offset..rdlen_offset + 2]) as usize;
let rdata_offset = rdlen_offset + 2;
let mut ecs_option = Vec::new();
let (family, source_prefix_length, address_bytes) = match ip_addr {
std::net::IpAddr::V4(ipv4) => {
let bytes = ipv4.octets();
(1u16, prefix_v4, bytes.to_vec())
}
std::net::IpAddr::V6(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 - 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).map_or(true, |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_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] = 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..]);
max_size = Some(size);
return Err(DnsError::InvalidPacket("Found OPT record".to_string()));
}
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 {
if 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(),
));
}
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());
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_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_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)]
#[allow(dead_code)]
pub struct EdnsClientSubnet {
pub family: u16,
pub source_prefix_length: u8,
pub scope_prefix_length: u8,
pub address: Vec<u8>,
}
#[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 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());
}
}