blob: bae7e9f6a7ec473250edcf31c83ca88b2ec1e49c [file] [log] [blame]
/*
* Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! Extended DNS options
use crate::error::*;
use crate::rr::rdata::opt::{self, EdnsCode, EdnsOption};
use crate::rr::rdata::OPT;
use crate::rr::{DNSClass, Name, RData, Record, RecordType};
use crate::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, 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,
// Is DNSSec supported (from TTL)
dnssec_ok: bool,
// max payload size, minimum of 512, (from RR CLASS)
max_payload: u16,
options: OPT,
}
impl Default for Edns {
fn default() -> Self {
Edns {
rcode_high: 0,
version: 0,
dnssec_ok: false,
max_payload: 512,
options: OPT::default(),
}
}
}
impl Edns {
/// Creates a new extended DNS object.
pub fn new() -> Self {
Default::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
}
/// Specifies that DNSSec is supported for this Client or Server
pub fn dnssec_ok(&self) -> bool {
self.dnssec_ok
}
/// 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
}
/// Set the high order bits for the result code.
pub fn set_rcode_high(&mut self, rcode_high: u8) {
self.rcode_high = rcode_high
}
/// Set the EDNS version
pub fn set_version(&mut self, version: u8) {
self.version = version
}
/// Set to true if DNSSec is supported
pub fn set_dnssec_ok(&mut self, dnssec_ok: bool) {
self.dnssec_ok = dnssec_ok
}
/// 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) {
self.max_payload = max_payload.max(512);
}
/// Set the specified EDNS option
pub fn set_option(&mut self, option: EdnsOption) {
self.options.insert(option);
}
}
impl<'a> From<&'a Record> for Edns {
fn from(value: &'a Record) -> Self {
assert!(value.rr_type() == RecordType::OPT);
let rcode_high: u8 = ((value.ttl() & 0xFF00_0000u32) >> 24) as u8;
let version: u8 = ((value.ttl() & 0x00FF_0000u32) >> 16) as u8;
let dnssec_ok: bool = value.ttl() & 0x0000_8000 == 0x0000_8000;
let max_payload: u16 = u16::from(value.dns_class());
let options: OPT = match *value.rdata() {
RData::NULL(..) => {
// NULL, there was no data in the OPT
OPT::default()
}
RData::OPT(ref 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.rdata()) // valid panic, never should happen
}
};
Edns {
rcode_high,
version,
dnssec_ok,
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) -> Record {
let mut record: Record = Record::new();
record.set_name(Name::root());
record.set_rr_type(RecordType::OPT);
record.set_dns_class(DNSClass::for_opt(value.max_payload()));
// rebuild the TTL field
let mut ttl: u32 = u32::from(value.rcode_high()) << 24;
ttl |= u32::from(value.version()) << 16;
if value.dnssec_ok() {
ttl |= 0x0000_8000;
}
record.set_ttl(ttl);
// 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/
record.set_rdata(RData::OPT(value.options().clone()));
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 = u32::from(self.rcode_high()) << 24;
ttl |= u32::from(self.version()) << 16;
if self.dnssec_ok() {
ttl |= 0x0000_8000;
}
encoder.emit_u32(ttl)?;
// write the opts as rdata...
let place = encoder.place::<u16>()?;
opt::emit(encoder, &self.options)?;
let len = encoder.len_since_place(&place);
assert!(len <= u16::max_value() as usize);
place.replace(encoder, len as u16)?;
Ok(())
}
}
#[cfg(feature = "dnssec")]
#[test]
fn test_encode_decode() {
use crate::rr::dnssec::SupportedAlgorithms;
let mut edns: Edns = Edns::new();
edns.set_dnssec_ok(true);
edns.set_max_payload(0x8008);
edns.set_version(0x40);
edns.set_rcode_high(0x01);
edns.set_option(EdnsOption::DAU(SupportedAlgorithms::all()));
let record: Record = (&edns).into();
let edns_decode: Edns = (&record).into();
assert_eq!(edns.dnssec_ok(), edns_decode.dnssec_ok());
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());
}