1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! # Dominion Parser
//!
//! DNS parser with a focus on usage of the type system to create a declarative
//! experience when parsing or serializing DNS packets. It allows parsing and serializing
//! whole packets or individual elements, like the header or the different questions and
//! resource records. Not all resource records have been implemented, if some are missing
//! that are relevant for your use case please open an [issue](https://github.com/lopukhov/dominion/issues).
//!
//! ## Parsing
//!
//! ```rust
//!
//! use dominion_parser::DnsPacket;
//!
//! const REQ: &'static [u8; 33] = include_bytes!("../assets/dns_request.bin");
//!
//! fn main() {
//! let packet = DnsPacket::try_from(&REQ[..]).unwrap();
//! println!("The request was:");
//! println!("{:#?}", packet);
//! }
//! ```
//!
//! Parsing can fail with a [ParseError].
//!
//! ## Serializing
//!
//! ```rust
//! use dominion_parser::body::{RecordData, RecordPreamble, ResourceRecord};
//! use dominion_parser::header::{AuthenticData, QueryResponse, RecursionAvailable};
//! use dominion_parser::DnsPacket;
//!
//! const REQ: &'static [u8; 33] = include_bytes!("../assets/dns_request.bin");
//!
//! fn main() {
//! let mut res = DnsPacket::try_from(&REQ[..]).unwrap();
//!
//! // Change some flags
//! res.header.flags.qr = QueryResponse::Response;
//! res.header.flags.ra = RecursionAvailable::Available;
//! res.header.flags.ad = AuthenticData::NotAuthentic;
//!
//! // Add answer
//! let preamble = RecordPreamble {
//! name: res.questions[0].name.clone(),
//! rrtype: res.questions[0]
//! .qtype
//! .try_into()
//! .expect("QType is not a valid Type"),
//! class: res.questions[0].class,
//! ttl: 300,
//! rdlen: 4,
//! };
//! let data = RecordData::A("204.74.99.100".parse().unwrap());
//! let answer = ResourceRecord { preamble, data };
//! res.header.answers = 1;
//! res.answers.push(answer);
//!
//! let res = Vec::<u8>::from(&res);
//!
//! println!("=================== My Response ===================");
//! println!("{:?}", res);
//! }
//! ```
#![warn(
missing_docs,
rust_2018_idioms,
missing_debug_implementations,
rustdoc::broken_intra_doc_links
)]
use thiserror::Error;
use body::Question;
use body::ResourceRecord;
use header::DnsHeader;
mod binutils;
/// The body of the DNS packet (Questions and Resource Records)
pub mod body;
/// The header of the DNS packet
pub mod header;
/// Represents a complete DNS packet.
///
/// A DNS packet has the following sections in order:
///
/// ```text
/// +---------------------+
/// | Header |
/// +---------------------+
/// | Question | the question(s) for the name server
/// +---------------------+
/// | Answer | RRs answering the question
/// +---------------------+
/// | Authority | RRs pointing toward an authority
/// +---------------------+
/// | Additional | RRs holding additional information
/// +---------------------+
/// ```
///
/// For the header the [DnsHeader] type is used. For the rest, Questions are represented
/// with the [Question] type, and RRs with the [ResourceRecord] type.
#[derive(Debug, Clone)]
pub struct DnsPacket<'a> {
/// The DNS Header
pub header: DnsHeader,
/// The question(s) for the name server
pub questions: Vec<Question<'a>>,
/// Resource Records answering the question(s)
pub answers: Vec<ResourceRecord<'a>>,
/// Resource Records pointing toward a domain authority
pub authority: Vec<ResourceRecord<'a>>,
/// Resource Records holding additional information
pub additional: Vec<ResourceRecord<'a>>,
}
impl<'a> TryFrom<&'a [u8]> for DnsPacket<'a> {
type Error = ParseError;
fn try_from(buff: &'a [u8]) -> Result<Self, Self::Error> {
let header = DnsHeader::try_from(buff)?;
let mut questions = Vec::with_capacity(header.questions as _);
let mut answers = Vec::with_capacity(header.answers as _);
let mut authority = Vec::with_capacity(header.authority as _);
let mut additional = Vec::with_capacity(header.additional as _);
let mut pos = 12;
for _ in 0..header.questions {
let (q, size) = Question::parse(buff, pos)?;
pos += size;
questions.push(q);
}
for _ in 0..header.answers {
let (a, size) = ResourceRecord::parse(buff, pos)?;
pos += size;
answers.push(a)
}
for _ in 0..header.authority {
let (a, size) = ResourceRecord::parse(buff, pos)?;
pos += size;
authority.push(a)
}
for _ in 0..header.additional {
let (a, size) = ResourceRecord::parse(buff, pos)?;
pos += size;
additional.push(a)
}
Ok(Self {
header,
questions,
answers,
authority,
additional,
})
}
}
impl From<&DnsPacket<'_>> for Vec<u8> {
fn from(dns: &DnsPacket<'_>) -> Self {
let mut out = (&dns.header).into();
for question in &dns.questions {
question.serialize(&mut out);
}
for answer in &dns.answers {
answer.serialize(&mut out);
}
for auth in &dns.authority {
auth.serialize(&mut out);
}
for extra in &dns.additional {
extra.serialize(&mut out);
}
out
}
}
/// An error was encountered when trying to parse a byte buffer into a DNS packet
#[derive(Error, Debug)]
pub enum ParseError {
/// The length of the header is too small.
#[error(
"Length of packet ({0} bytes) is too small to contain a DNS header (12 bytes in length)."
)]
HeaderLength(usize),
/// Some header flag used a value that has not been implemented.
#[error("Flag {0} has no implementation for value {1} in the current version.")]
HeaderFlag(&'static str, u16),
/// There was a jump to a position forward in the packet (it does not follow the specification) or to itself (it is not sound as it would result in a DoS).
#[error("Jump points to a section of the packet equal or greater than the current position.")]
InvalidJump,
/// Some domain name has been compressed with too many jumps. This error may be removed in the future.
#[error(
"DNS compression contains excesive number of jumps {0} (maximum {})",
crate::body::name::MAX_JUMPS
)]
ExcesiveJumps(u8),
/// The DNS packet contains a label prefix that is not a length prefix or a pointer. Those values dont have a standard definition so are not implemented.
#[error("Byte {0:#b} does not have a pointer or length prefix.")]
LabelPrefix(u8),
/// The packet tried to cause an out-of-bound read.
#[error("Out-of-bounds read attempt at position {0}")]
OobRead(usize),
/// Some text is not valid UTF-8.
#[error("Non UTF-8 text string: {0}")]
NonUtf8(#[from] std::str::Utf8Error),
/// Error when parsing a domain name
#[error("Domain name could not be parsed: {0}")]
InvalidName(#[from] crate::body::name::NameError),
}