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

mod dns_class;
mod dns_message;
mod dns_message_header;
mod dns_name;
mod dns_op_code;
mod dns_question;
mod dns_record;
mod dns_response_code;
mod dns_type;
mod server;

pub use dns_class::DnsClass;
pub use dns_message::DnsMessage;
pub use dns_message_header::DnsMessageHeader;
pub use dns_name::DnsName;
pub use dns_op_code::DnsOpCode;
pub use dns_question::DnsQuestion;
pub use dns_record::DnsRecord;
pub use dns_response_code::DnsResponseCode;
pub use dns_type::DnsType;
pub use server::{process_datagram, process_request, serve_udp, Builder};

use fixed_buffer::FixedBuf;

fn read_exact<const N: usize, const M: usize>(buf: &mut FixedBuf<N>) -> Result<[u8; M], DnsError> {
    let mut result = [0_u8; M];
    buf.try_read_exact(&mut result).ok_or(DnsError::Truncated)?;
    Ok(result)
}

fn read_u8<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u8, DnsError> {
    buf.try_read_byte().ok_or(DnsError::Truncated)
}

// fn write_u8<const N: usize>(out: &mut FixedBuf<N>, value: u8) -> Result<(), DnsError> {
//     out.write_bytes(&[value])
//         .map_err(|_| DnsError::ResponseBufferFull)?;
//     Ok(())
// }

fn read_u16_be<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u16, DnsError> {
    let bytes: [u8; 2] = read_exact(buf)?;
    Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
}

fn read_u32_be<const N: usize>(buf: &mut FixedBuf<N>) -> Result<u32, DnsError> {
    let bytes: [u8; 4] = read_exact(buf)?;
    Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}

fn write_bytes<const N: usize>(out: &mut FixedBuf<N>, bytes: &[u8]) -> Result<(), DnsError> {
    out.write_bytes(bytes)
        .map_err(|_| DnsError::ResponseBufferFull)?;
    Ok(())
}

fn write_u16_be<const N: usize>(out: &mut FixedBuf<N>, value: u16) -> Result<(), DnsError> {
    let bytes: [u8; 2] = value.to_be_bytes();
    out.write_bytes(&bytes)
        .map_err(|_| DnsError::ResponseBufferFull)?;
    Ok(())
}

fn write_u32_be<const N: usize>(out: &mut FixedBuf<N>, value: u32) -> Result<(), DnsError> {
    let bytes: [u8; 4] = value.to_be_bytes();
    out.write_bytes(&bytes)
        .map_err(|_| DnsError::ResponseBufferFull)?;
    Ok(())
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum DnsError {
    InvalidClass,
    InvalidLabel,
    InvalidOpCode,
    NameTooLong,
    NoQuestion,
    NotARequest,
    NotFound,
    ResponseBufferFull,
    QueryHasAdditionalRecords,
    QueryHasAnswer,
    QueryHasNameServer,
    TooManyAdditional,
    TooManyAnswers,
    TooManyLabels,
    TooManyNameServers,
    TooManyQuestions,
    Truncated,
    Internal(String),
    Unreachable(&'static str, u32),
}