summaryrefslogtreecommitdiffstats
path: root/third_party/rust/dns-parser/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /third_party/rust/dns-parser/src
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/dns-parser/src')
-rw-r--r--third_party/rust/dns-parser/src/builder.rs132
-rw-r--r--third_party/rust/dns-parser/src/enums.rs299
-rw-r--r--third_party/rust/dns-parser/src/error.rs71
-rw-r--r--third_party/rust/dns-parser/src/header.rs203
-rw-r--r--third_party/rust/dns-parser/src/lib.rs39
-rw-r--r--third_party/rust/dns-parser/src/name.rs183
-rw-r--r--third_party/rust/dns-parser/src/parser.rs455
-rw-r--r--third_party/rust/dns-parser/src/rdata/a.rs21
-rw-r--r--third_party/rust/dns-parser/src/rdata/aaaa.rs85
-rw-r--r--third_party/rust/dns-parser/src/rdata/all.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/axfr.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/cname.rs102
-rw-r--r--third_party/rust/dns-parser/src/rdata/hinfo.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/maila.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/mailb.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/mb.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/mf.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/mg.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/minfo.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/mod.rs83
-rw-r--r--third_party/rust/dns-parser/src/rdata/mr.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/mx.rs92
-rw-r--r--third_party/rust/dns-parser/src/rdata/ns.rs88
-rw-r--r--third_party/rust/dns-parser/src/rdata/nsec.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/null.rs11
-rw-r--r--third_party/rust/dns-parser/src/rdata/opt.rs18
-rw-r--r--third_party/rust/dns-parser/src/rdata/ptr.rs74
-rw-r--r--third_party/rust/dns-parser/src/rdata/soa.rs101
-rw-r--r--third_party/rust/dns-parser/src/rdata/srv.rs102
-rw-r--r--third_party/rust/dns-parser/src/rdata/txt.rs125
-rw-r--r--third_party/rust/dns-parser/src/rdata/wks.rs11
-rw-r--r--third_party/rust/dns-parser/src/structs.rs50
32 files changed, 2466 insertions, 0 deletions
diff --git a/third_party/rust/dns-parser/src/builder.rs b/third_party/rust/dns-parser/src/builder.rs
new file mode 100644
index 0000000000..22aff0b962
--- /dev/null
+++ b/third_party/rust/dns-parser/src/builder.rs
@@ -0,0 +1,132 @@
+use byteorder::{ByteOrder, BigEndian, WriteBytesExt};
+
+use {Opcode, ResponseCode, Header, QueryType, QueryClass};
+
+/// Allows to build a DNS packet
+///
+/// Both query and answer packets may be built with this interface, although,
+/// much of functionality is not implemented yet.
+#[derive(Debug)]
+pub struct Builder {
+ buf: Vec<u8>,
+}
+
+impl Builder {
+ /// Creates a new query
+ ///
+ /// Initially all sections are empty. You're expected to fill
+ /// the questions section with `add_question`
+ pub fn new_query(id: u16, recursion: bool) -> Builder {
+ let mut buf = Vec::with_capacity(512);
+ let head = Header {
+ id: id,
+ query: true,
+ opcode: Opcode::StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: recursion,
+ recursion_available: false,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: ResponseCode::NoError,
+ questions: 0,
+ answers: 0,
+ nameservers: 0,
+ additional: 0,
+ };
+ buf.extend([0u8; 12].iter());
+ head.write(&mut buf[..12]);
+ Builder { buf: buf }
+ }
+ /// Adds a question to the packet
+ ///
+ /// # Panics
+ ///
+ /// * Answers, nameservers or additional section has already been written
+ /// * There are already 65535 questions in the buffer.
+ /// * When name is invalid
+ pub fn add_question(&mut self, qname: &str, prefer_unicast: bool,
+ qtype: QueryType, qclass: QueryClass)
+ -> &mut Builder
+ {
+ if &self.buf[6..12] != b"\x00\x00\x00\x00\x00\x00" {
+ panic!("Too late to add a question");
+ }
+ self.write_name(qname);
+ self.buf.write_u16::<BigEndian>(qtype as u16).unwrap();
+ let prefer_unicast: u16 = if prefer_unicast { 0x8000 } else { 0x0000 };
+ self.buf.write_u16::<BigEndian>(qclass as u16 | prefer_unicast).unwrap();
+ let oldq = BigEndian::read_u16(&self.buf[4..6]);
+ if oldq == 65535 {
+ panic!("Too many questions");
+ }
+ BigEndian::write_u16(&mut self.buf[4..6], oldq+1);
+ self
+ }
+ fn write_name(&mut self, name: &str) {
+ for part in name.split('.') {
+ assert!(part.len() < 63);
+ let ln = part.len() as u8;
+ self.buf.push(ln);
+ self.buf.extend(part.as_bytes());
+ }
+ self.buf.push(0);
+ }
+ /// Returns the final packet
+ ///
+ /// When packet is not truncated method returns `Ok(packet)`. If
+ /// packet is truncated the method returns `Err(packet)`. In both
+ /// cases the packet is fully valid.
+ ///
+ /// In the server implementation you may use
+ /// `x.build().unwrap_or_else(|x| x)`.
+ ///
+ /// In the client implementation it's probably unwise to send truncated
+ /// packet, as it doesn't make sense. Even panicking may be more
+ /// appropriate.
+ // TODO(tailhook) does the truncation make sense for TCP, and how
+ // to treat it for EDNS0?
+ pub fn build(mut self) -> Result<Vec<u8>,Vec<u8>> {
+ // TODO(tailhook) optimize labels
+ if self.buf.len() > 512 {
+ Header::set_truncated(&mut self.buf[..12]);
+ Err(self.buf)
+ } else {
+ Ok(self.buf)
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use QueryType as QT;
+ use QueryClass as QC;
+ use super::Builder;
+
+ #[test]
+ fn build_query() {
+ let mut bld = Builder::new_query(1573, true);
+ bld.add_question("example.com", false, QT::A, QC::IN);
+ let result = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x00\x01";
+ assert_eq!(&bld.build().unwrap()[..], &result[..]);
+ }
+
+ #[test]
+ fn build_unicast_query() {
+ let mut bld = Builder::new_query(1573, true);
+ bld.add_question("example.com", true, QT::A, QC::IN);
+ let result = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x80\x01";
+ assert_eq!(&bld.build().unwrap()[..], &result[..]);
+ }
+
+ #[test]
+ fn build_srv_query() {
+ let mut bld = Builder::new_query(23513, true);
+ bld.add_question("_xmpp-server._tcp.gmail.com", false, QT::SRV, QC::IN);
+ let result = b"[\xd9\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01";
+ assert_eq!(&bld.build().unwrap()[..], &result[..]);
+ }
+}
diff --git a/third_party/rust/dns-parser/src/enums.rs b/third_party/rust/dns-parser/src/enums.rs
new file mode 100644
index 0000000000..9f58ddfec1
--- /dev/null
+++ b/third_party/rust/dns-parser/src/enums.rs
@@ -0,0 +1,299 @@
+use {Error};
+use rdata::Record;
+use rdata::*;
+
+/// The TYPE value according to RFC 1035
+///
+/// All "EXPERIMENTAL" markers here are from the RFC
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Type {
+ /// a host addresss
+ A = a::Record::TYPE,
+ /// an authoritative name server
+ NS = ns::Record::TYPE,
+ /// a mail forwarder (Obsolete - use MX)
+ MF = mf::Record::TYPE,
+ /// the canonical name for an alias
+ CNAME = cname::Record::TYPE,
+ /// marks the start of a zone of authority
+ SOA = soa::Record::TYPE,
+ /// a mailbox domain name (EXPERIMENTAL)
+ MB = mb::Record::TYPE,
+ /// a mail group member (EXPERIMENTAL)
+ MG = mg::Record::TYPE,
+ /// a mail rename domain name (EXPERIMENTAL)
+ MR = mr::Record::TYPE,
+ /// a null RR (EXPERIMENTAL)
+ NULL = null::Record::TYPE,
+ /// a well known service description
+ WKS = wks::Record::TYPE,
+ /// a domain name pointer
+ PTR = ptr::Record::TYPE,
+ /// host information
+ HINFO = hinfo::Record::TYPE,
+ /// mailbox or mail list information
+ MINFO = minfo::Record::TYPE,
+ /// mail exchange
+ MX = mx::Record::TYPE,
+ /// text strings
+ TXT = txt::Record::TYPE,
+ /// IPv6 host address (RFC 2782)
+ AAAA = aaaa::Record::TYPE,
+ /// service record (RFC 2782)
+ SRV = srv::Record::TYPE,
+ /// EDNS0 options (RFC 6891)
+ OPT = opt::Record::TYPE,
+ /// next secure record (RFC 4034, RFC 6762)
+ NSEC = nsec::Record::TYPE,
+}
+
+/// The QTYPE value according to RFC 1035
+///
+/// All "EXPERIMENTAL" markers here are from the RFC
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
+pub enum QueryType {
+ /// a host addresss
+ A = a::Record::TYPE,
+ /// an authoritative name server
+ NS = ns::Record::TYPE,
+ /// a mail forwarder (Obsolete - use MX)
+ MF = mf::Record::TYPE,
+ /// the canonical name for an alias
+ CNAME = cname::Record::TYPE,
+ /// marks the start of a zone of authority
+ SOA = soa::Record::TYPE,
+ /// a mailbox domain name (EXPERIMENTAL)
+ MB = mb::Record::TYPE,
+ /// a mail group member (EXPERIMENTAL)
+ MG = mg::Record::TYPE,
+ /// a mail rename domain name (EXPERIMENTAL)
+ MR = mr::Record::TYPE,
+ /// a null RR (EXPERIMENTAL)
+ NULL = null::Record::TYPE,
+ /// a well known service description
+ WKS = wks::Record::TYPE,
+ /// a domain name pointer
+ PTR = ptr::Record::TYPE,
+ /// host information
+ HINFO = hinfo::Record::TYPE,
+ /// mailbox or mail list information
+ MINFO = minfo::Record::TYPE,
+ /// mail exchange
+ MX = mx::Record::TYPE,
+ /// text strings
+ TXT = txt::Record::TYPE,
+ /// IPv6 host address (RFC 2782)
+ AAAA = aaaa::Record::TYPE,
+ /// service record (RFC 2782)
+ SRV = srv::Record::TYPE,
+ /// A request for a transfer of an entire zone
+ AXFR = axfr::Record::TYPE,
+ /// A request for mailbox-related records (MB, MG or MR)
+ MAILB = mailb::Record::TYPE,
+ /// A request for mail agent RRs (Obsolete - see MX)
+ MAILA = maila::Record::TYPE,
+ /// A request for all records
+ All = all::Record::TYPE,
+}
+
+
+/// The CLASS value according to RFC 1035
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Class {
+ /// the Internet
+ IN = 1,
+ /// the CSNET class (Obsolete - used only for examples in some obsolete
+ /// RFCs)
+ CS = 2,
+ /// the CHAOS class
+ CH = 3,
+ /// Hesiod [Dyer 87]
+ HS = 4,
+}
+
+/// The QCLASS value according to RFC 1035
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum QueryClass {
+ /// the Internet
+ IN = 1,
+ /// the CSNET class (Obsolete - used only for examples in some obsolete
+ /// RFCs)
+ CS = 2,
+ /// the CHAOS class
+ CH = 3,
+ /// Hesiod [Dyer 87]
+ HS = 4,
+ /// Any class
+ Any = 255,
+}
+
+/// The OPCODE value according to RFC 1035
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Opcode {
+ /// Normal query
+ StandardQuery,
+ /// Inverse query (query a name by IP)
+ InverseQuery,
+ /// Server status request
+ ServerStatusRequest,
+ /// Reserved opcode for future use
+ Reserved(u16),
+}
+
+quick_error! {
+ /// The RCODE value according to RFC 1035
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
+ #[allow(missing_docs)] // names are from spec
+ pub enum ResponseCode {
+ NoError
+ FormatError
+ ServerFailure
+ NameError
+ NotImplemented
+ Refused
+ Reserved(code: u8)
+ }
+}
+
+impl From<u16> for Opcode {
+ fn from(code: u16) -> Opcode {
+ use self::Opcode::*;
+ match code {
+ 0 => StandardQuery,
+ 1 => InverseQuery,
+ 2 => ServerStatusRequest,
+ x => Reserved(x),
+ }
+ }
+}
+impl Into<u16> for Opcode {
+ fn into(self) -> u16 {
+ use self::Opcode::*;
+ match self {
+ StandardQuery => 0,
+ InverseQuery => 1,
+ ServerStatusRequest => 2,
+ Reserved(x) => x,
+ }
+ }
+}
+
+impl From<u8> for ResponseCode {
+ fn from(code: u8) -> ResponseCode {
+ use self::ResponseCode::*;
+ match code {
+ 0 => NoError,
+ 1 => FormatError,
+ 2 => ServerFailure,
+ 3 => NameError,
+ 4 => NotImplemented,
+ 5 => Refused,
+ 6...15 => Reserved(code),
+ x => panic!("Invalid response code {}", x),
+ }
+ }
+}
+impl Into<u8> for ResponseCode {
+ fn into(self) -> u8 {
+ use self::ResponseCode::*;
+ match self {
+ NoError => 0,
+ FormatError => 1,
+ ServerFailure => 2,
+ NameError => 3,
+ NotImplemented => 4,
+ Refused => 5,
+ Reserved(code) => code,
+ }
+ }
+}
+
+impl QueryType {
+ /// Parse a query type code
+ pub fn parse(code: u16) -> Result<QueryType, Error> {
+ use self::QueryType::*;
+ match code as isize {
+ a::Record::TYPE => Ok(A),
+ ns::Record::TYPE => Ok(NS),
+ mf::Record::TYPE => Ok(MF),
+ cname::Record::TYPE => Ok(CNAME),
+ soa::Record::TYPE => Ok(SOA),
+ mb::Record::TYPE => Ok(MB),
+ mg::Record::TYPE => Ok(MG),
+ mr::Record::TYPE => Ok(MR),
+ null::Record::TYPE => Ok(NULL),
+ wks::Record::TYPE => Ok(WKS),
+ ptr::Record::TYPE => Ok(PTR),
+ hinfo::Record::TYPE => Ok(HINFO),
+ minfo::Record::TYPE => Ok(MINFO),
+ mx::Record::TYPE => Ok(MX),
+ txt::Record::TYPE => Ok(TXT),
+ aaaa::Record::TYPE => Ok(AAAA),
+ srv::Record::TYPE => Ok(SRV),
+ axfr::Record::TYPE => Ok(AXFR),
+ mailb::Record::TYPE => Ok(MAILB),
+ maila::Record::TYPE => Ok(MAILA),
+ all::Record::TYPE => Ok(All),
+ x => Err(Error::InvalidQueryType(x as u16)),
+ }
+ }
+}
+
+impl QueryClass {
+ /// Parse a query class code
+ pub fn parse(code: u16) -> Result<QueryClass, Error> {
+ use self::QueryClass::*;
+ match code {
+ 1 => Ok(IN),
+ 2 => Ok(CS),
+ 3 => Ok(CH),
+ 4 => Ok(HS),
+ 255 => Ok(Any),
+ x => Err(Error::InvalidQueryClass(x)),
+ }
+ }
+}
+
+impl Type {
+ /// Parse a type code
+ pub fn parse(code: u16) -> Result<Type, Error> {
+ use self::Type::*;
+ match code as isize {
+ a::Record::TYPE => Ok(A),
+ ns::Record::TYPE => Ok(NS),
+ mf::Record::TYPE => Ok(MF),
+ cname::Record::TYPE => Ok(CNAME),
+ soa::Record::TYPE => Ok(SOA),
+ mb::Record::TYPE => Ok(MB),
+ mg::Record::TYPE => Ok(MG),
+ mr::Record::TYPE => Ok(MR),
+ null::Record::TYPE => Ok(NULL),
+ wks::Record::TYPE => Ok(WKS),
+ ptr::Record::TYPE => Ok(PTR),
+ hinfo::Record::TYPE => Ok(HINFO),
+ minfo::Record::TYPE => Ok(MINFO),
+ mx::Record::TYPE => Ok(MX),
+ txt::Record::TYPE => Ok(TXT),
+ aaaa::Record::TYPE => Ok(AAAA),
+ srv::Record::TYPE => Ok(SRV),
+ opt::Record::TYPE => Ok(OPT),
+ nsec::Record::TYPE => Ok(NSEC),
+ x => Err(Error::InvalidType(x as u16)),
+ }
+ }
+}
+
+impl Class {
+ /// Parse a class code
+ pub fn parse(code: u16) -> Result<Class, Error> {
+ use self::Class::*;
+ match code {
+ 1 => Ok(IN),
+ 2 => Ok(CS),
+ 3 => Ok(CH),
+ 4 => Ok(HS),
+ x => Err(Error::InvalidClass(x)),
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/error.rs b/third_party/rust/dns-parser/src/error.rs
new file mode 100644
index 0000000000..ae6a39747d
--- /dev/null
+++ b/third_party/rust/dns-parser/src/error.rs
@@ -0,0 +1,71 @@
+use std::str::Utf8Error;
+
+quick_error! {
+ /// Error parsing DNS packet
+ #[derive(Debug)]
+ pub enum Error {
+ /// Invalid compression pointer not pointing backwards
+ /// when parsing label
+ BadPointer {
+ description("invalid compression pointer not pointing backwards \
+ when parsing label")
+ }
+ /// Packet is smaller than header size
+ HeaderTooShort {
+ description("packet is smaller than header size")
+ }
+ /// Packet ihas incomplete data
+ UnexpectedEOF {
+ description("packet is has incomplete data")
+ }
+ /// Wrong (too short or too long) size of RDATA
+ WrongRdataLength {
+ description("wrong (too short or too long) size of RDATA")
+ }
+ /// Packet has non-zero reserved bits
+ ReservedBitsAreNonZero {
+ description("packet has non-zero reserved bits")
+ }
+ /// Label in domain name has unknown label format
+ UnknownLabelFormat {
+ description("label in domain name has unknown label format")
+ }
+ /// Query type code is invalid
+ InvalidQueryType(code: u16) {
+ description("query type code is invalid")
+ display("query type {} is invalid", code)
+ }
+ /// Query class code is invalid
+ InvalidQueryClass(code: u16) {
+ description("query class code is invalid")
+ display("query class {} is invalid", code)
+ }
+ /// Type code is invalid
+ InvalidType(code: u16) {
+ description("type code is invalid")
+ display("type {} is invalid", code)
+ }
+ /// Class code is invalid
+ InvalidClass(code: u16) {
+ description("class code is invalid")
+ display("class {} is invalid", code)
+ }
+ /// Invalid characters encountered while reading label
+ LabelIsNotAscii {
+ description("invalid characters encountered while reading label")
+ }
+ /// Invalid characters encountered while reading TXT
+ TxtDataIsNotUTF8(error: Utf8Error) {
+ description("invalid characters encountered while reading TXT")
+ display("{:?}", error)
+ }
+ /// Parser is in the wrong state
+ WrongState {
+ description("parser is in the wrong state")
+ }
+ /// Additional OPT record found
+ AdditionalOPT {
+ description("additional OPT record found")
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/header.rs b/third_party/rust/dns-parser/src/header.rs
new file mode 100644
index 0000000000..01d18add99
--- /dev/null
+++ b/third_party/rust/dns-parser/src/header.rs
@@ -0,0 +1,203 @@
+use byteorder::{BigEndian, ByteOrder};
+
+use {Error, ResponseCode, Opcode};
+
+mod flag {
+ pub const QUERY: u16 = 0b1000_0000_0000_0000;
+ pub const OPCODE_MASK: u16 = 0b0111_1000_0000_0000;
+ pub const AUTHORITATIVE: u16 = 0b0000_0100_0000_0000;
+ pub const TRUNCATED: u16 = 0b0000_0010_0000_0000;
+ pub const RECURSION_DESIRED: u16 = 0b0000_0001_0000_0000;
+ pub const RECURSION_AVAILABLE: u16 = 0b0000_0000_1000_0000;
+ pub const AUTHENTICATED_DATA: u16 = 0b0000_0000_0010_0000;
+ pub const CHECKING_DISABLED: u16 = 0b0000_0000_0001_0000;
+ pub const RESERVED_MASK: u16 = 0b0000_0000_0100_0000;
+ pub const RESPONSE_CODE_MASK: u16 = 0b0000_0000_0000_1111;
+}
+
+/// Represents parsed header of the packet
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[allow(missing_docs)] // fields are from the spec I think
+pub struct Header {
+ pub id: u16,
+ pub query: bool,
+ pub opcode: Opcode,
+ pub authoritative: bool,
+ pub truncated: bool,
+ pub recursion_desired: bool,
+ pub recursion_available: bool,
+ pub authenticated_data: bool,
+ pub checking_disabled: bool,
+ pub response_code: ResponseCode,
+ pub questions: u16,
+ pub answers: u16,
+ pub nameservers: u16,
+ pub additional: u16,
+}
+
+impl Header {
+ /// Parse the header into a header structure
+ pub fn parse(data: &[u8]) -> Result<Header, Error> {
+ if data.len() < 12 {
+ return Err(Error::HeaderTooShort);
+ }
+ let flags = BigEndian::read_u16(&data[2..4]);
+ if flags & flag::RESERVED_MASK != 0 {
+ return Err(Error::ReservedBitsAreNonZero);
+ }
+ let header = Header {
+ id: BigEndian::read_u16(&data[..2]),
+ query: flags & flag::QUERY == 0,
+ opcode: ((flags & flag::OPCODE_MASK)
+ >> flag::OPCODE_MASK.trailing_zeros()).into(),
+ authoritative: flags & flag::AUTHORITATIVE != 0,
+ truncated: flags & flag::TRUNCATED != 0,
+ recursion_desired: flags & flag::RECURSION_DESIRED != 0,
+ recursion_available: flags & flag::RECURSION_AVAILABLE != 0,
+ authenticated_data: flags & flag::AUTHENTICATED_DATA != 0,
+ checking_disabled: flags & flag::CHECKING_DISABLED != 0,
+ response_code: From::from((flags&flag::RESPONSE_CODE_MASK) as u8),
+ questions: BigEndian::read_u16(&data[4..6]),
+ answers: BigEndian::read_u16(&data[6..8]),
+ nameservers: BigEndian::read_u16(&data[8..10]),
+ additional: BigEndian::read_u16(&data[10..12]),
+ };
+ Ok(header)
+ }
+ /// Write a header to a buffer slice
+ ///
+ /// # Panics
+ ///
+ /// When buffer size is not exactly 12 bytes
+ pub fn write(&self, data: &mut [u8]) {
+ if data.len() != 12 {
+ panic!("Header size is exactly 12 bytes");
+ }
+ let mut flags = 0u16;
+ flags |= Into::<u16>::into(self.opcode)
+ << flag::OPCODE_MASK.trailing_zeros();
+ flags |= Into::<u8>::into(self.response_code) as u16;
+ if !self.query { flags |= flag::QUERY; }
+ if self.authoritative { flags |= flag::AUTHORITATIVE; }
+ if self.recursion_desired { flags |= flag::RECURSION_DESIRED; }
+ if self.recursion_available { flags |= flag::RECURSION_AVAILABLE; }
+ if self.truncated { flags |= flag::TRUNCATED; }
+ BigEndian::write_u16(&mut data[..2], self.id);
+ BigEndian::write_u16(&mut data[2..4], flags);
+ BigEndian::write_u16(&mut data[4..6], self.questions);
+ BigEndian::write_u16(&mut data[6..8], self.answers);
+ BigEndian::write_u16(&mut data[8..10], self.nameservers);
+ BigEndian::write_u16(&mut data[10..12], self.additional);
+ }
+ /// Set "truncated flag" in the raw data
+ // shouldn't this method be non-public?
+ pub fn set_truncated(data: &mut [u8]) {
+ let oldflags = BigEndian::read_u16(&data[2..4]);
+ BigEndian::write_u16(&mut data[2..4], oldflags & flag::TRUNCATED);
+ }
+ /// Returns a size of the header (always 12 bytes)
+ pub fn size() -> usize { 12 }
+}
+
+
+#[cfg(test)]
+mod test {
+
+ use {Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+
+ #[test]
+ fn parse_example_query() {
+ let query = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x00\x01";
+ let header = Header::parse(query).unwrap();
+ assert_eq!(header, Header {
+ id: 1573,
+ query: true,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: false,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 0,
+ nameservers: 0,
+ additional: 0,
+ });
+ }
+
+ #[test]
+ fn parse_example_response() {
+ let response = b"\x06%\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x00\x01\
+ \xc0\x0c\x00\x01\x00\x01\x00\x00\x04\xf8\
+ \x00\x04]\xb8\xd8\"";
+ let header = Header::parse(response).unwrap();
+ assert_eq!(header, Header {
+ id: 1573,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 1,
+ nameservers: 0,
+ additional: 0,
+ });
+ }
+
+ #[test]
+ fn parse_query_with_ad_set() {
+ let query = b"\x06%\x01\x20\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x00\x01";
+ let header = Header::parse(query).unwrap();
+ assert_eq!(header, Header {
+ id: 1573,
+ query: true,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: false,
+ authenticated_data: true,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 0,
+ nameservers: 0,
+ additional: 0,
+ });
+ }
+
+ #[test]
+ fn parse_query_with_cd_set() {
+ let query = b"\x06%\x01\x10\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x00\x01";
+ let header = Header::parse(query).unwrap();
+ assert_eq!(header, Header {
+ id: 1573,
+ query: true,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: false,
+ authenticated_data: false,
+ checking_disabled: true,
+ response_code: NoError,
+ questions: 1,
+ answers: 0,
+ nameservers: 0,
+ additional: 0,
+ });
+ }
+}
diff --git a/third_party/rust/dns-parser/src/lib.rs b/third_party/rust/dns-parser/src/lib.rs
new file mode 100644
index 0000000000..e89c005244
--- /dev/null
+++ b/third_party/rust/dns-parser/src/lib.rs
@@ -0,0 +1,39 @@
+#![recursion_limit="100"]
+//! The network-agnostic DNS parser library
+//!
+//! [Documentation](https://docs.rs/dns-parser) |
+//! [Github](https://github.com/tailhook/dns-parser) |
+//! [Crate](https://crates.io/crates/dns-parser)
+//!
+//! Use [`Builder`] to create a new outgoing packet.
+//!
+//! Use [`Packet::parse`] to parse a packet into a data structure.
+//!
+//! [`Builder`]: struct.Builder.html
+//! [`Packet::parse`]: struct.Packet.html#method.parse
+//!
+#![warn(missing_docs)]
+#![warn(missing_debug_implementations)]
+
+extern crate byteorder;
+#[cfg(test)] #[macro_use] extern crate matches;
+#[macro_use(quick_error)] extern crate quick_error;
+#[cfg(feature = "with-serde")] #[macro_use] extern crate serde_derive;
+
+mod enums;
+mod structs;
+mod name;
+mod parser;
+mod error;
+mod header;
+mod builder;
+
+pub mod rdata;
+
+pub use enums::{Type, QueryType, Class, QueryClass, ResponseCode, Opcode};
+pub use structs::{Question, ResourceRecord, Packet};
+pub use name::{Name};
+pub use error::{Error};
+pub use header::{Header};
+pub use rdata::{RData};
+pub use builder::{Builder};
diff --git a/third_party/rust/dns-parser/src/name.rs b/third_party/rust/dns-parser/src/name.rs
new file mode 100644
index 0000000000..8763259cbe
--- /dev/null
+++ b/third_party/rust/dns-parser/src/name.rs
@@ -0,0 +1,183 @@
+use std::fmt;
+use std::fmt::Write;
+use std::str::from_utf8;
+
+// Deprecated since rustc 1.23
+#[allow(unused_imports, deprecated)]
+use std::ascii::AsciiExt;
+
+use byteorder::{BigEndian, ByteOrder};
+
+use {Error};
+
+/// The DNS name as stored in the original packet
+///
+/// This contains just a reference to a slice that contains the data.
+/// You may turn this into a string using `.to_string()`
+#[derive(Clone, Copy)]
+pub struct Name<'a>{
+ labels: &'a [u8],
+ /// This is the original buffer size. The compressed names in original
+ /// are calculated in this buffer
+ original: &'a [u8],
+}
+
+impl<'a> Name<'a> {
+ /// Scan the data to get Name object
+ ///
+ /// The `data` should be a part of `original` where name should start.
+ /// The `original` is the data starting a the start of a packet, so
+ /// that offsets in compressed name starts from the `original`.
+ pub fn scan(data: &'a[u8], original: &'a[u8]) -> Result<Name<'a>, Error> {
+ let mut parse_data = data;
+ let mut return_pos = None;
+ let mut pos = 0;
+ if parse_data.len() <= pos {
+ return Err(Error::UnexpectedEOF);
+ }
+ // By setting the largest_pos to be the original len, a side effect
+ // is that the pos variable can move forwards in the buffer once.
+ let mut largest_pos = original.len();
+ let mut byte = parse_data[pos];
+ while byte != 0 {
+ if parse_data.len() <= pos {
+ return Err(Error::UnexpectedEOF);
+ }
+ if byte & 0b1100_0000 == 0b1100_0000 {
+ if parse_data.len() < pos+2 {
+ return Err(Error::UnexpectedEOF);
+ }
+ let off = (BigEndian::read_u16(&parse_data[pos..pos+2])
+ & !0b1100_0000_0000_0000) as usize;
+ if off >= original.len() {
+ return Err(Error::UnexpectedEOF);
+ }
+ // Set value for return_pos which is the pos in the original
+ // data buffer that should be used to return after validating
+ // the offsetted labels.
+ if let None = return_pos {
+ return_pos = Some(pos);
+ }
+
+ // Check then set largest_pos to ensure we never go backwards
+ // in the buffer.
+ if off >= largest_pos {
+ return Err(Error::BadPointer);
+ }
+ largest_pos = off;
+ pos = 0;
+ parse_data = &original[off..];
+ } else if byte & 0b1100_0000 == 0 {
+ let end = pos + byte as usize + 1;
+ if parse_data.len() < end {
+ return Err(Error::UnexpectedEOF);
+ }
+ if !parse_data[pos+1..end].is_ascii() {
+ return Err(Error::LabelIsNotAscii);
+ }
+ pos = end;
+ if parse_data.len() <= pos {
+ return Err(Error::UnexpectedEOF);
+ }
+ } else {
+ return Err(Error::UnknownLabelFormat);
+ }
+ byte = parse_data[pos];
+ }
+ if let Some(return_pos) = return_pos {
+ return Ok(Name {labels: &data[..return_pos+2], original: original});
+ } else {
+ return Ok(Name {labels: &data[..pos+1], original: original });
+ }
+ }
+ /// Number of bytes serialized name occupies
+ pub fn byte_len(&self) -> usize {
+ self.labels.len()
+ }
+}
+
+impl<'a> fmt::Display for Name<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ let data = self.labels;
+ let original = self.original;
+ let mut pos = 0;
+ loop {
+ let byte = data[pos];
+ if byte == 0 {
+ return Ok(());
+ } else if byte & 0b1100_0000 == 0b1100_0000 {
+ let off = (BigEndian::read_u16(&data[pos..pos+2])
+ & !0b1100_0000_0000_0000) as usize;
+ if pos != 0 {
+ try!(fmt.write_char('.'));
+ }
+ return fmt::Display::fmt(
+ &Name::scan(&original[off..], original).unwrap(), fmt)
+ } else if byte & 0b1100_0000 == 0 {
+ if pos != 0 {
+ try!(fmt.write_char('.'));
+ }
+ let end = pos + byte as usize + 1;
+ try!(fmt.write_str(from_utf8(&data[pos+1..end]).unwrap()));
+ pos = end;
+ continue;
+ } else {
+ unreachable!();
+ }
+ }
+ }
+}
+impl<'a> fmt::Debug for Name<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ fmt.debug_tuple("Name")
+ .field(&format!("{}", self))
+ .finish()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use Error;
+ use Name;
+
+ #[test]
+ fn parse_badpointer_same_offset() {
+ // A buffer where an offset points to itself,
+ // which is a bad compression pointer.
+ let same_offset = vec![192, 2, 192, 2];
+ let is_match = matches!(Name::scan(&same_offset, &same_offset),
+ Err(Error::BadPointer));
+
+ assert!(is_match);
+ }
+
+ #[test]
+ fn parse_badpointer_forward_offset() {
+ // A buffer where the offsets points back to each other which causes
+ // infinite recursion if never checked, a bad compression pointer.
+ let forwards_offset = vec![192, 2, 192, 4, 192, 2];
+ let is_match = matches!(Name::scan(&forwards_offset, &forwards_offset),
+ Err(Error::BadPointer));
+
+ assert!(is_match);
+ }
+
+ #[test]
+ fn nested_names() {
+ // A buffer where an offset points to itself, a bad compression pointer.
+ let buf = b"\x02xx\x00\x02yy\xc0\x00\x02zz\xc0\x04";
+
+ assert_eq!(Name::scan(&buf[..], buf).unwrap().to_string(),
+ "xx");
+ assert_eq!(Name::scan(&buf[..], buf).unwrap().labels,
+ b"\x02xx\x00");
+ assert_eq!(Name::scan(&buf[4..], buf).unwrap().to_string(),
+ "yy.xx");
+ assert_eq!(Name::scan(&buf[4..], buf).unwrap().labels,
+ b"\x02yy\xc0\x00");
+ assert_eq!(Name::scan(&buf[9..], buf).unwrap().to_string(),
+ "zz.yy.xx");
+ assert_eq!(Name::scan(&buf[9..], buf).unwrap().labels,
+ b"\x02zz\xc0\x04");
+ }
+}
diff --git a/third_party/rust/dns-parser/src/parser.rs b/third_party/rust/dns-parser/src/parser.rs
new file mode 100644
index 0000000000..0718798349
--- /dev/null
+++ b/third_party/rust/dns-parser/src/parser.rs
@@ -0,0 +1,455 @@
+use std::i32;
+
+use byteorder::{BigEndian, ByteOrder};
+
+use {Header, Packet, Error, Question, Name, QueryType, QueryClass};
+use {Type, Class, ResourceRecord, RData};
+use rdata::opt::Record as Opt;
+
+const OPT_RR_START: [u8; 3] = [0, 0, 41];
+
+impl<'a> Packet<'a> {
+ /// Parse a full DNS Packet and return a structure that has all the
+ /// data borrowed from the passed buffer.
+ pub fn parse(data: &[u8]) -> Result<Packet, Error> {
+ let header = try!(Header::parse(data));
+ let mut offset = Header::size();
+ let mut questions = Vec::with_capacity(header.questions as usize);
+ for _ in 0..header.questions {
+ let name = try!(Name::scan(&data[offset..], data));
+ offset += name.byte_len();
+ if offset + 4 > data.len() {
+ return Err(Error::UnexpectedEOF);
+ }
+ let qtype = try!(QueryType::parse(
+ BigEndian::read_u16(&data[offset..offset+2])));
+ offset += 2;
+
+ let (prefer_unicast, qclass) = try!(parse_qclass_code(
+ BigEndian::read_u16(&data[offset..offset+2])));
+ offset += 2;
+
+ questions.push(Question {
+ qname: name,
+ qtype: qtype,
+ prefer_unicast: prefer_unicast,
+ qclass: qclass,
+ });
+ }
+ let mut answers = Vec::with_capacity(header.answers as usize);
+ for _ in 0..header.answers {
+ answers.push(try!(parse_record(data, &mut offset)));
+ }
+ let mut nameservers = Vec::with_capacity(header.nameservers as usize);
+ for _ in 0..header.nameservers {
+ nameservers.push(try!(parse_record(data, &mut offset)));
+ }
+ let mut additional = Vec::with_capacity(header.additional as usize);
+ let mut opt = None;
+ for _ in 0..header.additional {
+ if offset + 3 <= data.len() && data[offset..offset+3] == OPT_RR_START {
+ if opt.is_none() {
+ opt = Some(try!(parse_opt_record(data, &mut offset)));
+ } else {
+ return Err(Error::AdditionalOPT);
+ }
+ } else {
+ additional.push(try!(parse_record(data, &mut offset)));
+ }
+ }
+ Ok(Packet {
+ header: header,
+ questions: questions,
+ answers: answers,
+ nameservers: nameservers,
+ additional: additional,
+ opt: opt,
+ })
+ }
+}
+
+fn parse_qclass_code(value: u16) -> Result<(bool, QueryClass), Error> {
+ let prefer_unicast = value & 0x8000 == 0x8000;
+ let qclass_code = value & 0x7FFF;
+
+ let qclass = try!(QueryClass::parse(qclass_code));
+ Ok((prefer_unicast, qclass))
+}
+
+fn parse_class_code(value: u16) -> Result<(bool, Class), Error> {
+ let is_unique = value & 0x8000 == 0x8000;
+ let class_code = value & 0x7FFF;
+
+ let cls = try!(Class::parse(class_code));
+ Ok((is_unique, cls))
+}
+
+// Generic function to parse answer, nameservers, and additional records.
+fn parse_record<'a>(data: &'a [u8], offset: &mut usize) -> Result<ResourceRecord<'a>, Error> {
+ let name = try!(Name::scan(&data[*offset..], data));
+ *offset += name.byte_len();
+ if *offset + 10 > data.len() {
+ return Err(Error::UnexpectedEOF);
+ }
+ let typ = try!(Type::parse(
+ BigEndian::read_u16(&data[*offset..*offset+2])));
+ *offset += 2;
+
+ let class_code = BigEndian::read_u16(&data[*offset..*offset+2]);
+ let (multicast_unique, cls) = try!(parse_class_code(class_code));
+ *offset += 2;
+
+ let mut ttl = BigEndian::read_u32(&data[*offset..*offset+4]);
+ if ttl > i32::MAX as u32 {
+ ttl = 0;
+ }
+ *offset += 4;
+ let rdlen = BigEndian::read_u16(&data[*offset..*offset+2]) as usize;
+ *offset += 2;
+ if *offset + rdlen > data.len() {
+ return Err(Error::UnexpectedEOF);
+ }
+ let data = try!(RData::parse(typ,
+ &data[*offset..*offset+rdlen], data));
+ *offset += rdlen;
+ Ok(ResourceRecord {
+ name: name,
+ multicast_unique: multicast_unique,
+ cls: cls,
+ ttl: ttl,
+ data: data,
+ })
+}
+
+// Function to parse an RFC 6891 OPT Pseudo RR
+fn parse_opt_record<'a>(data: &'a [u8], offset: &mut usize) -> Result<Opt<'a>, Error> {
+ if *offset + 11 > data.len() {
+ return Err(Error::UnexpectedEOF);
+ }
+ *offset += 1;
+ let typ = try!(Type::parse(
+ BigEndian::read_u16(&data[*offset..*offset+2])));
+ if typ != Type::OPT {
+ return Err(Error::InvalidType(typ as u16));
+ }
+ *offset += 2;
+ let udp = BigEndian::read_u16(&data[*offset..*offset+2]);
+ *offset += 2;
+ let extrcode = data[*offset];
+ *offset += 1;
+ let version = data[*offset];
+ *offset += 1;
+ let flags = BigEndian::read_u16(&data[*offset..*offset+2]);
+ *offset += 2;
+ let rdlen = BigEndian::read_u16(&data[*offset..*offset+2]) as usize;
+ *offset += 2;
+ if *offset + rdlen > data.len() {
+ return Err(Error::UnexpectedEOF);
+ }
+ let data = try!(RData::parse(typ,
+ &data[*offset..*offset+rdlen], data));
+ *offset += rdlen;
+
+ Ok(Opt {
+ udp: udp,
+ extrcode: extrcode,
+ version: version,
+ flags: flags,
+ data: data,
+ })
+}
+
+#[cfg(test)]
+mod test {
+
+ use std::net::Ipv4Addr;
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+
+ #[test]
+ fn parse_example_query() {
+ let query = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x00\x01";
+ let packet = Packet::parse(query).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 1573,
+ query: true,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: false,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 0,
+ nameservers: 0,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "example.com");
+ assert_eq!(packet.answers.len(), 0);
+ }
+
+ #[test]
+ fn parse_example_response() {
+ let response = b"\x06%\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x00\x01\
+ \xc0\x0c\x00\x01\x00\x01\x00\x00\x04\xf8\
+ \x00\x04]\xb8\xd8\"";
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 1573,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 1,
+ nameservers: 0,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "example.com");
+ assert_eq!(packet.answers.len(), 1);
+ assert_eq!(&packet.answers[0].name.to_string()[..], "example.com");
+ assert_eq!(packet.answers[0].multicast_unique, false);
+ assert_eq!(packet.answers[0].cls, C::IN);
+ assert_eq!(packet.answers[0].ttl, 1272);
+ match packet.answers[0].data {
+ RData::A(addr) => {
+ assert_eq!(addr.0, Ipv4Addr::new(93, 184, 216, 34));
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+
+ #[test]
+ fn parse_response_with_multicast_unique() {
+ let response = b"\x06%\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x00\x01\
+ \xc0\x0c\x00\x01\x80\x01\x00\x00\x04\xf8\
+ \x00\x04]\xb8\xd8\"";
+ let packet = Packet::parse(response).unwrap();
+
+ assert_eq!(packet.answers.len(), 1);
+ assert_eq!(packet.answers[0].multicast_unique, true);
+ assert_eq!(packet.answers[0].cls, C::IN);
+ }
+
+ #[test]
+ fn parse_additional_record_response() {
+ let response = b"\x4a\xf0\x81\x80\x00\x01\x00\x01\x00\x01\x00\x01\
+ \x03www\x05skype\x03com\x00\x00\x01\x00\x01\
+ \xc0\x0c\x00\x05\x00\x01\x00\x00\x0e\x10\
+ \x00\x1c\x07\x6c\x69\x76\x65\x63\x6d\x73\x0e\x74\
+ \x72\x61\x66\x66\x69\x63\x6d\x61\x6e\x61\x67\x65\
+ \x72\x03\x6e\x65\x74\x00\
+ \xc0\x42\x00\x02\x00\x01\x00\x01\xd5\xd3\x00\x11\
+ \x01\x67\x0c\x67\x74\x6c\x64\x2d\x73\x65\x72\x76\x65\x72\x73\
+ \xc0\x42\
+ \x01\x61\xc0\x55\x00\x01\x00\x01\x00\x00\xa3\x1c\
+ \x00\x04\xc0\x05\x06\x1e";
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 19184,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 1,
+ nameservers: 1,
+ additional: 1,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com");
+ assert_eq!(packet.answers.len(), 1);
+ assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com");
+ assert_eq!(packet.answers[0].cls, C::IN);
+ assert_eq!(packet.answers[0].ttl, 3600);
+ match packet.answers[0].data {
+ RData::CNAME(cname) => {
+ assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net");
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ assert_eq!(packet.nameservers.len(), 1);
+ assert_eq!(&packet.nameservers[0].name.to_string()[..], "net");
+ assert_eq!(packet.nameservers[0].cls, C::IN);
+ assert_eq!(packet.nameservers[0].ttl, 120275);
+ match packet.nameservers[0].data {
+ RData::NS(ns) => {
+ assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net");
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ assert_eq!(packet.additional.len(), 1);
+ assert_eq!(&packet.additional[0].name.to_string()[..], "a.gtld-servers.net");
+ assert_eq!(packet.additional[0].cls, C::IN);
+ assert_eq!(packet.additional[0].ttl, 41756);
+ match packet.additional[0].data {
+ RData::A(addr) => {
+ assert_eq!(addr.0, Ipv4Addr::new(192, 5, 6, 30));
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+
+ #[test]
+ fn parse_multiple_answers() {
+ let response = b"\x9d\xe9\x81\x80\x00\x01\x00\x06\x00\x00\x00\x00\
+ \x06google\x03com\x00\x00\x01\x00\x01\xc0\x0c\
+ \x00\x01\x00\x01\x00\x00\x00\xef\x00\x04@\xe9\
+ \xa4d\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\xef\
+ \x00\x04@\xe9\xa4\x8b\xc0\x0c\x00\x01\x00\x01\
+ \x00\x00\x00\xef\x00\x04@\xe9\xa4q\xc0\x0c\x00\
+ \x01\x00\x01\x00\x00\x00\xef\x00\x04@\xe9\xa4f\
+ \xc0\x0c\x00\x01\x00\x01\x00\x00\x00\xef\x00\x04@\
+ \xe9\xa4e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\xef\
+ \x00\x04@\xe9\xa4\x8a";
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 40425,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 6,
+ nameservers: 0,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com");
+ assert_eq!(packet.answers.len(), 6);
+ let ips = vec![
+ Ipv4Addr::new(64, 233, 164, 100),
+ Ipv4Addr::new(64, 233, 164, 139),
+ Ipv4Addr::new(64, 233, 164, 113),
+ Ipv4Addr::new(64, 233, 164, 102),
+ Ipv4Addr::new(64, 233, 164, 101),
+ Ipv4Addr::new(64, 233, 164, 138),
+ ];
+ for i in 0..6 {
+ assert_eq!(&packet.answers[i].name.to_string()[..], "google.com");
+ assert_eq!(packet.answers[i].cls, C::IN);
+ assert_eq!(packet.answers[i].ttl, 239);
+ match packet.answers[i].data {
+ RData::A(addr) => {
+ assert_eq!(addr.0, ips[i]);
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+ }
+
+ #[test]
+ fn parse_srv_query() {
+ let query = b"[\xd9\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01";
+ let packet = Packet::parse(query).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 23513,
+ query: true,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: false,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 0,
+ nameservers: 0,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::SRV);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(packet.questions[0].prefer_unicast, false);
+ assert_eq!(&packet.questions[0].qname.to_string()[..],
+ "_xmpp-server._tcp.gmail.com");
+ assert_eq!(packet.answers.len(), 0);
+ }
+
+ #[test]
+ fn parse_multicast_prefer_unicast_query() {
+ let query = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
+ \x07example\x03com\x00\x00\x01\x80\x01";
+ let packet = Packet::parse(query).unwrap();
+
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(packet.questions[0].prefer_unicast, true);
+ }
+
+ #[test]
+ fn parse_example_query_edns() {
+ let query = b"\x95\xce\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\
+ \x06google\x03com\x00\x00\x01\x00\
+ \x01\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00";
+ let packet = Packet::parse(query).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 38350,
+ query: true,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: false,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 0,
+ nameservers: 0,
+ additional: 1,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com");
+ assert_eq!(packet.answers.len(), 0);
+ match packet.opt {
+ Some(opt) => {
+ assert_eq!(opt.udp, 4096);
+ assert_eq!(opt.extrcode, 0);
+ assert_eq!(opt.version, 0);
+ assert_eq!(opt.flags, 0);
+ },
+ None => panic!("Missing OPT RR")
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/a.rs b/third_party/rust/dns-parser/src/rdata/a.rs
new file mode 100644
index 0000000000..f4196d5ac2
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/a.rs
@@ -0,0 +1,21 @@
+use std::net::Ipv4Addr;
+
+use Error;
+use byteorder::{BigEndian, ByteOrder};
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record(pub Ipv4Addr);
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 1;
+
+ fn parse(rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ if rdata.len() != 4 {
+ return Err(Error::WrongRdataLength);
+ }
+ let address = Ipv4Addr::from(BigEndian::read_u32(rdata));
+ let record = Record(address);
+ Ok(super::RData::A(record))
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/aaaa.rs b/third_party/rust/dns-parser/src/rdata/aaaa.rs
new file mode 100644
index 0000000000..e59937a525
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/aaaa.rs
@@ -0,0 +1,85 @@
+use std::net::Ipv6Addr;
+
+use Error;
+use byteorder::{BigEndian, ByteOrder};
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record(pub Ipv6Addr);
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 28;
+
+ fn parse(rdata: &'a [u8], _record: &'a [u8]) -> super::RDataResult<'a> {
+ if rdata.len() != 16 {
+ return Err(Error::WrongRdataLength);
+ }
+ let address = Ipv6Addr::new(
+ BigEndian::read_u16(&rdata[0..2]),
+ BigEndian::read_u16(&rdata[2..4]),
+ BigEndian::read_u16(&rdata[4..6]),
+ BigEndian::read_u16(&rdata[6..8]),
+ BigEndian::read_u16(&rdata[8..10]),
+ BigEndian::read_u16(&rdata[10..12]),
+ BigEndian::read_u16(&rdata[12..14]),
+ BigEndian::read_u16(&rdata[14..16]),
+ );
+ let record = Record(address);
+ Ok(super::RData::AAAA(record))
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+ use super::*;
+
+ #[test]
+ fn parse_response() {
+ let response = b"\xa9\xd9\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\x06\
+ google\x03com\x00\x00\x1c\x00\x01\xc0\x0c\x00\x1c\x00\x01\x00\x00\
+ \x00\x8b\x00\x10*\x00\x14P@\t\x08\x12\x00\x00\x00\x00\x00\x00 \x0e";
+
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 43481,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 1,
+ nameservers: 0,
+ additional: 0,
+ });
+
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::AAAA);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com");
+ assert_eq!(packet.answers.len(), 1);
+ assert_eq!(&packet.answers[0].name.to_string()[..], "google.com");
+ assert_eq!(packet.answers[0].cls, C::IN);
+ assert_eq!(packet.answers[0].ttl, 139);
+ match packet.answers[0].data {
+ RData::AAAA(addr) => {
+ assert_eq!(addr.0, Ipv6Addr::new(
+ 0x2A00, 0x1450, 0x4009, 0x812, 0, 0, 0, 0x200e)
+ );
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/all.rs b/third_party/rust/dns-parser/src/rdata/all.rs
new file mode 100644
index 0000000000..106c91b089
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/all.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 255;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/axfr.rs b/third_party/rust/dns-parser/src/rdata/axfr.rs
new file mode 100644
index 0000000000..b48dc1ae24
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/axfr.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 252;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/cname.rs b/third_party/rust/dns-parser/src/rdata/cname.rs
new file mode 100644
index 0000000000..0dcb46952f
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/cname.rs
@@ -0,0 +1,102 @@
+use Name;
+
+#[derive(Debug, Clone, Copy)]
+pub struct Record<'a>(pub Name<'a>);
+
+impl<'a> ToString for Record<'a> {
+ #[inline]
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+}
+
+impl<'a> super::Record<'a> for Record<'a> {
+
+ const TYPE: isize = 5;
+
+ fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> {
+ let name = Name::scan(rdata, original)?;
+ let record = Record(name);
+ Ok(super::RData::CNAME(record))
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use std::net::Ipv4Addr;
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+
+ #[test]
+ fn parse_response() {
+ let response = b"\xfc\x9d\x81\x80\x00\x01\x00\x06\x00\x02\x00\x02\x03\
+ cdn\x07sstatic\x03net\x00\x00\x01\x00\x01\xc0\x0c\x00\x05\x00\x01\
+ \x00\x00\x00f\x00\x02\xc0\x10\xc0\x10\x00\x01\x00\x01\x00\x00\x00\
+ f\x00\x04h\x10g\xcc\xc0\x10\x00\x01\x00\x01\x00\x00\x00f\x00\x04h\
+ \x10k\xcc\xc0\x10\x00\x01\x00\x01\x00\x00\x00f\x00\x04h\x10h\xcc\
+ \xc0\x10\x00\x01\x00\x01\x00\x00\x00f\x00\x04h\x10j\xcc\xc0\x10\
+ \x00\x01\x00\x01\x00\x00\x00f\x00\x04h\x10i\xcc\xc0\x10\x00\x02\
+ \x00\x01\x00\x00\x99L\x00\x0b\x08cf-dns02\xc0\x10\xc0\x10\x00\x02\
+ \x00\x01\x00\x00\x99L\x00\x0b\x08cf-dns01\xc0\x10\xc0\xa2\x00\x01\
+ \x00\x01\x00\x00\x99L\x00\x04\xad\xf5:5\xc0\x8b\x00\x01\x00\x01\x00\
+ \x00\x99L\x00\x04\xad\xf5;\x04";
+
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 64669,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 6,
+ nameservers: 2,
+ additional: 2,
+ });
+
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "cdn.sstatic.net");
+ assert_eq!(packet.answers.len(), 6);
+ assert_eq!(&packet.answers[0].name.to_string()[..], "cdn.sstatic.net");
+ assert_eq!(packet.answers[0].cls, C::IN);
+ assert_eq!(packet.answers[0].ttl, 102);
+ match packet.answers[0].data {
+ RData::CNAME(cname) => {
+ assert_eq!(&cname.0.to_string(), "sstatic.net");
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+
+ let ips = vec![
+ Ipv4Addr::new(104, 16, 103, 204),
+ Ipv4Addr::new(104, 16, 107, 204),
+ Ipv4Addr::new(104, 16, 104, 204),
+ Ipv4Addr::new(104, 16, 106, 204),
+ Ipv4Addr::new(104, 16, 105, 204),
+ ];
+ for i in 1..6 {
+ assert_eq!(&packet.answers[i].name.to_string()[..], "sstatic.net");
+ assert_eq!(packet.answers[i].cls, C::IN);
+ assert_eq!(packet.answers[i].ttl, 102);
+ match packet.answers[i].data {
+ RData::A(addr) => {
+ assert_eq!(addr.0, ips[i-1]);
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/hinfo.rs b/third_party/rust/dns-parser/src/rdata/hinfo.rs
new file mode 100644
index 0000000000..6ee160bb86
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/hinfo.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 13;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/maila.rs b/third_party/rust/dns-parser/src/rdata/maila.rs
new file mode 100644
index 0000000000..ab1166cf31
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/maila.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 254;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/mailb.rs b/third_party/rust/dns-parser/src/rdata/mailb.rs
new file mode 100644
index 0000000000..9f043c60c3
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/mailb.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 253;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/mb.rs b/third_party/rust/dns-parser/src/rdata/mb.rs
new file mode 100644
index 0000000000..d14e7b4fc5
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/mb.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 7;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/mf.rs b/third_party/rust/dns-parser/src/rdata/mf.rs
new file mode 100644
index 0000000000..11c935d9f7
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/mf.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 4;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/mg.rs b/third_party/rust/dns-parser/src/rdata/mg.rs
new file mode 100644
index 0000000000..4fce456b7d
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/mg.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 8;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/minfo.rs b/third_party/rust/dns-parser/src/rdata/minfo.rs
new file mode 100644
index 0000000000..29b3a459b2
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/minfo.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 14;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/mod.rs b/third_party/rust/dns-parser/src/rdata/mod.rs
new file mode 100644
index 0000000000..72df27c232
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/mod.rs
@@ -0,0 +1,83 @@
+//! Data types and methods for handling the RData field
+
+#![allow(missing_docs)] // resource records are pretty self-descriptive
+
+pub mod a;
+pub mod aaaa;
+pub mod all;
+pub mod axfr;
+pub mod cname;
+pub mod hinfo;
+pub mod maila;
+pub mod mailb;
+pub mod mb;
+pub mod mf;
+pub mod mg;
+pub mod minfo;
+pub mod mr;
+pub mod mx;
+pub mod ns;
+pub mod nsec;
+pub mod null;
+pub mod opt;
+pub mod ptr;
+pub mod soa;
+pub mod srv;
+pub mod txt;
+pub mod wks;
+
+use {Type, Error};
+
+pub use self::a::Record as A;
+pub use self::aaaa::Record as Aaaa;
+pub use self::cname::Record as Cname;
+pub use self::mx::Record as Mx;
+pub use self::ns::Record as Ns;
+pub use self::nsec::Record as Nsec;
+pub use self::opt::Record as Opt;
+pub use self::ptr::Record as Ptr;
+pub use self::soa::Record as Soa;
+pub use self::srv::Record as Srv;
+pub use self::txt::Record as Txt;
+
+pub type RDataResult<'a> = Result<RData<'a>, Error>;
+
+/// The enumeration that represents known types of DNS resource records data
+#[derive(Debug)]
+pub enum RData<'a> {
+ A(A),
+ AAAA(Aaaa),
+ CNAME(Cname<'a>),
+ MX(Mx<'a>),
+ NS(Ns<'a>),
+ PTR(Ptr<'a>),
+ SOA(Soa<'a>),
+ SRV(Srv<'a>),
+ TXT(Txt<'a>),
+ /// Anything that can't be parsed yet
+ Unknown(&'a [u8]),
+}
+
+pub (crate) trait Record<'a> {
+ const TYPE: isize;
+
+ fn parse(rdata: &'a [u8], original: &'a [u8]) -> RDataResult<'a>;
+}
+
+impl<'a> RData<'a> {
+ /// Parse an RR data and return RData enumeration
+ pub fn parse(typ: Type, rdata: &'a [u8], original: &'a [u8]) -> RDataResult<'a> {
+ match typ {
+ Type::A => A::parse(rdata, original),
+ Type::AAAA => Aaaa::parse(rdata, original),
+ Type::CNAME => Cname::parse(rdata, original),
+ Type::NS => Ns::parse(rdata, original),
+ Type::MX => Mx::parse(rdata, original),
+ Type::PTR => Ptr::parse(rdata, original),
+ Type::SOA => Soa::parse(rdata, original),
+ Type::SRV => Srv::parse(rdata, original),
+ Type::TXT => Txt::parse(rdata, original),
+ _ => Ok(RData::Unknown(rdata)),
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/mr.rs b/third_party/rust/dns-parser/src/rdata/mr.rs
new file mode 100644
index 0000000000..e313372c4a
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/mr.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 9;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/mx.rs b/third_party/rust/dns-parser/src/rdata/mx.rs
new file mode 100644
index 0000000000..33c5c41dfc
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/mx.rs
@@ -0,0 +1,92 @@
+use {Name, Error};
+use byteorder::{BigEndian, ByteOrder};
+
+#[derive(Debug, Clone, Copy)]
+pub struct Record<'a> {
+ pub preference: u16,
+ pub exchange: Name<'a>,
+}
+
+impl<'a> super::Record<'a> for Record<'a> {
+
+ const TYPE: isize = 15;
+
+ fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> {
+ if rdata.len() < 3 {
+ return Err(Error::WrongRdataLength);
+ }
+ let record = Record {
+ preference: BigEndian::read_u16(&rdata[..2]),
+ exchange: Name::scan(&rdata[2..], original)?,
+ };
+ Ok(super::RData::MX(record))
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+ use super::*;
+
+ #[test]
+ fn parse_response() {
+ let response = b"\xe3\xe8\x81\x80\x00\x01\x00\x05\x00\x00\x00\x00\
+ \x05gmail\x03com\x00\x00\x0f\x00\x01\xc0\x0c\x00\x0f\x00\x01\
+ \x00\x00\x04|\x00\x1b\x00\x05\rgmail-smtp-in\x01l\x06google\xc0\
+ \x12\xc0\x0c\x00\x0f\x00\x01\x00\x00\x04|\x00\t\x00\
+ \n\x04alt1\xc0)\xc0\x0c\x00\x0f\x00\x01\x00\x00\x04|\
+ \x00\t\x00(\x04alt4\xc0)\xc0\x0c\x00\x0f\x00\x01\x00\
+ \x00\x04|\x00\t\x00\x14\x04alt2\xc0)\xc0\x0c\x00\x0f\
+ \x00\x01\x00\x00\x04|\x00\t\x00\x1e\x04alt3\xc0)";
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 58344,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 5,
+ nameservers: 0,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::MX);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..],
+ "gmail.com");
+ assert_eq!(packet.answers.len(), 5);
+ let items = vec![
+ ( 5, "gmail-smtp-in.l.google.com"),
+ (10, "alt1.gmail-smtp-in.l.google.com"),
+ (40, "alt4.gmail-smtp-in.l.google.com"),
+ (20, "alt2.gmail-smtp-in.l.google.com"),
+ (30, "alt3.gmail-smtp-in.l.google.com"),
+ ];
+ for i in 0..5 {
+ assert_eq!(&packet.answers[i].name.to_string()[..],
+ "gmail.com");
+ assert_eq!(packet.answers[i].cls, C::IN);
+ assert_eq!(packet.answers[i].ttl, 1148);
+ match *&packet.answers[i].data {
+ RData::MX( Record { preference, exchange }) => {
+ assert_eq!(preference, items[i].0);
+ assert_eq!(exchange.to_string(), (items[i].1).to_string());
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/ns.rs b/third_party/rust/dns-parser/src/rdata/ns.rs
new file mode 100644
index 0000000000..42a2f29ab1
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/ns.rs
@@ -0,0 +1,88 @@
+use Name;
+
+#[derive(Debug, Clone, Copy)]
+pub struct Record<'a>(pub Name<'a>);
+
+impl<'a> ToString for Record<'a> {
+ #[inline]
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+}
+
+impl<'a> super::Record<'a> for Record<'a> {
+
+ const TYPE: isize = 2;
+
+ fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> {
+ let name = Name::scan(rdata, original)?;
+ let record = Record(name);
+ Ok(super::RData::NS(record))
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+
+ #[test]
+ fn parse_response() {
+ let response = b"\x4a\xf0\x81\x80\x00\x01\x00\x01\x00\x01\x00\x00\
+ \x03www\x05skype\x03com\x00\x00\x01\x00\x01\
+ \xc0\x0c\x00\x05\x00\x01\x00\x00\x0e\x10\
+ \x00\x1c\x07\x6c\x69\x76\x65\x63\x6d\x73\x0e\x74\
+ \x72\x61\x66\x66\x69\x63\x6d\x61\x6e\x61\x67\x65\
+ \x72\x03\x6e\x65\x74\x00\
+ \xc0\x42\x00\x02\x00\x01\x00\x01\xd5\xd3\x00\x11\
+ \x01\x67\x0c\x67\x74\x6c\x64\x2d\x73\x65\x72\x76\x65\x72\x73\
+ \xc0\x42";
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 19184,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 1,
+ nameservers: 1,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com");
+ assert_eq!(packet.answers.len(), 1);
+ assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com");
+ assert_eq!(packet.answers[0].cls, C::IN);
+ assert_eq!(packet.answers[0].ttl, 3600);
+ match packet.answers[0].data {
+ RData::CNAME(cname) => {
+ assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net");
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ assert_eq!(packet.nameservers.len(), 1);
+ assert_eq!(&packet.nameservers[0].name.to_string()[..], "net");
+ assert_eq!(packet.nameservers[0].cls, C::IN);
+ assert_eq!(packet.nameservers[0].ttl, 120275);
+ match packet.nameservers[0].data {
+ RData::NS(ns) => {
+ assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net");
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/nsec.rs b/third_party/rust/dns-parser/src/rdata/nsec.rs
new file mode 100644
index 0000000000..354c512273
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/nsec.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 47;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/null.rs b/third_party/rust/dns-parser/src/rdata/null.rs
new file mode 100644
index 0000000000..b3bfcd35ee
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/null.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 10;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/opt.rs b/third_party/rust/dns-parser/src/rdata/opt.rs
new file mode 100644
index 0000000000..694d393440
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/opt.rs
@@ -0,0 +1,18 @@
+/// RFC 6891 OPT RR
+#[derive(Debug)]
+pub struct Record<'a> {
+ pub udp: u16,
+ pub extrcode: u8,
+ pub version: u8,
+ pub flags: u16,
+ pub data: super::RData<'a>,
+}
+
+impl<'a> super::Record<'a> for Record<'a> {
+
+ const TYPE: isize = 41;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/ptr.rs b/third_party/rust/dns-parser/src/rdata/ptr.rs
new file mode 100644
index 0000000000..315c3795a8
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/ptr.rs
@@ -0,0 +1,74 @@
+use Name;
+
+#[derive(Debug, Clone, Copy)]
+pub struct Record<'a>(pub Name<'a>);
+
+impl<'a> ToString for Record<'a> {
+ #[inline]
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+}
+
+impl<'a> super::Record<'a> for Record<'a> {
+
+ const TYPE: isize = 12;
+
+ fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> {
+ let name = Name::scan(rdata, original)?;
+ let record = Record(name);
+ Ok(super::RData::PTR(record))
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+
+ #[test]
+ fn parse_response() {
+ let response = b"\x53\xd6\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\
+ \x0269\x0293\x0275\x0272\x07in-addr\x04arpa\x00\
+ \x00\x0c\x00\x01\
+ \xc0\x0c\x00\x0c\x00\x01\x00\x01\x51\x80\x00\x1e\
+ \x10pool-72-75-93-69\x07verizon\x03net\x00";
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 21462,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 1,
+ nameservers: 0,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::PTR);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "69.93.75.72.in-addr.arpa");
+ assert_eq!(packet.answers.len(), 1);
+ assert_eq!(&packet.answers[0].name.to_string()[..], "69.93.75.72.in-addr.arpa");
+ assert_eq!(packet.answers[0].cls, C::IN);
+ assert_eq!(packet.answers[0].ttl, 86400);
+ match packet.answers[0].data {
+ RData::PTR(name) => {
+ assert_eq!(&name.0.to_string()[..], "pool-72-75-93-69.verizon.net");
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/soa.rs b/third_party/rust/dns-parser/src/rdata/soa.rs
new file mode 100644
index 0000000000..e6d5f1712d
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/soa.rs
@@ -0,0 +1,101 @@
+use {Name, Error};
+use byteorder::{BigEndian, ByteOrder};
+
+/// The SOA (Start of Authority) record
+#[derive(Debug, Clone, Copy)]
+pub struct Record<'a> {
+ pub primary_ns: Name<'a>,
+ pub mailbox: Name<'a>,
+ pub serial: u32,
+ pub refresh: u32,
+ pub retry: u32,
+ pub expire: u32,
+ pub minimum_ttl: u32,
+}
+
+impl<'a> super::Record<'a> for Record<'a> {
+
+ const TYPE: isize = 6;
+
+ fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> {
+ let mut pos = 0;
+ let primary_name_server = try!(Name::scan(rdata, original));
+ pos += primary_name_server.byte_len();
+ let mailbox = try!(Name::scan(&rdata[pos..], original));
+ pos += mailbox.byte_len();
+ if rdata[pos..].len() < 20 {
+ return Err(Error::WrongRdataLength);
+ }
+ let record = Record {
+ primary_ns: primary_name_server,
+ mailbox: mailbox,
+ serial: BigEndian::read_u32(&rdata[pos..(pos+4)]),
+ refresh: BigEndian::read_u32(&rdata[(pos+4)..(pos+8)]),
+ retry: BigEndian::read_u32(&rdata[(pos+8)..(pos+12)]),
+ expire: BigEndian::read_u32(&rdata[(pos+12)..(pos+16)]),
+ minimum_ttl: BigEndian::read_u32(&rdata[(pos+16)..(pos+20)]),
+ };
+ Ok(super::RData::SOA(record))
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NameError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+
+ #[test]
+ fn parse_response() {
+ let response = b"\x9f\xc5\x85\x83\x00\x01\x00\x00\x00\x01\x00\x00\
+ \x0edlkfjkdjdslfkj\x07youtube\x03com\x00\x00\x01\x00\x01\
+ \xc0\x1b\x00\x06\x00\x01\x00\x00\x2a\x30\x00\x1e\xc0\x1b\
+ \x05admin\xc0\x1b\x77\xed\x2a\x73\x00\x00\x51\x80\x00\x00\
+ \x0e\x10\x00\x00\x3a\x80\x00\x00\x2a\x30";
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 40901,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: true,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NameError,
+ questions: 1,
+ answers: 0,
+ nameservers: 1,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::A);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "dlkfjkdjdslfkj.youtube.com");
+ assert_eq!(packet.answers.len(), 0);
+
+ assert_eq!(packet.nameservers.len(), 1);
+ assert_eq!(&packet.nameservers[0].name.to_string()[..], "youtube.com");
+ assert_eq!(packet.nameservers[0].cls, C::IN);
+ assert_eq!(packet.nameservers[0].multicast_unique, false);
+ assert_eq!(packet.nameservers[0].ttl, 10800);
+ match packet.nameservers[0].data {
+ RData::SOA(ref soa_rec) => {
+ assert_eq!(&soa_rec.primary_ns.to_string()[..], "youtube.com");
+ assert_eq!(&soa_rec.mailbox.to_string()[..], "admin.youtube.com");
+ assert_eq!(soa_rec.serial, 2012031603);
+ assert_eq!(soa_rec.refresh, 20864);
+ assert_eq!(soa_rec.retry, 3600);
+ assert_eq!(soa_rec.expire, 14976);
+ assert_eq!(soa_rec.minimum_ttl, 10800);
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/srv.rs b/third_party/rust/dns-parser/src/rdata/srv.rs
new file mode 100644
index 0000000000..dbc151d8aa
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/srv.rs
@@ -0,0 +1,102 @@
+use {Name, Error};
+use byteorder::{BigEndian, ByteOrder};
+
+#[derive(Debug, Clone, Copy)]
+pub struct Record<'a> {
+ pub priority: u16,
+ pub weight: u16,
+ pub port: u16,
+ pub target: Name<'a>,
+}
+
+impl<'a> super::Record<'a> for Record<'a> {
+
+ const TYPE: isize = 33;
+
+ fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> {
+ if rdata.len() < 7 {
+ return Err(Error::WrongRdataLength);
+ }
+ let record = Record {
+ priority: BigEndian::read_u16(&rdata[..2]),
+ weight: BigEndian::read_u16(&rdata[2..4]),
+ port: BigEndian::read_u16(&rdata[4..6]),
+ target: Name::scan(&rdata[6..], original)?,
+ };
+ Ok(super::RData::SRV(record))
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+ use super::*;
+
+ #[test]
+ fn parse_response() {
+ let response = b"[\xd9\x81\x80\x00\x01\x00\x05\x00\x00\x00\x00\
+ \x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01\
+ \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00 \x00\x05\x00\x00\
+ \x14\x95\x0bxmpp-server\x01l\x06google\x03com\x00\xc0\x0c\x00!\
+ \x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\x14\x95\
+ \x04alt3\x0bxmpp-server\x01l\x06google\x03com\x00\
+ \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\
+ \x14\x95\x04alt1\x0bxmpp-server\x01l\x06google\x03com\x00\
+ \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\
+ \x14\x95\x04alt2\x0bxmpp-server\x01l\x06google\x03com\x00\
+ \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\
+ \x14\x95\x04alt4\x0bxmpp-server\x01l\x06google\x03com\x00";
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 23513,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 5,
+ nameservers: 0,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::SRV);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..],
+ "_xmpp-server._tcp.gmail.com");
+ assert_eq!(packet.answers.len(), 5);
+ let items = vec![
+ (5, 0, 5269, "xmpp-server.l.google.com"),
+ (20, 0, 5269, "alt3.xmpp-server.l.google.com"),
+ (20, 0, 5269, "alt1.xmpp-server.l.google.com"),
+ (20, 0, 5269, "alt2.xmpp-server.l.google.com"),
+ (20, 0, 5269, "alt4.xmpp-server.l.google.com"),
+ ];
+ for i in 0..5 {
+ assert_eq!(&packet.answers[i].name.to_string()[..],
+ "_xmpp-server._tcp.gmail.com");
+ assert_eq!(packet.answers[i].cls, C::IN);
+ assert_eq!(packet.answers[i].ttl, 900);
+ match *&packet.answers[i].data {
+ RData::SRV(Record { priority, weight, port, target }) => {
+ assert_eq!(priority, items[i].0);
+ assert_eq!(weight, items[i].1);
+ assert_eq!(port, items[i].2);
+ assert_eq!(target.to_string(), (items[i].3).to_string());
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/txt.rs b/third_party/rust/dns-parser/src/rdata/txt.rs
new file mode 100644
index 0000000000..8f5f5fc26d
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/txt.rs
@@ -0,0 +1,125 @@
+use Error;
+
+#[derive(Debug, Clone)]
+pub struct Record<'a> {
+ bytes: &'a [u8],
+}
+
+#[derive(Debug)]
+pub struct RecordIter<'a> {
+ bytes: &'a [u8],
+}
+
+impl<'a> Iterator for RecordIter<'a> {
+ type Item = &'a [u8];
+ fn next(&mut self) -> Option<&'a [u8]> {
+ if self.bytes.len() >= 1 {
+ let len = self.bytes[0] as usize;
+ debug_assert!(self.bytes.len() >= len+1);
+ let (head, tail) = self.bytes[1..].split_at(len);
+ self.bytes = tail;
+ return Some(head);
+ }
+ return None;
+ }
+}
+
+impl<'a> Record<'a> {
+
+ // Returns iterator over text chunks
+ pub fn iter(&self) -> RecordIter<'a> {
+ RecordIter {
+ bytes: self.bytes,
+ }
+ }
+}
+
+impl<'a> super::Record<'a> for Record<'a> {
+
+ const TYPE: isize = 16;
+
+ fn parse(rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ // Just a quick check that record is valid
+ let len = rdata.len();
+ if len < 1 {
+ return Err(Error::WrongRdataLength);
+ }
+ let mut pos = 0;
+ while pos < len {
+ let rdlen = rdata[pos] as usize;
+ pos += 1;
+ if len < rdlen + pos {
+ return Err(Error::WrongRdataLength);
+ }
+ pos += rdlen;
+ }
+ Ok(super::RData::TXT(Record {
+ bytes: rdata,
+ }))
+ }
+}
+
+#[cfg(test)]
+mod test {
+
+ use std::str::from_utf8;
+
+ use {Packet, Header};
+ use Opcode::*;
+ use ResponseCode::NoError;
+ use QueryType as QT;
+ use QueryClass as QC;
+ use Class as C;
+ use RData;
+
+ #[test]
+ fn parse_response_multiple_strings() {
+ let response = b"\x06%\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\
+ \x08facebook\x03com\x00\x00\x10\x00\x01\
+ \xc0\x0c\x00\x10\x00\x01\x00\x01\x51\x3d\x00\x23\
+ \x15\x76\x3d\x73\x70\x66\x31\x20\x72\x65\x64\x69\
+ \x72\x65\x63\x74\x3d\x5f\x73\x70\x66\x2e\
+ \x0c\x66\x61\x63\x65\x62\x6f\x6f\x6b\x2e\x63\x6f\x6d";
+
+ let packet = Packet::parse(response).unwrap();
+ assert_eq!(packet.header, Header {
+ id: 1573,
+ query: false,
+ opcode: StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: true,
+ recursion_available: true,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: NoError,
+ questions: 1,
+ answers: 1,
+ nameservers: 0,
+ additional: 0,
+ });
+ assert_eq!(packet.questions.len(), 1);
+ assert_eq!(packet.questions[0].qtype, QT::TXT);
+ assert_eq!(packet.questions[0].qclass, QC::IN);
+ assert_eq!(&packet.questions[0].qname.to_string()[..], "facebook.com");
+ assert_eq!(packet.answers.len(), 1);
+ assert_eq!(&packet.answers[0].name.to_string()[..], "facebook.com");
+ assert_eq!(packet.answers[0].multicast_unique, false);
+ assert_eq!(packet.answers[0].cls, C::IN);
+ assert_eq!(packet.answers[0].ttl, 86333);
+ match packet.answers[0].data {
+ RData::TXT(ref text) => {
+ assert_eq!(text.iter()
+ .map(|x| from_utf8(x).unwrap())
+ .collect::<Vec<_>>()
+ .concat(), "v=spf1 redirect=_spf.facebook.com");
+
+ // also assert boundaries are kept
+ assert_eq!(text.iter().collect::<Vec<_>>(),
+ ["v=spf1 redirect=_spf.".as_bytes(),
+ "facebook.com".as_bytes()]);
+ }
+ ref x => panic!("Wrong rdata {:?}", x),
+ }
+ }
+}
diff --git a/third_party/rust/dns-parser/src/rdata/wks.rs b/third_party/rust/dns-parser/src/rdata/wks.rs
new file mode 100644
index 0000000000..ff6e5f1881
--- /dev/null
+++ b/third_party/rust/dns-parser/src/rdata/wks.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Record;
+
+impl<'a> super::Record<'a> for Record {
+
+ const TYPE: isize = 11;
+
+ fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> {
+ unimplemented!();
+ }
+}
diff --git a/third_party/rust/dns-parser/src/structs.rs b/third_party/rust/dns-parser/src/structs.rs
new file mode 100644
index 0000000000..4f60639047
--- /dev/null
+++ b/third_party/rust/dns-parser/src/structs.rs
@@ -0,0 +1,50 @@
+use {QueryType, QueryClass, Name, Class, Header, RData};
+use rdata::opt;
+
+
+/// Parsed DNS packet
+#[derive(Debug)]
+#[allow(missing_docs)] // should be covered by spec
+pub struct Packet<'a> {
+ pub header: Header,
+ pub questions: Vec<Question<'a>>,
+ pub answers: Vec<ResourceRecord<'a>>,
+ pub nameservers: Vec<ResourceRecord<'a>>,
+ pub additional: Vec<ResourceRecord<'a>>,
+ /// Optional Pseudo-RR
+ /// When present it is sent as an RR in the additional section. In this RR
+ /// the `class` and `ttl` fields store max udp packet size and flags
+ /// respectively. To keep `ResourceRecord` clean we store the OPT record
+ /// here.
+ pub opt: Option<opt::Record<'a>>,
+}
+
+/// A parsed chunk of data in the Query section of the packet
+#[derive(Debug)]
+#[allow(missing_docs)] // should be covered by spec
+pub struct Question<'a> {
+ pub qname: Name<'a>,
+ /// Whether or not we prefer unicast responses.
+ /// This is used in multicast DNS.
+ pub prefer_unicast: bool,
+ pub qtype: QueryType,
+ pub qclass: QueryClass,
+}
+
+/// A single DNS record
+///
+/// We aim to provide whole range of DNS records available. But as time is
+/// limited we have some types of packets which are parsed and other provided
+/// as unparsed slice of bytes.
+#[derive(Debug)]
+#[allow(missing_docs)] // should be covered by spec
+pub struct ResourceRecord<'a> {
+ pub name: Name<'a>,
+ /// Whether or not the set of resource records is fully contained in the
+ /// packet, or whether there will be more resource records in future
+ /// packets. Only used for multicast DNS.
+ pub multicast_unique: bool,
+ pub cls: Class,
+ pub ttl: u32,
+ pub data: RData<'a>,
+}