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