Skip to main content

matrix_oracle/
server.rs

1//! Resolution for the server-server API
2
3use std::net::{IpAddr, SocketAddr};
4
5use hickory_resolver::{TokioResolver, net::NetError, proto::rr::RData};
6use reqwest_middleware::ClientWithMiddleware;
7use serde::{Deserialize, Serialize};
8use tracing::{debug, info, instrument};
9
10use crate::cache;
11
12pub mod error;
13
14/// well-known information about the delegated server for server-server
15/// communication.
16///
17/// See [the specification] for more information.
18///
19/// [the specification]: https://matrix.org/docs/spec/server_server/latest#get-well-known-matrix-server
20#[derive(Debug, Clone, Deserialize, Serialize)]
21pub struct ServerWellKnown {
22	/// The server name to delegate server-server communications to, with
23	/// optional port
24	#[serde(rename = "m.server")]
25	pub server: String,
26}
27
28/// Client for server-server well-known lookups.
29#[derive(Debug, Clone)]
30pub struct Resolver {
31	/// HTTP client.
32	http: ClientWithMiddleware,
33	/// DNS resolver.
34	resolver: TokioResolver,
35}
36
37/// Resolved server name
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum Server {
40	/// IP address with implicit default port (8448)
41	Ip(IpAddr),
42	/// IP address and explicit port
43	Socket(SocketAddr),
44	/// Host string with implicit default port (8448)
45	Host(String),
46	/// Host string with explicit port.
47	HostPort(String),
48	/// Address from srv record, hostname from server name.
49	Srv(String, String),
50}
51
52impl Server {
53	/// The value to use for the `Host` HTTP header.
54	#[must_use]
55	pub fn host_header(&self) -> String {
56		match self {
57			Server::Ip(addr) => addr.to_string(),
58			Server::Socket(addr) => addr.to_string(),
59			Server::Host(host) => host.clone(),
60			Server::HostPort(host) => host.clone(),
61			Server::Srv(_, host) => host.to_string(),
62		}
63	}
64
65	/// The address to connect to.
66	#[must_use]
67	pub fn address(&self) -> String {
68		match self {
69			Server::Ip(addr) => format!("{}:8448", addr),
70			Server::Socket(addr) => addr.to_string(),
71			Server::Host(host) => format!("{}:8448", host),
72			Server::HostPort(host) => host.clone(),
73			Server::Srv(host, _) => host.clone(),
74		}
75	}
76}
77
78impl Resolver {
79	/// Constructs a new client.
80	pub fn new() -> Result<Self, NetError> {
81		Ok(Self {
82			http: reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
83				.with(cache())
84				.build(),
85			resolver: TokioResolver::builder_tokio()?.build()?,
86		})
87	}
88
89	/// Constructs a new client with the given HTTP client and DNS resolver
90	/// instances.
91	#[must_use]
92	pub fn with(http: reqwest::Client, resolver: TokioResolver) -> Self {
93		Self { http: reqwest_middleware::ClientBuilder::new(http).with(cache()).build(), resolver }
94	}
95
96	/// Resolve the given server name
97	#[instrument(skip(self, port), err)]
98	pub async fn resolve(
99		&self,
100		name: &str,
101		#[cfg(test)] port: Option<u16>,
102	) -> error::Result<Server> {
103		// 1. The host is an ip literal
104		debug!("Parsing socket literal");
105		if let Ok(addr) = name.parse::<SocketAddr>() {
106			info!("The server name is a socket literal");
107			return Ok(Server::Socket(addr));
108		}
109		debug!("Parsing IP literal");
110		if let Ok(addr) = name.parse::<IpAddr>() {
111			info!("The server name is an IP literal");
112			return Ok(Server::Ip(addr));
113		}
114		// 2. The host is not an ip literal, but includes a port
115		debug!("Parsing host with port");
116		if split_port(name).is_some() {
117			info!("The servername is a host with port");
118			return Ok(Server::HostPort(name.to_owned()));
119		}
120		// 3. Query the .well-known endpoint
121		debug!("Querying well known");
122		if let Some(well_known) = self
123			.well_known(
124				name,
125				#[cfg(test)]
126				port,
127			)
128			.await?
129		{
130			debug!("Well-known received: {:?}", &well_known);
131			// 3.1 delegated_hostname is an ip literal
132			debug!("Parsing delegated socket literal");
133			if let Ok(addr) = well_known.server.parse::<SocketAddr>() {
134				info!("The server name is a delegated IP literal");
135				return Ok(Server::Socket(addr));
136			}
137			debug!("Parsing delegated IP literal");
138			if let Ok(addr) = well_known.server.parse::<IpAddr>() {
139				info!("The server name is a delegated socket literal");
140				return Ok(Server::Ip(addr));
141			}
142			// 3.2 delegated_hostname includes a port
143			debug!("Parsing delegated hostname with port");
144			if split_port(&well_known.server).is_some() {
145				info!("The server name is a delegated hostname with port");
146				return Ok(Server::HostPort(well_known.server));
147			}
148			// 3.3 Look up SRV record (fed record, then deprecated)
149			debug!("Looking up SRV record for delegated hostname");
150			if let Some(name) = self.srv_lookup_with_fallback(&well_known.server).await {
151				info!("The server name is a delegated SRV record");
152				return Ok(Server::Srv(name, well_known.server));
153			}
154			// 3.4 Use hostname in .well-known
155			debug!("Using delegated hostname directly");
156			return Ok(Server::Host(well_known.server));
157		}
158		// 4. The .well-known lookup failed, query SRV (fed record, then deprecated)
159		debug!("Looking up SRV record for hostname");
160		if let Some(srv) = self.srv_lookup_with_fallback(name).await {
161			info!("The server name is an SRV record");
162			return Ok(Server::Srv(srv, name.to_owned()));
163		}
164		// 5. No SRV record found, use hostname
165		debug!("Using provided hostname directly");
166		Ok(Server::Host(name.to_owned()))
167	}
168
169	/// Query the .well-known information for a host.
170	#[cfg_attr(test, allow(unused_variables))]
171	#[instrument(skip(self, name, port), err)]
172	async fn well_known(
173		&self,
174		name: &str,
175		#[cfg(test)] port: Option<u16>,
176	) -> error::Result<Option<ServerWellKnown>> {
177		#[cfg(not(test))]
178		let response = self.http.get(format!("https://{}/.well-known/matrix/server", name)).send().await;
179
180		#[cfg(test)]
181		#[allow(clippy::expect_used)]
182		let response = self
183			.http
184			.get(format!(
185				"http://{name}:{port}/.well-known/matrix/server",
186				port = port.expect("port needed for test env")
187			))
188			.send()
189			.await;
190
191		// Only return Err on connection failure, skip to next step for other errors.
192		let response = match response {
193			Ok(response) => response,
194			Err(reqwest_middleware::Error::Reqwest(e)) if e.is_connect() => return Err(e.into()),
195			Err(_) => return Ok(None),
196		};
197		let well_known = response.json::<ServerWellKnown>().await.ok();
198		Ok(well_known)
199	}
200
201	/// Query a single SRV record set and return `host:port` of the
202	/// lowest-priority record, if any.
203	#[instrument(skip(self))]
204	async fn srv_lookup(&self, query: &str) -> Option<String> {
205		let srv = self.resolver.srv_lookup(query).await.ok()?;
206		// Get a record with the lowest priority value
207		match srv
208			.answers()
209			.iter()
210			.filter_map(|record| match &record.data {
211				RData::SRV(srv) => Some(srv),
212				_ => None,
213			})
214			.min_by_key(|srv| srv.priority)
215		{
216			Some(srv) => {
217				let target = srv.target.to_ascii();
218				let host = target.trim_end_matches('.');
219				Some(format!("{}:{}", host, srv.port))
220			}
221			None => None,
222		}
223	}
224
225	/// Look up the federation SRV record, falling back to the deprecated
226	/// `_matrix._tcp` record per the server-server resolution spec.
227	#[instrument(skip(self))]
228	async fn srv_lookup_with_fallback(&self, name: &str) -> Option<String> {
229		if let Some(found) = self.srv_lookup(&fed_srv_name(name)).await {
230			debug!("Found _matrix-fed._tcp SRV record");
231			return Some(found);
232		}
233		debug!("No _matrix-fed._tcp record; trying deprecated _matrix._tcp");
234		self.srv_lookup(&deprecated_srv_name(name)).await
235	}
236
237	/// Get the [`SocketAddr`] of an address
238	pub async fn socket(&self, server: &Server) -> Result<SocketAddr, NetError> {
239		let (host, port) = match *server {
240			Server::Ip(ip) => return Ok(SocketAddr::new(ip, 8448)),
241			Server::Socket(socket) => return Ok(socket),
242			Server::Host(ref host) => (host.as_str(), 8448),
243			#[allow(clippy::expect_used)]
244			Server::HostPort(ref host) => split_port(host).expect("HostPort was constructed with port"),
245			#[allow(clippy::expect_used)]
246			Server::Srv(ref addr, _) => split_port(addr).expect("The SRV record includes the port"),
247		};
248		let record = self.resolver.lookup_ip(host).await?;
249		// We naively get the first IP.
250		let socket =
251			SocketAddr::new(record.iter().next().ok_or(NetError::Message("No records"))?, port);
252		Ok(socket)
253	}
254}
255
256/// The current (spec v1.8+) federation SRV record name for a server name.
257fn fed_srv_name(name: &str) -> String {
258	format!("_matrix-fed._tcp.{name}")
259}
260
261/// The deprecated SRV record name, kept as a fallback per the spec.
262fn deprecated_srv_name(name: &str) -> String {
263	format!("_matrix._tcp.{name}")
264}
265
266/// Get the port at the end of a host string if there is one.
267fn split_port(host: &str) -> Option<(&str, u16)> {
268	match &host.split(':').collect::<Vec<_>>()[..] {
269		[host, port] => match port.parse() {
270			Ok(port) => Some((host, port)),
271			Err(_) => None,
272		},
273		_ => None,
274	}
275}
276
277#[cfg(test)]
278mod tests {
279	use std::net::{IpAddr, SocketAddr};
280
281	use hickory_resolver::TokioResolver;
282	use proptest::prelude::*;
283	use wiremock::{
284		Mock, MockServer, ResponseTemplate,
285		matchers::{method, path},
286	};
287
288	use super::{Resolver, Server};
289
290	/// Validates the SRV query-name construction for the fed record and the
291	/// deprecated fallback record.
292	#[test]
293	fn srv_query_names() {
294		assert_eq!(super::fed_srv_name("example.test"), "_matrix-fed._tcp.example.test");
295		assert_eq!(super::deprecated_srv_name("example.test"), "_matrix._tcp.example.test");
296	}
297
298	/// Validates correct parsing of IP literals and server name with port
299	#[tokio::test]
300	async fn literals() -> Result<(), Box<dyn std::error::Error>> {
301		let resolver = Resolver::new()?;
302		assert_eq!(
303			resolver.resolve("127.0.0.1", None).await?,
304			Server::Ip(IpAddr::from([127, 0, 0, 1])),
305			"1. IP literal"
306		);
307		assert_eq!(
308			resolver.resolve("127.0.0.1:4884", None).await?,
309			Server::Socket(SocketAddr::new(IpAddr::from([127, 0, 0, 1]), 4884)),
310			"1. Socket literal"
311		);
312		assert_eq!(
313			resolver.resolve("example.test:1234", None).await?,
314			Server::HostPort(String::from("example.test:1234")),
315			"2. Host with port"
316		);
317		Ok(())
318	}
319
320	/// Validates correct handing of the .well-known http endpoint.
321	#[tokio::test]
322	async fn http() -> Result<(), Box<dyn std::error::Error>> {
323		let mock_server = MockServer::start().await;
324
325		let client = reqwest::Client::builder()
326			.resolve("example.test", *mock_server.address())
327			.resolve("destination.test", *mock_server.address())
328			.build()?;
329		let resolver = Resolver::with(client, TokioResolver::builder_tokio()?.build()?);
330
331		let addr = mock_server.address();
332
333		Mock::given(method("GET"))
334			.and(path("/.well-known/matrix/server"))
335			.respond_with(
336				ResponseTemplate::new(200).set_body_raw(
337					format!(r#"{{"m.server": "{}"}}"#, addr.ip()),
338					"application/json",
339				),
340			)
341			.up_to_n_times(1)
342			.expect(1)
343			.mount(&mock_server)
344			.await;
345
346		assert_eq!(
347			resolver.resolve("example.test", Some(addr.port())).await?,
348			Server::Ip(addr.ip()),
349			"3.1 delegated_hostname is an IP literal"
350		);
351
352		Mock::given(method("GET"))
353			.and(path("/.well-known/matrix/server"))
354			.respond_with(
355				ResponseTemplate::new(200)
356					.set_body_raw(format!(r#"{{"m.server": "{}"}}"#, addr), "application/json"),
357			)
358			.up_to_n_times(1)
359			.expect(1)
360			.mount(&mock_server)
361			.await;
362
363		assert_eq!(
364			resolver.resolve("example.test", Some(addr.port())).await?,
365			Server::Socket(*mock_server.address()),
366			"3.1 delegated_hostname is a socket literal"
367		);
368
369		Mock::given(method("GET"))
370			.and(path("/.well-known/matrix/server"))
371			.respond_with(ResponseTemplate::new(200).set_body_raw(
372				format!(r#"{{"m.server": "destination.test:{}"}}"#, addr.port()),
373				"application/json",
374			))
375			.expect(1)
376			.up_to_n_times(1)
377			.mount(&mock_server)
378			.await;
379
380		assert_eq!(
381			resolver.resolve("example.test", Some(addr.port())).await?,
382			Server::HostPort(format!("destination.test:{}", addr.port())),
383			"3.2 delegated_hostname includes a port"
384		);
385		Ok(())
386	}
387
388	proptest! {
389		/// `split_port` round-trips a `host:port` string for any valid u16 port
390		/// and non-colon host.
391		#[test]
392		fn split_port_roundtrip(host in "[a-z][a-z0-9-]{0,20}", port in any::<u16>()) {
393			let input = format!("{host}:{port}");
394			prop_assert_eq!(super::split_port(&input), Some((host.as_str(), port)));
395		}
396
397		/// A host with no colon never parses as host:port.
398		#[test]
399		fn split_port_rejects_bare_host(host in "[a-z][a-z0-9-]{0,20}") {
400			prop_assert_eq!(super::split_port(&host), None);
401		}
402
403		/// SRV query builders always produce the spec-prefixed names.
404		#[test]
405		fn srv_names_prefixed(name in "[a-z][a-z0-9.-]{0,40}") {
406			prop_assert!(super::fed_srv_name(&name).starts_with("_matrix-fed._tcp."));
407			prop_assert!(super::deprecated_srv_name(&name).starts_with("_matrix._tcp."));
408			prop_assert!(super::fed_srv_name(&name).ends_with(&name));
409		}
410
411		/// `Host` always derives the default federation port 8448 in `address`,
412		/// and the bare host in `host_header`.
413		#[test]
414		fn host_derivations(host in "[a-z][a-z0-9.-]{0,40}") {
415			let server = Server::Host(host.clone());
416			prop_assert_eq!(server.address(), format!("{host}:8448"));
417			prop_assert_eq!(server.host_header(), host);
418		}
419	}
420}