step
This commit is contained in:
parent
63cd9196f9
commit
2e380af9bb
7
Cargo.lock
generated
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 = []
|
||||
|
115
lib/dnsbox-base/src/common_types/binary.rs
Normal file
115
lib/dnsbox-base/src/common_types/binary.rs
Normal file
@ -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))
|
||||
}
|
||||
}
|
144
lib/dnsbox-base/src/common_types/classes.rs
Normal file
144
lib/dnsbox-base/src/common_types/classes.rs
Normal file
@ -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);
|
89
lib/dnsbox-base/src/common_types/name/name_text_parser.rs
Normal file
89
lib/dnsbox-base/src/common_types/name/name_text_parser.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
153
lib/dnsbox-base/src/common_types/nsec.rs
Normal file
153
lib/dnsbox-base/src/common_types/nsec.rs
Normal file
@ -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))
|
||||
}
|
||||
}
|
94
lib/dnsbox-base/src/common_types/nxt.rs
Normal file
94
lib/dnsbox-base/src/common_types/nxt.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
|
115
lib/dnsbox-base/src/common_types/text.rs
Normal file
115
lib/dnsbox-base/src/common_types/text.rs
Normal file
@ -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))
|
||||
}
|
||||
}
|
38
lib/dnsbox-base/src/common_types/time.rs
Normal file
38
lib/dnsbox-base/src/common_types/time.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
36
lib/dnsbox-base/src/common_types/uri.rs
Normal file
36
lib/dnsbox-base/src/common_types/uri.rs
Normal file
@ -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 _)
|
||||
}
|
||||
|
||||
struct TagRRDataType<T: RRData>(PhantomData<T>);
|
||||
|
||||
struct Registry {
|
||||
names_to_type: HashMap<String, Type>,
|
||||
type_names: HashMap<Type, String>,
|
||||
type_parser: HashMap<Type, ()>,
|
||||
// make sure registrations are in order
|
||||
prev_type: Option<Type>,
|
||||
@ -38,6 +48,7 @@ impl Registry {
|
||||
fn init() -> Self {
|
||||
let mut r = Registry {
|
||||
names_to_type: HashMap::new(),
|
||||
type_names: HashMap::new(),
|
||||
type_parser: HashMap::new(),
|
||||
prev_type: None,
|
||||
};
|
||||
@ -51,8 +62,8 @@ impl Registry {
|
||||
r.register_known::<structs::MB>();
|
||||
r.register_known::<structs::MG>();
|
||||
r.register_known::<structs::MR>();
|
||||
r.register_known::<structs::NULL>();
|
||||
r.register_known::<structs::WKS>();
|
||||
r.register_unknown("NULL" , types::NULL);
|
||||
r.register_unknown("WKS" , types::WKS);
|
||||
r.register_known::<structs::PTR>();
|
||||
r.register_known::<structs::HINFO>();
|
||||
r.register_known::<structs::MINFO>();
|
||||
@ -62,16 +73,16 @@ impl Registry {
|
||||
r.register_known::<structs::AFSDB>();
|
||||
r.register_unknown("X25" , types::X25);
|
||||
r.register_unknown("ISDN" , types::ISDN);
|
||||
r.register_unknown("RT" , types::RT);
|
||||
r.register_known::<structs::RT>();
|
||||
r.register_unknown("NSAP" , types::NSAP);
|
||||
r.register_unknown("NSAP-PTR" , types::NSAP_PTR);
|
||||
r.register_unknown("SIG" , types::SIG);
|
||||
r.register_known::<structs::NSAP_PTR>();
|
||||
r.register_known::<structs::SIG>();
|
||||
r.register_known::<structs::KEY>();
|
||||
r.register_unknown("PX" , types::PX);
|
||||
r.register_known::<structs::PX>();
|
||||
r.register_unknown("GPOS" , types::GPOS);
|
||||
r.register_known::<structs::AAAA>();
|
||||
r.register_known::<structs::LOC>();
|
||||
r.register_unknown("NXT" , types::NXT);
|
||||
r.register_known::<structs::NXT>();
|
||||
r.register_unknown("EID" , types::EID);
|
||||
r.register_unknown("NIMLOC" , types::NIMLOC);
|
||||
r.register_known::<structs::SRV>();
|
||||
@ -79,7 +90,7 @@ impl Registry {
|
||||
r.register_known::<structs::NAPTR>();
|
||||
r.register_known::<structs::KX>();
|
||||
r.register_known::<structs::CERT>();
|
||||
r.register_unknown("A6" , types::A6);
|
||||
r.register_known::<structs::A6>();
|
||||
r.register_known::<structs::DNAME>();
|
||||
r.register_unknown("SINK" , types::SINK);
|
||||
r.register_unknown("OPT" , types::OPT);
|
||||
@ -121,8 +132,8 @@ impl Registry {
|
||||
r.register_unknown("MAILB" , types::MAILB);
|
||||
r.register_unknown("MAILA" , types::MAILA);
|
||||
r.register_unknown("ANY" , types::ANY);
|
||||
r.register_unknown("URI" , types::URI);
|
||||
r.register_unknown("CAA" , types::CAA);
|
||||
r.register_known::<structs::URI>();
|
||||
r.register_known::<structs::CAA>();
|
||||
r.register_unknown("AVC" , types::AVC);
|
||||
r.register_unknown("DOA" , types::DOA);
|
||||
r.register_unknown("DLV" , types::DLV);
|
||||
@ -132,18 +143,23 @@ impl Registry {
|
||||
r
|
||||
}
|
||||
|
||||
fn register_unknown(&mut self, name: &'static str, rrtype: Type) {
|
||||
fn register_name(&mut self, name: &str, rrtype: Type) {
|
||||
assert!(self.prev_type < Some(rrtype), "registration not in order");
|
||||
self.prev_type = Some(rrtype);
|
||||
assert!(self.names_to_type.insert(name.into(), rrtype).is_none());
|
||||
let mut name: String = name.into();
|
||||
name.make_ascii_uppercase();
|
||||
assert!(self.names_to_type.insert(name.clone(), rrtype).is_none());
|
||||
self.type_names.insert(rrtype, name);
|
||||
}
|
||||
|
||||
fn register_known<T: RRData>(&mut self) {
|
||||
let n = T::rr_type_name();
|
||||
let t = T::rr_type();
|
||||
assert!(self.prev_type < Some(t), "registration not in order");
|
||||
self.prev_type = Some(t);
|
||||
assert!(self.names_to_type.insert(n.into_owned(), t).is_none());
|
||||
self.type_parser.insert(t, ());
|
||||
fn register_unknown(&mut self, name: &'static str, rrtype: Type) {
|
||||
self.register_name(name, rrtype);
|
||||
}
|
||||
|
||||
fn register_known<T: StaticRRData>(&mut self) {
|
||||
let rrtype = T::TYPE;
|
||||
let name = T::NAME;
|
||||
self.register_name(name, rrtype);
|
||||
self.type_parser.insert(rrtype, ());
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,44 @@
|
||||
use bytes::Bytes;
|
||||
use ser::DnsPacketData;
|
||||
use common_types::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
// unless otherwise documented, class should probably be IN (0x0001)
|
||||
|
||||
// deriving RRData will add a unit test to make sure the type is
|
||||
// registered; there must be a records::types::$name `Type` constant
|
||||
// with the same name as the struct.
|
||||
|
||||
// class IN
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct A {
|
||||
addr: Ipv4Addr,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct NS {
|
||||
nsdname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct MD {
|
||||
madname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct MF {
|
||||
madname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct CNAME {
|
||||
cname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct SOA {
|
||||
mname: DnsCompressedName,
|
||||
rname: DnsCompressedName,
|
||||
@ -48,66 +50,68 @@ pub struct SOA {
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct MB {
|
||||
madname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct MG {
|
||||
mgmname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct MR {
|
||||
newname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
pub struct NULL {
|
||||
anything: RemainingText,
|
||||
}
|
||||
// not allowed in zone files anyway, i.e. no text representation.
|
||||
// content not restricted either, just some bytes. no need to parse it.
|
||||
//
|
||||
// class independent pub struct NULL;
|
||||
|
||||
// text representation like: `WKS 127.0.0.1 TCP smtp http 110`. would
|
||||
// have to parse protocol and service names.
|
||||
//
|
||||
// class IN
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
pub struct WKS {
|
||||
address: Ipv4Addr,
|
||||
protocol: u8,
|
||||
bitmap: RemainingText,
|
||||
}
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct WKS {
|
||||
// address: Ipv4Addr,
|
||||
// protocol: u8,
|
||||
// bitmap: ..., // remaining bytes
|
||||
// }
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct PTR {
|
||||
ptrdname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct HINFO {
|
||||
cpu: ShortText,
|
||||
os: ShortText,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct MINFO {
|
||||
rmailbx: DnsCompressedName,
|
||||
emailbx: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct MX {
|
||||
preference: u16,
|
||||
mxname: DnsCompressedName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct TXT {
|
||||
text: LongText,
|
||||
}
|
||||
@ -115,132 +119,153 @@ pub struct TXT {
|
||||
// end of RFC 1035: no DnsCompressedName below!
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct RP {
|
||||
mbox: DnsName,
|
||||
txt: DnsName,
|
||||
mbox: DnsCanonicalName,
|
||||
txt: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct AFSDB {
|
||||
subtype: u16,
|
||||
hostname: DnsName,
|
||||
hostname: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc1183#section-3.1 says "its format in
|
||||
// master files is a <character-string>" which say nothing about the
|
||||
// binary encoding; later it says "<PSDN-address> is a string of decimal
|
||||
// digits", so it would seem that there is no length encoding or
|
||||
// restriction.
|
||||
//
|
||||
// wireshark and bind use <character-string> though (bind also wants at
|
||||
// least 4 bytes in the field: probably due to "beginning with the 4
|
||||
// digit DNIC").
|
||||
//
|
||||
// class independent
|
||||
// pub struct X25 {
|
||||
// psdn_address: ShortText,
|
||||
// }
|
||||
|
||||
// class independent
|
||||
// pub struct ISDN {
|
||||
// isdn_address: ShortText,
|
||||
// subaddress: Option<ShortText>,
|
||||
// }
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct RT {
|
||||
preference: u16,
|
||||
intermediate: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// pub struct NSAP;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
#[RRTypeName = "NSAP-PTR"]
|
||||
pub struct NSAP_PTR {
|
||||
owner: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// class independent
|
||||
// pub struct X25;
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct SIG {
|
||||
rr_type: Type,
|
||||
algorithm: u8,
|
||||
labels: u8,
|
||||
// RFC says this can be omitted in text form if it is the same as
|
||||
// the TTL on the SIG record. not supported to be omitted here
|
||||
// (TODO?).
|
||||
original_ttl: u32,
|
||||
signature_expiration: Time,
|
||||
signature_inception: Time,
|
||||
key_tag: u16,
|
||||
signers_name: DnsCanonicalName,
|
||||
signature: Base64RemainingBlob,
|
||||
}
|
||||
|
||||
// class independent
|
||||
// pub struct ISDN;
|
||||
|
||||
// class independent
|
||||
// pub struct RT;
|
||||
|
||||
// pub struct NSAP;
|
||||
// pub struct NSAP_PTR;
|
||||
// pub struct SIG;
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct KEY {
|
||||
flags: u16,
|
||||
protocol: u8,
|
||||
algorithm: u8,
|
||||
certificate: RemainingText,
|
||||
public_key: Base64RemainingBlob,
|
||||
}
|
||||
|
||||
// class IN
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct PX {
|
||||
preference: u16,
|
||||
map822: DnsCanonicalName,
|
||||
mapx400: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// pub struct PX;
|
||||
// pub struct GPOS;
|
||||
|
||||
// class IN
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct AAAA {
|
||||
addr: Ipv6Addr,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, RRData)]
|
||||
pub enum LOC {
|
||||
Version0(LOC0),
|
||||
UnknownVersion{
|
||||
version: u8,
|
||||
data: RemainingText
|
||||
},
|
||||
pub use super::weird_structs::LOC;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct NXT {
|
||||
next: DnsCanonicalName,
|
||||
types: NxtTypeBitmap,
|
||||
}
|
||||
|
||||
impl DnsPacketData for LOC {
|
||||
fn deserialize(data: &mut ::std::io::Cursor<Bytes>) -> ::errors::Result<Self> {
|
||||
let version: u8 = DnsPacketData::deserialize(data)?;
|
||||
if 0 == version {
|
||||
Ok(LOC::Version0(DnsPacketData::deserialize(data)?))
|
||||
} else {
|
||||
Ok(LOC::UnknownVersion{
|
||||
version: version,
|
||||
data: DnsPacketData::deserialize(data)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, DnsPacketData)]
|
||||
pub struct LOC0 {
|
||||
size: u8,
|
||||
horizontal_precision: u8,
|
||||
vertical_precision: u8,
|
||||
latitude: u32,
|
||||
longitude: u32,
|
||||
altitude: u32,
|
||||
}
|
||||
|
||||
// pub struct NXT;
|
||||
// pub struct EID;
|
||||
// pub struct NIMLOC;
|
||||
|
||||
// class IN
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct SRV {
|
||||
preference: u16,
|
||||
weight: u16,
|
||||
port: u16,
|
||||
target: DnsName,
|
||||
target: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// pub struct ATMA;
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct NAPTR {
|
||||
order: u16,
|
||||
preference: u16,
|
||||
flags: ShortText,
|
||||
service: ShortText,
|
||||
regexp: ShortText,
|
||||
replacement: DnsName,
|
||||
replacement: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// class IN
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct KX {
|
||||
preference: u16,
|
||||
exchanger: DnsName,
|
||||
exchanger: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// class ??
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct CERT {
|
||||
cert_type: u16,
|
||||
key_tag: u16,
|
||||
algorithm: u8,
|
||||
certificate: RemainingText,
|
||||
certificate: Base64RemainingBlob,
|
||||
}
|
||||
|
||||
// pub struct A6;
|
||||
pub use super::weird_structs::A6;
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct DNAME {
|
||||
target: DnsName,
|
||||
target: DnsCanonicalName,
|
||||
}
|
||||
|
||||
// pub struct SINK;
|
||||
@ -251,7 +276,7 @@ pub struct DNAME {
|
||||
|
||||
// pub struct APL;
|
||||
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct DS {
|
||||
key_tag: u16,
|
||||
algorithm: u8,
|
||||
@ -259,67 +284,64 @@ pub struct DS {
|
||||
digest: HexRemainingBlob,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct SSHFP {
|
||||
algorithm: u8,
|
||||
fingerprint_type: u8,
|
||||
// RFC 4255 doesn't specify whether whitespace is allowed.
|
||||
// `HexRemainingBlob` allows whitespace.
|
||||
fingerprint: HexRemainingBlob,
|
||||
}
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct IPSECKEY;
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct RRSIG {
|
||||
rr_type: Type,
|
||||
algorithm: u8,
|
||||
labels: u8,
|
||||
original_ttl: u32,
|
||||
signature_expiration: u32,
|
||||
signature_inception: u32,
|
||||
signature_expiration: Time,
|
||||
signature_inception: Time,
|
||||
key_tag: u16,
|
||||
signers_name: DnsName,
|
||||
signature: RemainingText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, DnsPacketData)]
|
||||
pub struct NsecTypeBitmap {
|
||||
types: RemainingText, // TODO: actually parse it
|
||||
signers_name: DnsCanonicalName,
|
||||
signature: Base64RemainingBlob,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct NSEC {
|
||||
next: DnsName,
|
||||
bitmap: NsecTypeBitmap,
|
||||
next: DnsCanonicalName,
|
||||
types: NsecTypeBitmap,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct DNSKEY {
|
||||
flags: u16,
|
||||
protocol: u8,
|
||||
algorithm: u8,
|
||||
public_key: RemainingText,
|
||||
public_key: Base64RemainingBlob,
|
||||
}
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct DHCID;
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct NSEC3 {
|
||||
hash_algorithm: u8,
|
||||
flags: u8,
|
||||
iterations: u16,
|
||||
salt: HexShortBlob,
|
||||
next_hashed: Base32ShortBlob,
|
||||
bitmap: NsecTypeBitmap,
|
||||
next_hashed: NextHashedOwnerName,
|
||||
types: NsecTypeBitmap,
|
||||
}
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct NSEC3PARAM {
|
||||
hash_algorithm: u8,
|
||||
flags: u8,
|
||||
@ -327,32 +349,32 @@ pub struct NSEC3PARAM {
|
||||
salt: HexShortBlob,
|
||||
}
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct TLSA;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct SMIMEA;
|
||||
|
||||
// pub struct HIP;
|
||||
// pub struct NINFO;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct RKEY;
|
||||
|
||||
// pub struct TALINK;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct CDS;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct CDNSKEY;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct OPENPGPKEY;
|
||||
|
||||
// pub struct CSYNC;
|
||||
|
||||
#[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct SPF {
|
||||
text: LongText,
|
||||
}
|
||||
@ -366,16 +388,16 @@ pub struct SPF {
|
||||
// pub struct L64;
|
||||
// pub struct LP;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct EUI48;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct EUI64;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct TKEY;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct TSIG;
|
||||
|
||||
// pub struct IXFR; // qtype only?
|
||||
@ -384,19 +406,27 @@ pub struct SPF {
|
||||
// pub struct MAILA; // qtype only?
|
||||
// pub struct ANY; // qtype only?
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// pub struct URI;
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct URI {
|
||||
priority: u16,
|
||||
weight: u16,
|
||||
target: UriText,
|
||||
}
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// pub struct CAA;
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
pub struct CAA {
|
||||
flags: u8,
|
||||
tag: UnquotedShortText,
|
||||
value: RemainingText,
|
||||
}
|
||||
|
||||
// pub struct AVC;
|
||||
// pub struct DOA;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct DLV;
|
||||
|
||||
// pub struct ADDR;
|
||||
|
||||
// #[derive(Clone, Debug, DnsPacketData, RRData)]
|
||||
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
|
||||
// pub struct ALIAS;
|
||||
|
@ -1,16 +1,118 @@
|
||||
use bytes::Bytes;
|
||||
use bytes::{Bytes, Buf};
|
||||
use failure::ResultExt;
|
||||
use records::structs;
|
||||
use ser::packet::deserialize;
|
||||
use ser::RRData;
|
||||
use ser::{packet, text, StaticRRData};
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
|
||||
fn check<T>(txt: &str, data: &'static [u8])
|
||||
fn rrdata_de<T>(data: &'static [u8]) -> ::errors::Result<T>
|
||||
where
|
||||
T: RRData
|
||||
T: StaticRRData
|
||||
{
|
||||
let d: T = deserialize(Bytes::from_static(data)).expect("couldn't parse record");
|
||||
let mut data = Cursor::new(Bytes::from_static(data));
|
||||
let result = T::deserialize_rr_data(3600, 0x0001, T::TYPE, &mut data)?;
|
||||
ensure!(!data.has_remaining(), "rrdata not read completely");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn check<T>(txt: &str, data: &'static [u8]) -> ::errors::Result<()>
|
||||
where
|
||||
T: StaticRRData + fmt::Debug + PartialEq
|
||||
{
|
||||
let d1: T = rrdata_de(data).context("couldn't parse binary record")?;
|
||||
let d2: T = text::parse(txt).context("couldn't parse text record")?;
|
||||
ensure!(d1 == d2, "decoded data not equal: {:?} != {:?}", d1, d2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check2<T>(txt: &str, data: &'static [u8], canon: &str) -> ::errors::Result<()>
|
||||
where
|
||||
T: StaticRRData + fmt::Debug + PartialEq
|
||||
{
|
||||
let d1: T = rrdata_de(data).context("couldn't parse binary record")?;
|
||||
let d2: T = text::parse(txt).context("couldn't parse text record")?;
|
||||
ensure!(d1 == d2, "decoded data not equal: {:?} != {:?}", d1, d2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a() {
|
||||
check::<structs::A>("127.0.0.1", b"\x7f\x00\x00\x01");
|
||||
check::<structs::A>("127.0.0.1", b"\x7f\x00\x00\x01").unwrap();
|
||||
}
|
||||
|
||||
fn test_txt_for<T>()
|
||||
where
|
||||
T: StaticRRData + fmt::Debug + PartialEq
|
||||
{
|
||||
// at least one "segment" (which could be empty)
|
||||
check2::<T>(r#" "" "#, b"", r#" "" "#).unwrap_err();
|
||||
check2::<T>(r#""#, b"\x00", r#" "" "#).unwrap_err();
|
||||
// one empty segment
|
||||
check2::<T>(r#" "" "#, b"\x00", r#" "" "#).unwrap();
|
||||
// one segment
|
||||
check::<T>(r#" "foo" "#, b"\x03foo").unwrap();
|
||||
// two segments
|
||||
check::<T>(r#" "foo" "bar!" "#, b"\x03foo\x04bar!").unwrap();
|
||||
// segment with too many bytes in text form
|
||||
{
|
||||
let mut s = String::new();
|
||||
s.push('"');
|
||||
for _ in 0..256 { s.push('a'); }
|
||||
s.push('"');
|
||||
text::parse::<T>(&s).unwrap_err();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_txt() {
|
||||
test_txt_for::<structs::TXT>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ds() {
|
||||
check::<structs::DS>(" 1 2 3 ", b"\x00\x01\x02\x03").unwrap();
|
||||
check::<structs::DS>(" 1 2 3 abcd", b"\x00\x01\x02\x03\xab\xcd").unwrap();
|
||||
check::<structs::DS>(" 1 2 3 a b c d", b"\x00\x01\x02\x03\xab\xcd").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nsec() {
|
||||
check::<structs::NSEC>("foo.bar. ", b"\x03foo\x03bar\x00").unwrap();
|
||||
check::<structs::NSEC>("foo.bar. A NS ", b"\x03foo\x03bar\x00\x00\x01\x60").unwrap();
|
||||
check::<structs::NSEC>("foo.bar. A NS TYPE256 TYPE65280 ", b"\x03foo\x03bar\x00\x00\x01\x60\x01\x01\x80\xff\x01\x80").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dnskey() {
|
||||
check::<structs::DNSKEY>("256 2 3", b"\x01\x00\x02\x03").unwrap();
|
||||
check::<structs::DNSKEY>("256 2 3 /w==", b"\x01\x00\x02\x03\xff").unwrap();
|
||||
check::<structs::DNSKEY>("256 2 3 /w ==", b"\x01\x00\x02\x03\xff").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nsec3() {
|
||||
check::<structs::NSEC3>("1 2 300 - vs", b"\x01\x02\x01\x2c\x00\x01\xff").unwrap();
|
||||
check::<structs::NSEC3>("1 2 300 - vs A NS", b"\x01\x02\x01\x2c\x00\x01\xff\x00\x01\x60").unwrap();
|
||||
check::<structs::NSEC3>("1 2 300 ab vs A NS", b"\x01\x02\x01\x2c\x01\xab\x01\xff\x00\x01\x60").unwrap();
|
||||
|
||||
// invalid base32 texts
|
||||
text::parse::<structs::NSEC3>("1 2 300 - v").unwrap_err();
|
||||
text::parse::<structs::NSEC3>("1 2 300 - vv").unwrap_err();
|
||||
|
||||
// invalid (empty) next-hashed values
|
||||
packet::deserialize::<structs::NSEC3>(Bytes::from_static(b"\x01\x02\x01\x2c\x00\x00")).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nsec3param() {
|
||||
check::<structs::NSEC3PARAM>("1 2 300 -", b"\x01\x02\x01\x2c\x00").unwrap();
|
||||
check::<structs::NSEC3PARAM>("1 2 300 ab", b"\x01\x02\x01\x2c\x01\xab").unwrap();
|
||||
// `salt` hex string must not contain spaces
|
||||
text::parse::<structs::NSEC3PARAM>("1 2 300 a b").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spf() {
|
||||
test_txt_for::<structs::SPF>();
|
||||
}
|
||||
|
47
lib/dnsbox-base/src/records/unknown.rs
Normal file
47
lib/dnsbox-base/src/records/unknown.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use bytes::Bytes;
|
||||
use common_types::*;
|
||||
use failure::{ResultExt, Fail};
|
||||
use ser::text::{DnsTextFormatter, next_field};
|
||||
use ser::packet::remaining_bytes;
|
||||
use common_types::binary::HEXLOWER_PERMISSIVE_ALLOW_WS;
|
||||
use std::fmt;
|
||||
use errors::*;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct UnknownRecord {
|
||||
rr_type: Type,
|
||||
raw: Bytes,
|
||||
}
|
||||
|
||||
impl UnknownRecord {
|
||||
pub fn new(rr_type: Type, raw: Bytes) -> Self {
|
||||
UnknownRecord {
|
||||
rr_type: rr_type,
|
||||
raw: raw,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize(rr_type: Type, data: &mut ::std::io::Cursor<Bytes>) -> Result<Self> {
|
||||
Ok(UnknownRecord::new(rr_type, remaining_bytes(data)))
|
||||
}
|
||||
|
||||
pub fn dns_parse(rr_type: Type, data: &mut &str) -> Result<Self> {
|
||||
let field = next_field(data)?;
|
||||
ensure!(field == r"\#", "expect \\# token to mark generic encoding");
|
||||
let field = next_field(data).context("generic record data length")?;
|
||||
let len: usize = field.parse()?;
|
||||
|
||||
let result = HEXLOWER_PERMISSIVE_ALLOW_WS.decode(data.as_bytes())
|
||||
.with_context(|e| e.context(format!("invalid hex: {:?}", data)))?;
|
||||
ensure!(len == result.len(), "length {} doesn't match length of encoded data {}", len, result.len());
|
||||
|
||||
Ok(UnknownRecord {
|
||||
rr_type,
|
||||
raw: result.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
||||
write!(f, "TYPE{} \\# {} {}", self.rr_type.0, self.raw.len(), HEXLOWER_PERMISSIVE_ALLOW_WS.encode(&self.raw))
|
||||
}
|
||||
}
|
153
lib/dnsbox-base/src/records/weird_structs.rs
Normal file
153
lib/dnsbox-base/src/records/weird_structs.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use bytes::{Bytes, Buf};
|
||||
use common_types::*;
|
||||
use failure::ResultExt;
|
||||
use ser::DnsPacketData;
|
||||
use ser::text::{DnsTextData, DnsTextFormatter};
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
// deriving RRData will add a unit test to make sure the type is
|
||||
// registered; there must be a records::types::$name `Type` constant
|
||||
// with the same name as the struct.
|
||||
|
||||
// class independent
|
||||
#[derive(Clone, PartialEq, Eq, Debug, RRData)]
|
||||
pub enum LOC {
|
||||
Version0(LOC0),
|
||||
UnknownVersion{
|
||||
version: u8,
|
||||
data: Bytes,
|
||||
},
|
||||
}
|
||||
|
||||
impl DnsPacketData for LOC {
|
||||
fn deserialize(data: &mut ::std::io::Cursor<Bytes>) -> ::errors::Result<Self> {
|
||||
let version: u8 = DnsPacketData::deserialize(data)?;
|
||||
if 0 == version {
|
||||
Ok(LOC::Version0(DnsPacketData::deserialize(data)?))
|
||||
} else {
|
||||
Ok(LOC::UnknownVersion{
|
||||
version: version,
|
||||
data: ::ser::packet::remaining_bytes(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsTextData for LOC {
|
||||
fn dns_parse(_data: &mut &str) -> ::errors::Result<Self> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn dns_format(&self, _f: &mut DnsTextFormatter) -> fmt::Result {
|
||||
// always prefer binary representation
|
||||
Err(fmt::Error)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData)]
|
||||
pub struct LOC0 {
|
||||
size: u8,
|
||||
horizontal_precision: u8,
|
||||
vertical_precision: u8,
|
||||
latitude: u32,
|
||||
longitude: u32,
|
||||
altitude: u32,
|
||||
}
|
||||
|
||||
// class IN
|
||||
#[derive(Clone, PartialEq, Eq, Debug, RRData)]
|
||||
pub struct A6 {
|
||||
prefix: u8, // [0...128]
|
||||
// might include non-zero padding
|
||||
dirty_suffix: Ipv6Addr,
|
||||
suffix: Ipv6Addr,
|
||||
prefix_name: Option<DnsCanonicalName>,
|
||||
}
|
||||
|
||||
impl DnsPacketData for A6 {
|
||||
fn deserialize(data: &mut ::std::io::Cursor<Bytes>) -> ::errors::Result<Self> {
|
||||
let prefix: u8 = DnsPacketData::deserialize(data)
|
||||
.context("failed parsing field A6::prefix")?;
|
||||
ensure!(prefix <= 128, "invalid A6::prefix {}", prefix);
|
||||
let suffix_offset = (prefix / 8) as usize;
|
||||
debug_assert!(suffix_offset <= 16);
|
||||
let suffix_len = 16 - suffix_offset;
|
||||
check_enough_data!(data, suffix_len, "A6::suffix");
|
||||
let mut addr = [0u8; 16];
|
||||
data.read_exact(&mut addr[suffix_offset..16])?;
|
||||
|
||||
let dirty_suffix = Ipv6Addr::from(addr);
|
||||
if suffix_offset < 16 {
|
||||
let mask = 0xff >> (prefix % 8);
|
||||
addr[suffix_offset] &= mask;
|
||||
}
|
||||
let suffix = Ipv6Addr::from(addr);
|
||||
|
||||
let prefix_name = if data.has_remaining() {
|
||||
Some(DnsPacketData::deserialize(data)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(A6 {
|
||||
prefix,
|
||||
dirty_suffix,
|
||||
suffix,
|
||||
prefix_name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsTextData for A6 {
|
||||
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
|
||||
let prefix: u8 = DnsTextData::dns_parse(data)
|
||||
.context("failed parsing field A6::prefix")?;
|
||||
ensure!(prefix <= 128, "invalid A6::prefix {}", prefix);
|
||||
|
||||
let suffix_offset = (prefix / 8) as usize;
|
||||
debug_assert!(suffix_offset <= 16);
|
||||
|
||||
let suffix: Ipv6Addr = DnsTextData::dns_parse(data)
|
||||
.context("failed parsing field A6::suffix")?;
|
||||
|
||||
// clear prefix bits
|
||||
let mut suffix = suffix.octets();
|
||||
for i in 0..suffix_offset {
|
||||
suffix[i] = 0;
|
||||
}
|
||||
if suffix_offset < 16 {
|
||||
let mask = 0xff >> (prefix % 8);
|
||||
suffix[suffix_offset] &= mask;
|
||||
}
|
||||
let suffix = Ipv6Addr::from(suffix);
|
||||
|
||||
let prefix_name = if !data.is_empty() {
|
||||
Some(DnsTextData::dns_parse(data)
|
||||
.context("failed parsing field A6::prefix_name")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(A6 {
|
||||
prefix,
|
||||
dirty_suffix: suffix.clone(),
|
||||
suffix,
|
||||
prefix_name,
|
||||
})
|
||||
}
|
||||
|
||||
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
||||
if self.dirty_suffix != self.suffix {
|
||||
// parsing text clears the padding, so we would loose data.
|
||||
// use binary representation instead.
|
||||
return Err(fmt::Error);
|
||||
}
|
||||
write!(f, "{} {}", self.prefix, self.suffix)?;
|
||||
if let Some(ref prefix_name) = self.prefix_name {
|
||||
write!(f, "{}", prefix_name)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -4,4 +4,4 @@ mod rrdata;
|
||||
|
||||
pub use self::packet::DnsPacketData;
|
||||
pub use self::text::DnsTextData;
|
||||
pub use self::rrdata::RRData;
|
||||
pub use self::rrdata::{RRDataPacket, RRData, StaticRRData};
|
||||
|
@ -19,3 +19,21 @@ where
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn remaining_bytes(data: &mut Cursor<Bytes>) -> Bytes {
|
||||
let pos = data.position() as usize;
|
||||
let len = data.remaining();
|
||||
let result = data.get_ref().slice(pos, pos + len);
|
||||
data.advance(len);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn short_blob(data: &mut Cursor<Bytes>) -> Result<Bytes> {
|
||||
check_enough_data!(data, 1, "short blob length");
|
||||
let blob_len = data.get_u8() as usize;
|
||||
check_enough_data!(data, blob_len, "short blob content");
|
||||
let pos = data.position() as usize;
|
||||
let blob = data.get_ref().slice(pos, pos + blob_len);
|
||||
data.advance(blob_len);
|
||||
Ok(blob)
|
||||
}
|
||||
|
@ -1,8 +1,24 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use bytes::Bytes;
|
||||
use common_types::Type;
|
||||
use errors::*;
|
||||
use std::io::Cursor;
|
||||
use ser::DnsPacketData;
|
||||
|
||||
pub trait RRData: super::DnsPacketData {
|
||||
fn rr_type() -> Type;
|
||||
fn rr_type_name() -> Cow<'static, str>;
|
||||
pub trait RRDataPacket: Sized {
|
||||
fn deserialize_rr_data(ttl: u32, rr_class: u16, rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl<T: DnsPacketData> RRDataPacket for T {
|
||||
fn deserialize_rr_data(_ttl: u32, _rr_class: u16, _rr_type: Type, data: &mut Cursor<Bytes>) -> Result<Self> {
|
||||
T::deserialize(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RRData: RRDataPacket + super::DnsTextData {
|
||||
fn rr_type(&self) -> Type;
|
||||
}
|
||||
|
||||
pub trait StaticRRData: RRData {
|
||||
const TYPE: Type;
|
||||
const NAME: &'static str;
|
||||
}
|
||||
|
@ -1,4 +1,112 @@
|
||||
pub trait DnsTextData: Sized {
|
||||
fn dns_parse(data: &str) -> ::errors::Result<Self>;
|
||||
fn dns_format(&self) -> ::errors::Result<String>;
|
||||
use std::fmt;
|
||||
|
||||
mod std_impls;
|
||||
pub mod quoted;
|
||||
|
||||
pub fn skip_whitespace(data: &mut &str) {
|
||||
*data = (*data).trim_left();
|
||||
}
|
||||
|
||||
pub fn next_field<'a>(data: &mut &'a str) -> ::errors::Result<&'a str> {
|
||||
*data = (*data).trim_left();
|
||||
if data.is_empty() { bail!("missing field"); }
|
||||
match data.find(char::is_whitespace) {
|
||||
None => {
|
||||
let result = *data;
|
||||
*data = "";
|
||||
Ok(result)
|
||||
},
|
||||
Some(next) => {
|
||||
let result = &(*data)[..next];
|
||||
*data = &(*data)[next..].trim_left();
|
||||
Ok(result)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_quoted_field(data: &mut &str) -> ::errors::Result<Vec<u8>> {
|
||||
*data = (*data).trim_left();
|
||||
if data.is_empty() { bail!("missing field"); }
|
||||
|
||||
let result = quoted::UnquoteIterator::new(data).collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn quote(data: &[u8]) -> String {
|
||||
let mut result = String::with_capacity(data.len() + 2);
|
||||
result.push('"');
|
||||
for qc in quoted::EncodeIterator::new_quoted(data) {
|
||||
result += &qc;
|
||||
}
|
||||
result.push('"');
|
||||
result
|
||||
}
|
||||
|
||||
// also escapes whitespace, but doesn't use quotes to surround it
|
||||
pub fn escape(data: &[u8]) -> String {
|
||||
let mut result = String::with_capacity(data.len());
|
||||
for qc in quoted::EncodeIterator::new_encode_whitespace(data) {
|
||||
result += &qc;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Each call to write!() makes sure a space is emitted to separate it
|
||||
/// from previous fields.
|
||||
pub struct DnsTextFormatter<'a> {
|
||||
f: &'a mut fmt::Formatter<'a>,
|
||||
need_space: bool,
|
||||
}
|
||||
|
||||
impl<'a> DnsTextFormatter<'a> {
|
||||
pub fn new(f: &'a mut fmt::Formatter<'a>) -> Self {
|
||||
DnsTextFormatter {
|
||||
f: f,
|
||||
need_space: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// make sure a field separator was emitted
|
||||
pub fn next_field(&mut self) -> fmt::Result {
|
||||
if self.need_space {
|
||||
write!(self.f, " ")?;
|
||||
self.need_space = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// a field was emitted through `inner`; next field needs a separator
|
||||
pub fn end_field(&mut self) {
|
||||
self.need_space = true;
|
||||
}
|
||||
|
||||
/// direct access to underlying output; you'll need to call
|
||||
/// `next_field` and `end_field` manually.
|
||||
pub fn inner(&mut self) -> &mut fmt::Formatter<'a> {
|
||||
self.f
|
||||
}
|
||||
|
||||
pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
|
||||
self.next_field()?;
|
||||
self.f.write_fmt(args)?;
|
||||
self.end_field();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DnsTextData: Sized {
|
||||
fn dns_parse(data: &mut &str) -> ::errors::Result<Self>;
|
||||
// format might fail if there is no (known) text representation.
|
||||
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result;
|
||||
}
|
||||
|
||||
pub fn parse<T>(data: &str) -> ::errors::Result<T>
|
||||
where
|
||||
T: DnsTextData
|
||||
{
|
||||
let mut data = data;
|
||||
let result = T::dns_parse(&mut data)?;
|
||||
let data = data.trim();
|
||||
ensure!(data.is_empty(), "didn't parse complete text, remaining: {:?}", data);
|
||||
Ok(result)
|
||||
}
|
||||
|
225
lib/dnsbox-base/src/ser/text/quoted.rs
Normal file
225
lib/dnsbox-base/src/ser/text/quoted.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use std::fmt;
|
||||
|
||||
pub struct EncodedByte {
|
||||
storage: [u8; 4], // max: `\000`
|
||||
used: u8,
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for EncodedByte {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
::unsafe_ops::from_utf8_unchecked(&self.storage[..self.used as usize])
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EncodeIterator<'a> {
|
||||
encode_whitespace: bool,
|
||||
data: &'a [u8]
|
||||
}
|
||||
|
||||
impl<'a> EncodeIterator<'a> {
|
||||
pub fn new_quoted(value: &'a [u8]) -> Self {
|
||||
EncodeIterator{
|
||||
encode_whitespace: false,
|
||||
data: value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_encode_whitespace(value: &'a [u8]) -> Self {
|
||||
EncodeIterator{
|
||||
encode_whitespace: true,
|
||||
data: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for EncodeIterator<'a> {
|
||||
type Item = EncodedByte;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.data.is_empty() { return None; }
|
||||
let b = self.data[0];
|
||||
self.data = &self.data[1..];
|
||||
if b < 32 || b > 127 || (self.encode_whitespace && is_ascii_whitespace(b)) {
|
||||
// `\ddd`
|
||||
let d1 = b / 100;
|
||||
let d2 = (b / 10) % 10;
|
||||
let d3 = b % 10;
|
||||
Some(EncodedByte{
|
||||
storage: [b'\\', b'0' + d1, b'0' + d2, b'0' + d3],
|
||||
used: 4,
|
||||
})
|
||||
} else if b == b'"' || b == b'\\' {
|
||||
// `\c`
|
||||
Some(EncodedByte{
|
||||
storage: [b'\\', b, 0, 0],
|
||||
used: 2,
|
||||
})
|
||||
} else {
|
||||
Some(EncodedByte{
|
||||
storage: [b, 0, 0, 0],
|
||||
used: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnquoteError {
|
||||
data: String,
|
||||
position: usize,
|
||||
msg: &'static str,
|
||||
}
|
||||
|
||||
impl fmt::Display for UnquoteError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "unquote error at position {} in {:?}: {}", self.position, self.data, self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::failure::Fail for UnquoteError {}
|
||||
|
||||
pub struct UnquoteIterator<'a, 'b: 'a> {
|
||||
quoted: bool,
|
||||
data: &'a mut &'b str,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
/// when walked to end without hitting errors between, the terminating
|
||||
/// `"` and following whitespace will be removed from `*data`.
|
||||
impl<'a, 'b: 'a> UnquoteIterator<'a, 'b> {
|
||||
pub fn new(data: &'a mut &'b str) -> Self {
|
||||
UnquoteIterator {
|
||||
quoted: false,
|
||||
data: data,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn err<T>(&mut self, msg: &'static str) -> Option<Result<T, UnquoteError>> {
|
||||
Some(Err(UnquoteError{
|
||||
data: (*self.data).into(),
|
||||
position: self.pos,
|
||||
msg: msg,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_ascii_whitespace(c: u8) -> bool {
|
||||
match c {
|
||||
0x09 => true, // horizontal tab: \t
|
||||
0x0a => true, // line feed: \n
|
||||
0x0c => true, // form feed: \f
|
||||
0x0d => true, // form feed: \r
|
||||
0x20 => true, // space: ' '
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> Iterator for UnquoteIterator<'a, 'b> {
|
||||
type Item = Result<u8, UnquoteError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let raw = self.data.as_bytes();
|
||||
|
||||
if raw.is_empty() { return self.err("empty input"); }
|
||||
|
||||
if 0 == self.pos {
|
||||
// check for starting quote:
|
||||
if raw[0] == b'"' {
|
||||
self.quoted = true;
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if self.pos >= raw.len() {
|
||||
if self.quoted {
|
||||
return self.err("unexpected end of string");
|
||||
} else {
|
||||
*self.data = "";
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if raw[self.pos] == b'"' {
|
||||
if self.quoted {
|
||||
// eat terminating quote
|
||||
// pos+1 is obviously a good utf-8 boundary
|
||||
*self.data = self.data[self.pos+1..].trim_left();
|
||||
return None;
|
||||
} else {
|
||||
return self.err("quote in the middle of unquoted string");
|
||||
}
|
||||
} else if !self.quoted && is_ascii_whitespace(raw[self.pos]) {
|
||||
// pos is obviously a good utf-8 boundary
|
||||
*self.data = self.data[self.pos..].trim_left();
|
||||
return None;
|
||||
} else if raw[self.pos] == b'\\' {
|
||||
if self.pos + 1 >= raw.len() { return self.err("unexpected end of string after backslash"); }
|
||||
if raw[self.pos+1] < b'0' || raw[self.pos+1] > b'9' {
|
||||
let result = raw[self.pos+1];
|
||||
if !self.quoted && is_ascii_whitespace(result) {
|
||||
return self.err("(escaped) whitespace not allowed in unquoted field");
|
||||
}
|
||||
self.pos += 2;
|
||||
return Some(Ok(result));
|
||||
}
|
||||
// otherwise require 3 decimal digits
|
||||
if self.pos + 3 >= raw.len() { return self.err("unexpected end of string after backslash with decimal"); }
|
||||
// raw[self.pos+1] already checked for digit above
|
||||
if raw[self.pos+2] < b'0' || raw[self.pos+2] > b'9' || raw[self.pos+3] < b'0' || raw[self.pos+3] > b'9' {
|
||||
return self.err("expecting 3 digits after backslash with decimal");
|
||||
}
|
||||
let d1 = raw[self.pos+1] - b'0';
|
||||
let d2 = raw[self.pos+2] - b'0';
|
||||
let d3 = raw[self.pos+3] - b'0';
|
||||
let val = (d1 as u32 * 100) + (d2 as u32 * 10) + (d3 as u32);
|
||||
if val > 255 { return self.err("invalid decimal escape"); }
|
||||
self.pos += 4;
|
||||
Some(Ok(val as u8))
|
||||
} else {
|
||||
let result = raw[self.pos];
|
||||
self.pos += 1;
|
||||
Some(Ok(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ser::text::{next_quoted_field, quote};
|
||||
|
||||
fn check_quote(data: &[u8], quoted: &str) {
|
||||
assert_eq!(
|
||||
quote(data),
|
||||
quoted
|
||||
);
|
||||
}
|
||||
|
||||
fn check_unquote(mut input: &str, data: &[u8]) {
|
||||
assert_eq!(
|
||||
next_quoted_field(&mut input).unwrap(),
|
||||
data
|
||||
);
|
||||
assert!(input.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escapes() {
|
||||
check_quote(b"\"hello \\ \xc3\xa4", r#""\"hello \\ \195\164""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
check_unquote(r#""\"hello \\ \195\164""#, b"\"hello \\ \xc3\xa4");
|
||||
check_unquote(r#" "\"hello \\ \195\164" "#, b"\"hello \\ \xc3\xa4");
|
||||
check_unquote(r#""\"hello \\ ä""#, b"\"hello \\ \xc3\xa4");
|
||||
check_unquote(r#" "\"hello \\ ä" "#, b"\"hello \\ \xc3\xa4");
|
||||
// unquoted input
|
||||
check_unquote(r#"foobarä"#, b"foobar\xc3\xa4");
|
||||
check_unquote(r#"foobar\195\164"#, b"foobar\xc3\xa4");
|
||||
check_unquote(r#" foobarä "#, b"foobar\xc3\xa4");
|
||||
// random (unnecessary) escapes:
|
||||
check_unquote(r#" "\x\%\@\." "#, b"x%@.");
|
||||
}
|
||||
}
|
100
lib/dnsbox-base/src/ser/text/std_impls.rs
Normal file
100
lib/dnsbox-base/src/ser/text/std_impls.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use std::fmt;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use ser::text::{DnsTextData, DnsTextFormatter, next_field};
|
||||
|
||||
/* only decimal representations are used for numbers */
|
||||
impl DnsTextData for u8 {
|
||||
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
|
||||
Ok(next_field(data)?.parse()?)
|
||||
}
|
||||
|
||||
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsTextData for u16 {
|
||||
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
|
||||
Ok(next_field(data)?.parse()?)
|
||||
}
|
||||
|
||||
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsTextData for u32 {
|
||||
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
|
||||
Ok(next_field(data)?.parse()?)
|
||||
}
|
||||
|
||||
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/* only decimal representations are needed for octets */
|
||||
impl DnsTextData for Ipv4Addr {
|
||||
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
|
||||
Ok(next_field(data)?.parse()?)
|
||||
}
|
||||
|
||||
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/* representation as in RFC 3513: https://tools.ietf.org/html/rfc3513#section-2.2 */
|
||||
impl DnsTextData for Ipv6Addr {
|
||||
fn dns_parse(data: &mut &str) -> ::errors::Result<Self> {
|
||||
Ok(next_field(data)?.parse()?)
|
||||
}
|
||||
|
||||
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn deserialize<T: DnsTextData>(data: &str) -> ::errors::Result<T> {
|
||||
let mut data = data;
|
||||
let res = T::dns_parse(&mut data)?;
|
||||
let data = data.trim();
|
||||
ensure!(data.is_empty(), "didn't read data completely");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ipv6() {
|
||||
assert_eq!(
|
||||
deserialize::<Ipv6Addr>("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210").unwrap(),
|
||||
Ipv6Addr::new(
|
||||
0xfedc, 0xba98, 0x7654, 0x3210, 0xfedc, 0xba98, 0x7654, 0x3210
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
deserialize::<Ipv6Addr>("1080::8:800:200C:417A").unwrap(),
|
||||
Ipv6Addr::new(
|
||||
0x1080, 0, 0, 0, 0x8, 0x800, 0x200c, 0x417a
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
deserialize::<Ipv6Addr>("::13.1.68.3").unwrap(),
|
||||
Ipv6Addr::new(
|
||||
0, 0, 0, 0, 0, 0, 0x0d01, 0x4403
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
deserialize::<Ipv6Addr>("::FFFF:129.144.52.38").unwrap(),
|
||||
Ipv6Addr::new(
|
||||
0, 0, 0, 0, 0, 0xffff, 0x8190, 0x3426
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
11
lib/dnsbox-base/src/unsafe_ops/mod.rs
Normal file
11
lib/dnsbox-base/src/unsafe_ops/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
#[cfg(not(feature = "no-unsafe"))]
|
||||
pub fn from_utf8_unchecked(v: &[u8]) -> &str {
|
||||
unsafe {
|
||||
::std::str::from_utf8_unchecked(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "no-unsafe")]
|
||||
pub fn from_utf8_unchecked(v: &[u8]) -> &str {
|
||||
::std::str::from_utf8(v).expect("from_utf8_unchecked")
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use syn;
|
||||
use quote;
|
||||
|
||||
pub fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||
pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||
let fields = match ast.body {
|
||||
syn::Body::Enum(_) => panic!("Deriving DnsPacketData not supported for enum types"),
|
||||
syn::Body::Struct(syn::VariantData::Struct(ref s)) => s,
|
||||
@ -25,9 +25,10 @@ pub fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||
let mut parse_fields = quote!{};
|
||||
for field in fields {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let ctx_msg = format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name));
|
||||
|
||||
parse_fields = quote!{#parse_fields
|
||||
#field_name: DnsPacketData::deserialize(_data).with_context(|_| format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name)))?,
|
||||
#field_name: DnsPacketData::deserialize(_data).context(#ctx_msg)?,
|
||||
};
|
||||
}
|
||||
|
||||
|
56
lib/dnsbox-derive/src/dns_text_data.rs
Normal file
56
lib/dnsbox-derive/src/dns_text_data.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use syn;
|
||||
use quote;
|
||||
|
||||
pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||
let fields = match ast.body {
|
||||
syn::Body::Enum(_) => panic!("Deriving DnsTextData not supported for enum types"),
|
||||
syn::Body::Struct(syn::VariantData::Struct(ref s)) => s,
|
||||
syn::Body::Struct(_) => panic!("Deriving DnsTextData not supported for unit / tuple struct types"),
|
||||
};
|
||||
|
||||
if !ast.generics.ty_params.is_empty() {
|
||||
panic!("Type parameters not supported for deriving DnsTextData");
|
||||
}
|
||||
|
||||
if !ast.generics.where_clause.predicates.is_empty() {
|
||||
panic!("Where clauses not supported for deriving DnsTextData");
|
||||
}
|
||||
|
||||
if !ast.generics.lifetimes.is_empty() {
|
||||
panic!("Lifetimes not supported for deriving DnsTextData");
|
||||
}
|
||||
|
||||
let name = &ast.ident;
|
||||
|
||||
let mut parse_fields = quote!{};
|
||||
let mut format_fields = quote!{};
|
||||
for field in fields {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
|
||||
parse_fields = quote!{#parse_fields
|
||||
#field_name: DnsTextData::dns_parse(_data).with_context(|_| format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name)))?,
|
||||
};
|
||||
|
||||
format_fields = quote!{#format_fields
|
||||
DnsTextData::dns_format(&self.#field_name, f)?;
|
||||
};
|
||||
}
|
||||
|
||||
quote!{
|
||||
#[allow(unused_imports)]
|
||||
impl ::dnsbox_base::ser::DnsTextData for #name {
|
||||
fn dns_parse(_data: &mut &str) -> ::dnsbox_base::errors::Result<Self> {
|
||||
use dnsbox_base::failure::ResultExt;
|
||||
use dnsbox_base::ser::DnsTextData;
|
||||
Ok(#name{ #parse_fields })
|
||||
}
|
||||
|
||||
fn dns_format(&self, f: &mut ::dnsbox_base::ser::text::DnsTextFormatter) -> ::std::fmt::Result {
|
||||
use dnsbox_base::ser::DnsTextData;
|
||||
use std::fmt::{self, Write};
|
||||
#format_fields
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
#![recursion_limit="128"]
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate syn;
|
||||
#[macro_use]
|
||||
@ -6,26 +8,56 @@ extern crate quote;
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod dns_packet_data;
|
||||
mod dns_text_data;
|
||||
mod rrdata;
|
||||
|
||||
fn attr_get_single_list_arg(attr: &syn::Attribute) -> quote::Tokens {
|
||||
match attr.value {
|
||||
syn::MetaItem::List(_, ref l) => {
|
||||
if l.len() != 1 {
|
||||
panic!("{:?} attribute requires exactly one argument", attr.name());
|
||||
}
|
||||
let arg = &l[0];
|
||||
quote!{#arg}
|
||||
},
|
||||
syn::MetaItem::NameValue(_, ref l) => {
|
||||
quote!{#l}
|
||||
},
|
||||
_ => {
|
||||
panic!("{:?} argument requires one argument like: [#{}(...)]", attr.name(), attr.name());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(DnsPacketData)]
|
||||
pub fn derive_dns_packet_data(input: TokenStream) -> TokenStream {
|
||||
let s = input.to_string();
|
||||
|
||||
let ast = syn::parse_derive_input(&s).unwrap();
|
||||
|
||||
let gen = dns_packet_data::impl_hello_world(&ast);
|
||||
let gen = dns_packet_data::build(&ast);
|
||||
|
||||
gen.parse().unwrap()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(RRData)]
|
||||
#[proc_macro_derive(DnsTextData)]
|
||||
pub fn derive_dns_text_data(input: TokenStream) -> TokenStream {
|
||||
let s = input.to_string();
|
||||
|
||||
let ast = syn::parse_derive_input(&s).unwrap();
|
||||
|
||||
let gen = dns_text_data::build(&ast);
|
||||
|
||||
gen.parse().unwrap()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(RRData, attributes(RRTypeName))]
|
||||
pub fn derive_rr_data(input: TokenStream) -> TokenStream {
|
||||
let s = input.to_string();
|
||||
|
||||
let ast = syn::parse_derive_input(&s).unwrap();
|
||||
|
||||
let gen = rrdata::impl_rr_data(&ast);
|
||||
let gen = rrdata::build(&ast);
|
||||
|
||||
gen.parse().unwrap()
|
||||
}
|
||||
|
@ -1,7 +1,34 @@
|
||||
use syn;
|
||||
use quote;
|
||||
|
||||
pub fn impl_rr_data(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||
use super::{attr_get_single_list_arg};
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
enum StructAttribute {
|
||||
RRTypeName(quote::Tokens),
|
||||
}
|
||||
|
||||
struct StructAttributeParser<'a>(pub &'a [syn::Attribute]);
|
||||
impl<'a> Iterator for StructAttributeParser<'a> {
|
||||
type Item = StructAttribute;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while !self.0.is_empty() {
|
||||
let a = &self.0[0];
|
||||
self.0 = &self.0[1..];
|
||||
if a.is_sugared_doc { continue; }
|
||||
match a.value.name() {
|
||||
"RRTypeName" => {
|
||||
return Some(StructAttribute::RRTypeName(attr_get_single_list_arg(a)));
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||
if !ast.generics.ty_params.is_empty() {
|
||||
panic!("Type parameters not supported for deriving RRData");
|
||||
}
|
||||
@ -15,31 +42,44 @@ pub fn impl_rr_data(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||
}
|
||||
|
||||
let name = &ast.ident;
|
||||
let name_str: &str = name.as_ref();
|
||||
let mut name_str = {
|
||||
let name_str: &str = name.as_ref();
|
||||
quote!{#name_str}
|
||||
};
|
||||
|
||||
for attr in StructAttributeParser(&ast.attrs) {
|
||||
match attr {
|
||||
StructAttribute::RRTypeName(name) => {
|
||||
name_str = name;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let test_mod_name = syn::Ident::from(format!("test_rr_{}", name));
|
||||
|
||||
quote!{
|
||||
impl ::dnsbox_base::ser::RRData for #name {
|
||||
fn rr_type() -> ::dnsbox_base::common_types::Type {
|
||||
::dnsbox_base::records::types::#name
|
||||
fn rr_type(&self) -> ::dnsbox_base::common_types::Type {
|
||||
::dnsbox_base::common_types::types::#name
|
||||
}
|
||||
}
|
||||
|
||||
fn rr_type_name() -> ::std::borrow::Cow<'static, str> {
|
||||
::std::borrow::Cow::Borrowed(#name_str)
|
||||
}
|
||||
impl ::dnsbox_base::ser::StaticRRData for #name {
|
||||
const TYPE: ::dnsbox_base::common_types::Type = ::dnsbox_base::common_types::types::#name;
|
||||
const NAME: &'static str = #name_str;
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
#[allow(non_snake_case, unused_imports)]
|
||||
mod #test_mod_name {
|
||||
use ::dnsbox_base::records::registry;
|
||||
use ::dnsbox_base::records::types;
|
||||
use dnsbox_base::records::registry;
|
||||
use dnsbox_base::common_types::types;
|
||||
use dnsbox_base::ser::StaticRRData;
|
||||
|
||||
#[test]
|
||||
fn test_registry() {
|
||||
assert_eq!(
|
||||
registry::known_name_to_type(#name_str),
|
||||
registry::known_name_to_type(#name::NAME),
|
||||
Some(types::#name)
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user