diff --git a/README.md b/README.md index d839ca9..3b9a7e9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # rpxy: A simple and ultrafast reverse-proxy for multiple host names with TLS termination, written in pure Rust +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) ![Unit Test](https://github.com/junkurihara/rust-rpxy/actions/workflows/ci.yml/badge.svg) ![Build and Publish Docker](https://github.com/junkurihara/rust-rpxy/actions/workflows/docker_build_push.yml/badge.svg) ![ShiftLeft Scan](https://github.com/junkurihara/rust-rpxy/actions/workflows/shift_left.yml/badge.svg) @@ -8,11 +9,13 @@ ## Introduction -`rpxy` [ahr-pik-see] is an (currently experimental) implementation of simple and lightweight reverse-proxy, which is based on [`hyper`](https://github.com/hyperium/hyper), [`rustls`](https://github.com/rustls/rustls) and [`tokio`](https://github.com/tokio-rs/tokio), i.e., written in pure Rust. Our `rpxy` allows to route multiple host names to appropriate backend application servers while serving TLS connections. +`rpxy` [ahr-pik-see] is an implementation of simple and lightweight reverse-proxy with some additional features. The implementation is based on [`hyper`](https://github.com/hyperium/hyper), [`rustls`](https://github.com/rustls/rustls) and [`tokio`](https://github.com/tokio-rs/tokio), i.e., written in pure Rust. Our `rpxy` allows to route multiple host names to appropriate backend application servers while serving TLS connections. -This project is still *work-in-progress*. But it is already working in some production environments and serves numbers of domain names. Furthermore it dramatically outperforms NGINX and Caddy in the setting of very simple HTTP reverse-proxy scenario (See [`bench`](./bench/) directory). + As default, `rpxy` provides the *TLS connection sanitization* by correctly binding a certificate used to establish secure channel with backend application. Specifically, it always keeps the consistency between the given SNI (server name indication) in `ClientHello` of the underlying TLS and the domain name given by the overlaid HTTP HOST header (or URL in Request line) [^1]. Additionally, as a somewhat unstable feature, our `rpxy` can handle the brand-new HTTP/3 connection thanks to [`quinn`](https://github.com/quinn-rs/quinn) and [`hyperium/h3`](https://github.com/hyperium/h3). - `rpxy` provides the sanitization of TLS's SNI (server name indication) in default by correctly binding a certificate used to establish an underlying TLS connection with backend application specified in the overlaid HTTP HOST header (or URL in Request line). Additionally, as a somewhat unstable feature, our `rpxy` can handle the brand-new HTTP/3 connection thanks to [`quinn`](https://github.com/quinn-rs/quinn) and [`hyperium/h3`](https://github.com/hyperium/h3). + This project is still *work-in-progress*. But it is already working in some production environments and serves numbers of domain names. Furthermore it *significantly outperforms* NGINX and Caddy, e.g., *1.5x faster than NGINX*, in the setting of very simple HTTP reverse-proxy scenario (See [`bench`](./bench/) directory). + + [^1]: We should note that NGINX doesn't guarantee such a consistency by default. To this end, you have to add `if` statement in the configuration file in NGINX. ## Making an executable binary diff --git a/src/config/parse.rs b/src/config/parse.rs index f0efe75..c0c1ea7 100644 --- a/src/config/parse.rs +++ b/src/config/parse.rs @@ -10,8 +10,6 @@ use crate::{ use clap::Arg; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use std::net::SocketAddr; - -// #[cfg(feature = "tls")] use std::path::PathBuf; pub fn parse_opts(globals: &mut Globals) -> std::result::Result<(), anyhow::Error> { diff --git a/src/error.rs b/src/error.rs index 4e55bfb..3771627 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,6 +13,9 @@ pub enum RpxyError { #[error("Http Request Message Error: {0}")] Request(&'static str), + #[error("TCP/UDP Proxy Layer Error: {0}")] + Proxy(String), + #[error("I/O Error")] Io(#[from] io::Error), diff --git a/src/handler/handler_main.rs b/src/handler/handler_main.rs index 7c156fe..a2767f3 100644 --- a/src/handler/handler_main.rs +++ b/src/handler/handler_main.rs @@ -144,15 +144,18 @@ where self.globals.runtime_handle.spawn(async move { let mut response_upgraded = onupgrade.await.map_err(|e| { error!("Failed to upgrade response: {}", e); - anyhow!("Failed to upgrade response: {}", e) + RpxyError::Hyper(e) })?; let mut request_upgraded = request_upgraded.await.map_err(|e| { error!("Failed to upgrade request: {}", e); - anyhow!("Failed to upgrade request: {}", e) + RpxyError::Hyper(e) })?; copy_bidirectional(&mut response_upgraded, &mut request_upgraded) .await - .map_err(|e| anyhow!("Coping between upgraded connections failed: {}", e))?; + .map_err(|e| { + error!("Coping between upgraded connections failed: {}", e); + RpxyError::Io(e) + })?; Ok(()) as Result<()> }); log_data.status_code(&res_backend.status()).output(); diff --git a/src/handler/utils_headers.rs b/src/handler/utils_headers.rs index acb929e..02553eb 100644 --- a/src/handler/utils_headers.rs +++ b/src/handler/utils_headers.rs @@ -24,10 +24,12 @@ pub(super) fn apply_upstream_options_to_header( match opt { UpstreamOption::OverrideHost => { // overwrite HOST value with upstream hostname (like 192.168.xx.x seen from rpxy) - let upstream_host = upstream_base_uri.host().ok_or_else(|| anyhow!("none"))?; + let upstream_host = upstream_base_uri + .host() + .ok_or_else(|| anyhow!("No hostname is given in override_host option"))?; headers .insert(header::HOST, HeaderValue::from_str(upstream_host)?) - .ok_or_else(|| anyhow!("none"))?; + .ok_or_else(|| anyhow!("Failed to insert host header in override_host option"))?; } UpstreamOption::UpgradeInsecureRequests => { // add upgrade-insecure-requests in request header if not exist diff --git a/src/proxy/proxy_h3.rs b/src/proxy/proxy_h3.rs index 1de5545..c7a2eb9 100644 --- a/src/proxy/proxy_h3.rs +++ b/src/proxy/proxy_h3.rs @@ -96,7 +96,7 @@ where size += body.remaining(); if size > max_body_size { error!("Exceeds max request body size for HTTP/3"); - return Err(anyhow!("Exceeds max request body size for HTTP/3")); + return Err(RpxyError::Proxy("Exceeds max request body size for HTTP/3".to_string())); } // create stream body to save memory, shallow copy (increment of ref-count) to Bytes using copy_to_bytes sender.send_data(body.copy_to_bytes(body.remaining())).await?; diff --git a/src/proxy/proxy_tls.rs b/src/proxy/proxy_tls.rs index be71acc..9a49df6 100644 --- a/src/proxy/proxy_tls.rs +++ b/src/proxy/proxy_tls.rs @@ -68,7 +68,7 @@ where debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name); let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec())); if server_name.is_none(){ - Err(anyhow!("No SNI is given")) + Err(RpxyError::Proxy("No SNI is given".to_string())) } else { // this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake. self_inner.client_serve(stream, server_clone, client_addr, server_name); // TODO: don't want to pass copied value... @@ -76,11 +76,11 @@ where } }, Err(e) => { - Err(anyhow!("Failed to handshake TLS: {}", e)) + Err(RpxyError::Proxy(format!("Failed to handshake TLS: {}", e))) } }, Err(e) => { - Err(anyhow!("Timeout to handshake TLS: {}", e)) + Err(RpxyError::Proxy(format!("Timeout to handshake TLS: {}", e))) } } };