diff --git a/src/new/base/record.rs b/src/new/base/record.rs index 8c9bceb29..766c38dd0 100644 --- a/src/new/base/record.rs +++ b/src/new/base/record.rs @@ -279,6 +279,9 @@ impl RType { /// The type of an [`Aaaa`](crate::new::rdata::Aaaa) record. pub const AAAA: Self = Self::new(28); + /// The type of an [`Srv`](crate::new::rdata::Srv) record. + pub const SRV: Self = Self::new(33); + /// The type of a [`DName`](crate::new::rdata::DName) record. pub const DNAME: Self = Self::new(39); @@ -361,6 +364,7 @@ impl RType { | Self::PTR | Self::MX | Self::RP + | Self::SRV | Self::DNAME | Self::RRSIG ) @@ -398,6 +402,7 @@ impl fmt::Debug for RType { Self::TXT => "RType::TXT", Self::RP => "RType::RP", Self::AAAA => "RType::AAAA", + Self::SRV => "RType::SRV", Self::DNAME => "RType::DNAME", Self::OPT => "RType::OPT", Self::DS => "RType::DS", diff --git a/src/new/rdata/mod.rs b/src/new/rdata/mod.rs index d66d37306..930c18551 100644 --- a/src/new/rdata/mod.rs +++ b/src/new/rdata/mod.rs @@ -43,6 +43,7 @@ //! - [`Mx`] //! - [`Txt`] //! - [`Rp`] +//! - [`Srv`] //! //! Indirection types: //! - [`CName`] @@ -112,6 +113,9 @@ pub use edns::{EdnsOptionsIter, Opt}; mod rp; pub use rp::Rp; +mod srv; +pub use srv::Srv; + mod dnssec; pub use dnssec::{ DNSKey, DNSKeyFlags, DigestType, Ds, NSec, NSec3, NSec3Flags, @@ -280,6 +284,9 @@ define_record_data! { /// The IPv6 address of a host responsible for this domain. Aaaa(Aaaa) = AAAA, + /// The location of a service. + Srv(Srv) = SRV, + /// Redirection for the descendants of this domain. DName(&'a DName) = DNAME, @@ -328,6 +335,7 @@ impl<'a, N> RecordData<'a, N> { Self::Txt(r) => RecordData::Txt(r), Self::Rp(r) => RecordData::Rp(r.map_names(f)), Self::Aaaa(r) => RecordData::Aaaa(r), + Self::Srv(r) => RecordData::Srv(r.map_name(f)), Self::DName(r) => RecordData::DName(r), Self::Opt(r) => RecordData::Opt(r), Self::Ds(r) => RecordData::Ds(r), @@ -357,6 +365,7 @@ impl<'a, N> RecordData<'a, N> { Self::Txt(r) => RecordData::Txt(r), Self::Rp(r) => RecordData::Rp(r.map_names_by_ref(f)), Self::Aaaa(r) => RecordData::Aaaa(*r), + Self::Srv(r) => RecordData::Srv(r.map_name_by_ref(f)), Self::DName(r) => RecordData::DName(r), Self::Opt(r) => RecordData::Opt(r), Self::Ds(r) => RecordData::Ds(r), @@ -392,6 +401,7 @@ impl<'a, N> RecordData<'a, N> { Self::Txt(r) => RecordData::Txt(copy_to_bump(*r, bump)), Self::Rp(r) => RecordData::Rp(r.clone()), Self::Aaaa(r) => RecordData::Aaaa(*r), + Self::Srv(r) => RecordData::Srv(r.clone()), Self::DName(r) => RecordData::DName(copy_to_bump(*r, bump)), Self::Opt(r) => RecordData::Opt(copy_to_bump(*r, bump)), Self::Ds(r) => RecordData::Ds(copy_to_bump(*r, bump)), @@ -447,6 +457,9 @@ impl<'a, N: SplitMessageBytes<'a>> ParseRecordData<'a> for RecordData<'a, N> { RType::AAAA => { Aaaa::parse_bytes(&contents[start..]).map(Self::Aaaa) } + RType::SRV => { + Srv::parse_message_bytes(contents, start).map(Self::Srv) + } RType::DNAME => { <&DName>::parse_bytes(&contents[start..]).map(Self::DName) } diff --git a/src/new/rdata/srv.rs b/src/new/rdata/srv.rs new file mode 100644 index 000000000..50d13a1ec --- /dev/null +++ b/src/new/rdata/srv.rs @@ -0,0 +1,255 @@ +//! The Service Locator record data type. +//! +//! See [RFC 2782](https://datatracker.ietf.org/doc/html/rfc2782). + +use core::cmp::Ordering; + +use crate::new::base::build::{BuildInMessage, NameCompressor}; +use crate::new::base::name::CanonicalName; +use crate::new::base::parse::{ParseMessageBytes, SplitMessageBytes}; +use crate::new::base::wire::*; +use crate::new::base::{ + CanonicalRecordData, ParseRecordData, ParseRecordDataBytes, RType, +}; + +//----------- Srv ------------------------------------------------------------ + +/// The location of a service. +/// +/// An [`Srv`] record advertises the host and port at which a particular +/// service can be reached for a given domain. Multiple [`Srv`] records may +/// exist for the same service; clients use the [`priority`](Self::priority) +/// and [`weight`](Self::weight) fields to choose between them. +/// +/// [`Srv`] is specified by [RFC 2782]. +/// +/// [RFC 2782]: https://datatracker.ietf.org/doc/html/rfc2782 +/// +/// ## Wire Format +/// +/// The wire format of an [`Srv`] record is the concatenation of three 16-bit +/// big-endian integers (priority, weight, port) followed by the target domain +/// name. Per [RFC 2782], the labels in the target name MUST NOT be compressed +/// when sending; this implementation will still decompress target names that +/// it receives, for compatibility with non-conforming senders. +/// +/// ## Usage +/// +/// Because [`Srv`] is a record data type, it is usually handled within an enum +/// like [`RecordData`]. This section describes how to use it independently +/// (or when building new record data from scratch). +/// +/// [`RecordData`]: crate::new::rdata::RecordData +/// +/// In order to build an [`Srv`], it's first important to choose a domain name +/// type. For short-term usage (where the [`Srv`] is a local variable), it is +/// common to pick [`RevNameBuf`]. If the [`Srv`] will be placed on the heap, +/// Box<[`RevName`]> will be more efficient. +/// +/// [`RevName`]: crate::new::base::name::RevName +/// [`RevNameBuf`]: crate::new::base::name::RevNameBuf +/// +/// The primary way to build a new [`Srv`] is to construct each field manually. +/// To parse an [`Srv`] from a DNS message, use [`ParseMessageBytes`]. In case +/// the input bytes don't use name compression, [`ParseBytes`] can be used. +/// +/// ``` +/// # use domain::new::base::name::{Name, RevNameBuf}; +/// # use domain::new::base::wire::{BuildBytes, ParseBytes, ParseBytesZC}; +/// # use domain::new::rdata::Srv; +/// # +/// // Build an 'Srv' manually: +/// let manual: Srv = Srv { +/// priority: 10.into(), +/// weight: 20.into(), +/// port: 5060.into(), +/// target: "sip.example.org".parse().unwrap(), +/// }; +/// +/// // Its wire format serialization looks like: +/// let bytes = b"\ +/// \x00\x0A\x00\x14\x13\xC4\ +/// \x03sip\x07example\x03org\x00"; +/// # let mut buffer = [0u8; 23]; +/// # manual.build_bytes(&mut buffer).unwrap(); +/// # assert_eq!(*bytes, buffer); +/// +/// // Parse an 'Srv' from the wire format, without name decompression: +/// let from_wire: Srv = Srv::parse_bytes(bytes).unwrap(); +/// # assert_eq!(manual, from_wire); +/// +/// // See 'ParseMessageBytes' for parsing with name decompression. +/// ``` +/// +/// Since [`Srv`] is a sized type, and it implements [`Copy`] and [`Clone`], +/// it's straightforward to handle and move around. However, this depends on +/// the domain name type. It can be changed using [`Srv::map_name()`] and +/// [`Srv::map_name_by_ref()`]. +/// +/// For debugging, [`Srv`] can be formatted using [`fmt::Debug`]. +/// +/// [`fmt::Debug`]: core::fmt::Debug +/// +/// To serialize an [`Srv`] in the wire format, use [`BuildInMessage`] (which +/// supports name compression). If name compression is not desired, use +/// [`BuildBytes`]. +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BuildBytes, + ParseBytes, + SplitBytes, +)] +pub struct Srv { + /// The priority of this target host. + /// + /// Clients MUST attempt to contact target hosts in order of increasing + /// priority; targets at the same priority are selected using + /// [`weight`](Self::weight). + pub priority: U16, + + /// The relative weight for selection among targets of equal priority. + /// + /// Larger values are more likely to be selected. A weight of zero + /// indicates that the target should only be selected if no other targets + /// of the same priority are available. + pub weight: U16, + + /// The TCP/UDP port on which the service is offered. + pub port: U16, + + /// The domain name of the target host. + /// + /// A target equal to the root domain (`.`) means that the service is + /// decidedly not available at this domain. + pub target: N, +} + +//--- Interaction + +impl Srv { + /// Map the domain name within to another type. + pub fn map_name R>(self, f: F) -> Srv { + Srv { + priority: self.priority, + weight: self.weight, + port: self.port, + target: (f)(self.target), + } + } + + /// Map a reference to the domain name within to another type. + pub fn map_name_by_ref<'r, R, F: FnOnce(&'r N) -> R>( + &'r self, + f: F, + ) -> Srv { + Srv { + priority: self.priority, + weight: self.weight, + port: self.port, + target: (f)(&self.target), + } + } +} + +//--- Canonical operations + +impl CanonicalRecordData for Srv { + fn build_canonical_bytes<'b>( + &self, + bytes: &'b mut [u8], + ) -> Result<&'b mut [u8], TruncationError> { + let bytes = self.priority.build_bytes(bytes)?; + let bytes = self.weight.build_bytes(bytes)?; + let bytes = self.port.build_bytes(bytes)?; + let bytes = self.target.build_lowercased_bytes(bytes)?; + Ok(bytes) + } + + fn cmp_canonical(&self, other: &Self) -> Ordering { + self.priority + .cmp(&other.priority) + .then_with(|| self.weight.cmp(&other.weight)) + .then_with(|| self.port.cmp(&other.port)) + .then_with(|| self.target.cmp_lowercase_composed(&other.target)) + } +} + +//--- Parsing from DNS messages + +impl<'a, N: ParseMessageBytes<'a>> ParseMessageBytes<'a> for Srv { + fn parse_message_bytes( + contents: &'a [u8], + start: usize, + ) -> Result { + let (&priority, rest) = <&U16>::split_message_bytes(contents, start)?; + let (&weight, rest) = <&U16>::split_message_bytes(contents, rest)?; + let (&port, rest) = <&U16>::split_message_bytes(contents, rest)?; + let target = N::parse_message_bytes(contents, rest)?; + Ok(Self { + priority, + weight, + port, + target, + }) + } +} + +//--- Building into DNS messages + +impl BuildInMessage for Srv { + fn build_in_message( + &self, + contents: &mut [u8], + mut start: usize, + compressor: &mut NameCompressor, + ) -> Result { + start = self + .priority + .as_bytes() + .build_in_message(contents, start, compressor)?; + start = self + .weight + .as_bytes() + .build_in_message(contents, start, compressor)?; + start = self + .port + .as_bytes() + .build_in_message(contents, start, compressor)?; + start = self.target.build_in_message(contents, start, compressor)?; + Ok(start) + } +} + +//--- Parsing record data + +impl<'a, N: ParseMessageBytes<'a>> ParseRecordData<'a> for Srv { + fn parse_record_data( + contents: &'a [u8], + start: usize, + rtype: RType, + ) -> Result { + match rtype { + RType::SRV => Self::parse_message_bytes(contents, start), + _ => Err(ParseError), + } + } +} + +impl<'a, N: ParseBytes<'a>> ParseRecordDataBytes<'a> for Srv { + fn parse_record_data_bytes( + bytes: &'a [u8], + rtype: RType, + ) -> Result { + match rtype { + RType::SRV => Self::parse_bytes(bytes), + _ => Err(ParseError), + } + } +}