1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! Extended DNS options
use std::fmt;
use crate::{
error::*,
rr::{
rdata::{
opt::{EdnsCode, EdnsOption},
OPT,
},
DNSClass, Name, RData, Record, RecordType,
},
serialize::binary::{BinEncodable, BinEncoder},
};
/// Edns implements the higher level concepts for working with extended dns as it is used to create or be
/// created from OPT record data.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Edns {
// high 8 bits that make up the 12 bit total field when included with the 4bit rcode from the
// header (from TTL)
rcode_high: u8,
// Indicates the implementation level of the setter. (from TTL)
version: u8,
// Is DNSSEC supported (from TTL)
dnssec_ok: bool,
// max payload size, minimum of 512, (from RR CLASS)
max_payload: u16,
options: OPT,
}
impl Default for Edns {
fn default() -> Self {
Self {
rcode_high: 0,
version: 0,
dnssec_ok: false,
max_payload: 512,
options: OPT::default(),
}
}
}
impl Edns {
/// Creates a new extended DNS object.
pub fn new() -> Self {
Self::default()
}
/// The high order bytes for the response code in the DNS Message
pub fn rcode_high(&self) -> u8 {
self.rcode_high
}
/// Returns the EDNS version
pub fn version(&self) -> u8 {
self.version
}
/// Specifies that DNSSEC is supported for this Client or Server
pub fn dnssec_ok(&self) -> bool {
self.dnssec_ok
}
/// Maximum supported size of the DNS payload
pub fn max_payload(&self) -> u16 {
self.max_payload
}
/// Returns the Option associated with the code
pub fn option(&self, code: EdnsCode) -> Option<&EdnsOption> {
self.options.get(code)
}
/// Returns the options portion of EDNS
pub fn options(&self) -> &OPT {
&self.options
}
/// Returns a mutable options portion of EDNS
pub fn options_mut(&mut self) -> &mut OPT {
&mut self.options
}
/// Set the high order bits for the result code.
pub fn set_rcode_high(&mut self, rcode_high: u8) -> &mut Self {
self.rcode_high = rcode_high;
self
}
/// Set the EDNS version
pub fn set_version(&mut self, version: u8) -> &mut Self {
self.version = version;
self
}
/// Set to true if DNSSEC is supported
pub fn set_dnssec_ok(&mut self, dnssec_ok: bool) -> &mut Self {
self.dnssec_ok = dnssec_ok;
self
}
/// Set the maximum payload which can be supported
/// From RFC 6891: `Values lower than 512 MUST be treated as equal to 512`
pub fn set_max_payload(&mut self, max_payload: u16) -> &mut Self {
self.max_payload = max_payload.max(512);
self
}
/// Set the specified EDNS option
#[deprecated(note = "Please use options_mut().insert() to modify")]
pub fn set_option(&mut self, option: EdnsOption) {
self.options.insert(option);
}
}
// FIXME: this should be a TryFrom
impl<'a> From<&'a Record> for Edns {
fn from(value: &'a Record) -> Self {
assert!(value.record_type() == RecordType::OPT);
let rcode_high: u8 = ((value.ttl() & 0xFF00_0000u32) >> 24) as u8;
let version: u8 = ((value.ttl() & 0x00FF_0000u32) >> 16) as u8;
let dnssec_ok: bool = value.ttl() & 0x0000_8000 == 0x0000_8000;
let max_payload: u16 = u16::from(value.dns_class());
let options: OPT = match value.data() {
Some(RData::NULL(..)) | None => {
// NULL, there was no data in the OPT
OPT::default()
}
Some(RData::OPT(ref option_data)) => {
option_data.clone() // TODO: Edns should just refer to this, have the same lifetime as the Record
}
_ => {
// this should be a coding error, as opposed to a parsing error.
panic!("rr_type doesn't match the RData: {:?}", value.data()) // valid panic, never should happen
}
};
Self {
rcode_high,
version,
dnssec_ok,
max_payload,
options,
}
}
}
impl<'a> From<&'a Edns> for Record {
/// This returns a Resource Record that is formatted for Edns(0).
/// Note: the rcode_high value is only part of the rcode, the rest is part of the base
fn from(value: &'a Edns) -> Self {
let mut record = Self::new();
record.set_name(Name::root());
record.set_rr_type(RecordType::OPT);
record.set_dns_class(DNSClass::for_opt(value.max_payload()));
// rebuild the TTL field
let mut ttl: u32 = u32::from(value.rcode_high()) << 24;
ttl |= u32::from(value.version()) << 16;
if value.dnssec_ok() {
ttl |= 0x0000_8000;
}
record.set_ttl(ttl);
// now for each option, write out the option array
// also, since this is a hash, there is no guarantee that ordering will be preserved from
// the original binary format.
// maybe switch to: https://crates.io/crates/linked-hash-map/
record.set_data(Some(RData::OPT(value.options().clone())));
record
}
}
impl BinEncodable for Edns {
fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
encoder.emit(0)?; // Name::root
RecordType::OPT.emit(encoder)?; //self.rr_type.emit(encoder)?;
DNSClass::for_opt(self.max_payload()).emit(encoder)?; // self.dns_class.emit(encoder)?;
// rebuild the TTL field
let mut ttl: u32 = u32::from(self.rcode_high()) << 24;
ttl |= u32::from(self.version()) << 16;
if self.dnssec_ok() {
ttl |= 0x0000_8000;
}
encoder.emit_u32(ttl)?;
// write the opts as rdata...
let place = encoder.place::<u16>()?;
self.options.emit(encoder)?;
let len = encoder.len_since_place(&place);
assert!(len <= u16::max_value() as usize);
place.replace(encoder, len as u16)?;
Ok(())
}
}
impl fmt::Display for Edns {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let version = self.version;
let dnssec_ok = self.dnssec_ok;
let max_payload = self.max_payload;
write!(
f,
"version: {version} dnssec_ok: {dnssec_ok} max_payload: {max_payload} opts: {opts_len}",
version = version,
dnssec_ok = dnssec_ok,
max_payload = max_payload,
opts_len = self.options().as_ref().len()
)
}
}
#[cfg(feature = "dnssec")]
#[test]
fn test_encode_decode() {
use crate::rr::dnssec::SupportedAlgorithms;
let mut edns: Edns = Edns::new();
edns.set_dnssec_ok(true);
edns.set_max_payload(0x8008);
edns.set_version(0x40);
edns.set_rcode_high(0x01);
edns.options_mut()
.insert(EdnsOption::DAU(SupportedAlgorithms::all()));
let record: Record = (&edns).into();
let edns_decode: Edns = (&record).into();
assert_eq!(edns.dnssec_ok(), edns_decode.dnssec_ok());
assert_eq!(edns.max_payload(), edns_decode.max_payload());
assert_eq!(edns.version(), edns_decode.version());
assert_eq!(edns.rcode_high(), edns_decode.rcode_high());
assert_eq!(edns.options(), edns_decode.options());
// re-insert and remove using mut
edns.options_mut()
.insert(EdnsOption::DAU(SupportedAlgorithms::all()));
edns.options_mut().remove(EdnsCode::DAU);
assert!(edns.option(EdnsCode::DAU).is_none());
}