preview
Stefan Bühler 6 years ago
parent 63cd9196f9
commit 2e380af9bb

7
Cargo.lock generated

@ -45,6 +45,11 @@ name = "cfg-if"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "data-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dbghelp-sys"
version = "0.2.0"
@ -67,6 +72,7 @@ version = "0.1.0"
dependencies = [
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"data-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dnsbox-derive 0.1.0",
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -198,6 +204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6"
"checksum cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7db2f146208d7e0fbee761b09cd65a7f51ccc38705d4e7262dad4d73b12a76b1"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum data-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "099d2591f809713931cd770f2bdf4b8a4d2eb7314bc762da4c375ecaa74af80f"
"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"

@ -11,3 +11,7 @@ failure = "0.1.1"
lazy_static = "1.0.0"
log = "0.3"
smallvec = "0.4.4"
data-encoding = "2.1.0"
[features]
no-unsafe = []

@ -0,0 +1,115 @@
use bytes::Bytes;
use data_encoding::{self, HEXLOWER_PERMISSIVE};
use errors::*;
use failure::{Fail, ResultExt};
use ser::packet::{DnsPacketData, remaining_bytes, short_blob};
use ser::text::*;
use std::fmt;
use std::io::Cursor;
static WHITESPACE: &str = "\t\n\x0c\r "; // \f == \x0c formfeed
lazy_static!{
pub(crate) static ref HEXLOWER_PERMISSIVE_ALLOW_WS: data_encoding::Encoding = {
let mut spec = data_encoding::Specification::new();
spec.symbols.push_str("0123456789abcdef");
spec.translate.from.push_str("ABCDEF");
spec.translate.to.push_str("abcdef");
spec.ignore.push_str(WHITESPACE);
spec.encoding().unwrap()
};
static ref BASE64_ALLOW_WS: data_encoding::Encoding = {
let mut spec = data_encoding::Specification::new();
spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
spec.padding = Some('=');
spec.ignore.push_str(WHITESPACE);
spec.encoding().unwrap()
};
}
// Similar to `ShortText`, but uses (unquoted, no spaces allowed) hex
// for text representation; "-" when empty
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct HexShortBlob(Bytes);
impl DnsPacketData for HexShortBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(HexShortBlob(short_blob(data)?))
}
}
impl DnsTextData for HexShortBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let s = next_field(data)?;
if s == "-" {
Ok(HexShortBlob(Bytes::new()))
} else {
let raw = HEXLOWER_PERMISSIVE.decode(s.as_bytes())
.with_context(|e| e.context(format!("invalid hex: {:?}", s)))?;
ensure!(raw.len() < 256, "short hex field must be at most 255 bytes long");
Ok(HexShortBlob(raw.into()))
}
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
if self.0.is_empty() {
write!(f, "-")
} else {
write!(f, "{}", HEXLOWER_PERMISSIVE.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.
//
// No following field allowed, i.e. last field in the record.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Base64RemainingBlob(Bytes);
impl DnsPacketData for Base64RemainingBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(Base64RemainingBlob(remaining_bytes(data)))
}
}
impl DnsTextData for Base64RemainingBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
skip_whitespace(data);
let result = BASE64_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid base64: {:?}", data)))?;
*data = "";
Ok(Base64RemainingBlob(result.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", BASE64_ALLOW_WS.encode(&self.0))
}
}
#[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)))
}
}
impl DnsTextData for HexRemainingBlob {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
skip_whitespace(data);
let result = HEXLOWER_PERMISSIVE_ALLOW_WS.decode(data.as_bytes())
.with_context(|e| e.context(format!("invalid hex: {:?}", data)))?;
*data = "";
Ok(HexRemainingBlob(result.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", HEXLOWER_PERMISSIVE_ALLOW_WS.encode(&self.0))
}
}

@ -1,90 +0,0 @@
use bytes::{Bytes, Buf};
use std::io::Cursor;
use ser::DnsPacketData;
use errors::*;
#[derive(Clone, Debug)]
pub struct HexShortBlob(Bytes);
// Similar to `ShortText`, but uses (unquoted, no spaces allowed) hex
// for text representation; "-" when empty
impl DnsPacketData for HexShortBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
check_enough_data!(data, 1, "HexShortBlob length");
let label_len = data.get_u8() as usize;
check_enough_data!(data, label_len, "HexShortBlob content");
let pos = data.position() as usize;
let text = data.get_ref().slice(pos, pos + label_len);
data.advance(label_len);
Ok(HexShortBlob(text))
}
}
#[derive(Clone, Debug)]
pub struct Base32ShortBlob(Bytes);
// Similar to `ShortText`, but uses (unquoted, no spaces allowed) base32
// for text representation; "-" when empty
impl DnsPacketData for Base32ShortBlob {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
check_enough_data!(data, 1, "Base32ShortBlob length");
let label_len = data.get_u8() as usize;
check_enough_data!(data, label_len, "Base32ShortBlob content");
let pos = data.position() as usize;
let text = data.get_ref().slice(pos, pos + label_len);
data.advance(label_len);
Ok(Base32ShortBlob(text))
}
}
#[derive(Clone, Debug)]
pub struct LongText(Vec<Bytes>);
// RFC 1035 names this `One or more <character-string>`. No following
// field allowed.
impl DnsPacketData for LongText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let mut texts = Vec::new();
loop {
check_enough_data!(data, 1, "LongText length");
let label_len = data.get_u8() as usize;
check_enough_data!(data, label_len, "LongText content");
let pos = data.position() as usize;
texts.push(data.get_ref().slice(pos, pos + label_len));
data.advance(label_len);
if !data.has_remaining() { break; }
}
Ok(LongText(texts))
}
}
#[derive(Clone, Debug)]
pub struct RemainingText(Bytes);
// No length byte, just all data to end of record. uses base64 encoding
// for text representation
impl DnsPacketData for RemainingText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let pos = data.position() as usize;
let len = data.remaining();
let text = data.get_ref().slice(pos, pos + len);
data.advance(len);
Ok(RemainingText(text))
}
}
#[derive(Clone, 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> {
let pos = data.position() as usize;
let len = data.remaining();
let text = data.get_ref().slice(pos, pos + len);
data.advance(len);
Ok(HexRemainingBlob(text))
}
}

@ -0,0 +1,144 @@
use bytes::Bytes;
use errors::*;
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use std::fmt;
use std::io::Cursor;
use std::borrow::Cow;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Class(pub u16);
pub const IN : Class = Class(KnownClass::IN as u16);
pub const CH : Class = Class(KnownClass::CH as u16);
pub const HS : Class = Class(KnownClass::HS as u16);
pub const NONE : Class = Class(KnownQClass::NONE as u16);
pub const ANY : Class = Class(KnownQClass::ANY as u16);
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u16)]
#[allow(non_camel_case_types)]
pub enum KnownClass {
// try to list "original" rfc
IN = 0x0001, // RFC 1035
CH = 0x0003, // "Chaos"
HS = 0x0004, // "Hesiod"
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u16)]
#[allow(non_camel_case_types)]
pub enum KnownQClass {
// try to list "original" rfc
NONE = 0x00fe, // RFC 2136
ANY = 0x00ff, // RFC 1035: "*"
}
impl Class {
pub fn name(self) -> Cow<'static, str> {
Cow::Borrowed(match self {
IN => "IN",
CH => "CH",
HS => "HS",
NONE => "NONE",
ANY => "ANY",
_ => return Cow::Owned(self.generic_name()),
})
}
/// uses generic name for QCLASS values (`is_qclass`)
pub fn class_name(self) -> Cow<'static, str> {
if self.is_qclass() {
Cow::Owned(self.generic_name())
} else {
self.name()
}
}
/// QCLASS names can overlap with (Q)TYPE names
///
/// classes unknown to this implementation never count as QCLASS,
/// but they also are only represented using the generic "CLASS..."
/// names, which don't overlap with (Q)TYPE names.
pub fn is_qclass(self) -> bool {
match self {
NONE => true,
ANY => true,
_ => false,
}
}
pub fn from_name(name: &str) -> Option<Self> {
use std::ascii::AsciiExt;
if let Some(n) = Self::class_from_name(name) { return Some(n); }
// explicit QCLASS names
if name.eq_ignore_ascii_case("NONE") { return Some(NONE); }
if name.eq_ignore_ascii_case("ANY") { return Some(ANY); }
None
}
/// similar to `from_name`, but doesn't accept QCLASS names (it
/// always accepts "CLASS..." names though, even if they are known to
/// be of type QCLASS)
pub fn class_from_name(name: &str) -> Option<Self> {
use std::ascii::AsciiExt;
if name.eq_ignore_ascii_case("IN") { return Some(IN); }
if name.eq_ignore_ascii_case("CH") { return Some(CH); }
if name.eq_ignore_ascii_case("HS") { return Some(HS); }
if name.as_bytes()[0..5].eq_ignore_ascii_case(b"CLASS") {
if let Ok(c) = name[5..].parse::<u16>() {
return Some(Class(c))
}
}
None
}
pub fn write_class_name(self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_qclass() {
self.write_generic_name(f)
} else {
write!(f, "{}", self)
}
}
pub fn write_generic_name(self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CLASS{}", self.0)
}
pub fn generic_name(self) -> String {
format!("CLASS{}", self.0)
}
}
impl fmt::Display for Class {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let n = match *self {
IN => "IN",
CH => "CH",
HS => "HS",
NONE => "NONE",
ANY => "ANY",
_ => {
return self.write_generic_name(f)
},
};
write!(f, "{}", n)
}
}
impl DnsPacketData for Class {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(Class(DnsPacketData::deserialize(data)?))
}
}
impl DnsTextData for Class {
fn dns_parse(data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
Class::from_name(field).ok_or_else(|| format_err!("unknown class {:?}", field))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}

@ -1,9 +1,20 @@
pub mod name;
pub mod text;
pub mod binary;
pub mod classes;
pub mod types;
mod nsec;
mod nxt;
mod rr_type;
mod time;
mod uri;
pub use self::name::{DnsName, DnsCompressedName};
pub use self::text::{ShortText};
pub use self::binary::{HexShortBlob, Base32ShortBlob, LongText, RemainingText, HexRemainingBlob};
pub use self::binary::{HexShortBlob, Base64RemainingBlob, HexRemainingBlob};
pub use self::name::{DnsName, DnsCanonicalName, DnsCompressedName};
pub use self::nsec::{NsecTypeBitmap, NextHashedOwnerName};
pub use self::rr_type::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;

@ -214,26 +214,25 @@ impl<'a> fmt::Debug for DnsLabelRef<'a> {
impl<'a> fmt::Display for DnsLabelRef<'a> {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
use std::str;
let mut done = 0;
for pos in 0..self.label.len() {
let c = self.label[pos];
if c <= 0x21 || c >= 0x7e || b'.' == c || b'\\' == c {
// flush
if done < pos {
w.write_str(unsafe {str::from_utf8_unchecked(&self.label[done..pos])})?;
w.write_str(::unsafe_ops::from_utf8_unchecked(&self.label[done..pos]))?;
}
match c {
b'.' => w.write_str(r#"\."#)?,
b'\\' => w.write_str(r#"\\"#)?,
_ => write!(w, r"\{:03o}", c)?,
_ => write!(w, r"\{:03}", c)?,
}
done = pos + 1;
}
}
// final flush
if done < self.label.len() {
w.write_str(unsafe {str::from_utf8_unchecked(&self.label[done..])})?;
w.write_str(::unsafe_ops::from_utf8_unchecked(&self.label[done..]))?;
}
Ok(())
}

@ -15,12 +15,13 @@ pub use self::label::*;
mod display;
mod label;
mod name_mutations;
mod name_parser;
mod name_packet_parser;
mod name_text_parser;
#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)]
enum LabelOffset {
LabelStart(u8),
PacketStart(u8),
PacketStart(u16),
}
// the heap meta data is usually at least 2*usize big; assuming 64-bit
@ -78,6 +79,15 @@ 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 {
@ -352,12 +362,12 @@ mod tests {
);
check_uncompressed_display(
b"\x07e!am.l\\\x03com\x00",
"e\\041am\\.l\\\\.com.",
"e\\033am\\.l\\\\.com.",
2,
);
check_uncompressed_debug(
b"\x07e!am.l\\\x03com\x00",
r#""e\\041am\\.l\\\\.com.""#,
r#""e\\033am\\.l\\\\.com.""#,
);
}
@ -451,27 +461,38 @@ mod tests {
);
}
#[test]
fn parse_invalid_compressed_name() {
de_compressed(b"\x11com\x00\x07example\xc0\x00", 5).unwrap_err();
de_compressed(b"\x10com\x00\x07example\xc0\x00", 5).unwrap_err();
}
#[test]
fn parse_and_display_compressed_name() {
check_compressed_display(
b"\x03com\x00\x07example\xc0", 5,
b"\x03com\x00\x07example\xc0\x00", 5,
"example.com.",
2,
);
check_compressed_display(
b"\x03com\x00\x07e!am.l\\\xc0", 5,
"e\\041am\\.l\\\\.com.",
b"\x03com\x00\x07e!am.l\\\xc0\x00", 5,
"e\\033am\\.l\\\\.com.",
2,
);
check_compressed_debug(
b"\x03com\x00\x07e!am.l\\\xc0", 5,
r#""e\\041am\\.l\\\\.com.""#,
b"\x03com\x00\x07e!am.l\\\xc0\x00", 5,
r#""e\\033am\\.l\\\\.com.""#,
);
check_compressed_display(
b"\x03com\x00\x07example\xc0\x00\x03www\xc0\x05", 15,
"www.example.com.",
3,
);
}
#[test]
fn modifications_compressed() {
let mut name = de_compressed(b"\x03com\x00\x07example\xc0", 5).unwrap();
let mut name = de_compressed(b"\x03com\x00\x07example\xc0\x00\xc0\x05", 15).unwrap();
name.push_front(DnsLabelRef::new(b"www").unwrap()).unwrap();
assert_eq!(

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

@ -5,7 +5,7 @@ impl DnsName {
/// `data`: bytes of packet from beginning until at least the end of the name
/// `start_pos`: position of first byte of the name
/// `uncmpr_offsets`: offsets of uncompressed labels so far
/// `label_len`: first compressed label length (`0xc0 | offset`)
/// `label_len`: first compressed label length (`0xc0 | offset-high, offset-low`)
/// `total_len`: length of (uncompressed) label encoding so far
fn parse_name_compressed_cont(data: Bytes, start_pos: usize, uncmpr_offsets: SmallVec<[u8;16]>, mut total_len: usize, mut label_len: u8) -> Result<Self> {
let mut label_offsets = uncmpr_offsets.into_iter()
@ -15,12 +15,14 @@ impl DnsName {
let mut pos = start_pos + total_len;
'next_compressed: loop {
{
let new_pos = (label_len & 0x3f) as usize;
if new_pos >= pos { bail!("Compressed label offset to big") }
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");
pos = new_pos;
}
loop {
ensure!(pos < data.len(), "not enough data for label");
label_len = data[pos];
if 0 == label_len {
@ -31,14 +33,14 @@ impl DnsName {
})
}
if label_len & 0xc == 0xc { continue 'next_compressed; }
if label_len > 63 { bail!("Invalid label length {}", label_len) }
if label_len & 0xc0 == 0xc0 { continue 'next_compressed; }
ensure!(label_len < 64, "Invalid label length {}", label_len);
total_len += 1 + label_len as usize;
// max len 255, but there also needs to be an empty label at the end
if total_len > 254 { bail!("DNS name too long") }
label_offsets.push(LabelOffset::PacketStart(pos as u8));
label_offsets.push(LabelOffset::PacketStart(pos as u16));
pos += 1 + label_len as usize;
}
}
@ -63,6 +65,9 @@ impl DnsName {
if label_len & 0xc0 == 0xc0 {
// compressed label
if !accept_compressed { bail!("Invalid label compression {}", label_len) }
check_enough_data!(data, 1, "DnsName compressed label target");
// eat second part of compressed label
data.get_u8();
let end_pos = data.position() as usize;
let data = data.get_ref().slice(0, end_pos);

@ -0,0 +1,89 @@
use super::*;
use ser::text::{DnsTextData, DnsTextFormatter, next_field, quoted};
impl DnsName {
/// Parse text representation of a domain name
pub fn parse<'a, O>(value: &str, origin: O) -> ::errors::Result<Self>
where
O: IntoIterator<Item = DnsLabelRef<'a>>
{
let raw = value.as_bytes();
let mut name = DnsName::new_root();
if raw == b"." {
return Ok(name);
} else if raw == b"@" {
for l in origin.into_iter() { name.push_back(l)?; }
return Ok(name);
}
ensure!(!raw.is_empty(), "invalid empty name");
let mut label = Vec::new();
let mut pos = 0;
while pos < raw.len() {
if raw[pos] == b'.' {
ensure!(!label.is_empty(), "empty label in name: {:?}", value);
name.push_back(DnsLabelRef::new(&label)?)?;
label.clear();
} else if raw[pos] == b'\\' {
ensure!(pos + 1 < raw.len(), "unexpected end of name after backslash: {:?}", value);
if raw[pos+1] >= b'0' && raw[pos+1] <= b'9' {
// \ddd escape
ensure!(pos + 3 < raw.len(), "unexpected end of name after backslash with digit: {:?}", value);
ensure!(raw[pos+2] >= b'0' && raw[pos+2] <= b'9' && raw[pos+3] >= b'0' && raw[pos+3] <= b'9', "expected three digits after backslash in name: {:?}", name);
let d1 = (raw[pos+1] - b'0') as u32;
let d2 = (raw[pos+2] - b'0') as u32;
let d3 = (raw[pos+3] - b'0') as u32;
let v = d1 * 100 + d2 * 10 + d3;
ensure!(v < 256, "invalid escape in name, {} > 255: {:?}", v, name);
label.push(v as u8);
} else {
ensure!(!quoted::is_ascii_whitespace(raw[pos+1]), "whitespace cannot be escaped with backslash prefix; encode it as \\{:03} in: {:?}", raw[pos+1], name);
label.push(raw[pos+1]);
}
} else {
ensure!(!quoted::is_ascii_whitespace(raw[pos+1]), "whitespace must be encoded as \\{:03} in: {:?}", raw[pos+1], name);
label.push(raw[pos]);
}
pos += 1;
}
if !label.is_empty() {
// no trailing dot, relative name
name.push_back(DnsLabelRef::new(&label)?)?;
for l in origin.into_iter() { name.push_back(l)?; }
}
Ok(name)
}
}
impl DnsTextData for DnsName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
DnsName::parse(field, &DnsName::new_root())
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl DnsCompressedName {
/// Parse text representation of a domain name
pub fn parse<'a, O>(value: &str, origin: O) -> ::errors::Result<Self>
where
O: IntoIterator<Item = DnsLabelRef<'a>>
{
Ok(DnsCompressedName(DnsName::parse(value, origin)?))
}
}
impl DnsTextData for DnsCompressedName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
DnsCompressedName::parse(field, &DnsName::new_root())
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
self.0.dns_format(f)
}
}

@ -0,0 +1,153 @@
use bytes::{Bytes, Buf};
use common_types::Type;
use data_encoding;
use errors::*;
use failure::{Fail, ResultExt};
use ser::packet::{DnsPacketData, remaining_bytes, short_blob};
use ser::text::{DnsTextData, DnsTextFormatter, skip_whitespace, next_field};
use std::collections::BTreeSet;
use std::fmt;
use std::io::Cursor;
static WHITESPACE: &str = "\t\n\x0c\r "; // \f == \x0c formfeed
lazy_static!{
static ref BASE32HEX_NOPAD_ALLOW_WS: data_encoding::Encoding = {
let mut spec = data_encoding::Specification::new();
spec.symbols.push_str("0123456789ABCDEFGHIJKLMNOPQRSTUV");
spec.translate.from.push_str("abcdefghijklmnopqrstuv");
spec.translate.to.push_str("ABCDEFGHIJKLMNOPQRSTUV");
spec.ignore.push_str(WHITESPACE);
spec.encoding().unwrap()
};
}
/// Last field in a record!
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NsecTypeBitmap {
raw: Bytes,
set: BTreeSet<Type>,
}
impl NsecTypeBitmap {
pub fn from_set(set: BTreeSet<Type>) -> Self {
let mut raw = Vec::new();
let mut current_window_base = None;
let mut window_pos = 0;
let mut window_len = 0;
for t in &set {
let base = (t.0 >> 8) as u8;
if current_window_base != Some(base) {
// new window
current_window_base = Some(base);
window_pos = raw.len();
window_len = 0;
raw.push(base);
raw.push(0);
}
let bit_ndx = t.0 as u8;
let byte_ndx = bit_ndx / 8;
if byte_ndx >= window_len {
// make window bigger, fill with zeroes
let new_window_len = byte_ndx + 1;
let new_raw_len = raw.len() + (new_window_len - window_len) as usize;
raw.resize(new_raw_len, 0);
raw[window_pos + 1] = new_window_len;
window_len = new_window_len;
}
let mask = 0x80 >> (bit_ndx % 8);
raw[window_pos + 2 + byte_ndx as usize] |= mask;
}
NsecTypeBitmap {
raw: raw.into(),
set: set,
}
}
}
impl DnsPacketData for NsecTypeBitmap {
fn deserialize(data: &mut Cursor<Bytes>) -> ::errors::Result<Self> {
// remember raw encoding
let raw = {
let mut data: Cursor<Bytes> = data.clone();
remaining_bytes(&mut data)
};
let mut set = BTreeSet::new();
let mut prev_window = None;
while data.has_remaining() {
let window_base = (data.get_u8() as u16) << 8;
ensure!(Some(window_base) > prev_window, "wrong nsec bitmap window order, {:?} <= {:?}", Some(window_base), prev_window);
prev_window = Some(window_base);
check_enough_data!(data, 1, "nsec bitmap window length");
let window_len = data.get_u8() as u16;
ensure!(window_len <= 32, "nsec bitmap window too long");
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 {
if 0 != v & 0x80 {
set.insert(Type(window_base + i*8 + j));
}
v <<= 1;
}
}
}
Ok(NsecTypeBitmap{
raw: raw,
set: set,
})
}
}
impl DnsTextData for NsecTypeBitmap {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let mut set = BTreeSet::new();
skip_whitespace(data);
while !data.is_empty() {
let t = Type::dns_parse(data)?;
set.insert(t);
}
Ok(NsecTypeBitmap::from_set(set))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
for t in &self.set {
write!(f, "{}", t)?;
}
Ok(())
}
}
/// `base32hex` encoding without padding for text representation (RFC
/// 4648). whitespaces not allowed (not the last field).
///
/// Between 1 and 255 bytes long.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NextHashedOwnerName(Bytes);
impl DnsPacketData for NextHashedOwnerName {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let text = short_blob(data)?;
ensure!(text.len() > 0, "NextHashedOwnerName must not be empty");
Ok(NextHashedOwnerName(text))
}
}
impl DnsTextData for NextHashedOwnerName {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
let raw = BASE32HEX_NOPAD_ALLOW_WS.decode(field.as_bytes())
.with_context(|e| e.context(format!("invalid base32hex (no padding): {:?}", field)))?;
ensure!(raw.len() > 0, "NextHashedOwnerName must not be empty");
ensure!(raw.len() < 256, "NextHashedOwnerName field must be at most 255 bytes long");
Ok(NextHashedOwnerName(raw.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
if self.0.is_empty() { return Err(fmt::Error); }
write!(f, "{}", BASE32HEX_NOPAD_ALLOW_WS.encode(&self.0))
}
}

@ -0,0 +1,94 @@
use bytes::{Bytes, Buf};
use common_types::Type;
use errors::*;
use ser::packet::{DnsPacketData, remaining_bytes};
use ser::text::{DnsTextData, DnsTextFormatter, skip_whitespace};
use std::collections::BTreeSet;
use std::fmt;
use std::io::Cursor;
/// Type Bitmap for NXT. Last field in a record.
///
/// RFC 2535 says for the bitmap: "If the zero bit of the type bit map
/// is a one, it indicates that a different format is being used which
/// will always be the case if a type number greater than 127 is
/// present."
///
/// But this other format isn't specified anywhere...
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NxtTypeBitmap {
raw: Bytes,
set: BTreeSet<Type>,
}
impl NxtTypeBitmap {
pub fn from_set(set: BTreeSet<Type>) -> Result<Self> {
let mut raw = Vec::new();
for t in &set {
ensure!(t.0 > 0, "type 0 cannot be represented in NXT bitmap");
ensure!(t.0 < 128, "type {} cannot be represented in NXT bitmap", t);
let bit_ndx = t.0 as u8;
let byte_ndx = (bit_ndx / 8) as usize;
if byte_ndx >= raw.len() {
raw.resize(byte_ndx + 1, 0);
}
let mask = 0x80 >> (bit_ndx % 8);
raw[byte_ndx] |= mask;
}
Ok(NxtTypeBitmap {
raw: raw.into(),
set: set,
})
}
}
impl DnsPacketData for NxtTypeBitmap {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
// remember raw encoding
let raw = {
let mut data: Cursor<Bytes> = data.clone();
remaining_bytes(&mut data)
};
let mut set = BTreeSet::new();
let mut current = 0;
while data.has_remaining() {
ensure!(current < 128, "NXT bitmap too long");
let mut v = data.get_u8();
for _ in 0..7 {
if 0 != v & 0x80 {
ensure!(0 != current, "Type 0 not allowed in NXT bitmap");
set.insert(Type(current));
}
v <<= 1;
current += 1;
}
}
Ok(NxtTypeBitmap{
raw: raw,
set: set,
})
}
}
impl DnsTextData for NxtTypeBitmap {
fn dns_parse(data: &mut &str) -> Result<Self> {
let mut set = BTreeSet::new();
skip_whitespace(data);
while !data.is_empty() {
let t = Type::dns_parse(data)?;
set.insert(t);
}
NxtTypeBitmap::from_set(set)
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
for t in &self.set {
write!(f, "{}", t)?;
}
Ok(())
}
}

@ -1,8 +1,99 @@
use bytes::Bytes;
use errors::*;
use ser::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use std::fmt;
use std::io::Cursor;
use records::registry::{name_to_type, type_name};
use common_types::types;
use std::borrow::Cow;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Type(pub u16);
impl ::ser::DnsPacketData for Type {
fn deserialize(data: &mut ::std::io::Cursor<::bytes::Bytes>) -> ::errors::Result<Self> {
Ok(Type(::ser::DnsPacketData::deserialize(data)?))
impl Type {
pub fn name(self) -> Cow<'static, str> {
if let Some(name) = type_name(self) {
Cow::Borrowed(name)
} else {
Cow::Owned(format!("TYPE{}", self.0))
}
}
/// defined in RFC 1035
pub fn well_known(self) -> bool {
// 0x0001 (A) ... 0x0010 (TXT) are defined in RFC 1035
self.0 >= 0x0001 && self.0 <= 0x0010
}
/// require converting to canonical form for DNSSEC (i.e. names
/// must be converted to (ASCII) lower case, no compression)
///
/// See https://tools.ietf.org/html/rfc4034#section-6.2 (updates RFC 3597).
pub fn canonical(self) -> bool {
match self {
types::NS => true,
types::MD => true,
types::MF => true,
types::CNAME => true,
types::SOA => true,
types::MB => true,
types::MG => true,
types::MR => true,
types::PTR => true,
// types::HINFO => true, // doesn't have a name in data
types::MINFO => true,
types::MX => true,
// types::HINFO => true, // see above, also duplicate in the RFCs
types::RP => true,
types::AFSDB => true,
types::RT => true,
types::NSAP_PTR => true, // not listed in the RFCs, but probably should be.
types::SIG => true,
types::PX => true,
types::NXT => true,
types::SRV => true, // moved up to match numeric order
types::NAPTR => true,
types::KX => true,
// types::SRV => true, // moved up to match numeric order
types::A6 => true, // moved up to match numeric order
types::DNAME => true,
// types::A6 => true, // moved up to match numeric order
types::RRSIG => true,
types::NSEC => true,
_ => false,
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(name) = type_name(*self) {
write!(f, "{}", name)
} else {
write!(f, "TYPE{}", self.0)
}
}
}
impl DnsPacketData for Type {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(Type(DnsPacketData::deserialize(data)?))
}
}
impl DnsTextData for Type {
fn dns_parse(data: &mut &str) -> Result<Self> {
let field = next_field(data)?;
if field.starts_with("TYPE") || field.starts_with("type") {
if let Ok(t) = field[4..].parse::<u16>() {
return Ok(Type(t));
}
}
name_to_type(field).ok_or_else(|| format_err!("unknown type {:?}", field))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self)
}
}

@ -0,0 +1,115 @@
use bytes::{Bytes, Buf};
use errors::*;
use ser::packet::{DnsPacketData, short_blob, remaining_bytes};
use ser::text::*;
use std::fmt;
use std::io::Cursor;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ShortText(Bytes);
// RFC 1035 names this `<character-string>`
impl DnsPacketData for ShortText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(ShortText(short_blob(data)?))
}
}
impl DnsTextData for ShortText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(raw.len() < 256, "short text must be at most 255 bytes long");
Ok(ShortText(raw.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", quote(&self.0))
}
}
/// RFC 1035 names this `One or more <character-string>`. No following
/// field allowed.
///
/// Used for TXT and SPF.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct LongText(Vec<Bytes>);
impl DnsPacketData for LongText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let mut texts = Vec::new();
loop {
texts.push(short_blob(data)?);
if !data.has_remaining() { break; }
}
Ok(LongText(texts))
}
}
impl DnsTextData for LongText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let mut result = Vec::new();
// `next_quoted_field` should skip trailing whitespace, we only
// need to skip the beginning whitespace for the first
// `is_empty` check.
skip_whitespace(data);
while !data.is_empty() {
let part = next_quoted_field(data)?;
ensure!(part.len() < 256, "long text component must be at most 255 bytes long");
result.push(part.into());
}
Ok(LongText(result))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
// actually emit as "multiple fields" (i.e. space separated)
for part in &self.0 {
write!(f, "{}", quote(part))?;
}
Ok(())
}
}
// similart to ShortText, but text output doesn't use quotes.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct UnquotedShortText(Bytes);
impl DnsPacketData for UnquotedShortText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(UnquotedShortText(short_blob(data)?))
}
}
impl DnsTextData for UnquotedShortText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(raw.len() < 256, "short text must be at most 255 bytes long");
Ok(UnquotedShortText(raw.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", escape(&self.0))
}
}
/// A single (possibly quoted) <character-string>, but no length
/// restriction.
///
/// Last field, but no whitespace allowed (unless quoted of course).
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct RemainingText(Bytes);
impl DnsPacketData for RemainingText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(RemainingText(remaining_bytes(data)))
}
}
impl DnsTextData for RemainingText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
Ok(RemainingText(next_quoted_field(data)?.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", quote(&self.0))
}
}

@ -1,21 +0,0 @@
use bytes::{Bytes, Buf};
use std::io::Cursor;
use ser::DnsPacketData;
use errors::*;
#[derive(Clone, Debug)]
pub struct ShortText(Bytes);
// RFC 1035 names this `<character-string>`
impl DnsPacketData for ShortText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
check_enough_data!(data, 1, "ShortText length");
let label_len = data.get_u8() as usize;
check_enough_data!(data, label_len, "ShortText content");
let pos = data.position() as usize;
let text = data.get_ref().slice(pos, pos + label_len);
data.advance(label_len);
Ok(ShortText(text))
}
}

@ -0,0 +1,38 @@
use bytes::Bytes;
use errors::*;
use ser::packet::DnsPacketData;
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
use std::fmt;
use std::io::Cursor;
/// A single quoted non-empty URL.
///
/// Actually shouldn't allow escapes (i.e. no backslash in the content);
/// but to make sure we can export and import any data we allow standard
/// escape mechanisms and even accept unquoted input.
///
/// No whitespace allowed, last field.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Time(u32);
impl DnsPacketData for Time {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
Ok(Time(DnsPacketData::deserialize(data)?))
}
}
impl DnsTextData for Time {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let field = next_field(data)?;
let epoch = field.parse::<u32>();
if field.len() == 14 && epoch.is_err() {
unimplemented!()
} else {
Ok(Time(epoch?))
}
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

@ -0,0 +1,36 @@
use bytes::Bytes;
use errors::*;
use ser::packet::{DnsPacketData, remaining_bytes};
use ser::text::*;
use std::fmt;
use std::io::Cursor;
/// A single quoted non-empty URL.
///
/// Actually shouldn't allow escapes (i.e. no backslash in the content);
/// but to make sure we can export and import any data we allow standard
/// escape mechanisms and even accept unquoted input.
///
/// No whitespace allowed, last field.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct UriText(Bytes);
impl DnsPacketData for UriText {
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
let raw = remaining_bytes(data);
ensure!(!raw.is_empty(), "URI must not be empty");
Ok(UriText(raw))
}
}
impl DnsTextData for UriText {
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
let raw = next_quoted_field(data)?;
ensure!(!raw.is_empty(), "URI must not be empty");
Ok(UriText(raw.into()))
}
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
write!(f, "{}", quote(&self.0))
}
}

@ -1,5 +1,6 @@
pub extern crate byteorder;
pub extern crate bytes;
pub extern crate data_encoding;
#[macro_use]
pub extern crate failure;
@ -9,6 +10,8 @@ extern crate smallvec;
#[macro_use]
extern crate lazy_static;
mod unsafe_ops;
#[macro_use]
pub mod errors;
pub mod common_types;

@ -1,8 +1,10 @@
mod weird_structs;
mod structs;
mod unknown;
pub mod registry;
pub mod types;
pub use self::structs::*;
pub use self::unknown::*;
#[cfg(test)]
mod tests;

@ -1,9 +1,11 @@
use std::collections::HashMap;
use std::ascii::AsciiExt;
use std::marker::PhantomData;
use records::structs;
use records::types;
use common_types::types;
use common_types::Type;
use ser::RRData;
use ser::{RRData, StaticRRData};
lazy_static!{
static ref REGISTRY: Registry = Registry::init();
@ -15,7 +17,7 @@ fn registry() -> &'static Registry {
pub fn known_name_to_type(name: &str) -> Option<Type> {
let registry = registry();
let &t = registry.names_to_type.get(name)?;
let t = name_to_type(name)?;
registry.type_parser.get(&t)?;
Some(t)
@ -27,8 +29,16 @@ pub fn name_to_type(name: &str) -> Option<Type> {
Some(t)
}
pub(crate) fn type_name(rrtype: Type) -> Option<&'static str> {
let registry = registry();
registry.type_names.get(&rrtype).map(|s| s as _)
}