diff --git a/lib/dnsbox-base/src/common_types/binary.rs b/lib/dnsbox-base/src/common_types/binary.rs index 215d3ee..d71d657 100644 --- a/lib/dnsbox-base/src/common_types/binary.rs +++ b/lib/dnsbox-base/src/common_types/binary.rs @@ -2,7 +2,7 @@ use bytes::{Bytes, BufMut}; use data_encoding::{self, HEXLOWER_PERMISSIVE}; use errors::*; use failure::{Fail, ResultExt}; -use ser::packet::{DnsPacketData, DnsPacketWriteContext, remaining_bytes, short_blob, write_short_blob}; +use ser::packet::{DnsPacketData, DnsPacketWriteContext, remaining_bytes, short_blob, write_short_blob, get_blob}; use ser::text::*; use std::fmt; use std::io::Cursor; @@ -65,6 +65,57 @@ impl DnsTextData for HexShortBlob { } } +// 16-bit length, uses decimal length + base64 encoding (if length > 0) +// for text representation. +// +// In base64 encoding no whitespace allowed and padding required. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Base64LongBlob(Bytes); + +impl DnsPacketData for Base64LongBlob { + fn deserialize(data: &mut Cursor) -> Result { + let len = u16::deserialize(data)? as usize; + check_enough_data!(data, len, "data for long blob"); + + Ok(Base64LongBlob(get_blob(data, len)?)) + } + + fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + let len = self.0.len(); + ensure!(len < 0x1_0000, "blob too long"); + (len as u16).serialize(context, packet)?; + packet.reserve(len); + packet.put_slice(&self.0); + Ok(()) + } +} + +impl DnsTextData for Base64LongBlob { + fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result { + let length_field = next_field(data)?; + let length = length_field.parse::() + .with_context(|_| format!("invalid length for blob: {:?}", length_field))?; + + if length > 0 { + let blob_field = next_field(data)?; + let result = BASE64_ALLOW_WS.decode(blob_field.as_bytes()) + .with_context(|e| e.context(format!("invalid base64: {:?}", blob_field)))?; + Ok(Base64LongBlob(result.into())) + } else { + Ok(Base64LongBlob(Bytes::new())) + } + } + + fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { + if self.0.len() >= 0x1_0000 { return Err(fmt::Error); } + if self.0.is_empty() { + write!(f, "0") + } else { + write!(f, "{} {}", self.0.len(), BASE64_ALLOW_WS.encode(&self.0)) + } + } +} + // No length byte (or restriction), just all data to end of record. uses // base64 encoding for text representation, whitespace allowed, padding // required. @@ -95,7 +146,11 @@ impl DnsTextData for Base64RemainingBlob { } fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { - write!(f, "{}", BASE64_ALLOW_WS.encode(&self.0)) + if !self.0.is_empty() { + write!(f, "{}", BASE64_ALLOW_WS.encode(&self.0)) + } else { + Ok(()) + } } } diff --git a/lib/dnsbox-base/src/common_types/eui.rs b/lib/dnsbox-base/src/common_types/eui.rs new file mode 100644 index 0000000..840ba09 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/eui.rs @@ -0,0 +1,143 @@ +use bytes::Bytes; +use errors::*; +use ser::packet::{DnsPacketData, DnsPacketWriteContext}; +use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field}; +use std::fmt; +use std::io::{Cursor, Read}; + +fn fmt_eui_hyphens(data: &[u8], f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:02x}", data[0])?; + for v in &data[1..] { + write!(f, "-{:02x}", v)?; + } + Ok(()) +} + +fn parse_eui_hyphens(dest: &mut [u8], source: &str) -> Result<()> { + let mut pos = 0; + for octet in source.split('-') { + ensure!(pos < dest.len(), "too many octets for EUI{}", dest.len() * 8); + ensure!(octet.len() == 2, "invalid octet {:?}", octet); + match u8::from_str_radix(octet, 16) { + Ok(o) => { + dest[pos] = o; + pos += 1; + }, + Err(_) => { + bail!("invalid octet {:?}", octet); + }, + } + } + ensure!(pos == dest.len(), "not enough octets for EUI{}", dest.len() * 8); + Ok(()) +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EUI48Addr(pub [u8; 6]); + +impl fmt::Display for EUI48Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_eui_hyphens(&self.0, f) + } +} + +impl fmt::Debug for EUI48Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_eui_hyphens(&self.0, f) + } +} + +impl DnsPacketData for EUI48Addr { + fn deserialize(data: &mut Cursor) -> Result { + let mut buf = [0u8; 6]; + check_enough_data!(data, 6, "not enough bytes for EUI48Addr"); + data.read_exact(&mut buf)?; + Ok(EUI48Addr(buf)) + } + + fn serialize(&self, _context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + packet.extend_from_slice(&self.0); + Ok(()) + } +} + +impl DnsTextData for EUI48Addr { + fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> Result { + let field = next_field(data)?; + let mut buf = [0u8; 6]; + parse_eui_hyphens(&mut buf, field)?; + Ok(EUI48Addr(buf)) + } + + // format might fail if there is no (known) text representation. + fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl From<[u8; 6]> for EUI48Addr { + fn from(v: [u8; 6]) -> Self { + EUI48Addr(v) + } +} + +impl Into<[u8; 6]> for EUI48Addr { + fn into(self) -> [u8; 6] { + self.0 + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EUI64Addr(pub [u8; 8]); + +impl fmt::Display for EUI64Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_eui_hyphens(&self.0, f) + } +} + +impl fmt::Debug for EUI64Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_eui_hyphens(&self.0, f) + } +} + +impl DnsPacketData for EUI64Addr { + fn deserialize(data: &mut Cursor) -> Result { + let mut buf = [0u8; 8]; + check_enough_data!(data, 8, "not enough bytes for EUI64Addr"); + data.read_exact(&mut buf)?; + Ok(EUI64Addr(buf)) + } + + fn serialize(&self, _context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + packet.extend_from_slice(&self.0); + Ok(()) + } +} + +impl DnsTextData for EUI64Addr { + fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> Result { + let field = next_field(data)?; + let mut buf = [0u8; 8]; + parse_eui_hyphens(&mut buf, field)?; + Ok(EUI64Addr(buf)) + } + + // format might fail if there is no (known) text representation. + fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl From<[u8; 8]> for EUI64Addr { + fn from(v: [u8; 8]) -> Self { + EUI64Addr(v) + } +} + +impl Into<[u8; 8]> for EUI64Addr { + fn into(self) -> [u8; 8] { + self.0 + } +} diff --git a/lib/dnsbox-base/src/common_types/mod.rs b/lib/dnsbox-base/src/common_types/mod.rs index baa89ae..f17db6f 100644 --- a/lib/dnsbox-base/src/common_types/mod.rs +++ b/lib/dnsbox-base/src/common_types/mod.rs @@ -1,19 +1,21 @@ -pub mod name; -pub mod text; pub mod binary; pub mod classes; +pub mod name; +pub mod text; pub mod types; +mod eui; mod nsec; mod nxt; mod time; mod uri; -pub use self::binary::{HexShortBlob, Base64RemainingBlob, HexRemainingBlob}; +pub use self::binary::{HexShortBlob, Base64LongBlob, Base64RemainingBlob, HexRemainingBlob}; +pub use self::classes::Class; +pub use self::eui::{EUI48Addr, EUI64Addr}; pub use self::name::{DnsName, DnsCanonicalName, DnsCompressedName}; pub use self::nsec::{NsecTypeBitmap, NextHashedOwnerName}; -pub use self::types::Type; -pub use self::classes::Class; -pub use self::text::{ShortText, LongText, UnquotedShortText, RemainingText}; -pub use self::uri::UriText; -pub use self::time::Time; pub use self::nxt::NxtTypeBitmap; +pub use self::text::{ShortText, LongText, UnquotedShortText, RemainingText}; +pub use self::time::{Time, Time48}; +pub use self::types::Type; +pub use self::uri::UriText; diff --git a/lib/dnsbox-base/src/common_types/time.rs b/lib/dnsbox-base/src/common_types/time.rs index 1649331..92fa779 100644 --- a/lib/dnsbox-base/src/common_types/time.rs +++ b/lib/dnsbox-base/src/common_types/time.rs @@ -9,7 +9,7 @@ use std::io::Cursor; /// /// Is expected to wrap around. #[derive(Clone, PartialEq, Eq, Debug)] -pub struct Time(u32); +pub struct Time(pub u32); impl DnsPacketData for Time { fn deserialize(data: &mut Cursor) -> Result { @@ -21,12 +21,80 @@ impl DnsPacketData for Time { } } + +struct Tm { + year: u16, // 1970...9999 + month: u8, // 01...12 + day: u8, // 01..31 + hour: u8, // 00..23 + minute: u8, // 00..59 + second: u8, // 00..59 +} + +impl Tm { + // 0..365 + fn dayofyear(&self) -> u16 { + let is_leap_year = (0 == self.year % 4) && ((0 != self.year % 100) || (0 == self.year % 400)); + static MONTH_START: [u16; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + let leap_add = if self.month > 2 && is_leap_year { 1 } else { 0 }; + MONTH_START[self.month as usize - 1] + leap_add + (self.day as u16 - 1) + } + + fn epoch(&self) -> u64 { + let yday = self.dayofyear(); + self.second as u64 + + 60 * self.minute as u64 + + 3600 * self.hour as u64 + + 86400 * yday as u64 + + 31536000 * (self.year as u64 - 1970) + + 86400 * ((self.year as u64 - 1969) / 4) + - 86400 * ((self.year as u64 - 1901) / 100) + + 86400 * ((self.year as u64 - 1900 + 299) / 400) + } +} + +impl ::std::str::FromStr for Tm { + type Err = ::failure::Error; + fn from_str(s: &str) -> ::errors::Result { + ensure!(s.len() == 14, "Tm string must be exactly 14 digits long"); + ensure!(s.as_bytes().iter().all(|&b| b >= b'0' && b <= b'9'), "Tm string must be exactly 14 digits long"); + let year = s[0..4].parse::()?; + ensure!(year >= 1970, "year must be >= 1970"); + ensure!(year <= 9999, "year must be <= 9999"); + + fn p(s: &str, min: u8, max: u8, name: &'static str) -> ::errors::Result { + let v = s.parse::()?; + ensure!(v >= min && v <= max, "{} {} out of range {}-{}", name, v, min, max); + Ok(v) + } + + let month = p(&s[4..6], 1, 12, "month")?; + let day = p(&s[6..8], 1, 31, "day")?; + let hour = p(&s[8..10], 0, 23, "hour")?; + let minute = p(&s[10..12], 0, 59, "minute")?; + let second = p(&s[12..14], 0, 59, "second")?; + + if 2 == month { + let is_leap_year = (0 == year % 4) && ((0 != year % 100) || (0 == year % 400)); + ensure!(day < 30, "day {} out of range in february", day); + ensure!(is_leap_year || day < 29, "day {} out of range in february (not a leap year)", day); + } else { + static DAYS_IN_MONTHS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let max_days = DAYS_IN_MONTHS[month as usize - 1]; + ensure!(day <= max_days, "day {} out of range for month {}", day, month); + } + + Ok(Tm{ year, month, day, hour, minute, second }) + } +} + impl DnsTextData for Time { fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result { let field = next_field(data)?; let epoch = field.parse::(); if field.len() == 14 && epoch.is_err() { - unimplemented!() + let tm = field.parse::()?; + Ok(Time(tm.epoch() as u32)) } else { Ok(Time(epoch?)) } @@ -36,3 +104,39 @@ impl DnsTextData for Time { write!(f, "{}", self.0) } } + +pub const TIME48_MAX: u64 = 0xffff_ffff_ffff; + +/// 48-bit timestamp in seconds since epoch (ignoring leap seconds) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Time48(pub u64); + +impl DnsPacketData for Time48 { + fn deserialize(data: &mut Cursor) -> Result { + let high16 = u16::deserialize(data)? as u64; + let low32 = u32::deserialize(data)? as u64; + Ok(Time48(high16 | low32)) + } + + fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + ensure!(self.0 <= TIME48_MAX, "time48 overflow"); + let high16 = (self.0 >> 32) as u16; + let low32 = self.0 as u32; + high16.serialize(context, packet)?; + low32.serialize(context, packet)?; + Ok(()) + } +} + +impl DnsTextData for Time48 { + fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> ::errors::Result { + let field = next_field(data)?; + let epoch = field.parse::()?; + ensure!(epoch <= TIME48_MAX, "time48 overflow"); + Ok(Time48(epoch)) + } + + fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/lib/dnsbox-base/src/records/powerdns_tests.rs b/lib/dnsbox-base/src/records/powerdns_tests.rs index fcdad76..5ffaff1 100644 --- a/lib/dnsbox-base/src/records/powerdns_tests.rs +++ b/lib/dnsbox-base/src/records/powerdns_tests.rs @@ -277,7 +277,8 @@ fn test_TXT() { None, b"\xfflong record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\x0a2222222222", ); -/* autosplitting not supported + // autosplitting not supported +/* check(types::TXT, "\"long record test 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222\"", Some("\"long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\" \"2222222222\""), @@ -514,7 +515,8 @@ fn test_IPSECKEY() { fn test_RRSIG() { check(types::RRSIG, "SOA 8 3 300 20130523000000 20130509000000 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=", - None, + // None, // we use the epoch as canoninc format + Some("SOA 8 3 300 1369267200 1368057600 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y="), b"\x00\x06\x08\x03\x00\x00\x01\x2c\x51\x9d\x5c\x00\x51\x8a\xe7\x00\xd3\xc8\x03\x72\x65\x63\x04\x74\x65\x73\x74\x00\x79\xc5\x8a\x0f\xf3\xac\x74\x08\x97\xa5\xb3\x3f\xb2\x03\xd3\xf3\x62\x95\x0f\xf5\xa2\x42\x79\xea\x73\x1a\x09\x82\x21\xf7\x8b\x11\xda\xf8\xb3\x80\x71\x85\x3b\x14\x6e\xd5\xe0\x14\x51\x27\x12\xeb\x89\x8d\x5e\xd2\xb0\x76\x80\x0b\x37\x90\x27\xa5\xf4\x3e\x1b\xcd\xf2\xba\xc0\x0a\x86\xe0\x38\xbf\x78\x75\x20\x1e\x9b\x51\x95\xa4\x76\xc9\xc1\x0b\x27\x8b\x75\xa6\x3c\xeb\x9e\xe1\xa2\x23\x30\x35\xc0\xcd\xa2\xcd\x19\x9e\x13\xe5\xe8\xcb\x5d\xfa\xed\x0b\xb4\xa4\xec\xf9\x0c\xbc\xd3\x7d\x02\x13\x58\x5b\x01\xda\x25\x33\x37\x86", ); } diff --git a/lib/dnsbox-base/src/records/registry.rs b/lib/dnsbox-base/src/records/registry.rs index f5ce870..7bd670a 100644 --- a/lib/dnsbox-base/src/records/registry.rs +++ b/lib/dnsbox-base/src/records/registry.rs @@ -158,22 +158,22 @@ impl Registry { r.register_unknown("APL" , types::APL); r.register_known::(); r.register_known::(); - r.register_unknown("IPSECKEY" , types::IPSECKEY); + r.register_known::(); r.register_known::(); r.register_known::(); r.register_known::(); - r.register_unknown("DHCID" , types::DHCID); + r.register_known::(); r.register_known::(); r.register_known::(); - r.register_unknown("TLSA" , types::TLSA); - r.register_unknown("SMIMEA" , types::SMIMEA); + r.register_known::(); + r.register_known::(); r.register_unknown("HIP" , types::HIP); r.register_unknown("NINFO" , types::NINFO); r.register_unknown("RKEY" , types::RKEY); r.register_unknown("TALINK" , types::TALINK); r.register_unknown("CDS" , types::CDS); r.register_unknown("CDNSKEY" , types::CDNSKEY); - r.register_unknown("OPENPGPKEY", types::OPENPGPKEY); + r.register_known::(); r.register_unknown("CSYNC" , types::CSYNC); r.register_known::(); r.register_unknown("UINFO" , types::UINFO); @@ -184,10 +184,10 @@ impl Registry { r.register_unknown("L32" , types::L32); r.register_unknown("L64" , types::L64); r.register_unknown("LP" , types::LP); - r.register_unknown("EUI48" , types::EUI48); - r.register_unknown("EUI64" , types::EUI64); - r.register_unknown("TKEY" , types::TKEY); - r.register_unknown("TSIG" , types::TSIG); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); r.register_unknown("IXFR" , types::IXFR); r.register_unknown("AXFR" , types::AXFR); r.register_unknown("MAILB" , types::MAILB); @@ -198,7 +198,7 @@ impl Registry { r.register_unknown("AVC" , types::AVC); r.register_unknown("DOA" , types::DOA); r.register_unknown("TA" , types::TA); - r.register_unknown("DLV" , types::DLV); + r.register_known::(); r.register_known::(); // "ALL" could be an alias for the ANY type? diff --git a/lib/dnsbox-base/src/records/structs.rs b/lib/dnsbox-base/src/records/structs.rs index 7569dfe..1be81d4 100644 --- a/lib/dnsbox-base/src/records/structs.rs +++ b/lib/dnsbox-base/src/records/structs.rs @@ -317,9 +317,7 @@ pub struct SSHFP { fingerprint: HexRemainingBlob, } -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct IPSECKEY; +pub use super::weird_structs::IPSECKEY; #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(ANY)] @@ -351,9 +349,11 @@ pub struct DNSKEY { public_key: Base64RemainingBlob, } -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct DHCID; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(IN)] +pub struct DHCID { + content: Base64RemainingBlob, +} #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(ANY)] @@ -375,13 +375,25 @@ pub struct NSEC3PARAM { salt: HexShortBlob, } -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct TLSA; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(ANY)] +pub struct TLSA { + // TODO: support acronyms from https://tools.ietf.org/html/rfc7218 + cert_usage: u8, + selector: u8, + matching_type: u8, + data: HexRemainingBlob, +} -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct SMIMEA; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(ANY)] +pub struct SMIMEA { + // TODO: support acronyms from https://tools.ietf.org/html/rfc7218 + cert_usage: u8, + selector: u8, + matching_type: u8, + data: HexRemainingBlob, +} // #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] // #[RRClass(?)] @@ -407,9 +419,11 @@ pub struct NSEC3PARAM { // #[RRClass(?)] // pub struct CDNSKEY; -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct OPENPGPKEY; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(ANY)] +pub struct OPENPGPKEY { + public_key: Base64RemainingBlob, +} // #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] // #[RRClass(?)] @@ -453,21 +467,41 @@ pub struct SPF { // #[RRClass(?)] // pub struct LP; -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct EUI48; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(ANY)] +pub struct EUI48 { + addr: EUI48Addr, +} -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct EUI64; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(ANY)] +pub struct EUI64 { + addr: EUI64Addr +} -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct TKEY; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(ANY)] +pub struct TKEY { + algorithm: DnsName, + inception: Time, + expiration: Time, + mode: u16, + error: u16, + key: Base64LongBlob, + other: Base64LongBlob, +} -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(?)] -// pub struct TSIG; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(ANY)] +pub struct TSIG { + algorithm: DnsName, + signed: Time48, + fudge: u16, + mac: Base64LongBlob, + original_id: u16, + error: u16, + other: Base64LongBlob, +} // QTYPEs: IXFR, AXFR, MAILB, MAILA, ANY @@ -490,8 +524,14 @@ pub struct CAA { // pub struct AVC; // pub struct DOA; -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// pub struct DLV; +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] +#[RRClass(ANY)] +pub struct DLV { + key_tag: u16, + algorithm: u8, + digest_type: u8, + digest: HexRemainingBlob, +} // powerdns #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] diff --git a/lib/dnsbox-base/src/records/weird_structs.rs b/lib/dnsbox-base/src/records/weird_structs.rs index 0200528..9ffc5a6 100644 --- a/lib/dnsbox-base/src/records/weird_structs.rs +++ b/lib/dnsbox-base/src/records/weird_structs.rs @@ -2,11 +2,11 @@ use bytes::{Bytes, Buf, BufMut}; use errors::*; use common_types::*; use failure::ResultExt; -use ser::packet::{DnsPacketData, DnsPacketWriteContext}; +use ser::packet::{DnsPacketData, DnsPacketWriteContext, remaining_bytes}; use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext}; use std::fmt; use std::io::Read; -use std::net::Ipv6Addr; +use std::net::{Ipv4Addr, Ipv6Addr}; // deriving RRData will add a unit test to make sure the type is // registered; there must be a records::types::$name `Type` constant @@ -30,7 +30,7 @@ impl DnsPacketData for LOC { } else { Ok(LOC::UnknownVersion{ version: version, - data: ::ser::packet::remaining_bytes(data), + data: remaining_bytes(data), }) } } @@ -182,3 +182,131 @@ impl DnsTextData for A6 { Ok(()) } } + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum IpsecKeyGateway { + None, + Ipv4(Ipv4Addr), + Ipv6(Ipv6Addr), + Name(DnsName), +} + +#[derive(Clone, PartialEq, Eq, Debug, RRData)] +#[RRClass(ANY)] +pub enum IPSECKEY { + Known{ + precedence: u8, + algorithm: u8, + gateway: IpsecKeyGateway, + public_key: Base64RemainingBlob, + }, + UnknownGateway{ + precedence: u8, + gateway_type: u8, + algorithm: u8, + // length of gateway is unknown, can't split gateway and public key + remaining: Bytes, + } +} + +impl DnsPacketData for IPSECKEY { + fn deserialize(data: &mut ::std::io::Cursor) -> Result { + let precedence = u8::deserialize(data)?; + let gateway_type = u8::deserialize(data)?; + let algorithm = u8::deserialize(data)?; + let gateway = match gateway_type { + 0 => IpsecKeyGateway::None, + 1 => IpsecKeyGateway::Ipv4(Ipv4Addr::deserialize(data)?), + 2 => IpsecKeyGateway::Ipv6(Ipv6Addr::deserialize(data)?), + 3 => IpsecKeyGateway::Name(DnsName::deserialize(data)?), + _ => return Ok(IPSECKEY::UnknownGateway{ + precedence, + gateway_type, + algorithm, + remaining: remaining_bytes(data), + }), + }; + Ok(IPSECKEY::Known{ + precedence, + algorithm, + gateway, + public_key: Base64RemainingBlob::deserialize(data)?, + }) + } + + fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + match *self { + IPSECKEY::Known{precedence, algorithm, ref gateway, ref public_key} => { + packet.reserve(3); + packet.put_u8(precedence); + let gateway_type: u8 = match *gateway { + IpsecKeyGateway::None => 0, + IpsecKeyGateway::Ipv4(_) => 1, + IpsecKeyGateway::Ipv6(_) => 2, + IpsecKeyGateway::Name(_) => 3, + }; + packet.put_u8(gateway_type); + packet.put_u8(algorithm); + match *gateway { + IpsecKeyGateway::None => (), + IpsecKeyGateway::Ipv4(ref a) => a.serialize(context, packet)?, + IpsecKeyGateway::Ipv6(ref a) => a.serialize(context, packet)?, + IpsecKeyGateway::Name(ref n) => n.serialize(context, packet)?, + }; + public_key.serialize(context, packet)?; + }, + IPSECKEY::UnknownGateway{precedence, gateway_type, algorithm, ref remaining} => { + packet.reserve(3 + remaining.len()); + packet.put_u8(precedence); + packet.put_u8(gateway_type); + packet.put_u8(algorithm); + packet.put_slice(remaining); + } + } + Ok(()) + } +} + +impl DnsTextData for IPSECKEY { + fn dns_parse(context: &DnsTextContext, data: &mut &str) -> Result { + let precedence = u8::dns_parse(context, data)?; + let gateway_type = u8::dns_parse(context, data)?; + let algorithm = u8::dns_parse(context, data)?; + let gateway = match gateway_type { + 0 => IpsecKeyGateway::None, + 1 => IpsecKeyGateway::Ipv4(Ipv4Addr::dns_parse(context, data)?), + 2 => IpsecKeyGateway::Ipv6(Ipv6Addr::dns_parse(context, data)?), + 3 => IpsecKeyGateway::Name(DnsName::dns_parse(context, data)?), + _ => bail!("unknown gateway type {} for IPSECKEY", gateway_type), + }; + Ok(IPSECKEY::Known{ + precedence, + algorithm, + gateway, + public_key: Base64RemainingBlob::dns_parse(context, data)?, + }) + } + + fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { + match *self { + IPSECKEY::Known{precedence, algorithm, ref gateway, ref public_key} => { + let gateway_type: u8 = match *gateway { + IpsecKeyGateway::None => 0, + IpsecKeyGateway::Ipv4(_) => 1, + IpsecKeyGateway::Ipv6(_) => 2, + IpsecKeyGateway::Name(_) => 3, + }; + write!(f, "{} {} {}", precedence, gateway_type, algorithm)?; + match *gateway { + IpsecKeyGateway::None => (), + IpsecKeyGateway::Ipv4(ref a) => a.dns_format(f)?, + IpsecKeyGateway::Ipv6(ref a) => a.dns_format(f)?, + IpsecKeyGateway::Name(ref n) => n.dns_format(f)?, + }; + public_key.dns_format(f)?; + Ok(()) + }, + IPSECKEY::UnknownGateway{..} => Err(fmt::Error), + } + } +} diff --git a/lib/dnsbox-base/src/ser/packet/mod.rs b/lib/dnsbox-base/src/ser/packet/mod.rs index 4e4560e..8a6ab22 100644 --- a/lib/dnsbox-base/src/ser/packet/mod.rs +++ b/lib/dnsbox-base/src/ser/packet/mod.rs @@ -30,14 +30,18 @@ pub fn remaining_bytes(data: &mut Cursor) -> Bytes { result } +pub fn get_blob(data: &mut Cursor, len: usize) -> Result { + check_enough_data!(data, len, "blob content"); + let pos = data.position() as usize; + let blob = data.get_ref().slice(pos, pos + len); + data.advance(len); + Ok(blob) +} + pub fn short_blob(data: &mut Cursor) -> Result { check_enough_data!(data, 1, "short blob length"); let blob_len = data.get_u8() as usize; - check_enough_data!(data, blob_len, "short blob content"); - let pos = data.position() as usize; - let blob = data.get_ref().slice(pos, pos + blob_len); - data.advance(blob_len); - Ok(blob) + get_blob(data, blob_len) } pub fn write_short_blob(data: &[u8], packet: &mut Vec) -> Result<()> {