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 */
1617//! Extended DNS options
1819use std::fmt;
2021use crate::error::*;
22use crate::rr::rdata::opt::{self, EdnsCode, EdnsOption};
23use crate::rr::rdata::OPT;
24use crate::rr::{DNSClass, Name, RData, Record, RecordType};
2526use crate::serialize::binary::{BinEncodable, BinEncoder};
2728/// 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)
34rcode_high: u8,
35// Indicates the implementation level of the setter. (from TTL)
36version: u8,
37// Is DNSSec supported (from TTL)
38dnssec_ok: bool,
39// max payload size, minimum of 512, (from RR CLASS)
40max_payload: u16,
4142 options: OPT,
43}
4445impl Default for Edns {
46fn default() -> Self {
47Self {
48 rcode_high: 0,
49 version: 0,
50 dnssec_ok: false,
51 max_payload: 512,
52 options: OPT::default(),
53 }
54 }
55}
5657impl Edns {
58/// Creates a new extended DNS object.
59pub fn new() -> Self {
60Self::default()
61 }
6263/// The high order bytes for the response code in the DNS Message
64pub fn rcode_high(&self) -> u8 {
65self.rcode_high
66 }
6768/// Returns the EDNS version
69pub fn version(&self) -> u8 {
70self.version
71 }
7273/// Specifies that DNSSec is supported for this Client or Server
74pub fn dnssec_ok(&self) -> bool {
75self.dnssec_ok
76 }
7778/// Maximum supported size of the DNS payload
79pub fn max_payload(&self) -> u16 {
80self.max_payload
81 }
8283/// Returns the Option associated with the code
84pub fn option(&self, code: EdnsCode) -> Option<&EdnsOption> {
85self.options.get(code)
86 }
8788/// Returns the options portion of EDNS
89pub fn options(&self) -> &OPT {
90&self.options
91 }
9293/// Returns a mutable options portion of EDNS
94pub fn options_mut(&mut self) -> &mut OPT {
95&mut self.options
96 }
9798/// Set the high order bits for the result code.
99pub fn set_rcode_high(&mut self, rcode_high: u8) -> &mut Self {
100self.rcode_high = rcode_high;
101self
102}
103104/// Set the EDNS version
105pub fn set_version(&mut self, version: u8) -> &mut Self {
106self.version = version;
107self
108}
109110/// Set to true if DNSSec is supported
111pub fn set_dnssec_ok(&mut self, dnssec_ok: bool) -> &mut Self {
112self.dnssec_ok = dnssec_ok;
113self
114}
115116/// Set the maximum payload which can be supported
117 /// From RFC 6891: `Values lower than 512 MUST be treated as equal to 512`
118pub fn set_max_payload(&mut self, max_payload: u16) -> &mut Self {
119self.max_payload = max_payload.max(512);
120self
121}
122123/// Set the specified EDNS option
124#[deprecated(note = "Please use options_mut().insert() to modify")]
125pub fn set_option(&mut self, option: EdnsOption) {
126self.options.insert(option);
127 }
128}
129130// FIXME: this should be a TryFrom
131impl<'a> From<&'a Record> for Edns {
132fn from(value: &'a Record) -> Self {
133assert!(value.rr_type() == RecordType::OPT);
134135let rcode_high: u8 = ((value.ttl() & 0xFF00_0000u32) >> 24) as u8;
136let version: u8 = ((value.ttl() & 0x00FF_0000u32) >> 16) as u8;
137let dnssec_ok: bool = value.ttl() & 0x0000_8000 == 0x0000_8000;
138let max_payload: u16 = u16::from(value.dns_class());
139140let options: OPT = match value.data() {
141Some(RData::NULL(..)) | None => {
142// NULL, there was no data in the OPT
143OPT::default()
144 }
145Some(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.
150panic!("rr_type doesn't match the RData: {:?}", value.data()) // valid panic, never should happen
151}
152 };
153154Self {
155 rcode_high,
156 version,
157 dnssec_ok,
158 max_payload,
159 options,
160 }
161 }
162}
163164impl<'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
167fn from(value: &'a Edns) -> Self {
168let mut record = Self::new();
169170 record.set_name(Name::root());
171 record.set_rr_type(RecordType::OPT);
172 record.set_dns_class(DNSClass::for_opt(value.max_payload()));
173174// rebuild the TTL field
175let mut ttl: u32 = u32::from(value.rcode_high()) << 24;
176 ttl |= u32::from(value.version()) << 16;
177178if value.dnssec_ok() {
179 ttl |= 0x0000_8000;
180 }
181 record.set_ttl(ttl);
182183// 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/
187record.set_data(Some(RData::OPT(value.options().clone())));
188189 record
190 }
191}
192193impl BinEncodable for Edns {
194fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
195 encoder.emit(0)?; // Name::root
196RecordType::OPT.emit(encoder)?; //self.rr_type.emit(encoder)?;
197DNSClass::for_opt(self.max_payload()).emit(encoder)?; // self.dns_class.emit(encoder)?;
198199 // rebuild the TTL field
200let mut ttl: u32 = u32::from(self.rcode_high()) << 24;
201 ttl |= u32::from(self.version()) << 16;
202203if self.dnssec_ok() {
204 ttl |= 0x0000_8000;
205 }
206207 encoder.emit_u32(ttl)?;
208209// write the opts as rdata...
210let place = encoder.place::<u16>()?;
211 opt::emit(encoder, &self.options)?;
212let len = encoder.len_since_place(&place);
213assert!(len <= u16::max_value() as usize);
214215 place.replace(encoder, len as u16)?;
216Ok(())
217 }
218}
219220impl fmt::Display for Edns {
221fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
222let version = self.version;
223let dnssec_ok = self.dnssec_ok;
224let max_payload = self.max_payload;
225226write!(
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}
236237#[cfg(feature = "dnssec")]
238#[test]
239fn test_encode_decode() {
240use crate::rr::dnssec::SupportedAlgorithms;
241242let mut edns: Edns = Edns::new();
243244 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()));
250251let record: Record = (&edns).into();
252let edns_decode: Edns = (&record).into();
253254assert_eq!(edns.dnssec_ok(), edns_decode.dnssec_ok());
255assert_eq!(edns.max_payload(), edns_decode.max_payload());
256assert_eq!(edns.version(), edns_decode.version());
257assert_eq!(edns.rcode_high(), edns_decode.rcode_high());
258assert_eq!(edns.options(), edns_decode.options());
259260// re-insert and remove using mut
261edns.options_mut()
262 .insert(EdnsOption::DAU(SupportedAlgorithms::all()));
263 edns.options_mut().remove(EdnsCode::DAU);
264assert!(edns.option(EdnsCode::DAU).is_none());
265}