hickory_proto/rr/rdata/naptr.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//! Dynamic Delegation Discovery System
9
10use alloc::{boxed::Box, string::String};
11use core::fmt;
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::{
17 error::{ProtoError, ProtoResult},
18 rr::{RData, RecordData, RecordType, domain::Name},
19 serialize::binary::*,
20};
21
22/// [RFC 3403 DDDS DNS Database, October 2002](https://tools.ietf.org/html/rfc3403#section-4)
23///
24/// ```text
25/// 4.1 Packet Format
26///
27/// The packet format of the NAPTR RR is given below. The DNS type code
28/// for NAPTR is 35.
29///
30/// The packet format for the NAPTR record is as follows
31/// 1 1 1 1 1 1
32/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
33/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
34/// | ORDER |
35/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
36/// | PREFERENCE |
37/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
38/// / FLAGS /
39/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
40/// / SERVICES /
41/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
42/// / REGEXP /
43/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
44/// / REPLACEMENT /
45/// / /
46/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
47///
48/// <character-string> and <domain-name> as used here are defined in RFC
49/// 1035 [7].
50/// ```
51#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
52#[derive(Debug, PartialEq, Eq, Hash, Clone)]
53pub struct NAPTR {
54 order: u16,
55 preference: u16,
56 flags: Box<[u8]>,
57 services: Box<[u8]>,
58 regexp: Box<[u8]>,
59 replacement: Name,
60}
61
62impl NAPTR {
63 /// Constructs a new NAPTR record
64 ///
65 /// # Arguments
66 ///
67 /// * `order` - the order in which the NAPTR records MUST be processed in order to accurately represent the ordered list of Rules.
68 /// * `preference` - this field is equivalent to the Priority value in the DDDS Algorithm.
69 /// * `flags` - flags to control aspects of the rewriting and interpretation of the fields in the record. Flags are single characters from the set A-Z and 0-9.
70 /// * `services` - the Service Parameters applicable to this this delegation path.
71 /// * `regexp` - substitution expression that is applied to the original string held by the client in order to construct the next domain name to lookup.
72 /// * `replacement` - the next domain-name to query for depending on the potential values found in the flags field.
73 pub fn new(
74 order: u16,
75 preference: u16,
76 flags: Box<[u8]>,
77 services: Box<[u8]>,
78 regexp: Box<[u8]>,
79 replacement: Name,
80 ) -> Self {
81 Self {
82 order,
83 preference,
84 flags,
85 services,
86 regexp,
87 replacement,
88 }
89 }
90
91 /// ```text
92 /// ORDER
93 /// A 16-bit unsigned integer specifying the order in which the NAPTR
94 /// records MUST be processed in order to accurately represent the
95 /// ordered list of Rules. The ordering is from lowest to highest.
96 /// If two records have the same order value then they are considered
97 /// to be the same rule and should be selected based on the
98 /// combination of the Preference values and Services offered.
99 /// ```
100 pub fn order(&self) -> u16 {
101 self.order
102 }
103
104 /// ```text
105 /// PREFERENCE
106 /// Although it is called "preference" in deference to DNS
107 /// terminology, this field is equivalent to the Priority value in the
108 /// DDDS Algorithm. It is a 16-bit unsigned integer that specifies
109 /// the order in which NAPTR records with equal Order values SHOULD be
110 /// processed, low numbers being processed before high numbers. This
111 /// is similar to the preference field in an MX record, and is used so
112 /// domain administrators can direct clients towards more capable
113 /// hosts or lighter weight protocols. A client MAY look at records
114 /// with higher preference values if it has a good reason to do so
115 /// such as not supporting some protocol or service very well.
116 ///
117 /// The important difference between Order and Preference is that once
118 /// a match is found the client MUST NOT consider records with a
119 /// different Order but they MAY process records with the same Order
120 /// but different Preferences. The only exception to this is noted in
121 /// the second important Note in the DDDS algorithm specification
122 /// concerning allowing clients to use more complex Service
123 /// determination between steps 3 and 4 in the algorithm. Preference
124 /// is used to give communicate a higher quality of service to rules
125 /// that are considered the same from an authority standpoint but not
126 /// from a simple load balancing standpoint.
127 ///
128 /// It is important to note that DNS contains several load balancing
129 /// mechanisms and if load balancing among otherwise equal services
130 /// should be needed then methods such as SRV records or multiple A
131 /// records should be utilized to accomplish load balancing.
132 /// ```
133 pub fn preference(&self) -> u16 {
134 self.preference
135 }
136
137 /// ```text
138 /// FLAGS
139 /// A <character-string> containing flags to control aspects of the
140 /// rewriting and interpretation of the fields in the record. Flags
141 /// are single characters from the set A-Z and 0-9. The case of the
142 /// alphabetic characters is not significant. The field can be empty.
143 ///
144 /// It is up to the Application specifying how it is using this
145 /// Database to define the Flags in this field. It must define which
146 /// ones are terminal and which ones are not.
147 /// ```
148 pub fn flags(&self) -> &[u8] {
149 &self.flags
150 }
151
152 /// ```text
153 /// SERVICES
154 /// A <character-string> that specifies the Service Parameters
155 /// applicable to this this delegation path. It is up to the
156 /// Application Specification to specify the values found in this
157 /// field.
158 /// ```
159 pub fn services(&self) -> &[u8] {
160 &self.services
161 }
162
163 /// ```text
164 /// REGEXP
165 /// A <character-string> containing a substitution expression that is
166 /// applied to the original string held by the client in order to
167 /// construct the next domain name to lookup. See the DDDS Algorithm
168 /// specification for the syntax of this field.
169 ///
170 /// As stated in the DDDS algorithm, The regular expressions MUST NOT
171 /// be used in a cumulative fashion, that is, they should only be
172 /// applied to the original string held by the client, never to the
173 /// domain name produced by a previous NAPTR rewrite. The latter is
174 /// tempting in some applications but experience has shown such use to
175 /// be extremely fault sensitive, very error prone, and extremely
176 /// difficult to debug.
177 /// ```
178 pub fn regexp(&self) -> &[u8] {
179 &self.regexp
180 }
181
182 /// ```text
183 /// REPLACEMENT
184 /// A <domain-name> which is the next domain-name to query for
185 /// depending on the potential values found in the flags field. This
186 /// field is used when the regular expression is a simple replacement
187 /// operation. Any value in this field MUST be a fully qualified
188 /// domain-name. Name compression is not to be used for this field.
189 ///
190 /// This field and the REGEXP field together make up the Substitution
191 /// Expression in the DDDS Algorithm. It is simply a historical
192 /// optimization specifically for DNS compression that this field
193 /// exists. The fields are also mutually exclusive. If a record is
194 /// returned that has values for both fields then it is considered to
195 /// be in error and SHOULD be either ignored or an error returned.
196 /// ```
197 pub fn replacement(&self) -> &Name {
198 &self.replacement
199 }
200}
201
202/// verifies that the flags are valid
203pub fn verify_flags(flags: &[u8]) -> bool {
204 flags
205 .iter()
206 .all(|c| matches!(c, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z'))
207}
208
209impl BinEncodable for NAPTR {
210 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
211 self.order.emit(encoder)?;
212 self.preference.emit(encoder)?;
213 encoder.emit_character_data(&self.flags)?;
214 encoder.emit_character_data(&self.services)?;
215 encoder.emit_character_data(&self.regexp)?;
216
217 encoder.with_canonical_names(|encoder| self.replacement.emit(encoder))?;
218 Ok(())
219 }
220}
221
222impl<'r> BinDecodable<'r> for NAPTR {
223 fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult<Self> {
224 Ok(Self::new(
225 decoder.read_u16()?.unverified(/*any u16 is valid*/),
226 decoder.read_u16()?.unverified(/*any u16 is valid*/),
227 // must be 0-9a-z
228 decoder
229 .read_character_data()?
230 .verify_unwrap(|s| verify_flags(s))
231 .map_err(|_e| ProtoError::from("flags are not within range [a-zA-Z0-9]"))?
232 .to_vec()
233 .into_boxed_slice(),
234 decoder.read_character_data()?.unverified(/*any chardata*/).to_vec().into_boxed_slice(),
235 decoder.read_character_data()?.unverified(/*any chardata*/).to_vec().into_boxed_slice(),
236 Name::read(decoder)?,
237 ))
238 }
239}
240
241impl RecordData for NAPTR {
242 fn try_from_rdata(data: RData) -> Result<Self, RData> {
243 match data {
244 RData::NAPTR(csync) => Ok(csync),
245 _ => Err(data),
246 }
247 }
248
249 fn try_borrow(data: &RData) -> Option<&Self> {
250 match data {
251 RData::NAPTR(csync) => Some(csync),
252 _ => None,
253 }
254 }
255
256 fn record_type(&self) -> RecordType {
257 RecordType::NAPTR
258 }
259
260 fn into_rdata(self) -> RData {
261 RData::NAPTR(self)
262 }
263}
264
265/// [RFC 2915](https://tools.ietf.org/html/rfc2915), NAPTR DNS RR, September 2000
266///
267/// ```text
268/// Master File Format
269///
270/// The master file format follows the standard rules in RFC-1035 [1].
271/// Order and preference, being 16-bit unsigned integers, shall be an
272/// integer between 0 and 65535. The Flags and Services and Regexp
273/// fields are all quoted <character-string>s. Since the Regexp field
274/// can contain numerous backslashes and thus should be treated with
275/// care. See Section 10 for how to correctly enter and escape the
276/// regular expression.
277///
278/// ;; order pflags service regexp replacement
279/// IN NAPTR 100 50 "a" "z3950+N2L+N2C" "" cidserver.example.com.
280/// IN NAPTR 100 50 "a" "rcds+N2C" "" cidserver.example.com.
281/// IN NAPTR 100 50 "s" "http+N2L+N2C+N2R" "" www.example.com.
282/// ```
283impl fmt::Display for NAPTR {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
285 write!(
286 f,
287 "{order} {pref} \"{flags}\" \"{service}\" \"{regexp}\" {replace}",
288 order = self.order,
289 pref = self.preference,
290 flags = &String::from_utf8_lossy(&self.flags),
291 service = &String::from_utf8_lossy(&self.services),
292 regexp = &String::from_utf8_lossy(&self.regexp),
293 replace = self.replacement
294 )
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 #![allow(clippy::dbg_macro, clippy::print_stdout)]
301
302 use alloc::vec::Vec;
303 #[cfg(feature = "std")]
304 use std::println;
305
306 use super::*;
307 #[test]
308 fn test() {
309 use core::str::FromStr;
310
311 let rdata = NAPTR::new(
312 8,
313 16,
314 b"aa11AA".to_vec().into_boxed_slice(),
315 b"services".to_vec().into_boxed_slice(),
316 b"regexpr".to_vec().into_boxed_slice(),
317 Name::from_str("naptr.example.com.").unwrap(),
318 );
319
320 let mut bytes = Vec::new();
321 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
322 assert!(rdata.emit(&mut encoder).is_ok());
323 let bytes = encoder.into_bytes();
324
325 #[cfg(feature = "std")]
326 println!("bytes: {bytes:?}");
327
328 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
329 let read_rdata = NAPTR::read(&mut decoder).expect("Decoding error");
330 assert_eq!(rdata, read_rdata);
331 }
332
333 #[test]
334 fn test_bad_data() {
335 use core::str::FromStr;
336
337 let rdata = NAPTR::new(
338 8,
339 16,
340 b"aa11AA-".to_vec().into_boxed_slice(),
341 b"services".to_vec().into_boxed_slice(),
342 b"regexpr".to_vec().into_boxed_slice(),
343 Name::from_str("naptr.example.com").unwrap(),
344 );
345
346 let mut bytes = Vec::new();
347 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
348 assert!(rdata.emit(&mut encoder).is_ok());
349 let bytes = encoder.into_bytes();
350
351 #[cfg(feature = "std")]
352 println!("bytes: {bytes:?}");
353
354 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
355 let read_rdata = NAPTR::read(&mut decoder);
356 assert!(
357 read_rdata.is_err(),
358 "should have failed decoding with bad flag data"
359 );
360 }
361}