diff --git a/lib/dnsbox-base/src/common_types/name/mod.rs b/lib/dnsbox-base/src/common_types/name/mod.rs index 36b7e22..e3e121f 100644 --- a/lib/dnsbox-base/src/common_types/name/mod.rs +++ b/lib/dnsbox-base/src/common_types/name/mod.rs @@ -79,15 +79,6 @@ pub struct DnsName { total_len: u8, } -/// names that should be written in canonical form for DNSSEC according -/// to https://tools.ietf.org/html/rfc4034#section-6.2 -/// -/// TODO: make it a newtype. -/// -/// DnsCompressedName always needs to be written in canonical form for -/// DNSSEC. -pub type DnsCanonicalName = DnsName; - impl DnsName { /// Create new name representing the DNS root (".") pub fn new_root() -> Self { @@ -274,6 +265,15 @@ impl DnsPacketData for DnsCompressedName { } } +/// names that should be written in canonical form for DNSSEC according +/// to https://tools.ietf.org/html/rfc4034#section-6.2 +/// +/// TODO: make it a newtype. +/// +/// DnsCompressedName always needs to be written in canonical form for +/// DNSSEC. +pub type DnsCanonicalName = DnsName; + /// Iterator type for [`DnsName::labels`] /// /// [`DnsName::labels`]: struct.DnsName.html#method.labels diff --git a/lib/dnsbox-base/src/common_types/name/name_text_parser.rs b/lib/dnsbox-base/src/common_types/name/name_text_parser.rs index 9e3ab0d..9e52d02 100644 --- a/lib/dnsbox-base/src/common_types/name/name_text_parser.rs +++ b/lib/dnsbox-base/src/common_types/name/name_text_parser.rs @@ -1,5 +1,5 @@ use super::*; -use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field, quoted}; +use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field, quoted, parse_with}; impl DnsName { /// Parse text representation of a domain name @@ -75,6 +75,14 @@ impl DnsTextData for DnsName { } } +impl ::std::str::FromStr for DnsName { + type Err = ::failure::Error; + + fn from_str(s: &str) -> Result { + parse_with(s, |data| DnsName::dns_parse(&DnsTextContext::new(), data)) + } +} + impl DnsCompressedName { /// Parse text representation of a domain name pub fn parse(context: &DnsTextContext, value: &str) -> Result @@ -93,3 +101,11 @@ impl DnsTextData for DnsCompressedName { self.0.dns_format(f) } } + +impl ::std::str::FromStr for DnsCompressedName { + type Err = ::failure::Error; + + fn from_str(s: &str) -> Result { + parse_with(s, |data| DnsCompressedName::dns_parse(&DnsTextContext::new(), data)) + } +} diff --git a/lib/dnsbox-base/src/common_types/uri.rs b/lib/dnsbox-base/src/common_types/uri.rs index e4a28e9..ecae57d 100644 --- a/lib/dnsbox-base/src/common_types/uri.rs +++ b/lib/dnsbox-base/src/common_types/uri.rs @@ -23,7 +23,7 @@ impl DnsPacketData for UriText { } fn serialize(&self, _context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { - ensure!(self.0.is_empty(), "URI must not be empty"); + ensure!(!self.0.is_empty(), "URI must not be empty"); packet.reserve(self.0.len()); packet.put_slice(&self.0); Ok(()) diff --git a/lib/dnsbox-base/src/lib.rs b/lib/dnsbox-base/src/lib.rs index f91621a..3e3f8f7 100644 --- a/lib/dnsbox-base/src/lib.rs +++ b/lib/dnsbox-base/src/lib.rs @@ -15,6 +15,7 @@ mod unsafe_ops; #[macro_use] pub mod errors; pub mod common_types; +pub mod packet; pub mod records; pub mod ser; diff --git a/lib/dnsbox-base/src/packet/mod.rs b/lib/dnsbox-base/src/packet/mod.rs new file mode 100644 index 0000000..222ffdf --- /dev/null +++ b/lib/dnsbox-base/src/packet/mod.rs @@ -0,0 +1,233 @@ +use byteorder::ByteOrder; +use bytes::{Bytes, Buf, BufMut, BigEndian}; +use common_types::{Type, Class, DnsCompressedName}; +use errors::*; +use ser::RRData; +use ser::packet::{DnsPacketData, DnsPacketWriteContext}; +use std::io::Cursor; +use records::registry::deserialize_rr_data; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum QueryResponse { + Query, + Response, +} + +impl Default for QueryResponse { + fn default() -> Self { + QueryResponse::Query + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub struct DnsHeaderFlags { + pub qr: QueryResponse, + pub opcode: u8, // 0...15 + pub authoritative_answer: bool, + pub truncation: bool, + pub recursion_desired: bool, + pub recursion_available: bool, + pub reserved_bit9: bool, + pub authentic_data: bool, + pub checking_disabled: bool, + pub rcode: u8, // 0...15 +} + +impl DnsPacketData for DnsHeaderFlags { + fn deserialize(data: &mut Cursor) -> Result { + let raw = u16::deserialize(data)?; + let qr = if 0 == raw & 0x8000 { QueryResponse::Query } else { QueryResponse::Response }; + let opcode = 0xf & (raw >> 11) as u8; + let authoritative_answer = 0 != raw & 0x0400; + let truncation = 0 != raw & 0x0200; + let recursion_desired = 0 != raw & 0x0100; + let recursion_available = 0 != raw & 0x0080; + let reserved_bit9 = 0 != raw & 0x0040; + let authentic_data = 0 != raw & 0x0020; + let checking_disabled = 0 != raw & 0x0010; + let rcode = 0xf & raw as u8; + Ok(DnsHeaderFlags{ + qr, + opcode, + authoritative_answer, + truncation, + recursion_desired, + recursion_available, + reserved_bit9, + authentic_data, + checking_disabled, + rcode, + }) + } + + fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + let flags: u16 = 0 + | match self.qr { + QueryResponse::Query => 0, + QueryResponse::Response => 1, + } + | (((0xf & self.opcode) as u16) << 11) + | if self.authoritative_answer { 0x0400 } else { 0 } + | if self.truncation { 0x0200 } else { 0 } + | if self.recursion_desired { 0x0100 } else { 0 } + | if self.recursion_available { 0x0080 } else { 0 } + | if self.reserved_bit9 { 0x0040 } else { 0 } + | if self.authentic_data { 0x0020 } else { 0 } + | if self.checking_disabled { 0x0010 } else { 0 } + | (0xf & self.rcode) as u16 + ; + flags.serialize(context, packet) + } +} + +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData)] +pub struct DnsHeader { + pub id: u16, + pub flags: DnsHeaderFlags, + pub qdcount: u16, + pub ancount: u16, + pub nscount: u16, + pub arcount: u16, +} + +#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData)] +pub struct Question { + pub qname: DnsCompressedName, + pub qtype: Type, + pub qclass: Class, +} + +#[derive(Clone, Debug)] +pub struct Resource { + pub name: DnsCompressedName, + pub class: Class, + pub ttl: u32, + pub data: Box, +} + +impl DnsPacketData for Resource { + fn deserialize(data: &mut Cursor) -> Result { + let name = DnsCompressedName::deserialize(data)?; + let rr_type = Type::deserialize(data)?; + let class = Class::deserialize(data)?; + let ttl = u32::deserialize(data)?; + + let rdlength = u16::deserialize(data)? as usize; + check_enough_data!(data, rdlength, "RDATA"); + let pos = data.position() as usize; + let rrdata_from0 = data.get_ref().slice(0, pos + rdlength); + data.advance(rdlength); + let mut rrdata = Cursor::new(rrdata_from0); + rrdata.advance(pos); + let rd = deserialize_rr_data(ttl, class, rr_type, &mut rrdata)?; + + ensure!(!rrdata.has_remaining(), "data remaining: {} bytes", rrdata.remaining()); + + Ok(Resource{ + name, + class, + ttl, + data: rd, + }) + } + + fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + self.name.serialize(context, packet)?; + let rrtype = self.data.rr_type(); + rrtype.serialize(context, packet)?; + self.class.serialize(context, packet)?; + self.ttl.serialize(context, packet)?; + + let rdlen_pos = packet.len(); + packet.reserve(2); + packet.put_u16::(0); // stub + + let rd_start = packet.len(); + self.data.serialize_rr_data(context, packet)?; + let rd_end = packet.len(); + let rdlen = rd_end - rd_start; + + ensure!(rdlen < 0x1_0000, "RDATA too big"); + + // now patch length + BigEndian::write_u16(&mut packet[rdlen_pos..][..2], rdlen as u16); + + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct DnsPacket { + pub id: u16, + pub flags: DnsHeaderFlags, + pub question: Vec, + pub answer: Vec, + pub authority: Vec, + pub additional: Vec, +} + +impl DnsPacket { + pub fn to_bytes(&self) -> Result> { + let mut buf = Vec::new(); + let mut ctx = DnsPacketWriteContext::new(); + ctx.enable_compression(); + self.serialize(&mut ctx, &mut buf)?; + Ok(buf) + } +} + +impl Default for DnsPacket { + fn default() -> Self { + DnsPacket{ + id: 0, + flags: DnsHeaderFlags::default(), + question: Vec::new(), + answer: Vec::new(), + authority: Vec::new(), + additional: Vec::new(), + } + } +} + +impl DnsPacketData for DnsPacket { + fn deserialize(data: &mut Cursor) -> Result { + let header = DnsHeader::deserialize(data)?; + Ok(DnsPacket { + id: header.id, + flags: header.flags, + question: (0..header.qdcount).map(|_| Question::deserialize(data)).collect::>>()?, + answer: (0..header.ancount).map(|_| Resource::deserialize(data)).collect::>>()?, + authority: (0..header.nscount).map(|_| Resource::deserialize(data)).collect::>>()?, + additional: (0..header.arcount).map(|_| Resource::deserialize(data)).collect::>>()?, + }) + } + + fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + ensure!(self.question.len() < 0x1_0000, "too many question entries"); + ensure!(self.answer.len() < 0x1_0000, "too many answer entries"); + ensure!(self.authority.len() < 0x1_0000, "too many authority entries"); + ensure!(self.additional.len() < 0x1_0000, "too many additional entries"); + let header = DnsHeader{ + id: self.id, + flags: self.flags, + qdcount: self.question.len() as u16, + ancount: self.answer.len() as u16, + nscount: self.authority.len() as u16, + arcount: self.additional.len() as u16, + }; + header.serialize(context, packet)?; + for r in &self.question { + r.serialize(context, packet)?; + } + for r in &self.answer { + r.serialize(context, packet)?; + } + for r in &self.authority { + r.serialize(context, packet)?; + } + for r in &self.additional { + r.serialize(context, packet)?; + } + Ok(()) + } +} diff --git a/lib/dnsbox-base/src/records/powerdns_tests.rs b/lib/dnsbox-base/src/records/powerdns_tests.rs index 955370f..fcdad76 100644 --- a/lib/dnsbox-base/src/records/powerdns_tests.rs +++ b/lib/dnsbox-base/src/records/powerdns_tests.rs @@ -1,49 +1,83 @@ #![allow(non_snake_case)] -use bytes::{Bytes, BufMut, BigEndian}; -use common_types::{DnsName, Type, types, classes}; -use records::registry; -use ser::{RRDataText, text, packet}; +use bytes::Bytes; +use common_types::{DnsName, DnsCompressedName, Class, Type, types, classes}; +use errors::*; +use packet::*; +use records::{UnknownRecord, registry}; +use ser::{RRData, RRDataText, text}; +use ser::packet::{DnsPacketData, deserialize_with}; +use std::io::Cursor; fn fake_packet(rrtype: Type, raw: &[u8]) -> Bytes { - let mut buf = Vec::from(&[ - // id: 0 - 0, 0, - // flags: 0, opcode: 0 (QUERY), no error - 0, 0, - // qdcount: 1, - 0, 1, - // ancount: 1, - 0, 1, - // nscount: 0, - 0, 0, - // arcount: 0, - 0, 0, - // question 0: - // - qname: "." - 0, - // - qtype: A (1), - 0, 1, - // - qclass: IN (1), - 0, 1, - // answer 0: - // - name: "rec.test" - 3, b'r', b'e', b'c', 4, b't', b'e', b's', b't', 0, - ][..]); - // - type: - buf.put_u16::(rrtype.0); - // - class: IN(1) - buf.put_u16::(1); - // - ttl: 0 - buf.put_u32::(0); - let len = raw.len(); - assert!(len <= 0xffff); - buf.put_u16::(len as u16); - buf.extend_from_slice(raw); + let p = DnsPacket{ + question: vec![ + Question { + qname: ".".parse().unwrap(), + qtype: rrtype, + qclass: classes::IN, + } + ], + answer: vec![ + Resource { + name: "rec.test.".parse().unwrap(), + class: classes::IN, + ttl: 0, + data: Box::new(UnknownRecord::new(rrtype, Bytes::from(raw))), + } + ], + .. Default::default() + }; - Bytes::from(buf) + p.to_bytes().unwrap().into() } +fn get_first_answer_rdata(packet: Bytes) -> Result { + let mut data = Cursor::new(packet); + let data = &mut data; + let header = DnsHeader::deserialize(data)?; + for _ in 0..header.qdcount { + Question::deserialize(data)?; + } + + ensure!(header.ancount > 0, "need at least one answer"); + + let _name = DnsCompressedName::deserialize(data)?; + let _rr_type = Type::deserialize(data)?; + let _class = Class::deserialize(data)?; + let _ttl = u32::deserialize(data)?; + + let rdlength = u16::deserialize(data)? as usize; + check_enough_data!(data, rdlength, "RDATA"); + let pos = data.position() as usize; + + Ok(data.get_ref().slice(pos, pos + rdlength)) +} + +fn serialized_answer(rrdata: Box) -> Result { + let p = DnsPacket{ + question: vec![ + Question { + qname: ".".parse().unwrap(), + qtype: rrdata.rr_type(), + qclass: classes::IN, + } + ], + answer: vec![ + Resource { + name: "rec.test.".parse().unwrap(), + class: classes::IN, + ttl: 0, + data: rrdata, + } + ], + .. Default::default() + }; + + get_first_answer_rdata(p.to_bytes()?.into()) +} + + fn check(q: Type, text_input: &'static str, canonic: Option<&'static str>, raw: &'static [u8]) { let canonic = canonic.unwrap_or(text_input); @@ -57,13 +91,8 @@ fn check(q: Type, text_input: &'static str, canonic: Option<&'static str>, raw: registry::parse_rr_data(&context, data) }).unwrap(); - let d_wire = packet::deserialize_with(fake_packet(q, raw), |data| { - // skip header, question, and header of answer - use bytes::Buf; - data.advance(37); - - registry::deserialize_rr_data(3600, classes::IN, q, data) - }).unwrap(); + let d_wire_packet = deserialize_with(fake_packet(q, raw), DnsPacket::deserialize).unwrap(); + let d_wire = &d_wire_packet.answer[0].data; let d_zone_text = d_zone.text().unwrap(); let d_wire_text = d_wire.text().unwrap(); @@ -71,7 +100,8 @@ fn check(q: Type, text_input: &'static str, canonic: Option<&'static str>, raw: assert_eq!(d_zone_text, d_wire_text, "data parsed from zone doesn't match data from wire"); assert_eq!(&d_zone_text.1, canonic); - // TODO: serialize d_zone_text and compare with raw. + let zone_as_wire = serialized_answer(d_zone).unwrap(); + assert_eq!(zone_as_wire, raw); } #[test] diff --git a/lib/dnsbox-base/src/records/tests.rs b/lib/dnsbox-base/src/records/tests.rs index 9fbe8fb..1c5a24a 100644 --- a/lib/dnsbox-base/src/records/tests.rs +++ b/lib/dnsbox-base/src/records/tests.rs @@ -2,7 +2,8 @@ use bytes::{Bytes, Buf}; use common_types::classes; use failure::ResultExt; use records::structs; -use ser::{packet, text, StaticRRData, DnsPacketData}; +use ser::{StaticRRData, packet, text}; +use ser::packet::DnsPacketData; use std::fmt; use std::io::Cursor; diff --git a/lib/dnsbox-base/src/records/unknown.rs b/lib/dnsbox-base/src/records/unknown.rs index 31f2014..4adcc11 100644 --- a/lib/dnsbox-base/src/records/unknown.rs +++ b/lib/dnsbox-base/src/records/unknown.rs @@ -86,4 +86,7 @@ impl RRDataText for UnknownRecord { } impl RRData for UnknownRecord { + fn clone_box(&self) -> Box { + Box::new(self.clone()) as _ + } } diff --git a/lib/dnsbox-base/src/ser/packet/mod.rs b/lib/dnsbox-base/src/ser/packet/mod.rs index 8814e82..4e4560e 100644 --- a/lib/dnsbox-base/src/ser/packet/mod.rs +++ b/lib/dnsbox-base/src/ser/packet/mod.rs @@ -18,9 +18,7 @@ where { let mut c = Cursor::new(data); let result = parser(&mut c)?; - if c.remaining() != 0 { - bail!("data remaining: {} bytes", c.remaining()) - } + ensure!(!c.has_remaining(), "data remaining: {} bytes", c.remaining()); Ok(result) } diff --git a/lib/dnsbox-base/src/ser/rrdata.rs b/lib/dnsbox-base/src/ser/rrdata.rs index 2aeeda6..4ac89a2 100644 --- a/lib/dnsbox-base/src/ser/rrdata.rs +++ b/lib/dnsbox-base/src/ser/rrdata.rs @@ -93,7 +93,14 @@ impl RRDataText for T { } } -pub trait RRData: RRDataPacket + RRDataText { +pub trait RRData: RRDataPacket + RRDataText + fmt::Debug { + fn clone_box(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } } pub trait StaticRRData: RRData { diff --git a/lib/dnsbox-derive/src/rrdata.rs b/lib/dnsbox-derive/src/rrdata.rs index f1ac317..1f356d6 100644 --- a/lib/dnsbox-derive/src/rrdata.rs +++ b/lib/dnsbox-derive/src/rrdata.rs @@ -73,6 +73,9 @@ pub fn build(ast: &syn::DeriveInput) -> quote::Tokens { quote!{ impl ::dnsbox_base::ser::RRData for #name { + fn clone_box(&self) -> Box<::dnsbox_base::ser::RRData> { + Box::new(self.clone() as #name) as _ + } } impl ::dnsbox_base::ser::StaticRRData for #name {