From 3f8eb48aac674a2cbd13767c7ca8eef74da6eac1 Mon Sep 17 00:00:00 2001 From: Benjamin Fry Date: Sat, 6 Mar 2021 18:34:08 -0800 Subject: [PATCH] Add new HTTPS and SVCB record types --- copyright.txt | 2 +- .../client/src/serialize/txt/parse_rdata.rs | 6 + crates/proto/src/rr/rdata/mod.rs | 2 + crates/proto/src/rr/rdata/svcb.rs | 450 ++++++++++++++++++ crates/proto/src/rr/record_data.rs | 89 +++- crates/proto/src/rr/record_type.rs | 38 +- 6 files changed, 553 insertions(+), 34 deletions(-) create mode 100644 crates/proto/src/rr/rdata/svcb.rs diff --git a/copyright.txt b/copyright.txt index 6bab617d23..d52a2a065a 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,4 +1,4 @@ -// Copyright 2015-2020 Benjamin Fry +// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license caa::parse(tokens).map(RData::CAA)?, RecordType::CNAME => RData::CNAME(name::parse(tokens, origin)?), RecordType::HINFO => RData::HINFO(hinfo::parse(tokens)?), + // FIXME: actually implement this + #[allow(clippy::unimplemented)] + RecordType::HTTPS => unimplemented!("HTTPS records not yet supported in zone files"), RecordType::IXFR => panic!("parsing IXFR doesn't make sense"), // valid panic, never should happen RecordType::MX => RData::MX(mx::parse(tokens, origin)?), RecordType::NAPTR => RData::NAPTR(naptr::parse(tokens, origin)?), @@ -57,6 +60,9 @@ impl RDataParser for RData { RecordType::SOA => RData::SOA(soa::parse(tokens, origin)?), RecordType::SRV => RData::SRV(srv::parse(tokens, origin)?), RecordType::SSHFP => RData::SSHFP(sshfp::parse(tokens)?), + // FIXME: actually implement this + #[allow(clippy::unimplemented)] + RecordType::SVCB => unimplemented!("SVCB records not yet supported in zone files"), RecordType::TLSA => RData::TLSA(tlsa::parse(tokens)?), RecordType::TXT => RData::TXT(txt::parse(tokens)?), RecordType::DNSSEC(DNSSECRecordType::SIG) => panic!("parsing SIG doesn't make sense"), // valid panic, never should happen diff --git a/crates/proto/src/rr/rdata/mod.rs b/crates/proto/src/rr/rdata/mod.rs index e08c686543..41dec58bc4 100644 --- a/crates/proto/src/rr/rdata/mod.rs +++ b/crates/proto/src/rr/rdata/mod.rs @@ -32,6 +32,7 @@ pub mod opt; pub mod soa; pub mod srv; pub mod sshfp; +pub mod svcb; pub mod tlsa; pub mod txt; @@ -45,5 +46,6 @@ pub use self::opt::OPT; pub use self::soa::SOA; pub use self::srv::SRV; pub use self::sshfp::SSHFP; +pub use self::svcb::SVCB; pub use self::tlsa::TLSA; pub use self::txt::TXT; diff --git a/crates/proto/src/rr/rdata/svcb.rs b/crates/proto/src/rr/rdata/svcb.rs new file mode 100644 index 0000000000..cb9cd634ed --- /dev/null +++ b/crates/proto/src/rr/rdata/svcb.rs @@ -0,0 +1,450 @@ +// Copyright 2015-2021 Benjamin Fry +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! SSHFP records for SSH public key fingerprints +use std::cmp::{Ord, Ordering, PartialOrd}; +use std::fmt; + +use crate::error::*; +use crate::rr::Name; +use crate::serialize::binary::*; + +/// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-2.2) +/// +/// ```text +/// 2.2. RDATA wire format +/// +/// The RDATA for the SVCB RR consists of: +/// +/// * a 2 octet field for SvcPriority as an integer in network byte +/// order. +/// * the uncompressed, fully-qualified TargetName, represented as a +/// sequence of length-prefixed labels as in Section 3.1 of [RFC1035]. +/// * the SvcParams, consuming the remainder of the record (so smaller +/// than 65535 octets and constrained by the RDATA and DNS message +/// sizes). +/// +/// When the list of SvcParams is non-empty (ServiceMode), it contains a +/// series of SvcParamKey=SvcParamValue pairs, represented as: +/// +/// * a 2 octet field containing the SvcParamKey as an integer in +/// network byte order. (See Section 14.3.2 for the defined values.) +/// * a 2 octet field containing the length of the SvcParamValue as an +/// integer between 0 and 65535 in network byte order (but constrained +/// by the RDATA and DNS message sizes). +/// * an octet string of this length whose contents are in a format +/// determined by the SvcParamKey. +/// +/// SvcParamKeys SHALL appear in increasing numeric order. +/// +/// Clients MUST consider an RR malformed if: +/// +/// * the end of the RDATA occurs within a SvcParam. +/// * SvcParamKeys are not in strictly increasing numeric order. +/// * the SvcParamValue for an SvcParamKey does not have the expected +/// format. +/// +/// Note that the second condition implies that there are no duplicate +/// SvcParamKeys. +/// +/// If any RRs are malformed, the client MUST reject the entire RRSet and +/// fall back to non-SVCB connection establishment. +/// ``` +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct SVCB { + svc_priority: u16, + target_name: Name, + svc_params: Vec<(SvcParamKey, SvcParamValue)>, +} + +impl SVCB { + /// Create a new SVCB record from parts + /// + /// It is up to the caller to validate the data going into the record + pub fn new( + svc_priority: u16, + target_name: Name, + svc_params: Vec<(SvcParamKey, SvcParamValue)>, + ) -> Self { + Self { + svc_priority, + target_name, + svc_params, + } + } +} + +/// ```text +/// 14.3.2. Initial contents +/// +/// The "Service Binding (SVCB) Parameter Registry" shall initially be +/// populated with the registrations below: +/// +/// +=============+=================+======================+===========+ +/// | Number | Name | Meaning | Reference | +/// +=============+=================+======================+===========+ +/// | 0 | mandatory | Mandatory keys in | (This | +/// | | | this RR | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// | 1 | alpn | Additional supported | (This | +/// | | | protocols | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// | 2 | no-default-alpn | No support for | (This | +/// | | | default protocol | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// | 3 | port | Port for alternative | (This | +/// | | | endpoint | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// | 4 | ipv4hint | IPv4 address hints | (This | +/// | | | | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// | 5 | echconfig | Encrypted | (This | +/// | | | ClientHello info | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// | 6 | ipv6hint | IPv6 address hints | (This | +/// | | | | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// | 65280-65534 | keyNNNNN | Private Use | (This | +/// | | | | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// | 65535 | key65535 | Reserved ("Invalid | (This | +/// | | | key") | document) | +/// +-------------+-----------------+----------------------+-----------+ +/// +/// parsing done via: +/// * a 2 octet field containing the SvcParamKey as an integer in +/// network byte order. (See Section 14.3.2 for the defined values.) +/// ``` +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum SvcParamKey { + /// Mandatory keys in this RR + Mandatory, + /// Additional supported protocols + Alpn, + /// No support for default protocol + NoDefaultAlpn, + /// Port for alternative endpoint + Port, + /// IPv4 address hints + Ipv4Hint, + /// Encrypted ClientHello info + EchConfig, + /// IPv6 address hints + Ipv6Hint, + /// Private Use + Key(u16), + /// Reserved ("Invalid key") + Key65535, + /// Unknown + Unknown(u16), +} + +impl From for SvcParamKey { + fn from(val: u16) -> Self { + match val { + 0 => SvcParamKey::Mandatory, + 1 => SvcParamKey::Alpn, + 2 => SvcParamKey::NoDefaultAlpn, + 3 => SvcParamKey::Port, + 4 => SvcParamKey::Ipv4Hint, + 5 => SvcParamKey::EchConfig, + 6 => SvcParamKey::Ipv6Hint, + 65280..=65534 => SvcParamKey::Key(val), + 65535 => SvcParamKey::Key65535, + _ => SvcParamKey::Unknown(val), + } + } +} + +impl From for u16 { + fn from(val: SvcParamKey) -> Self { + match val { + SvcParamKey::Mandatory => 0, + SvcParamKey::Alpn => 1, + SvcParamKey::NoDefaultAlpn => 2, + SvcParamKey::Port => 3, + SvcParamKey::Ipv4Hint => 4, + SvcParamKey::EchConfig => 5, + SvcParamKey::Ipv6Hint => 6, + SvcParamKey::Key(val) => val, + SvcParamKey::Key65535 => 65535, + SvcParamKey::Unknown(val) => val, + } + } +} + +impl fmt::Display for SvcParamKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let mut write_key = |name| write!(f, "{}", name); + + match *self { + SvcParamKey::Mandatory => write_key("mandatory")?, + SvcParamKey::Alpn => write_key("alpn")?, + SvcParamKey::NoDefaultAlpn => write_key("no-default-alpn")?, + SvcParamKey::Port => write_key("port")?, + SvcParamKey::Ipv4Hint => write_key("ipv4hint")?, + SvcParamKey::EchConfig => write_key("echconfig")?, + SvcParamKey::Ipv6Hint => write_key("ipv6hint")?, + SvcParamKey::Key(val) => write!(f, "key{}", val)?, + SvcParamKey::Key65535 => write_key("key65535")?, + SvcParamKey::Unknown(val) => write!(f, "unknown{}", val)?, + } + + Ok(()) + } +} + +impl Ord for SvcParamKey { + fn cmp(&self, other: &Self) -> Ordering { + u16::from(*self).cmp(&u16::from(*other)) + } +} + +impl PartialOrd for SvcParamKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Warning, it is currently up to users of this type to validate the data against that expected by the key +/// +/// ```text +/// * a 2 octet field containing the length of the SvcParamValue as an +/// integer between 0 and 65535 in network byte order (but constrained +/// by the RDATA and DNS message sizes). +/// * an octet string of this length whose contents are in a format +/// determined by the SvcParamKey. +/// ``` +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[repr(transparent)] +pub struct SvcParamValue(Vec); + +impl SvcParamValue { + /// Return the inner data as a slice of bytes + pub fn as_slice(&self) -> &[u8] { + &self.0 + } +} + +impl From> for SvcParamValue { + fn from(val: Vec) -> Self { + Self(val) + } +} + +impl From for Vec { + fn from(val: SvcParamValue) -> Self { + val.0 + } +} + +/// Reads the SVCB record from the decoder. +/// +/// ```text +/// Clients MUST consider an RR malformed if: +/// +/// * the end of the RDATA occurs within a SvcParam. +/// * SvcParamKeys are not in strictly increasing numeric order. +/// * the SvcParamValue for an SvcParamKey does not have the expected +/// format. +/// +/// Note that the second condition implies that there are no duplicate +/// SvcParamKeys. +/// +/// If any RRs are malformed, the client MUST reject the entire RRSet and +/// fall back to non-SVCB connection establishment. +/// ``` +pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict) -> ProtoResult { + let start_index = decoder.index(); + + let svc_priority = decoder.read_u16()?.unverified(/*any u16 is valid*/); + let target_name = Name::read(decoder)?; + + let mut remainder_len = rdata_length.map(|len| len as usize - (decoder.index() - start_index)).unverified(/*valid len*/); + let mut svc_params: Vec<(SvcParamKey, SvcParamValue)> = Vec::new(); + + // must have at least 4 bytes left for the key and the length + while remainder_len >= 4 { + // a 2 octet field containing the SvcParamKey as an integer in + // network byte order. (See Section 14.3.2 for the defined values.) + let key: SvcParamKey = decoder.read_u16()?.unverified(/*any u16 is valid*/).into(); + + // a 2 octet field containing the length of the SvcParamValue as an + // integer between 0 and 65535 in network byte order (but constrained + // by the RDATA and DNS message sizes). + let len: u16 = decoder + .read_u16()? + .verify_unwrap(|len| *len as usize <= remainder_len) + .map_err(|u| { + ProtoError::from(format!( + "length of SvcParamValue ({}) exceeds remainder in RDATA ({})", + u, remainder_len + )) + })?; + + // an octet string of this length whose contents are in a format + // determined by the SvcParamKey. + let value = decoder.read_vec(len as usize)?.unverified(/*char data for users*/); + + if let Some(last_key) = svc_params.last().map(|(key, _)| key) { + if last_key >= &key { + return Err(ProtoError::from("SvcParams out of order")); + } + } + + svc_params.push((key, value.into())); + remainder_len = rdata_length.map(|len| len as usize - (decoder.index() - start_index)).unverified(/*valid len*/); + } + + Ok(SVCB { + svc_priority, + target_name, + svc_params, + }) +} + +/// Write the RData from the given Decoder +pub fn emit(encoder: &mut BinEncoder<'_>, svcb: &SVCB) -> ProtoResult<()> { + svcb.svc_priority.emit(encoder)?; + svcb.target_name.emit(encoder)?; + + let mut last_key: Option = None; + for (key, param) in svcb.svc_params.iter() { + if let Some(last_key) = last_key { + if key <= &last_key { + return Err(ProtoError::from("SvcParams out of order")); + } + } + + if param.as_slice().len() > u16::MAX as usize { + return Err(ProtoError::from("SvcParams exceeds u16 max size")); + } + + encoder.emit_u16((*key).into())?; + encoder.emit_u16(param.as_slice().len() as u16)?; + encoder.emit_vec(param.as_slice())?; + + last_key = Some(*key); + } + + Ok(()) +} + +/// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-10.3) +/// +/// ```text +/// simple.example. 7200 IN HTTPS 1 . alpn=h3 +/// pool 7200 IN HTTPS 1 h3pool alpn=h2,h3 echconfig="123..." +/// HTTPS 2 . alpn=h2 echconfig="abc..." +/// @ 7200 IN HTTPS 0 www +/// _8765._baz.api.example.com. 7200 IN SVCB 0 svc4-baz.example.net. +/// ``` +impl fmt::Display for SVCB { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "{svc_priority} {target_name}", + svc_priority = self.svc_priority, + target_name = self.target_name, + )?; + + for (key, param) in self.svc_params.iter() { + let param = String::from_utf8_lossy(param.as_slice()); + write!(f, " {key}=\"{param}\"", key = key, param = param)? + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_svcb_key() { + assert_eq!(SvcParamKey::Mandatory, 0.into()); + assert_eq!(SvcParamKey::Alpn, 1.into()); + assert_eq!(SvcParamKey::NoDefaultAlpn, 2.into()); + assert_eq!(SvcParamKey::Port, 3.into()); + assert_eq!(SvcParamKey::Ipv4Hint, 4.into()); + assert_eq!(SvcParamKey::EchConfig, 5.into()); + assert_eq!(SvcParamKey::Ipv6Hint, 6.into()); + assert_eq!(SvcParamKey::Key(65280), 65280.into()); + assert_eq!(SvcParamKey::Key(65534), 65534.into()); + assert_eq!(SvcParamKey::Key65535, 65535.into()); + assert_eq!(SvcParamKey::Unknown(65279), 65279.into()); + } + + #[test] + fn read_svcb_key_to_u16() { + assert_eq!(u16::from(SvcParamKey::Mandatory), 0); + assert_eq!(u16::from(SvcParamKey::Alpn), 1); + assert_eq!(u16::from(SvcParamKey::NoDefaultAlpn), 2); + assert_eq!(u16::from(SvcParamKey::Port), 3); + assert_eq!(u16::from(SvcParamKey::Ipv4Hint), 4); + assert_eq!(u16::from(SvcParamKey::EchConfig), 5); + assert_eq!(u16::from(SvcParamKey::Ipv6Hint), 6); + assert_eq!(u16::from(SvcParamKey::Key(65280)), 65280); + assert_eq!(u16::from(SvcParamKey::Key(65534)), 65534); + assert_eq!(u16::from(SvcParamKey::Key65535), 65535); + assert_eq!(u16::from(SvcParamKey::Unknown(65279)), 65279); + } + + #[track_caller] + fn test_encode_decode(rdata: SVCB) { + let mut bytes = Vec::new(); + let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); + emit(&mut encoder, &rdata).expect("failed to emit SVCB"); + let bytes = encoder.into_bytes(); + + println!("svcb: {}", rdata); + println!("bytes: {:?}", bytes); + + let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); + let read_rdata = + read(&mut decoder, Restrict::new(bytes.len() as u16)).expect("failed to read back"); + assert_eq!(rdata, read_rdata); + } + + #[test] + fn test_encode_decode_svcb() { + test_encode_decode(SVCB::new( + 0, + Name::from_utf8("www.example.com.").unwrap(), + vec![], + )); + test_encode_decode(SVCB::new( + 0, + Name::from_utf8(".").unwrap(), + vec![(SvcParamKey::Alpn, "h2".as_bytes().to_vec().into())], + )); + test_encode_decode(SVCB::new( + 0, + Name::from_utf8("example.com.").unwrap(), + vec![ + (SvcParamKey::Mandatory, "alpn".as_bytes().to_vec().into()), + (SvcParamKey::Alpn, "h2".as_bytes().to_vec().into()), + ], + )); + } + + #[test] + #[should_panic( + expected = "failed to emit SVCB: ProtoError { kind: Message(\"SvcParams out of order\") }" + )] + fn test_encode_decode_svcb_bad_order() { + test_encode_decode(SVCB::new( + 0, + Name::from_utf8(".").unwrap(), + vec![ + (SvcParamKey::Alpn, "h2".as_bytes().to_vec().into()), + (SvcParamKey::Mandatory, "alpn".as_bytes().to_vec().into()), + ], + )); + } +} diff --git a/crates/proto/src/rr/record_data.rs b/crates/proto/src/rr/record_data.rs index 800cf6351c..21196bde44 100644 --- a/crates/proto/src/rr/record_data.rs +++ b/crates/proto/src/rr/record_data.rs @@ -1,18 +1,9 @@ -/* - * Copyright (C) 2015-2019 Benjamin Fry - * - * 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. - */ +// Copyright 2015-2021 Benjamin Fry +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. //! record data enum variants @@ -27,7 +18,9 @@ use log::{trace, warn}; use super::domain::Name; use super::rdata; -use super::rdata::{CAA, HINFO, MX, NAPTR, NULL, OPENPGPKEY, OPT, SOA, SRV, SSHFP, TLSA, TXT}; +use super::rdata::{ + CAA, HINFO, MX, NAPTR, NULL, OPENPGPKEY, OPT, SOA, SRV, SSHFP, SVCB, TLSA, TXT, +}; use super::record_type::RecordType; use crate::error::*; use crate::serialize::binary::*; @@ -195,6 +188,29 @@ pub enum RData { /// `HINFO` is also used by [RFC 8482](https://tools.ietf.org/html/rfc8482) HINFO(HINFO), + /// [RFC draft-ietf-dnsop-svcb-https-03, DNS SVCB and HTTPS RRs](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-8) + /// + /// ```text + /// 8. Using SVCB with HTTPS and HTTP + /// + /// Use of any protocol with SVCB requires a protocol-specific mapping + /// specification. This section specifies the mapping for HTTPS and + /// HTTP. + /// + /// To enable special handling for the HTTPS and HTTP use-cases, the + /// HTTPS RR type is defined as a SVCB-compatible RR type, specific to + /// the https and http schemes. Clients MUST NOT perform SVCB queries or + /// accept SVCB responses for "https" or "http" schemes. + /// + /// The HTTPS RR wire format and presentation format are identical to + /// SVCB, and both share the SvcParamKey registry. SVCB semantics apply + /// equally to HTTPS RRs unless specified otherwise. The presentation + /// format of the record is: + /// + /// Name TTL IN HTTPS SvcPriority TargetName SvcParams + /// ``` + HTTPS(SVCB), + /// ```text /// 3.3.9. MX RDATA format /// @@ -574,6 +590,31 @@ pub enum RData { /// [RFC 7479](https://tools.ietf.org/html/rfc7479). SSHFP(SSHFP), + /// [RFC draft-ietf-dnsop-svcb-https-03, DNS SVCB and HTTPS RRs](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-2) + /// + /// ```text + /// 2. The SVCB record type + /// + /// The SVCB DNS resource record (RR) type (RR type 64) is used to locate + /// alternative endpoints for a service. + /// + /// The algorithm for resolving SVCB records and associated address + /// records is specified in Section 3. + /// + /// Other SVCB-compatible resource record types can also be defined as- + /// needed. In particular, the HTTPS RR (RR type 65) provides special + /// handling for the case of "https" origins as described in Section 8. + /// + /// SVCB RRs are extensible by a list of SvcParams, which are pairs + /// consisting of a SvcParamKey and a SvcParamValue. Each SvcParamKey + /// has a presentation name and a registered number. Values are in a + /// format specific to the SvcParamKey. Their definition should specify + /// both their presentation format and wire encoding (e.g., domain names, + /// binary data, or numeric values). The initial SvcParamKeys and + /// formats are defined in Section 6. + /// ``` + SVCB(SVCB), + /// [RFC 6698, DNS-Based Authentication for TLS](https://tools.ietf.org/html/rfc6698#section-2.1) /// /// ```text @@ -672,6 +713,10 @@ impl RData { trace!("reading HINFO"); rdata::hinfo::read(decoder).map(RData::HINFO) } + RecordType::HTTPS => { + trace!("reading HTTPS"); + rdata::svcb::read(decoder, rdata_length).map(RData::HTTPS) + } RecordType::ZERO => { trace!("reading EMPTY"); return Ok(RData::ZERO); @@ -716,6 +761,10 @@ impl RData { trace!("reading SSHFP"); rdata::sshfp::read(decoder, rdata_length).map(RData::SSHFP) } + RecordType::SVCB => { + trace!("reading SVCB"); + rdata::svcb::read(decoder, rdata_length).map(RData::SVCB) + } RecordType::TLSA => { trace!("reading TLSA"); rdata::tlsa::read(decoder, rdata_length).map(RData::TLSA) @@ -831,6 +880,7 @@ impl RData { rdata::name::emit(encoder, name) } RData::HINFO(ref hinfo) => rdata::hinfo::emit(encoder, hinfo), + RData::HTTPS(ref svcb) => rdata::svcb::emit(encoder, svcb), RData::ZERO => Ok(()), // to_lowercase for rfc4034 and rfc6840 RData::MX(ref mx) => rdata::mx::emit(encoder, mx), @@ -851,6 +901,7 @@ impl RData { RData::SSHFP(ref sshfp) => { encoder.with_canonical_names(|encoder| rdata::sshfp::emit(encoder, sshfp)) } + RData::SVCB(ref svcb) => rdata::svcb::emit(encoder, svcb), RData::TLSA(ref tlsa) => { encoder.with_canonical_names(|encoder| rdata::tlsa::emit(encoder, tlsa)) } @@ -870,6 +921,7 @@ impl RData { RData::CAA(..) => RecordType::CAA, RData::CNAME(..) => RecordType::CNAME, RData::HINFO(..) => RecordType::HINFO, + RData::HTTPS(..) => RecordType::HTTPS, RData::MX(..) => RecordType::MX, RData::NAPTR(..) => RecordType::NAPTR, RData::NS(..) => RecordType::NS, @@ -880,6 +932,7 @@ impl RData { RData::SOA(..) => RecordType::SOA, RData::SRV(..) => RecordType::SRV, RData::SSHFP(..) => RecordType::SSHFP, + RData::SVCB(..) => RecordType::SVCB, RData::TLSA(..) => RecordType::TLSA, RData::TXT(..) => RecordType::TXT, #[cfg(feature = "dnssec")] @@ -913,6 +966,7 @@ impl fmt::Display for RData { // to_lowercase for rfc4034 and rfc6840 RData::CNAME(ref name) | RData::NS(ref name) | RData::PTR(ref name) => w(f, name), RData::HINFO(ref hinfo) => w(f, hinfo), + RData::HTTPS(ref svcb) => w(f, svcb), RData::ZERO => Ok(()), // to_lowercase for rfc4034 and rfc6840 RData::MX(ref mx) => w(f, mx), @@ -926,6 +980,7 @@ impl fmt::Display for RData { // to_lowercase for rfc4034 and rfc6840 RData::SRV(ref srv) => w(f, srv), RData::SSHFP(ref sshfp) => w(f, sshfp), + RData::SVCB(ref svcb) => w(f, svcb), RData::TLSA(ref tlsa) => w(f, tlsa), RData::TXT(ref txt) => w(f, txt), #[cfg(feature = "dnssec")] @@ -1158,6 +1213,7 @@ mod tests { RData::CAA(..) => RecordType::CAA, RData::CNAME(..) => RecordType::CNAME, RData::HINFO(..) => RecordType::HINFO, + RData::HTTPS(..) => RecordType::HTTPS, RData::MX(..) => RecordType::MX, RData::NAPTR(..) => RecordType::NAPTR, RData::NS(..) => RecordType::NS, @@ -1168,6 +1224,7 @@ mod tests { RData::SOA(..) => RecordType::SOA, RData::SRV(..) => RecordType::SRV, RData::SSHFP(..) => RecordType::SSHFP, + RData::SVCB(..) => RecordType::SVCB, RData::TLSA(..) => RecordType::TLSA, RData::TXT(..) => RecordType::TXT, #[cfg(feature = "dnssec")] diff --git a/crates/proto/src/rr/record_type.rs b/crates/proto/src/rr/record_type.rs index c041e8d78e..f582ecd5f1 100644 --- a/crates/proto/src/rr/record_type.rs +++ b/crates/proto/src/rr/record_type.rs @@ -1,18 +1,9 @@ -/* - * Copyright (C) 2015-2019 Benjamin Fry - * - * 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. - */ +// Copyright 2015-2021 Benjamin Fry +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. //! record type definitions @@ -64,6 +55,8 @@ pub enum RecordType { /// RFC 1035[1] host information HINFO, // HIP, // 55 RFC 5205 Host Identity Protocol + /// RFC draft-ietf-dnsop-svcb-https-03 DNS SVCB and HTTPS RRs + HTTPS, // IPSECKEY, // 45 RFC 4025 IPsec Key /// RFC 1996 Incremental Zone Transfer IXFR, @@ -90,6 +83,8 @@ pub enum RecordType { SRV, /// RFC 4255 SSH Public Key Fingerprint SSHFP, + /// RFC draft-ietf-dnsop-svcb-https-03 DNS SVCB and HTTPS RRs + SVCB, // TA, // 32768 N/A DNSSEC Trust Authorities // TKEY, // 249 RFC 2930 Secret key record /// RFC 6698 TLSA certificate association @@ -172,6 +167,7 @@ impl FromStr for RecordType { "CAA" => Ok(RecordType::CAA), "CNAME" => Ok(RecordType::CNAME), "HINFO" => Ok(RecordType::HINFO), + "HTTPS" => Ok(RecordType::HTTPS), "NULL" => Ok(RecordType::NULL), "MX" => Ok(RecordType::MX), "NAPTR" => Ok(RecordType::NAPTR), @@ -181,6 +177,7 @@ impl FromStr for RecordType { "SOA" => Ok(RecordType::SOA), "SRV" => Ok(RecordType::SRV), "SSHFP" => Ok(RecordType::SSHFP), + "SVCB" => Ok(RecordType::SVCB), "TLSA" => Ok(RecordType::TLSA), "TXT" => Ok(RecordType::TXT), "ANY" | "*" => Ok(RecordType::ANY), @@ -213,7 +210,8 @@ impl From for RecordType { 252 => RecordType::AXFR, 257 => RecordType::CAA, 5 => RecordType::CNAME, - 0 => RecordType::ZERO, + 13 => RecordType::HINFO, + 65 => RecordType::HTTPS, 15 => RecordType::MX, 35 => RecordType::NAPTR, 2 => RecordType::NS, @@ -224,8 +222,10 @@ impl From for RecordType { 6 => RecordType::SOA, 33 => RecordType::SRV, 44 => RecordType::SSHFP, + 64 => RecordType::SVCB, 52 => RecordType::TLSA, 16 => RecordType::TXT, + 0 => RecordType::ZERO, #[cfg(feature = "dnssec")] 48/*DNSKEY*/ | 43/*DS*/ | @@ -283,7 +283,8 @@ impl From for &'static str { RecordType::CAA => "CAA", RecordType::CNAME => "CNAME", RecordType::HINFO => "HINFO", - RecordType::ZERO => "", + RecordType::HTTPS => "HTTPS", + RecordType::ZERO => "ZERO", RecordType::IXFR => "IXFR", RecordType::MX => "MX", RecordType::NAPTR => "NAPTR", @@ -295,6 +296,7 @@ impl From for &'static str { RecordType::SOA => "SOA", RecordType::SRV => "SRV", RecordType::SSHFP => "SSHFP", + RecordType::SVCB => "SVCB", RecordType::TLSA => "TLSA", RecordType::TXT => "TXT", #[cfg(feature = "dnssec")] @@ -325,6 +327,7 @@ impl From for u16 { RecordType::CAA => 257, RecordType::CNAME => 5, RecordType::HINFO => 13, + RecordType::HTTPS => 65, RecordType::ZERO => 0, RecordType::IXFR => 251, RecordType::MX => 15, @@ -337,6 +340,7 @@ impl From for u16 { RecordType::SOA => 6, RecordType::SRV => 33, RecordType::SSHFP => 44, + RecordType::SVCB => 64, RecordType::TLSA => 52, RecordType::TXT => 16, #[cfg(feature = "dnssec")]