dns_stamp_parser/lib.rs
1//! Provides a library to encode and decode [DNS stamp].
2//!
3//! [DNS stamp]: https://dnscrypt.info/stamps-specifications
4#![warn(
5 missing_debug_implementations,
6 missing_docs,
7 rust_2018_idioms,
8 unreachable_pub,
9 non_snake_case,
10 non_upper_case_globals
11)]
12#![allow(clippy::cognitive_complexity)]
13#![doc(test(
14 no_crate_inject,
15 attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
16))]
17mod decode;
18mod encode;
19mod error;
20
21pub use crate::error::{DecodeError, DecodeResult, EncodeError, EncodeResult};
22use bitflags::bitflags;
23use std::{
24 convert::TryFrom,
25 io,
26 net::{IpAddr, SocketAddr},
27};
28
29bitflags! {
30 /// Represent the [`props`].
31 ///
32 /// [`props`]: https://dnscrypt.info/stamps-specifications#dnscrypt-stamps
33 #[derive(Debug, Clone, PartialEq, Eq)]
34 pub struct Props: u64 {
35 /// If this flag is present then the server supports [DNSSEC].
36 ///
37 /// [DNSSEC]: https://tools.ietf.org/html/rfc3833
38 const DNSSEC = 0x01;
39 /// If this flag is present then the server does not keep logs
40 const NO_LOGS = 0x02;
41 /// If this flag is present then the server does not intentionally block domains
42 const NO_FILTER = 0x04;
43 }
44}
45
46/// This enum represent an address.
47/// An address in DNS Stamp can have port or a IP-Address and port.
48#[derive(Debug, Copy, PartialEq, Eq, Clone)]
49pub enum Addr {
50 /// a SocketAddr
51 SocketAddr(SocketAddr),
52 /// port number represented as a u16
53 Port(u16),
54}
55
56/// This enum represent all DNS Stamp type.
57#[repr(u8)]
58#[derive(Debug, Copy, Clone)]
59pub enum DnsStampType {
60 /// See [Plain DNS stamps].
61 ///
62 /// [Plain DNS stamps]: https://dnscrypt.info/stamps-specifications#plain-dns-stamps
63 Plain = 0x00,
64 /// See [DNSCrypt stamps].
65 ///
66 /// [DNSCrypt stamps]: https://dnscrypt.info/stamps-specifications#dnscrypt-stamps
67 DnsCrypt = 0x01,
68 /// See [DNS-over-HTTPS stamps].
69 ///
70 /// [DNS-over-HTTPS stamps]: https://dnscrypt.info/stamps-specifications#dns-over-https-stamps
71 DnsOverHttps = 0x02,
72 /// See [DNS-over-TLS stamps].
73 ///
74 /// [DNS-over-TLS stamps]: https://dnscrypt.info/stamps-specifications#dns-over-tls-stamps
75 DnsOverTls = 0x03,
76 /// See [DNS-over-QUIC stamps].
77 ///
78 /// [DNS-over-QUIC stamps]: https://dnscrypt.info/stamps-specifications#dns-over-quic-stamps
79 DnsOverQuic = 0x04,
80 /// See [Oblivious DoH target stamps].
81 ///
82 /// [Oblivious DoH target stamps]: https://dnscrypt.info/stamps-specifications#oblivious-doh-target-stamps
83 ObliviousDoHTarget = 0x05,
84 /// See [Anonymized DNSCrypt relay stamps].
85 ///
86 /// [Anonymized DNSCrypt relay stamps]: https://dnscrypt.info/stamps-specifications#anonymized-dnscrypt-relay-stamps
87 AnonymizedDnsCryptRelay = 0x81,
88 /// See [Oblivious DoH relay stamps].
89 ///
90 /// [Oblivious DoH relay stamps]: https://dnscrypt.info/stamps-specifications#oblivious-doh-relay-stamps
91 ObliviousDoHRelay = 0x85,
92}
93
94impl TryFrom<u8> for DnsStampType {
95 type Error = io::Error;
96
97 fn try_from(value: u8) -> Result<Self, Self::Error> {
98 match value {
99 0x00 => Ok(DnsStampType::Plain),
100 0x01 => Ok(DnsStampType::DnsCrypt),
101 0x02 => Ok(DnsStampType::DnsOverHttps),
102 0x03 => Ok(DnsStampType::DnsOverTls),
103 0x04 => Ok(DnsStampType::DnsOverQuic),
104 0x05 => Ok(DnsStampType::ObliviousDoHTarget),
105 0x81 => Ok(DnsStampType::AnonymizedDnsCryptRelay),
106 0x85 => Ok(DnsStampType::ObliviousDoHRelay),
107 _ => Err(io::Error::new(
108 io::ErrorKind::InvalidData,
109 "dns stamp type not found",
110 )),
111 }
112 }
113}
114
115/// This enum represent a [DNS Stamp].
116///
117/// [DNS Stamp]: https://dnscrypt.info/stamps-specifications/
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub enum DnsStamp {
120 /// See [Plain DNS stamps].
121 ///
122 /// [Plain DNS stamps]: https://dnscrypt.info/stamps-specifications#plain-dns-stamps
123 DnsPlain(DnsPlain),
124 /// See [DNSCrypt stamps].
125 ///
126 /// [DNSCrypt stamps]: https://dnscrypt.info/stamps-specifications#dnscrypt-stamps
127 DnsCrypt(DnsCrypt),
128 /// See [DNS-over-HTTPS stamps].
129 ///
130 /// [DNS-over-HTTPS stamps]: https://dnscrypt.info/stamps-specifications#dns-over-https-stamps
131 DnsOverHttps(DnsOverHttps),
132 /// See [DNS-over-TLS stamps].
133 ///
134 /// [DNS-over-TLS stamps]: https://dnscrypt.info/stamps-specifications#dns-over-tls-stamps
135 DnsOverTls(DnsOverTls),
136 /// See [DNS-over-QUIC stamps].
137 ///
138 /// [DNS-over-QUIC stamps]: https://dnscrypt.info/stamps-specifications#dns-over-quic-stamps
139 DnsOverQuic(DnsOverTls),
140 /// See [Oblivious DoH target stamps].
141 ///
142 /// [Oblivious DoH target stamps]: https://dnscrypt.info/stamps-specifications#oblivious-doh-target-stamps
143 ObliviousDoHTarget(ObliviousDoHTarget),
144 /// See [Plain DNS stamps].
145 ///
146 /// [Plain DNS stamps]: https://dnscrypt.info/stamps-specifications#anonymized-dnscrypt-relay-stamps
147 AnonymizedDnsCryptRelay(AnonymizedDnsCryptRelay),
148 /// See [Oblivious DoH relay stamps].
149 ///
150 /// [Oblivious DoH relay stamps]: https://dnscrypt.info/stamps-specifications#oblivious-doh-relay-stamps
151 ObliviousDoHRelay(DnsOverHttps),
152}
153
154/// Dnscrypt configuration parsed from dnsstamp
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct DnsCrypt {
157 /// server properties
158 props: Props,
159 /// addr is the IP address, as a string, with a port number if the server
160 /// is not accessible over the standard port for the protocol (443).
161 addr: SocketAddr,
162 /// pk is the DNSCrypt provider’s Ed25519 public key, as 32 raw bytes.
163 pk: [u8; 32],
164 /// providerName is the DNSCrypt provider name.
165 provider_name: String,
166}
167
168/// DoH configuration parsed from a dnsstamp
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct DnsOverHttps {
171 /// server properties
172 pub props: Props,
173 /// addr is the IP address of the server. It can be an empty string,
174 /// or just a port number, represented with a preceding colon (:443).
175 /// In that case, the host name will be resolved to an IP address using another resolver.
176 pub addr: Option<Addr>,
177 /// hashi is the SHA256 digest of one of the TBS certificate found in the validation chain,
178 /// typically the certificate used to sign the resolver’s certificate. Multiple hashes can
179 /// be provided for seamless rotations.
180 pub hashi: Vec<[u8; 32]>,
181 /// hostname is the server host name which will also be used as a SNI name.
182 /// If the host name contains characters outside the URL-permitted range,
183 /// these characters should be sent as-is, without any extra encoding
184 /// (neither URL-encoded nor punycode).
185 pub hostname: String,
186 /// path is the absolute URI path, such as /dns-query.
187 pub path: String,
188 /// bootstrap_ipi are IP addresses of recommended resolvers accessible over standard DNS
189 /// in order to resolve hostname. This is optional, and clients can ignore this information.
190 pub bootstrap_ipi: Vec<IpAddr>,
191}
192
193/// DNS over TLS configuration
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct DnsOverTls {
196 /// server properties
197 pub props: Props,
198 /// addr is the IP address of the server. It can be an empty string,
199 /// or just a port number, represented with a preceding colon (:443).
200 /// In that case, the host name will be resolved to an IP address using another resolver.
201 pub addr: Option<Addr>,
202 /// hashi is the SHA256 digest of one of the TBS certificate found in the validation chain,
203 /// typically the certificate used to sign the resolver’s certificate. Multiple hashes can
204 /// be provided for seamless rotations.
205 pub hashi: Vec<[u8; 32]>,
206 /// hostname is the server host name which will also be used as a SNI name.
207 /// If the host name contains characters outside the URL-permitted range,
208 /// these characters should be sent as-is, without any extra encoding
209 /// (neither URL-encoded nor punycode).
210 pub hostname: String,
211 /// bootstrap_ipi are IP addresses of recommended resolvers accessible over standard DNS
212 /// in order to resolve hostname. This is optional, and clients can ignore this information.
213 pub bootstrap_ipi: Vec<IpAddr>,
214}
215
216/// Oblivious DoH target stamps
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub struct ObliviousDoHTarget {
219 /// server properties
220 pub props: Props,
221 /// hostname is the server host name which, for relays, will also be used as a SNI name.
222 /// If the host name contains characters outside the URL-permitted range, these characters
223 /// should be sent as-is, without any extra encoding (neither URL-encoded nor punycode).
224 pub hostname: String,
225 /// path is the absolute URI path, such as /dns-query.
226 pub path: String,
227}
228
229/// Plain dns configuration parsed from a dnsstamp
230#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct DnsPlain {
232 /// server properties
233 pub props: Props,
234 /// addr is the IP address of the server.
235 /// IPv6 strings must be included in square brackets: `[fe80::6d6d:f72c:3ad:60b8]`.
236 /// Scopes are permitted.
237 pub addr: IpAddr,
238}
239
240/// Anonymized dnscrypt relay configuration parsed from a dnsstamp
241#[derive(Debug, Clone, PartialEq, Eq)]
242pub struct AnonymizedDnsCryptRelay {
243 /// 0x81 is the protocol identifier for a DNSCrypt relay.
244 /// addr is the IP address and port, as a string.
245 /// IPv6 strings must be included in square brackets: `[fe80::6d6d:f72c:3ad:60b8]:443`.
246 pub addr: SocketAddr,
247}
248
249impl DnsOverHttps {
250 /// get hostname for DOH config
251 pub fn hostname(&self) -> String {
252 hostname(self.addr, &self.hostname)
253 }
254
255 /// get hostname based on the bootstrap information
256 #[cfg(feature = "resolve")]
257 pub fn bootstrap_hostname(&self) -> io::Result<String> {
258 bootstrap_hostname(self.addr, &self.hostname, &self.bootstrap_ipi)
259 }
260}
261
262impl DnsOverTls {
263 /// get hostname from config
264 pub fn hostname(&self) -> String {
265 hostname(self.addr, &self.hostname)
266 }
267
268 /// get hostname based on the bootstrap information
269 #[cfg(feature = "resolve")]
270 pub fn bootstrap_hostname(&self) -> io::Result<String> {
271 bootstrap_hostname(self.addr, &self.hostname, &self.bootstrap_ipi)
272 }
273}
274
275#[inline]
276fn hostname(addr: Option<Addr>, host: &str) -> String {
277 match addr {
278 None => format!("{}:443", host),
279 Some(Addr::Port(port)) => format!("{}:{}", host, port),
280 Some(Addr::SocketAddr(addr)) => addr.to_string(),
281 }
282}
283
284#[cfg(feature = "resolve")]
285#[inline]
286fn bootstrap_hostname(addr: Option<Addr>, host: &str, bootstrap: &[IpAddr]) -> io::Result<String> {
287 use trust_dns_resolver::config::*;
288 use trust_dns_resolver::Resolver;
289
290 if !bootstrap.is_empty() {
291 let mut config = ResolverConfig::new();
292 for ip in bootstrap {
293 let socket_addr = SocketAddr::new(*ip, 53);
294 config.add_name_server(NameServerConfig::new(socket_addr, Protocol::Udp));
295 }
296
297 let resolver = Resolver::new(config, ResolverOpts::default()).unwrap();
298
299 let resp = resolver.lookup_ip(host)?;
300
301 Ok(resp
302 .iter()
303 .next()
304 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "address not found"))?
305 .to_string())
306 } else {
307 Ok(hostname(addr, host))
308 }
309}