Skip to main content

tuwunel_service/resolver/
actual.rs

1use std::{fmt::Debug, net::IpAddr};
2
3use futures::{FutureExt, TryFutureExt};
4use hickory_resolver::{
5	net::{DnsError, NetError},
6	proto::rr::{RData, rdata::SRV},
7};
8use ipaddress::IPAddress;
9use ruma::ServerName;
10use tuwunel_core::{
11	Err, Result, debug, debug_info, debug_warn, err, error, format_array_string, implement,
12	trace, utils::string::to_small_string,
13};
14
15use super::{
16	DestString, FedDest,
17	cache::{CachedDest, CachedOverride, MAX_IPS},
18	fed::{HostString, PortString, add_port_to_hostname, get_ip_with_port},
19};
20
21#[derive(Clone, Debug)]
22pub(crate) struct ActualDest {
23	pub(crate) dest: FedDest,
24	pub(crate) host: DestString,
25}
26
27impl ActualDest {
28	#[inline]
29	pub(crate) fn to_string(&self) -> DestString { self.dest.https_string() }
30}
31
32#[implement(super::Service)]
33#[tracing::instrument(skip_all, level = "debug", name = "resolve")]
34pub(crate) async fn get_actual_dest(&self, server_name: &ServerName) -> Result<ActualDest> {
35	let (CachedDest { dest, host, .. }, _cached) = self.lookup_actual_dest(server_name).await?;
36
37	Ok(ActualDest { dest, host })
38}
39
40#[implement(super::Service)]
41pub(crate) async fn lookup_actual_dest(
42	&self,
43	server_name: &ServerName,
44) -> Result<(CachedDest, bool)> {
45	if let Ok(result) = self.cache.get_destination(server_name).await {
46		return Ok((result, true));
47	}
48
49	let _dedup = self.resolving.lock(server_name).await;
50	if let Ok(result) = self.cache.get_destination(server_name).await {
51		return Ok((result, true));
52	}
53
54	self.resolve_actual_dest(server_name, true)
55		.inspect_ok(|result| self.cache.set_destination(server_name, result))
56		.map_ok(|result| (result, false))
57		.boxed()
58		.await
59}
60
61/// Returns: `actual_destination`, host header
62/// Implemented according to the specification at <https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names>
63/// Numbers in comments below refer to bullet points in linked section of
64/// specification
65#[implement(super::Service)]
66#[tracing::instrument(name = "actual", level = "debug", skip(self, cache))]
67pub async fn resolve_actual_dest(&self, dest: &ServerName, cache: bool) -> Result<CachedDest> {
68	self.validate_dest(dest)?;
69	let mut host: DestString = dest.as_str().into();
70	let actual_dest = self.actual_dest(dest, cache, &mut host).await?;
71	let actual_host = Self::dest_host(&host);
72
73	debug!("Actual destination: {actual_dest:?} hostname: {actual_host:?}");
74	Ok(CachedDest {
75		dest: actual_dest,
76		host: actual_host.uri_string(),
77		expire: CachedDest::default_expire(),
78	})
79}
80
81#[implement(super::Service)]
82fn dest_host(host: &DestString) -> FedDest {
83	// Preserve an unspecified port on an IP address.
84	host.parse()
85		.map(FedDest::Literal)
86		.or_else(|_| {
87			host.parse().map(|addr: IpAddr| {
88				FedDest::Named(addr.to_string().into(), FedDest::default_port())
89			})
90		})
91		.unwrap_or_else(|_| {
92			host.find(':').map_or_else(
93				|| FedDest::Named(host.as_str().into(), FedDest::default_port()),
94				|pos| {
95					let (host, port) = host.split_at(pos);
96
97					FedDest::Named(
98						host.into(),
99						port.try_into()
100							.unwrap_or_else(|_| FedDest::default_port()),
101					)
102				},
103			)
104		})
105}
106
107#[implement(super::Service)]
108async fn actual_dest(
109	&self,
110	dest: &ServerName,
111	cache: bool,
112	host: &mut DestString,
113) -> Result<FedDest> {
114	match get_ip_with_port(dest.as_str()) {
115		| Some(host_port) => Self::actual_dest_1(host_port),
116		| None if let Some(pos) = dest.as_str().find(':') =>
117			self.actual_dest_2(dest, cache, pos).await,
118		| None => {
119			self.maybe_query_and_cache(dest.as_str(), 8448, true)
120				.await?;
121			self.services.server.check_running()?;
122			match self.request_well_known(dest.as_str()).await? {
123				| Some(delegated) => self.actual_dest_3(host, cache, &delegated).await,
124				| _ => match self.query_srv_record(dest.as_str()).await? {
125					| Some(overrider) => self.actual_dest_4(host, cache, overrider).await,
126					| _ => self.actual_dest_5(dest, cache).await,
127				},
128			}
129		},
130	}
131}
132
133#[implement(super::Service)]
134fn actual_dest_1(host_port: FedDest) -> Result<FedDest> {
135	debug!("1: IP literal with provided or default port");
136	Ok(host_port)
137}
138
139#[implement(super::Service)]
140async fn actual_dest_2(&self, dest: &ServerName, cache: bool, pos: usize) -> Result<FedDest> {
141	debug!("2: Hostname with included port");
142	let (host, port) = dest.as_str().split_at(pos);
143	let port_num = port
144		.trim_start_matches(':')
145		.parse::<u16>()
146		.unwrap_or(8448);
147
148	self.maybe_query_and_cache(host, port_num, cache)
149		.await?;
150
151	let port = port
152		.try_into()
153		.unwrap_or_else(|_| FedDest::default_port());
154
155	Ok(FedDest::Named(host.into(), port))
156}
157
158#[implement(super::Service)]
159async fn actual_dest_3(
160	&self,
161	host: &mut DestString,
162	cache: bool,
163	delegated: &str,
164) -> Result<FedDest> {
165	debug!("3: A .well-known file is available");
166	*host = add_port_to_hostname(delegated).uri_string();
167	match get_ip_with_port(delegated) {
168		| Some(host_and_port) => Self::actual_dest_3_1(host_and_port),
169		| None =>
170			if let Some(pos) = delegated.find(':') {
171				self.actual_dest_3_2(cache, delegated, pos).await
172			} else {
173				trace!("Delegated hostname has no port in this branch");
174				match self.query_srv_record(delegated).await? {
175					| Some(overrider) =>
176						self.actual_dest_3_3(cache, delegated, overrider)
177							.await,
178					| _ => self.actual_dest_3_4(cache, delegated).await,
179				}
180			},
181	}
182}
183
184#[implement(super::Service)]
185fn actual_dest_3_1(host_and_port: FedDest) -> Result<FedDest> {
186	debug!("3.1: IP literal in .well-known file");
187	Ok(host_and_port)
188}
189
190#[implement(super::Service)]
191async fn actual_dest_3_2(&self, cache: bool, delegated: &str, pos: usize) -> Result<FedDest> {
192	debug!("3.2: Hostname with port in .well-known file");
193	let (host, port) = delegated.split_at(pos);
194	let port_num = port
195		.trim_start_matches(':')
196		.parse::<u16>()
197		.unwrap_or(8448);
198
199	self.maybe_query_and_cache(host, port_num, cache)
200		.await?;
201
202	let port = port
203		.try_into()
204		.unwrap_or_else(|_| FedDest::default_port());
205
206	Ok(FedDest::Named(host.into(), port))
207}
208
209#[implement(super::Service)]
210async fn actual_dest_3_3(
211	&self,
212	cache: bool,
213	delegated: &str,
214	overrider: FedDest,
215) -> Result<FedDest> {
216	debug!("3.3: SRV lookup successful");
217	let force_port = overrider.port();
218	self.maybe_query_and_cache_override(
219		delegated,
220		&overrider.hostname(),
221		force_port.unwrap_or(8448),
222		cache,
223	)
224	.await?;
225
226	if let Some(port) = force_port {
227		let port: PortString = format_array_string!(":{port}");
228
229		return Ok(FedDest::Named(delegated.into(), port));
230	}
231
232	Ok(add_port_to_hostname(delegated))
233}
234
235#[implement(super::Service)]
236async fn actual_dest_3_4(&self, cache: bool, delegated: &str) -> Result<FedDest> {
237	debug!("3.4: No SRV records, just use the hostname from .well-known");
238	self.maybe_query_and_cache(delegated, 8448, cache)
239		.await?;
240
241	Ok(add_port_to_hostname(delegated))
242}
243
244#[implement(super::Service)]
245async fn actual_dest_4(&self, host: &str, cache: bool, overrider: FedDest) -> Result<FedDest> {
246	debug!("4: No .well-known; SRV record found");
247	let force_port = overrider.port();
248	self.maybe_query_and_cache_override(
249		host,
250		&overrider.hostname(),
251		force_port.unwrap_or(8448),
252		cache,
253	)
254	.await?;
255
256	if let Some(port) = force_port {
257		let port: PortString = format_array_string!(":{port}");
258
259		return Ok(FedDest::Named(host.into(), port));
260	}
261
262	Ok(add_port_to_hostname(host))
263}
264
265#[implement(super::Service)]
266async fn actual_dest_5(&self, dest: &ServerName, cache: bool) -> Result<FedDest> {
267	debug!("5: No SRV record found");
268	self.maybe_query_and_cache(dest.as_str(), 8448, cache)
269		.await?;
270
271	Ok(add_port_to_hostname(dest.as_str()))
272}
273
274#[implement(super::Service)]
275#[inline]
276async fn maybe_query_and_cache(&self, hostname: &str, port: u16, cache: bool) -> Result {
277	self.maybe_query_and_cache_override(hostname, hostname, port, cache)
278		.await
279}
280
281#[implement(super::Service)]
282#[inline]
283async fn maybe_query_and_cache_override(
284	&self,
285	untername: &str,
286	hostname: &str,
287	port: u16,
288	cache: bool,
289) -> Result {
290	if !cache {
291		return Ok(());
292	}
293
294	if self.cache.has_override(untername).await {
295		return Ok(());
296	}
297
298	self.query_and_cache_override(untername, hostname, port)
299		.await
300}
301
302#[implement(super::Service)]
303#[tracing::instrument(name = "ip", level = "debug", skip(self))]
304async fn query_and_cache_override(
305	&self,
306	untername: &'_ str,
307	hostname: &'_ str,
308	port: u16,
309) -> Result {
310	self.services.server.check_running()?;
311
312	debug!("querying IP for {untername:?} ({hostname:?}:{port})");
313	match self
314		.resolver
315		.resolver
316		.lookup_ip(hostname.to_owned())
317		.await
318	{
319		| Err(e) => Self::handle_resolve_error(&e, hostname),
320		| Ok(override_ip) => {
321			self.cache
322				.set_override(untername, &CachedOverride {
323					ips: override_ip.iter().take(MAX_IPS).collect(),
324					port,
325					expire: CachedOverride::default_expire(),
326					overriding: (hostname != untername)
327						.then_some(hostname.into())
328						.inspect(|_| debug_info!("{untername:?} overridden by {hostname:?}")),
329				});
330
331			Ok(())
332		},
333	}
334}
335
336#[implement(super::Service)]
337#[tracing::instrument(name = "srv", level = "debug", skip(self))]
338async fn query_srv_record(&self, hostname: &'_ str) -> Result<Option<FedDest>> {
339	let hostnames =
340		[format!("_matrix-fed._tcp.{hostname}."), format!("_matrix._tcp.{hostname}.")];
341
342	for hostname in hostnames {
343		self.services.server.check_running()?;
344
345		debug!("querying SRV for {hostname:?}");
346		let hostname = hostname.trim_end_matches('.');
347		match self.resolver.resolver.srv_lookup(hostname).await {
348			| Err(e) => Self::handle_resolve_error(&e, hostname)?,
349			| Ok(result) => {
350				let srv = result
351					.answers()
352					.iter()
353					.find_map(|r| match &r.data {
354						| RData::SRV(srv) => Some(srv),
355						| _ => None,
356					});
357
358				return Ok(srv.map(Self::srv_dest));
359			},
360		}
361	}
362
363	Ok(None)
364}
365
366#[implement(super::Service)]
367fn srv_dest(srv: &SRV) -> FedDest {
368	let host: HostString = to_small_string(&srv.target);
369	let port: PortString = format_array_string!(":{}", srv.port);
370
371	FedDest::Named(host.trim_end_matches('.').into(), port)
372}
373
374#[implement(super::Service)]
375fn handle_resolve_error(e: &NetError, host: &'_ str) -> Result {
376	// `NetError::Dns(_)` covers responses returned by the remote side (NXDOMAIN,
377	// SERVFAIL, REFUSED, ...) only seen with verbose-logging. Local-origin failures
378	// (Timeout, NoConnections, Io, ...) keep their warn/error level so an operator
379	// notices when their own resolver is unhealthy.
380	match e {
381		| NetError::Dns(DnsError::NoRecordsFound(_)) => {
382			// Raise to debug_warn if we can find out the result wasn't from cache
383			debug!(%host, "No DNS records found: {e}");
384			Ok(())
385		},
386		| NetError::Dns(_) => {
387			debug_warn!(%host, "DNS response error: {e}");
388			Ok(())
389		},
390		| NetError::Timeout => Err!(warn!(%host, "DNS {e}")),
391		| NetError::NoConnections => {
392			error!(
393				"Your DNS server is overloaded and has ran out of connections. It is strongly \
394				 recommended you remediate this issue to ensure proper federation connectivity."
395			);
396
397			Err!(error!(%host, "DNS error: {e}"))
398		},
399		| _ => Err!(error!(%host, "DNS error: {e}")),
400	}
401}
402
403#[implement(super::Service)]
404fn validate_dest(&self, dest: &ServerName) -> Result {
405	if dest == self.services.server.name && !self.services.server.config.federation_loopback {
406		return Err!("Won't send federation request to ourselves");
407	}
408
409	if dest.is_ip_literal() || IPAddress::is_valid(dest.host()) {
410		self.validate_dest_ip_literal(dest)?;
411	}
412
413	Ok(())
414}
415
416#[implement(super::Service)]
417fn validate_dest_ip_literal(&self, dest: &ServerName) -> Result {
418	trace!("Destination is an IP literal, checking against IP range denylist.",);
419	debug_assert!(
420		dest.is_ip_literal() || !IPAddress::is_valid(dest.host()),
421		"Destination is not an IP literal."
422	);
423	let ip = IPAddress::parse(dest.host()).map_err(|e| {
424		err!(BadServerResponse(debug_error!("Failed to parse IP literal from string: {e}")))
425	})?;
426
427	self.validate_ip(&ip)?;
428
429	Ok(())
430}
431
432#[implement(super::Service)]
433pub(crate) fn validate_ip(&self, ip: &IPAddress) -> Result {
434	if !self.services.client.valid_cidr_range(ip) {
435		return Err!(BadServerResponse("Not allowed to send requests to this IP"));
436	}
437
438	Ok(())
439}