trust_dns_proto/op/
edns.rs

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