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}