Skip to main content

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}