1use std::{
2 net::{IpAddr, SocketAddr},
3 ops::Deref,
4 sync::{Arc, LazyLock},
5 time::Duration,
6};
7
8use bytes::{Bytes, BytesMut};
9use ipaddress::{IPAddress, ipv4::from_u32 as ipv4_from_u32};
10use reqwest::{Client, ClientBuilder, Url, dns::Resolve, header::HeaderValue, redirect::Policy};
11use tuwunel_core::{
12 Config, Err, Result, config::proxy::ProxySnapshot, debug, either::Either, err,
13 error::error_chain, implement, trace,
14};
15use url::Host;
16
17use crate::{Services, resolver::Validating, service};
18
19#[cfg(test)]
20mod tests;
21
22type DisableEncoding = fn(ClientBuilder) -> ClientBuilder;
23
24pub struct Clients {
25 pub default: Client,
26 pub url_preview: Client,
27 pub extern_media: Client,
28 pub well_known: Client,
29 pub federation: Client,
30 pub synapse: Client,
31 pub sender: Client,
32 pub appservice: Client,
33 pub pusher: Client,
34 pub oauth: Client,
35}
36
37pub struct Service {
38 pub clients: LazyLock<Clients, Box<dyn FnOnce() -> Clients + Send>>,
39
40 pub proxy: Arc<ProxySnapshot>,
45 pub cidr_range_denylist: Arc<[IPAddress]>,
46}
47
48impl Deref for Service {
49 type Target = Clients;
50
51 fn deref(&self) -> &Self::Target { &self.clients }
52}
53
54impl crate::Service for Service {
55 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
56 let config = &args.server.config;
57 let proxy = Arc::new(ProxySnapshot::new(&config.proxy)?);
58
59 probe_tls(config, &proxy)?;
60
61 Ok(Arc::new(Self {
62 clients: LazyLock::new(Box::new({
63 let services = args.services.clone();
64
65 move || make_clients(&services).expect("failed to construct clients")
66 })),
67
68 proxy,
69 cidr_range_denylist: config
70 .ip_range_denylist
71 .iter()
72 .map(IPAddress::parse)
73 .inspect(|cidr| trace!("Denied CIDR range: {cidr:?}"))
74 .collect::<Result<Vec<_>, String>>()
75 .map(Arc::from)
76 .map_err(|e| err!(Config("ip_range_denylist", e)))?,
77 }))
78 }
79
80 fn name(&self) -> &str { service::make_name(std::module_path!()) }
81}
82
83fn probe_tls(config: &Config, proxy: &ProxySnapshot) -> Result {
89 base(config, proxy, None)?
90 .build()
91 .map(drop)
92 .map_err(|e| {
93 err!(error!(
94 chain = %error_chain(&e),
95 "Failed to construct an HTTPS client. If the system trust store is empty, \
96 install a CA bundle (ca-certificates on Debian and Ubuntu) or point \
97 SSL_CERT_FILE at one.",
98 ))
99 })
100}
101
102fn make_clients(services: &Services) -> Result<Clients> {
103 macro_rules! with {
104 ($builder:ident => $make:expr) => {{
105 let $builder = base(&services.config, &services.client.proxy, None)?;
106
107 $make.build()?
108 }};
109 ($name:literal, $builder:ident => $make:expr) => {{
110 let $builder = base(&services.config, &services.client.proxy, Some($name))?;
111
112 $make.build()?
113 }};
114 }
115
116 Ok(Clients {
117 default: with!(cb => cb.dns_resolver(Arc::clone(&services.resolver.resolver))),
118
119 url_preview: with!("preview", cb => preview_builder(services, cb)?),
120
121 extern_media: with!(cb => cb
122 .dns_resolver(Validating::new(
123 Arc::clone(&services.resolver.resolver),
124 Arc::clone(&services.client.cidr_range_denylist),
125 services.client.proxy.shared_hosts(),
126 ))
127 .redirect(guarded_redirect(services, 3))),
128
129 well_known: with!(cb => cb
130 .dns_resolver(Arc::clone(&services.resolver.resolver))
131 .connect_timeout(Duration::from_secs(
132 services.config.well_known_conn_timeout,
133 ))
134 .read_timeout(Duration::from_secs(services.config.well_known_timeout))
135 .timeout(Duration::from_secs(services.config.well_known_timeout))
136 .pool_max_idle_per_host(0)
137 .redirect(Policy::limited(4))),
138
139 federation: with!(cb => cb
140 .dns_resolver(Arc::clone(&services.resolver.resolver.hooked))
141 .read_timeout(Duration::from_secs(services.config.federation_timeout))
142 .pool_max_idle_per_host(services.config.federation_idle_per_host.into())
143 .pool_idle_timeout(Duration::from_secs(
144 services.config.federation_idle_timeout,
145 ))
146 .redirect(Policy::limited(3))),
147
148 synapse: with!(cb => cb
149 .dns_resolver(Arc::clone(&services.resolver.resolver.hooked))
150 .read_timeout(Duration::from_secs(305))
151 .pool_max_idle_per_host(0)
152 .redirect(Policy::limited(3))),
153
154 sender: with!(cb => cb
155 .dns_resolver(Arc::clone(&services.resolver.resolver.hooked))
156 .read_timeout(Duration::from_secs(services.config.sender_timeout))
157 .timeout(Duration::from_secs(services.config.sender_timeout))
158 .pool_max_idle_per_host(1)
159 .pool_idle_timeout(Duration::from_secs(
160 services.config.sender_idle_timeout,
161 ))
162 .redirect(Policy::limited(2))),
163
164 appservice: with!(cb => cb
165 .dns_resolver(appservice_resolver(services))
166 .connect_timeout(Duration::from_secs(5))
167 .read_timeout(Duration::from_secs(services.config.appservice_timeout))
168 .timeout(Duration::from_secs(services.config.appservice_timeout))
169 .pool_max_idle_per_host(1)
170 .pool_idle_timeout(Duration::from_secs(
171 services.config.appservice_idle_timeout,
172 ))
173 .redirect(Policy::limited(2))),
174
175 pusher: with!(cb => cb
176 .dns_resolver(Validating::new(
177 Arc::clone(&services.resolver.resolver),
178 Arc::clone(&services.client.cidr_range_denylist),
179 services.client.proxy.shared_hosts(),
180 ))
181 .pool_max_idle_per_host(1)
182 .pool_idle_timeout(Duration::from_secs(
183 services.config.pusher_idle_timeout,
184 ))
185 .redirect(guarded_redirect(services, 2))),
186
187 oauth: with!(cb => cb
188 .dns_resolver(Arc::clone(&services.resolver.resolver))
189 .redirect(Policy::limited(0))
190 .pool_max_idle_per_host(1)),
191 })
192}
193
194fn preview_builder(services: &Services, builder: ClientBuilder) -> Result<ClientBuilder> {
200 let interface = &services.config.url_preview_bound_interface;
201
202 let bind_addr = interface.clone().and_then(Either::left);
203 let bind_iface = interface.clone().and_then(Either::right);
204
205 let resolver = Validating::new(
206 Arc::clone(&services.resolver.resolver),
207 Arc::clone(&services.client.cidr_range_denylist),
208 services.client.proxy.shared_hosts(),
209 );
210
211 Ok(builder_interface(builder, bind_iface.as_deref())?
212 .local_address(bind_addr)
213 .dns_resolver(resolver)
214 .redirect(guarded_redirect(services, 3)))
215}
216
217fn guarded_redirect(services: &Services, max: usize) -> Policy {
218 let proxy = Arc::clone(&services.client.proxy);
219 let denylist = Arc::clone(&services.client.cidr_range_denylist);
220 let limited = Policy::limited(max);
221
222 Policy::custom(move |attempt| {
223 if proxy.resolver_alias(attempt.url()) || !valid_cidr_range_url(&denylist, attempt.url())
224 {
225 attempt.error("redirect destination is not allowed")
226 } else {
227 limited.redirect(attempt)
228 }
229 })
230}
231
232fn valid_cidr_range_url(denylist: &[IPAddress], url: &Url) -> bool {
233 let ip = match url.host() {
234 | Some(Host::Ipv4(ip)) => IpAddr::V4(ip),
235 | Some(Host::Ipv6(ip)) => IpAddr::V6(ip),
236 | Some(Host::Domain(_)) | None => return true,
237 };
238
239 let ip = ipaddress_from_std(ip);
240
241 denylist.iter().all(|cidr| !cidr.includes(&ip))
242}
243
244fn base(config: &Config, proxy: &ProxySnapshot, name: Option<&str>) -> Result<ClientBuilder> {
245 let user_agent = tuwunel_core::version::user_agent();
246 let user_agent: HeaderValue = name
247 .map(|name| format!("{user_agent} {name}").try_into())
248 .unwrap_or_else(|| user_agent.try_into())?;
249
250 let builder = Client::builder()
251 .connect_timeout(Duration::from_secs(config.request_conn_timeout))
252 .read_timeout(Duration::from_secs(config.request_timeout))
253 .timeout(Duration::from_secs(config.request_total_timeout))
254 .pool_idle_timeout(Duration::from_secs(config.request_idle_timeout))
255 .pool_max_idle_per_host(config.request_idle_per_host.into())
256 .user_agent(user_agent)
257 .redirect(Policy::limited(6))
258 .danger_accept_invalid_certs(config.allow_invalid_tls_certificates)
259 .connection_verbose(cfg!(debug_assertions))
260 .tls_sslkeylogfile(std::env::var_os("SSLKEYLOGFILE").is_some());
262
263 let encodings: [(bool, DisableEncoding); 3] = [
264 (config.request_gzip, ClientBuilder::no_gzip),
265 (config.request_brotli, ClientBuilder::no_brotli),
266 (config.request_zstd, ClientBuilder::no_zstd),
267 ];
268
269 let builder = encodings
270 .into_iter()
271 .filter(|(enabled, _)| !enabled)
272 .fold(builder, |builder, (_, disable)| disable(builder));
273
274 Ok(proxy.configure(builder))
275}
276
277pub async fn read_response_capped(
283 mut response: reqwest::Response,
284 limit: usize,
285) -> Result<Bytes> {
286 let mut body = match response.content_length() {
287 | Some(len) if len > limit.try_into().unwrap_or(u64::MAX) => {
288 debug!(%len, %limit, "rejecting response: advertised body exceeds limit");
289 return Err!(BadServerResponse(
290 "Response body length {len} exceeds the {limit} byte limit"
291 ));
292 },
293 | Some(len) => BytesMut::with_capacity(usize::try_from(len).unwrap_or(limit)),
294 | None => BytesMut::new(),
295 };
296 while let Some(chunk) = response.chunk().await? {
297 if body.len().saturating_add(chunk.len()) > limit {
298 debug!(%limit, "rejecting response: streamed body exceeds limit");
299 return Err!(BadServerResponse("Response body exceeds the {limit} byte limit"));
300 }
301
302 body.extend_from_slice(&chunk);
303 }
304
305 Ok(body.freeze())
306}
307
308#[cfg(any(
309 target_os = "android",
310 target_os = "fuchsia",
311 target_os = "linux"
312))]
313fn builder_interface(builder: ClientBuilder, config: Option<&str>) -> Result<ClientBuilder> {
314 if let Some(iface) = config {
315 Ok(builder.interface(iface))
316 } else {
317 Ok(builder)
318 }
319}
320
321#[cfg(not(any(
322 target_os = "android",
323 target_os = "fuchsia",
324 target_os = "linux"
325)))]
326fn builder_interface(builder: ClientBuilder, config: Option<&str>) -> Result<ClientBuilder> {
327 use tuwunel_core::Err;
328
329 config.map_or_else(
330 || Ok(builder),
331 |iface| {
332 Err!(
333 "Binding to network-interface {iface:?} by name is not supported on this \
334 platform."
335 )
336 },
337 )
338}
339
340fn appservice_resolver(services: &Services) -> Arc<dyn Resolve> {
341 if services.server.config.dns_passthru_appservices {
342 services.resolver.resolver.passthru.clone()
343 } else {
344 services.resolver.resolver.clone()
345 }
346}
347
348#[implement(Service)]
353#[inline]
354#[must_use]
355pub fn valid_cidr_range_remote_addr(&self, url: &Url, remote_addr: SocketAddr) -> bool {
356 self.valid_cidr_range_ip(remote_addr.ip()) || self.proxied(url)
357}
358
359#[inline]
360#[must_use]
361#[implement(Service)]
362pub fn valid_cidr_range(&self, ip: &IPAddress) -> bool {
363 self.cidr_range_denylist
364 .iter()
365 .all(|cidr| !cidr.includes(ip))
366}
367
368#[inline]
369#[must_use]
370#[implement(Service)]
371pub fn valid_cidr_range_ip(&self, ip: IpAddr) -> bool {
372 let addr = ipaddress_from_std(ip);
373 self.cidr_range_denylist
374 .iter()
375 .all(|cidr| !cidr.includes(&addr))
376}
377
378#[implement(Service)]
383#[inline]
384#[must_use]
385pub fn valid_cidr_range_url(&self, url: &Url) -> bool {
386 valid_cidr_range_url(&self.cidr_range_denylist, url)
387}
388
389#[implement(Service)]
394#[inline]
395#[must_use]
396pub fn proxied(&self, url: &Url) -> bool { self.proxy.intercepts(url) }
397
398#[must_use]
399pub(crate) fn ipaddress_from_std(ip: IpAddr) -> IPAddress {
400 match ip {
401 | IpAddr::V4(v4) =>
402 ipv4_from_u32(u32::from(v4), 32).expect("/32 is always a valid prefix"),
403 | IpAddr::V6(v6) =>
405 IPAddress::parse(v6.to_string()).expect("Ipv6Addr Display output parses"),
406 }
407}