This commit is contained in:
Stefan Bühler 2017-12-16 21:58:18 +01:00
commit 63cd9196f9
31 changed files with 3303 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target/
**/*.rs.bk

217
Cargo.lock generated Normal file
View File

@ -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"

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "dnsbox"
version = "0.1.0"
authors = ["Stefan Bühler <stbuehler@web.de>"]
[dependencies]
dnsbox-base = { path = "lib/dnsbox-base" }
[workspace]

View File

@ -0,0 +1,13 @@
[package]
name = "dnsbox-base"
version = "0.1.0"
authors = ["Stefan Bühler <stbuehler@web.de>"]
[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"

View File

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

View File

@ -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;

View File

@ -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<Item=DnsLabelRef<'a>>+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<Item=DnsLabelRef<'a>>+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<Item=DnsLabelRef<'a>>+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(())
}
}

View File

@ -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<Self> {
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<DnsLabelRef<'a>>>(&self, rhs: L) -> Ordering {
compare_ascii_lc(self.as_raw(), rhs.into().as_raw())
}
}
impl<'a> From<DnsLabelRef<'a>> for DnsLabel {
fn from(label_ref: DnsLabelRef<'a>) -> Self {
DnsLabel{
label: Bytes::from(label_ref.label),
}
}
}
impl PartialEq<DnsLabel> for DnsLabel {
#[inline]
fn eq(&self, rhs: &DnsLabel) -> bool {
self.as_ref().eq(&rhs.as_ref())
}
}
impl<'a> PartialEq<DnsLabelRef<'a>> for DnsLabel {
#[inline]
fn eq(&self, rhs: &DnsLabelRef<'a>) -> bool {
self.as_ref().eq(rhs)
}
}
impl Eq for DnsLabel{}
impl PartialOrd<DnsLabel> for DnsLabel {
#[inline]
fn partial_cmp(&self, rhs: &DnsLabel) -> Option<Ordering> {
Some(self.compare(rhs))
}
}
impl<'a> PartialOrd<DnsLabelRef<'a>> for DnsLabel {
#[inline]
fn partial_cmp(&self, rhs: &DnsLabelRef<'a>) -> Option<Ordering> {
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<Self> {
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<DnsLabelRef<'b>>>(&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<DnsLabelRef<'a>> 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<DnsLabel> 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<DnsLabelRef<'a>> for DnsLabelRef<'b> {
#[inline]
fn partial_cmp(&self, rhs: &DnsLabelRef<'a>) -> Option<Ordering> {
Some(self.compare(*rhs))
}
}
impl<'a> PartialOrd<DnsLabel> for DnsLabelRef<'a> {
#[inline]
fn partial_cmp(&self, rhs: &DnsLabel) -> Option<Ordering> {
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(())
}
}

View File

@ -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<DnsName> 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<Bytes>) -> Result<Self> {
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<DnsCompressedName> for DnsCompressedName {
fn eq(&self, rhs: &DnsCompressedName) -> bool {
self.0 == rhs.0
}
}
impl PartialEq<DnsName> for DnsCompressedName {
fn eq(&self, rhs: &DnsName) -> bool {
&self.0 == rhs
}
}
impl PartialEq<DnsCompressedName> 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<Bytes>) -> Result<Self> {
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<Self::Item> {
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<usize>) {
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<Self::Item> {
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<DnsName> {
let result = packet::deserialize::<DnsName>(Bytes::from_static(bytes))?;
{
let check_result = packet::deserialize::<DnsName>(result.clone().encode()).unwrap();
assert_eq!(check_result, result);
}
Ok(result)
}
*/
fn de_uncompressed(bytes: &'static [u8]) -> Result<DnsName> {
let result = packet::deserialize::<DnsName>(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<DnsCompressedName> {
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."
);
}
}

View File

@ -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<DnsLabelRef<'a>>>(&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<DnsLabelRef<'a>>>(&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
}
}