1use std::{fmt::Debug, mem, time::Duration};
2
3use bytes::Bytes;
4use ipaddress::IPAddress;
5use reqwest::{Client, Method, Request, Response, Url};
6use ruma::{
7 ServerName,
8 api::{
9 EndpointError, IncomingResponse, MatrixVersion, OutgoingRequest, OutgoingRequestExt,
10 SupportedVersions,
11 error::{Error as RumaError, ErrorBody},
12 },
13};
14use tokio::time::timeout;
15use tuwunel_core::{
16 Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err, implement,
17 trace,
18};
19
20use super::{
21 ShouldAttempt,
22 peer::classify_error,
23 scheme::{FedAuth, FedPath},
24};
25use crate::{client::read_response_capped, resolver::actual::ActualDest};
26
27#[implement(super::Service)]
29#[tracing::instrument(skip_all, name = "request", level = "debug")]
30pub async fn execute<T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
31where
32 T: OutgoingRequest + Debug + Send,
33 T::Authentication: FedAuth,
34 T::PathBuilder: FedPath,
35{
36 let client = &self.services.client.federation;
37 self.execute_on(client, dest, request).await
38}
39
40#[implement(super::Service)]
46#[tracing::instrument(skip_all, name = "keys", level = "debug")]
47pub async fn execute_keys<T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
48where
49 T: OutgoingRequest + Debug + Send,
50 T::Authentication: FedAuth,
51 T::PathBuilder: FedPath,
52{
53 if matches!(self.should_attempt(dest).await, ShouldAttempt::No { .. }) {
54 return Err!("{dest} is in federation backoff; skipping key lookup");
55 }
56
57 let timeout_dur = Duration::from_secs(
58 self.services
59 .server
60 .config
61 .federation_keys_timeout,
62 );
63
64 let client = &self.services.client.federation;
65
66 match timeout(timeout_dur, self.execute_uncounted(client, dest, request)).await {
67 | Ok(result) => result,
68 | Err(_elapsed) => Err!("{dest} key lookup exceeded {}s", timeout_dur.as_secs()),
69 }
70}
71
72#[implement(super::Service)]
74#[tracing::instrument(skip_all, name = "synapse", level = "debug")]
75pub async fn execute_synapse<T>(
76 &self,
77 dest: &ServerName,
78 request: T,
79) -> Result<T::IncomingResponse>
80where
81 T: OutgoingRequest + Debug + Send,
82 T::Authentication: FedAuth,
83 T::PathBuilder: FedPath,
84{
85 let client = &self.services.client.synapse;
86 self.execute_on(client, dest, request).await
87}
88
89#[implement(super::Service)]
90pub async fn execute_on<T>(
91 &self,
92 client: &Client,
93 dest: &ServerName,
94 request: T,
95) -> Result<T::IncomingResponse>
96where
97 T: OutgoingRequest + Send,
98 T::Authentication: FedAuth,
99 T::PathBuilder: FedPath,
100{
101 let result = self
102 .execute_uncounted(client, dest, request)
103 .await;
104
105 match &result {
106 | Ok(_) => self.record_success(dest).await,
107 | Err(error) =>
108 if let Some(class) = classify_error(error) {
109 self.record_failure(dest, class);
110 },
111 }
112
113 result
114}
115
116#[implement(super::Service)]
119#[tracing::instrument(
120 name = "fed",
121 level = INFO_SPAN_LEVEL,
122 skip(self, client, request),
123)]
124async fn execute_uncounted<T>(
125 &self,
126 client: &Client,
127 dest: &ServerName,
128 request: T,
129) -> Result<T::IncomingResponse>
130where
131 T: OutgoingRequest + Send,
132 T::Authentication: FedAuth,
133 T::PathBuilder: FedPath,
134{
135 if !self.services.server.config.allow_federation {
136 return Err!(Config("allow_federation", "Federation is disabled."));
137 }
138
139 if self
140 .services
141 .server
142 .config
143 .is_forbidden_remote_server_name(dest)
144 {
145 return Err!(Request(Forbidden(debug_warn!("Federation with {dest} is not allowed."))));
146 }
147
148 let actual = self
149 .services
150 .resolver
151 .get_actual_dest(dest)
152 .await?;
153
154 let request = self.prepare(&actual, dest, request)?;
155
156 self.perform::<T>(&actual, dest, request, client)
157 .await
158}
159
160#[implement(super::Service)]
161async fn perform<T>(
162 &self,
163 actual: &ActualDest,
164 dest: &ServerName,
165 request: Request,
166 client: &Client,
167) -> Result<T::IncomingResponse>
168where
169 T: OutgoingRequest + Send,
170 T::Authentication: FedAuth,
171 T::PathBuilder: FedPath,
172{
173 let url = request.url().clone();
174 let method = request.method().clone();
175
176 debug!(?method, ?url, "Sending request");
177 let limit = self.services.server.config.max_response_size;
178
179 match client.execute(request).await {
180 | Ok(response) => handle_response::<T>(actual, dest, &method, &url, response, limit)
181 .await
182 .inspect_err(|error| self.evict_misrouted(dest, actual, error)),
183 | Err(error) => Err(self
184 .handle_error(dest, actual, &method, &url, error)
185 .expect_err("always returns error")),
186 }
187}
188
189#[implement(super::Service)]
190fn prepare<T>(&self, actual: &ActualDest, dest: &ServerName, request: T) -> Result<Request>
191where
192 T: OutgoingRequest + Send,
193 T::Authentication: FedAuth,
194 T::PathBuilder: FedPath,
195{
196 let request = self.to_http_request::<T>(actual, dest, request)?;
197 let request = Request::try_from(request)?;
198 self.validate_url(request.url())?;
199 self.services.server.check_running()?;
200
201 Ok(request)
202}
203
204#[implement(super::Service)]
205fn validate_url(&self, url: &Url) -> Result {
206 if let Some(url_host) = url.host_str()
207 && let Ok(ip) = IPAddress::parse(url_host)
208 {
209 trace!("Checking request URL IP {ip:?}");
210 self.services.resolver.validate_ip(&ip)?;
211 }
212
213 Ok(())
214}
215
216async fn handle_response<T>(
217 actual: &ActualDest,
218 dest: &ServerName,
219 method: &Method,
220 url: &Url,
221 response: Response,
222 limit: usize,
223) -> Result<T::IncomingResponse>
224where
225 T: OutgoingRequest + Send,
226 T::Authentication: FedAuth,
227 T::PathBuilder: FedPath,
228{
229 let response = into_http_response(dest, actual, method, url, response, limit).await?;
230
231 T::IncomingResponse::try_from_http_response(response)
232 .map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
233}
234
235async fn into_http_response(
236 dest: &ServerName,
237 actual: &ActualDest,
238 method: &Method,
239 url: &Url,
240 mut response: Response,
241 limit: usize,
242) -> Result<http::Response<Bytes>> {
243 let status = response.status();
244 trace!(
245 ?status, ?method,
246 request_url = ?url,
247 response_url = ?response.url(),
248 "Received response from {}",
249 actual.to_string(),
250 );
251
252 let mut http_response_builder = http::Response::builder()
253 .status(status)
254 .version(response.version());
255
256 mem::swap(
257 response.headers_mut(),
258 http_response_builder
259 .headers_mut()
260 .expect("http::response::Builder is usable"),
261 );
262
263 trace!("Waiting for response body...");
265 let body = read_response_capped(response, limit).await?;
266
267 let http_response = http_response_builder
268 .body(body)
269 .expect("reqwest body is valid http body");
270
271 debug!("Got {status:?} for {method} {url}");
272 if !status.is_success() {
273 return Err(Error::Federation(
274 dest.to_owned(),
275 RumaError::from_http_response(http_response),
276 ));
277 }
278
279 Ok(http_response)
280}
281
282#[implement(super::Service)]
283fn handle_error(
284 &self,
285 dest: &ServerName,
286 actual: &ActualDest,
287 method: &Method,
288 url: &Url,
289 mut e: reqwest::Error,
290) -> Result {
291 if e.is_timeout() || e.is_connect() {
292 e = e.without_url();
293 debug_warn!("{e:?}");
294 } else if e.is_redirect() {
295 debug_error!(
296 method = ?method,
297 url = ?url,
298 final_url = ?e.url(),
299 "Redirect loop {}: {}",
300 actual.host,
301 e,
302 );
303 } else {
304 debug_error!("{e:?}");
305 }
306
307 self.evict_route(dest, actual);
308
309 Err(e.into())
310}
311
312#[implement(super::Service)]
315fn evict_misrouted(&self, dest: &ServerName, actual: &ActualDest, error: &Error) {
316 let Error::Federation(_, response) = error else {
317 return;
318 };
319
320 if matches!(response.body, ErrorBody::NotJson { .. }) {
321 self.evict_route(dest, actual);
322 }
323}
324
325#[implement(super::Service)]
328fn evict_route(&self, dest: &ServerName, actual: &ActualDest) {
329 self.services.resolver.cache.del_destination(dest);
330 self.services
331 .resolver
332 .cache
333 .del_override(&actual.dest.hostname());
334}
335
336#[implement(super::Service)]
337fn to_http_request<T>(
338 &self,
339 actual: &ActualDest,
340 dest: &ServerName,
341 request: T,
342) -> Result<http::Request<Vec<u8>>>
343where
344 T: OutgoingRequest + Send,
345 T::Authentication: FedAuth,
346 T::PathBuilder: FedPath,
347{
348 const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_11];
349 let supported = SupportedVersions {
350 versions: VERSIONS.into(),
351 features: Default::default(),
352 };
353
354 let auth = T::Authentication::input(
355 self.services.server.name.clone(),
356 dest.to_owned(),
357 self.services.server_keys.keypair(),
358 );
359 let path = T::PathBuilder::input(&supported);
360
361 request
362 .try_into_http_request::<Vec<u8>>(actual.to_string().as_str(), auth, path)
363 .map_err(|e| err!(BadServerResponse("Invalid destination: {e:?}")))
364}