postrust_proxy/vendored/proxy.rs
1//! Vendored proxy service from rpxy-lib: proxy/proxy_main.rs
2//!
3//! This module provides the main proxy service that handles incoming connections.
4
5use crate::config::ProxyConfig;
6use crate::health::HealthChecker;
7use crate::ratelimit::{RateLimitKey, RateLimiter};
8use crate::vendored::backend::BackendAppManager;
9use crate::vendored::forwarder::ForwarderClient;
10use crate::vendored::handler::MessageHandler;
11use crate::vendored::hyper_ext::timer::Timeout;
12use crate::vendored::hyper_ext::{IncomingBodyExt, ProxyBody};
13use crate::vendored::types::PathName;
14use hyper::body::Incoming;
15use hyper::server::conn::http2;
16use hyper::service::service_fn;
17use hyper::{Request, Response};
18use hyper_util::rt::{TokioExecutor, TokioIo};
19use hyper_util::server::conn::auto;
20use std::convert::Infallible;
21use std::net::SocketAddr;
22use std::sync::Arc;
23use std::time::Duration;
24use tokio::net::TcpListener;
25use tokio::sync::RwLock;
26use tokio_util::sync::CancellationToken;
27use tracing::{debug, error, info, warn};
28
29/// Proxy service that handles incoming HTTP connections.
30pub struct ProxyService {
31 /// Backend manager
32 backend_manager: Arc<BackendAppManager>,
33 /// Forwarder client
34 forwarder: Arc<ForwarderClient>,
35 /// Rate limiter
36 rate_limiter: Arc<RateLimiter>,
37 /// Configuration
38 config: Arc<RwLock<ProxyConfig>>,
39}
40
41impl ProxyService {
42 /// Create a new proxy service.
43 pub fn new(
44 config: Arc<RwLock<ProxyConfig>>,
45 health_checker: Arc<HealthChecker>,
46 rate_limiter: Arc<RateLimiter>,
47 ) -> Self {
48 let backend_manager =
49 Arc::new(BackendAppManager::new().with_health_checker(health_checker));
50 let forwarder = Arc::new(ForwarderClient::default());
51
52 Self {
53 backend_manager,
54 forwarder,
55 rate_limiter,
56 config,
57 }
58 }
59
60 /// Load configuration into the backend manager.
61 pub async fn load_config(&self) {
62 let config = self.config.read().await;
63
64 // Register upstreams
65 for upstream in &config.upstreams {
66 self.backend_manager.register_upstream(upstream.clone());
67 }
68
69 // Register routes
70 for route in &config.routes {
71 // Find upstream by name
72 if let Some(upstream) = config.upstreams.iter().find(|u| u.name == route.upstream) {
73 use crate::vendored::types::ServerName;
74 let host = route.match_.host.as_deref().unwrap_or("*");
75 let path = route.match_.path.as_deref().unwrap_or("/");
76 let host = ServerName::new(host);
77 let path = PathName::new(path);
78 self.backend_manager
79 .register_route(host, path, upstream.resolved_id());
80 }
81 }
82
83 info!(
84 "Loaded {} routes and {} upstreams",
85 config.routes.len(),
86 config.upstreams.len()
87 );
88 }
89
90 /// Start the HTTP proxy server.
91 pub async fn serve_http(
92 self: Arc<Self>,
93 addr: SocketAddr,
94 cancel_token: CancellationToken,
95 ) -> std::io::Result<()> {
96 let listener = TcpListener::bind(addr).await?;
97 info!("HTTP proxy listening on {}", addr);
98
99 loop {
100 tokio::select! {
101 _ = cancel_token.cancelled() => {
102 info!("HTTP proxy stopped");
103 break;
104 }
105 result = listener.accept() => {
106 match result {
107 Ok((stream, client_addr)) => {
108 // Same reasoning as the upstream leg: relay the
109 // client's own chunking rather than Nagle's.
110 if let Err(e) = stream.set_nodelay(true) {
111 debug!("Could not set TCP_NODELAY on the client socket: {}", e);
112 }
113 let service = self.clone();
114 tokio::spawn(async move {
115 let service_fn = service_fn(|req| {
116 let svc = service.clone();
117 async move {
118 svc.handle_request(req, client_addr, "http").await
119 }
120 });
121
122 // The auto builder negotiates HTTP/1.1 and
123 // prior-knowledge h2c on the same port, and the
124 // _with_upgrades variant is what lets a 101 hand
125 // the connection back to us for tunnelling.
126 // enable_connect_protocol advertises
127 // SETTINGS_ENABLE_CONNECT_PROTOCOL, which is what
128 // permits WebSocket over HTTP/2 (RFC 8441).
129 let mut builder = auto::Builder::new(TokioExecutor::new());
130 builder.http2().enable_connect_protocol();
131 if let Err(err) = builder
132 .serve_connection_with_upgrades(
133 TokioIo::new(stream),
134 service_fn,
135 )
136 .await
137 {
138 debug!("Connection error: {}", err);
139 }
140 });
141 }
142 Err(e) => {
143 error!("Accept error: {}", e);
144 }
145 }
146 }
147 }
148 }
149
150 Ok(())
151 }
152
153 /// Serve HTTPS, negotiating the protocol with ALPN.
154 ///
155 /// ALPN is the whole point: without it a TLS listener negotiates nothing and
156 /// every client falls back to HTTP/1.1, which leaves HTTP/2 reachable only
157 /// as cleartext h2c and WebSocket only as `ws://`.
158 ///
159 /// Because ALPN *decides* the protocol rather than guessing it, the h2 path
160 /// can use the HTTP/2 builder directly instead of sniffing -- so, unlike the
161 /// cleartext port, an invalid preface here is answered with a proper GOAWAY.
162 pub async fn serve_https(
163 self: Arc<Self>,
164 addr: SocketAddr,
165 tls_config: Arc<tokio_rustls::rustls::ServerConfig>,
166 cancel_token: CancellationToken,
167 ) -> std::io::Result<()> {
168 let acceptor = tokio_rustls::TlsAcceptor::from(tls_config);
169 let listener = TcpListener::bind(addr).await?;
170 info!("HTTPS proxy listening on {} (ALPN: h2, http/1.1)", addr);
171
172 loop {
173 tokio::select! {
174 _ = cancel_token.cancelled() => {
175 info!("HTTPS proxy stopped");
176 break;
177 }
178 result = listener.accept() => {
179 match result {
180 Ok((stream, client_addr)) => {
181 if let Err(e) = stream.set_nodelay(true) {
182 debug!("Could not set TCP_NODELAY on the client socket: {}", e);
183 }
184 let service = self.clone();
185 let acceptor = acceptor.clone();
186 tokio::spawn(async move {
187 let tls_stream = match acceptor.accept(stream).await {
188 Ok(s) => s,
189 Err(e) => {
190 debug!("TLS handshake failed: {}", e);
191 return;
192 }
193 };
194
195 let is_h2 = tls_stream
196 .get_ref()
197 .1
198 .alpn_protocol()
199 .map(|p| p == b"h2")
200 .unwrap_or(false);
201
202 let service_fn = service_fn(|req| {
203 let svc = service.clone();
204 async move {
205 svc.handle_request(req, client_addr, "https").await
206 }
207 });
208
209 let io = TokioIo::new(tls_stream);
210 let result = if is_h2 {
211 let mut builder = http2::Builder::new(TokioExecutor::new());
212 builder.enable_connect_protocol();
213 builder
214 .serve_connection(io, service_fn)
215 .await
216 .map_err(|e| {
217 Box::<dyn std::error::Error + Send + Sync>::from(
218 e.to_string(),
219 )
220 })
221 } else {
222 // http/1.1, or a client that offered no ALPN
223 // at all. Upgrades stay available, which is
224 // what makes wss:// work.
225 let mut builder = auto::Builder::new(TokioExecutor::new());
226 builder.http2().enable_connect_protocol();
227 builder
228 .serve_connection_with_upgrades(io, service_fn)
229 .await
230 .map_err(|e| {
231 Box::<dyn std::error::Error + Send + Sync>::from(
232 e.to_string(),
233 )
234 })
235 };
236
237 if let Err(err) = result {
238 debug!("HTTPS connection error: {}", err);
239 }
240 });
241 }
242 Err(e) => {
243 error!("Accept error: {}", e);
244 }
245 }
246 }
247 }
248 }
249
250 Ok(())
251 }
252
253 /// Serve HTTP/2 only, on a dedicated port.
254 ///
255 /// The main listener negotiates by sniffing the opening bytes, which is why
256 /// a corrupted HTTP/2 preface there gets an HTTP/1 400 instead of
257 /// GOAWAY(PROTOCOL_ERROR): it cannot be told apart from a malformed HTTP/1
258 /// request (h2spec 3.5). A listener that only ever speaks HTTP/2 has no
259 /// such ambiguity and answers the way the RFC asks.
260 ///
261 /// This is additive. The main port still serves HTTP/1.1 and h2c together;
262 /// nothing is given up by enabling it.
263 pub async fn serve_h2c(
264 self: Arc<Self>,
265 addr: SocketAddr,
266 cancel_token: CancellationToken,
267 ) -> std::io::Result<()> {
268 let listener = TcpListener::bind(addr).await?;
269 info!("HTTP/2-only proxy listening on {}", addr);
270
271 loop {
272 tokio::select! {
273 _ = cancel_token.cancelled() => {
274 info!("HTTP/2-only proxy stopped");
275 break;
276 }
277 result = listener.accept() => {
278 match result {
279 Ok((stream, client_addr)) => {
280 if let Err(e) = stream.set_nodelay(true) {
281 debug!("Could not set TCP_NODELAY on the client socket: {}", e);
282 }
283 let service = self.clone();
284 tokio::spawn(async move {
285 let service_fn = service_fn(|req| {
286 let svc = service.clone();
287 async move {
288 svc.handle_request(req, client_addr, "http").await
289 }
290 });
291
292 let mut builder = http2::Builder::new(TokioExecutor::new());
293 builder.enable_connect_protocol();
294 if let Err(err) = builder
295 .serve_connection(TokioIo::new(stream), service_fn)
296 .await
297 {
298 debug!("HTTP/2 connection error: {}", err);
299 }
300 });
301 }
302 Err(e) => {
303 error!("Accept error: {}", e);
304 }
305 }
306 }
307 }
308 }
309
310 Ok(())
311 }
312
313 /// Handle a single HTTP request.
314 async fn handle_request(
315 &self,
316 request: Request<Incoming>,
317 client_addr: SocketAddr,
318 proto: &str,
319 ) -> Result<Response<ProxyBody>, Infallible> {
320 let uri = request.uri().clone();
321 let method = request.method().clone();
322
323 // Host, falling back to the URI authority. HTTP/2 has no Host header,
324 // so reading only the header routed nothing for any host-matched route
325 // over h2 or h2c. See MessageHandler::request_host.
326 let host = MessageHandler::request_host(&request);
327 let host = host.as_str();
328
329 let path = uri.path();
330
331 debug!("{} {} {} from {}", method, host, path, client_addr);
332
333 // Find matching route and upstream
334 let (route, upstream_id) = {
335 let config = self.config.read().await;
336 // The whole of RouteMatch, not just host and path prefix. The
337 // previous filter chain ignored path_type, methods and headers,
338 // each of which silently widened a route past what its author
339 // wrote -- `path_type = "exact"` on /health also matched
340 // /health-internal, and `methods = ["GET"]` accepted DELETE.
341 let matched = config
342 .routes
343 .iter()
344 .filter(|r| r.enabled)
345 .filter(|r| {
346 r.match_
347 .matches(host, path, method.as_str(), request.headers())
348 })
349 .max_by_key(|r| {
350 let path_len = r.match_.path.as_ref().map(|p| p.len()).unwrap_or(0);
351 (r.priority, path_len)
352 })
353 .cloned();
354
355 match matched {
356 Some(r) => {
357 // Find upstream ID
358 let upstream_id = config
359 .upstreams
360 .iter()
361 .find(|u| u.name == r.upstream)
362 .map(|u| u.resolved_id());
363 (Some(r), upstream_id)
364 }
365 None => (None, None),
366 }
367 };
368
369 let route = match route {
370 Some(r) => r,
371 None => {
372 debug!("No route found for {} {}", host, path);
373 return Ok(MessageHandler::not_found());
374 }
375 };
376
377 let upstream_id = match upstream_id {
378 Some(id) => id,
379 None => {
380 warn!(
381 "Upstream '{}' not found for route {}",
382 route.upstream, route.name
383 );
384 return Ok(MessageHandler::service_unavailable("Upstream not found"));
385 }
386 };
387
388 // Rate limiting
389 if let Some(ref rate_limit) = route.rate_limit {
390 let key = RateLimitKey::Ip(client_addr.ip());
391 // Convert requests/window to rps
392 let rps = rate_limit
393 .requests
394 .checked_div(rate_limit.window_secs)
395 .unwrap_or(rate_limit.requests);
396 if !self
397 .rate_limiter
398 .check_with_config(key, rps, rate_limit.requests)
399 {
400 return Ok(MessageHandler::too_many_requests());
401 }
402 }
403
404 // Select backend
405 let backend = match self.backend_manager.select_backend(upstream_id, None) {
406 Some(b) => b,
407 None => {
408 warn!("No healthy backends for route {}", route.name);
409 return Ok(MessageHandler::service_unavailable(
410 "No healthy backends available",
411 ));
412 }
413 };
414
415 // Build forwarded request
416 let (parts, body) = request.into_parts();
417 let mut forwarded_request = Request::from_parts(parts, body.boxed_body());
418
419 // Reject ambiguous body framing before the Transfer-Encoding that
420 // signals it is stripped below.
421 if MessageHandler::has_ambiguous_framing(&forwarded_request) {
422 warn!("Rejecting request with both Content-Length and Transfer-Encoding");
423 return Ok(MessageHandler::bad_request(
424 "Both Content-Length and Transfer-Encoding present",
425 ));
426 }
427
428 // An upgrade request keeps its Connection/Upgrade pair, but only after the
429 // strip has removed everything else -- see restore_upgrade_headers.
430 let upgrade_protocol = MessageHandler::upgrade_protocol(&forwarded_request);
431 // The HTTP/2 spelling of the same intent (RFC 8441), which carries no
432 // Upgrade header at all and so survives the strip untouched.
433 let h2_protocol = MessageHandler::h2_connect_protocol(&forwarded_request);
434
435 // Drop hop-by-hop headers before anything else, so a client cannot name
436 // our own X-Forwarded-* headers in Connection to suppress them.
437 MessageHandler::strip_hop_by_hop_headers(&mut forwarded_request);
438
439 // Add forwarding headers
440 MessageHandler::add_forwarding_headers(&mut forwarded_request, client_addr, proto);
441
442 // Strip path prefix if configured
443 if route.strip_path {
444 if let Some(ref prefix) = route.match_.path {
445 MessageHandler::strip_path_prefix(&mut forwarded_request, prefix);
446 }
447 }
448
449 // Apply route headers
450 MessageHandler::apply_route_headers(&mut forwarded_request, &route);
451
452 // Rewrite host header
453 MessageHandler::rewrite_host_header(&mut forwarded_request, &backend.address);
454
455 // WebSocket over HTTP/2: translate to an HTTP/1.1 upgrade for the origin.
456 if let Some(protocol) = h2_protocol {
457 debug!(
458 "Extended CONNECT to {} via {} (RFC 8441)",
459 protocol, backend.address
460 );
461 return match self
462 .forwarder
463 .forward_h2_websocket(&backend, forwarded_request, &protocol)
464 .await
465 {
466 Ok(response) => Ok(response),
467 Err(e) => {
468 error!("Extended CONNECT error to {}: {}", backend.address, e);
469 Ok(MessageHandler::bad_gateway(&format!(
470 "Extended CONNECT error: {}",
471 e
472 )))
473 }
474 };
475 }
476
477 // Tunnel an upgrade rather than treating it as request/response.
478 if let Some(protocol) = upgrade_protocol {
479 MessageHandler::restore_upgrade_headers(&mut forwarded_request, &protocol);
480 debug!(
481 "Upgrading connection to {} via {}",
482 protocol, backend.address
483 );
484 return match self
485 .forwarder
486 .forward_upgrade(&backend, forwarded_request)
487 .await
488 {
489 Ok(response) => Ok(response),
490 Err(e) => {
491 error!("Upgrade error to {}: {}", backend.address, e);
492 Ok(MessageHandler::bad_gateway(&format!(
493 "Upgrade error: {}",
494 e
495 )))
496 }
497 };
498 }
499
500 // Forward request, under the route's timeout.
501 //
502 // `route.timeout_secs` was declarable and unread: an upstream that
503 // accepted a connection and then never answered held the request open
504 // for as long as it liked, and the timer machinery this uses was sitting
505 // in hyper_ext with nothing calling it.
506 //
507 // 504 rather than 502: the distinction is the difference between "the
508 // upstream refused" and "the upstream is not answering", and an operator
509 // reads them differently.
510 let timeout = Duration::from_secs(u64::from(route.timeout_secs));
511 let forwarded = Timeout::new(self.forwarder.forward(&backend, forwarded_request), timeout);
512
513 match forwarded.await {
514 Ok(Ok(response)) => {
515 let (parts, body) = response.into_parts();
516 Ok(Response::from_parts(parts, body.boxed_body()))
517 }
518 Ok(Err(e)) => {
519 error!("Forward error to {}: {}", backend.address, e);
520 Ok(MessageHandler::bad_gateway(&format!(
521 "Backend error: {}",
522 e
523 )))
524 }
525 Err(_) => {
526 error!(
527 "Upstream {} did not answer within {}s (route {})",
528 backend.address, route.timeout_secs, route.name
529 );
530 Ok(MessageHandler::gateway_timeout(&format!(
531 "Upstream did not answer within {}s",
532 route.timeout_secs
533 )))
534 }
535 }
536 }
537}