dns_server/
lib.rs

1//! dns-server
2//! ========
3//! [![crates.io version](https://img.shields.io/crates/v/dns-server.svg)](https://crates.io/crates/dns-server)
4//! [![license: Apache 2.0](https://gitlab.com/leonhard-llc/ops/-/raw/main/license-apache-2.0.svg)](https://gitlab.com/leonhard-llc/ops/-/raw/main/dns-server/LICENSE)
5//! [![unsafe forbidden](https://gitlab.com/leonhard-llc/ops/-/raw/main/unsafe-forbidden.svg)](https://github.com/rust-secure-code/safety-dance/)
6//! [![pipeline status](https://gitlab.com/leonhard-llc/ops/badges/main/pipeline.svg)](https://gitlab.com/leonhard-llc/ops/-/pipelines)
7//!
8//! A threaded DNS server library.
9//!
10//! # Use Cases
11//! - Make your API server its own DNS server.
12//!   This eliminates the DNS server as a separate point of failure.
13//! - Keep your DNS config in code, next to your server code.
14//!   Include it in code reviews and integration tests.
15//! - DNS-based
16//!   [domain validation for free ACME certificates](https://letsencrypt.org/how-it-works/).
17//!   This is useful for servers that don't listen on port 80.
18//!   Servers on port 80 can use HTTP for domain validation and don't need to use this.
19//!
20//! # Features
21//! - Depends only on `std`
22//! - `forbid(unsafe_code)`
23//! - ?% test coverage
24//!
25//! # Limitations
26//! - Brand new.
27//!
28//! # Example
29//! ```
30//! use dns_server::DnsRecord;
31//! use permit::Permit;
32//! use signal_hook::consts::{SIGHUP, SIGINT, SIGQUIT, SIGTERM};
33//! use signal_hook::iterator::Signals;
34//!
35//! let top_permit = Permit::new();
36//! let permit = top_permit.new_sub();
37//! # top_permit.revoke();
38//! std::thread::spawn(move || {
39//!     Signals::new([SIGHUP, SIGINT, SIGQUIT, SIGTERM])
40//!         .unwrap()
41//!         .forever()
42//!         .next();
43//!     drop(top_permit);
44//! });
45//! let records = vec![
46//!     DnsRecord::new_a("aaa.example.com", "10.0.0.1").unwrap(),
47//!     DnsRecord::new_aaaa("aaa.example.com", "2606:2800:220:1:248:1893:25c8:1946").unwrap(),
48//!     DnsRecord::new_cname("bbb.example.com", "ccc.example.com").unwrap(),
49//! ];
50//! dns_server::Builder::new_port(8053)
51//!     .unwrap()
52//!     .with_permit(permit)
53//!     .serve_static(&records)
54//!     .unwrap();
55//! ```
56//!
57//! # Related Crates
58//!
59//! # Cargo Geiger Safety Report
60//! # Changelog
61//! - v0.2.4 - Depend on `permit` v0.2
62//! - v0.2.3 - Remove a dependency
63//! - v0.2.2 - Support [case randomization](https://datatracker.ietf.org/doc/html/draft-vixie-dnsext-dns0x20-00).
64//! - v0.2.1
65//!     - New API supporting dynamic responses.
66//!     - Randomize order of static responses.
67//! - v0.1.0 - Initial version
68//!
69//! # To Do
70//! - Message compression
71//! - Decide whether to send back error responses.
72//! - Ergonomic constructors that take `OsStr`, for using environment variables
73//! - Custom TTLs
74//! - NS records (and glue)
75//! - Client
76//! - Caching client
77//! - Recursive resolver
78//!
79//! # Alternatives
80//!
81#![forbid(unsafe_code)]
82
83mod dns_class;
84mod dns_message;
85mod dns_message_header;
86mod dns_name;
87mod dns_op_code;
88mod dns_question;
89mod dns_record;
90mod dns_response_code;
91mod dns_type;
92mod server;
93
94pub use dns_class::DnsClass;
95pub use dns_message::DnsMessage;
96pub use dns_message_header::DnsMessageHeader;
97pub use dns_name::DnsName;
98pub use dns_op_code::DnsOpCode;
99pub use dns_question::DnsQuestion;
100pub use dns_record::DnsRecord;
101pub use dns_response_code::DnsResponseCode;
102pub use dns_type::DnsType;
103pub use server::{process_datagram, process_request, serve_udp, Builder};
104
105use fixed_buffer::FixedBuf;
106
107fn read_exact<const N: usize, const M: usize>(buf: &mut FixedBuf<N>) -> Result<[u8; M], DnsError> {
108    let mut result = [0_u8; M];
109    buf.try_read_exact(&mut result).ok_or(DnsError::Truncated)?;
110    Ok(result)
111}
112
113fn read_u8<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u8, DnsError> {
114    buf.try_read_byte().ok_or(DnsError::Truncated)
115}
116
117// fn write_u8<const N: usize>(out: &mut FixedBuf<N>, value: u8) -> Result<(), DnsError> {
118//     out.write_bytes(&[value])
119//         .map_err(|_| DnsError::ResponseBufferFull)?;
120//     Ok(())
121// }
122
123fn read_u16_be<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u16, DnsError> {
124    let bytes: [u8; 2] = read_exact(buf)?;
125    Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
126}
127
128fn read_u32_be<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u32, DnsError> {
129    let bytes: [u8; 4] = read_exact(buf)?;
130    Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
131}
132
133fn write_bytes<const N: usize>(out: &mut FixedBuf<N>, bytes: &[u8]) -> Result<(), DnsError> {
134    out.write_bytes(bytes)
135        .map_err(|_| DnsError::ResponseBufferFull)?;
136    Ok(())
137}
138
139fn write_u16_be<const N: usize>(out: &mut FixedBuf<N>, value: u16) -> Result<(), DnsError> {
140    let bytes: [u8; 2] = value.to_be_bytes();
141    out.write_bytes(&bytes)
142        .map_err(|_| DnsError::ResponseBufferFull)?;
143    Ok(())
144}
145
146fn write_u32_be<const N: usize>(out: &mut FixedBuf<N>, value: u32) -> Result<(), DnsError> {
147    let bytes: [u8; 4] = value.to_be_bytes();
148    out.write_bytes(&bytes)
149        .map_err(|_| DnsError::ResponseBufferFull)?;
150    Ok(())
151}
152
153#[derive(Clone, Debug, Eq, Hash, PartialEq)]
154pub enum DnsError {
155    InvalidClass,
156    InvalidLabel,
157    InvalidOpCode,
158    NameTooLong,
159    NoQuestion,
160    NotARequest,
161    NotFound,
162    ResponseBufferFull,
163    QueryHasAdditionalRecords,
164    QueryHasAnswer,
165    QueryHasNameServer,
166    TooManyAdditional,
167    TooManyAnswers,
168    TooManyLabels,
169    TooManyNameServers,
170    TooManyQuestions,
171    Truncated,
172    Internal(String),
173    Unreachable(&'static str, u32),
174}