diff --git a/src/enums.rs b/src/enums.rs index 9f58ddf..7f9aa50 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -45,6 +45,10 @@ 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, + /// SVCB parameters request + SVCB = svcb::Record::TYPE, } /// The QTYPE value according to RFC 1035 @@ -95,6 +99,12 @@ 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, + /// A request for SVCB records, see + /// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-10 + SVCB = svcb::Record::TYPE, } @@ -218,6 +228,8 @@ impl QueryType { ns::Record::TYPE => Ok(NS), 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), @@ -264,6 +276,8 @@ impl Type { ns::Record::TYPE => Ok(NS), 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 new file mode 100644 index 0000000..c91b057 --- /dev/null +++ b/src/rdata/https.rs @@ -0,0 +1,142 @@ +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 + 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> { + if rdata.len() < 1 { + return Err(Error::WrongRdataLength); + } + 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] + 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" + ); + } + + #[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 770f46b..d30108b 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; @@ -23,6 +24,7 @@ pub mod opt; pub mod ptr; pub mod soa; pub mod srv; +pub mod svcb; pub mod txt; pub mod wks; @@ -31,6 +33,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; @@ -38,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>; @@ -48,11 +52,13 @@ pub enum RData<'a> { A(A), AAAA(Aaaa), CNAME(Cname<'a>), + HTTPS(Https<'a>), MX(Mx<'a>), NS(Ns<'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]), @@ -71,11 +77,13 @@ 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), 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)), } @@ -89,11 +97,13 @@ 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, 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), + } + } +}