From c4c84bd8873f0df359f94fd935f5c5898b124df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Tue, 26 Dec 2017 22:23:47 +0100 Subject: [PATCH] step --- lib/dnsbox-base/src/common_types/classes.rs | 182 ++++++--- lib/dnsbox-base/src/common_types/time.rs | 8 +- lib/dnsbox-base/src/common_types/types.rs | 401 +++++++++++++++----- lib/dnsbox-base/src/records/tests.rs | 19 +- lib/dnsbox-base/src/records/unknown.rs | 4 + lib/dnsbox-base/src/ser/rrdata.rs | 65 +++- lib/dnsbox-base/src/ser/text/mod.rs | 19 +- lib/dnsbox-derive/src/rrdata.rs | 3 - 8 files changed, 510 insertions(+), 191 deletions(-) diff --git a/lib/dnsbox-base/src/common_types/classes.rs b/lib/dnsbox-base/src/common_types/classes.rs index e77eb46..3daa2f2 100644 --- a/lib/dnsbox-base/src/common_types/classes.rs +++ b/lib/dnsbox-base/src/common_types/classes.rs @@ -1,3 +1,5 @@ +//! Types and constants for DNS CLASSes + use bytes::Bytes; use errors::*; use ser::DnsPacketData; @@ -6,13 +8,15 @@ use std::fmt; use std::io::Cursor; use std::borrow::Cow; -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct Class(pub u16); - +/// CLASS Internet pub const IN : Class = Class(KnownClass::IN as u16); +/// CLASS "Chaos" pub const CH : Class = Class(KnownClass::CH as u16); +/// CLASS "Hesiod" pub const HS : Class = Class(KnownClass::HS as u16); +/// QCLASS NONE pub const NONE : Class = Class(KnownQClass::NONE as u16); +/// QCLASS "*" (ANY) pub const ANY : Class = Class(KnownQClass::ANY as u16); #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -20,9 +24,12 @@ pub const ANY : Class = Class(KnownQClass::ANY as u16); #[allow(non_camel_case_types)] pub enum KnownClass { // try to list "original" rfc + /// CLASS Internet IN = 0x0001, // RFC 1035 - // CS = 0x0002, // "CSNET" + // CS = 0x0002, // "CSNET" (not just obsolete; unassigned in the IANA registry) + /// CLASS "Chaos" CH = 0x0003, // "Chaos" + /// CLASS "Hesiod" HS = 0x0004, // "Hesiod" } @@ -31,83 +38,141 @@ pub enum KnownClass { #[allow(non_camel_case_types)] pub enum KnownQClass { // try to list "original" rfc + /// QCLASS NONE NONE = 0x00fe, // RFC 2136 - ANY = 0x00ff, // RFC 1035: "*" + /// QCLASS "*" (ANY) + ANY = 0x00ff, // RFC 1035 } +/// DNS CLASS +/// +/// Originally QCLASS was a superset of CLASS; RFC 6895 now defines: +/// +/// > There are currently two subcategories of DNS CLASSes: normal, +/// > data-containing classes; and QCLASSes that are only meaningful in +/// > queries or updates. +/// +/// ## `ANY` +/// +/// QTYPE 255 either (rules from RFC 6895): +/// +/// - doesn't have a mnemonic, violating the existence rule +/// - has "*" as mnemonic, violating the formatting rule +/// - has "ANY" as mnemonic, violating the uniquess rule (class ANY) +/// +/// Solution: use "ANY" for type 255, don't accept "ANY" as class in +/// places a type could be given too: `*_without_any`. +/// +/// The QCLASS `ANY` is mostly useless anyway and shouldn't be used in +/// normal queries. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct Class(pub u16); + impl Class { - pub fn name(self) -> Cow<'static, str> { - Cow::Borrowed(match self { + /// map to `KnownQClass` + pub fn known_qclass(self) -> Option { + Some(match self { + NONE => KnownQClass::NONE, + ANY => KnownQClass::ANY, + _ => return None, + }) + } + + /// whether class is known to be a QCLASS + pub fn is_known_qclass(self) -> bool { + self.known_qclass().is_some() + } + + /// name for CLASS if known + /// + /// Known CLASSes are represented using their known mnemonic, others + /// return None. + pub fn known_name(self) -> Option<&'static str> { + Some(match self { IN => "IN", CH => "CH", HS => "HS", NONE => "NONE", ANY => "ANY", - _ => return Cow::Owned(self.generic_name()), + _ => return None, }) } - /// uses generic name for QCLASS values (`is_qclass`) - pub fn class_name(self) -> Cow<'static, str> { - if self.is_qclass() { - Cow::Owned(self.generic_name()) - } else { - self.name() - } - } - - /// QCLASS names can overlap with (Q)TYPE names + /// name for CLASS /// - /// classes unknown to this implementation never count as QCLASS, - /// but they also are only represented using the generic "CLASS..." - /// names, which don't overlap with (Q)TYPE names. - pub fn is_qclass(self) -> bool { - match self { - NONE => true, - ANY => true, - _ => false, + /// Known CLASSes are represented using their known mnemonic, others + /// using the "CLASS..." syntax (RFC 3597). + pub fn name(self) -> Cow<'static, str> { + match self.known_name() { + Some(n) => Cow::Borrowed(n), + None => Cow::Owned(self.generic_name()), } } - pub fn from_name(name: &str) -> Option { - use std::ascii::AsciiExt; - if let Some(n) = Self::class_from_name(name) { return Some(n); } - // explicit QCLASS names - if name.eq_ignore_ascii_case("NONE") { return Some(NONE); } - if name.eq_ignore_ascii_case("ANY") { return Some(ANY); } - None + /// similar to `name`, but returns "CLASS255" for the `ANY` class + pub fn name_without_any(self) -> Cow<'static, str> { + match self { + ANY => Cow::Borrowed("CLASS255"), + _ => self.name(), + } } - /// similar to `from_name`, but doesn't accept QCLASS names (it - /// always accepts "CLASS..." names though, even if they are known to - /// be of type QCLASS) - pub fn class_from_name(name: &str) -> Option { + /// generic name "CLASS..." + pub fn generic_name(self) -> String { + let mut result = String::new(); + self.write_generic_name(&mut result).unwrap(); + result + } + + /// directly write generic name "CLASS..." to some target + pub fn write_generic_name(self, w: &mut W) -> fmt::Result { + write!(w, "CLASS{}", self.0) + } + + /// only parse known names (mnemonics), but not `ANY` + /// + /// Avoids conflict with parsing RRTYPE mnemonics. + pub fn from_known_name_without_any(name: &str) -> Option { use std::ascii::AsciiExt; if name.eq_ignore_ascii_case("IN") { return Some(IN); } if name.eq_ignore_ascii_case("CH") { return Some(CH); } if name.eq_ignore_ascii_case("HS") { return Some(HS); } - if name.as_bytes()[0..5].eq_ignore_ascii_case(b"CLASS") { - if let Ok(c) = name[5..].parse::() { - return Some(Class(c)) - } - } + if name.eq_ignore_ascii_case("NONE") { return Some(NONE); } None } - pub fn write_class_name(self, f: &mut fmt::Formatter) -> fmt::Result { - if self.is_qclass() { - self.write_generic_name(f) + /// parses known names (mnemonics) + pub fn from_known_name(name: &str) -> Option { + use std::ascii::AsciiExt; + Self::from_known_name_without_any(name).or_else(|| { + if name.eq_ignore_ascii_case("ANY") { return Some(ANY); } + None + }) + } + + /// parses generic names of the form "CLASS..." + pub fn from_generic_name(name: &str) -> Option { + use std::ascii::AsciiExt; + if name.as_bytes()[0..5].eq_ignore_ascii_case(b"CLASS") { + name[5..].parse::().ok().map(Class) } else { - write!(f, "{}", self) + None } } - pub fn write_generic_name(self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "CLASS{}", self.0) + /// parses any name (mnemonics and "CLASS..."), but not `ANY` + /// ("CLASS255" works though) + pub fn from_name_without_any(name: &str) -> Option { + Self::from_generic_name(name).or_else(|| { + Self::from_known_name_without_any(name) + }) } - pub fn generic_name(self) -> String { - format!("CLASS{}", self.0) + /// parses any name (mnemonics and "CLASS...") + pub fn from_name(name: &str) -> Option { + Self::from_generic_name(name).or_else(|| { + Self::from_known_name(name) + }) } } @@ -125,17 +190,10 @@ impl From for Class { impl fmt::Display for Class { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let n = match *self { - IN => "IN", - CH => "CH", - HS => "HS", - NONE => "NONE", - ANY => "ANY", - _ => { - return self.write_generic_name(f) - }, - }; - write!(f, "{}", n) + match self.known_name() { + Some(n) => write!(f, "{}", n), + None => self.write_generic_name(f), + } } } @@ -148,7 +206,7 @@ impl DnsPacketData for Class { impl DnsTextData for Class { fn dns_parse(data: &mut &str) -> Result { let field = next_field(data)?; - Class::from_name(field).ok_or_else(|| format_err!("unknown class {:?}", field)) + Class::from_name(field).ok_or_else(|| format_err!("unknown CLASS {:?}", field)) } fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { diff --git a/lib/dnsbox-base/src/common_types/time.rs b/lib/dnsbox-base/src/common_types/time.rs index 99e065f..2ed8962 100644 --- a/lib/dnsbox-base/src/common_types/time.rs +++ b/lib/dnsbox-base/src/common_types/time.rs @@ -5,13 +5,9 @@ use ser::text::{DnsTextData, DnsTextFormatter, next_field}; use std::fmt; use std::io::Cursor; -/// A single quoted non-empty URL. +/// timestamp in seconds since epoch (ignoring leap seconds) /// -/// Actually shouldn't allow escapes (i.e. no backslash in the content); -/// but to make sure we can export and import any data we allow standard -/// escape mechanisms and even accept unquoted input. -/// -/// No whitespace allowed, last field. +/// Is expected to wrap around. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Time(u32); diff --git a/lib/dnsbox-base/src/common_types/types.rs b/lib/dnsbox-base/src/common_types/types.rs index f395568..1c30862 100644 --- a/lib/dnsbox-base/src/common_types/types.rs +++ b/lib/dnsbox-base/src/common_types/types.rs @@ -1,3 +1,5 @@ +//! Types and constants for DNS TYPes + use bytes::Bytes; use errors::*; use records::registry::{lookup_type_to_name, lookup_type_name}; @@ -7,305 +9,504 @@ use std::borrow::Cow; use std::fmt; use std::io::Cursor; -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct Type(pub u16); - +/// a host address pub const A : Type = Type(KnownType::A as u16); +/// an authoritative name server pub const NS : Type = Type(KnownType::NS as u16); +/// a mail destination (OBSOLETE - use MX) pub const MD : Type = Type(KnownType::MD as u16); +/// a mail forwarder (OBSOLETE - use MX) pub const MF : Type = Type(KnownType::MF as u16); +/// the canonical name for an alias pub const CNAME : Type = Type(KnownType::CNAME as u16); +/// marks the start of a zone of authority pub const SOA : Type = Type(KnownType::SOA as u16); +/// a mailbox domain name (EXPERIMENTAL) pub const MB : Type = Type(KnownType::MB as u16); +/// a mail group member (EXPERIMENTAL) pub const MG : Type = Type(KnownType::MG as u16); +/// a mail rename domain name (EXPERIMENTAL) pub const MR : Type = Type(KnownType::MR as u16); +/// a null RR (EXPERIMENTAL) pub const NULL : Type = Type(KnownType::NULL as u16); +/// a well known service description pub const WKS : Type = Type(KnownType::WKS as u16); +/// a domain name pointer pub const PTR : Type = Type(KnownType::PTR as u16); +/// host information pub const HINFO : Type = Type(KnownType::HINFO as u16); +/// mailbox or mail list information pub const MINFO : Type = Type(KnownType::MINFO as u16); +/// mail exchange pub const MX : Type = Type(KnownType::MX as u16); +/// text strings pub const TXT : Type = Type(KnownType::TXT as u16); +/// for Responsible Person pub const RP : Type = Type(KnownType::RP as u16); +/// for AFS Data Base location pub const AFSDB : Type = Type(KnownType::AFSDB as u16); +/// for X.25 PSDN address pub const X25 : Type = Type(KnownType::X25 as u16); +/// for ISDN address pub const ISDN : Type = Type(KnownType::ISDN as u16); +/// for Route Through pub const RT : Type = Type(KnownType::RT as u16); +/// for NSAP address, NSAP style A record pub const NSAP : Type = Type(KnownType::NSAP as u16); +/// for domain name pointer, NSAP style pub const NSAP_PTR : Type = Type(KnownType::NSAP_PTR as u16); +/// for security signature pub const SIG : Type = Type(KnownType::SIG as u16); +/// for security key pub const KEY : Type = Type(KnownType::KEY as u16); +/// X.400 mail mapping information pub const PX : Type = Type(KnownType::PX as u16); +/// Geographical Position pub const GPOS : Type = Type(KnownType::GPOS as u16); +/// IP6 Address pub const AAAA : Type = Type(KnownType::AAAA as u16); +/// Location Information pub const LOC : Type = Type(KnownType::LOC as u16); +/// Next Domain (OBSOLETE) pub const NXT : Type = Type(KnownType::NXT as u16); +/// Endpoint Identifier pub const EID : Type = Type(KnownType::EID as u16); +/// Nimrod Locator pub const NIMLOC : Type = Type(KnownType::NIMLOC as u16); +/// Server Selection pub const SRV : Type = Type(KnownType::SRV as u16); +/// ATM Address pub const ATMA : Type = Type(KnownType::ATMA as u16); +/// Naming Authority Pointer pub const NAPTR : Type = Type(KnownType::NAPTR as u16); +/// Key Exchanger pub const KX : Type = Type(KnownType::KX as u16); +/// CERT pub const CERT : Type = Type(KnownType::CERT as u16); +/// A6 (OBSOLETE - use AAAA) pub const A6 : Type = Type(KnownType::A6 as u16); +/// DNAME pub const DNAME : Type = Type(KnownType::DNAME as u16); +/// SINK pub const SINK : Type = Type(KnownType::SINK as u16); +/// OPT pub const OPT : Type = Type(KnownMetaType::OPT as u16); +/// APL pub const APL : Type = Type(KnownType::APL as u16); +/// Delegation Signer pub const DS : Type = Type(KnownType::DS as u16); +/// SSH Key Fingerprint pub const SSHFP : Type = Type(KnownType::SSHFP as u16); +/// IPSECKEY pub const IPSECKEY : Type = Type(KnownType::IPSECKEY as u16); +/// RRSIG pub const RRSIG : Type = Type(KnownType::RRSIG as u16); +/// NSEC pub const NSEC : Type = Type(KnownType::NSEC as u16); +/// DNSKEY pub const DNSKEY : Type = Type(KnownType::DNSKEY as u16); +/// DHCID pub const DHCID : Type = Type(KnownType::DHCID as u16); +/// NSEC3 pub const NSEC3 : Type = Type(KnownType::NSEC3 as u16); +/// NSEC3PARAM pub const NSEC3PARAM : Type = Type(KnownType::NSEC3PARAM as u16); +/// TLSA pub const TLSA : Type = Type(KnownType::TLSA as u16); +/// S/MIME cert association pub const SMIMEA : Type = Type(KnownType::SMIMEA as u16); +/// Host Identity Protocol pub const HIP : Type = Type(KnownType::HIP as u16); +/// NINFO pub const NINFO : Type = Type(KnownType::NINFO as u16); +/// RKEY pub const RKEY : Type = Type(KnownType::RKEY as u16); +/// Trust Anchor LINK pub const TALINK : Type = Type(KnownType::TALINK as u16); +/// Child DS pub const CDS : Type = Type(KnownType::CDS as u16); +/// DNSKEY(s) the Child wants reflected in DS pub const CDNSKEY : Type = Type(KnownType::CDNSKEY as u16); +/// OpenPGP Key pub const OPENPGPKEY : Type = Type(KnownType::OPENPGPKEY as u16); +/// Child-To-Parent Synchronization pub const CSYNC : Type = Type(KnownType::CSYNC as u16); +/// SPF pub const SPF : Type = Type(KnownType::SPF as u16); +/// UINFO pub const UINFO : Type = Type(KnownType::UINFO as u16); +/// UID pub const UID : Type = Type(KnownType::UID as u16); +/// GID pub const GID : Type = Type(KnownType::GID as u16); +/// UNSPEC pub const UNSPEC : Type = Type(KnownType::UNSPEC as u16); +/// NID pub const NID : Type = Type(KnownType::NID as u16); +/// L32 pub const L32 : Type = Type(KnownType::L32 as u16); +/// L64 pub const L64 : Type = Type(KnownType::L64 as u16); +/// LP pub const LP : Type = Type(KnownType::LP as u16); +/// an EUI-48 address pub const EUI48 : Type = Type(KnownType::EUI48 as u16); +/// an EUI-64 address pub const EUI64 : Type = Type(KnownType::EUI64 as u16); +/// Transaction Key pub const TKEY : Type = Type(KnownMetaType::TKEY as u16); +/// Transaction Signature pub const TSIG : Type = Type(KnownMetaType::TSIG as u16); +/// incremental transfer pub const IXFR : Type = Type(KnownQType::IXFR as u16); +/// transfer of an entire zone pub const AXFR : Type = Type(KnownQType::AXFR as u16); +/// mailbox-related RRs (MB, MG or MR) pub const MAILB : Type = Type(KnownQType::MAILB as u16); +/// mail agent RRs (OBSOLETE - see MX) pub const MAILA : Type = Type(KnownQType::MAILA as u16); +/// "*", a request for all records the server/cache has available pub const ANY : Type = Type(KnownQType::ANY as u16); +/// URI pub const URI : Type = Type(KnownType::URI as u16); +/// Certification Authority Restriction pub const CAA : Type = Type(KnownType::CAA as u16); +/// Application Visibility and Control pub const AVC : Type = Type(KnownType::AVC as u16); +/// Digital Object Architecture pub const DOA : Type = Type(KnownType::DOA as u16); +/// DNSSEC Trust Authorities pub const TA : Type = Type(KnownType::TA as u16); +/// DNSSEC Lookaside Validation pub const DLV : Type = Type(KnownType::DLV as u16); +/// powerdns feature: authoritate should resolve to A and AAAA pub const ALIAS : Type = Type(KnownType::ALIAS as u16); +/// known data TYPEs #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(u16)] #[allow(non_camel_case_types)] pub enum KnownType { // try to list "original" rfc + /// a host address A = 0x0001, // RFC 1035 + /// an authoritative name server NS = 0x0002, // RFC 1035 + /// a mail destination (OBSOLETE - use MX) MD = 0x0003, // RFC 1035 + /// a mail forwarder (OBSOLETE - use MX) MF = 0x0004, // RFC 1035 + /// the canonical name for an alias CNAME = 0x0005, // RFC 1035 + /// marks the start of a zone of authority SOA = 0x0006, // RFC 1035 + /// a mailbox domain name (EXPERIMENTAL) MB = 0x0007, // RFC 1035 + /// a mail group member (EXPERIMENTAL) MG = 0x0008, // RFC 1035 + /// a mail rename domain name (EXPERIMENTAL) MR = 0x0009, // RFC 1035 + /// a null RR (EXPERIMENTAL) NULL = 0x000a, // RFC 1035 + /// a well known service description WKS = 0x000b, // RFC 1035 + /// a domain name pointer PTR = 0x000c, // RFC 1035 + /// host information HINFO = 0x000d, // RFC 1035 + /// mailbox or mail list information MINFO = 0x000e, // RFC 1035 + /// mail exchange MX = 0x000f, // RFC 1035 + /// text strings TXT = 0x0010, // RFC 1035 + /// for Responsible Person RP = 0x0011, // RFC 1183 + /// for AFS Data Base location AFSDB = 0x0012, // RFC 1183 + /// for X.25 PSDN address X25 = 0x0013, // RFC 1183 + /// for ISDN address ISDN = 0x0014, // RFC 1183 + /// for Route Through RT = 0x0015, // RFC 1183 + /// for NSAP address, NSAP style A record NSAP = 0x0016, // RFC 1706 + /// for domain name pointer, NSAP style NSAP_PTR = 0x0017, // RFC 1348 + /// for security signature SIG = 0x0018, // RFC 2535 + /// for security key KEY = 0x0019, // RFC 2535 + /// X.400 mail mapping information PX = 0x001a, // RFC 2163 + /// Geographical Position GPOS = 0x001b, // RFC 1712 + /// IP6 Address AAAA = 0x001c, // RFC 3596 + /// Location Information LOC = 0x001d, // RFC 1876 + /// Next Domain (OBSOLETE) NXT = 0x001e, // RFC 2535 + /// Endpoint Identifier EID = 0x001f, // Michael Patton: http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt + /// Nimrod Locator NIMLOC = 0x0020, // Michael Patton: http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt + /// Server Selection SRV = 0x0021, // RFC 2782 + /// ATM Address ATMA = 0x0022, // http://www.broadband-forum.org/ftp/pub/approved-specs/af-dans-0152.000.pdf + /// Naming Authority Pointer NAPTR = 0x0023, // RFC 2168 + /// Key Exchanger KX = 0x0024, // RFC 2230 + /// CERT CERT = 0x0025, // RFC 4398 + /// A6 (OBSOLETE - use AAAA) A6 = 0x0026, // RFC 2874 + /// DNAME DNAME = 0x0027, // RFC 6672 + /// SINK SINK = 0x0028, // Donald E Eastlake: http://tools.ietf.org/html/draft-eastlake-kitchen-sink // OPT (0x0029) is a meta type + /// APL APL = 0x002a, // RFC 3123 + /// Delegation Signer DS = 0x002b, // RFC 3658 + /// SSH Key Fingerprint SSHFP = 0x002c, // RFC 4255 + /// IPSECKEY IPSECKEY = 0x002d, // RFC 4025 + /// RRSIG RRSIG = 0x002e, // RFC 4034 + /// NSEC NSEC = 0x002f, // RFC 4034 + /// DNSKEY DNSKEY = 0x0030, // RFC 4034 + /// DHCID DHCID = 0x0031, // RFC 4701 + /// NSEC3 NSEC3 = 0x0032, // RFC 5155 + /// NSEC3PARAM NSEC3PARAM = 0x0033, // RFC 5155 + /// TLSA TLSA = 0x0034, // RFC 6698 + /// S/MIME cert association SMIMEA = 0x0035, // RFC 8162 + /// Host Identity Protocol HIP = 0x0037, // RFC 8005 + /// NINFO NINFO = 0x0038, // Jim Reid + /// RKEY RKEY = 0x0039, // Jim Reid + /// Trust Anchor LINK TALINK = 0x003a, // Wouter Wijngaards + /// Child DS CDS = 0x003b, // RFC 7344 + /// DNSKEY(s) the Child wants reflected in DS CDNSKEY = 0x003c, // RFC 7344 + /// OpenPGP Key OPENPGPKEY = 0x003d, // RFC 7929 + /// Child-To-Parent Synchronization CSYNC = 0x003e, // RFC 7477 + /// SPF SPF = 0x0063, // RFC 7208 + /// UINFO UINFO = 0x0064, // IANA-Reserved + /// UID UID = 0x0065, // IANA-Reserved + /// GID GID = 0x0066, // IANA-Reserved + /// UNSPEC UNSPEC = 0x0067, // IANA-Reserved + /// NID NID = 0x0068, // RFC 6742 + /// L32 L32 = 0x0069, // RFC 6742 + /// L64 L64 = 0x006a, // RFC 6742 + /// LP LP = 0x006b, // RFC 6742 + /// an EUI-48 address EUI48 = 0x006c, // RFC 7043 + /// an EUI-64 address EUI64 = 0x006d, // RFC 7043 // 0x0080..0x00ff: meta and qtypes + /// URI URI = 0x0100, // RFC 7553 + /// Certification Authority Restriction CAA = 0x0101, // RFC 6844 + /// Application Visibility and Control AVC = 0x0102, // Wolfgang Riedel + /// Digital Object Architecture DOA = 0x0103, // http://www.iana.org/go/draft-durand-doa-over-dns + /// DNSSEC Trust Authorities TA = 0x8000, // + /// DNSSEC Lookaside Validation DLV = 0x8001, // RFC 4431 + /// powerdns feature: authoritate should resolve to A and AAAA ALIAS = 0xff79, // powerdns } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -#[repr(u16)] -#[allow(non_camel_case_types)] -pub enum KnownMetaType { - OPT = 0x0029, // RFC 6891 - TKEY = 0x00f9, // RFC 2930 - TSIG = 0x00fa, // RFC 2845 -} - +/// known QTYPEs #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(u16)] #[allow(non_camel_case_types)] pub enum KnownQType { + /// incremental transfer IXFR = 0x00fb, // RFC 1995 + /// transfer of an entire zone AXFR = 0x00fc, // RFC 1035 + /// mailbox-related RRs (MB, MG or MR) MAILB = 0x00fd, // RFC 1035 + /// mail agent RRs (OBSOLETE - see MX) MAILA = 0x00fe, // RFC 1035 - ANY = 0x00ff, // RFC 1035, "*" + /// "*", a request for all records the server/cache has available + ANY = 0x00ff, // RFC 1035 } -impl Type { - pub fn name(self) -> Cow<'static, str> { - if let Some(name) = lookup_type_to_name(self) { - Cow::Borrowed(name) - } else { - Cow::Owned(self.generic_name()) - } - } +/// known Meta-TYPEs +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[repr(u16)] +#[allow(non_camel_case_types)] +pub enum KnownMetaType { + /// OPT + OPT = 0x0029, // RFC 6891 + /// Transaction Key + TKEY = 0x00f9, // RFC 2930 + /// Transaction Signature + TSIG = 0x00fa, // RFC 2845 +} - // 0x0080-0x00FF is reserved for QTYPEs and Meta TYPEs. - // OPT is a special meta type +/// DNS (RR)TYPE +/// +/// Originally QTYPE was a superset of TYPE; RFC 6895 now defines: +/// +/// > There are three subcategories of RRTYPE numbers: data TYPEs, +/// > QTYPEs, and Meta-TYPEs. +/// +/// ## `ANY` +/// +/// QTYPE 255 ("*") doesn't seem to have an official mnemonic; `ANY` is +/// used in most tools though. +/// +/// The `ANY` mnemonic conflicts with the QCLASS `ANY` though; the name +/// functions for `Class` have `_without_any` variants though. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct Type(pub u16); + +impl Type { + /// whether TYPE is known or reserved to be a QTYPE or a Meta-TYPE. + /// + /// 0x0080-0x00FF is reserved for QTYPEs and Meta-TYPEs; OPT is a + /// special Meta-TYPE. pub fn is_q_or_meta_type(self) -> bool { self == OPT || (self.0 >= 128 && self.0 < 256) } - // checks for known qtypes + /// map to `KnownQType` + pub fn known_qtype(self) -> Option { + Some(match self { + IXFR => KnownQType::IXFR, + AXFR => KnownQType::AXFR, + MAILB => KnownQType::MAILB, + MAILA => KnownQType::MAILA, + ANY => KnownQType::ANY, + _ => return None, + }) + } + + /// whether TYPE is known to be a QTYPE pub fn is_known_qtype(self) -> bool { - match self { - IXFR => true, - AXFR => true, - MAILB => true, - MAILA => true, - ANY => true, - _ => false, - } + self.known_qtype().is_some() } - // checks for known meta types + /// map to `KnownMetaType` + pub fn known_meta_type(self) -> Option { + Some(match self { + OPT => KnownMetaType::OPT, + TKEY => KnownMetaType::TKEY, + TSIG => KnownMetaType::TSIG, + _ => return None, + }) + } + + /// whether TYPE is known to be a Meta-TYPE pub fn is_known_meta_type(self) -> bool { - match self { - OPT => true, - TKEY => true, - TSIG => true, - _ => false, - } - } - - /// uses generic name for QTYPEs/Meta TYPEs (`is_q_or_meta_type`) - pub fn type_name(self) -> Cow<'static, str> { - if self.is_q_or_meta_type() { - Cow::Owned(self.generic_name()) - } else { - self.name() + self.known_meta_type().is_some() + } + + /// name for TYPE if known + /// + /// Known TYPEs are represented using their known mnemonic, others + /// return None. + pub fn known_name(self) -> Option<&'static str> { + lookup_type_to_name(self) + } + + /// name for TYPE + /// + /// Known TYPEes are represented using their known mnemonic, others + /// using the "TYPE..." syntax (RFC 3597). + pub fn name(self) -> Cow<'static, str> { + match self.known_name() { + Some(name) => Cow::Borrowed(name), + None => Cow::Owned(self.generic_name()) } } + /// generic name "TYPE..." pub fn generic_name(self) -> String { - format!("TYPE{}", self.0) + let mut result = String::new(); + self.write_generic_name(&mut result).unwrap(); + result } - pub fn from_name(name: &str) -> Option { + /// directly write generic name "TYPE..." to some target + pub fn write_generic_name(self, w: &mut W) -> fmt::Result { + write!(w, "TYPE{}", self.0) + } + + /// parses known names (mnemonics) + pub fn from_known_name(name: &str) -> Option { + lookup_type_name(name) + } + + /// parses generic names of the form "TYPE..." + pub fn from_generic_name(name: &str) -> Option { use std::ascii::AsciiExt; - if let Some(t) = lookup_type_name(name) { return Some(t); } if name.as_bytes()[0..4].eq_ignore_ascii_case(b"TYPE") { - if let Ok(t) = name[4..].parse::() { - return Some(Type(t)); - } - } - None - } - - /// similar to `from_name`, but doesn't accept QTYPE/Meta TYPE names (it - /// always accepts "TYPE..." names though, even if they are known to - /// be QTYPE/Meta TYPE) - pub fn type_from_name(name: &str) -> Option { - use std::ascii::AsciiExt; - if let Some(t) = lookup_type_name(name) { - if t.is_q_or_meta_type() { return None; } - return Some(t); - } - if name.as_bytes()[0..4].eq_ignore_ascii_case(b"TYPE") { - if let Ok(t) = name[4..].parse::() { - return Some(Type(t)); - } - } - None - } - - pub fn write_type_name(self, f: &mut fmt::Formatter) -> fmt::Result { - if self.is_q_or_meta_type() { - self.write_generic_name(f) + name[4..].parse::().ok().map(Type) } else { - write!(f, "{}", self) + None } } - pub fn write_generic_name(self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TYPE{}", self.0) + /// parses any name (mnemonics and "TYPE...") + pub fn from_name(name: &str) -> Option { + Self::from_generic_name(name).or_else(|| { + Self::from_known_name(name) + }) } - /// defined in RFC 1035 - pub fn well_known(self) -> bool { + /// whether TYPE is defined in RFC 1035 + pub fn is_well_known(self) -> bool { // 0x0001 (A) ... 0x0010 (TXT) are defined in RFC 1035 self.0 >= 0x0001 && self.0 <= 0x0010 } - /// require converting to canonical form for DNSSEC (i.e. names - /// must be converted to (ASCII) lower case, no compression) + /// require converting to canonical form for DNSSEC (i.e. names must + /// be converted to (ASCII) lower case). /// - /// See https://tools.ietf.org/html/rfc4034#section-6.2 (updates RFC 3597). - /// Also updated by https://tools.ietf.org/html/rfc6840#section-5.1 - pub fn canonical(self) -> bool { + /// For DNSSEC name compression is forbidden too; all compressable + /// TYPEs are included in the list anyway. + /// + /// See [RFC 4034 section 6.2][1] (obsoletes RFC 3597). Also + /// updated by [RFC 6840 section 5.1][2]. + /// + /// [1]: https://tools.ietf.org/html/rfc4034#section-6.2 + /// [2]: https://tools.ietf.org/html/rfc6840#section-5.1 + pub fn use_canonical_names(self) -> bool { match self { NS => true, MD => true, @@ -347,24 +548,23 @@ impl From for Type { } } -impl From for Type { - fn from(value: KnownMetaType) -> Self { - Type(value as u16) - } -} - impl From for Type { fn from(value: KnownQType) -> Self { Type(value as u16) } } +impl From for Type { + fn from(value: KnownMetaType) -> Self { + Type(value as u16) + } +} + impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(name) = lookup_type_to_name(*self) { - write!(f, "{}", name) - } else { - self.write_generic_name(f) + match self.known_name() { + Some(name) => write!(f, "{}", name), + None => self.write_generic_name(f), } } } @@ -378,7 +578,7 @@ impl DnsPacketData for Type { impl DnsTextData for Type { fn dns_parse(data: &mut &str) -> Result { let field = next_field(data)?; - Type::from_name(field).ok_or_else(|| format_err!("unknown type {:?}", field)) + Type::from_name(field).ok_or_else(|| format_err!("unknown TYPE {:?}", field)) } fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { @@ -386,7 +586,6 @@ impl DnsTextData for Type { } } - #[cfg(test)] macro_rules! check_type { ($t:ident, $dec:expr) => { @@ -409,6 +608,10 @@ macro_rules! check_type { }; } +// the IANA registry uses decimal numbers; make sure there were no typos +// when converting them to hex in the constants above. +// +// also checks the registry has the name-to-number mapping. #[cfg(test)] #[test] fn check_types() { diff --git a/lib/dnsbox-base/src/records/tests.rs b/lib/dnsbox-base/src/records/tests.rs index e4ff9ac..34e126f 100644 --- a/lib/dnsbox-base/src/records/tests.rs +++ b/lib/dnsbox-base/src/records/tests.rs @@ -16,12 +16,23 @@ where Ok(result) } +fn rrdata_parse(data: &str) -> ::errors::Result +where + T: StaticRRData +{ + let mut data = data; + let result = T::dns_parse_rr_data(3600, classes::IN, T::TYPE, &mut data)?; + let data = data.trim(); + ensure!(data.is_empty(), "didn't parse complete rrdata text, remaining: {:?}", data); + Ok(result) +} + fn check(txt: &str, data: &'static [u8]) -> ::errors::Result<()> where T: StaticRRData + fmt::Debug + PartialEq { let d1: T = rrdata_de(data).context("couldn't parse binary record")?; - let d2: T = text::parse(txt).context("couldn't parse text record")?; + let d2: T = rrdata_parse(txt).context("couldn't parse text record")?; ensure!(d1 == d2, "decoded data not equal: {:?} != {:?}", d1, d2); Ok(()) } @@ -31,9 +42,9 @@ where T: StaticRRData + fmt::Debug + PartialEq { let d1: T = rrdata_de(data).context("couldn't parse binary record")?; - let d2: T = text::parse(txt).context("couldn't parse text record")?; + let d2: T = rrdata_parse(txt).context("couldn't parse text record")?; ensure!(d1 == d2, "decoded data not equal: {:?} != {:?}", d1, d2); - + Ok(()) } @@ -61,7 +72,7 @@ where s.push('"'); for _ in 0..256 { s.push('a'); } s.push('"'); - text::parse::(&s).unwrap_err(); + rrdata_parse::(&s).unwrap_err(); } } diff --git a/lib/dnsbox-base/src/records/unknown.rs b/lib/dnsbox-base/src/records/unknown.rs index d05d29d..f8d294c 100644 --- a/lib/dnsbox-base/src/records/unknown.rs +++ b/lib/dnsbox-base/src/records/unknown.rs @@ -52,4 +52,8 @@ impl RRDataPacket for UnknownRecord { fn deserialize_rr_data(_ttl: u32, _rr_class: Class, rr_type: Type, data: &mut Cursor) -> Result { UnknownRecord::deserialize(rr_type, data) } + + fn rr_type(&self) -> Type { + self.rr_type + } } diff --git a/lib/dnsbox-base/src/ser/rrdata.rs b/lib/dnsbox-base/src/ser/rrdata.rs index b40d257..80aed9b 100644 --- a/lib/dnsbox-base/src/ser/rrdata.rs +++ b/lib/dnsbox-base/src/ser/rrdata.rs @@ -1,9 +1,9 @@ use bytes::Bytes; -use common_types::{Class, Type}; -use common_types::classes; +use common_types::{Class, Type, classes}; use errors::*; use ser::DnsPacketData; use ser::text::{DnsTextData, DnsTextFormatter}; +use std::borrow::Cow; use std::fmt; use std::io::Cursor; @@ -12,19 +12,25 @@ pub trait RRDataPacket { where Self: Sized, ; + + fn rr_type(&self) -> Type; } impl RRDataPacket for T { fn deserialize_rr_data(_ttl: u32, rr_class: Class, rr_type: Type, data: &mut Cursor) -> Result { ensure!(rr_type == T::TYPE, "type mismatch"); if T::CLASS != classes::ANY { - ensure!(rr_class == T::CLASS, "class mismatch"); + ensure!(rr_class == T::CLASS, "class mismatch: got {}, need {}", rr_class, T::CLASS); } T::deserialize(data) } + + fn rr_type(&self) -> Type { + T::TYPE + } } -pub trait RRDataText: Sized { +pub trait RRDataText { fn dns_parse_rr_data(ttl: u32, rr_class: Class, rr_type: Type, data: &mut &str) -> Result where Self: Sized, @@ -32,18 +38,53 @@ pub trait RRDataText: Sized { // format might fail if there is no (known) text representation. fn dns_format_rr_data(&self, f: &mut DnsTextFormatter) -> fmt::Result; -} -/* -impl RRDataText for T { - fn deserialize_rr_data(_ttl: u32, _rr_class: Class, _rr_type: Type, data: &mut Cursor) -> Result { - T::deserialize(data) + fn rr_type_txt(&self) -> Cow<'static, str> { + unimplemented!() + } + + // (type, rrdata) + fn text(&self) -> Result<(String, String)> { + use std::fmt::Write; + let mut buf = String::new(); + match write!(&mut buf, "{}", DnsDisplayRR(self)) { + Ok(()) => { + return Ok((self.rr_type_txt().into(), buf)) + }, + Err(_) => (), + } + buf.clear(); + unimplemented!() } } -*/ -pub trait RRData: RRDataPacket + DnsTextData { - fn rr_type(&self) -> Type; +impl RRDataText for T { + fn dns_parse_rr_data(_ttl: u32, rr_class: Class, rr_type: Type, data: &mut &str) -> Result + where + Self: Sized, + { + ensure!(rr_type == T::TYPE, "type mismatch"); + if T::CLASS != classes::ANY { + ensure!(rr_class == T::CLASS, "class mismatch: got {}, need {}", rr_class, T::CLASS); + } + T::dns_parse(data) + } + + fn dns_format_rr_data(&self, f: &mut DnsTextFormatter) -> fmt::Result { + self.dns_format(f) + } +} + +#[derive(Debug)] +pub struct DnsDisplayRR<'a, T: RRDataText + ?Sized + 'a>(pub &'a T); + +impl<'a, T: RRDataText + ?Sized + 'a> fmt::Display for DnsDisplayRR<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.dns_format_rr_data(&mut DnsTextFormatter::new(f)) + } +} + +pub trait RRData: RRDataPacket + RRDataText { } pub trait StaticRRData: RRData { diff --git a/lib/dnsbox-base/src/ser/text/mod.rs b/lib/dnsbox-base/src/ser/text/mod.rs index 963f0d6..692c6f5 100644 --- a/lib/dnsbox-base/src/ser/text/mod.rs +++ b/lib/dnsbox-base/src/ser/text/mod.rs @@ -53,13 +53,13 @@ pub fn escape(data: &[u8]) -> String { /// Each call to write!() makes sure a space is emitted to separate it /// from previous fields. -pub struct DnsTextFormatter<'a> { - f: &'a mut fmt::Formatter<'a>, +pub struct DnsTextFormatter<'a, 'b: 'a> { + f: &'a mut fmt::Formatter<'b>, need_space: bool, } -impl<'a> DnsTextFormatter<'a> { - pub fn new(f: &'a mut fmt::Formatter<'a>) -> Self { +impl<'a, 'b> DnsTextFormatter<'a, 'b> { + pub fn new(f: &'a mut fmt::Formatter<'b>) -> Self { DnsTextFormatter { f: f, need_space: false, @@ -82,7 +82,7 @@ impl<'a> DnsTextFormatter<'a> { /// direct access to underlying output; you'll need to call /// `next_field` and `end_field` manually. - pub fn inner(&mut self) -> &mut fmt::Formatter<'a> { + pub fn inner(&mut self) -> &mut fmt::Formatter<'b> { self.f } @@ -113,3 +113,12 @@ where ensure!(data.is_empty(), "didn't parse complete text, remaining: {:?}", data); Ok(result) } + +#[derive(Debug)] +pub struct DnsDisplay<'a, T: DnsTextData + ?Sized + 'a>(pub &'a T); + +impl<'a, T: DnsTextData + ?Sized + 'a> fmt::Display for DnsDisplay<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.dns_format(&mut DnsTextFormatter::new(f)) + } +} diff --git a/lib/dnsbox-derive/src/rrdata.rs b/lib/dnsbox-derive/src/rrdata.rs index f378ba2..f1ac317 100644 --- a/lib/dnsbox-derive/src/rrdata.rs +++ b/lib/dnsbox-derive/src/rrdata.rs @@ -73,9 +73,6 @@ pub fn build(ast: &syn::DeriveInput) -> quote::Tokens { quote!{ impl ::dnsbox_base::ser::RRData for #name { - fn rr_type(&self) -> ::dnsbox_base::common_types::Type { - ::dnsbox_base::common_types::types::#name - } } impl ::dnsbox_base::ser::StaticRRData for #name {