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> {
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);
}
Ok(result)
@ -328,7 +328,7 @@ mod tests {
*/
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());
Ok(result)
}

View File

@ -141,6 +141,10 @@ impl DnsName {
if label_offsets.is_empty() {
// root name
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); }
data[0] = 0;
return (data, 0)

View File

@ -17,7 +17,7 @@ impl DnsName {
{
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);
ensure!(new_pos < pos, "Compressed label offset to big");
ensure!(new_pos < pos, "Compressed label offset too big: {} >= {}", new_pos, 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");
for i in 0..window_len {
let mut v = data.get_u8();
for j in 0..7 {
for j in 0..8 {
if 0 != v & 0x80 {
set.insert(Type(window_base + i*8 + j));
}

View File

@ -7,4 +7,7 @@ pub use self::structs::*;
pub use self::unknown::*;
#[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 records::structs;
use ser::{RRData, StaticRRData};
use ser::text::DnsTextContext;
// this should be enough for registered names
const TYPE_NAME_MAX_LEN: usize = 16;
@ -42,6 +43,26 @@ pub fn known_name_to_type(name: &str) -> Option<Type> {
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> {
let registry = registry();
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 parse_rr_data(&self, context: &DnsTextContext, data: &mut &str) -> Result<Box<RRData>>;
}
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>> {
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 {

View File

@ -2,7 +2,7 @@ use bytes::{Bytes, Buf};
use common_types::classes;
use failure::ResultExt;
use records::structs;
use ser::{packet, text, StaticRRData};
use ser::{packet, text, StaticRRData, DnsPacketData};
use std::fmt;
use std::io::Cursor;
@ -55,6 +55,11 @@ fn test_a() {
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>()
where
T: StaticRRData + fmt::Debug + PartialEq
@ -94,6 +99,7 @@ fn test_ds() {
fn test_nsec() {
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 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();
}
@ -115,7 +121,7 @@ fn test_nsec3() {
rrdata_parse::<structs::NSEC3>("1 2 300 - vv").unwrap_err();
// 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]

View File

@ -4,8 +4,9 @@ use common_types::binary::HEXLOWER_PERMISSIVE_ALLOW_WS;
use errors::*;
use failure::{ResultExt, Fail};
use ser::packet::remaining_bytes;
use ser::RRDataPacket;
use ser::text::{DnsTextFormatter, next_field};
use ser::{RRData, RRDataPacket, RRDataText};
use ser::text::{DnsTextFormatter, DnsTextContext, next_field};
use std::borrow::Cow;
use std::fmt;
use std::io::Cursor;
@ -36,16 +37,13 @@ impl UnknownRecord {
let result = HEXLOWER_PERMISSIVE_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid hex: {:?}", data)))?;
ensure!(len == result.len(), "length {} doesn't match length of encoded data {}", len, result.len());
*data = ""; // read all data
Ok(UnknownRecord {
rr_type,
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 {
@ -57,3 +55,29 @@ impl RRDataPacket for UnknownRecord {
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::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>;
}
pub fn deserialize<T>(data: Bytes) -> Result<T>
pub fn deserialize_with<F, O>(data: Bytes, parser: F) -> Result<O>
where
T: DnsPacketData
F: FnOnce(&mut Cursor<Bytes>) -> Result<O>,
{
let mut c = Cursor::new(data);
let result = T::deserialize(&mut c)?;
let result = parser(&mut c)?;
if c.remaining() != 0 {
bail!("data remaining: {}", c.remaining())
bail!("data remaining: {} bytes", c.remaining())
}
Ok(result)
}

View File

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

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

View File

@ -25,7 +25,7 @@ pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
let mut parse_fields = quote!{};
for field in fields {
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
#field_name: DnsPacketData::deserialize(_data).context(#ctx_msg)?,