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//!     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}