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}