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