Skip to main content

hickory_proto/rr/rdata/
srv.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! service records for identify port mapping for specific services on a host
9use alloc::string::ToString;
10use core::{fmt, str::FromStr};
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15use crate::{
16    error::ProtoResult,
17    rr::{RData, RecordData, RecordType, domain::Name},
18    serialize::{
19        binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder, DecodeError, RDataEncoding},
20        txt::ParseError,
21    },
22};
23
24/// [RFC 2782, DNS SRV RR, February 2000](https://tools.ietf.org/html/rfc2782)
25///
26/// ```text
27/// Introductory example
28///
29///  If a SRV-cognizant LDAP client wants to discover a LDAP server that
30///  supports TCP protocol and provides LDAP service for the domain
31///  example.com., it does a lookup of
32///
33/// _ldap._tcp.example.com
34///
35///  as described in [ARM].  The example zone file near the end of this
36///  memo contains answering RRs for an SRV query.
37///
38///  Note: LDAP is chosen as an example for illustrative purposes only,
39///  and the LDAP examples used in this document should not be considered
40///  a definitive statement on the recommended way for LDAP to use SRV
41///  records. As described in the earlier applicability section, consult
42///  the appropriate LDAP documents for the recommended procedures.
43///
44/// The format of the SRV RR
45///
46///  Here is the format of the SRV RR, whose DNS type code is 33:
47///
48/// _Service._Proto.Name TTL Class SRV Priority Weight Port Target
49///
50/// (There is an example near the end of this document.)
51///
52///  Service
53/// The symbolic name of the desired service, as defined in Assigned
54/// Numbers [STD 2] or locally.  An underscore (_) is prepended to
55/// the service identifier to avoid collisions with DNS labels that
56/// occur in nature.
57///
58/// Some widely used services, notably POP, don't have a single
59/// universal name.  If Assigned Numbers names the service
60/// indicated, that name is the only name which is legal for SRV
61/// lookups.  The Service is case insensitive.
62///
63///  Proto
64/// The symbolic name of the desired protocol, with an underscore
65/// (_) prepended to prevent collisions with DNS labels that occur
66/// in nature.  _TCP and _UDP are at present the most useful values
67/// for this field, though any name defined by Assigned Numbers or
68/// locally may be used (as for Service).  The Proto is case
69/// insensitive.
70///
71///  Name
72/// The domain this RR refers to.  The SRV RR is unique in that the
73/// name one searches for is not this name; the example near the end
74/// shows this clearly.
75///
76///  TTL
77/// Standard DNS meaning [RFC 1035].
78///
79///  Class
80/// Standard DNS meaning [RFC 1035].   SRV records occur in the IN
81/// Class.
82///
83/// ```
84#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
85#[derive(Debug, PartialEq, Eq, Hash, Clone)]
86#[non_exhaustive]
87pub struct SRV {
88    /// ```text
89    ///  Priority
90    /// The priority of this target host.  A client MUST attempt to
91    /// contact the target host with the lowest-numbered priority it can
92    /// reach; target hosts with the same priority SHOULD be tried in an
93    /// order defined by the weight field.  The range is 0-65535.  This
94    /// is a 16 bit unsigned integer in network byte order.
95    /// ```
96    pub priority: u16,
97
98    /// ```text
99    ///  Weight
100    /// A server selection mechanism.  The weight field specifies a
101    /// relative weight for entries with the same priority. Larger
102    /// weights SHOULD be given a proportionately higher probability of
103    /// being selected. The range of this number is 0-65535.  This is a
104    /// 16 bit unsigned integer in network byte order.  Domain
105    /// administrators SHOULD use Weight 0 when there isn't any server
106    /// selection to do, to make the RR easier to read for humans (less
107    /// noisy).  In the presence of records containing weights greater
108    /// than 0, records with weight 0 should have a very small chance of
109    /// being selected.
110    ///
111    /// In the absence of a protocol whose specification calls for the
112    /// use of other weighting information, a client arranges the SRV
113    /// RRs of the same Priority in the order in which target hosts,
114    /// specified by the SRV RRs, will be contacted. The following
115    /// algorithm SHOULD be used to order the SRV RRs of the same
116    /// priority:
117    ///
118    /// To select a target to be contacted next, arrange all SRV RRs
119    /// (that have not been ordered yet) in any order, except that all
120    /// those with weight 0 are placed at the beginning of the list.
121    ///
122    /// Compute the sum of the weights of those RRs, and with each RR
123    /// associate the running sum in the selected order. Then choose a
124    /// uniform random number between 0 and the sum computed
125    /// (inclusive), and select the RR whose running sum value is the
126    /// first in the selected order which is greater than or equal to
127    /// the random number selected. The target host specified in the
128    /// selected SRV RR is the next one to be contacted by the client.
129    /// Remove this SRV RR from the set of the unordered SRV RRs and
130    /// apply the described algorithm to the unordered SRV RRs to select
131    /// the next target host.  Continue the ordering process until there
132    /// are no unordered SRV RRs.  This process is repeated for each
133    /// Priority.
134    /// ```
135    pub weight: u16,
136
137    /// ```text
138    ///  Port
139    /// The port on this target host of this service.  The range is 0-
140    /// 65535.  This is a 16 bit unsigned integer in network byte order.
141    /// This is often as specified in Assigned Numbers but need not be.
142    /// ```
143    pub port: u16,
144
145    /// ```text
146    ///  Target
147    /// The domain name of the target host.  There MUST be one or more
148    /// address records for this name, the name MUST NOT be an alias (in
149    /// the sense of RFC 1034 or RFC 2181).  Implementors are urged, but
150    /// not required, to return the address record(s) in the Additional
151    /// Data section.  Unless and until permitted by future standards
152    /// action, name compression is not to be used for this field.
153    ///
154    /// A Target of "." means that the service is decidedly not
155    /// available at this domain.
156    /// ```
157    pub target: Name,
158}
159
160impl SRV {
161    /// Creates a new SRV record data.
162    ///
163    /// # Arguments
164    ///
165    /// * `priority` - lower values have a higher priority and clients will attempt to use these
166    ///   first.
167    /// * `weight` - for servers with the same priority, higher weights will be chosen more often.
168    /// * `port` - the socket port number on which the service is listening.
169    /// * `target` - like CNAME, this is the target domain name to which the service is associated.
170    ///
171    /// # Return value
172    ///
173    /// The newly constructed SRV record data.
174    pub fn new(priority: u16, weight: u16, port: u16, target: Name) -> Self {
175        Self {
176            priority,
177            weight,
178            port,
179            target,
180        }
181    }
182
183    /// Parse the RData from a set of Tokens
184    pub(crate) fn from_tokens<'i, I: Iterator<Item = &'i str>>(
185        mut tokens: I,
186        origin: Option<&Name>,
187    ) -> Result<Self, ParseError> {
188        let priority: u16 = tokens
189            .next()
190            .ok_or_else(|| ParseError::MissingToken("priority".to_string()))
191            .and_then(|s| u16::from_str(s).map_err(Into::into))?;
192
193        let weight: u16 = tokens
194            .next()
195            .ok_or_else(|| ParseError::MissingToken("weight".to_string()))
196            .and_then(|s| u16::from_str(s).map_err(Into::into))?;
197
198        let port: u16 = tokens
199            .next()
200            .ok_or_else(|| ParseError::MissingToken("port".to_string()))
201            .and_then(|s| u16::from_str(s).map_err(Into::into))?;
202
203        let target: Name = tokens
204            .next()
205            .ok_or_else(|| ParseError::MissingToken("target".to_string()))
206            .and_then(|s| Name::parse(s, origin).map_err(ParseError::from))?;
207
208        Ok(Self::new(priority, weight, port, target))
209    }
210}
211
212impl BinEncodable for SRV {
213    /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
214    ///
215    /// This is accurate for all currently known name records.
216    ///
217    /// ```text
218    /// 6.2.  Canonical RR Form
219    ///
220    ///    For the purposes of DNS security, the canonical form of an RR is the
221    ///    wire format of the RR where:
222    ///
223    ///    ...
224    ///
225    ///    3.  if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
226    ///        HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
227    ///        SRV, DNAME, A6, RRSIG, or (rfc6840 removes NSEC), all uppercase
228    ///        US-ASCII letters in the DNS names contained within the RDATA are replaced
229    ///        by the corresponding lowercase US-ASCII letters;
230    /// ```
231    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
232        let mut encoder = encoder.with_rdata_behavior(RDataEncoding::Canonical);
233
234        encoder.emit_u16(self.priority)?;
235        encoder.emit_u16(self.weight)?;
236        encoder.emit_u16(self.port)?;
237        self.target.emit(&mut encoder)?;
238        Ok(())
239    }
240}
241
242impl<'r> BinDecodable<'r> for SRV {
243    fn read(decoder: &mut BinDecoder<'r>) -> Result<Self, DecodeError> {
244        // SRV { priority: u16, weight: u16, port: u16, target: Name, },
245        Ok(Self::new(
246            decoder.read_u16()?.unverified(/*any u16 is valid*/),
247            decoder.read_u16()?.unverified(/*any u16 is valid*/),
248            decoder.read_u16()?.unverified(/*any u16 is valid*/),
249            Name::read(decoder)?,
250        ))
251    }
252}
253
254impl RecordData for SRV {
255    fn try_borrow(data: &RData) -> Option<&Self> {
256        match data {
257            RData::SRV(data) => Some(data),
258            _ => None,
259        }
260    }
261
262    fn record_type(&self) -> RecordType {
263        RecordType::SRV
264    }
265
266    fn into_rdata(self) -> RData {
267        RData::SRV(self)
268    }
269}
270
271/// [RFC 2782, DNS SRV RR, February 2000](https://tools.ietf.org/html/rfc2782)
272///
273/// ```text
274/// The format of the SRV RR
275///
276///   Here is the format of the SRV RR, whose DNS type code is 33:
277///
278///   _Service._Proto.Name TTL Class SRV Priority Weight Port Target
279///
280///   (There is an example near the end of this document.)
281/// ```
282impl fmt::Display for SRV {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
284        write!(
285            f,
286            "{priority} {weight} {port} {target}",
287            priority = self.priority,
288            weight = self.weight,
289            port = self.port,
290            target = self.target,
291        )
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    #![allow(clippy::dbg_macro, clippy::print_stdout)]
298
299    use alloc::vec::Vec;
300    #[cfg(feature = "std")]
301    use std::println;
302
303    use super::*;
304
305    #[test]
306    fn test() {
307        use core::str::FromStr;
308
309        let rdata = SRV::new(1, 2, 3, Name::from_str("_dns._tcp.example.com.").unwrap());
310
311        let mut bytes = Vec::new();
312        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
313        assert!(rdata.emit(&mut encoder).is_ok());
314        let bytes = encoder.into_bytes();
315
316        #[cfg(feature = "std")]
317        println!("bytes: {bytes:?}");
318
319        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
320
321        let read_rdata = SRV::read(&mut decoder).expect("Decoding error");
322        assert_eq!(rdata, read_rdata);
323    }
324}