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}