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