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