dns_server/lib.rs
1//! dns-server
2//! ========
3//! [](https://crates.io/crates/dns-server)
4//! [](https://gitlab.com/leonhard-llc/ops/-/raw/main/dns-server/LICENSE)
5//! [](https://github.com/rust-secure-code/safety-dance/)
6//! [](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//! DnsRecord::new_ns("sub.example.com", "ns1.sub.example.com").unwrap(),
50//! ];
51//! dns_server::Builder::new_port(8053)
52//! .unwrap()
53//! .with_permit(permit)
54//! .serve_static(&records)
55//! .unwrap();
56//! ```
57//!
58//! # Related Crates
59//!
60//! # Cargo Geiger Safety Report
61//! # Changelog
62//! - 2025-08-21 v0.2.6
63//! - TXT records.
64//! - Rejects malformed A/AAAA/CNAME/NS records with extra unused bytes
65//! - 2025-05-25 v0.2.5 - NS records
66//! - 2024-09-29 v0.2.4 - Update a dependency
67//! - 2024-04-08 v0.2.3 - Remove a dependency
68//! - 2024-04-08 v0.2.2 - Support [case randomization](https://datatracker.ietf.org/doc/html/draft-vixie-dnsext-dns0x20-00).
69//! - 2024-03-27 v0.2.1
70//! - Dynamic responses
71//! - Randomize order of static responses
72//! - 2022-04-25 v0.1.0 - Initial version
73//!
74//! # To Do
75//! - Message compression
76//! - Decide whether to send back error responses.
77//! - Ergonomic constructors that take `OsStr`, for using environment variables
78//! - Custom TTLs
79//! - NS glue records
80//! - Client
81//! - Caching client
82//! - Recursive resolver
83//!
84//! # Alternatives
85//!
86#![forbid(unsafe_code)]
87
88mod dns_class;
89mod dns_message;
90mod dns_message_header;
91mod dns_name;
92mod dns_op_code;
93mod dns_question;
94mod dns_record;
95mod dns_response_code;
96mod dns_string;
97mod dns_type;
98mod server;
99
100pub use dns_class::DnsClass;
101pub use dns_message::DnsMessage;
102pub use dns_message_header::DnsMessageHeader;
103pub use dns_name::DnsName;
104pub use dns_op_code::DnsOpCode;
105pub use dns_question::DnsQuestion;
106pub use dns_record::DnsRecord;
107pub use dns_response_code::DnsResponseCode;
108pub use dns_type::DnsType;
109pub use server::{process_datagram, process_request, serve_udp, Builder};
110
111use fixed_buffer::FixedBuf;
112
113fn read_exact<const N: usize, const M: usize>(buf: &mut FixedBuf<N>) -> Result<[u8; M], DnsError> {
114 let mut result = [0_u8; M];
115 buf.try_read_exact(&mut result).ok_or(DnsError::Truncated)?;
116 Ok(result)
117}
118
119fn read_u8<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u8, DnsError> {
120 buf.try_read_byte().ok_or(DnsError::Truncated)
121}
122
123// fn write_u8<const N: usize>(out: &mut FixedBuf<N>, value: u8) -> Result<(), DnsError> {
124// out.write_bytes(&[value])
125// .map_err(|_| DnsError::ResponseBufferFull)?;
126// Ok(())
127// }
128
129fn read_u16_be<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u16, DnsError> {
130 let bytes: [u8; 2] = read_exact(buf)?;
131 Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
132}
133
134fn read_u32_be<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u32, DnsError> {
135 let bytes: [u8; 4] = read_exact(buf)?;
136 Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
137}
138
139fn write_bytes<const N: usize>(out: &mut FixedBuf<N>, bytes: &[u8]) -> Result<(), DnsError> {
140 out.write_bytes(bytes)
141 .map_err(|_| DnsError::ResponseBufferFull)?;
142 Ok(())
143}
144
145fn write_u16_be<const N: usize>(out: &mut FixedBuf<N>, value: u16) -> Result<(), DnsError> {
146 let bytes: [u8; 2] = value.to_be_bytes();
147 out.write_bytes(&bytes)
148 .map_err(|_| DnsError::ResponseBufferFull)?;
149 Ok(())
150}
151
152fn write_u32_be<const N: usize>(out: &mut FixedBuf<N>, value: u32) -> Result<(), DnsError> {
153 let bytes: [u8; 4] = value.to_be_bytes();
154 out.write_bytes(&bytes)
155 .map_err(|_| DnsError::ResponseBufferFull)?;
156 Ok(())
157}
158
159#[derive(Clone, Debug, Eq, Hash, PartialEq)]
160pub enum DnsError {
161 InvalidClass,
162 InvalidLabel,
163 InvalidOpCode,
164 NameTooLong,
165 NoQuestion,
166 NotARequest,
167 NotFound,
168 RecordHasAdditionalBytes,
169 ResponseBufferFull,
170 QueryHasAdditionalRecords,
171 QueryHasAnswer,
172 QueryHasNameServer,
173 StringTooLong,
174 TooManyAdditional,
175 TooManyAnswers,
176 TooManyLabels,
177 TooManyNameServers,
178 TooManyQuestions,
179 Truncated,
180 Internal(String),
181 Unreachable(&'static str, u32),
182}