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}