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