Skip to main content

hickory_proto/op/
edns.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! Extended DNS options
9
10use core::fmt;
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15#[cfg(feature = "__dnssec")]
16use crate::dnssec::{Algorithm, SupportedAlgorithms};
17use crate::{
18    error::*,
19    rr::{
20        DNSClass, Name, RData, Record, RecordType,
21        rdata::{
22            OPT,
23            opt::{EdnsCode, EdnsOption},
24        },
25    },
26    serialize::binary::{BinEncodable, BinEncoder},
27};
28
29/// Edns implements the higher level concepts for working with extended dns as it is used to create or be
30/// created from OPT record data.
31#[derive(Debug, PartialEq, Eq, Clone)]
32#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
33pub struct Edns {
34    // high 8 bits that make up the 12 bit total field when included with the 4bit rcode from the
35    //  header (from TTL)
36    rcode_high: u8,
37    // Indicates the implementation level of the setter. (from TTL)
38    version: u8,
39    flags: EdnsFlags,
40    // max payload size, minimum of 512, (from RR CLASS)
41    max_payload: u16,
42
43    options: OPT,
44}
45
46impl Default for Edns {
47    fn default() -> Self {
48        Self {
49            rcode_high: 0,
50            version: 0,
51            flags: EdnsFlags::default(),
52            max_payload: 512,
53            options: OPT::default(),
54        }
55    }
56}
57
58impl Edns {
59    /// Creates a new extended DNS object.
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    /// The high order bytes for the response code in the DNS Message
65    pub fn rcode_high(&self) -> u8 {
66        self.rcode_high
67    }
68
69    /// Returns the EDNS version
70    pub fn version(&self) -> u8 {
71        self.version
72    }
73
74    /// Returns the [`EdnsFlags`] portion of EDNS
75    pub fn flags(&self) -> &EdnsFlags {
76        &self.flags
77    }
78
79    /// Returns a mutable reference to the [`EdnsFlags`]
80    pub fn flags_mut(&mut self) -> &mut EdnsFlags {
81        &mut self.flags
82    }
83
84    /// Maximum supported size of the DNS payload
85    pub fn max_payload(&self) -> u16 {
86        self.max_payload
87    }
88
89    /// Returns the Option associated with the code
90    pub fn option(&self, code: EdnsCode) -> Option<&EdnsOption> {
91        self.options.get(code)
92    }
93
94    /// Returns the options portion of EDNS
95    pub fn options(&self) -> &OPT {
96        &self.options
97    }
98
99    /// Returns a mutable options portion of EDNS
100    pub fn options_mut(&mut self) -> &mut OPT {
101        &mut self.options
102    }
103
104    /// Set the high order bits for the result code.
105    pub fn set_rcode_high(&mut self, rcode_high: u8) -> &mut Self {
106        self.rcode_high = rcode_high;
107        self
108    }
109
110    /// Set the EDNS version
111    pub fn set_version(&mut self, version: u8) -> &mut Self {
112        self.version = version;
113        self
114    }
115
116    /// Creates a new extended DNS object prepared for DNSSEC messages.
117    #[cfg(feature = "__dnssec")]
118    pub fn enable_dnssec(&mut self) {
119        self.set_dnssec_ok(true);
120        self.set_default_algorithms();
121    }
122
123    /// Set the default algorithms which are supported by this handle
124    #[cfg(feature = "__dnssec")]
125    pub fn set_default_algorithms(&mut self) -> &mut Self {
126        let mut algorithms = SupportedAlgorithms::new();
127
128        for algorithm in [
129            Algorithm::RSASHA256,
130            Algorithm::RSASHA512,
131            Algorithm::ECDSAP256SHA256,
132            Algorithm::ECDSAP384SHA384,
133            Algorithm::ED25519,
134        ] {
135            if algorithm.is_supported() {
136                algorithms.set(algorithm);
137            }
138        }
139
140        let dau = EdnsOption::DAU(algorithms);
141
142        self.options_mut().insert(dau);
143        self
144    }
145
146    /// Set to true if DNSSEC is supported
147    pub fn set_dnssec_ok(&mut self, dnssec_ok: bool) -> &mut Self {
148        self.flags.dnssec_ok = dnssec_ok;
149        self
150    }
151
152    /// Set the maximum payload which can be supported
153    /// From RFC 6891: `Values lower than 512 MUST be treated as equal to 512`
154    pub fn set_max_payload(&mut self, max_payload: u16) -> &mut Self {
155        self.max_payload = max_payload.max(512);
156        self
157    }
158}
159
160// FIXME: this should be a TryFrom
161impl<'a> From<&'a Record> for Edns {
162    fn from(value: &'a Record) -> Self {
163        assert!(value.record_type() == RecordType::OPT);
164
165        let rcode_high = ((value.ttl & 0xFF00_0000u32) >> 24) as u8;
166        let version = ((value.ttl & 0x00FF_0000u32) >> 16) as u8;
167        let flags = EdnsFlags::from((value.ttl & 0x0000_FFFFu32) as u16);
168        let max_payload = u16::from(value.dns_class);
169
170        let options = match &value.data {
171            RData::Update0(..) | RData::NULL(..) => {
172                // NULL, there was no data in the OPT
173                OPT::default()
174            }
175            RData::OPT(option_data) => {
176                option_data.clone() // TODO: Edns should just refer to this, have the same lifetime as the Record
177            }
178            _ => {
179                // this should be a coding error, as opposed to a parsing error.
180                panic!("rr_type doesn't match the RData: {:?}", value.data) // valid panic, never should happen
181            }
182        };
183
184        Self {
185            rcode_high,
186            version,
187            flags,
188            max_payload,
189            options,
190        }
191    }
192}
193
194impl<'a> From<&'a Edns> for Record {
195    /// This returns a Resource Record that is formatted for Edns(0).
196    /// Note: the rcode_high value is only part of the rcode, the rest is part of the base
197    fn from(value: &'a Edns) -> Self {
198        // rebuild the TTL field
199        let mut ttl: u32 = u32::from(value.rcode_high()) << 24;
200        ttl |= u32::from(value.version()) << 16;
201        ttl |= u32::from(u16::from(value.flags));
202
203        // now for each option, write out the option array
204        //  also, since this is a hash, there is no guarantee that ordering will be preserved from
205        //  the original binary format.
206        // maybe switch to: https://crates.io/crates/linked-hash-map/
207        let mut record = Self::from_rdata(Name::root(), ttl, RData::OPT(value.options().clone()));
208        record.dns_class = DNSClass::for_opt(value.max_payload());
209        record
210    }
211}
212
213impl BinEncodable for Edns {
214    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
215        encoder.emit(0)?; // Name::root
216        RecordType::OPT.emit(encoder)?; //self.rr_type.emit(encoder)?;
217        DNSClass::for_opt(self.max_payload()).emit(encoder)?; // self.dns_class.emit(encoder)?;
218
219        // rebuild the TTL field
220        let mut ttl = u32::from(self.rcode_high()) << 24;
221        ttl |= u32::from(self.version()) << 16;
222        ttl |= u32::from(u16::from(self.flags));
223
224        encoder.emit_u32(ttl)?;
225
226        // write the opts as rdata...
227        let place = encoder.place::<u16>()?;
228        self.options.emit(encoder)?;
229        let len = encoder.len_since_place(&place);
230        assert!(len <= u16::MAX as usize);
231
232        place.replace(encoder, len as u16)?;
233        Ok(())
234    }
235}
236
237impl fmt::Display for Edns {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
239        let version = self.version;
240        let dnssec_ok = self.flags.dnssec_ok;
241        let z_flags = self.flags.z;
242        let max_payload = self.max_payload;
243
244        write!(
245            f,
246            "version: {version} dnssec_ok: {dnssec_ok} z_flags: {z_flags} max_payload: {max_payload} opts: {opts_len}",
247            opts_len = self.options().as_ref().len()
248        )
249    }
250}
251
252/// EDNS flags
253///
254/// <https://www.rfc-editor.org/rfc/rfc6891#section-6.1.4>
255#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
256#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
257pub struct EdnsFlags {
258    /// DNSSEC OK bit as defined by RFC 3225
259    pub dnssec_ok: bool,
260    /// Remaining bits in the flags field
261    ///
262    /// Note that the most significant bit in this value is represented by the `dnssec_ok` field.
263    /// As such, it will be zero when decoding and will not be encoded.
264    ///
265    /// Unless you have a specific need to set this value, we recommend leaving this as zero.
266    pub z: u16,
267}
268
269impl From<u16> for EdnsFlags {
270    fn from(flags: u16) -> Self {
271        Self {
272            dnssec_ok: flags & 0x8000 == 0x8000,
273            z: flags & 0x7FFF,
274        }
275    }
276}
277
278impl From<EdnsFlags> for u16 {
279    fn from(flags: EdnsFlags) -> Self {
280        match flags.dnssec_ok {
281            true => 0x8000 | flags.z,
282            false => 0x7FFF & flags.z,
283        }
284    }
285}
286
287/// Default maximum payload length for EDNS messages.
288///
289/// Per 2020 DNS flag day, default to 1232 bytes.
290///
291/// <https://www.dnsflagday.net/2020/>
292pub const DEFAULT_MAX_PAYLOAD_LEN: u16 = 1232;
293
294#[cfg(all(test, feature = "__dnssec"))]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn test_encode_decode() {
300        let mut edns = Edns::new();
301
302        let flags = edns.flags_mut();
303        flags.dnssec_ok = true;
304        flags.z = 1;
305        edns.set_max_payload(0x8008);
306        edns.set_version(0x40);
307        edns.set_rcode_high(0x01);
308        edns.options_mut()
309            .insert(EdnsOption::DAU(SupportedAlgorithms::all()));
310
311        let record = Record::from(&edns);
312        let edns_decode = Edns::from(&record);
313
314        assert_eq!(edns.flags().dnssec_ok, edns_decode.flags().dnssec_ok);
315        assert_eq!(edns.flags().z, edns_decode.flags().z);
316        assert_eq!(edns.max_payload(), edns_decode.max_payload());
317        assert_eq!(edns.version(), edns_decode.version());
318        assert_eq!(edns.rcode_high(), edns_decode.rcode_high());
319        assert_eq!(edns.options(), edns_decode.options());
320
321        // re-insert and remove using mut
322        edns.options_mut()
323            .insert(EdnsOption::DAU(SupportedAlgorithms::all()));
324        edns.options_mut().remove(EdnsCode::DAU);
325        assert!(edns.option(EdnsCode::DAU).is_none());
326    }
327}