step
parent
63cd9196f9
commit
2e380af9bb
@ -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;
|
||||
|
@ -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,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;
|