This commit is contained in:
Stefan Bühler 2017-12-27 12:41:54 +01:00
parent 302300c184
commit e8df03648a
14 changed files with 852 additions and 41 deletions

View File

@ -318,9 +318,9 @@ mod tests {
/* /*
fn deserialize(bytes: &'static [u8]) -> Result<DnsName> { fn deserialize(bytes: &'static [u8]) -> Result<DnsName> {
let result = packet::deserialize::<DnsName>(Bytes::from_static(bytes))?; let result = packet::deserialize_with(Bytes::from_static(bytes), DnsName::deserialize)?;
{ {
let check_result = packet::deserialize::<DnsName>(result.clone().encode()).unwrap(); let check_result = packet::deserialize_with(result.clone().encode(), DnsName::deserialize).unwrap();
assert_eq!(check_result, result); assert_eq!(check_result, result);
} }
Ok(result) Ok(result)
@ -328,7 +328,7 @@ mod tests {
*/ */
fn de_uncompressed(bytes: &'static [u8]) -> Result<DnsName> { fn de_uncompressed(bytes: &'static [u8]) -> Result<DnsName> {
let result = packet::deserialize::<DnsName>(Bytes::from_static(bytes))?; let result = packet::deserialize_with(Bytes::from_static(bytes), DnsName::deserialize)?;
assert_eq!(bytes, result.clone().encode()); assert_eq!(bytes, result.clone().encode());
Ok(result) Ok(result)
} }

View File

@ -141,6 +141,10 @@ impl DnsName {
if label_offsets.is_empty() { if label_offsets.is_empty() {
// root name // root name
let mut data = data.unwrap_or_else(|_| BytesMut::with_capacity(new_len)); let mut data = data.unwrap_or_else(|_| BytesMut::with_capacity(new_len));
if data.len() < new_len {
let add = new_len - data.len();
data.reserve(add);
}
unsafe { data.set_len(new_len); } unsafe { data.set_len(new_len); }
data[0] = 0; data[0] = 0;
return (data, 0) return (data, 0)

View File

@ -17,7 +17,7 @@ impl DnsName {
{ {
ensure!(pos + 1 < data.len(), "not enough data for compressed label"); ensure!(pos + 1 < data.len(), "not enough data for compressed label");
let new_pos = ((label_len as usize & 0x3f) << 8) | (data[pos + 1] as usize); let new_pos = ((label_len as usize & 0x3f) << 8) | (data[pos + 1] as usize);
ensure!(new_pos < pos, "Compressed label offset to big"); ensure!(new_pos < pos, "Compressed label offset too big: {} >= {}", new_pos, pos);
pos = new_pos; pos = new_pos;
} }

View File

@ -87,7 +87,7 @@ impl DnsPacketData for NsecTypeBitmap {
check_enough_data!(data, window_len as usize, "nsec bitmap window length"); check_enough_data!(data, window_len as usize, "nsec bitmap window length");
for i in 0..window_len { for i in 0..window_len {
let mut v = data.get_u8(); let mut v = data.get_u8();
for j in 0..7 { for j in 0..8 {
if 0 != v & 0x80 { if 0 != v & 0x80 {
set.insert(Type(window_base + i*8 + j)); set.insert(Type(window_base + i*8 + j));
} }

View File

@ -7,4 +7,7 @@ pub use self::structs::*;
pub use self::unknown::*; pub use self::unknown::*;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
#[cfg(test)]
mod powerdns_tests;

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@ use common_types::{Class, Type, types};
use errors::*; use errors::*;
use records::structs; use records::structs;
use ser::{RRData, StaticRRData}; use ser::{RRData, StaticRRData};
use ser::text::DnsTextContext;
// this should be enough for registered names // this should be enough for registered names
const TYPE_NAME_MAX_LEN: usize = 16; const TYPE_NAME_MAX_LEN: usize = 16;
@ -42,6 +43,26 @@ pub fn known_name_to_type(name: &str) -> Option<Type> {
Some(t) Some(t)
} }
pub fn deserialize_rr_data(ttl: u32, rr_class: Class, rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Box<RRData>> {
let registry = registry();
match registry.type_parser.get(&rr_type) {
Some(p) => p.deserialize_rr_data(ttl, rr_class, rr_type, data),
None => Ok(Box::new(super::UnknownRecord::deserialize(rr_type, data)?) as _),
}
}
pub fn parse_rr_data(context: &DnsTextContext, data: &mut &str) -> Result<Box<RRData>> {
let registry = registry();
let t = match context.record_type() {
Some(t) => t,
None => bail!("require record type to parse record data"),
};
match registry.type_parser.get(&t) {
Some(p) => p.parse_rr_data(context, data),
None => bail!("unknown type: {}", t),
}
}
pub(crate) fn lookup_type_to_name(rrtype: Type) -> Option<&'static str> { pub(crate) fn lookup_type_to_name(rrtype: Type) -> Option<&'static str> {
let registry = registry(); let registry = registry();
registry.type_names.get(&rrtype).map(|s| s as _) registry.type_names.get(&rrtype).map(|s| s as _)
@ -61,12 +82,18 @@ trait RRDataTypeParse: 'static {
} }
fn deserialize_rr_data(&self, ttl: u32, rr_class: Class, rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Box<RRData>>; fn deserialize_rr_data(&self, ttl: u32, rr_class: Class, rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Box<RRData>>;
fn parse_rr_data(&self, context: &DnsTextContext, data: &mut &str) -> Result<Box<RRData>>;
} }
impl<T: RRData + 'static> RRDataTypeParse for TagRRDataType<T> { impl<T: RRData + 'static> RRDataTypeParse for TagRRDataType<T> {
fn deserialize_rr_data(&self, ttl: u32, rr_class: Class, rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Box<RRData>> { fn deserialize_rr_data(&self, ttl: u32, rr_class: Class, rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Box<RRData>> {
T::deserialize_rr_data(ttl, rr_class, rr_type, data).map(|d| Box::new(d) as _) T::deserialize_rr_data(ttl, rr_class, rr_type, data).map(|d| Box::new(d) as _)
} }
fn parse_rr_data(&self, context: &DnsTextContext, data: &mut &str) -> Result<Box<RRData>> {
T::dns_parse_rr_data(context, data).map(|d| Box::new(d) as _)
}
} }
struct Registry { struct Registry {

View File

@ -2,7 +2,7 @@ use bytes::{Bytes, Buf};
use common_types::classes; use common_types::classes;
use failure::ResultExt; use failure::ResultExt;
use records::structs; use records::structs;
use ser::{packet, text, StaticRRData}; use ser::{packet, text, StaticRRData, DnsPacketData};
use std::fmt; use std::fmt;
use std::io::Cursor; use std::io::Cursor;
@ -55,6 +55,11 @@ fn test_a() {
check::<structs::A>("127.0.0.1", b"\x7f\x00\x00\x01").unwrap(); check::<structs::A>("127.0.0.1", b"\x7f\x00\x00\x01").unwrap();
} }
#[test]
fn test_mx() {
check::<structs::MX>("10 mx.rec.test.", b"\x00\x0a\x02mx\x03rec\x04test\x00").unwrap();
}
fn test_txt_for<T>() fn test_txt_for<T>()
where where
T: StaticRRData + fmt::Debug + PartialEq T: StaticRRData + fmt::Debug + PartialEq
@ -94,6 +99,7 @@ fn test_ds() {
fn test_nsec() { fn test_nsec() {
check::<structs::NSEC>("foo.bar. ", b"\x03foo\x03bar\x00").unwrap(); check::<structs::NSEC>("foo.bar. ", b"\x03foo\x03bar\x00").unwrap();
check::<structs::NSEC>("foo.bar. A NS ", b"\x03foo\x03bar\x00\x00\x01\x60").unwrap(); check::<structs::NSEC>("foo.bar. A NS ", b"\x03foo\x03bar\x00\x00\x01\x60").unwrap();
check::<structs::NSEC>("foo.bar. A NS SOA MX AAAA RRSIG NSEC DNSKEY ", b"\x03foo\x03bar\x00\x00\x07\x62\x01\x00\x08\x00\x03\x80").unwrap();
check::<structs::NSEC>("foo.bar. A NS TYPE256 TYPE65280 ", b"\x03foo\x03bar\x00\x00\x01\x60\x01\x01\x80\xff\x01\x80").unwrap(); check::<structs::NSEC>("foo.bar. A NS TYPE256 TYPE65280 ", b"\x03foo\x03bar\x00\x00\x01\x60\x01\x01\x80\xff\x01\x80").unwrap();
} }
@ -115,7 +121,7 @@ fn test_nsec3() {
rrdata_parse::<structs::NSEC3>("1 2 300 - vv").unwrap_err(); rrdata_parse::<structs::NSEC3>("1 2 300 - vv").unwrap_err();
// invalid (empty) next-hashed values // invalid (empty) next-hashed values
packet::deserialize::<structs::NSEC3>(Bytes::from_static(b"\x01\x02\x01\x2c\x00\x00")).unwrap_err(); packet::deserialize_with(Bytes::from_static(b"\x01\x02\x01\x2c\x00\x00"), structs::NSEC3::deserialize).unwrap_err();
} }
#[test] #[test]

View File

@ -4,8 +4,9 @@ use common_types::binary::HEXLOWER_PERMISSIVE_ALLOW_WS;
use errors::*; use errors::*;
use failure::{ResultExt, Fail}; use failure::{ResultExt, Fail};
use ser::packet::remaining_bytes; use ser::packet::remaining_bytes;
use ser::RRDataPacket; use ser::{RRData, RRDataPacket, RRDataText};
use ser::text::{DnsTextFormatter, next_field}; use ser::text::{DnsTextFormatter, DnsTextContext, next_field};
use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::io::Cursor; use std::io::Cursor;
@ -36,16 +37,13 @@ impl UnknownRecord {
let result = HEXLOWER_PERMISSIVE_ALLOW_WS.decode(data.as_bytes()) let result = HEXLOWER_PERMISSIVE_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid hex: {:?}", data)))?; .with_context(|e| e.context(format!("invalid hex: {:?}", data)))?;
ensure!(len == result.len(), "length {} doesn't match length of encoded data {}", len, result.len()); ensure!(len == result.len(), "length {} doesn't match length of encoded data {}", len, result.len());
*data = ""; // read all data
Ok(UnknownRecord { Ok(UnknownRecord {
rr_type, rr_type,
raw: result.into(), raw: result.into(),
}) })
} }
pub fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "TYPE{} \\# {} {}", self.rr_type.0, self.raw.len(), HEXLOWER_PERMISSIVE_ALLOW_WS.encode(&self.raw))
}
} }
impl RRDataPacket for UnknownRecord { impl RRDataPacket for UnknownRecord {
@ -57,3 +55,29 @@ impl RRDataPacket for UnknownRecord {
self.rr_type self.rr_type
} }
} }
impl RRDataText for UnknownRecord {
fn dns_parse_rr_data(context: &DnsTextContext, data: &mut &str) -> Result<Self>
where
Self: Sized,
{
let t = match context.record_type() {
Some(t) => t,
None => bail!("must parse DNS record with record type context"),
};
UnknownRecord::dns_parse(t, data)
}
// format might fail if there is no (known) text representation.
fn dns_format_rr_data(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "\\# {} {}", self.raw.len(), HEXLOWER_PERMISSIVE_ALLOW_WS.encode(&self.raw))
}
fn rr_type_txt(&self) -> Cow<'static, str> {
Cow::Owned(self.rr_type.generic_name())
}
}
impl RRData for UnknownRecord {
}

View File

@ -4,4 +4,4 @@ mod rrdata;
pub use self::packet::DnsPacketData; pub use self::packet::DnsPacketData;
pub use self::text::DnsTextData; pub use self::text::DnsTextData;
pub use self::rrdata::{RRDataPacket, RRData, StaticRRData}; pub use self::rrdata::{RRDataPacket, RRDataText, RRData, StaticRRData};

View File

@ -8,14 +8,14 @@ pub trait DnsPacketData: Sized {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self>; fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self>;
} }
pub fn deserialize<T>(data: Bytes) -> Result<T> pub fn deserialize_with<F, O>(data: Bytes, parser: F) -> Result<O>
where where
T: DnsPacketData F: FnOnce(&mut Cursor<Bytes>) -> Result<O>,
{ {
let mut c = Cursor::new(data); let mut c = Cursor::new(data);
let result = T::deserialize(&mut c)?; let result = parser(&mut c)?;
if c.remaining() != 0 { if c.remaining() != 0 {
bail!("data remaining: {}", c.remaining()) bail!("data remaining: {} bytes", c.remaining())
} }
Ok(result) Ok(result)
} }

View File

@ -39,15 +39,12 @@ pub trait RRDataText {
// format might fail if there is no (known) text representation. // format might fail if there is no (known) text representation.
fn dns_format_rr_data(&self, f: &mut DnsTextFormatter) -> fmt::Result; fn dns_format_rr_data(&self, f: &mut DnsTextFormatter) -> fmt::Result;
fn rr_type_txt(&self) -> Cow<'static, str> { fn rr_type_txt(&self) -> Cow<'static, str>;
unimplemented!()
}
// (type, rrdata) // (type, rrdata)
fn text(&self) -> Result<(String, String)> { fn text(&self) -> Result<(String, String)> {
use std::fmt::Write;
let mut buf = String::new(); let mut buf = String::new();
match write!(&mut buf, "{}", DnsDisplayRR(self)) { match self.dns_format_rr_data(&mut DnsTextFormatter::new(&mut buf)) {
Ok(()) => { Ok(()) => {
return Ok((self.rr_type_txt().into(), buf)) return Ok((self.rr_type_txt().into(), buf))
}, },
@ -75,14 +72,9 @@ impl<T: DnsTextData + StaticRRData> RRDataText for T {
fn dns_format_rr_data(&self, f: &mut DnsTextFormatter) -> fmt::Result { fn dns_format_rr_data(&self, f: &mut DnsTextFormatter) -> fmt::Result {
self.dns_format(f) self.dns_format(f)
} }
}
#[derive(Debug)] fn rr_type_txt(&self) -> Cow<'static, str> {
pub struct DnsDisplayRR<'a, T: RRDataText + ?Sized + 'a>(pub &'a T); Cow::Borrowed(T::NAME)
impl<'a, T: RRDataText + ?Sized + 'a> fmt::Display for DnsDisplayRR<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.dns_format_rr_data(&mut DnsTextFormatter::new(f))
} }
} }

View File

@ -54,15 +54,15 @@ pub fn escape(data: &[u8]) -> String {
/// Each call to write!() makes sure a space is emitted to separate it /// Each call to write!() makes sure a space is emitted to separate it
/// from previous fields. /// from previous fields.
pub struct DnsTextFormatter<'a, 'b: 'a> { pub struct DnsTextFormatter<'a> {
f: &'a mut fmt::Formatter<'b>, w: &'a mut fmt::Write,
need_space: bool, need_space: bool,
} }
impl<'a, 'b> DnsTextFormatter<'a, 'b> { impl<'a> DnsTextFormatter<'a> {
pub fn new(f: &'a mut fmt::Formatter<'b>) -> Self { pub fn new(w: &'a mut fmt::Write) -> Self {
DnsTextFormatter { DnsTextFormatter {
f: f, w: w,
need_space: false, need_space: false,
} }
} }
@ -70,7 +70,7 @@ impl<'a, 'b> DnsTextFormatter<'a, 'b> {
/// make sure a field separator was emitted /// make sure a field separator was emitted
pub fn next_field(&mut self) -> fmt::Result { pub fn next_field(&mut self) -> fmt::Result {
if self.need_space { if self.need_space {
write!(self.f, " ")?; write!(self.w, " ")?;
self.need_space = false; self.need_space = false;
} }
Ok(()) Ok(())
@ -83,13 +83,13 @@ impl<'a, 'b> DnsTextFormatter<'a, 'b> {
/// direct access to underlying output; you'll need to call /// direct access to underlying output; you'll need to call
/// `next_field` and `end_field` manually. /// `next_field` and `end_field` manually.
pub fn inner(&mut self) -> &mut fmt::Formatter<'b> { pub fn inner(&mut self) -> &mut fmt::Write {
self.f self.w
} }
pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
self.next_field()?; self.next_field()?;
self.f.write_fmt(args)?; self.w.write_fmt(args)?;
self.end_field(); self.end_field();
Ok(()) Ok(())
} }

View File

@ -25,7 +25,7 @@ pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
let mut parse_fields = quote!{}; let mut parse_fields = quote!{};
for field in fields { for field in fields {
let field_name = field.ident.as_ref().unwrap(); let field_name = field.ident.as_ref().unwrap();
let ctx_msg = format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name)); let ctx_msg = format!("failed parsing field {}::{}", name, field_name);
parse_fields = quote!{#parse_fields parse_fields = quote!{#parse_fields
#field_name: DnsPacketData::deserialize(_data).context(#ctx_msg)?, #field_name: DnsPacketData::deserialize(_data).context(#ctx_msg)?,