From dc5c4a3e6891c6e28bcc9799febe51b09b6907ed Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Fri, 22 Sep 2023 15:35:37 -0700 Subject: [PATCH 1/3] Fixes #64: Add support for HTTPS query type (type 65) Summary: I was getting 'query type 65 is invalid' errors parsing DNS packets on my machine and this fixes it. Test Plan: Added a unit test that parses a wild packet... it works --- src/enums.rs | 6 ++++ src/rdata/https.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++ src/rdata/mod.rs | 4 +++ 3 files changed, 92 insertions(+) create mode 100644 src/rdata/https.rs diff --git a/src/enums.rs b/src/enums.rs index 9f58ddf..1724271 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -45,6 +45,8 @@ pub enum Type { OPT = opt::Record::TYPE, /// next secure record (RFC 4034, RFC 6762) NSEC = nsec::Record::TYPE, + /// HTTPS parameters request + HTTPS = https::Record::TYPE, } /// The QTYPE value according to RFC 1035 @@ -95,6 +97,9 @@ pub enum QueryType { MAILA = maila::Record::TYPE, /// A request for all records All = all::Record::TYPE, + /// A request for HTTPS records, see + /// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-10 + HTTPS = https::Record::TYPE, } @@ -218,6 +223,7 @@ impl QueryType { ns::Record::TYPE => Ok(NS), mf::Record::TYPE => Ok(MF), cname::Record::TYPE => Ok(CNAME), + https::Record::TYPE => Ok(HTTPS), soa::Record::TYPE => Ok(SOA), mb::Record::TYPE => Ok(MB), mg::Record::TYPE => Ok(MG), diff --git a/src/rdata/https.rs b/src/rdata/https.rs new file mode 100644 index 0000000..4ff092b --- /dev/null +++ b/src/rdata/https.rs @@ -0,0 +1,82 @@ +use byteorder::{BigEndian, ByteOrder}; +use {Error, Name}; + +#[derive(Debug, Clone, Copy)] +pub struct Record<'a> { + pub target: Name<'a>, +} + +impl<'a> super::Record<'a> for Record<'a> { + const TYPE: isize = 65; + + fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { + if rdata.len() < 1 { + return Err(Error::WrongRdataLength); + } + let record = Record { + target: Name::scan(&rdata[0..], original)?, + }; + Ok(super::RData::HTTPS(record)) + } +} + +#[cfg(test)] +mod test { + + use Opcode::*; + use QueryClass as QC; + use QueryType as QT; + use ResponseCode::NoError; + use {Header, Packet}; + + #[test] + fn parse_response() { + // from wireshark + let response: [u8; 215] = [ + 0x21, 0x8b, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x06, 0x6d, + 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x09, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x41, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x6d, 0x00, 0x27, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x06, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x04, 0x64, 0x61, 0x74, 0x61, 0x0e, 0x74, 0x72, + 0x61, 0x66, 0x66, 0x69, 0x63, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x03, 0x6e, + 0x65, 0x74, 0x00, 0xc0, 0x3e, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x29, 0x10, 0x6f, 0x6e, 0x65, 0x64, 0x73, 0x63, 0x6f, 0x6c, 0x70, 0x72, 0x64, 0x65, + 0x75, 0x73, 0x30, 0x37, 0x06, 0x65, 0x61, 0x73, 0x74, 0x75, 0x73, 0x08, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x61, 0x70, 0x70, 0x05, 0x61, 0x7a, 0x75, 0x72, 0x65, 0xc0, 0x29, + 0xc0, 0x82, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x31, 0x07, 0x6e, + 0x73, 0x31, 0x2d, 0x32, 0x30, 0x31, 0x09, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x2d, 0x64, + 0x6e, 0x73, 0xc0, 0x29, 0x06, 0x6d, 0x73, 0x6e, 0x68, 0x73, 0x74, 0xc0, 0x1f, 0x00, + 0x00, 0x27, 0x11, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x09, 0x3a, + 0x80, 0x00, 0x00, 0x00, 0x3c, + ]; + + let packet = Packet::parse(&response).unwrap(); + assert_eq!( + packet.header, + Header { + id: 0x218b, + 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: 2, + nameservers: 1, + additional: 0, + } + ); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::HTTPS); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!( + &packet.questions[0].qname.to_string()[..], + "mobile.events.data.microsoft.com" + ); + } +} diff --git a/src/rdata/mod.rs b/src/rdata/mod.rs index 770f46b..1731b69 100644 --- a/src/rdata/mod.rs +++ b/src/rdata/mod.rs @@ -8,6 +8,7 @@ pub mod all; pub mod axfr; pub mod cname; pub mod hinfo; +pub mod https; pub mod maila; pub mod mailb; pub mod mb; @@ -31,6 +32,7 @@ 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::https::Record as Https; pub use self::mx::Record as Mx; pub use self::ns::Record as Ns; pub use self::nsec::Record as Nsec; @@ -48,6 +50,7 @@ pub enum RData<'a> { A(A), AAAA(Aaaa), CNAME(Cname<'a>), + HTTPS(Https<'a>), MX(Mx<'a>), NS(Ns<'a>), PTR(Ptr<'a>), @@ -89,6 +92,7 @@ impl<'a> RData<'a> { RData::A(..) => Type::A, RData::AAAA(..) => Type::AAAA, RData::CNAME(..) => Type::CNAME, + RData::HTTPS(..) => Type::HTTPS, RData::NS(..) => Type::NS, RData::MX(..) => Type::MX, RData::PTR(..) => Type::PTR, From 60a570446e8096ee81a16203f6d32d1cd435e2e4 Mon Sep 17 00:00:00 2001 From: Gregor Maier Date: Sat, 14 Oct 2023 14:35:42 -0400 Subject: [PATCH 2/3] Handle cases with HTTPS resource records in the response --- src/enums.rs | 1 + src/rdata/https.rs | 74 +++++++++++++++++++++++++++++++++++++++++----- src/rdata/mod.rs | 1 + 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/enums.rs b/src/enums.rs index 1724271..fe2bd52 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -270,6 +270,7 @@ impl Type { ns::Record::TYPE => Ok(NS), mf::Record::TYPE => Ok(MF), cname::Record::TYPE => Ok(CNAME), + https::Record::TYPE => Ok(HTTPS), soa::Record::TYPE => Ok(SOA), mb::Record::TYPE => Ok(MB), mg::Record::TYPE => Ok(MG), diff --git a/src/rdata/https.rs b/src/rdata/https.rs index 4ff092b..fde3db6 100644 --- a/src/rdata/https.rs +++ b/src/rdata/https.rs @@ -1,32 +1,31 @@ -use byteorder::{BigEndian, ByteOrder}; -use {Error, Name}; +use Error; #[derive(Debug, Clone, Copy)] pub struct Record<'a> { - pub target: Name<'a>, + // TODO: we are not actually parsing the contents of the HTTPS RR for now + pub raw: &'a [u8], } impl<'a> super::Record<'a> for Record<'a> { const TYPE: isize = 65; - fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { + fn parse(rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { if rdata.len() < 1 { return Err(Error::WrongRdataLength); } - let record = Record { - target: Name::scan(&rdata[0..], original)?, - }; + let record = Record { raw: rdata }; Ok(super::RData::HTTPS(record)) } } #[cfg(test)] mod test { - use Opcode::*; use QueryClass as QC; use QueryType as QT; use ResponseCode::NoError; + + use crate::RData; use {Header, Packet}; #[test] @@ -79,4 +78,63 @@ mod test { "mobile.events.data.microsoft.com" ); } + + #[test] + fn parse_foo() { + let response = [ + 0x6c, 0x78, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x77, + 0x77, 0x77, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, + 0x00, 0x41, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x41, 0x00, 0x01, 0x00, 0x00, 0x50, 0xce, + 0x00, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x06, 0x02, 0x68, 0x32, 0x02, 0x68, + 0x33, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x04, 0x8e, + 0xfb, 0x10, 0x69, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, + 0x04, 0x8e, 0xfb, 0x10, 0x67, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xc3, 0x00, 0x04, 0x8e, 0xfb, 0x10, 0x6a, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x00, 0xc3, 0x00, 0x04, 0x8e, 0xfb, 0x10, 0x68, 0xc0, 0x0c, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x04, 0x8e, 0xfb, 0x10, 0x63, 0xc0, 0x0c, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x04, 0x8e, 0xfb, 0x10, 0x93, 0xc0, + 0x0c, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x10, 0x26, 0x07, 0xf8, + 0xb0, 0x40, 0x04, 0x0c, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xc0, + 0x0c, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x10, 0x26, 0x07, 0xf8, + 0xb0, 0x40, 0x04, 0x0c, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xc0, + 0x0c, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x10, 0x26, 0x07, 0xf8, + 0xb0, 0x40, 0x04, 0x0c, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0xc0, + 0x0c, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x10, 0x26, 0x07, 0xf8, + 0xb0, 0x40, 0x04, 0x0c, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, + ]; + + let packet = Packet::parse(&response).unwrap(); + println!("{:#?}", packet); + assert_eq!( + packet.header, + Header { + id: 27768, + 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: 10, + } + ); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::HTTPS); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "www.google.com"); + assert_eq!(packet.answers.len(), 1); + let https_ans = &packet.answers[0]; + match &https_ans.data { + RData::HTTPS(rec) => { + assert_eq!(rec.raw, [0, 1, 0, 0, 1, 0, 6, 2, 104, 50, 2, 104, 51]); + } + x => panic!("Expected HTTPS type, got {:?}", x), + } + } } diff --git a/src/rdata/mod.rs b/src/rdata/mod.rs index 1731b69..01b5ab2 100644 --- a/src/rdata/mod.rs +++ b/src/rdata/mod.rs @@ -74,6 +74,7 @@ impl<'a> RData<'a> { Type::A => A::parse(rdata, original), Type::AAAA => Aaaa::parse(rdata, original), Type::CNAME => Cname::parse(rdata, original), + Type::HTTPS => Https::parse(rdata, original), Type::NS => Ns::parse(rdata, original), Type::MX => Mx::parse(rdata, original), Type::PTR => Ptr::parse(rdata, original), From 2315637a90cb90ffd8d1a6dbfcec4ea72a927880 Mon Sep 17 00:00:00 2001 From: Gregor Maier Date: Wed, 1 Nov 2023 19:26:04 -0400 Subject: [PATCH 3/3] Add support for DNS type 64 (SVCB) --- src/enums.rs | 7 ++ src/rdata/https.rs | 2 + src/rdata/mod.rs | 5 ++ src/rdata/svcb.rs | 155 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 src/rdata/svcb.rs diff --git a/src/enums.rs b/src/enums.rs index fe2bd52..7f9aa50 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -47,6 +47,8 @@ pub enum Type { NSEC = nsec::Record::TYPE, /// HTTPS parameters request HTTPS = https::Record::TYPE, + /// SVCB parameters request + SVCB = svcb::Record::TYPE, } /// The QTYPE value according to RFC 1035 @@ -100,6 +102,9 @@ pub enum QueryType { /// A request for HTTPS records, see /// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-10 HTTPS = https::Record::TYPE, + /// A request for SVCB records, see + /// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-10 + SVCB = svcb::Record::TYPE, } @@ -224,6 +229,7 @@ impl QueryType { mf::Record::TYPE => Ok(MF), cname::Record::TYPE => Ok(CNAME), https::Record::TYPE => Ok(HTTPS), + svcb::Record::TYPE => Ok(SVCB), soa::Record::TYPE => Ok(SOA), mb::Record::TYPE => Ok(MB), mg::Record::TYPE => Ok(MG), @@ -271,6 +277,7 @@ impl Type { mf::Record::TYPE => Ok(MF), cname::Record::TYPE => Ok(CNAME), https::Record::TYPE => Ok(HTTPS), + svcb::Record::TYPE => Ok(SVCB), soa::Record::TYPE => Ok(SOA), mb::Record::TYPE => Ok(MB), mg::Record::TYPE => Ok(MG), diff --git a/src/rdata/https.rs b/src/rdata/https.rs index fde3db6..c91b057 100644 --- a/src/rdata/https.rs +++ b/src/rdata/https.rs @@ -1,5 +1,7 @@ use Error; +// see https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-10 + #[derive(Debug, Clone, Copy)] pub struct Record<'a> { // TODO: we are not actually parsing the contents of the HTTPS RR for now diff --git a/src/rdata/mod.rs b/src/rdata/mod.rs index 01b5ab2..d30108b 100644 --- a/src/rdata/mod.rs +++ b/src/rdata/mod.rs @@ -24,6 +24,7 @@ pub mod opt; pub mod ptr; pub mod soa; pub mod srv; +pub mod svcb; pub mod txt; pub mod wks; @@ -40,6 +41,7 @@ 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::svcb::Record as Svcb; pub use self::txt::Record as Txt; pub type RDataResult<'a> = Result, Error>; @@ -56,6 +58,7 @@ pub enum RData<'a> { PTR(Ptr<'a>), SOA(Soa<'a>), SRV(Srv<'a>), + SVCB(Svcb<'a>), TXT(Txt<'a>), /// Anything that can't be parsed yet Unknown(Type, &'a [u8]), @@ -80,6 +83,7 @@ impl<'a> RData<'a> { Type::PTR => Ptr::parse(rdata, original), Type::SOA => Soa::parse(rdata, original), Type::SRV => Srv::parse(rdata, original), + Type::SVCB => Svcb::parse(rdata, original), Type::TXT => Txt::parse(rdata, original), _ => Ok(RData::Unknown(typ, rdata)), } @@ -99,6 +103,7 @@ impl<'a> RData<'a> { RData::PTR(..) => Type::PTR, RData::SOA(..) => Type::SOA, RData::SRV(..) => Type::SRV, + RData::SVCB(..) => Type::SVCB, RData::TXT(..) => Type::TXT, RData::Unknown(t, _) => t, } diff --git a/src/rdata/svcb.rs b/src/rdata/svcb.rs new file mode 100644 index 0000000..fddccde --- /dev/null +++ b/src/rdata/svcb.rs @@ -0,0 +1,155 @@ +use Error; + +#[derive(Debug, Clone, Copy)] +pub struct Record<'a> { + // TODO: we are not actually parsing the contents of the HTTPS RR for now + pub raw: &'a [u8], +} + +impl<'a> super::Record<'a> for Record<'a> { + const TYPE: isize = 64; + + fn parse(rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + if rdata.len() < 1 { + return Err(Error::WrongRdataLength); + } + let record = Record { raw: rdata }; + Ok(super::RData::SVCB(record)) + } +} + +#[cfg(test)] +mod test { + use Opcode::*; + use QueryClass as QC; + use QueryType as QT; + use ResponseCode::NoError; + + use crate::RData; + use {Header, Packet}; + + #[test] + fn parse_request() { + // from wireshark + let response: [u8; 36] = [ + 0x47, 0xaf, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x5f, 0x64, 0x6e, + 0x73, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, + 0x65, 0x72, 0x04, 0x61, 0x72, 0x70, 0x61, 0x00, + 0x00, 0x40, 0x00, 0x01 + ]; + + let packet = Packet::parse(&response).unwrap(); + assert_eq!( + packet.header, + Header { + id: 18351, + 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::SVCB); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!( + &packet.questions[0].qname.to_string()[..], + "_dns.resolver.arpa" + ); + } + + #[test] + fn parse_response() { + let response: [u8; 347] = [ + 0x31, 0x3b, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x04, 0x04, 0x5f, 0x64, 0x6e, + 0x73, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, + 0x65, 0x72, 0x04, 0x61, 0x72, 0x70, 0x61, 0x00, + 0x00, 0x40, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x40, + 0x00, 0x01, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x67, + 0x00, 0x01, 0x03, 0x6f, 0x6e, 0x65, 0x03, 0x6f, + 0x6e, 0x65, 0x03, 0x6f, 0x6e, 0x65, 0x03, 0x6f, + 0x6e, 0x65, 0x00, 0x00, 0x01, 0x00, 0x06, 0x02, + 0x68, 0x32, 0x02, 0x68, 0x33, 0x00, 0x03, 0x00, + 0x02, 0x01, 0xbb, 0x00, 0x04, 0x00, 0x08, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, + 0x06, 0x00, 0x20, 0x26, 0x06, 0x47, 0x00, 0x47, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x11, 0x11, 0x26, 0x06, 0x47, 0x00, 0x47, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x01, 0x00, 0x07, 0x00, 0x10, 0x2f, + 0x64, 0x6e, 0x73, 0x2d, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x7b, 0x3f, 0x64, 0x6e, 0x73, 0x7d, 0xc0, + 0x0c, 0x00, 0x40, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x2c, 0x00, 0x51, 0x00, 0x02, 0x03, 0x6f, 0x6e, + 0x65, 0x03, 0x6f, 0x6e, 0x65, 0x03, 0x6f, 0x6e, + 0x65, 0x03, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x01, + 0x00, 0x04, 0x03, 0x64, 0x6f, 0x74, 0x00, 0x03, + 0x00, 0x02, 0x03, 0x55, 0x00, 0x04, 0x00, 0x08, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x06, 0x00, 0x20, 0x26, 0x06, 0x47, 0x00, + 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x11, 0x26, 0x06, 0x47, 0x00, + 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x01, 0x03, 0x6f, 0x6e, 0x65, + 0x03, 0x6f, 0x6e, 0x65, 0x03, 0x6f, 0x6e, 0x65, + 0x03, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x1c, 0x00, + 0x01, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x10, 0x26, + 0x06, 0x47, 0x00, 0x47, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0xc0, + 0xf4, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x2c, 0x00, 0x10, 0x26, 0x06, 0x47, 0x00, 0x47, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x01, 0xc0, 0xf4, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x04, 0x01, + 0x01, 0x01, 0x01, 0xc0, 0xf4, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x04, 0x01, + 0x00, 0x00, 0x01 + ]; + + let packet = Packet::parse(&response).unwrap(); + assert_eq!( + packet.header, + Header { + id: 12603, + 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: 2, + nameservers: 0, + additional: 4, + } + ); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::SVCB); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!( + &packet.questions[0].qname.to_string()[..], + "_dns.resolver.arpa" + ); + let https_ans = &packet.answers[0]; + match &https_ans.data { + RData::SVCB(rec) => { + assert_eq!(rec.raw.len(), 103); + } + x => panic!("Expected SVCB type, got {:?}", x), + } + } +}