From 63cd9196f9f6de384827a9715651db9f57cb8bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Sat, 16 Dec 2017 21:58:18 +0100 Subject: [PATCH] init --- .gitignore | 2 + Cargo.lock | 217 +++++++ Cargo.toml | 9 + lib/dnsbox-base/Cargo.toml | 13 + .../src/common_types/binary/mod.rs | 90 +++ lib/dnsbox-base/src/common_types/mod.rs | 9 + .../src/common_types/name/display.rs | 79 +++ .../src/common_types/name/label.rs | 240 +++++++ lib/dnsbox-base/src/common_types/name/mod.rs | 509 +++++++++++++++ .../src/common_types/name/name_mutations.rs | 265 ++++++++ .../src/common_types/name/name_parser.rs | 81 +++ .../src/common_types/rr_type/mod.rs | 8 + lib/dnsbox-base/src/common_types/text/mod.rs | 21 + lib/dnsbox-base/src/errors.rs | 42 ++ lib/dnsbox-base/src/lib.rs | 22 + lib/dnsbox-base/src/records/mod.rs | 8 + lib/dnsbox-base/src/records/registry.rs | 149 +++++ lib/dnsbox-base/src/records/structs.rs | 402 ++++++++++++ lib/dnsbox-base/src/records/tests.rs | 16 + lib/dnsbox-base/src/records/types.rs | 294 +++++++++ lib/dnsbox-base/src/ser/mod.rs | 7 + lib/dnsbox-base/src/ser/packet/mod.rs | 21 + lib/dnsbox-base/src/ser/packet/std_impls.rs | 64 ++ lib/dnsbox-base/src/ser/rrdata.rs | 8 + lib/dnsbox-base/src/ser/text/mod.rs | 4 + lib/dnsbox-derive/Cargo.toml | 11 + lib/dnsbox-derive/src/dns_packet_data.rs | 44 ++ lib/dnsbox-derive/src/lib.rs | 31 + lib/dnsbox-derive/src/rrdata.rs | 48 ++ old/name_old.rs | 584 ++++++++++++++++++ src/main.rs | 5 + 31 files changed, 3303 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 lib/dnsbox-base/Cargo.toml create mode 100644 lib/dnsbox-base/src/common_types/binary/mod.rs create mode 100644 lib/dnsbox-base/src/common_types/mod.rs create mode 100644 lib/dnsbox-base/src/common_types/name/display.rs create mode 100644 lib/dnsbox-base/src/common_types/name/label.rs create mode 100644 lib/dnsbox-base/src/common_types/name/mod.rs create mode 100644 lib/dnsbox-base/src/common_types/name/name_mutations.rs create mode 100644 lib/dnsbox-base/src/common_types/name/name_parser.rs create mode 100644 lib/dnsbox-base/src/common_types/rr_type/mod.rs create mode 100644 lib/dnsbox-base/src/common_types/text/mod.rs create mode 100644 lib/dnsbox-base/src/errors.rs create mode 100644 lib/dnsbox-base/src/lib.rs create mode 100644 lib/dnsbox-base/src/records/mod.rs create mode 100644 lib/dnsbox-base/src/records/registry.rs create mode 100644 lib/dnsbox-base/src/records/structs.rs create mode 100644 lib/dnsbox-base/src/records/tests.rs create mode 100644 lib/dnsbox-base/src/records/types.rs create mode 100644 lib/dnsbox-base/src/ser/mod.rs create mode 100644 lib/dnsbox-base/src/ser/packet/mod.rs create mode 100644 lib/dnsbox-base/src/ser/packet/std_impls.rs create mode 100644 lib/dnsbox-base/src/ser/rrdata.rs create mode 100644 lib/dnsbox-base/src/ser/text/mod.rs create mode 100644 lib/dnsbox-derive/Cargo.toml create mode 100644 lib/dnsbox-derive/src/dns_packet_data.rs create mode 100644 lib/dnsbox-derive/src/lib.rs create mode 100644 lib/dnsbox-derive/src/rrdata.rs create mode 100644 old/name_old.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ee83d18 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,217 @@ +[[package]] +name = "backtrace" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "dbghelp-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dnsbox" +version = "0.1.0" +dependencies = [ + "dnsbox-base 0.1.0", +] + +[[package]] +name = "dnsbox-base" +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)", + "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)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dnsbox-derive" +version = "0.1.0" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-demangle" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum backtrace 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "99f2ce94e22b8e664d95c57fff45b98a966c2252b60691d0b7aeeccd88d70983" +"checksum backtrace-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c63ea141ef8fdb10409d0f5daf30ac51f84ef43bff66f16627773d2a292cd189" +"checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" +"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 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" +"checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" +"checksum libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d1419b2939a0bc44b77feb34661583c7546b532b192feab36249ab584b86856c" +"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" +"checksum smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4f357e8cd37bf8822e1b964e96fd39e2cb5a0424f8aaa284ccaccc2162411c" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2feedad --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "dnsbox" +version = "0.1.0" +authors = ["Stefan Bühler "] + +[dependencies] +dnsbox-base = { path = "lib/dnsbox-base" } + +[workspace] diff --git a/lib/dnsbox-base/Cargo.toml b/lib/dnsbox-base/Cargo.toml new file mode 100644 index 0000000..e91aa32 --- /dev/null +++ b/lib/dnsbox-base/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dnsbox-base" +version = "0.1.0" +authors = ["Stefan Bühler "] + +[dependencies] +byteorder = "1.1.0" +bytes = "0.4" +dnsbox-derive = { path = "../dnsbox-derive" } +failure = "0.1.1" +lazy_static = "1.0.0" +log = "0.3" +smallvec = "0.4.4" diff --git a/lib/dnsbox-base/src/common_types/binary/mod.rs b/lib/dnsbox-base/src/common_types/binary/mod.rs new file mode 100644 index 0000000..e7242ef --- /dev/null +++ b/lib/dnsbox-base/src/common_types/binary/mod.rs @@ -0,0 +1,90 @@ +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) -> Result { + 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) -> Result { + 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); + +// RFC 1035 names this `One or more `. No following +// field allowed. +impl DnsPacketData for LongText { + fn deserialize(data: &mut Cursor) -> Result { + 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) -> Result { + 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) -> Result { + 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)) + } +} diff --git a/lib/dnsbox-base/src/common_types/mod.rs b/lib/dnsbox-base/src/common_types/mod.rs new file mode 100644 index 0000000..29b64e1 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/mod.rs @@ -0,0 +1,9 @@ +pub mod name; +pub mod text; +pub mod binary; +mod rr_type; + +pub use self::name::{DnsName, DnsCompressedName}; +pub use self::text::{ShortText}; +pub use self::binary::{HexShortBlob, Base32ShortBlob, LongText, RemainingText, HexRemainingBlob}; +pub use self::rr_type::Type; diff --git a/lib/dnsbox-base/src/common_types/name/display.rs b/lib/dnsbox-base/src/common_types/name/display.rs new file mode 100644 index 0000000..fc8e840 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/name/display.rs @@ -0,0 +1,79 @@ +use std::fmt; +use super::DnsLabelRef; + +/// Customize formatting of DNS labels +/// +/// The default uses "." as separator and adds a trailing separator. +/// +/// The `Debug` formatters just format as `Display` to a String and then +/// format that string as `Debug`. +#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)] +pub struct DisplayLabelsOptions { + /// separator to insert between (escaped) labels + pub separator: &'static str, + /// whether a trailing separator is added. + /// + /// without a trailing separator the root zone is represented as + /// empty string! + pub trailing: bool, +} + +impl Default for DisplayLabelsOptions { + fn default() -> Self { + DisplayLabelsOptions{ + separator: ".", + trailing: true, + } + } +} + +/// Wrap anything representing a collection of labels (`DnsLabelRef`) to +/// format using the given `options`. +/// +/// As name you can pass any cloneable `(Into)Iterator` with +/// `DnsLabelRef` items, e.g: +/// +/// * `&DnsName` +/// * `DnsNameIterator` +#[derive(Clone)] +pub struct DisplayLabels<'a, I> +where + I: IntoIterator>+Clone +{ + /// Label collection to iterate over + pub labels: I, + /// Options + pub options: DisplayLabelsOptions, +} + +impl<'a, I> fmt::Debug for DisplayLabels<'a, I> +where + I: IntoIterator>+Clone +{ + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + // just escape the display version1 + format!("{}", self).fmt(w) + } +} + +impl<'a, I> fmt::Display for DisplayLabels<'a, I> +where + I: IntoIterator>+Clone +{ + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + let mut i = self.labels.clone().into_iter(); + if let Some(first_label) = i.next() { + // first label + fmt::Display::fmt(&first_label, w)?; + // remaining labels + while let Some(label) = i.next() { + w.write_str(self.options.separator)?; + fmt::Display::fmt(&label, w)?; + } + } + if self.options.trailing { + w.write_str(self.options.separator)?; + } + Ok(()) + } +} diff --git a/lib/dnsbox-base/src/common_types/name/label.rs b/lib/dnsbox-base/src/common_types/name/label.rs new file mode 100644 index 0000000..66f7809 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/name/label.rs @@ -0,0 +1,240 @@ +use bytes::Bytes; +use errors::*; +use std::fmt; +use std::cmp::Ordering; + +#[inline] +fn check_label(label: &[u8]) -> Result<()> { + if label.len() == 0 { + bail!("label must not be empty") + } + if label.len() > 63 { + bail!("label must not be longer than 63 bytes") + } + Ok(()) +} + +/// A DNS label (any binary string with `0 < length < 64`) +#[derive(Clone)] +pub struct DnsLabel { + pub(super) label: Bytes, // 0 < len < 64 +} + +impl DnsLabel { + /// Create new label from existing storage + /// + /// Fails when the length doesn't match the requirement `0 < length < 64`. + pub fn new(label: Bytes) -> Result { + check_label(&label)?; + Ok(DnsLabel{label}) + } + + /// Convert to a representation without storage + pub fn as_ref<'a>(&'a self) -> DnsLabelRef<'a> { + DnsLabelRef{label: self.label.as_ref()} + } + + /// Access as raw bytes + pub fn as_bytes(&self) -> &Bytes { + &self.label + } + + /// Access as raw bytes + pub fn as_raw(&self) -> &[u8] { + &self.label + } + + /// Length of label (0 < len < 64) + pub fn len(&self) -> u8 { + self.label.len() as u8 + } + + /// compares two labels (ASCII case insensitive) + #[inline] + pub fn compare<'a, L: Into>>(&self, rhs: L) -> Ordering { + compare_ascii_lc(self.as_raw(), rhs.into().as_raw()) + } +} + +impl<'a> From> for DnsLabel { + fn from(label_ref: DnsLabelRef<'a>) -> Self { + DnsLabel{ + label: Bytes::from(label_ref.label), + } + } +} + +impl PartialEq for DnsLabel { + #[inline] + fn eq(&self, rhs: &DnsLabel) -> bool { + self.as_ref().eq(&rhs.as_ref()) + } +} + +impl<'a> PartialEq> for DnsLabel { + #[inline] + fn eq(&self, rhs: &DnsLabelRef<'a>) -> bool { + self.as_ref().eq(rhs) + } +} + +impl Eq for DnsLabel{} + +impl PartialOrd for DnsLabel { + #[inline] + fn partial_cmp(&self, rhs: &DnsLabel) -> Option { + Some(self.compare(rhs)) + } +} + +impl<'a> PartialOrd> for DnsLabel { + #[inline] + fn partial_cmp(&self, rhs: &DnsLabelRef<'a>) -> Option { + Some(self.compare(*rhs)) + } +} + +impl Ord for DnsLabel { + #[inline] + fn cmp(&self, rhs: &Self) -> Ordering { + self.compare(rhs) + } +} + +impl fmt::Debug for DnsLabel { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + self.as_ref().fmt(w) + } +} + +impl fmt::Display for DnsLabel { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + self.as_ref().fmt(w) + } +} + +/// A DNS label (any binary string with `0 < length < 64`) +/// +/// Storage is provided through lifetime. +#[derive(Clone,Copy)] +pub struct DnsLabelRef<'a> { + pub(super) label: &'a [u8], // 0 < len < 64 +} + +impl<'a> DnsLabelRef<'a> { + /// Create new label from existing storage + /// + /// Fails when the length doesn't match the requirement `0 < length < 64`. + pub fn new(label: &'a [u8]) -> Result { + check_label(label)?; + Ok(DnsLabelRef{label}) + } + + /// Access as raw bytes + pub fn as_raw(&self) -> &'a [u8] { + self.label + } + + /// Length of label (0 < len < 64) + pub fn len(&self) -> u8 { + self.label.len() as u8 + } + + /// compares two labels (ASCII case insensitive) + #[inline] + pub fn compare<'b, L: Into>>(&self, rhs: L) -> Ordering { + compare_ascii_lc(self.as_raw(), rhs.into().as_raw()) + } +} + +fn compare_ascii_lc(lhs: &[u8], rhs: &[u8]) -> Ordering { + use std::ascii::AsciiExt; + use std::cmp::min; + let l = min(lhs.len(), rhs.len()); + for i in 0..l { + let a : u8 = AsciiExt::to_ascii_lowercase(&lhs[i]); + let b : u8 = AsciiExt::to_ascii_lowercase(&rhs[i]); + match Ord::cmp(&a, &b) { + Ordering::Equal => continue, + c => return c, + } + } + Ord::cmp(&lhs.len(), &rhs.len()) +} + +impl<'a> From<&'a DnsLabel> for DnsLabelRef<'a> { + fn from(label: &'a DnsLabel) -> Self { + label.as_ref() + } +} + +impl<'a, 'b> PartialEq> for DnsLabelRef<'b> { + fn eq(&self, rhs: &DnsLabelRef<'a>) -> bool { + use std::ascii::AsciiExt; + AsciiExt::eq_ignore_ascii_case(self.as_raw(), rhs.as_raw()) + } +} + +impl<'a> PartialEq for DnsLabelRef<'a> { + #[inline] + fn eq(&self, rhs: &DnsLabel) -> bool { + self.eq(&rhs.as_ref()) + } +} + +impl<'a> Eq for DnsLabelRef<'a>{} + +impl<'a, 'b> PartialOrd> for DnsLabelRef<'b> { + #[inline] + fn partial_cmp(&self, rhs: &DnsLabelRef<'a>) -> Option { + Some(self.compare(*rhs)) + } +} + +impl<'a> PartialOrd for DnsLabelRef<'a> { + #[inline] + fn partial_cmp(&self, rhs: &DnsLabel) -> Option { + Some(self.compare(rhs)) + } +} + +impl<'a> Ord for DnsLabelRef<'a> { + #[inline] + fn cmp(&self, rhs: &Self) -> Ordering { + self.compare(*rhs) + } +} + +impl<'a> fmt::Debug for DnsLabelRef<'a> { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + // just escape the display version + format!("{}", self).fmt(w) + } +} + +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])})?; + } + match c { + b'.' => w.write_str(r#"\."#)?, + b'\\' => w.write_str(r#"\\"#)?, + _ => write!(w, r"\{:03o}", c)?, + } + done = pos + 1; + } + } + // final flush + if done < self.label.len() { + w.write_str(unsafe {str::from_utf8_unchecked(&self.label[done..])})?; + } + Ok(()) + } +} diff --git a/lib/dnsbox-base/src/common_types/name/mod.rs b/lib/dnsbox-base/src/common_types/name/mod.rs new file mode 100644 index 0000000..4d1168f --- /dev/null +++ b/lib/dnsbox-base/src/common_types/name/mod.rs @@ -0,0 +1,509 @@ +#![deny(missing_docs)] +//! Various structs to represents DNS names and labels + +use bytes::Bytes; +use errors::*; +use ser::DnsPacketData; +use smallvec::SmallVec; +use std::fmt; +use std::io::Cursor; +use std::ops::{Deref, DerefMut}; + +pub use self::display::*; +pub use self::label::*; + +mod display; +mod label; +mod name_mutations; +mod name_parser; + +#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)] +enum LabelOffset { + LabelStart(u8), + PacketStart(u8), +} + +// the heap meta data is usually at least 2*usize big; assuming 64-bit +// platforms it should be ok to use 16 bytes in the smallvec. +#[derive(Clone,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)] +enum LabelOffsets { + Uncompressed(SmallVec<[u8;16]>), + Compressed(usize, SmallVec<[LabelOffset;8]>), +} + +impl LabelOffsets { + fn len(&self) -> u8 { + let l = match *self { + LabelOffsets::Uncompressed(ref offs) => offs.len(), + LabelOffsets::Compressed(_, ref offs) => offs.len(), + }; + debug_assert!(l < 128); + l as u8 + } + + fn label_pos(&self, ndx: u8) -> usize { + debug_assert!(ndx < 127); + match *self { + LabelOffsets::Uncompressed(ref offs) => offs[ndx as usize] as usize, + LabelOffsets::Compressed(start, ref offs) => match offs[ndx as usize] { + LabelOffset::LabelStart(o) => start + (o as usize), + LabelOffset::PacketStart(o) => o as usize, + } + } + } + + fn is_compressed(&self) -> bool { + match *self { + LabelOffsets::Uncompressed(_) => false, + LabelOffsets::Compressed(_, _) => true, + } + } +} + +/// A DNS name +/// +/// Uses the "original" raw representation for storage (i.e. can share +/// memory with a parsed packet) +#[derive(Clone,Hash)] +pub struct DnsName { + // in uncompressed form always includes terminating null octect; + // but even in uncompressed form can include unused bytes at the + // beginning + // + // may be empty for the root name (".", no labels) + data: Bytes, + // either uncompressed or compressed offsets + label_offsets: LabelOffsets, + // length of encoded form + total_len: u8, +} + +impl DnsName { + /// Create new name representing the DNS root (".") + pub fn new_root() -> Self { + DnsName{ + data: Bytes::new(), + label_offsets: LabelOffsets::Uncompressed(SmallVec::new()), + total_len: 1, + } + } + + /// Create new name representing the DNS root (".") and pre-allocate + /// storage + pub fn with_capacity(labels: u8, total_len: u8) -> Self { + DnsName{ + data: Bytes::with_capacity(total_len as usize), + label_offsets: LabelOffsets::Uncompressed(SmallVec::with_capacity(labels as usize)), + total_len: 1, + } + } + + /// Returns whether name represents the DNS root (".") + pub fn is_root(&self) -> bool { + 0 == self.label_count() + } + + /// How many labels the name has (without the trailing empty label, + /// at most 127) + pub fn label_count(&self) -> u8 { + self.label_offsets.len() + } + + /// Iterator over the labels (in the order they are stored in memory, + /// i.e. top-level name last). + pub fn labels<'a>(&'a self) -> DnsNameIterator<'a> { + DnsNameIterator{ + name: &self, + front_label: 0, + back_label: self.label_offsets.len(), + } + } + + /// Return label at index `ndx` + /// + /// # Panics + /// + /// panics if `ndx >= self.label_count()`. + pub fn label_ref<'a>(&'a self, ndx: u8) -> DnsLabelRef<'a> { + let pos = self.label_offsets.label_pos(ndx); + let label_len = self.data[pos]; + debug_assert!(label_len < 64); + let end = pos + 1 + label_len as usize; + DnsLabelRef{label: &self.data[pos + 1..end]} + } + + /// Return label at index `ndx` + /// + /// # Panics + /// + /// panics if `ndx >= self.label_count()`. + pub fn label(&self, ndx: u8) -> DnsLabel { + let pos = self.label_offsets.label_pos(ndx); + let label_len = self.data[pos]; + debug_assert!(label_len < 64); + let end = pos + 1 + label_len as usize; + DnsLabel{label: self.data.slice(pos + 1, end) } + } +} + +impl<'a> IntoIterator for &'a DnsName { + type Item = DnsLabelRef<'a>; + type IntoIter = DnsNameIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.labels() + } +} + +impl PartialEq for DnsName { + fn eq(&self, rhs: &DnsName) -> bool { + let a_labels = self.labels(); + let b_labels = rhs.labels(); + if a_labels.len() != b_labels.len() { return false; } + a_labels.zip(b_labels).all(|(a,b)| a == b) + } +} + +impl Eq for DnsName{} + +impl fmt::Debug for DnsName { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + DisplayLabels{ + labels: self, + options: Default::default(), + }.fmt(w) + } +} + +impl fmt::Display for DnsName { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + DisplayLabels{ + labels: self, + options: Default::default(), + }.fmt(w) + } +} + +impl DnsPacketData for DnsName { + fn deserialize(data: &mut Cursor) -> Result { + DnsName::parse_name(data, false) + } +} + +/// Similar to `DnsName`, but allows using compressed labels in the +/// serialized form +#[derive(Clone)] +pub struct DnsCompressedName(pub DnsName); + +impl Deref for DnsCompressedName { + type Target = DnsName; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DnsCompressedName { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> IntoIterator for &'a DnsCompressedName { + type Item = DnsLabelRef<'a>; + type IntoIter = DnsNameIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.labels() + } +} + +impl PartialEq for DnsCompressedName { + fn eq(&self, rhs: &DnsCompressedName) -> bool { + self.0 == rhs.0 + } +} + +impl PartialEq for DnsCompressedName { + fn eq(&self, rhs: &DnsName) -> bool { + &self.0 == rhs + } +} + +impl PartialEq for DnsName { + fn eq(&self, rhs: &DnsCompressedName) -> bool { + self == &rhs.0 + } +} + +impl Eq for DnsCompressedName{} + +impl fmt::Debug for DnsCompressedName { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(w) + } +} + +impl fmt::Display for DnsCompressedName { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(w) + } +} + +impl DnsPacketData for DnsCompressedName { + fn deserialize(data: &mut Cursor) -> Result { + Ok(DnsCompressedName(DnsName::parse_name(data, true)?)) + } +} + +/// Iterator type for [`DnsName::labels`] +/// +/// [`DnsName::labels`]: struct.DnsName.html#method.labels +#[derive(Clone)] +pub struct DnsNameIterator<'a> { + name: &'a DnsName, + front_label: u8, + back_label: u8, +} + +impl<'a> Iterator for DnsNameIterator<'a> { + type Item = DnsLabelRef<'a>; + + fn next(&mut self) -> Option { + if self.front_label >= self.back_label { return None } + let label = self.name.label_ref(self.front_label); + self.front_label += 1; + Some(label) + } + + fn size_hint(&self) -> (usize, Option) { + let count = self.len(); + (count, Some(count)) + } + + fn count(self) -> usize { + self.len() + } +} + +impl<'a> ExactSizeIterator for DnsNameIterator<'a> { + fn len(&self) -> usize { + (self.back_label - self.front_label) as usize + } +} + +impl<'a> DoubleEndedIterator for DnsNameIterator<'a> { + fn next_back(&mut self) -> Option { + if self.front_label >= self.back_label { return None } + self.back_label -= 1; + let label = self.name.label_ref(self.back_label); + Some(label) + } +} + +#[cfg(test)] +mod tests { + use ser::packet; + use super::*; + +/* + fn deserialize(bytes: &'static [u8]) -> Result { + let result = packet::deserialize::(Bytes::from_static(bytes))?; + { + let check_result = packet::deserialize::(result.clone().encode()).unwrap(); + assert_eq!(check_result, result); + } + Ok(result) + } +*/ + + fn de_uncompressed(bytes: &'static [u8]) -> Result { + let result = packet::deserialize::(Bytes::from_static(bytes))?; + assert_eq!(bytes, result.clone().encode()); + Ok(result) + } + + fn check_uncompressed_display(bytes: &'static [u8], txt: &str, label_count: u8) { + let name = de_uncompressed(bytes).unwrap(); + assert_eq!( + name.labels().count(), + label_count as usize + ); + assert_eq!( + format!("{}", name), + txt + ); + } + + fn check_uncompressed_debug(bytes: &'static [u8], txt: &str) { + let name = de_uncompressed(bytes).unwrap(); + assert_eq!( + format!("{:?}", name), + txt + ); + } + + #[test] + fn parse_and_display_name() { + check_uncompressed_display( + b"\x07example\x03com\x00", + "example.com.", + 2, + ); + check_uncompressed_display( + b"\x07e!am.l\\\x03com\x00", + "e\\041am\\.l\\\\.com.", + 2, + ); + check_uncompressed_debug( + b"\x07e!am.l\\\x03com\x00", + r#""e\\041am\\.l\\\\.com.""#, + ); + } + + #[test] + fn parse_and_reverse_name() { + let name = de_uncompressed(b"\x03www\x07example\x03com\x00").unwrap(); + assert_eq!( + format!( + "{}", + DisplayLabels{ + labels: name.labels().rev(), + options: DisplayLabelsOptions{ + separator: " ", + trailing: false, + }, + } + ), + "com example www" + ); + } + + #[test] + fn modifications() { + let mut name = de_uncompressed(b"\x07example\x03com\x00").unwrap(); + + name.push_front(DnsLabelRef::new(b"www").unwrap()).unwrap(); + assert_eq!( + format!("{}", name), + "www.example.com." + ); + + name.push_back(DnsLabelRef::new(b"org").unwrap()).unwrap(); + assert_eq!( + format!("{}", name), + "www.example.com.org." + ); + + name.pop_front(); + assert_eq!( + format!("{}", name), + "example.com.org." + ); + + name.push_front(DnsLabelRef::new(b"mx").unwrap()).unwrap(); + assert_eq!( + format!("{}", name), + "mx.example.com.org." + ); + // the "mx" label should fit into the place "www" used before, + // make sure the buffer was reused and the name not moved within + assert_eq!(1, name.label_offsets.label_pos(0)); + + name.push_back(DnsLabelRef::new(b"com").unwrap()).unwrap(); + assert_eq!( + format!("{}", name), + "mx.example.com.org.com." + ); + } + + + + fn de_compressed(bytes: &'static [u8], offset: usize) -> Result { + use bytes::Buf; + + let mut c = Cursor::new(Bytes::from_static(bytes)); + c.set_position(offset as u64); + let result = DnsPacketData::deserialize(&mut c)?; + if c.remaining() != 0 { + bail!("data remaining: {}", c.remaining()) + } + Ok(result) + } + + fn check_compressed_display(bytes: &'static [u8], offset: usize, txt: &str, label_count: u8) { + let name = de_compressed(bytes, offset).unwrap(); + assert_eq!( + name.labels().count(), + label_count as usize + ); + assert_eq!( + format!("{}", name), + txt + ); + } + + fn check_compressed_debug(bytes: &'static [u8], offset: usize, txt: &str) { + let name = de_compressed(bytes, offset).unwrap(); + assert_eq!( + format!("{:?}", name), + txt + ); + } + + #[test] + fn parse_and_display_compressed_name() { + check_compressed_display( + b"\x03com\x00\x07example\xc0", 5, + "example.com.", + 2, + ); + check_compressed_display( + b"\x03com\x00\x07e!am.l\\\xc0", 5, + "e\\041am\\.l\\\\.com.", + 2, + ); + check_compressed_debug( + b"\x03com\x00\x07e!am.l\\\xc0", 5, + r#""e\\041am\\.l\\\\.com.""#, + ); + } + + #[test] + fn modifications_compressed() { + let mut name = de_compressed(b"\x03com\x00\x07example\xc0", 5).unwrap(); + + name.push_front(DnsLabelRef::new(b"www").unwrap()).unwrap(); + assert_eq!( + format!("{}", name), + "www.example.com." + ); + + name.push_back(DnsLabelRef::new(b"org").unwrap()).unwrap(); + assert_eq!( + format!("{}", name), + "www.example.com.org." + ); + + name.pop_front(); + assert_eq!( + format!("{}", name), + "example.com.org." + ); + + name.push_front(DnsLabelRef::new(b"mx").unwrap()).unwrap(); + assert_eq!( + format!("{}", name), + "mx.example.com.org." + ); + // the "mx" label should fit into the place "www" used before, + // make sure the buffer was reused and the name not moved within + assert_eq!(1, name.label_offsets.label_pos(0)); + + name.push_back(DnsLabelRef::new(b"com").unwrap()).unwrap(); + assert_eq!( + format!("{}", name), + "mx.example.com.org.com." + ); + } +} diff --git a/lib/dnsbox-base/src/common_types/name/name_mutations.rs b/lib/dnsbox-base/src/common_types/name/name_mutations.rs new file mode 100644 index 0000000..a7e3bb2 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/name/name_mutations.rs @@ -0,0 +1,265 @@ +use bytes::{BytesMut,BufMut}; +use super::*; + +impl DnsName { + /// Remove the front label + /// + /// Returns `false` if the name was the root (".") + pub fn try_pop_front(&mut self) -> bool { + match self.label_offsets { + LabelOffsets::Uncompressed(ref mut offs) => { + if offs.is_empty() { return false; } + self.total_len -= self.data[offs[0] as usize] + 1; + offs.remove(0); + }, + LabelOffsets::Compressed(ref mut start_pos, ref mut offs) => { + if offs.is_empty() { return false; } + match offs[0] { + LabelOffset::LabelStart(o) => { + let label_space = self.data[*start_pos + o as usize] + 1; + self.total_len -= label_space; + *start_pos += label_space as usize; + }, + LabelOffset::PacketStart(o) => { + self.total_len -= self.data[o as usize] + 1; + }, + } + offs.remove(0); + }, + } + true + } + + /// Remove the front label + /// + /// # Panics + /// + /// Panics if the name was the root (".") + pub fn pop_front(&mut self) { + if !self.try_pop_front() { panic!("Cannot pop label from root name") } + } + + /// Insert a new label at the front + /// + /// Returns an error if the resulting name would be too long + pub fn push_front<'a, L: Into>>(&mut self, label: L) -> Result<()> { + let label = label.into(); + if label.len() > 254 - self.total_len { bail!("Cannot append label, resulting name too long") } + let (mut data, start) = self.reserve(label.len() as usize + 1, 0); + + let new_label_pos = start - (label.len() + 1) as usize; + data[new_label_pos] = label.len(); + data[new_label_pos+1..new_label_pos+1+label.len() as usize].copy_from_slice(label.as_raw()); + self.data = data.freeze(); + self.total_len += label.len() + 1; + match self.label_offsets { + LabelOffsets::Uncompressed(ref mut offs) => { + offs.insert(0, new_label_pos as u8); + }, + LabelOffsets::Compressed(_, _) => unreachable!(), + } + + Ok(()) + } + + /// Remove the back label + /// + /// Returns `false` if the name was the root (".") + pub fn try_pop_back(&mut self) -> bool { + match self.label_offsets { + LabelOffsets::Uncompressed(ref mut offs) => { + if offs.is_empty() { return false; } + self.total_len -= self.data[offs[offs.len()-1] as usize] + 1; + offs.pop(); + }, + LabelOffsets::Compressed(ref mut start_pos, ref mut offs) => { + if offs.is_empty() { return false; } + match offs[offs.len()-1] { + LabelOffset::LabelStart(o) => { + self.total_len -= self.data[*start_pos + o as usize] + 1; + }, + LabelOffset::PacketStart(o) => { + self.total_len -= self.data[o as usize] + 1; + }, + } + offs.pop(); + }, + } + true + } + + /// Remove the back label + /// + /// # Panics + /// + /// Panics if the name was the root (".") + pub fn pop_back(&mut self) { + if !self.try_pop_back() { panic!("Cannot pop label from root name") } + } + + /// Insert a new label at the back + /// + /// Returns an error if the resulting name would be too long + pub fn push_back<'a, L: Into>>(&mut self, label: L) -> Result<()> { + let label = label.into(); + if label.len() > 254 - self.total_len { bail!("Cannot append label, resulting name too long") } + let (mut data, start) = self.reserve(0, label.len() as usize + 1); + + let new_label_pos = start + self.total_len as usize - 1; + data[new_label_pos] = label.len(); + data[new_label_pos+1..new_label_pos+1+label.len() as usize].copy_from_slice(label.as_raw()); + data[new_label_pos+1+label.len() as usize] = 0; + self.data = data.freeze(); + self.total_len += label.len() + 1; + match self.label_offsets { + LabelOffsets::Uncompressed(ref mut offs) => { + offs.push(new_label_pos as u8); + }, + LabelOffsets::Compressed(_, _) => unreachable!(), + } + + Ok(()) + } + + // returns mutable buffer and position of the start of the current + // name (null terminated). + // + // adjusts self.label_offsets accordingly + fn reserve(&mut self, prefix: usize, suffix: usize) -> (BytesMut, usize) { + use std::ptr; + + let new_len = self.total_len as usize + prefix + suffix; + assert!(new_len < 256); + self.uncompress(prefix, suffix); + let label_offsets = match self.label_offsets { + LabelOffsets::Uncompressed(ref mut offs) => offs, + LabelOffsets::Compressed(_, _) => unreachable!(), + }; + // steal buffer from self (so it has a change to be owned) + let data = self.data.split_off(0).try_mut(); + + if label_offsets.is_empty() { + // root name + let mut data = data.unwrap_or_else(|_| BytesMut::with_capacity(new_len)); + data.put_u8(0); + return (data, 0) + } + + let old_start = label_offsets[0] as usize; + // if current "prefix" space (old_start) is bigger than + // requested but fits, just increase the prefix + let (prefix, new_len) = if old_start > prefix && self.total_len as usize + old_start + suffix < 256 { + (old_start, self.total_len as usize + old_start + suffix) + } else { + (prefix, new_len) + }; + + // check if we need to reallocate + let data = match data { + Ok(data) => { + if data.capacity() < new_len { + // need a new allocation anyway, pretend we couldn't + // own the buffer + Err(data.freeze()) + } else { + Ok(data) + } + }, + Err(data) => Err(data), + }; + + + match data { + Ok(mut data) => { + if data.len() < new_len { + let add = new_len - data.len(); + data.reserve(add); + unsafe { data.set_len(new_len); } + } + if old_start < prefix { + // need more space in front, move back + let p_old = &data[old_start as usize] as *const u8; + let p_new = &mut data[prefix] as *mut u8; + unsafe { + ptr::copy(p_old, p_new, self.total_len as usize); + } + // adjust labels + for o in label_offsets.iter_mut() { + *o = *o + (prefix - old_start) as u8; + } + return (data, prefix); + } else if old_start > prefix { + // too much space in front, (suffix didn't fit into + // length restriction, see check above), move to + // front + let p_old = &data[old_start as usize] as *const u8; + let p_new = &mut data[prefix] as *mut u8; + unsafe { + ptr::copy(p_old, p_new, self.total_len as usize); + } + // adjust labels + for o in label_offsets.iter_mut() { + *o = *o - (old_start - prefix) as u8; + } + return (data, prefix); + } else { + return (data, old_start); + } + }, + Err(data) => { + let mut new_data = BytesMut::with_capacity(new_len); + unsafe { new_data.set_len(new_len); } + // copy old data + new_data[prefix..prefix + self.total_len as usize].copy_from_slice(&data[old_start..old_start+self.total_len as usize]); + // adjust labels + for o in label_offsets.iter_mut() { + *o = (*o - old_start as u8) + prefix as u8; + } + return (new_data, prefix); + }, + } + } + + /// return encoded (uncompressed) representation + /// + /// might uncompress the inner representation. + pub fn encode(&mut self) -> Bytes { + self.uncompress(0, 0); + if self.is_root() { + return Bytes::from_static(b"\x00"); + } + // at least one label, find first one + let from = self.label_offsets.label_pos(0); + self.data.slice(from, from + self.total_len as usize) + } + + fn uncompress(&mut self, prefix_capacity: usize, suffix_capacity: usize) { + if self.label_offsets.is_compressed() { + let name = { + let labels = self.labels(); + let label_count = labels.len(); + let new_len = self.total_len as usize + prefix_capacity + suffix_capacity; + assert!(new_len < 256); + let mut data = BytesMut::with_capacity(new_len); + let mut offsets = SmallVec::<[u8;16]>::with_capacity(label_count); + unsafe { data.set_len(prefix_capacity) } + let mut pos = prefix_capacity as u8; + + for label in labels { + offsets.push(pos); + pos += label.len() + 1; + data.put_u8(label.len()); + data.put_slice(label.as_raw()); + } + data.put_u8(0); + + DnsName{ + data: data.freeze(), + label_offsets: LabelOffsets::Uncompressed(offsets), + total_len: self.total_len, + } + }; + *self = name + } + } +} diff --git a/lib/dnsbox-base/src/common_types/name/name_parser.rs b/lib/dnsbox-base/src/common_types/name/name_parser.rs new file mode 100644 index 0000000..c78d7e8 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/name/name_parser.rs @@ -0,0 +1,81 @@ +use bytes::Buf; +use super::*; + +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`) + /// `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 { + let mut label_offsets = uncmpr_offsets.into_iter() + .map(LabelOffset::LabelStart) + .collect::>(); + + 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") } + pos = new_pos; + } + + loop { + label_len = data[pos]; + + if 0 == label_len { + return Ok(DnsName{ + data: data, + label_offsets: LabelOffsets::Compressed(start_pos, label_offsets), + total_len: total_len as u8 + 1, + }) + } + + if label_len & 0xc == 0xc { continue 'next_compressed; } + if label_len > 63 { bail!("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)); + pos += 1 + label_len as usize; + } + } + } + + pub(super) fn parse_name(data: &mut Cursor, accept_compressed: bool) -> Result { + check_enough_data!(data, 1, "DnsName"); + let start_pos = data.position() as usize; + let mut total_len : usize = 0; + let mut label_offsets = SmallVec::new(); + loop { + check_enough_data!(data, 1, "DnsName label len"); + let label_len = data.get_u8() as usize; + if 0 == label_len { + let end_pos = data.position() as usize; + return Ok(DnsName{ + data: data.get_ref().slice(start_pos, end_pos), + label_offsets: LabelOffsets::Uncompressed(label_offsets), + total_len: total_len as u8 + 1, + }) + } + if label_len & 0xc0 == 0xc0 { + // compressed label + if !accept_compressed { bail!("Invalid label compression {}", label_len) } + + let end_pos = data.position() as usize; + let data = data.get_ref().slice(0, end_pos); + + return Self::parse_name_compressed_cont(data, start_pos, label_offsets, total_len, label_len as u8); + } + label_offsets.push(total_len as u8); + if label_len > 63 { bail!("Invalid label length {}", label_len) } + total_len += 1 + label_len; + // max len 255, but there also needs to be an empty label at the end + if total_len > 254 { bail!{"DNS name too long"} } + check_enough_data!(data, (label_len), "DnsName label"); + data.advance(label_len); + } + } +} diff --git a/lib/dnsbox-base/src/common_types/rr_type/mod.rs b/lib/dnsbox-base/src/common_types/rr_type/mod.rs new file mode 100644 index 0000000..fbfd751 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/rr_type/mod.rs @@ -0,0 +1,8 @@ +#[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 { + Ok(Type(::ser::DnsPacketData::deserialize(data)?)) + } +} diff --git a/lib/dnsbox-base/src/common_types/text/mod.rs b/lib/dnsbox-base/src/common_types/text/mod.rs new file mode 100644 index 0000000..3a3b744 --- /dev/null +++ b/lib/dnsbox-base/src/common_types/text/mod.rs @@ -0,0 +1,21 @@ +use bytes::{Bytes, Buf}; +use std::io::Cursor; + +use ser::DnsPacketData; +use errors::*; + +#[derive(Clone, Debug)] +pub struct ShortText(Bytes); + +// RFC 1035 names this `` +impl DnsPacketData for ShortText { + fn deserialize(data: &mut Cursor) -> Result { + 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)) + } +} diff --git a/lib/dnsbox-base/src/errors.rs b/lib/dnsbox-base/src/errors.rs new file mode 100644 index 0000000..ea95147 --- /dev/null +++ b/lib/dnsbox-base/src/errors.rs @@ -0,0 +1,42 @@ +use bytes::{Buf, Bytes}; +use failure; +use std::fmt; +use std::io; +use std::result; + +pub type Result = result::Result; + +#[derive(Debug)] +pub struct NotEnoughData { + position: u64, + data: Bytes, +} + +impl NotEnoughData { + pub fn check(data: &mut io::Cursor, need: usize) -> Result<()> { + if data.remaining() < need { + bail!(NotEnoughData{ + position: data.position(), + data: data.get_ref().clone(), + }) + } + Ok(()) + } +} + +impl fmt::Display for NotEnoughData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "not enough data at position {} in {:?}", self.position, self.data) + } +} + +impl failure::Fail for NotEnoughData {} + +macro_rules! check_enough_data { + ($data:ident, $n:expr, $context:expr) => { + { + use $crate::failure::ResultExt; + $crate::errors::NotEnoughData::check($data, $n).context($context)?; + } + }; +} diff --git a/lib/dnsbox-base/src/lib.rs b/lib/dnsbox-base/src/lib.rs new file mode 100644 index 0000000..5efc869 --- /dev/null +++ b/lib/dnsbox-base/src/lib.rs @@ -0,0 +1,22 @@ +pub extern crate byteorder; +pub extern crate bytes; +#[macro_use] +pub extern crate failure; + +#[macro_use] +extern crate dnsbox_derive; +extern crate smallvec; +#[macro_use] +extern crate lazy_static; + +#[macro_use] +pub mod errors; +pub mod common_types; +pub mod records; +pub mod ser; + +// dnsbox_derive will use ::dnsbox_base::...; make this work in this crate too. +mod dnsbox_base { + #[allow(unused_imports)] + pub use super::*; +} diff --git a/lib/dnsbox-base/src/records/mod.rs b/lib/dnsbox-base/src/records/mod.rs new file mode 100644 index 0000000..8cc14b4 --- /dev/null +++ b/lib/dnsbox-base/src/records/mod.rs @@ -0,0 +1,8 @@ +mod structs; +pub mod registry; +pub mod types; + +pub use self::structs::*; + +#[cfg(test)] +mod tests; \ No newline at end of file diff --git a/lib/dnsbox-base/src/records/registry.rs b/lib/dnsbox-base/src/records/registry.rs new file mode 100644 index 0000000..04c6494 --- /dev/null +++ b/lib/dnsbox-base/src/records/registry.rs @@ -0,0 +1,149 @@ +use std::collections::HashMap; + +use records::structs; +use records::types; +use common_types::Type; +use ser::RRData; + +lazy_static!{ + static ref REGISTRY: Registry = Registry::init(); +} + +fn registry() -> &'static Registry { + &*REGISTRY +} + +pub fn known_name_to_type(name: &str) -> Option { + let registry = registry(); + let &t = registry.names_to_type.get(name)?; + registry.type_parser.get(&t)?; + + Some(t) +} + +pub fn name_to_type(name: &str) -> Option { + let registry = registry(); + let &t = registry.names_to_type.get(name)?; + Some(t) +} + +struct Registry { + names_to_type: HashMap, + type_parser: HashMap, + // make sure registrations are in order + prev_type: Option, +} + +impl Registry { + fn init() -> Self { + let mut r = Registry { + names_to_type: HashMap::new(), + type_parser: HashMap::new(), + prev_type: None, + }; + + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_unknown("X25" , types::X25); + r.register_unknown("ISDN" , types::ISDN); + r.register_unknown("RT" , types::RT); + r.register_unknown("NSAP" , types::NSAP); + r.register_unknown("NSAP-PTR" , types::NSAP_PTR); + r.register_unknown("SIG" , types::SIG); + r.register_known::(); + r.register_unknown("PX" , types::PX); + r.register_unknown("GPOS" , types::GPOS); + r.register_known::(); + r.register_known::(); + r.register_unknown("NXT" , types::NXT); + r.register_unknown("EID" , types::EID); + r.register_unknown("NIMLOC" , types::NIMLOC); + r.register_known::(); + r.register_unknown("ATMA" , types::ATMA); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_unknown("A6" , types::A6); + r.register_known::(); + r.register_unknown("SINK" , types::SINK); + r.register_unknown("OPT" , types::OPT); + r.register_unknown("APL" , types::APL); + r.register_known::(); + r.register_known::(); + r.register_unknown("IPSECKEY" , types::IPSECKEY); + r.register_known::(); + r.register_known::(); + r.register_known::(); + r.register_unknown("DHCID" , types::DHCID); + r.register_known::(); + r.register_known::(); + r.register_unknown("TLSA" , types::TLSA); + r.register_unknown("SMIMEA" , types::SMIMEA); + r.register_unknown("HIP" , types::HIP); + r.register_unknown("NINFO" , types::NINFO); + r.register_unknown("RKEY" , types::RKEY); + r.register_unknown("TALINK" , types::TALINK); + r.register_unknown("CDS" , types::CDS); + r.register_unknown("CDNSKEY" , types::CDNSKEY); + r.register_unknown("OPENPGPKEY", types::OPENPGPKEY); + r.register_unknown("CSYNC" , types::CSYNC); + r.register_known::(); + r.register_unknown("UINFO" , types::UINFO); + r.register_unknown("UID" , types::UID); + r.register_unknown("GID" , types::GID); + r.register_unknown("UNSPEC" , types::UNSPEC); + r.register_unknown("NID" , types::NID); + r.register_unknown("L32" , types::L32); + r.register_unknown("L64" , types::L64); + r.register_unknown("LP" , types::LP); + r.register_unknown("EUI48" , types::EUI48); + r.register_unknown("EUI64" , types::EUI64); + r.register_unknown("TKEY" , types::TKEY); + r.register_unknown("TSIG" , types::TSIG); + r.register_unknown("IXFR" , types::IXFR); + r.register_unknown("AXFR" , types::AXFR); + 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_unknown("AVC" , types::AVC); + r.register_unknown("DOA" , types::DOA); + r.register_unknown("DLV" , types::DLV); + r.register_unknown("ADDR" , types::ADDR); + r.register_unknown("ALIAS" , types::ALIAS); + + r + } + + fn register_unknown(&mut self, name: &'static 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()); + } + + fn register_known(&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, ()); + } +} diff --git a/lib/dnsbox-base/src/records/structs.rs b/lib/dnsbox-base/src/records/structs.rs new file mode 100644 index 0000000..c3b0c3b --- /dev/null +++ b/lib/dnsbox-base/src/records/structs.rs @@ -0,0 +1,402 @@ +use bytes::Bytes; +use ser::DnsPacketData; +use common_types::*; +use std::net::{Ipv4Addr, Ipv6Addr}; + +// unless otherwise documented, class should probably be IN (0x0001) + +// class IN +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct A { + addr: Ipv4Addr, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct NS { + nsdname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct MD { + madname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct MF { + madname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct CNAME { + cname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct SOA { + mname: DnsCompressedName, + rname: DnsCompressedName, + serial: u32, + refresh: u32, + retry: u32, + expire: u32, + minimum: u32, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct MB { + madname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct MG { + mgmname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct MR { + newname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct NULL { + anything: RemainingText, +} + +// class IN +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct WKS { + address: Ipv4Addr, + protocol: u8, + bitmap: RemainingText, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct PTR { + ptrdname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct HINFO { + cpu: ShortText, + os: ShortText, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct MINFO { + rmailbx: DnsCompressedName, + emailbx: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct MX { + preference: u16, + mxname: DnsCompressedName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct TXT { + text: LongText, +} + +// end of RFC 1035: no DnsCompressedName below! + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct RP { + mbox: DnsName, + txt: DnsName, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct AFSDB { + subtype: u16, + hostname: DnsName, +} + +// class independent +// pub struct X25; + +// 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)] +pub struct KEY { + flags: u16, + protocol: u8, + algorithm: u8, + certificate: RemainingText, +} + +// pub struct PX; +// pub struct GPOS; + +// class IN +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct AAAA { + addr: Ipv6Addr, +} + +// class independent +#[derive(Clone, Debug, RRData)] +pub enum LOC { + Version0(LOC0), + UnknownVersion{ + version: u8, + data: RemainingText + }, +} + +impl DnsPacketData for LOC { + fn deserialize(data: &mut ::std::io::Cursor) -> ::errors::Result { + 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)] +pub struct SRV { + preference: u16, + weight: u16, + port: u16, + target: DnsName, +} + +// pub struct ATMA; + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct NAPTR { + order: u16, + preference: u16, + flags: ShortText, + service: ShortText, + regexp: ShortText, + replacement: DnsName, +} + +// class IN +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct KX { + preference: u16, + exchanger: DnsName, +} + +// class ?? +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct CERT { + cert_type: u16, + key_tag: u16, + algorithm: u8, + certificate: RemainingText, +} + +// pub struct A6; + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct DNAME { + target: DnsName, +} + +// pub struct SINK; + +// OPT should be decoded at "transport level", abuses ttl and class +// fields too. +// pub struct OPT; + +// pub struct APL; + +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct DS { + key_tag: u16, + algorithm: u8, + digest_type: u8, + digest: HexRemainingBlob, +} + +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct SSHFP { + algorithm: u8, + fingerprint_type: u8, + fingerprint: HexRemainingBlob, +} + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct IPSECKEY; + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct RRSIG { + rr_type: Type, + algorithm: u8, + labels: u8, + original_ttl: u32, + signature_expiration: u32, + signature_inception: u32, + key_tag: u16, + signers_name: DnsName, + signature: RemainingText, +} + +#[derive(Clone, Debug, DnsPacketData)] +pub struct NsecTypeBitmap { + types: RemainingText, // TODO: actually parse it +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct NSEC { + next: DnsName, + bitmap: NsecTypeBitmap, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct DNSKEY { + flags: u16, + protocol: u8, + algorithm: u8, + public_key: RemainingText, +} + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct DHCID; + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct NSEC3 { + hash_algorithm: u8, + flags: u8, + iterations: u16, + salt: HexShortBlob, + next_hashed: Base32ShortBlob, + bitmap: NsecTypeBitmap, +} + +// class independent +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct NSEC3PARAM { + hash_algorithm: u8, + flags: u8, + iterations: u16, + salt: HexShortBlob, +} + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct TLSA; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct SMIMEA; + +// pub struct HIP; +// pub struct NINFO; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct RKEY; + +// pub struct TALINK; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct CDS; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct CDNSKEY; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct OPENPGPKEY; + +// pub struct CSYNC; + +#[derive(Clone, Debug, DnsPacketData, RRData)] +pub struct SPF { + text: LongText, +} + +// pub struct UINFO; +// pub struct UID; +// pub struct GID; +// pub struct UNSPEC; +// pub struct NID; +// pub struct L32; +// pub struct L64; +// pub struct LP; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct EUI48; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct EUI64; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct TKEY; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct TSIG; + +// pub struct IXFR; // qtype only? +// pub struct AXFR; // qtype only? +// pub struct MAILB; // qtype only? +// pub struct MAILA; // qtype only? +// pub struct ANY; // qtype only? + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct URI; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct CAA; + +// pub struct AVC; +// pub struct DOA; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct DLV; + +// pub struct ADDR; + +// #[derive(Clone, Debug, DnsPacketData, RRData)] +// pub struct ALIAS; diff --git a/lib/dnsbox-base/src/records/tests.rs b/lib/dnsbox-base/src/records/tests.rs new file mode 100644 index 0000000..49d3564 --- /dev/null +++ b/lib/dnsbox-base/src/records/tests.rs @@ -0,0 +1,16 @@ +use bytes::Bytes; +use records::structs; +use ser::packet::deserialize; +use ser::RRData; + +fn check(txt: &str, data: &'static [u8]) +where + T: RRData +{ + let d: T = deserialize(Bytes::from_static(data)).expect("couldn't parse record"); +} + +#[test] +fn test_a() { + check::("127.0.0.1", b"\x7f\x00\x00\x01"); +} diff --git a/lib/dnsbox-base/src/records/types.rs b/lib/dnsbox-base/src/records/types.rs new file mode 100644 index 0000000..d807924 --- /dev/null +++ b/lib/dnsbox-base/src/records/types.rs @@ -0,0 +1,294 @@ +use common_types::Type; + +pub const A : Type = Type(KnownType::A as u16); +pub const NS : Type = Type(KnownType::NS as u16); +pub const MD : Type = Type(KnownType::MD as u16); +pub const MF : Type = Type(KnownType::MF as u16); +pub const CNAME : Type = Type(KnownType::CNAME as u16); +pub const SOA : Type = Type(KnownType::SOA as u16); +pub const MB : Type = Type(KnownType::MB as u16); +pub const MG : Type = Type(KnownType::MG as u16); +pub const MR : Type = Type(KnownType::MR as u16); +pub const NULL : Type = Type(KnownType::NULL as u16); +pub const WKS : Type = Type(KnownType::WKS as u16); +pub const PTR : Type = Type(KnownType::PTR as u16); +pub const HINFO : Type = Type(KnownType::HINFO as u16); +pub const MINFO : Type = Type(KnownType::MINFO as u16); +pub const MX : Type = Type(KnownType::MX as u16); +pub const TXT : Type = Type(KnownType::TXT as u16); +pub const RP : Type = Type(KnownType::RP as u16); +pub const AFSDB : Type = Type(KnownType::AFSDB as u16); +pub const X25 : Type = Type(KnownType::X25 as u16); +pub const ISDN : Type = Type(KnownType::ISDN as u16); +pub const RT : Type = Type(KnownType::RT as u16); +pub const NSAP : Type = Type(KnownType::NSAP as u16); +pub const NSAP_PTR : Type = Type(KnownType::NSAP_PTR as u16); +pub const SIG : Type = Type(KnownType::SIG as u16); +pub const KEY : Type = Type(KnownType::KEY as u16); +pub const PX : Type = Type(KnownType::PX as u16); +pub const GPOS : Type = Type(KnownType::GPOS as u16); +pub const AAAA : Type = Type(KnownType::AAAA as u16); +pub const LOC : Type = Type(KnownType::LOC as u16); +pub const NXT : Type = Type(KnownType::NXT as u16); +pub const EID : Type = Type(KnownType::EID as u16); +pub const NIMLOC : Type = Type(KnownType::NIMLOC as u16); +pub const SRV : Type = Type(KnownType::SRV as u16); +pub const ATMA : Type = Type(KnownType::ATMA as u16); +pub const NAPTR : Type = Type(KnownType::NAPTR as u16); +pub const KX : Type = Type(KnownType::KX as u16); +pub const CERT : Type = Type(KnownType::CERT as u16); +pub const A6 : Type = Type(KnownType::A6 as u16); +pub const DNAME : Type = Type(KnownType::DNAME as u16); +pub const SINK : Type = Type(KnownType::SINK as u16); +pub const OPT : Type = Type(KnownType::OPT as u16); +pub const APL : Type = Type(KnownType::APL as u16); +pub const DS : Type = Type(KnownType::DS as u16); +pub const SSHFP : Type = Type(KnownType::SSHFP as u16); +pub const IPSECKEY : Type = Type(KnownType::IPSECKEY as u16); +pub const RRSIG : Type = Type(KnownType::RRSIG as u16); +pub const NSEC : Type = Type(KnownType::NSEC as u16); +pub const DNSKEY : Type = Type(KnownType::DNSKEY as u16); +pub const DHCID : Type = Type(KnownType::DHCID as u16); +pub const NSEC3 : Type = Type(KnownType::NSEC3 as u16); +pub const NSEC3PARAM : Type = Type(KnownType::NSEC3PARAM as u16); +pub const TLSA : Type = Type(KnownType::TLSA as u16); +pub const SMIMEA : Type = Type(KnownType::SMIMEA as u16); +pub const HIP : Type = Type(KnownType::HIP as u16); +pub const NINFO : Type = Type(KnownType::NINFO as u16); +pub const RKEY : Type = Type(KnownType::RKEY as u16); +pub const TALINK : Type = Type(KnownType::TALINK as u16); +pub const CDS : Type = Type(KnownType::CDS as u16); +pub const CDNSKEY : Type = Type(KnownType::CDNSKEY as u16); +pub const OPENPGPKEY : Type = Type(KnownType::OPENPGPKEY as u16); +pub const CSYNC : Type = Type(KnownType::CSYNC as u16); +pub const SPF : Type = Type(KnownType::SPF as u16); +pub const UINFO : Type = Type(KnownType::UINFO as u16); +pub const UID : Type = Type(KnownType::UID as u16); +pub const GID : Type = Type(KnownType::GID as u16); +pub const UNSPEC : Type = Type(KnownType::UNSPEC as u16); +pub const NID : Type = Type(KnownType::NID as u16); +pub const L32 : Type = Type(KnownType::L32 as u16); +pub const L64 : Type = Type(KnownType::L64 as u16); +pub const LP : Type = Type(KnownType::LP as u16); +pub const EUI48 : Type = Type(KnownType::EUI48 as u16); +pub const EUI64 : Type = Type(KnownType::EUI64 as u16); +pub const TKEY : Type = Type(KnownType::TKEY as u16); +pub const TSIG : Type = Type(KnownType::TSIG as u16); +pub const IXFR : Type = Type(KnownType::IXFR as u16); +pub const AXFR : Type = Type(KnownType::AXFR as u16); +pub const MAILB : Type = Type(KnownType::MAILB as u16); +pub const MAILA : Type = Type(KnownType::MAILA as u16); +pub const ANY : Type = Type(KnownType::ANY as u16); +pub const URI : Type = Type(KnownType::URI as u16); +pub const CAA : Type = Type(KnownType::CAA as u16); +pub const AVC : Type = Type(KnownType::AVC as u16); +pub const DOA : Type = Type(KnownType::DOA as u16); +pub const DLV : Type = Type(KnownType::DLV as u16); +pub const ADDR : Type = Type(KnownType::ADDR as u16); +pub const ALIAS : Type = Type(KnownType::ALIAS as u16); + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[repr(u16)] +#[allow(non_camel_case_types)] +pub enum KnownType { + // try to list "original" rfc + A = 0x0001, // RFC 1035 + NS = 0x0002, // RFC 1035 + MD = 0x0003, // RFC 1035 + MF = 0x0004, // RFC 1035 + CNAME = 0x0005, // RFC 1035 + SOA = 0x0006, // RFC 1035 + MB = 0x0007, // RFC 1035 + MG = 0x0008, // RFC 1035 + MR = 0x0009, // RFC 1035 + NULL = 0x000a, // RFC 1035 + WKS = 0x000b, // RFC 1035 + PTR = 0x000c, // RFC 1035 + HINFO = 0x000d, // RFC 1035 + MINFO = 0x000e, // RFC 1035 + MX = 0x000f, // RFC 1035 + TXT = 0x0010, // RFC 1035 + RP = 0x0011, // RFC 1183 + AFSDB = 0x0012, // RFC 1183 + X25 = 0x0013, // RFC 1183 + ISDN = 0x0014, // RFC 1183 + RT = 0x0015, // RFC 1183 + NSAP = 0x0016, // RFC 1706 + NSAP_PTR = 0x0017, // RFC 1348 + SIG = 0x0018, // RFC 2535 + KEY = 0x0019, // RFC 2535 + PX = 0x001a, // RFC 2163 + GPOS = 0x001b, // RFC 1712 + AAAA = 0x001c, // RFC 3596 + LOC = 0x001d, // RFC 1876 + NXT = 0x001e, // RFC 2535 + EID = 0x001f, // Michael Patton: http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt + NIMLOC = 0x0020, // Michael Patton: http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt + SRV = 0x0021, // RFC 2782 + ATMA = 0x0022, // http://www.broadband-forum.org/ftp/pub/approved-specs/af-dans-0152.000.pdf + NAPTR = 0x0023, // RFC 2168 + KX = 0x0024, // RFC 2230 + CERT = 0x0025, // RFC 4398 + A6 = 0x0026, // RFC 2874 + DNAME = 0x0027, // RFC 6672 + SINK = 0x0028, // Donald E Eastlake: http://tools.ietf.org/html/draft-eastlake-kitchen-sink + OPT = 0x0029, // RFC 6891 + APL = 0x002a, // RFC 3123 + DS = 0x002b, // RFC 3658 + SSHFP = 0x002c, // RFC 4255 + IPSECKEY = 0x002d, // RFC 4025 + RRSIG = 0x002e, // RFC 4034 + NSEC = 0x002f, // RFC 4034 + DNSKEY = 0x0030, // RFC 4034 + DHCID = 0x0031, // RFC 4701 + NSEC3 = 0x0032, // RFC 5155 + NSEC3PARAM = 0x0033, // RFC 5155 + TLSA = 0x0034, // RFC 6698 + SMIMEA = 0x0035, // RFC 8162 + HIP = 0x0037, // RFC 8005 + NINFO = 0x0038, // Jim Reid + RKEY = 0x0039, // Jim Reid + TALINK = 0x003a, // Wouter Wijngaards + CDS = 0x003b, // RFC 7344 + CDNSKEY = 0x003c, // RFC 7344 + OPENPGPKEY = 0x003d, // RFC 7929 + CSYNC = 0x003e, // RFC 7477 + SPF = 0x0063, // RFC 7208 + UINFO = 0x0064, // IANA-Reserved + UID = 0x0065, // IANA-Reserved + GID = 0x0066, // IANA-Reserved + UNSPEC = 0x0067, // IANA-Reserved + NID = 0x0068, // RFC 6742 + L32 = 0x0069, // RFC 6742 + L64 = 0x006a, // RFC 6742 + LP = 0x006b, // RFC 6742 + EUI48 = 0x006c, // RFC 7043 + EUI64 = 0x006d, // RFC 7043 + TKEY = 0x00f9, // RFC 2930 + TSIG = 0x00fa, // RFC 2845 + IXFR = 0x00fb, // RFC 1995 + AXFR = 0x00fc, // RFC 1035 + MAILB = 0x00fd, // RFC 1035 + MAILA = 0x00fe, // RFC 1035 + ANY = 0x00ff, // RFC 1035, "*" + URI = 0x0100, // RFC 7553 + CAA = 0x0101, // RFC 6844 + AVC = 0x0102, // Wolfgang Riedel + DOA = 0x0103, // http://www.iana.org/go/draft-durand-doa-over-dns + DLV = 0x8001, // RFC 4431 + ADDR = 0xff78, // powerdns? + ALIAS = 0xff79, // powerdns? +} + +#[cfg(test)] +macro_rules! check_type { + ($t:ident, $dec:expr) => { + { + use records::registry; + // compare decimal value + assert_eq!($t, Type($dec), "wrong decimal value for {}", stringify!($t)); + // make sure it's registered + assert_eq!(registry::name_to_type(stringify!($t)), Some($t), "{} not registered", stringify!($t)); + } + }; + ($t:ident, $dec:expr, $name:expr) => { + { + use records::registry; + // compare decimal value + assert_eq!($t, Type($dec), "wrong decimal value for {}", stringify!($t)); + // make sure it's registered + assert_eq!(registry::name_to_type($name), Some($t), "{} not registered as {:?}", stringify!($t), $name); + } + }; +} + +#[cfg(test)] +#[test] +fn check_types() { + check_type!(A , 1); + check_type!(NS , 2); + check_type!(MD , 3); + check_type!(MF , 4); + check_type!(CNAME , 5); + check_type!(SOA , 6); + check_type!(MB , 7); + check_type!(MG , 8); + check_type!(MR , 9); + check_type!(NULL , 10); + check_type!(WKS , 11); + check_type!(PTR , 12); + check_type!(HINFO , 13); + check_type!(MINFO , 14); + check_type!(MX , 15); + check_type!(TXT , 16); + check_type!(RP , 17); + check_type!(AFSDB , 18); + check_type!(X25 , 19); + check_type!(ISDN , 20); + check_type!(RT , 21); + check_type!(NSAP , 22); + check_type!(NSAP_PTR , 23, "NSAP-PTR"); + check_type!(SIG , 24); + check_type!(KEY , 25); + check_type!(PX , 26); + check_type!(GPOS , 27); + check_type!(AAAA , 28); + check_type!(LOC , 29); + check_type!(NXT , 30); + check_type!(EID , 31); + check_type!(NIMLOC , 32); + check_type!(SRV , 33); + check_type!(ATMA , 34); + check_type!(NAPTR , 35); + check_type!(KX , 36); + check_type!(CERT , 37); + check_type!(A6 , 38); + check_type!(DNAME , 39); + check_type!(SINK , 40); + check_type!(OPT , 41); + check_type!(APL , 42); + check_type!(DS , 43); + check_type!(SSHFP , 44); + check_type!(IPSECKEY , 45); + check_type!(RRSIG , 46); + check_type!(NSEC , 47); + check_type!(DNSKEY , 48); + check_type!(DHCID , 49); + check_type!(NSEC3 , 50); + check_type!(NSEC3PARAM, 51); + check_type!(TLSA , 52); + check_type!(SMIMEA , 53); + check_type!(HIP , 55); + check_type!(NINFO , 56); + check_type!(RKEY , 57); + check_type!(TALINK , 58); + check_type!(CDS , 59); + check_type!(CDNSKEY , 60); + check_type!(OPENPGPKEY, 61); + check_type!(CSYNC , 62); + check_type!(SPF , 99); + check_type!(UINFO , 100); + check_type!(UID , 101); + check_type!(GID , 102); + check_type!(UNSPEC , 103); + check_type!(NID , 104); + check_type!(L32 , 105); + check_type!(L64 , 106); + check_type!(LP , 107); + check_type!(EUI48 , 108); + check_type!(EUI64 , 109); + check_type!(TKEY , 249); + check_type!(TSIG , 250); + check_type!(IXFR , 251); + check_type!(AXFR , 252); + check_type!(MAILB , 253); + check_type!(MAILA , 254); + check_type!(ANY , 255); + check_type!(URI , 256); + check_type!(CAA , 257); + check_type!(AVC , 258); + check_type!(DOA , 259); + check_type!(DLV , 32769); + check_type!(ADDR , 65400); + check_type!(ALIAS , 65401); +} diff --git a/lib/dnsbox-base/src/ser/mod.rs b/lib/dnsbox-base/src/ser/mod.rs new file mode 100644 index 0000000..5618c56 --- /dev/null +++ b/lib/dnsbox-base/src/ser/mod.rs @@ -0,0 +1,7 @@ +pub mod packet; +pub mod text; +mod rrdata; + +pub use self::packet::DnsPacketData; +pub use self::text::DnsTextData; +pub use self::rrdata::RRData; diff --git a/lib/dnsbox-base/src/ser/packet/mod.rs b/lib/dnsbox-base/src/ser/packet/mod.rs new file mode 100644 index 0000000..496630f --- /dev/null +++ b/lib/dnsbox-base/src/ser/packet/mod.rs @@ -0,0 +1,21 @@ +use bytes::{Bytes, Buf}; +use errors::*; +use std::io::Cursor; + +mod std_impls; + +pub trait DnsPacketData: Sized { + fn deserialize(data: &mut Cursor) -> Result; +} + +pub fn deserialize(data: Bytes) -> Result +where + T: DnsPacketData +{ + let mut c = Cursor::new(data); + let result = T::deserialize(&mut c)?; + if c.remaining() != 0 { + bail!("data remaining: {}", c.remaining()) + } + Ok(result) +} diff --git a/lib/dnsbox-base/src/ser/packet/std_impls.rs b/lib/dnsbox-base/src/ser/packet/std_impls.rs new file mode 100644 index 0000000..4c507fa --- /dev/null +++ b/lib/dnsbox-base/src/ser/packet/std_impls.rs @@ -0,0 +1,64 @@ +use bytes::{Bytes,Buf,BigEndian}; +use errors::*; +use ser::packet::DnsPacketData; +use std::io::{Cursor, Read}; +use std::mem::size_of; +use std::net::{Ipv4Addr, Ipv6Addr}; + +impl DnsPacketData for u8 { + fn deserialize(data: &mut Cursor) -> Result { + check_enough_data!(data, size_of::(), "u8"); + Ok(data.get_u8()) + } +} + +impl DnsPacketData for u16 { + fn deserialize(data: &mut Cursor) -> Result { + check_enough_data!(data, size_of::(), "u16"); + Ok(data.get_u16::()) + } +} + +impl DnsPacketData for u32 { + fn deserialize(data: &mut Cursor) -> Result { + check_enough_data!(data, size_of::(), "u32"); + Ok(data.get_u32::()) + } +} + +impl DnsPacketData for Ipv4Addr { + fn deserialize(data: &mut Cursor) -> Result { + check_enough_data!(data, size_of::(), "Ipv4Addr"); + Ok(data.get_u32::().into()) + } +} + +impl DnsPacketData for Ipv6Addr { + fn deserialize(data: &mut Cursor) -> Result { + check_enough_data!(data, 16, "Ipv6Addr"); + let mut buf = [0u8; 16]; + data.read_exact(&mut buf)?; + Ok(buf.into()) + } +} + +#[cfg(test)] +mod tests { + use errors::*; + + fn deserialize(data: &'static [u8]) -> Result { + use bytes::{Bytes,Buf}; + use std::io::Cursor; + let mut c = Cursor::new(Bytes::from_static(data)); + let result = T::deserialize(&mut c)?; + if c.remaining() != 0 { + bail!("data remaining: {}", c.remaining()) + } + Ok(result) + } + + #[test] + fn test_u16() { + assert!(deserialize::(b"\x80\x08").unwrap() == 0x8008); + } +} diff --git a/lib/dnsbox-base/src/ser/rrdata.rs b/lib/dnsbox-base/src/ser/rrdata.rs new file mode 100644 index 0000000..c86709a --- /dev/null +++ b/lib/dnsbox-base/src/ser/rrdata.rs @@ -0,0 +1,8 @@ +use std::borrow::Cow; + +use common_types::Type; + +pub trait RRData: super::DnsPacketData { + fn rr_type() -> Type; + fn rr_type_name() -> Cow<'static, str>; +} diff --git a/lib/dnsbox-base/src/ser/text/mod.rs b/lib/dnsbox-base/src/ser/text/mod.rs new file mode 100644 index 0000000..4aed284 --- /dev/null +++ b/lib/dnsbox-base/src/ser/text/mod.rs @@ -0,0 +1,4 @@ +pub trait DnsTextData: Sized { + fn dns_parse(data: &str) -> ::errors::Result; + fn dns_format(&self) -> ::errors::Result; +} diff --git a/lib/dnsbox-derive/Cargo.toml b/lib/dnsbox-derive/Cargo.toml new file mode 100644 index 0000000..6163d06 --- /dev/null +++ b/lib/dnsbox-derive/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "dnsbox-derive" +version = "0.1.0" +authors = ["Stefan Bühler "] + +[lib] +proc-macro = true + +[dependencies] +syn = "0.11" +quote = "0.3" diff --git a/lib/dnsbox-derive/src/dns_packet_data.rs b/lib/dnsbox-derive/src/dns_packet_data.rs new file mode 100644 index 0000000..dd178c3 --- /dev/null +++ b/lib/dnsbox-derive/src/dns_packet_data.rs @@ -0,0 +1,44 @@ +use syn; +use quote; + +pub fn impl_hello_world(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, + syn::Body::Struct(_) => panic!("Deriving DnsPacketData not supported for unit / tuple struct types"), + }; + + if !ast.generics.ty_params.is_empty() { + panic!("Type parameters not supported for deriving DnsPacketData"); + } + + if !ast.generics.where_clause.predicates.is_empty() { + panic!("Where clauses not supported for deriving DnsPacketData"); + } + + if !ast.generics.lifetimes.is_empty() { + panic!("Lifetimes not supported for deriving DnsPacketData"); + } + + let name = &ast.ident; + + let mut parse_fields = quote!{}; + for field in fields { + let field_name = field.ident.as_ref().unwrap(); + + parse_fields = quote!{#parse_fields + #field_name: DnsPacketData::deserialize(_data).with_context(|_| format!("failed parsing field {}::{}", stringify!(#name), stringify!(#field_name)))?, + }; + } + + quote!{ + impl ::dnsbox_base::ser::DnsPacketData for #name { + #[allow(unused_imports)] + fn deserialize(_data: &mut ::std::io::Cursor<::dnsbox_base::bytes::Bytes>) -> ::dnsbox_base::errors::Result { + use ::dnsbox_base::failure::ResultExt; + use ::dnsbox_base::ser::DnsPacketData; + Ok(#name{ #parse_fields }) + } + } + } +} diff --git a/lib/dnsbox-derive/src/lib.rs b/lib/dnsbox-derive/src/lib.rs new file mode 100644 index 0000000..c16cf78 --- /dev/null +++ b/lib/dnsbox-derive/src/lib.rs @@ -0,0 +1,31 @@ +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; + +mod dns_packet_data; +mod rrdata; + +#[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); + + gen.parse().unwrap() +} + +#[proc_macro_derive(RRData)] +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); + + gen.parse().unwrap() +} diff --git a/lib/dnsbox-derive/src/rrdata.rs b/lib/dnsbox-derive/src/rrdata.rs new file mode 100644 index 0000000..9d52747 --- /dev/null +++ b/lib/dnsbox-derive/src/rrdata.rs @@ -0,0 +1,48 @@ +use syn; +use quote; + +pub fn impl_rr_data(ast: &syn::DeriveInput) -> quote::Tokens { + if !ast.generics.ty_params.is_empty() { + panic!("Type parameters not supported for deriving RRData"); + } + + if !ast.generics.where_clause.predicates.is_empty() { + panic!("Where clauses not supported for deriving RRData"); + } + + if !ast.generics.lifetimes.is_empty() { + panic!("Lifetimes not supported for deriving RRData"); + } + + let name = &ast.ident; + let name_str: &str = name.as_ref(); + + 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_name() -> ::std::borrow::Cow<'static, str> { + ::std::borrow::Cow::Borrowed(#name_str) + } + } + + // #[cfg(test)] + #[allow(non_snake_case, unused_imports)] + mod #test_mod_name { + use ::dnsbox_base::records::registry; + use ::dnsbox_base::records::types; + + #[test] + fn test_registry() { + assert_eq!( + registry::known_name_to_type(#name_str), + Some(types::#name) + ); + } + } + } +} diff --git a/old/name_old.rs b/old/name_old.rs new file mode 100644 index 0000000..e7b5942 --- /dev/null +++ b/old/name_old.rs @@ -0,0 +1,584 @@ +#![deny(missing_docs)] +//! Various structs to represents DNS names and labels + +use bytes::{Bytes,Buf,BytesMut}; +use errors::*; +use std::fmt; +use std::io::Cursor; +use packet_data::{DnsPacketData,deserialize}; + +#[inline] +fn check_label(label: &[u8]) -> Result<()> { + if label.len() == 0 { + bail!("label must not be empty") + } + if label.len() > 63 { + bail!("label must not be longer than 63 bytes") + } + Ok(()) +} + +/// A DNS label (any binary string with `0 < length < 64`) +#[derive(Clone,PartialEq,Eq,PartialOrd,Ord,Hash)] +pub struct DnsLabel { + label: Bytes, // 0 < len < 64 +} + +impl DnsLabel { + /// Create new label from existing storage + /// + /// Fails when the length doesn't match the requirement `0 < length < 64`. + pub fn new(label: Bytes) -> Result { + check_label(&label)?; + Ok(DnsLabel{label}) + } + + /// Convert to a representation without storage + pub fn as_ref<'a>(&'a self) -> DnsLabelRef<'a> { + DnsLabelRef{label: self.label.as_ref()} + } + + /// Access as raw bytes + pub fn as_bytes(&self) -> &Bytes { + &self.label + } + + /// Access as raw bytes + pub fn as_raw(&self) -> &[u8] { + &self.label + } +} + +impl<'a> From> for DnsLabel { + fn from(label_ref: DnsLabelRef<'a>) -> Self { + DnsLabel{ + label: Bytes::from(label_ref.label), + } + } +} + +impl fmt::Debug for DnsLabel { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + self.as_ref().fmt(w) + } +} + +impl fmt::Display for DnsLabel { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + self.as_ref().fmt(w) + } +} + +/// A DNS label (any binary string with `0 < length < 64`) +/// +/// Storage is provided through lifetime. +#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash)] +pub struct DnsLabelRef<'a> { + label: &'a [u8], // 0 < len < 64 +} + +impl<'a> DnsLabelRef<'a> { + /// Create new label from existing storage + /// + /// Fails when the length doesn't match the requirement `0 < length < 64`. + pub fn new(label: &'a [u8]) -> Result { + check_label(label)?; + Ok(DnsLabelRef{label}) + } + + /// Access as raw bytes + pub fn as_raw(&self) -> &'a [u8] { + self.label + } +} + +impl<'a> From<&'a DnsLabel> for DnsLabelRef<'a> { + fn from(label: &'a DnsLabel) -> Self { + label.as_ref() + } +} + +impl<'a> fmt::Debug for DnsLabelRef<'a> { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + // just escape the display version + format!("{}", self).fmt(w) + } +} + +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])})?; + } + match c { + b'.' => w.write_str(r#"\."#)?, + b'\\' => w.write_str(r#"\\"#)?, + _ => write!(w, r"\{:03o}", c)?, + } + done = pos + 1; + } + } + // final flush + if done < self.label.len() { + w.write_str(unsafe {str::from_utf8_unchecked(&self.label[done..])})?; + } + Ok(()) + } +} + +/// Customize formatting of DNS labels +/// +/// The default uses "." as separator and adds a trailing separator. +/// +/// The `Debug` formatters just format as `Display` to a String and then +/// format that string as `Debug`. +#[derive(Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,Debug)] +pub struct DisplayLabelsOptions { + /// separator to insert between (escaped) labels + pub separator: &'static str, + /// whether a trailing separator is added. + /// + /// without a trailing separator the root zone is represented as + /// empty string! + pub trailing: bool, +} + +impl Default for DisplayLabelsOptions { + fn default() -> Self { + DisplayLabelsOptions{ + separator: ".", + trailing: true, + } + } +} + +/// Wrap anything representing a collection of labels (`DnsLabelRef`) to +/// format using the given `options`. +/// +/// As name you can pass any cloneable `(Into)Iterator` with +/// `DnsLabelRef` items, e.g: +/// +/// * `&DnsPlainName` +/// * `&DnsName` +/// * `DnsNameIterator` +/// * `DnsPlainNameIterator` +#[derive(Clone)] +pub struct DisplayLabels<'a, I> +where + I: IntoIterator>+Clone +{ + /// Label collection to iterate over + pub labels: I, + /// Options + pub options: DisplayLabelsOptions, +} + +impl<'a, I> fmt::Debug for DisplayLabels<'a, I> +where + I: IntoIterator>+Clone +{ + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + // just escape the display version1 + format!("{}", self).fmt(w) + } +} + +impl<'a, I> fmt::Display for DisplayLabels<'a, I> +where + I: IntoIterator>+Clone +{ + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + let mut i = self.labels.clone().into_iter(); + if let Some(first_label) = i.next() { + // first label + fmt::Display::fmt(&first_label, w)?; + // remaining labels + while let Some(label) = i.next() { + w.write_str(self.options.separator)?; + fmt::Display::fmt(&label, w)?; + } + } + if self.options.trailing { + w.write_str(self.options.separator)?; + } + Ok(()) + } +} + +/// A DNS name +/// +/// Uses the "original" uncompressed raw representation for storage +/// (i.e. can share memory with a parsed packet) +#[derive(Clone,PartialEq,Eq,PartialOrd,Ord,Hash)] +pub struct DnsPlainName { + // uncompressed raw representation + data: Bytes, // at most 255 bytes + label_count: u8, // at most 127 labels; doesn't count the final (empty) label +} + +impl DnsPlainName { + /// Parse a name from raw bytes + pub fn new(raw: Bytes) -> Result { + deserialize(raw) + } + + /// How many labels the name has (without the trailing empty label, + /// at most 127) + pub fn label_count(&self) -> u8 { + self.label_count + } + + /// Iterator over the labels (in the order they are stored in memory, + /// i.e. top-level name last). + pub fn labels<'a>(&'a self) -> DnsPlainNameIterator<'a> { + DnsPlainNameIterator{ + name_data: self.data.as_ref(), + position: 0, + labels_done: 0, + label_count: self.label_count, + } + } +} + +impl<'a> IntoIterator for &'a DnsPlainName { + type Item = DnsLabelRef<'a>; + type IntoIter = DnsPlainNameIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.labels() + } +} + +impl fmt::Debug for DnsPlainName { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + DisplayLabels{ + labels: self, + options: Default::default(), + }.fmt(w) + } +} + +impl fmt::Display for DnsPlainName { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + DisplayLabels{ + labels: self, + options: Default::default(), + }.fmt(w) + } +} + +impl DnsPacketData for DnsPlainName { + fn deserialize(data: &mut Cursor) -> Result { + check_enough_data!(data, 1, "DnsPlainName"); + let start_pos = data.position() as usize; + let mut total_len : usize = 0; + let mut label_count: u8 = 0; + loop { + check_enough_data!(data, 1, "DnsPlainName label len"); + let label_len = data.get_u8() as usize; + total_len += 1; + if total_len > 255 { bail!{"DNS name too long"} } + if 0 == label_len { break; } + label_count += 1; // can't overflow: total_len <= 255, and each label so far was not empty, i.e. used at least two bytes. + if label_len > 63 { bail!("Invalid label length {}", label_len) } + total_len += label_len; + if total_len > 255 { bail!{"DNS name too long"} } + check_enough_data!(data, (label_len), "DnsPlainName label"); + data.advance(label_len); + } + let end_pos = data.position() as usize; + Ok(DnsPlainName{ + data: data.get_ref().slice(start_pos, end_pos), + label_count: label_count, + }) + } +} + +/// Iterator type for [`DnsPlainName::labels`] +/// +/// [`DnsPlainName::labels`]: struct.DnsPlainName.html#method.labels +#[derive(Clone)] +pub struct DnsPlainNameIterator<'a> { + name_data: &'a [u8], + position: u8, + labels_done: u8, + label_count: u8, +} + +impl<'a> Iterator for DnsPlainNameIterator<'a> { + type Item = DnsLabelRef<'a>; + + fn next(&mut self) -> Option { + if self.labels_done >= self.label_count { return None } + self.labels_done += 1; + let label_len = self.name_data[self.position as usize]; + let end = self.position+1+label_len; + let label = DnsLabelRef{label: &self.name_data[(self.position+1) as usize..end as usize]}; + self.position = end; + Some(label) + } + + fn size_hint(&self) -> (usize, Option) { + let count = self.len(); + (count, Some(count)) + } + + fn count(self) -> usize { + self.len() + } +} + +impl<'a> ExactSizeIterator for DnsPlainNameIterator<'a> { + fn len(&self) -> usize { + (self.label_count - self.labels_done) as usize + } +} + +/// A DNS name +/// +/// Uses a modified representation for storage (i.e. can NOT share +/// memory with a parsed packet), but supports a `DoubleEndedIterator` +/// to iterate over the labels. +#[derive(Clone,PartialEq,Eq,PartialOrd,Ord,Hash)] +pub struct DnsName { + // similar to the DNS encoding; but each "length" octet is the "XOR" + // of the length of the surrounding labels. + data: Bytes, // at most 255 bytes + label_count: u8, // at most 127 labels; doesn't count the final (empty) label +} + +impl DnsName { + /// Parse a name from raw bytes + pub fn new(raw: Bytes) -> Result { + deserialize(raw) + } + + /// How many labels the name has (without the trailing empty label, + /// at most 127) + pub fn label_count(&self) -> u8 { + self.label_count + } + + /// Iterator over the labels (in the order they are stored in memory, + /// i.e. top-level name last). + pub fn labels(&self) -> DnsNameIterator { + DnsNameIterator{ + name_data: self.data.as_ref(), + front_position: 0, + front_prev_label_len: 0, + back_position: self.data.len() as u8 - 1, + back_next_label_len: 0, + labels_done: 0, + label_count: self.label_count, + } + } +} + +impl From for DnsName { + fn from(plain: DnsPlainName) -> Self { + let mut data = plain.data.try_mut().unwrap_or_else(BytesMut::from); + let mut pos : u8 = 0; + let mut prev_len : u8 = 0; + loop { + let label_len = data[pos as usize]; + data[pos as usize] ^= prev_len; + if 0 == label_len { break; } + pos += label_len + 1; + prev_len = label_len; + } + DnsName{ + data: data.freeze(), + label_count: plain.label_count, + } + } +} + +impl From for DnsPlainName { + fn from(name: DnsName) -> DnsPlainName { + let mut data = name.data.try_mut().unwrap_or_else(BytesMut::from); + let mut pos : u8 = 0; + let mut prev_len : u8 = 0; + loop { + let label_len = data[pos as usize] ^ prev_len; + data[pos as usize] = label_len; + if 0 == label_len { break; } + pos += label_len + 1; + prev_len = label_len; + } + DnsPlainName{ + data: data.freeze(), + label_count: name.label_count, + } + } +} + +impl<'a> IntoIterator for &'a DnsName { + type Item = DnsLabelRef<'a>; + type IntoIter = DnsNameIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.labels() + } +} + +impl fmt::Debug for DnsName { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + DisplayLabels{ + labels: self, + options: Default::default(), + }.fmt(w) + } +} + +impl fmt::Display for DnsName { + fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { + DisplayLabels{ + labels: self, + options: Default::default(), + }.fmt(w) + } +} + +impl DnsPacketData for DnsName { + fn deserialize(data: &mut Cursor) -> Result { + Ok(DnsName::from(DnsPlainName::deserialize(data)?)) + } +} + +/// Iterator type for [`DnsName::labels`] +/// +/// [`DnsName::labels`]: struct.DnsName.html#method.labels +#[derive(Clone)] +pub struct DnsNameIterator<'a> { + name_data: &'a [u8], + front_position: u8, + front_prev_label_len: u8, + back_position: u8, + back_next_label_len: u8, + labels_done: u8, + label_count: u8, +} + +impl<'a> Iterator for DnsNameIterator<'a> { + type Item = DnsLabelRef<'a>; + + fn next(&mut self) -> Option { + if self.labels_done >= self.label_count { return None } + self.labels_done += 1; + let label_len = self.name_data[self.front_position as usize] ^ self.front_prev_label_len; + self.front_prev_label_len = label_len; + let end = self.front_position+1+label_len; + let label = DnsLabelRef{label: &self.name_data[(self.front_position+1) as usize..end as usize]}; + self.front_position = end; + Some(label) + } + + fn size_hint(&self) -> (usize, Option) { + let count = self.len(); + (count, Some(count)) + } + + fn count(self) -> usize { + self.len() + } +} + +impl<'a> ExactSizeIterator for DnsNameIterator<'a> { + fn len(&self) -> usize { + (self.label_count - self.labels_done) as usize + } +} + +impl<'a> DoubleEndedIterator for DnsNameIterator<'a> { + fn next_back(&mut self) -> Option { + if self.labels_done >= self.label_count { return None } + self.labels_done += 1; + let label_len = self.name_data[self.back_position as usize] ^ self.back_next_label_len; + self.back_next_label_len = label_len; + let end = self.back_position; + self.back_position -= 1 + label_len; + let label = DnsLabelRef{label: &self.name_data[(self.back_position+1) as usize..end as usize]}; + Some(label) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn do_parse_and_display_name() + where + T: fmt::Display+fmt::Debug+DnsPacketData, + for<'a> &'a T: IntoIterator>, + { + { + let name = deserialize::(Bytes::from_static(b"\x07example\x03com\x00")).unwrap(); + assert_eq!( + format!("{}", name + ), + "example.com." + ); + assert_eq!( + name.into_iter().count(), + 2 + ); + } + assert_eq!( + format!( + "{}", + deserialize::(Bytes::from_static(b"\x07e!am.l\\\x03com\x00")).unwrap() + ), + "e\\041am\\.l\\\\.com." + ); + assert_eq!( + format!( + "{:?}", + deserialize::(Bytes::from_static(b"\x07e!am.l\\\x03com\x00")).unwrap() + ), + r#""e\\041am\\.l\\\\.com.""# + ); + } + + #[test] + fn parse_and_display_plain_name() { + do_parse_and_display_name::(); + } + + #[test] + fn parse_and_display_name() { + do_parse_and_display_name::(); + } + + #[test] + fn parse_and_reverse_name() { + let name = deserialize::(Bytes::from_static(b"\x03www\x07example\x03com\x00")).unwrap(); + assert_eq!( + format!( + "{}", + DisplayLabels{ + labels: name.labels().rev(), + options: DisplayLabelsOptions{ + separator: " ", + trailing: false, + }, + } + ), + "com example www" + ); + } + + #[test] + fn parse_and_convert_names() { + let name = deserialize::(Bytes::from_static(b"\x03www\x07example\x03com\x00")).unwrap(); + assert_eq!( + DnsPlainName::from(DnsName::from(name.clone())), + name + ); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..445bf42 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,5 @@ +extern crate dnsbox_base; + +fn main() { + println!("Test"); +}