Commit Graph
7195 Commits
Author SHA1 Message Date
theS1LV3R 03e312f2b4 chore: Formatting + Commenting
diff --git c/src/service/resolver/actual.rs i/src/service/resolver/actual.rs
index 9cd1aec15..495d83172 100644
--- c/src/service/resolver/actual.rs
+++ i/src/service/resolver/actual.rs
@@ -1,9 +1,5 @@
 use std::fmt::Debug;

-use super::{
-	cache::{CachedDest, CachedOverride, MAX_IPS},
-	fed::{FedDest, PortString, add_port_to_hostname, ensure_host_has_port, get_ip_with_port},
-};
 use conduwuit::{Err, Result, debug, debug_info, err, error, trace};
 use futures::{FutureExt, TryFutureExt};
 use hickory_resolver::{
@@ -15,9 +11,11 @@

 use super::{
 	cache::{CachedDest, CachedOverride, MAX_IPS},
-	fed::{FedDest, PortString, add_port_to_hostname, get_ip_with_port},
+	fed::{FedDest, PortString, add_port_to_hostname, ensure_host_has_port, get_ip_with_port},
 };

+const DEFAULT_PORT: u16 = 8448;
+
 #[derive(Clone, Debug)]
 pub(crate) struct ActualDest {
 	pub(crate) dest: FedDest,
@@ -26,9 +24,7 @@ pub(crate) struct ActualDest {

 impl ActualDest {
 	#[inline]
-	pub(crate) fn string(&self) -> String {
-		self.dest.https_string()
-	}
+	pub(crate) fn string(&self) -> String { self.dest.https_string() }
 }

 impl super::Service {
@@ -75,7 +71,8 @@ pub async fn resolve_actual_dest(
 		// Ensure dest is a valid connection endpoint
 		self.validate_dest(dest)?;

-		// Clippy believes this can be a clone, however we are actually converting ServerName to String
+		// Clippy believes this can be a clone, however we are actually converting
+		// ServerName to String
 		#[allow(clippy::implicit_clone)]
 		let mut host = dest.to_string().to_owned();
 		let actual_dest = self.resolve_server_name(dest, cache, &mut host).await?;
@@ -103,8 +100,8 @@ async fn resolve_server_name(
 		cache: bool,
 		host: &mut String,
 	) -> Result<FedDest> {
-		// 1. If `dest` is an IP, use it directly. If a port is provided as well (IP:port socket pair)
-		//    use that, otherwise default to port 8448
+		// 1. If `dest` is an IP, use it directly. If a port is provided as well
+		//    (IP:port socket pair) use that, otherwise default to port 8448
 		if let Some(fed_dest) = get_ip_with_port(dest.as_str()) {
 			debug!("1: IP literal with provided or default port");
 			return Ok(fed_dest);
@@ -117,8 +114,8 @@ async fn resolve_server_name(
 				.await?;
 		}

-		// Pre-resolve IP? Unsure what overrides exactly do, system is due to be removed either way
-		// https://matrix.to/#/!da26JtAjE6APGLnX8ncWsvc-skF2KQZ9Nw_MbNpYD2k/%24_hq6JP0JXANbMTMPdV64iZbgbsZdhy92M5ndDYGy6No
+		// Pre-resolve IP? Unsure what overrides exactly do, system is due to be removed
+		// either way https://matrix.to/#/!da26JtAjE6APGLnX8ncWsvc-skF2KQZ9Nw_MbNpYD2k/%24_hq6JP0JXANbMTMPdV64iZbgbsZdhy92M5ndDYGy6No
 		self.conditional_query_and_cache(dest.as_str(), DEFAULT_PORT, true)
 			.await?;

@@ -126,7 +123,8 @@ async fn resolve_server_name(
 		self.services.server.check_running()?;

 		// 3. If `dest` is a hostname with no port, send GET to `https://<dest>/.well-known/matrix/server`.
-		// If invalid JSON (throws error), skip to step 4. Otherwise, parse `delegated` as `<hostname>[:<port>]` and...
+		// If invalid JSON (throws error), skip to step 4. Otherwise, parse `delegated`
+		// as `<hostname>[:<port>]` and...
 		if let Some(delegated) = self.request_well_known(dest.as_str()).await? {
 			// delegated=matrix-federation.matrix.org:443 // host=matrix.org
 			self.resolve_3_well_known(host, cache, delegated).await?;
@@ -137,11 +135,13 @@ async fn resolve_server_name(
 			self.resolve_4_srv_lookup(host, cache, overrider).await?;
 		}

-		// 5. if .well-known errored and no SRV exists, resolve IP and connect on default port (8448)
+		// 5. if .well-known errored and no SRV exists, resolve IP and connect on
+		//    default port (8448)
 		self.resolve_5_direct(dest, cache).await
 	}

-	/// Parse a host:port socket pair into separate parts, and resolve the hostname into an IP address
+	/// Parse a host:port socket pair into separate parts, and resolve the
+	/// hostname into an IP address
 	async fn resolve_2_host_port(
 		&self,
 		dest: &ServerName,
@@ -180,14 +180,16 @@ async fn resolve_3_well_known(
 			return Ok(host_and_port);
 		}

-		// 3.2 - If <delegated> is not an IP and a port is present, lookup IP for hostname and connect
+		// 3.2 - If <delegated> is not an IP and a port is present, lookup IP for
+		// hostname and connect
 		if let Some(pos) = &delegated.find(':') {
 			self.resolve_3_2_hostname_port(cache, &delegated, *pos)
 				.await?;
 		}

-		// 3.3 - If <delegated> is not an IP and there is no port, lookup SRV `_matrix._tcp.<delegated>`
-		// (which may provide a new hostname + port to use, see steps 3.1 and 3.2)
+		// 3.3 - If <delegated> is not an IP and there is no port, lookup SRV
+		// `_matrix._tcp.<delegated>` (which may provide a new hostname + port to use,
+		// see steps 3.1 and 3.2)
 		trace!("Delegated hostname has no port, querying SRV");
 		if let Some(overrider) = self.query_srv_record(&delegated).await? {
 			self.resolve_3_3_use_srv(cache, &delegated, overrider)
@@ -342,17 +344,14 @@ async fn query_and_cache_override(
 		match self.resolver.resolver.lookup_ip(hostname.to_owned()).await {
 			| Err(e) => Self::handle_resolve_error(&e, hostname),
 			| Ok(override_ip) => {
-				self.cache.set_override(
-					untername,
-					&CachedOverride {
-						ips: override_ip.iter().take(MAX_IPS).collect(),
-						port,
-						expire: CachedOverride::default_expire(),
-						overriding: (hostname != untername)
-							.then_some(hostname.into())
-							.inspect(|_| debug_info!("{untername:?} overridden by {hostname:?}")),
-					},
-				);
+				self.cache.set_override(untername, &CachedOverride {
+					ips: override_ip.into_iter().take(MAX_IPS).collect(),
+					port,
+					expire: CachedOverride::default_expire(),
+					overriding: (hostname != untername)
+						.then_some(hostname.into())
+						.inspect(|_| debug_info!("{untername:?} overridden by {hostname:?}")),
+				});

 				Ok(())
 			},
@@ -415,7 +414,8 @@ fn handle_resolve_error(err: &NetError, host: &'_ str) -> Result<()> {
 		}
 	}

-	/// Ensure `dest` is a valid destination (valid ip if it is an IP), and not ourselves (unless in config)
+	/// Ensure `dest` is a valid destination (valid ip if it is an IP), and not
+	/// ourselves (unless in config)
 	fn validate_dest(&self, dest: &ServerName) -> Result<()> {
 		if dest == self.services.server.name && !self.services.server.config.federation_loopback {
 			return Err!("Won't send federation request to ourselves");
diff --git c/src/service/resolver/fed.rs i/src/service/resolver/fed.rs
index b43f62eed..83601a98a 100644
--- c/src/service/resolver/fed.rs
+++ i/src/service/resolver/fed.rs
@@ -9,8 +9,8 @@

 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub enum FedDest {
-	Literal(SocketAddr),
-	Named(String, PortString),
+	Literal(SocketAddr),       // "ip:port"
+	Named(String, PortString), // ("hostname", ":port")
 }

 /// numeric or service-name
@@ -18,6 +18,9 @@ pub enum FedDest {

 const DEFAULT_PORT: &str = ":8448";

+/// Attempt to parse `dest_str` as either an IP:port socket pair or as a plain
+/// IP (adding the default port), returning `None` if dest_str is neither a
+/// socket pair nor a plain IP.
 pub(crate) fn get_ip_with_port(dest_str: &str) -> Option<FedDest> {
 	if let Ok(dest) = dest_str.parse::<SocketAddr>() {
 		Some(FedDest::Literal(dest))
@@ -28,6 +31,8 @@ pub(crate) fn get_ip_with_port(dest_str: &str) -> Option<FedDest> {
 	}
 }

+/// Convert a `dest` string with or without port into a FedDest with either
+/// the provided port (if host:port format) or the default port (8448)
 pub(crate) fn add_port_to_hostname(dest: &str) -> FedDest {
 	let (host, port) = match dest.find(':') {
 		| None => (dest, DEFAULT_PORT),
@@ -42,8 +47,8 @@ pub(crate) fn add_port_to_hostname(dest: &str) -> FedDest {

 /// Ensure `host` always has a port
 ///
-/// `get_ip_with_port` returns `None` if `host` isn't an IP:port string or plain IP,
-/// in which case `add_port_to_hostname` adds it instead
+/// `get_ip_with_port` returns `None` if `host` isn't an IP:port string or plain
+/// IP, in which case `add_port_to_hostname` adds it instead
 #[inline]
 pub(crate) fn ensure_host_has_port(host: &str) -> FedDest {
 	get_ip_with_port(host).unwrap_or_else(|| add_port_to_hostname(host))
2026-07-03 10:59:10 +02:00
theS1LV3R cb031aef84 refactor: Rewrite resolver service
No functional changes, only visual and slight logic updates.
All inputs should return the same outputs.

# Conflicts:
#	src/service/resolver/actual.rs

diff --git c/src/service/resolver/actual.rs i/src/service/resolver/actual.rs
index 7eaeb96ab..9cd1aec15 100644
--- c/src/service/resolver/actual.rs
+++ i/src/service/resolver/actual.rs
@@ -1,8 +1,9 @@
-use std::{
-	fmt::Debug,
-	net::{IpAddr, SocketAddr},
-};
+use std::fmt::Debug;

+use super::{
+	cache::{CachedDest, CachedOverride, MAX_IPS},
+	fed::{FedDest, PortString, add_port_to_hostname, ensure_host_has_port, get_ip_with_port},
+};
 use conduwuit::{Err, Result, debug, debug_info, err, error, trace};
 use futures::{FutureExt, TryFutureExt};
 use hickory_resolver::{
@@ -25,7 +26,9 @@ pub(crate) struct ActualDest {

 impl ActualDest {
 	#[inline]
-	pub(crate) fn string(&self) -> String { self.dest.https_string() }
+	pub(crate) fn string(&self) -> String {
+		self.dest.https_string()
+	}
 }

 impl super::Service {
@@ -57,71 +60,103 @@ pub(crate) async fn lookup_actual_dest(
 			.await
 	}

-	/// Returns: `actual_destination`, host header
-	/// Implemented according to the specification at <https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names>
-	/// Numbers in comments below refer to bullet points in linked section of
-	/// specification
+	/// Returns: `actual_destination` + `host` variable used for logging
 	#[tracing::instrument(name = "actual", level = "debug", skip(self, cache))]
 	pub async fn resolve_actual_dest(
 		&self,
 		dest: &ServerName,
 		cache: bool,
 	) -> Result<CachedDest> {
+		debug!(
+			dest = %dest,
+			cache = %cache,
+			"Resolving server name and port"
+		);
+		// Ensure dest is a valid connection endpoint
 		self.validate_dest(dest)?;
-		let mut host = dest.as_str().to_owned();
-		let actual_dest = match get_ip_with_port(dest.as_str()) {
-			| Some(host_port) => Self::actual_dest_1(host_port)?,
-			| None =>
-				if let Some(pos) = dest.as_str().find(':') {
-					self.actual_dest_2(dest, cache, pos).await?
-				} else {
-					self.services.server.check_running()?;
-					match self.request_well_known(dest.as_str()).await? {
-						| Some(delegated) =>
-							self.actual_dest_3(&mut host, cache, delegated).await?,
-						| _ => match self.query_srv_record(dest.as_str()).await? {
-							| Some(overrider) =>
-								self.actual_dest_4(&host, cache, overrider).await?,
-							| _ => self.actual_dest_5(dest, cache).await?,
-						},
-					}
-				},
-		};

-		// Can't use get_ip_with_port here because we don't want to add a port
-		// to an IP address if it wasn't specified
-		let host = if let Ok(addr) = host.parse::<SocketAddr>() {
-			FedDest::Literal(addr)
-		} else if let Ok(addr) = host.parse::<IpAddr>() {
-			FedDest::Named(addr.to_string(), FedDest::default_port())
-		} else if let Some(pos) = host.find(':') {
-			let (host, port) = host.split_at(pos);
-			FedDest::Named(
-				host.to_owned(),
-				port.try_into().unwrap_or_else(|_| FedDest::default_port()),
-			)
-		} else {
-			FedDest::Named(host, FedDest::default_port())
-		};
+		// Clippy believes this can be a clone, however we are actually converting ServerName to String
+		#[allow(clippy::implicit_clone)]
+		let mut host = dest.to_string().to_owned();
+		let actual_dest = self.resolve_server_name(dest, cache, &mut host).await?;

-		debug!("Actual destination: {actual_dest:?} hostname: {host:?}");
+		host = ensure_host_has_port(&host).to_string();
+
+		debug!(
+			dest = %dest, // matrix.org
+			actual_dest = %actual_dest, // FedDest::Named(server.matrix.org, 443)
+			host = %host, // matrix.org
+			"Finished resolving server name"
+		);
 		Ok(CachedDest {
 			dest: actual_dest,
-			host: host.uri_string(),
+			host,
 			expire: CachedDest::default_expire(),
 		})
 	}

-	fn actual_dest_1(host_port: FedDest) -> Result<FedDest> {
-		debug!("1: IP literal with provided or default port");
-		Ok(host_port)
+	/// Performs the server resolution steps as per the specification:
+	/// <https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names>
+	async fn resolve_server_name(
+		&self,
+		dest: &ServerName,
+		cache: bool,
+		host: &mut String,
+	) -> Result<FedDest> {
+		// 1. If `dest` is an IP, use it directly. If a port is provided as well (IP:port socket pair)
+		//    use that, otherwise default to port 8448
+		if let Some(fed_dest) = get_ip_with_port(dest.as_str()) {
+			debug!("1: IP literal with provided or default port");
+			return Ok(fed_dest);
+		}
+
+		// 2. If `dest` is a hostname and has a provided port (format of `host:port`),
+		//    resolve the hostname to an IP address and connect it and the provided port
+		if let Some(colon_position) = dest.as_str().find(':') {
+			self.resolve_2_host_port(dest, cache, colon_position)
+				.await?;
+		}
+
+		// Pre-resolve IP? Unsure what overrides exactly do, system is due to be removed either way
+		// https://matrix.to/#/!da26JtAjE6APGLnX8ncWsvc-skF2KQZ9Nw_MbNpYD2k/%24_hq6JP0JXANbMTMPdV64iZbgbsZdhy92M5ndDYGy6No
+		self.conditional_query_and_cache(dest.as_str(), DEFAULT_PORT, true)
+			.await?;
+
+		// Ensure server is running (not shutting down) before continuing resolution
+		self.services.server.check_running()?;
+
+		// 3. If `dest` is a hostname with no port, send GET to `https://<dest>/.well-known/matrix/server`.
+		// If invalid JSON (throws error), skip to step 4. Otherwise, parse `delegated` as `<hostname>[:<port>]` and...
+		if let Some(delegated) = self.request_well_known(dest.as_str()).await? {
+			// delegated=matrix-federation.matrix.org:443 // host=matrix.org
+			self.resolve_3_well_known(host, cache, delegated).await?;
+		}
+
+		// 4. if .well-known errored, perform SRV (see 3.3)
+		if let Some(overrider) = self.query_srv_record(dest.as_str()).await? {
+			self.resolve_4_srv_lookup(host, cache, overrider).await?;
+		}
+
+		// 5. if .well-known errored and no SRV exists, resolve IP and connect on default port (8448)
+		self.resolve_5_direct(dest, cache).await
 	}

-	async fn actual_dest_2(&self, dest: &ServerName, cache: bool, pos: usize) -> Result<FedDest> {
+	/// Parse a host:port socket pair into separate parts, and resolve the hostname into an IP address
+	async fn resolve_2_host_port(
+		&self,
+		dest: &ServerName,
+		cache: bool,
+		pos: usize,
+	) -> Result<FedDest> {
 		debug!("2: Hostname with included port");
 		let (host, port) = dest.as_str().split_at(pos);
-		self.conditional_query_and_cache(host, port.parse::<u16>().unwrap_or(8448), cache)
-			.await?;
+
+		self.conditional_query_and_cache(
+			host,
+			port.parse::<u16>().unwrap_or(DEFAULT_PORT),
+			cache,
+		)
+		.await?;

 		Ok(FedDest::Named(
 			host.to_owned(),
@@ -129,7 +164,7 @@ async fn actual_dest_2(&self, dest: &ServerName, cache: bool, pos: usize) -> Res
 		))
 	}

-	async fn actual_dest_3(
+	async fn resolve_3_well_known(
 		&self,
 		host: &mut String,
 		cache: bool,
@@ -137,63 +172,75 @@ async fn actual_dest_3(
 	) -> Result<FedDest> {
 		debug!("3: A .well-known file is available");
 		*host = add_port_to_hostname(&delegated).uri_string();
-		match get_ip_with_port(&delegated) {
-			| Some(host_and_port) => Self::actual_dest_3_1(host_and_port),
-			| None =>
-				if let Some(pos) = delegated.find(':') {
-					self.actual_dest_3_2(cache, delegated, pos).await
-				} else {
-					trace!("Delegated hostname has no port in this branch");
-					match self.query_srv_record(&delegated).await? {
-						| Some(overrider) =>
-							self.actual_dest_3_3(cache, delegated, overrider).await,
-						| _ => self.actual_dest_3_4(cache, delegated).await,
-					}
-				},
+
+		// 3.1 - If <delegated> is of IP:port format, connect to that,
+		//       or IP with default port if no port provided (8448)
+		if let Some(host_and_port) = get_ip_with_port(&delegated) {
+			debug!("3.1: IP with port in .well-known file");
+			return Ok(host_and_port);
 		}
+
+		// 3.2 - If <delegated> is not an IP and a port is present, lookup IP for hostname and connect
+		if let Some(pos) = &delegated.find(':') {
+			self.resolve_3_2_hostname_port(cache, &delegated, *pos)
+				.await?;
+		}
+
+		// 3.3 - If <delegated> is not an IP and there is no port, lookup SRV `_matrix._tcp.<delegated>`
+		// (which may provide a new hostname + port to use, see steps 3.1 and 3.2)
+		trace!("Delegated hostname has no port, querying SRV");
+		if let Some(overrider) = self.query_srv_record(&delegated).await? {
+			self.resolve_3_3_use_srv(cache, &delegated, overrider)
+				.await?;
+		}
+
+		self.resolve_3_4_use_default_port(cache, delegated).await
 	}

-	fn actual_dest_3_1(host_and_port: FedDest) -> Result<FedDest> {
-		debug!("3.1: IP literal in .well-known file");
-		Ok(host_and_port)
-	}
-
-	async fn actual_dest_3_2(
+	async fn resolve_3_2_hostname_port(
 		&self,
 		cache: bool,
-		delegated: String,
+		delegated: &str,
 		pos: usize,
 	) -> Result<FedDest> {
 		debug!("3.2: Hostname with port in .well-known file");
-		let (host, port) = delegated.split_at(pos);
-		self.conditional_query_and_cache(host, port.parse::<u16>().unwrap_or(8448), cache)
-			.await?;
+		let (host, port) = &delegated.split_at(pos);
+		self.conditional_query_and_cache(
+			host,
+			port.parse::<u16>().unwrap_or(DEFAULT_PORT),
+			cache,
+		)
+		.await?;

+		trace!("Successfully resolved IP for {delegated}");
 		Ok(FedDest::Named(
-			host.to_owned(),
-			port.try_into().unwrap_or_else(|_| FedDest::default_port()),
+			host.to_owned().to_owned(),
+			port.to_owned()
+				.try_into()
+				.unwrap_or_else(|_| FedDest::default_port()),
 		))
 	}

-	async fn actual_dest_3_3(
+	async fn resolve_3_3_use_srv(
 		&self,
 		cache: bool,
-		delegated: String,
+		delegated: &String,
 		overrider: FedDest,
 	) -> Result<FedDest> {
 		debug!("3.3: SRV lookup successful");
+
 		let force_port = overrider.port();
 		self.conditional_query_and_cache_override(
-			&delegated,
+			delegated,
 			&overrider.hostname(),
-			force_port.unwrap_or(8448),
+			force_port.unwrap_or(DEFAULT_PORT),
 			cache,
 		)
 		.await?;

 		if let Some(port) = force_port {
 			return Ok(FedDest::Named(
-				delegated,
+				delegated.to_owned(),
 				format!(":{port}")
 					.as_str()
 					.try_into()
@@ -201,17 +248,21 @@ async fn actual_dest_3_3(
 			));
 		}

-		Ok(add_port_to_hostname(&delegated))
+		Ok(add_port_to_hostname(delegated))
 	}

-	async fn actual_dest_3_4(&self, cache: bool, delegated: String) -> Result<FedDest> {
-		debug!("3.4: No SRV records, just use the hostname from .well-known");
-		self.conditional_query_and_cache(&delegated, 8448, cache)
+	async fn resolve_3_4_use_default_port(
+		&self,
+		cache: bool,
+		delegated: String,
+	) -> Result<FedDest> {
+		debug!("3.4: No SRV records found, use the hostname from .well-known with default port");
+		self.conditional_query_and_cache(&delegated, DEFAULT_PORT, cache)
 			.await?;
 		Ok(add_port_to_hostname(&delegated))
 	}

-	async fn actual_dest_4(
+	async fn resolve_4_srv_lookup(
 		&self,
 		host: &str,
 		cache: bool,
@@ -222,7 +273,7 @@ async fn actual_dest_4(
 		self.conditional_query_and_cache_override(
 			host,
 			&overrider.hostname(),
-			force_port.unwrap_or(8448),
+			force_port.unwrap_or(DEFAULT_PORT),
 			cache,
 		)
 		.await?;
@@ -239,9 +290,9 @@ async fn actual_dest_4(
 		Ok(add_port_to_hostname(host))
 	}

-	async fn actual_dest_5(&self, dest: &ServerName, cache: bool) -> Result<FedDest> {
-		debug!("5: No SRV record found");
-		self.conditional_query_and_cache(dest.as_str(), 8448, cache)
+	async fn resolve_5_direct(&self, dest: &ServerName, cache: bool) -> Result<FedDest> {
+		debug!("5: No port provided and no SRV record found");
+		self.conditional_query_and_cache(dest.as_str(), DEFAULT_PORT, cache)
 			.await?;

 		Ok(add_port_to_hostname(dest.as_str()))
@@ -261,9 +312,9 @@ async fn conditional_query_and_cache(
 	#[inline]
 	async fn conditional_query_and_cache_override(
 		&self,
-		untername: &str,
-		hostname: &str,
-		port: u16,
+		untername: &str, // matrix.org
+		hostname: &str,  // server.matrix.org
+		port: u16,       // 443
 		cache: bool,
 	) -> Result {
 		if !cache {
@@ -281,9 +332,9 @@ async fn conditional_query_and_cache_override(
 	#[tracing::instrument(name = "ip", level = "debug", skip(self))]
 	async fn query_and_cache_override(
 		&self,
-		untername: &'_ str,
-		hostname: &'_ str,
-		port: u16,
+		untername: &'_ str, // matrix.org
+		hostname: &'_ str,  // server.matrix.org
+		port: u16,          // 443
 	) -> Result {
 		self.services.server.check_running()?;

@@ -291,14 +342,17 @@ async fn query_and_cache_override(
 		match self.resolver.resolver.lookup_ip(hostname.to_owned()).await {
 			| Err(e) => Self::handle_resolve_error(&e, hostname),
 			| Ok(override_ip) => {
-				self.cache.set_override(untername, &CachedOverride {
-					ips: override_ip.iter().take(MAX_IPS).collect(),
-					port,
-					expire: CachedOverride::default_expire(),
-					overriding: (hostname != untername)
-						.then_some(hostname.into())
-						.inspect(|_| debug_info!("{untername:?} overridden by {hostname:?}")),
-				});
+				self.cache.set_override(
+					untername,
+					&CachedOverride {
+						ips: override_ip.iter().take(MAX_IPS).collect(),
+						port,
+						expire: CachedOverride::default_expire(),
+						overriding: (hostname != untername)
+							.then_some(hostname.into())
+							.inspect(|_| debug_info!("{untername:?} overridden by {hostname:?}")),
+					},
+				);

 				Ok(())
 			},
@@ -361,6 +415,7 @@ fn handle_resolve_error(err: &NetError, host: &'_ str) -> Result<()> {
 		}
 	}

+	/// Ensure `dest` is a valid destination (valid ip if it is an IP), and not ourselves (unless in config)
 	fn validate_dest(&self, dest: &ServerName) -> Result<()> {
 		if dest == self.services.server.name && !self.services.server.config.federation_loopback {
 			return Err!("Won't send federation request to ourselves");
@@ -370,6 +425,7 @@ fn validate_dest(&self, dest: &ServerName) -> Result<()> {
 			self.validate_dest_ip_literal(dest)?;
 		}

+		debug!(dest = %dest, "Valid destination for resolution");
 		Ok(())
 	}

diff --git c/src/service/resolver/fed.rs i/src/service/resolver/fed.rs
index e5bee9ac2..b43f62eed 100644
--- c/src/service/resolver/fed.rs
+++ i/src/service/resolver/fed.rs
@@ -40,6 +40,15 @@ pub(crate) fn add_port_to_hostname(dest: &str) -> FedDest {
 	)
 }

+/// Ensure `host` always has a port
+///
+/// `get_ip_with_port` returns `None` if `host` isn't an IP:port string or plain IP,
+/// in which case `add_port_to_hostname` adds it instead
+#[inline]
+pub(crate) fn ensure_host_has_port(host: &str) -> FedDest {
+	get_ip_with_port(host).unwrap_or_else(|| add_port_to_hostname(host))
+}
+
 impl FedDest {
 	pub(crate) fn https_string(&self) -> String {
 		match self {
2026-07-03 10:59:06 +02:00
Jade Ellis d4f6c053bc fix: Log the set password when creating users
Closes #1894
2026-07-02 16:41:33 +01:00
Renovate Bot 4f72326b07 chore(deps): update rust crate rand to v0.10.2 2026-07-02 10:19:35 +00:00
Renovate Bot b7e449f490 chore(deps): update actions/cache action to v6 2026-07-02 10:14:57 +00:00
renovate 2a1d7e49d9 chore(Nix): Updated flake hashes 2026-07-02 10:14:36 +00:00
Renovate Bot b7792f9e86 chore(deps): update rust to v1.96.1 2026-07-02 10:14:36 +00:00
Henry-Hiles adfe9f9f53 chore: disable doctests in conduwuit_core
These aren't wanted and are failing anyways. Fixes nix package build.
2026-07-01 23:39:28 -04:00
Renovate Bot 8fe1715019 chore(deps): update rust crate aws-lc-rs to v1.17.1 2026-07-01 19:46:32 +00:00
Renovate Bot 9a91dce600 chore(deps): update rust crate serde_html_form to v0.4.1 2026-07-01 19:45:14 +00:00
Jade Ellis eee4ef50d2 chore: merge 1818 nex/perf/get-missing-events 2026-07-01 18:45:08 +00:00
timedout 62e0b53f52 feat: Prioritise sending dummy events when the extremity count reaches 20 or more 2026-07-01 19:15:35 +01:00
timedout 5fa3d5f6c8 fix: Don't create fake objects when defaulting a partial PDU build 2026-07-01 19:15:35 +01:00
timedout 9b80a99aa7 style: De-duplicate build_local_dag definitions, replace outdated comment on unwrap safety 2026-07-01 19:15:22 +01:00
timedout bd525c100f style: Re-use room_id parameter instead of potentially recalculating 2026-07-01 16:31:59 +01:00
timedout e6225f3265 fix: Use infallible room ID getter 2026-07-01 16:13:55 +01:00
Charlotte 🦝 Deleńkec 54c94ca9ad fix(appservice): Re-add room membership conditions for event sending
They accidentally got removed in a recent refactor

Fixes #1890
2026-07-01 10:20:57 +01:00
Ginger 017d4b3894 fix: Enable compat-get-3pids ruma feature 2026-06-30 12:27:49 -04:00
Renovate Bot 3b3eaf8744 chore(deps): update rust crate cargo_toml to v1 2026-06-30 13:51:11 +00:00
Renovate Bot 0a634907f2 chore(deps): update github-actions-digest 2026-06-30 05:03:24 +00:00
Henry-Hiles 2f2848728f fix: add missing brackets in nix devshell 2026-06-30 01:24:33 +00:00
timedout 19a83c6891 style: Add docstrings to squasher functions 2026-06-29 18:37:04 +01:00
timedout 2bfd678c71 feat: Add jitter to extremity squasher 2026-06-29 18:29:04 +01:00
timedout e936d18324 feat: Don't panic in build_local_dag
Previously the function assumed the caller had performed proper validation on the inputs (and all current callers do), but this is a poor reason to panic when sane error handling is available.
Events with no prev events now return an error, and prev events which are illegal are simply skipped.
2026-06-29 18:12:16 +01:00
timedout 90797fa3cd fix: Avoid string roundtrip when fetching timestamps in build_local_dag 2026-06-29 17:44:13 +01:00
Ginger 5eea8fe880 chore: News fragment 2026-06-29 16:46:47 +01:00
Ginger 12491dcc26 feat: Mark spec version 1.17 as supported 2026-06-29 16:46:39 +01:00
Ginger 2eff454787 feat: Finish implementing MSC4190 2026-06-29 16:46:07 +01:00
timedout e5dc5bedfc fix: MSC2666 is stable but not yet in a release 2026-06-29 13:56:22 +00:00
timedout 496ca52987 style: Missing semicolon on line 50 2026-06-29 13:56:22 +00:00
timedout 72f4c3cc53 chore: Remove unstable features that are now stable or abandoned 2026-06-29 13:56:22 +00:00
timedout 6a8a114197 feat: Update capabilities and advertise spec version v1.18 2026-06-29 13:56:22 +00:00
timedout 549864a052 feat: Support MSC4323 locking endpoints 2026-06-29 13:56:22 +00:00
Ginger 52026bb0f1 chore: Formatting 2026-06-29 09:41:59 -04:00
Ginger aed8e7d769 fix: Adjust error code 2026-06-29 09:35:51 -04:00
Ginger 32fb6f247a fix: Use a LRU cache for storing pending auth code grants 2026-06-29 09:35:51 -04:00
Ginger 90e14abecb fix: Spacing 2026-06-29 09:35:51 -04:00
Ginger 6ced2511e4 chore: Clippy fixes 2026-06-29 09:35:51 -04:00
Ginger 93407ade03 fix: Accessibility improvements 2026-06-29 09:35:51 -04:00
Ginger 6742d9237a fix: More CSS tweaks 2026-06-29 09:35:51 -04:00
Ginger a32c7d0d9a fix: Set correct default for registration terms config section 2026-06-29 09:35:51 -04:00
Ginger 761a6a53d0 fix: Check for existing device when creating oauth session 2026-06-29 09:35:51 -04:00
Ginger c56c035c15 fix: Use RFC-compliant error responses for OAuth endpoints 2026-06-29 09:35:51 -04:00
Ginger c804bf5780 fix: Force trusted flow UI for first-run registration 2026-06-29 09:35:51 -04:00
Ginger b87afeef80 fix: Panic when trying to check an unknown UIAA stage type 2026-06-29 09:35:51 -04:00
Ginger d0c7a62ce7 fix: Use the right error code for CAPTCHA errors 2026-06-29 09:35:50 -04:00
Ginger 47582d1922 refactor: Update logic for checking if a username is available 2026-06-29 09:35:50 -04:00
Ginger 36a30f16b1 fix: CSS adjustments 2026-06-29 09:35:50 -04:00
Ginger 52ec6bc6da fix: Adjust error codes to comply with MSC4190 2026-06-29 09:35:50 -04:00
Ginger 74788dcb19 feat: Mark spec version 1.15 as supported 2026-06-29 09:35:50 -04:00