This commit is contained in:
Stefan Bühler 2017-12-29 22:25:43 +01:00
parent 503bc29fcf
commit 36ee9e704a
10 changed files with 435 additions and 20 deletions

View File

@ -116,11 +116,11 @@ impl DnsTextData for Base64LongBlob {
}
}
// No length byte (or restriction), just all data to end of record. uses
// base64 encoding for text representation, whitespace allowed, padding
// required.
//
// No following field allowed, i.e. last field in the record.
/// No length byte (or restriction), just all data to end of record. uses
/// base64 encoding for text representation, whitespace allowed, padding
/// required.
///
/// No following field allowed, i.e. last field in the record.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Base64RemainingBlob(Bytes);
@ -154,11 +154,13 @@ impl DnsTextData for Base64RemainingBlob {
}
}
/// No length byte (or restriction), just all data to end of record. uses
/// hex encoding for text representation, whitespace allowed.
///
/// No following field allowed, i.e. last field in the record.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct HexRemainingBlob(Bytes);
// No length byte, just all data to end of record. uses hex encoding for
// text representation (spaces are ignored - last field in record).
impl DnsPacketData for HexRemainingBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(HexRemainingBlob(remaining_bytes(data)))

View File

@ -200,6 +200,13 @@ impl DnsPacketData for DnsName {
#[derive(Clone)]
pub struct DnsCompressedName(pub DnsName);
impl DnsCompressedName {
/// Create new name representing the DNS root (".")
pub fn new_root() -> Self {
DnsCompressedName(DnsName::new_root())
}
}
impl Deref for DnsCompressedName {
type Target = DnsName;

View File

@ -1,12 +1,14 @@
use byteorder::ByteOrder;
use bytes::{Bytes, Buf, BufMut, BigEndian};
use common_types::{Type, Class, DnsCompressedName};
use common_types::{Type, Class, DnsCompressedName, types};
use errors::*;
use ser::RRData;
use ser::packet::{DnsPacketData, DnsPacketWriteContext};
use std::io::Cursor;
use records::registry::deserialize_rr_data;
pub mod opt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum QueryResponse {
Query,
@ -164,16 +166,40 @@ pub struct DnsPacket {
pub answer: Vec<Resource>,
pub authority: Vec<Resource>,
pub additional: Vec<Resource>,
pub opt: Option<opt::OptResult<opt::Opt>>,
}
impl DnsPacket {
pub fn to_bytes(&self) -> Result<Vec<u8>> {
/// also serializes OPT before conversion if present
pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
if self.opt.is_some() {
// delete other OPTs, so only call it if there is a "new" OPT
self.serialize_opt()?;
}
let mut buf = Vec::new();
let mut ctx = DnsPacketWriteContext::new();
ctx.enable_compression();
self.serialize(&mut ctx, &mut buf)?;
Ok(buf)
}
/// overwrites existing OPT records, and serializes a new one (if
/// self.opt is not None)
pub fn serialize_opt(&mut self) -> Result<()> {
// delete all additional OPT records
self.additional.retain(|r| r.data.rr_type() != types::OPT);
match self.opt.take() {
Some(Err(e)) => bail!("can't serialize broken OPT: {:?}", e),
Some(Ok(opt)) => {
self.additional.push(opt.serialize()?);
},
None => (),
}
Ok(())
}
}
impl Default for DnsPacket {
@ -185,6 +211,7 @@ impl Default for DnsPacket {
answer: Vec::new(),
authority: Vec::new(),
additional: Vec::new(),
opt: None,
}
}
}
@ -192,14 +219,31 @@ impl Default for DnsPacket {
impl DnsPacketData for DnsPacket {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let header = DnsHeader::deserialize(data)?;
Ok(DnsPacket {
let mut p = DnsPacket {
id: header.id,
flags: header.flags,
question: (0..header.qdcount).map(|_| Question::deserialize(data)).collect::<Result<Vec<_>>>()?,
answer: (0..header.ancount).map(|_| Resource::deserialize(data)).collect::<Result<Vec<_>>>()?,
authority: (0..header.nscount).map(|_| Resource::deserialize(data)).collect::<Result<Vec<_>>>()?,
additional: (0..header.arcount).map(|_| Resource::deserialize(data)).collect::<Result<Vec<_>>>()?,
})
opt: None,
};
let mut opt_resource_ndx = None;
for (i, r) in p.additional.iter().enumerate() {
if r.data.rr_type() == types::OPT {
ensure!(opt_resource_ndx.is_none(), "multiple OPT resource records");
opt_resource_ndx = Some(i);
}
}
if let Some(ndx) = opt_resource_ndx {
let opt_rr = p.additional.remove(ndx);
p.opt = Some(opt::Opt::deserialize(&opt_rr)?);
}
Ok(p)
}
fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec<u8>) -> Result<()> {

View File

@ -0,0 +1,285 @@
use byteorder::ByteOrder;
use bytes::{Bytes, Buf, BufMut, BigEndian};
use common_types::{DnsCompressedName, Class, types};
use errors::*;
use packet::Resource;
use records::UnknownRecord;
use ser::packet::{DnsPacketData, DnsPacketWriteContext, get_blob, remaining_bytes};
use std::io::{Cursor, Read};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum OptError {
UnknownVersion,
InvalidData,
}
/// parsing OPT can fail without having to throw away the packet
/// completely; just fail in some way
///
/// TODO: define some useful error...
pub type OptResult<T> = ::std::result::Result<T, OptError>;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct OptFlag(pub u16);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct OptFlags(pub u16);
impl ::std::ops::BitOr<OptFlag> for OptFlags {
type Output = bool;
fn bitor(self, rhs: OptFlag) -> Self::Output {
0 != self.0 & rhs.0
}
}
impl ::std::ops::BitOr<OptFlags> for OptFlag {
type Output = bool;
fn bitor(self, rhs: OptFlags) -> Self::Output {
0 != self.0 & rhs.0
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum DnsOption {
/// DNS Name Server Identifier (0x0003)
NSID(Bytes),
// DAU (0x0005)
// DHU (0x0006)
// N3U (0x0007)
// edns-client-subnet (0x0008)
ClientSubnet {
source_prefix_length: u8,
scope_prefix_length: u8,
addr: IpAddr,
},
// EDNS EXPIRE (0x0009)
// COOKIE (0x000a)
// edns-tcp-keepalive (0x000b)
// Padding (0x000c)
// CHAIN (0x000d)
// edns-key-tag (0x000e)
// DeviceID (26946 = 0x6942)
Unknown {
code: u16,
data: Bytes,
}
}
impl DnsOption {
fn parse_client_subnet(data: &mut Cursor<Bytes>) -> Result<Self> {
let addr_family = u16::deserialize(data)?;
let source_prefix_length = u8::deserialize(data)?;
let scope_prefix_length = u8::deserialize(data)?;
let addr_prefix_len = ((source_prefix_length + 7) / 8) as usize;
ensure!(scope_prefix_length <= source_prefix_length, "scope prefix {} > source prefix {}", scope_prefix_length, source_prefix_length);
let addr = match addr_family {
1 => {
ensure!(source_prefix_length <= 32, "invalid prefix for IPv4");
let mut o = [0u8; 4];
data.read_exact(&mut o[0..addr_prefix_len])?;
if 0 != source_prefix_length % 8 {
let mask = 0xff >> (source_prefix_length % 8);
ensure!(0 == o[addr_prefix_len - 1] & mask, "non-zero padding");
}
IpAddr::V4(Ipv4Addr::from(o))
},
2 => {
ensure!(source_prefix_length <= 128, "invalid prefix for IPv6");
let mut o = [0u8; 16];
data.read_exact(&mut o[0..addr_prefix_len])?;
if 0 != source_prefix_length % 8 {
let mask = 0xff >> (source_prefix_length % 8);
ensure!(0 == o[addr_prefix_len - 1] & mask, "non-zero padding");
}
IpAddr::V6(Ipv6Addr::from(o))
},
_ => {
bail!("unknown address family {}", addr_family);
},
};
Ok(DnsOption::ClientSubnet{
source_prefix_length,
scope_prefix_length,
addr,
})
}
fn parse_opt(code: u16, opt_data: Bytes) -> Result<Self> {
let mut data = Cursor::new(opt_data);
let result = (|| Ok(match code {
0x0003 => DnsOption::NSID(remaining_bytes(&mut data)),
0x0008 => DnsOption::parse_client_subnet(&mut data)?,
_ => bail!("unknown option {}", code),
}))()?;
ensure!(!data.has_remaining(), "option data remaining: {} bytes", data.remaining());
Ok(result)
}
fn write_opt_data(&self, _context: &mut DnsPacketWriteContext, packet: &mut Vec<u8>) -> Result<()> {
match *self {
DnsOption::NSID(ref id) => {
packet.reserve(id.len());
packet.put_slice(id);
},
DnsOption::ClientSubnet{source_prefix_length, scope_prefix_length, ref addr} => {
let addr_prefix_len = ((source_prefix_length + 7) / 8) as usize;
packet.reserve(4 + addr_prefix_len);
packet.put_u16::<BigEndian>(match *addr {
IpAddr::V4(_) => 1,
IpAddr::V6(_) => 2,
});
packet.put_u8(source_prefix_length);
packet.put_u8(scope_prefix_length);
match *addr {
IpAddr::V4(ref addr) => {
let o = addr.octets();
packet.put_slice(&o[..addr_prefix_len]);
},
IpAddr::V6(ref addr) => {
let o = addr.octets();
packet.put_slice(&o[..addr_prefix_len]);
},
}
},
DnsOption::Unknown{ref data, ..} => {
packet.reserve(data.len());
packet.put_slice(data);
},
}
Ok(())
}
}
impl DnsPacketData for DnsOption {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let code = u16::deserialize(data)?;
let opt_len = u16::deserialize(data)? as usize;
let opt_data = get_blob(data, opt_len)?;
DnsOption::parse_opt(code, opt_data.clone()).or_else(|_| {
Ok(DnsOption::Unknown{
code: code,
data: opt_data,
})
})
}
fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec<u8>) -> Result<()> {
let code: u16 = match *self {
DnsOption::NSID(_) => 0x0003,
DnsOption::ClientSubnet{..} => 0x0008,
DnsOption::Unknown{code, ..} => code,
};
code.serialize(context, packet)?;
let opt_len_pos = packet.len();
packet.reserve(2);
packet.put_u16::<BigEndian>(0); // stub
let opt_start = packet.len();
self.write_opt_data(context, packet)?;
let opt_end = packet.len();
let opt_len = opt_end - opt_start;
ensure!(opt_len < 0x1_0000, "OPTION DATA too big");
// now patch length
BigEndian::write_u16(&mut packet[opt_len_pos..][..2], opt_len as u16);
Ok(())
}
}
pub const DNSSEC_OK: OptFlag = OptFlag(0x8000);
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Opt {
// "CLASS"
pub udp_payload_size: u16,
// "TTL"
pub extended_rcode_high: u8,
pub version: u8,
pub flags: OptFlags,
// RDATA
pub options: Vec<DnsOption>,
}
impl Opt {
fn deserialize_options(ur: &UnknownRecord) -> Result<Vec<DnsOption>> {
let mut data = Cursor::new(ur.raw().clone());
let mut options = Vec::new();
while data.has_remaining() {
options.push(DnsOption::deserialize(&mut data)?);
}
Ok(options)
}
pub fn deserialize(r: &Resource) -> Result<OptResult<Self>> {
let udp_payload_size = r.class.0;
let extended_rcode_high = (r.ttl >> 24) as u8;
let version = (r.ttl >> 16) as u8;
let flags = OptFlags(r.ttl as u16);
if version > 0 { return Ok(Err(OptError::UnknownVersion)); }
let ur = match r.data.as_any().downcast_ref::<UnknownRecord>() {
Some(ur) => ur,
None => bail!("need to parse OPT from UnknownRecord"),
};
let options = match Opt::deserialize_options(ur) {
Ok(options) => options,
Err(_) => return Ok(Err(OptError::InvalidData)),
};
Ok(Ok(Opt {
udp_payload_size,
extended_rcode_high,
version,
flags,
options,
}))
}
pub fn serialize(&self) -> Result<Resource> {
let mut data = Vec::new();
let mut ctx = DnsPacketWriteContext::new();
for o in &self.options {
o.serialize(&mut ctx, &mut data)?;
}
let ur = UnknownRecord::new(types::OPT, data.into());
let ttl = ((self.extended_rcode_high as u32) << 24)
| ((self.version as u32) << 16)
| self.flags.0 as u32;
Ok(Resource{
name: DnsCompressedName::new_root(),
class: Class(self.udp_payload_size),
ttl: ttl,
data: Box::new(ur) as _,
})
}
}
impl Default for Opt {
fn default() -> Self {
Opt {
udp_payload_size: 500,
extended_rcode_high: 0,
version: 0,
flags: OptFlags(0),
options: Vec::new(),
}
}
}

View File

@ -4,13 +4,14 @@ use bytes::Bytes;
use common_types::{DnsName, DnsCompressedName, Class, Type, types, classes};
use errors::*;
use packet::*;
use records::{UnknownRecord, registry};
use packet::opt::{Opt, DnsOption};
use records::{UnknownRecord, registry, A};
use ser::{RRData, RRDataText, text};
use ser::packet::{DnsPacketData, deserialize_with};
use std::io::Cursor;
fn fake_packet(rrtype: Type, raw: &[u8]) -> Bytes {
let p = DnsPacket{
let mut p = DnsPacket{
question: vec![
Question {
qname: ".".parse().unwrap(),
@ -55,7 +56,7 @@ fn get_first_answer_rdata(packet: Bytes) -> Result<Bytes> {
}
fn serialized_answer(rrdata: Box<RRData>) -> Result<Bytes> {
let p = DnsPacket{
let mut p = DnsPacket{
question: vec![
Question {
qname: ".".parse().unwrap(),
@ -841,3 +842,57 @@ fn test_invalid_data_checks() {
check_invalid_zone(types::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789012.");
check_invalid_zone(types::SOA, "ns.rec.test hostmaster.test.rec 20130512010 3600 3600 604800 120"); // too long serial
}
#[test]
fn test_opt_record_in() {
let p = deserialize_with(
Bytes::from_static(b"\xf0\x01\x01\x00\x00\x01\x00\x01\x00\x00\x00\x01\x03www\x08powerdns\x03com\x00\x00\x01\x00\x01\x03www\x08powerdns\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x10\x00\x04\x7f\x00\x00\x01\x00\x00\x29\x05\x00\x00\x00\x00\x00\x00\x0c\x00\x03\x00\x08powerdns"),
DnsPacket::deserialize
).unwrap();
let opt = p.opt.unwrap().unwrap();
assert_eq!(opt.udp_payload_size, 1280);
assert_eq!(opt.options, vec![
DnsOption::NSID(Bytes::from_static(b"powerdns")),
]);
}
#[test]
fn test_opt_record_out() {
let mut p = DnsPacket{
id: 0xf001,
flags: DnsHeaderFlags {
recursion_desired: true,
.. Default::default()
},
question: vec![
Question {
qname: "www.powerdns.com.".parse().unwrap(),
qtype: types::A,
qclass: classes::IN,
}
],
answer: vec![
Resource {
name: "www.powerdns.com.".parse().unwrap(),
class: classes::IN,
ttl: 16,
data: Box::new(A { addr: "127.0.0.1".parse().unwrap() }),
}
],
opt: Some(Ok(Opt {
udp_payload_size: 1280,
options: vec![
DnsOption::NSID(Bytes::from_static(b"powerdns")),
],
.. Default::default()
})),
.. Default::default()
};
assert_eq!(
&p.to_bytes().unwrap() as &[u8],
b"\xf0\x01\x01\x00\x00\x01\x00\x01\x00\x00\x00\x01\x03www\x08powerdns\x03com\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x10\x00\x04\x7f\x00\x00\x01\x00\x00\x29\x05\x00\x00\x00\x00\x00\x00\x0c\x00\x03\x00\x08powerdns" as &[u8]
);
}

View File

@ -10,7 +10,7 @@ use std::net::{Ipv4Addr, Ipv6Addr};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(IN)]
pub struct A {
addr: Ipv4Addr,
pub addr: Ipv4Addr,
}
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]

View File

@ -48,6 +48,13 @@ where
let d2: T = rrdata_parse(txt).context("couldn't parse text record")?;
ensure!(d1 == d2, "decoded data not equal: {:?} != {:?}", d1, d2);
let d1_text = d1.text().unwrap();
let d2_text = d2.text().unwrap();
let canon_text = (T::NAME.to_owned(), canon.into());
ensure!(d1_text == canon_text, "re-formatted binary record not equal to canonical representation: {:?} != {:?}", d1_text, canon_text);
ensure!(d2_text == canon_text, "re-formatted text record not equal to canonical representation: {:?} != {:?}", d2_text, canon_text);
Ok(())
}

View File

@ -24,6 +24,10 @@ impl UnknownRecord {
}
}
pub fn raw(&self) -> &Bytes {
&self.raw
}
pub fn deserialize(rr_type: Type, data: &mut ::std::io::Cursor<Bytes>) -> Result<Self> {
Ok(UnknownRecord::new(rr_type, remaining_bytes(data)))
}
@ -89,4 +93,8 @@ impl RRData for UnknownRecord {
fn clone_box(&self) -> Box<RRData> {
Box::new(self.clone()) as _
}
fn as_any(&self) -> &::std::any::Any {
self as _
}
}

View File

@ -1,12 +1,13 @@
use bytes::Bytes;
use common_types::{Class, Type, classes};
use errors::*;
use records::UnknownRecord;
use ser::packet::{DnsPacketData, DnsPacketWriteContext};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext};
use std::any::Any;
use std::borrow::Cow;
use std::fmt;
use std::io::Cursor;
use records::UnknownRecord;
pub trait RRDataPacket {
fn deserialize_rr_data(ttl: u32, rr_class: Class, rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Self>
@ -93,8 +94,10 @@ impl<T: DnsTextData + StaticRRData> RRDataText for T {
}
}
pub trait RRData: RRDataPacket + RRDataText + fmt::Debug {
pub trait RRData: RRDataPacket + RRDataText + fmt::Debug + 'static {
fn clone_box(&self) -> Box<RRData>;
fn as_any(&self) -> &Any;
}
impl Clone for Box<RRData> {

View File

@ -76,6 +76,10 @@ pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
fn clone_box(&self) -> Box<::dnsbox_base::ser::RRData> {
Box::new(self.clone() as #name) as _
}
fn as_any(&self) -> &::std::any::Any {
self as _
}
}
impl ::dnsbox_base::ser::StaticRRData for #name {