| // Copyright 2015-2019 Benjamin Fry <benjaminfry@me.com> |
| // |
| // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or |
| // http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or |
| // http://opensource.org/licenses/MIT>, at your option. This file may not be |
| // copied, modified, or distributed except according to those terms. |
| |
| //! Dynamic Delegation Discovery System |
| |
| use crate::error::*; |
| use crate::rr::domain::Name; |
| use crate::serialize::binary::*; |
| |
| /// [RFC 3403 DDDS DNS Database, October 2002](https://tools.ietf.org/html/rfc3403#section-4) |
| /// |
| /// ```text |
| /// 4.1 Packet Format |
| /// |
| /// The packet format of the NAPTR RR is given below. The DNS type code |
| /// for NAPTR is 35. |
| /// |
| /// The packet format for the NAPTR record is as follows |
| /// 1 1 1 1 1 1 |
| /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 |
| /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| /// | ORDER | |
| /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| /// | PREFERENCE | |
| /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| /// / FLAGS / |
| /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| /// / SERVICES / |
| /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| /// / REGEXP / |
| /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| /// / REPLACEMENT / |
| /// / / |
| /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| /// |
| /// <character-string> and <domain-name> as used here are defined in RFC |
| /// 1035 [7]. |
| /// ``` |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub struct NAPTR { |
| order: u16, |
| preference: u16, |
| flags: Box<[u8]>, |
| services: Box<[u8]>, |
| regexp: Box<[u8]>, |
| replacement: Name, |
| } |
| |
| impl NAPTR { |
| /// Constructs a new NAPTR record |
| /// |
| /// # Arguments |
| /// |
| /// * `order` - the order in which the NAPTR records MUST be processed in order to accurately represent the ordered list of Rules. |
| /// * `preference` - this field is equivalent to the Priority value in the DDDS Algorithm. |
| /// * `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. |
| /// * `services` - the Service Parameters applicable to this this delegation path. |
| /// * `regexp` - substitution expression that is applied to the original string held by the client in order to construct the next domain name to lookup. |
| /// * `replacement` - the next domain-name to query for depending on the potential values found in the flags field. |
| pub fn new( |
| order: u16, |
| preference: u16, |
| flags: Box<[u8]>, |
| services: Box<[u8]>, |
| regexp: Box<[u8]>, |
| replacement: Name, |
| ) -> Self { |
| Self { |
| order, |
| preference, |
| flags, |
| services, |
| regexp, |
| replacement, |
| } |
| } |
| |
| /// ```text |
| /// ORDER |
| /// A 16-bit unsigned integer specifying the order in which the NAPTR |
| /// records MUST be processed in order to accurately represent the |
| /// ordered list of Rules. The ordering is from lowest to highest. |
| /// If two records have the same order value then they are considered |
| /// to be the same rule and should be selected based on the |
| /// combination of the Preference values and Services offered. |
| /// ``` |
| pub fn order(&self) -> u16 { |
| self.order |
| } |
| |
| /// ```text |
| /// PREFERENCE |
| /// Although it is called "preference" in deference to DNS |
| /// terminology, this field is equivalent to the Priority value in the |
| /// DDDS Algorithm. It is a 16-bit unsigned integer that specifies |
| /// the order in which NAPTR records with equal Order values SHOULD be |
| /// processed, low numbers being processed before high numbers. This |
| /// is similar to the preference field in an MX record, and is used so |
| /// domain administrators can direct clients towards more capable |
| /// hosts or lighter weight protocols. A client MAY look at records |
| /// with higher preference values if it has a good reason to do so |
| /// such as not supporting some protocol or service very well. |
| /// |
| /// The important difference between Order and Preference is that once |
| /// a match is found the client MUST NOT consider records with a |
| /// different Order but they MAY process records with the same Order |
| /// but different Preferences. The only exception to this is noted in |
| /// the second important Note in the DDDS algorithm specification |
| /// concerning allowing clients to use more complex Service |
| /// determination between steps 3 and 4 in the algorithm. Preference |
| /// is used to give communicate a higher quality of service to rules |
| /// that are considered the same from an authority standpoint but not |
| /// from a simple load balancing standpoint. |
| /// |
| /// It is important to note that DNS contains several load balancing |
| /// mechanisms and if load balancing among otherwise equal services |
| /// should be needed then methods such as SRV records or multiple A |
| /// records should be utilized to accomplish load balancing. |
| /// ``` |
| pub fn preference(&self) -> u16 { |
| self.preference |
| } |
| |
| /// ```text |
| /// FLAGS |
| /// A <character-string> containing 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. The case of the |
| /// alphabetic characters is not significant. The field can be empty. |
| /// |
| /// It is up to the Application specifying how it is using this |
| /// Database to define the Flags in this field. It must define which |
| /// ones are terminal and which ones are not. |
| /// ``` |
| pub fn flags(&self) -> &[u8] { |
| &self.flags |
| } |
| |
| /// ```text |
| /// SERVICES |
| /// A <character-string> that specifies the Service Parameters |
| /// applicable to this this delegation path. It is up to the |
| /// Application Specification to specify the values found in this |
| /// field. |
| /// ``` |
| pub fn services(&self) -> &[u8] { |
| &self.services |
| } |
| |
| /// ```text |
| /// REGEXP |
| /// A <character-string> containing a substitution expression that is |
| /// applied to the original string held by the client in order to |
| /// construct the next domain name to lookup. See the DDDS Algorithm |
| /// specification for the syntax of this field. |
| /// |
| /// As stated in the DDDS algorithm, The regular expressions MUST NOT |
| /// be used in a cumulative fashion, that is, they should only be |
| /// applied to the original string held by the client, never to the |
| /// domain name p roduced by a previous NAPTR rewrite. The latter is |
| /// tempting in some applications but experience has shown such use to |
| /// be extremely fault sensitive, very error prone, and extremely |
| /// difficult to debug. |
| /// ``` |
| pub fn regexp(&self) -> &[u8] { |
| &self.regexp |
| } |
| |
| /// ```text |
| /// REPLACEMENT |
| /// A <domain-name> which is the next domain-name to query for |
| /// depending on the potential values found in the flags field. This |
| /// field is used when the regular expression is a simple replacement |
| /// operation. Any value in this field MUST be a fully qualified |
| /// domain-name. Name compression is not to be used for this field. |
| /// |
| /// This field and the REGEXP field together make up the Substitution |
| /// Expression in the DDDS Algorithm. It is simply a historical |
| /// optimization specifically for DNS compression that this field |
| /// exists. The fields are also mutually exclusive. If a record is |
| /// returned that has values for both fields then it is considered to |
| /// be in error and SHOULD be either ignored or an error returned. |
| /// ``` |
| pub fn replacement(&self) -> &Name { |
| &self.replacement |
| } |
| } |
| |
| /// verifies that the flags are valid |
| pub fn verify_flags(flags: &[u8]) -> bool { |
| flags.iter().all(|c| match c { |
| b'0'..=b'9' => true, |
| b'a'..=b'z' => true, |
| b'A'..=b'Z' => true, |
| _ => false, |
| }) |
| } |
| |
| /// Read the RData from the given Decoder |
| pub fn read(decoder: &mut BinDecoder) -> ProtoResult<NAPTR> { |
| Ok(NAPTR::new( |
| decoder.read_u16()?.unverified(/*any u16 is valid*/), |
| decoder.read_u16()?.unverified(/*any u16 is valid*/), |
| // must be 0-9a-z |
| decoder |
| .read_character_data()? |
| .verify_unwrap(|s| verify_flags(s)) |
| .map_err(|_e| ProtoError::from("flags are not within range [a-zA-Z0-9]"))? |
| .to_vec() |
| .into_boxed_slice(), |
| decoder.read_character_data()?.unverified(/*any chardata*/).to_vec().into_boxed_slice(), |
| decoder.read_character_data()?.unverified(/*any chardata*/).to_vec().into_boxed_slice(), |
| Name::read(decoder)?, |
| )) |
| } |
| |
| /// Declares the method for emitting this type |
| pub fn emit(encoder: &mut BinEncoder, naptr: &NAPTR) -> ProtoResult<()> { |
| naptr.order.emit(encoder)?; |
| naptr.preference.emit(encoder)?; |
| encoder.emit_character_data(&naptr.flags)?; |
| encoder.emit_character_data(&naptr.services)?; |
| encoder.emit_character_data(&naptr.regexp)?; |
| |
| encoder.with_canonical_names(|encoder| naptr.replacement.emit(encoder))?; |
| Ok(()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| #![allow(clippy::dbg_macro, clippy::print_stdout)] |
| |
| use super::*; |
| #[test] |
| pub fn test() { |
| use std::str::FromStr; |
| |
| let rdata = NAPTR::new( |
| 8, |
| 16, |
| b"aa11AA".to_vec().into_boxed_slice(), |
| b"services".to_vec().into_boxed_slice(), |
| b"regexpr".to_vec().into_boxed_slice(), |
| Name::from_str("naptr.example.com").unwrap(), |
| ); |
| |
| let mut bytes = Vec::new(); |
| let mut encoder: BinEncoder = BinEncoder::new(&mut bytes); |
| assert!(emit(&mut encoder, &rdata).is_ok()); |
| let bytes = encoder.into_bytes(); |
| |
| println!("bytes: {:?}", bytes); |
| |
| let mut decoder: BinDecoder = BinDecoder::new(bytes); |
| let read_rdata = read(&mut decoder).expect("Decoding error"); |
| assert_eq!(rdata, read_rdata); |
| } |
| |
| #[test] |
| pub fn test_bad_data() { |
| use std::str::FromStr; |
| |
| let rdata = NAPTR::new( |
| 8, |
| 16, |
| b"aa11AA-".to_vec().into_boxed_slice(), |
| b"services".to_vec().into_boxed_slice(), |
| b"regexpr".to_vec().into_boxed_slice(), |
| Name::from_str("naptr.example.com").unwrap(), |
| ); |
| |
| let mut bytes = Vec::new(); |
| let mut encoder: BinEncoder = BinEncoder::new(&mut bytes); |
| assert!(emit(&mut encoder, &rdata).is_ok()); |
| let bytes = encoder.into_bytes(); |
| |
| println!("bytes: {:?}", bytes); |
| |
| let mut decoder: BinDecoder = BinDecoder::new(bytes); |
| let read_rdata = read(&mut decoder); |
| assert!( |
| read_rdata.is_err(), |
| "should have failed decoding with bad flag data" |
| ); |
| } |
| } |