Skip to main content

web_faith_dns/
settings.rs

1//! Resolver settings.
2use std::{fmt, net::SocketAddr, time::Duration};
3
4use hickory_resolver::{
5	config::{NameServerConfig, ProtocolConfig},
6	proto::rr::Name,
7};
8
9use crate::transport::{ServerSpec, Transport};
10
11/// How a nameserver came to be reached the way it is.
12// spec:OBS#resolvers
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum ResolverSource {
16	/// Named by the caller.
17	Configured,
18	/// Discovered from the system's resolver configuration.
19	Conventional,
20}
21
22impl fmt::Display for ResolverSource {
23	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24		f.write_str(match self {
25			Self::Configured => "configured",
26			Self::Conventional => "conventional",
27		})
28	}
29}
30
31/// One line of `resolvers()`.
32#[derive(Clone, Debug)]
33#[non_exhaustive]
34pub struct ResolverReport {
35	/// The nameserver's address.
36	pub address: SocketAddr,
37	/// The transport in use.
38	pub transport: Transport,
39	/// How that transport was arrived at.
40	pub source: ResolverSource,
41}
42
43/// [`ResolverConfig::serve_stale`]'s default.
44///
45/// Long enough that a resolver outage does not stop an agent reaching hosts it knows, short enough
46/// that a host which has moved stops being served a dead address for the life of the process.
47// spec:DNS#serving-stale-answers
48pub const DEFAULT_MAX_STALE: Duration = Duration::from_secs(3600);
49
50/// Configuration for initialising a [`FaithResolver`](crate::FaithResolver).
51#[derive(Clone, Debug)]
52pub struct ResolverConfig {
53	/// The nameservers to consult, in order. Empty takes the system's own configuration.
54	pub servers: Vec<ServerSpec>,
55	/// How long a lookup may take across the whole server list, so exhausting several dead
56	/// servers costs one timeout rather than one each.
57	pub timeout: Option<Duration>,
58	/// How many dots a name must contain before it is tried as given, ahead of the search list.
59	pub ndots: Option<usize>,
60	/// The domains appended to a name that is not fully qualified, replacing the system's list.
61	pub search_domains: Option<Vec<Name>>,
62	/// Whether to consult the hosts file. `None` follows the platform's own convention.
63	pub hosts_file: Option<bool>,
64	/// Further domains to send to the system resolver, added to the ones always exempt.
65	pub exempt_domains: Vec<Name>,
66	/// Whether an expired answer may be served, and how long for.
67	///
68	/// A fresh lookup runs behind one that is; an entry older than this is discarded instead.
69	pub serve_stale: Option<Duration>,
70}
71
72impl Default for ResolverConfig {
73	fn default() -> Self {
74		Self {
75			servers: Vec::new(),
76			timeout: None,
77			ndots: None,
78			search_domains: None,
79			hosts_file: None,
80			exempt_domains: Vec::new(),
81			// Defaulted here as well as in the option parsing, so a resolver built directly (in tests,
82			// and for the global default agent) serves stale like a configured one.
83			serve_stale: Some(DEFAULT_MAX_STALE),
84		}
85	}
86}
87
88/// The suffixes handed to the system resolver rather than the configured ones: `localhost` and
89/// `local` always, plus the system's own and the caller's.
90///
91/// The root name is never a suffix here, whichever list it arrives in. It is the parent of every
92/// name, so admitting it would exempt the lot and route every lookup to the system resolver with
93/// servers configured and unused. It does arrive in practice: a Windows host with no DNS
94/// domain of its own reports the root as its domain, so the check keeps the encrypted
95/// transports working there rather than being quietly bypassed.
96// spec:DNS#exempt-names
97pub(crate) fn exempt_suffixes(system: Vec<Name>, configured: &[Name]) -> Vec<Name> {
98	let mut names = vec![
99		Name::from_ascii("localhost").unwrap(),
100		Name::from_ascii("local").unwrap(),
101	];
102	names.extend(
103		system
104			.into_iter()
105			.chain(configured.iter().cloned())
106			.filter(|name| !name.is_root()),
107	);
108	names
109}
110
111/// Summarise name servers for `resolvers()`, in the order they are queried.
112pub(crate) fn report(
113	name_servers: &[NameServerConfig],
114	source: ResolverSource,
115) -> Vec<ResolverReport> {
116	let mut reports = Vec::new();
117	for server in name_servers {
118		for connection in &server.connections {
119			let transport = match connection.protocol {
120				ProtocolConfig::Udp => Transport::Udp,
121				ProtocolConfig::Tcp => Transport::Tcp,
122				ProtocolConfig::Tls { .. } => Transport::Tls,
123				ProtocolConfig::Https { .. } => Transport::Https,
124				ProtocolConfig::Quic { .. } => Transport::Quic,
125				ProtocolConfig::H3 { .. } => Transport::H3,
126			};
127			reports.push(ResolverReport {
128				address: SocketAddr::new(server.ip, connection.port),
129				transport,
130				source,
131			});
132		}
133	}
134	reports
135}
136
137#[cfg(test)]
138mod tests;