hickory_proto/op/edns.rs
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 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// https://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;
#[cfg(feature = "dnssec")]
use crate::rr::dnssec::{Algorithm, SupportedAlgorithms};
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,
flags: EdnsFlags,
// 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,
flags: EdnsFlags::default(),
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
}
/// Returns the [`EdnsFlags`] portion of EDNS
pub fn flags(&self) -> &EdnsFlags {
&self.flags
}
/// Returns a mutable reference to the [`EdnsFlags`]
pub fn flags_mut(&mut self) -> &mut EdnsFlags {
&mut self.flags
}
/// 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
}
/// Creates a new extended DNS object prepared for DNSSEC messages.
#[cfg(feature = "dnssec")]
pub fn enable_dnssec(&mut self) {
self.set_dnssec_ok(true);
self.set_default_algorithms();
}
/// Set the default algorithms which are supported by this handle
///
/// Set both Algorithms Understood (DAU) and Hash Understood (DHU) to the same algorithms.
#[cfg(feature = "dnssec")]
pub fn set_default_algorithms(&mut self) -> &mut Self {
let mut algorithms = SupportedAlgorithms::new();
#[cfg(feature = "dnssec-ring")]
algorithms.set(Algorithm::ED25519);
algorithms.set(Algorithm::ECDSAP256SHA256);
algorithms.set(Algorithm::ECDSAP384SHA384);
algorithms.set(Algorithm::RSASHA256);
let dau = EdnsOption::DAU(algorithms);
let dhu = EdnsOption::DHU(algorithms);
self.options_mut().insert(dau);
self.options_mut().insert(dhu);
self
}
/// Set to true if DNSSEC is supported
pub fn set_dnssec_ok(&mut self, dnssec_ok: bool) -> &mut Self {
self.flags.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 = ((value.ttl() & 0xFF00_0000u32) >> 24) as u8;
let version = ((value.ttl() & 0x00FF_0000u32) >> 16) as u8;
let flags = EdnsFlags::from((value.ttl() & 0x0000_FFFFu32) as u16);
let max_payload = u16::from(value.dns_class());
let options = match value.data() {
RData::Update0(..) | RData::NULL(..) => {
// NULL, there was no data in the OPT
OPT::default()
}
RData::OPT(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,
flags,
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 {
// rebuild the TTL field
let mut ttl: u32 = u32::from(value.rcode_high()) << 24;
ttl |= u32::from(value.version()) << 16;
ttl |= u32::from(u16::from(value.flags));
// 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/
let mut record = Self::from_rdata(Name::root(), ttl, RData::OPT(value.options().clone()));
record.set_dns_class(DNSClass::for_opt(value.max_payload()));
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::from(self.rcode_high()) << 24;
ttl |= u32::from(self.version()) << 16;
ttl |= u32::from(u16::from(self.flags));
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 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.flags.dnssec_ok;
let z_flags = self.flags.z;
let max_payload = self.max_payload;
write!(
f,
"version: {version} dnssec_ok: {dnssec_ok} z_flags: {z_flags} max_payload: {max_payload} opts: {opts_len}",
opts_len = self.options().as_ref().len()
)
}
}
/// EDNS flags
///
/// <https://www.rfc-editor.org/rfc/rfc6891#section-6.1.4>
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct EdnsFlags {
/// DNSSEC OK bit as defined by RFC 3225
pub dnssec_ok: bool,
/// Set to zero by senders and ignored by receivers
pub z: bool,
}
impl From<u16> for EdnsFlags {
fn from(flags: u16) -> Self {
Self {
dnssec_ok: flags & 0x8000 == 0x8000,
z: flags & 0x7FFF != 0,
}
}
}
impl From<EdnsFlags> for u16 {
fn from(flags: EdnsFlags) -> Self {
let mut result = 0;
if flags.dnssec_ok {
result |= 0x8000;
}
if flags.z {
result |= 0x7FFF;
}
result
}
}
#[cfg(all(test, feature = "dnssec"))]
mod tests {
use super::*;
#[test]
fn test_encode_decode() {
let mut edns = Edns::new();
let flags = edns.flags_mut();
flags.dnssec_ok = true;
flags.z = 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::from(&edns);
let edns_decode = Edns::from(&record);
assert_eq!(edns.flags().dnssec_ok, edns_decode.flags().dnssec_ok);
assert_eq!(edns.flags().z, edns_decode.flags().z);
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());
}
}