use crate::error::{CrafterError, Result};
use super::{
DNS_EDNS_EXTENDED_RCODE_SHIFT, DNS_EDNS_FLAG_DO, DNS_EDNS_OPTION_CLIENT_SUBNET,
DNS_EDNS_OPTION_COOKIE, DNS_EDNS_OPTION_DAU, DNS_EDNS_OPTION_DHU, DNS_EDNS_OPTION_EXPIRE,
DNS_EDNS_OPTION_EXTENDED_ERROR, DNS_EDNS_OPTION_N3U, DNS_EDNS_OPTION_NSID,
DNS_EDNS_OPTION_PADDING, DNS_EDNS_OPTION_TCP_KEEPALIVE, DNS_EDNS_VERSION_SHIFT,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EdnsOption {
code: u16,
data: Vec<u8>,
}
impl EdnsOption {
pub fn new(code: u16, data: impl Into<Vec<u8>>) -> Self {
Self {
code,
data: data.into(),
}
}
pub fn nsid(data: impl Into<Vec<u8>>) -> Self {
Self::new(DNS_EDNS_OPTION_NSID, data)
}
pub fn cookie(data: impl Into<Vec<u8>>) -> Self {
Self::new(DNS_EDNS_OPTION_COOKIE, data)
}
pub fn padding(len: usize) -> Self {
Self::new(DNS_EDNS_OPTION_PADDING, vec![0u8; len])
}
pub const fn code(&self) -> u16 {
self.code
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn option_code_name(&self) -> Option<&'static str> {
edns_option_code_name(self.code)
}
pub(super) fn encoded_len(&self) -> usize {
4 + self.data.len()
}
pub(super) fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
let length = u16::try_from(self.data.len()).map_err(|_| {
CrafterError::invalid_field_value(
"dns.opt.option.length",
"EDNS option data exceeds 65535 bytes",
)
})?;
out.extend_from_slice(&self.code.to_be_bytes());
out.extend_from_slice(&length.to_be_bytes());
out.extend_from_slice(&self.data);
Ok(())
}
}
pub fn edns_option_code_name(code: u16) -> Option<&'static str> {
Some(match code {
DNS_EDNS_OPTION_NSID => "NSID",
DNS_EDNS_OPTION_DAU => "DAU",
DNS_EDNS_OPTION_DHU => "DHU",
DNS_EDNS_OPTION_N3U => "N3U",
DNS_EDNS_OPTION_CLIENT_SUBNET => "edns-client-subnet",
DNS_EDNS_OPTION_EXPIRE => "EDNS EXPIRE",
DNS_EDNS_OPTION_COOKIE => "COOKIE",
DNS_EDNS_OPTION_TCP_KEEPALIVE => "edns-tcp-keepalive",
DNS_EDNS_OPTION_PADDING => "Padding",
DNS_EDNS_OPTION_EXTENDED_ERROR => "Extended DNS Error",
_ => return None,
})
}
pub(super) fn encode_edns_ttl(extended_rcode: u8, version: u8, dnssec_ok: bool, z: u16) -> u32 {
let mut flags = z & !DNS_EDNS_FLAG_DO;
if dnssec_ok {
flags |= DNS_EDNS_FLAG_DO;
}
((extended_rcode as u32) << DNS_EDNS_EXTENDED_RCODE_SHIFT)
| ((version as u32) << DNS_EDNS_VERSION_SHIFT)
| (flags as u32)
}
#[cfg(test)]
mod dns_edns {
use super::super::{
decode_record_data, edns_option_code_name, Dns, DnsName, DnsRecord, DnsRecordData,
EdnsOption, DNS_EDNS_DEFAULT_UDP_PAYLOAD_SIZE, DNS_EDNS_FLAG_DO, DNS_EDNS_OPTION_COOKIE,
DNS_EDNS_OPTION_DAU, DNS_EDNS_OPTION_NSID, DNS_EDNS_OPTION_PADDING, DNS_TYPE_OPT,
};
use crate::{Ipv4, NetworkLayer, Packet, Udp};
use core::net::Ipv4Addr;
fn round_trip_opt(opt: DnsRecord) -> (DnsRecord, Vec<u8>, Vec<u8>) {
let original = Dns::a_query("example.com.").id(0x4242).additional(opt);
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 53))
/ Udp::new().sport(53001).dport(53)
/ original)
.compile()
.unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes()).unwrap();
let record = decoded.layer::<Dns>().unwrap().additionals()[0].clone();
let recompiled = decoded.compile().unwrap();
(
record,
bytes.as_bytes().to_vec(),
recompiled.as_bytes().to_vec(),
)
}
#[test]
fn dns_edns_opt_with_no_options_round_trips() {
let opt = DnsRecord::opt(DNS_EDNS_DEFAULT_UDP_PAYLOAD_SIZE, 0, 0, true, Vec::new());
let (record, original, recompiled) = round_trip_opt(opt);
assert!(record.is_opt());
assert_eq!(record.class(), 4096);
assert_eq!(record.edns_udp_payload_size(), 4096);
assert_eq!(record.edns_extended_rcode(), 0);
assert_eq!(record.edns_version(), 0);
assert!(record.edns_dnssec_ok());
assert_eq!(record.edns_options(), Some(&[][..]));
assert_eq!(record.data(), &DnsRecordData::Opt(Vec::new()));
assert_eq!(record.name(), ".");
assert_eq!(recompiled, original);
}
#[test]
fn dns_edns_opt_with_typed_option_round_trips() {
let opt = DnsRecord::opt(1232, 0, 0, false, vec![EdnsOption::nsid(b"ns1".to_vec())]);
let (record, original, recompiled) = round_trip_opt(opt);
let options = record.edns_options().unwrap();
assert_eq!(options.len(), 1);
assert_eq!(options[0].code(), DNS_EDNS_OPTION_NSID);
assert_eq!(options[0].data(), b"ns1");
assert_eq!(options[0].option_code_name(), Some("NSID"));
assert_eq!(record.edns_udp_payload_size(), 1232);
assert!(!record.edns_dnssec_ok());
assert_eq!(recompiled, original);
}
#[test]
fn dns_edns_opt_unknown_option_round_trips_as_raw_bytes() {
let unknown_code = 0xfffeu16;
let opt = DnsRecord::opt(
512,
0,
0,
false,
vec![
EdnsOption::cookie(b"clientcookie".to_vec()),
EdnsOption::new(unknown_code, vec![0xde, 0xad, 0xbe, 0xef]),
],
);
let (record, original, recompiled) = round_trip_opt(opt);
let options = record.edns_options().unwrap();
assert_eq!(options.len(), 2);
assert_eq!(options[0].code(), DNS_EDNS_OPTION_COOKIE);
assert_eq!(options[0].option_code_name(), Some("COOKIE"));
assert_eq!(options[1].code(), unknown_code);
assert_eq!(options[1].option_code_name(), None);
assert_eq!(options[1].data(), &[0xde, 0xad, 0xbe, 0xef]);
assert_eq!(recompiled, original);
}
#[test]
fn dns_edns_unsupported_version_is_preserved_not_rejected() {
let opt = DnsRecord::opt(4096, 0, 7, false, vec![EdnsOption::padding(4)]);
let (record, original, recompiled) = round_trip_opt(opt);
assert_eq!(record.edns_version(), 7);
assert_eq!(record.edns_options().unwrap()[0].data(), &[0, 0, 0, 0]);
assert_eq!(recompiled, original);
}
#[test]
fn dns_edns_extended_rcode_and_flags_fold_into_ttl() {
let opt = DnsRecord::opt(4096, 0x12, 0, true, Vec::new());
assert_eq!(opt.edns_extended_rcode(), 0x12);
assert!(opt.edns_dnssec_ok());
assert_eq!(opt.edns_flags(), super::super::DNS_EDNS_FLAG_DO);
}
#[test]
fn dns_edns_option_length_overrun_is_rejected() {
let mut rdata = Vec::new();
rdata.extend_from_slice(&DNS_EDNS_OPTION_NSID.to_be_bytes()); rdata.extend_from_slice(&8u16.to_be_bytes()); rdata.extend_from_slice(&[0u8; 2]); let end = rdata.len();
assert!(decode_record_data(DNS_TYPE_OPT, &rdata, 0, end).is_err());
}
#[test]
fn dns_edns_truncated_option_header_is_rejected() {
let rdata = [0x00u8, 0x03, 0x00]; assert!(decode_record_data(DNS_TYPE_OPT, &rdata, 0, rdata.len()).is_err());
}
#[test]
fn dns_edns_option_data_too_large_is_rejected_on_encode() {
let oversized = EdnsOption::new(DNS_EDNS_OPTION_NSID, vec![0u8; 65_536]);
let opt = DnsRecord::opt(4096, 0, 0, false, vec![oversized]);
assert!(Packet::from_layer(Dns::new().additional(opt))
.compile()
.is_err());
}
#[test]
fn dns_edns_oversized_option_data_returns_structured_error_on_encode() {
use crate::error::CrafterError;
let oversized = EdnsOption::new(DNS_EDNS_OPTION_NSID, vec![0u8; 65_536]);
let opt = DnsRecord::opt(4096, 0, 0, false, vec![oversized]);
let error = Packet::from_layer(Dns::new().additional(opt))
.compile()
.expect_err("oversized EDNS option data must be rejected on encode");
match error {
CrafterError::InvalidFieldValue { field, .. } => {
assert_eq!(field, "dns.opt.option.length")
}
other => panic!("expected dns.opt.option.length invalid-field-value, got {other:?}"),
}
}
#[test]
fn dns_edns_opt_basic_fields_round_trip_through_typed_getters() {
for (payload_size, extended_rcode, version, dnssec_ok) in [
(DNS_EDNS_DEFAULT_UDP_PAYLOAD_SIZE, 0u8, 0u8, false),
(512u16, 0u8, 0u8, true),
(1232u16, 0x12u8, 0u8, false),
(65535u16, 0u8, 1u8, true),
] {
let opt = DnsRecord::opt(payload_size, extended_rcode, version, dnssec_ok, Vec::new());
let (record, original, recompiled) = round_trip_opt(opt);
assert!(record.is_opt());
assert_eq!(record.name(), ".");
assert_eq!(record.class(), payload_size);
assert_eq!(record.edns_udp_payload_size(), payload_size);
assert_eq!(record.edns_extended_rcode(), extended_rcode);
assert_eq!(record.edns_version(), version);
assert_eq!(record.edns_dnssec_ok(), dnssec_ok);
assert_eq!(record.edns_options(), Some(&[][..]));
assert_eq!(record.data(), &DnsRecordData::Opt(Vec::new()));
let expected_flags = if dnssec_ok { DNS_EDNS_FLAG_DO } else { 0 };
assert_eq!(record.edns_flags(), expected_flags);
assert_eq!(recompiled, original);
}
}
#[test]
fn dns_edns_opt_nonzero_z_bits_are_preserved() {
let z_bits: u16 = 0x0102; let ttl = u32::from(DNS_EDNS_FLAG_DO | z_bits);
let opt = DnsRecord::new(
DnsName::root(),
DNS_TYPE_OPT,
1232,
ttl,
DnsRecordData::Opt(Vec::new()),
);
let (record, original, recompiled) = round_trip_opt(opt);
assert!(record.is_opt());
assert_eq!(record.edns_udp_payload_size(), 1232);
assert_eq!(record.edns_extended_rcode(), 0);
assert_eq!(record.edns_version(), 0);
assert!(record.edns_dnssec_ok());
assert_eq!(record.edns_flags(), DNS_EDNS_FLAG_DO | z_bits);
assert_eq!(recompiled, original);
}
#[test]
fn dns_edns_option_code_name_registry_covers_the_option_matrix() {
assert_eq!(edns_option_code_name(DNS_EDNS_OPTION_NSID), Some("NSID"));
assert_eq!(
edns_option_code_name(DNS_EDNS_OPTION_COOKIE),
Some("COOKIE")
);
assert_eq!(
edns_option_code_name(DNS_EDNS_OPTION_PADDING),
Some("Padding")
);
assert_eq!(edns_option_code_name(DNS_EDNS_OPTION_DAU), Some("DAU"));
assert_eq!(edns_option_code_name(0xfffe), None);
assert_eq!(
EdnsOption::new(DNS_EDNS_OPTION_DAU, vec![5, 1, 8, 10]).option_code_name(),
edns_option_code_name(DNS_EDNS_OPTION_DAU),
);
}
#[test]
fn dns_edns_option_matrix_preserves_code_and_data_in_order() {
let unknown_code = 0xfffeu16;
let options = vec![
EdnsOption::nsid(b"ns01".to_vec()),
EdnsOption::cookie(vec![1, 2, 3, 4, 5, 6, 7, 8]),
EdnsOption::padding(8),
EdnsOption::new(DNS_EDNS_OPTION_DAU, vec![5, 1, 8, 10]),
EdnsOption::new(unknown_code, vec![0xca, 0xfe]),
];
let opt = DnsRecord::opt(4096, 0, 0, true, options);
let (record, original, recompiled) = round_trip_opt(opt);
let decoded = record.edns_options().unwrap();
assert_eq!(decoded.len(), 5);
assert_eq!(decoded[0].code(), DNS_EDNS_OPTION_NSID);
assert_eq!(decoded[0].data(), b"ns01");
assert_eq!(decoded[0].option_code_name(), Some("NSID"));
assert_eq!(decoded[1].code(), DNS_EDNS_OPTION_COOKIE);
assert_eq!(decoded[1].data(), &[1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(decoded[1].option_code_name(), Some("COOKIE"));
assert_eq!(decoded[2].code(), DNS_EDNS_OPTION_PADDING);
assert_eq!(decoded[2].data(), &[0u8; 8]);
assert_eq!(decoded[2].option_code_name(), Some("Padding"));
assert_eq!(decoded[3].code(), DNS_EDNS_OPTION_DAU);
assert_eq!(decoded[3].data(), &[5, 1, 8, 10]);
assert_eq!(decoded[3].option_code_name(), Some("DAU"));
assert_eq!(decoded[4].code(), unknown_code);
assert_eq!(decoded[4].data(), &[0xca, 0xfe]);
assert_eq!(decoded[4].option_code_name(), None);
assert_eq!(recompiled, original);
}
}