From ec85b0bb28c2c3ef0c6e908d8b4403bdffcbd4c1 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 21 Jul 2023 17:22:10 +0900 Subject: [PATCH 01/10] deps --- Cargo.toml | 12 ++++++------ h3 | 2 +- quinn | 2 +- src/globals.rs | 3 +-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f7b0ea..e950521 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,14 +17,14 @@ http3 = ["quinn", "h3", "h3-quinn"] sticky-cookie = ["base64", "sha2", "chrono"] [dependencies] -anyhow = "1.0.71" -clap = { version = "4.3.11", features = ["std", "cargo", "wrap_help"] } +anyhow = "1.0.72" +clap = { version = "4.3.17", features = ["std", "cargo", "wrap_help"] } rand = "0.8.5" toml = { version = "0.7.6", default-features = false, features = ["parse"] } rustc-hash = "1.1.0" -serde = { version = "1.0.171", default-features = false, features = ["derive"] } +serde = { version = "1.0.174", default-features = false, features = ["derive"] } bytes = "1.4.0" -thiserror = "1.0.43" +thiserror = "1.0.44" x509-parser = "0.15.0" derive_builder = "0.12.0" futures = { version = "0.3.28", features = ["alloc", "async-await"] } @@ -35,7 +35,7 @@ tokio = { version = "1.29.1", default-features = false, features = [ "sync", "macros", ] } -async-trait = "0.1.71" +async-trait = "0.1.72" hot_reload = "0.1.2" # reloading certs # http and tls @@ -53,7 +53,7 @@ hyper-rustls = { version = "0.24.1", default-features = false, features = [ ] } tokio-rustls = { version = "0.24.1", features = ["early-data"] } rustls-pemfile = "1.0.3" -rustls = { version = "0.21.3", default-features = false } +rustls = { version = "0.21.5", default-features = false } webpki = "0.22.0" # logging diff --git a/h3 b/h3 index dccb3cd..3991dca 160000 --- a/h3 +++ b/h3 @@ -1 +1 @@ -Subproject commit dccb3cdae9d5a9d720fae5f774b53f0bd8a16019 +Subproject commit 3991dcaf3801595e49d0bb7fb1649b4cf50292b7 diff --git a/quinn b/quinn index e652b6d..0ae7c60 160000 --- a/quinn +++ b/quinn @@ -1 +1 @@ -Subproject commit e652b6d999f053ffe21eeea247854882ae480281 +Subproject commit 0ae7c60b15637d7343410ba1e5cc3151e3814557 diff --git a/src/globals.rs b/src/globals.rs index b85733b..5150bff 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,5 +1,4 @@ -use crate::certs::CryptoSource; -use crate::{backend::Backends, constants::*}; +use crate::{backend::Backends, certs::CryptoSource, constants::*}; use std::net::SocketAddr; use std::sync::{ atomic::{AtomicUsize, Ordering}, From 13e82035a88c6add4d85f2e0431d5d4a120de08f Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 21 Jul 2023 18:48:40 +0900 Subject: [PATCH 02/10] refactor: initial implementation of separeted lib and bin --- Cargo.toml | 87 +------------------ rpxy-bin/Cargo.toml | 49 +++++++++++ {src => rpxy-bin/src}/cert_file_reader.rs | 10 +-- {src => rpxy-bin/src}/config/mod.rs | 0 {src => rpxy-bin/src}/config/parse.rs | 4 +- {src => rpxy-bin/src}/config/toml.rs | 9 +- rpxy-bin/src/constants.rs | 2 + rpxy-bin/src/error.rs | 1 + rpxy-bin/src/log.rs | 24 +++++ rpxy-bin/src/main.rs | 38 ++++++++ rpxy-lib/Cargo.toml | 77 ++++++++++++++++ {src => rpxy-lib/src}/backend/load_balance.rs | 0 .../src}/backend/load_balance_sticky.rs | 0 {src => rpxy-lib/src}/backend/mod.rs | 1 + .../src}/backend/sticky_cookie.rs | 0 {src => rpxy-lib/src}/backend/upstream.rs | 0 .../src}/backend/upstream_opts.rs | 0 {src => rpxy-lib/src}/certs.rs | 0 {src => rpxy-lib/src}/constants.rs | 4 +- {src => rpxy-lib/src}/error.rs | 0 {src => rpxy-lib/src}/globals.rs | 0 {src => rpxy-lib/src}/handler/handler_main.rs | 0 {src => rpxy-lib/src}/handler/mod.rs | 0 .../src}/handler/utils_headers.rs | 0 .../src}/handler/utils_request.rs | 0 .../src}/handler/utils_synth_response.rs | 0 src/main.rs => rpxy-lib/src/lib.rs | 50 +++-------- {src => rpxy-lib/src}/log.rs | 23 ----- {src => rpxy-lib/src}/proxy/crypto_service.rs | 0 {src => rpxy-lib/src}/proxy/mod.rs | 0 .../src}/proxy/proxy_client_cert.rs | 0 {src => rpxy-lib/src}/proxy/proxy_h3.rs | 0 {src => rpxy-lib/src}/proxy/proxy_main.rs | 0 {src => rpxy-lib/src}/proxy/proxy_tls.rs | 0 {src => rpxy-lib/src}/utils/bytes_name.rs | 3 + {src => rpxy-lib/src}/utils/mod.rs | 0 {src => rpxy-lib/src}/utils/socket_addr.rs | 0 37 files changed, 225 insertions(+), 157 deletions(-) create mode 100644 rpxy-bin/Cargo.toml rename {src => rpxy-bin/src}/cert_file_reader.rs (98%) rename {src => rpxy-bin/src}/config/mod.rs (100%) rename {src => rpxy-bin/src}/config/parse.rs (98%) rename {src => rpxy-bin/src}/config/toml.rs (97%) create mode 100644 rpxy-bin/src/constants.rs create mode 100644 rpxy-bin/src/error.rs create mode 100644 rpxy-bin/src/log.rs create mode 100644 rpxy-bin/src/main.rs create mode 100644 rpxy-lib/Cargo.toml rename {src => rpxy-lib/src}/backend/load_balance.rs (100%) rename {src => rpxy-lib/src}/backend/load_balance_sticky.rs (100%) rename {src => rpxy-lib/src}/backend/mod.rs (98%) rename {src => rpxy-lib/src}/backend/sticky_cookie.rs (100%) rename {src => rpxy-lib/src}/backend/upstream.rs (100%) rename {src => rpxy-lib/src}/backend/upstream_opts.rs (100%) rename {src => rpxy-lib/src}/certs.rs (100%) rename {src => rpxy-lib/src}/constants.rs (90%) rename {src => rpxy-lib/src}/error.rs (100%) rename {src => rpxy-lib/src}/globals.rs (100%) rename {src => rpxy-lib/src}/handler/handler_main.rs (100%) rename {src => rpxy-lib/src}/handler/mod.rs (100%) rename {src => rpxy-lib/src}/handler/utils_headers.rs (100%) rename {src => rpxy-lib/src}/handler/utils_request.rs (100%) rename {src => rpxy-lib/src}/handler/utils_synth_response.rs (100%) rename src/main.rs => rpxy-lib/src/lib.rs (56%) rename {src => rpxy-lib/src}/log.rs (80%) rename {src => rpxy-lib/src}/proxy/crypto_service.rs (100%) rename {src => rpxy-lib/src}/proxy/mod.rs (100%) rename {src => rpxy-lib/src}/proxy/proxy_client_cert.rs (100%) rename {src => rpxy-lib/src}/proxy/proxy_h3.rs (100%) rename {src => rpxy-lib/src}/proxy/proxy_main.rs (100%) rename {src => rpxy-lib/src}/proxy/proxy_tls.rs (100%) rename {src => rpxy-lib/src}/utils/bytes_name.rs (98%) rename {src => rpxy-lib/src}/utils/mod.rs (100%) rename {src => rpxy-lib/src}/utils/socket_addr.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index e950521..64d1414 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,88 +1,7 @@ -[package] -name = "rpxy" -version = "0.3.0" -authors = ["Jun Kurihara"] -homepage = "https://github.com/junkurihara/rust-rpxy" -repository = "https://github.com/junkurihara/rust-rpxy" -license = "MIT" -readme = "README.md" -edition = "2021" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[features] -default = ["http3", "sticky-cookie"] -http3 = ["quinn", "h3", "h3-quinn"] -sticky-cookie = ["base64", "sha2", "chrono"] - -[dependencies] -anyhow = "1.0.72" -clap = { version = "4.3.17", features = ["std", "cargo", "wrap_help"] } -rand = "0.8.5" -toml = { version = "0.7.6", default-features = false, features = ["parse"] } -rustc-hash = "1.1.0" -serde = { version = "1.0.174", default-features = false, features = ["derive"] } -bytes = "1.4.0" -thiserror = "1.0.44" -x509-parser = "0.15.0" -derive_builder = "0.12.0" -futures = { version = "0.3.28", features = ["alloc", "async-await"] } -tokio = { version = "1.29.1", default-features = false, features = [ - "net", - "rt-multi-thread", - "time", - "sync", - "macros", -] } -async-trait = "0.1.72" -hot_reload = "0.1.2" # reloading certs - -# http and tls -hyper = { version = "0.14.27", default-features = false, features = [ - "server", - "http1", - "http2", - "stream", -] } -hyper-rustls = { version = "0.24.1", default-features = false, features = [ - "tokio-runtime", - "webpki-tokio", - "http1", - "http2", -] } -tokio-rustls = { version = "0.24.1", features = ["early-data"] } -rustls-pemfile = "1.0.3" -rustls = { version = "0.21.5", default-features = false } -webpki = "0.22.0" - -# logging -tracing = { version = "0.1.37" } -tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } - -# http/3 -# quinn = { version = "0.9.3", optional = true } -quinn = { path = "./quinn/quinn", optional = true } # Tentative to support rustls-0.21 -h3 = { path = "./h3/h3/", optional = true } -# h3-quinn = { path = "./h3/h3-quinn/", optional = true } -h3-quinn = { path = "./h3-quinn/", optional = true } # Tentative to support rustls-0.21 - -# cookie handling for sticky cookie -chrono = { version = "0.4.26", default-features = false, features = [ - "unstable-locales", - "alloc", - "clock", -], optional = true } -base64 = { version = "0.21.2", optional = true } -sha2 = { version = "0.10.7", default-features = false, optional = true } - - -[target.'cfg(not(target_env = "msvc"))'.dependencies] -tikv-jemallocator = "0.5.0" - - -[dev-dependencies] +[workspace] +members = ["rpxy-bin", "rpxy-lib"] +exclude = ["quinn", "h3-quinn", "h3"] [profile.release] codegen-units = 1 diff --git a/rpxy-bin/Cargo.toml b/rpxy-bin/Cargo.toml new file mode 100644 index 0000000..9f65325 --- /dev/null +++ b/rpxy-bin/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "rpxy" +version = "0.4.0" +authors = ["Jun Kurihara"] +homepage = "https://github.com/junkurihara/rust-rpxy" +repository = "https://github.com/junkurihara/rust-rpxy" +license = "MIT" +readme = "README.md" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] + +[dependencies] +rpxy-lib = { path = "../rpxy-lib/", features = ["http3", "sticky-cookie"] } + +anyhow = "1.0.72" +rustc-hash = "1.1.0" +serde = { version = "1.0.174", default-features = false, features = ["derive"] } +derive_builder = "0.12.0" +tokio = { version = "1.29.1", default-features = false, features = [ + "net", + "rt-multi-thread", + "time", + "sync", + "macros", +] } +async-trait = "0.1.72" + +# config +clap = { version = "4.3.17", features = ["std", "cargo", "wrap_help"] } +toml = { version = "0.7.6", default-features = false, features = ["parse"] } + +# reloading certs +hot_reload = "0.1.2" +rustls-pemfile = "1.0.3" + +# logging +tracing = { version = "0.1.37" } +tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } + + +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemallocator = "0.5.0" + + +[dev-dependencies] diff --git a/src/cert_file_reader.rs b/rpxy-bin/src/cert_file_reader.rs similarity index 98% rename from src/cert_file_reader.rs rename to rpxy-bin/src/cert_file_reader.rs index e25dcf7..ffe3099 100644 --- a/src/cert_file_reader.rs +++ b/rpxy-bin/src/cert_file_reader.rs @@ -1,10 +1,10 @@ -use crate::{ - certs::{CertsAndKeys, CryptoSource}, - log::*, -}; +use crate::log::*; use async_trait::async_trait; use derive_builder::Builder; -use rustls::{Certificate, PrivateKey}; +use rpxy_lib::{ + reexports::{Certificate, PrivateKey}, + CertsAndKeys, CryptoSource, +}; use std::{ fs::File, io::{self, BufReader, Cursor, Read}, diff --git a/src/config/mod.rs b/rpxy-bin/src/config/mod.rs similarity index 100% rename from src/config/mod.rs rename to rpxy-bin/src/config/mod.rs diff --git a/src/config/parse.rs b/rpxy-bin/src/config/parse.rs similarity index 98% rename from src/config/parse.rs rename to rpxy-bin/src/config/parse.rs index 1dc2545..27c8cd8 100644 --- a/src/config/parse.rs +++ b/rpxy-bin/src/config/parse.rs @@ -1,13 +1,11 @@ use super::toml::ConfigToml; use crate::{ - backend::Backends, cert_file_reader::CryptoFileSource, error::{anyhow, ensure}, - globals::*, log::*, - utils::BytesName, }; use clap::Arg; +use rpxy_lib::{Backends, BytesName, Globals, ProxyConfig}; use tokio::runtime::Handle; pub fn build_globals(runtime_handle: Handle) -> std::result::Result, anyhow::Error> { diff --git a/src/config/toml.rs b/rpxy-bin/src/config/toml.rs similarity index 97% rename from src/config/toml.rs rename to rpxy-bin/src/config/toml.rs index f33ea4d..c73f8c1 100644 --- a/src/config/toml.rs +++ b/rpxy-bin/src/config/toml.rs @@ -1,10 +1,11 @@ use crate::{ - backend::{Backend, BackendBuilder, ReverseProxy, Upstream, UpstreamGroup, UpstreamGroupBuilder, UpstreamOption}, cert_file_reader::{CryptoFileSource, CryptoFileSourceBuilder}, constants::*, error::{anyhow, ensure}, - globals::ProxyConfig, - utils::PathNameBytesExp, +}; +use rpxy_lib::{ + reexports::Uri, Backend, BackendBuilder, PathNameBytesExp, ProxyConfig, ReverseProxy, Upstream, UpstreamGroup, + UpstreamGroupBuilder, UpstreamOption, }; use rustc_hash::FxHashMap as HashMap; use serde::Deserialize; @@ -265,7 +266,7 @@ impl TryInto for &UpstreamParams { }; let location = format!("{}://{}", scheme, self.location); Ok(Upstream { - uri: location.parse::().map_err(|e| anyhow!("{}", e))?, + uri: location.parse::().map_err(|e| anyhow!("{}", e))?, }) } } diff --git a/rpxy-bin/src/constants.rs b/rpxy-bin/src/constants.rs new file mode 100644 index 0000000..4181a26 --- /dev/null +++ b/rpxy-bin/src/constants.rs @@ -0,0 +1,2 @@ +pub const LISTEN_ADDRESSES_V4: &[&str] = &["0.0.0.0"]; +pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"]; diff --git a/rpxy-bin/src/error.rs b/rpxy-bin/src/error.rs new file mode 100644 index 0000000..b559bce --- /dev/null +++ b/rpxy-bin/src/error.rs @@ -0,0 +1 @@ +pub use anyhow::{anyhow, bail, ensure, Context}; diff --git a/rpxy-bin/src/log.rs b/rpxy-bin/src/log.rs new file mode 100644 index 0000000..3fcf694 --- /dev/null +++ b/rpxy-bin/src/log.rs @@ -0,0 +1,24 @@ +pub use tracing::{debug, error, info, warn}; + +pub fn init_logger() { + use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + + let format_layer = fmt::layer() + .with_line_number(false) + .with_thread_ids(false) + .with_target(false) + .with_thread_names(true) + .with_target(true) + .with_level(true) + .compact(); + + // This limits the logger to emits only rpxy crate + let level_string = std::env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_| "info".to_string()); + let filter_layer = EnvFilter::new(format!("{}={}", env!("CARGO_PKG_NAME"), level_string)); + // let filter_layer = EnvFilter::from_default_env(); + + tracing_subscriber::registry() + .with(format_layer) + .with(filter_layer) + .init(); +} diff --git a/rpxy-bin/src/main.rs b/rpxy-bin/src/main.rs new file mode 100644 index 0000000..cd74116 --- /dev/null +++ b/rpxy-bin/src/main.rs @@ -0,0 +1,38 @@ +#[cfg(not(target_env = "msvc"))] +use tikv_jemallocator::Jemalloc; + +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; + +mod cert_file_reader; +mod config; +mod constants; +mod error; +mod log; + +use crate::{cert_file_reader::CryptoFileSource, config::build_globals, log::*}; +use rpxy_lib::{entrypoint, Globals}; +use std::sync::Arc; + +fn main() { + init_logger(); + + let mut runtime_builder = tokio::runtime::Builder::new_multi_thread(); + runtime_builder.enable_all(); + runtime_builder.thread_name("rpxy"); + let runtime = runtime_builder.build().unwrap(); + + runtime.block_on(async { + let globals: Globals = match build_globals(runtime.handle().clone()) { + Ok(g) => g, + Err(e) => { + error!("Invalid configuration: {}", e); + std::process::exit(1); + } + }; + + entrypoint(Arc::new(globals)).await.unwrap() + }); + warn!("rpxy exited!"); +} diff --git a/rpxy-lib/Cargo.toml b/rpxy-lib/Cargo.toml new file mode 100644 index 0000000..48cb437 --- /dev/null +++ b/rpxy-lib/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "rpxy-lib" +version = "0.4.0" +authors = ["Jun Kurihara"] +homepage = "https://github.com/junkurihara/rust-rpxy" +repository = "https://github.com/junkurihara/rust-rpxy" +license = "MIT" +readme = "README.md" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["http3", "sticky-cookie"] +http3 = ["quinn", "h3", "h3-quinn"] +sticky-cookie = ["base64", "sha2", "chrono"] + +[dependencies] +rand = "0.8.5" +rustc-hash = "1.1.0" +bytes = "1.4.0" +derive_builder = "0.12.0" +futures = { version = "0.3.28", features = ["alloc", "async-await"] } +tokio = { version = "1.29.1", default-features = false, features = [ + "net", + "rt-multi-thread", + "time", + "sync", + "macros", +] } +async-trait = "0.1.72" +hot_reload = "0.1.2" # reloading certs + +# Error handling +anyhow = "1.0.72" +thiserror = "1.0.44" + +# http and tls +hyper = { version = "0.14.27", default-features = false, features = [ + "server", + "http1", + "http2", + "stream", +] } +hyper-rustls = { version = "0.24.1", default-features = false, features = [ + "tokio-runtime", + "webpki-tokio", + "http1", + "http2", +] } +tokio-rustls = { version = "0.24.1", features = ["early-data"] } +rustls = { version = "0.21.5", default-features = false } +webpki = "0.22.0" +x509-parser = "0.15.0" + +# logging +tracing = { version = "0.1.37" } + +# http/3 +# quinn = { version = "0.9.3", optional = true } +quinn = { path = "../quinn/quinn", optional = true } # Tentative to support rustls-0.21 +h3 = { path = "../h3/h3/", optional = true } +# h3-quinn = { path = "./h3/h3-quinn/", optional = true } +h3-quinn = { path = "../h3-quinn/", optional = true } # Tentative to support rustls-0.21 + +# cookie handling for sticky cookie +chrono = { version = "0.4.26", default-features = false, features = [ + "unstable-locales", + "alloc", + "clock", +], optional = true } +base64 = { version = "0.21.2", optional = true } +sha2 = { version = "0.10.7", default-features = false, optional = true } + + +[dev-dependencies] diff --git a/src/backend/load_balance.rs b/rpxy-lib/src/backend/load_balance.rs similarity index 100% rename from src/backend/load_balance.rs rename to rpxy-lib/src/backend/load_balance.rs diff --git a/src/backend/load_balance_sticky.rs b/rpxy-lib/src/backend/load_balance_sticky.rs similarity index 100% rename from src/backend/load_balance_sticky.rs rename to rpxy-lib/src/backend/load_balance_sticky.rs diff --git a/src/backend/mod.rs b/rpxy-lib/src/backend/mod.rs similarity index 98% rename from src/backend/mod.rs rename to rpxy-lib/src/backend/mod.rs index 524f30b..73c4466 100644 --- a/src/backend/mod.rs +++ b/rpxy-lib/src/backend/mod.rs @@ -67,6 +67,7 @@ impl Backends where T: CryptoSource, { + #[allow(clippy::new_without_default)] pub fn new() -> Self { Backends { apps: HashMap::>::default(), diff --git a/src/backend/sticky_cookie.rs b/rpxy-lib/src/backend/sticky_cookie.rs similarity index 100% rename from src/backend/sticky_cookie.rs rename to rpxy-lib/src/backend/sticky_cookie.rs diff --git a/src/backend/upstream.rs b/rpxy-lib/src/backend/upstream.rs similarity index 100% rename from src/backend/upstream.rs rename to rpxy-lib/src/backend/upstream.rs diff --git a/src/backend/upstream_opts.rs b/rpxy-lib/src/backend/upstream_opts.rs similarity index 100% rename from src/backend/upstream_opts.rs rename to rpxy-lib/src/backend/upstream_opts.rs diff --git a/src/certs.rs b/rpxy-lib/src/certs.rs similarity index 100% rename from src/certs.rs rename to rpxy-lib/src/certs.rs diff --git a/src/constants.rs b/rpxy-lib/src/constants.rs similarity index 90% rename from src/constants.rs rename to rpxy-lib/src/constants.rs index 2ed14d1..72cce78 100644 --- a/src/constants.rs +++ b/rpxy-lib/src/constants.rs @@ -1,5 +1,5 @@ -pub const LISTEN_ADDRESSES_V4: &[&str] = &["0.0.0.0"]; -pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"]; +// pub const LISTEN_ADDRESSES_V4: &[&str] = &["0.0.0.0"]; +// pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"]; // pub const HTTP_LISTEN_PORT: u16 = 8080; // pub const HTTPS_LISTEN_PORT: u16 = 8443; pub const PROXY_TIMEOUT_SEC: u64 = 60; diff --git a/src/error.rs b/rpxy-lib/src/error.rs similarity index 100% rename from src/error.rs rename to rpxy-lib/src/error.rs diff --git a/src/globals.rs b/rpxy-lib/src/globals.rs similarity index 100% rename from src/globals.rs rename to rpxy-lib/src/globals.rs diff --git a/src/handler/handler_main.rs b/rpxy-lib/src/handler/handler_main.rs similarity index 100% rename from src/handler/handler_main.rs rename to rpxy-lib/src/handler/handler_main.rs diff --git a/src/handler/mod.rs b/rpxy-lib/src/handler/mod.rs similarity index 100% rename from src/handler/mod.rs rename to rpxy-lib/src/handler/mod.rs diff --git a/src/handler/utils_headers.rs b/rpxy-lib/src/handler/utils_headers.rs similarity index 100% rename from src/handler/utils_headers.rs rename to rpxy-lib/src/handler/utils_headers.rs diff --git a/src/handler/utils_request.rs b/rpxy-lib/src/handler/utils_request.rs similarity index 100% rename from src/handler/utils_request.rs rename to rpxy-lib/src/handler/utils_request.rs diff --git a/src/handler/utils_synth_response.rs b/rpxy-lib/src/handler/utils_synth_response.rs similarity index 100% rename from src/handler/utils_synth_response.rs rename to rpxy-lib/src/handler/utils_synth_response.rs diff --git a/src/main.rs b/rpxy-lib/src/lib.rs similarity index 56% rename from src/main.rs rename to rpxy-lib/src/lib.rs index 7f8dcfc..7d7764a 100644 --- a/src/main.rs +++ b/rpxy-lib/src/lib.rs @@ -1,15 +1,5 @@ -use certs::CryptoSource; -#[cfg(not(target_env = "msvc"))] -use tikv_jemallocator::Jemalloc; - -#[cfg(not(target_env = "msvc"))] -#[global_allocator] -static GLOBAL: Jemalloc = Jemalloc; - mod backend; -mod cert_file_reader; mod certs; -mod config; mod constants; mod error; mod globals; @@ -18,39 +8,27 @@ mod log; mod proxy; mod utils; -use crate::{ - cert_file_reader::CryptoFileSource, config::build_globals, error::*, globals::*, handler::HttpMessageHandlerBuilder, - log::*, proxy::ProxyBuilder, -}; +use crate::{error::*, handler::HttpMessageHandlerBuilder, log::*, proxy::ProxyBuilder}; use futures::future::select_all; use hyper::Client; // use hyper_trust_dns::TrustDnsResolver; use std::sync::Arc; -fn main() { - init_logger(); - - let mut runtime_builder = tokio::runtime::Builder::new_multi_thread(); - runtime_builder.enable_all(); - runtime_builder.thread_name("rpxy"); - let runtime = runtime_builder.build().unwrap(); - - runtime.block_on(async { - let globals: Globals = match build_globals(runtime.handle().clone()) { - Ok(g) => g, - Err(e) => { - error!("Invalid configuration: {}", e); - std::process::exit(1); - } - }; - - entrypoint(Arc::new(globals)).await.unwrap() - }); - warn!("rpxy exited!"); +pub use crate::{ + backend::{ + Backend, BackendBuilder, Backends, ReverseProxy, Upstream, UpstreamGroup, UpstreamGroupBuilder, UpstreamOption, + }, + certs::{CertsAndKeys, CryptoSource}, + globals::{Globals, ProxyConfig}, // TODO: BackendConfigに変える + utils::{BytesName, PathNameBytesExp}, +}; +pub mod reexports { + pub use hyper::Uri; + pub use rustls::{Certificate, PrivateKey}; } -// entrypoint creates and spawns tasks of proxy services -async fn entrypoint(globals: Arc>) -> Result<()> +/// Entrypoint that creates and spawns tasks of reverse proxy services +pub async fn entrypoint(globals: Arc>) -> Result<()> where T: CryptoSource + Clone + Send + Sync + 'static, { diff --git a/src/log.rs b/rpxy-lib/src/log.rs similarity index 80% rename from src/log.rs rename to rpxy-lib/src/log.rs index d391607..0fb7812 100644 --- a/src/log.rs +++ b/rpxy-lib/src/log.rs @@ -95,26 +95,3 @@ impl MessageLog { ); } } - -pub fn init_logger() { - use tracing_subscriber::{fmt, prelude::*, EnvFilter}; - - let format_layer = fmt::layer() - .with_line_number(false) - .with_thread_ids(false) - .with_target(false) - .with_thread_names(true) - .with_target(true) - .with_level(true) - .compact(); - - // This limits the logger to emits only rpxy crate - let level_string = std::env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_| "info".to_string()); - let filter_layer = EnvFilter::new(format!("{}={}", env!("CARGO_PKG_NAME"), level_string)); - // let filter_layer = EnvFilter::from_default_env(); - - tracing_subscriber::registry() - .with(format_layer) - .with(filter_layer) - .init(); -} diff --git a/src/proxy/crypto_service.rs b/rpxy-lib/src/proxy/crypto_service.rs similarity index 100% rename from src/proxy/crypto_service.rs rename to rpxy-lib/src/proxy/crypto_service.rs diff --git a/src/proxy/mod.rs b/rpxy-lib/src/proxy/mod.rs similarity index 100% rename from src/proxy/mod.rs rename to rpxy-lib/src/proxy/mod.rs diff --git a/src/proxy/proxy_client_cert.rs b/rpxy-lib/src/proxy/proxy_client_cert.rs similarity index 100% rename from src/proxy/proxy_client_cert.rs rename to rpxy-lib/src/proxy/proxy_client_cert.rs diff --git a/src/proxy/proxy_h3.rs b/rpxy-lib/src/proxy/proxy_h3.rs similarity index 100% rename from src/proxy/proxy_h3.rs rename to rpxy-lib/src/proxy/proxy_h3.rs diff --git a/src/proxy/proxy_main.rs b/rpxy-lib/src/proxy/proxy_main.rs similarity index 100% rename from src/proxy/proxy_main.rs rename to rpxy-lib/src/proxy/proxy_main.rs diff --git a/src/proxy/proxy_tls.rs b/rpxy-lib/src/proxy/proxy_tls.rs similarity index 100% rename from src/proxy/proxy_tls.rs rename to rpxy-lib/src/proxy/proxy_tls.rs diff --git a/src/utils/bytes_name.rs b/rpxy-lib/src/utils/bytes_name.rs similarity index 98% rename from src/utils/bytes_name.rs rename to rpxy-lib/src/utils/bytes_name.rs index a093c41..5d2fef5 100644 --- a/src/utils/bytes_name.rs +++ b/rpxy-lib/src/utils/bytes_name.rs @@ -23,6 +23,9 @@ impl PathNameBytesExp { pub fn len(&self) -> usize { self.0.len() } + pub fn is_empty(&self) -> bool { + self.0.len() == 0 + } pub fn get(&self, index: I) -> Option<&I::Output> where I: std::slice::SliceIndex<[u8]>, diff --git a/src/utils/mod.rs b/rpxy-lib/src/utils/mod.rs similarity index 100% rename from src/utils/mod.rs rename to rpxy-lib/src/utils/mod.rs diff --git a/src/utils/socket_addr.rs b/rpxy-lib/src/utils/socket_addr.rs similarity index 100% rename from src/utils/socket_addr.rs rename to rpxy-lib/src/utils/socket_addr.rs From f6c4032f833986383d69069679260e5a09146535 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 21 Jul 2023 22:07:36 +0900 Subject: [PATCH 03/10] refactor: cleanup codes --- rpxy-bin/Cargo.toml | 8 +- rpxy-bin/src/cert_file_reader.rs | 10 +- rpxy-bin/src/config/mod.rs | 2 +- rpxy-bin/src/config/parse.rs | 44 +++----- rpxy-bin/src/config/toml.rs | 116 +++++++++----------- rpxy-bin/src/main.rs | 11 +- rpxy-lib/src/error.rs | 6 ++ rpxy-lib/src/globals.rs | 178 ++++++++++++++++++++++++++++++- rpxy-lib/src/lib.rs | 21 ++-- rpxy-lib/src/proxy/proxy_tls.rs | 8 +- 10 files changed, 280 insertions(+), 124 deletions(-) diff --git a/rpxy-bin/Cargo.toml b/rpxy-bin/Cargo.toml index 9f65325..90094c4 100644 --- a/rpxy-bin/Cargo.toml +++ b/rpxy-bin/Cargo.toml @@ -12,6 +12,8 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +default = ["http3"] +http3 = [] [dependencies] rpxy-lib = { path = "../rpxy-lib/", features = ["http3", "sticky-cookie"] } @@ -28,14 +30,12 @@ tokio = { version = "1.29.1", default-features = false, features = [ "macros", ] } async-trait = "0.1.72" +rustls-pemfile = "1.0.3" # config clap = { version = "4.3.17", features = ["std", "cargo", "wrap_help"] } toml = { version = "0.7.6", default-features = false, features = ["parse"] } - -# reloading certs -hot_reload = "0.1.2" -rustls-pemfile = "1.0.3" +# hot_reload = "0.1.2" # logging tracing = { version = "0.1.37" } diff --git a/rpxy-bin/src/cert_file_reader.rs b/rpxy-bin/src/cert_file_reader.rs index ffe3099..0a6a14f 100644 --- a/rpxy-bin/src/cert_file_reader.rs +++ b/rpxy-bin/src/cert_file_reader.rs @@ -150,8 +150,8 @@ mod tests { use super::*; #[tokio::test] async fn read_server_crt_key_files() { - let tls_cert_path = "example-certs/server.crt"; - let tls_cert_key_path = "example-certs/server.key"; + let tls_cert_path = "../example-certs/server.crt"; + let tls_cert_key_path = "../example-certs/server.key"; let crypto_file_source = CryptoFileSourceBuilder::default() .tls_cert_key_path(tls_cert_key_path) .tls_cert_path(tls_cert_path) @@ -165,9 +165,9 @@ mod tests { #[tokio::test] async fn read_server_crt_key_files_with_client_ca_crt() { - let tls_cert_path = "example-certs/server.crt"; - let tls_cert_key_path = "example-certs/server.key"; - let client_ca_cert_path = Some("example-certs/client.ca.crt".to_string()); + let tls_cert_path = "../example-certs/server.crt"; + let tls_cert_key_path = "../example-certs/server.key"; + let client_ca_cert_path = Some("../example-certs/client.ca.crt".to_string()); let crypto_file_source = CryptoFileSourceBuilder::default() .tls_cert_key_path(tls_cert_key_path) .tls_cert_path(tls_cert_path) diff --git a/rpxy-bin/src/config/mod.rs b/rpxy-bin/src/config/mod.rs index 54b2600..a71ca6e 100644 --- a/rpxy-bin/src/config/mod.rs +++ b/rpxy-bin/src/config/mod.rs @@ -1,4 +1,4 @@ mod parse; mod toml; -pub use parse::build_globals; +pub use parse::build_settings; diff --git a/rpxy-bin/src/config/parse.rs b/rpxy-bin/src/config/parse.rs index 27c8cd8..109dc4b 100644 --- a/rpxy-bin/src/config/parse.rs +++ b/rpxy-bin/src/config/parse.rs @@ -5,10 +5,9 @@ use crate::{ log::*, }; use clap::Arg; -use rpxy_lib::{Backends, BytesName, Globals, ProxyConfig}; -use tokio::runtime::Handle; +use rpxy_lib::{AppConfig, AppConfigList, ProxyConfig}; -pub fn build_globals(runtime_handle: Handle) -> std::result::Result, anyhow::Error> { +pub fn build_settings() -> std::result::Result<(ProxyConfig, AppConfigList), anyhow::Error> { let _ = include_str!("../../Cargo.toml"); let options = clap::command!().arg( Arg::new("config_file") @@ -76,39 +75,22 @@ pub fn build_globals(runtime_handle: Handle) -> std::result::Result>::new(); + + // let mut backends = Backends::new(); for (app_name, app) in apps.0.iter() { let server_name_string = app.server_name.as_ref().ok_or(anyhow!("No server name"))?; - let backend = app.try_into()?; - backends.apps.insert(server_name_string.to_server_name_vec(), backend); + let app_config = app.try_into()?; + app_config_list_inner.push(app_config); info!("Registering application: {} ({})", app_name, server_name_string); } - // default backend application for plaintext http requests - if let Some(d) = config.default_app { - let d_sn: Vec<&str> = backends - .apps - .iter() - .filter(|(_k, v)| v.app_name == d) - .map(|(_, v)| v.server_name.as_ref()) - .collect(); - if !d_sn.is_empty() { - info!( - "Serving plaintext http for requests to unconfigured server_name by app {} (server_name: {}).", - d, d_sn[0] - ); - backends.default_server_name_bytes = Some(d_sn[0].to_server_name_vec()); - } - } - - /////////////////////////////////// - let globals = Globals { - proxy_config, - backends, - request_count: Default::default(), - runtime_handle, + let app_config_list = AppConfigList { + inner: app_config_list_inner, + default_app: config.default_app, // default backend application for plaintext http requests }; - Ok(globals) + Ok((proxy_config, app_config_list)) + // todo!() } diff --git a/rpxy-bin/src/config/toml.rs b/rpxy-bin/src/config/toml.rs index c73f8c1..55faf93 100644 --- a/rpxy-bin/src/config/toml.rs +++ b/rpxy-bin/src/config/toml.rs @@ -3,10 +3,7 @@ use crate::{ constants::*, error::{anyhow, ensure}, }; -use rpxy_lib::{ - reexports::Uri, Backend, BackendBuilder, PathNameBytesExp, ProxyConfig, ReverseProxy, Upstream, UpstreamGroup, - UpstreamGroupBuilder, UpstreamOption, -}; +use rpxy_lib::{reexports::Uri, AppConfig, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri}; use rustc_hash::FxHashMap as HashMap; use serde::Deserialize; use std::{fs, net::SocketAddr}; @@ -148,8 +145,7 @@ impl TryInto for &ConfigToml { if x == 0u64 { proxy_config.h3_max_idle_timeout = None; } else { - proxy_config.h3_max_idle_timeout = - Some(quinn::IdleTimeout::try_from(tokio::time::Duration::from_secs(x)).unwrap()) + proxy_config.h3_max_idle_timeout = Some(tokio::time::Duration::from_secs(x)) } } } @@ -172,101 +168,87 @@ impl ConfigToml { } } -impl TryInto> for &Application { +impl TryInto> for &Application { type Error = anyhow::Error; - fn try_into(self) -> std::result::Result, Self::Error> { + fn try_into(self) -> std::result::Result, Self::Error> { let server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?; - // backend builder - let mut backend_builder = BackendBuilder::default(); // reverse proxy settings - let reverse_proxy = self.try_into()?; + let reverse_proxy_config: Vec = self.try_into()?; - backend_builder - .app_name(server_name_string) - .server_name(server_name_string) - .reverse_proxy(reverse_proxy); - - // TLS settings and build backend instance - let backend = if self.tls.is_none() { - backend_builder.build()? - } else { + // tls settings + let tls_config = if self.tls.is_some() { let tls = self.tls.as_ref().unwrap(); ensure!(tls.tls_cert_key_path.is_some() && tls.tls_cert_path.is_some()); - - let https_redirection = if tls.https_redirection.is_none() { - Some(true) // Default true - } else { - tls.https_redirection - }; - - let crypto_source = CryptoFileSourceBuilder::default() + let inner = CryptoFileSourceBuilder::default() .tls_cert_path(tls.tls_cert_path.as_ref().unwrap()) .tls_cert_key_path(tls.tls_cert_key_path.as_ref().unwrap()) .client_ca_cert_path(&tls.client_ca_cert_path) .build()?; - backend_builder - .https_redirection(https_redirection) - .crypto_source(Some(crypto_source)) - .build()? + let https_redirection = if tls.https_redirection.is_none() { + true // Default true + } else { + tls.https_redirection.unwrap() + }; + + Some(TlsConfig { + inner, + https_redirection, + }) + } else { + None }; - Ok(backend) + + Ok(AppConfig { + server_name: server_name_string.to_owned(), + reverse_proxy: reverse_proxy_config, + tls: tls_config, + }) } } -impl TryInto for &Application { +impl TryInto> for &Application { type Error = anyhow::Error; - fn try_into(self) -> std::result::Result { - let server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?; + fn try_into(self) -> std::result::Result, Self::Error> { + let _server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?; let rp_settings = self.reverse_proxy.as_ref().ok_or(anyhow!("Missing reverse_proxy"))?; - let mut upstream: HashMap = HashMap::default(); + let mut reverse_proxies: Vec = Vec::new(); - rp_settings.iter().for_each(|rpo| { - let upstream_vec: Vec = rpo.upstream.iter().map(|x| x.try_into().unwrap()).collect(); - // let upstream_iter = rpo.upstream.iter().map(|x| x.to_upstream().unwrap()); - // let lb_upstream_num = vec_upstream.len(); - let elem = UpstreamGroupBuilder::default() - .upstream(&upstream_vec) - .path(&rpo.path) - .replace_path(&rpo.replace_path) - .lb(&rpo.load_balance, &upstream_vec, server_name_string, &rpo.path) - .opts(&rpo.upstream_options) - .build() - .unwrap(); + for rpo in rp_settings.iter() { + let upstream_res: Vec> = rpo.upstream.iter().map(|v| v.try_into().ok()).collect(); + if !upstream_res.iter().all(|v| v.is_some()) { + return Err(anyhow!("[{}] Upstream uri is invalid", &_server_name_string)); + } + let upstream = upstream_res.into_iter().map(|v| v.unwrap()).collect(); - upstream.insert(elem.path.clone(), elem); - }); - ensure!( - rp_settings.iter().filter(|rpo| rpo.path.is_none()).count() < 2, - "Multiple default reverse proxy setting" - ); - ensure!( - upstream - .iter() - .all(|(_, elem)| !(elem.opts.contains(&UpstreamOption::ConvertHttpsTo11) - && elem.opts.contains(&UpstreamOption::ConvertHttpsTo2))), - "either one of force_http11 or force_http2 can be enabled" - ); + reverse_proxies.push(ReverseProxyConfig { + path: rpo.path.clone(), + replace_path: rpo.replace_path.clone(), + upstream, + upstream_options: rpo.upstream_options.clone(), + load_balance: rpo.load_balance.clone(), + }) + } - Ok(ReverseProxy { upstream }) + Ok(reverse_proxies) } } -impl TryInto for &UpstreamParams { +impl TryInto for &UpstreamParams { type Error = anyhow::Error; - fn try_into(self) -> std::result::Result { + fn try_into(self) -> std::result::Result { let scheme = match self.tls { Some(true) => "https", _ => "http", }; let location = format!("{}://{}", scheme, self.location); - Ok(Upstream { - uri: location.parse::().map_err(|e| anyhow!("{}", e))?, + Ok(UpstreamUri { + inner: location.parse::().map_err(|e| anyhow!("{}", e))?, }) } } diff --git a/rpxy-bin/src/main.rs b/rpxy-bin/src/main.rs index cd74116..ed0cba5 100644 --- a/rpxy-bin/src/main.rs +++ b/rpxy-bin/src/main.rs @@ -11,9 +11,8 @@ mod constants; mod error; mod log; -use crate::{cert_file_reader::CryptoFileSource, config::build_globals, log::*}; -use rpxy_lib::{entrypoint, Globals}; -use std::sync::Arc; +use crate::{config::build_settings, log::*}; +use rpxy_lib::entrypoint; fn main() { init_logger(); @@ -24,7 +23,7 @@ fn main() { let runtime = runtime_builder.build().unwrap(); runtime.block_on(async { - let globals: Globals = match build_globals(runtime.handle().clone()) { + let (proxy_conf, app_conf) = match build_settings() { Ok(g) => g, Err(e) => { error!("Invalid configuration: {}", e); @@ -32,7 +31,9 @@ fn main() { } }; - entrypoint(Arc::new(globals)).await.unwrap() + entrypoint(proxy_conf, app_conf, runtime.handle().clone()) + .await + .unwrap() }); warn!("rpxy exited!"); } diff --git a/rpxy-lib/src/error.rs b/rpxy-lib/src/error.rs index 187c993..3407e8a 100644 --- a/rpxy-lib/src/error.rs +++ b/rpxy-lib/src/error.rs @@ -10,9 +10,15 @@ pub enum RpxyError { #[error("Proxy build error")] ProxyBuild(#[from] crate::proxy::ProxyBuilderError), + #[error("Backend build error")] + BackendBuild(#[from] crate::backend::BackendBuilderError), + #[error("MessageHandler build error")] HandlerBuild(#[from] crate::handler::HttpMessageHandlerBuilderError), + #[error("Config builder error: {0}")] + ConfigBuild(&'static str), + #[error("Http Message Handler Error: {0}")] Handler(&'static str), diff --git a/rpxy-lib/src/globals.rs b/rpxy-lib/src/globals.rs index 5150bff..f97bc3b 100644 --- a/rpxy-lib/src/globals.rs +++ b/rpxy-lib/src/globals.rs @@ -1,4 +1,14 @@ -use crate::{backend::Backends, certs::CryptoSource, constants::*}; +use crate::{ + backend::{ + Backend, BackendBuilder, Backends, ReverseProxy, Upstream, UpstreamGroup, UpstreamGroupBuilder, UpstreamOption, + }, + certs::CryptoSource, + constants::*, + error::RpxyError, + log::*, + utils::{BytesName, PathNameBytesExp}, +}; +use rustc_hash::FxHashMap as HashMap; use std::net::SocketAddr; use std::sync::{ atomic::{AtomicUsize, Ordering}, @@ -26,6 +36,7 @@ where } /// Configuration parameters for proxy transport and request handlers +#[derive(PartialEq, Eq)] pub struct ProxyConfig { pub listen_sockets: Vec, // when instantiate server pub http_port: Option, // when instantiate server @@ -54,7 +65,7 @@ pub struct ProxyConfig { #[cfg(feature = "http3")] pub h3_max_concurrent_connections: u32, #[cfg(feature = "http3")] - pub h3_max_idle_timeout: Option, + pub h3_max_idle_timeout: Option, } impl Default for ProxyConfig { @@ -87,11 +98,172 @@ impl Default for ProxyConfig { #[cfg(feature = "http3")] h3_max_concurrent_unistream: H3::MAX_CONCURRENT_UNISTREAM.into(), #[cfg(feature = "http3")] - h3_max_idle_timeout: Some(quinn::IdleTimeout::try_from(Duration::from_secs(H3::MAX_IDLE_TIMEOUT)).unwrap()), + h3_max_idle_timeout: Some(Duration::from_secs(H3::MAX_IDLE_TIMEOUT)), } } } +/// Configuration parameters for backend applications +#[derive(PartialEq, Eq)] +pub struct AppConfigList +where + T: CryptoSource, +{ + pub inner: Vec>, + pub default_app: Option, +} +impl TryInto> for AppConfigList +where + T: CryptoSource + Clone, +{ + type Error = RpxyError; + + fn try_into(self) -> Result, Self::Error> { + let mut backends = Backends::new(); + for app_config in self.inner.iter() { + let backend = app_config.try_into()?; + backends + .apps + .insert(app_config.server_name.clone().to_server_name_vec(), backend); + info!("Registering application: ({})", &app_config.server_name); + } + + // default backend application for plaintext http requests + if let Some(d) = self.default_app { + let d_sn: Vec<&str> = backends + .apps + .iter() + .filter(|(_k, v)| v.app_name == d) + .map(|(_, v)| v.server_name.as_ref()) + .collect(); + if !d_sn.is_empty() { + info!( + "Serving plaintext http for requests to unconfigured server_name by app {} (server_name: {}).", + d, d_sn[0] + ); + backends.default_server_name_bytes = Some(d_sn[0].to_server_name_vec()); + } + } + Ok(backends) + } +} + +/// Configuration parameters for single backend application +#[derive(PartialEq, Eq)] +pub struct AppConfig +where + T: CryptoSource, +{ + pub server_name: String, + pub reverse_proxy: Vec, + pub tls: Option>, +} +impl TryInto> for &AppConfig +where + T: CryptoSource + Clone, +{ + type Error = RpxyError; + + fn try_into(self) -> Result, Self::Error> { + // backend builder + let mut backend_builder = BackendBuilder::default(); + // reverse proxy settings + let reverse_proxy = self.try_into()?; + + backend_builder + .app_name(self.server_name.clone()) + .server_name(self.server_name.clone()) + .reverse_proxy(reverse_proxy); + + // TLS settings and build backend instance + let backend = if self.tls.is_none() { + backend_builder.build().map_err(RpxyError::BackendBuild)? + } else { + let tls = self.tls.as_ref().unwrap(); + + backend_builder + .https_redirection(Some(tls.https_redirection)) + .crypto_source(Some(tls.inner.clone())) + .build()? + }; + Ok(backend) + } +} +impl TryInto for &AppConfig +where + T: CryptoSource + Clone, +{ + type Error = RpxyError; + + fn try_into(self) -> Result { + let mut upstream: HashMap = HashMap::default(); + + self.reverse_proxy.iter().for_each(|rpo| { + let upstream_vec: Vec = rpo.upstream.iter().map(|x| x.try_into().unwrap()).collect(); + // let upstream_iter = rpo.upstream.iter().map(|x| x.to_upstream().unwrap()); + // let lb_upstream_num = vec_upstream.len(); + let elem = UpstreamGroupBuilder::default() + .upstream(&upstream_vec) + .path(&rpo.path) + .replace_path(&rpo.replace_path) + .lb(&rpo.load_balance, &upstream_vec, &self.server_name, &rpo.path) + .opts(&rpo.upstream_options) + .build() + .unwrap(); + + upstream.insert(elem.path.clone(), elem); + }); + if self.reverse_proxy.iter().filter(|rpo| rpo.path.is_none()).count() >= 2 { + error!("Multiple default reverse proxy setting"); + return Err(RpxyError::ConfigBuild("Invalid reverse proxy setting")); + } + + if !(upstream.iter().all(|(_, elem)| { + !(elem.opts.contains(&UpstreamOption::ConvertHttpsTo11) && elem.opts.contains(&UpstreamOption::ConvertHttpsTo2)) + })) { + error!("Either one of force_http11 or force_http2 can be enabled"); + return Err(RpxyError::ConfigBuild("Invalid upstream option setting")); + } + + Ok(ReverseProxy { upstream }) + } +} + +/// Configuration parameters for single reverse proxy corresponding to the path +#[derive(PartialEq, Eq)] +pub struct ReverseProxyConfig { + pub path: Option, + pub replace_path: Option, + pub upstream: Vec, + pub upstream_options: Option>, + pub load_balance: Option, +} + +/// Configuration parameters for single upstream destination from a reverse proxy +#[derive(PartialEq, Eq)] +pub struct UpstreamUri { + pub inner: hyper::Uri, +} +impl TryInto for &UpstreamUri { + type Error = anyhow::Error; + + fn try_into(self) -> std::result::Result { + Ok(Upstream { + uri: self.inner.clone(), + }) + } +} + +/// Configuration parameters on TLS for a single backend application +#[derive(PartialEq, Eq)] +pub struct TlsConfig +where + T: CryptoSource, +{ + pub inner: T, + pub https_redirection: bool, +} + #[derive(Debug, Clone, Default)] /// Counter for serving requests pub struct RequestCount(Arc); diff --git a/rpxy-lib/src/lib.rs b/rpxy-lib/src/lib.rs index 7d7764a..7820db6 100644 --- a/rpxy-lib/src/lib.rs +++ b/rpxy-lib/src/lib.rs @@ -8,19 +8,15 @@ mod log; mod proxy; mod utils; -use crate::{error::*, handler::HttpMessageHandlerBuilder, log::*, proxy::ProxyBuilder}; +use crate::{error::*, globals::Globals, handler::HttpMessageHandlerBuilder, log::*, proxy::ProxyBuilder}; use futures::future::select_all; use hyper::Client; // use hyper_trust_dns::TrustDnsResolver; use std::sync::Arc; pub use crate::{ - backend::{ - Backend, BackendBuilder, Backends, ReverseProxy, Upstream, UpstreamGroup, UpstreamGroupBuilder, UpstreamOption, - }, certs::{CertsAndKeys, CryptoSource}, - globals::{Globals, ProxyConfig}, // TODO: BackendConfigに変える - utils::{BytesName, PathNameBytesExp}, + globals::{AppConfig, AppConfigList, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri}, }; pub mod reexports { pub use hyper::Uri; @@ -28,10 +24,21 @@ pub mod reexports { } /// Entrypoint that creates and spawns tasks of reverse proxy services -pub async fn entrypoint(globals: Arc>) -> Result<()> +pub async fn entrypoint( + proxy_config: ProxyConfig, + app_config_list: AppConfigList, + runtime_handle: tokio::runtime::Handle, +) -> Result<()> where T: CryptoSource + Clone + Send + Sync + 'static, { + // build global + let globals = Arc::new(Globals { + proxy_config, + backends: app_config_list.try_into()?, + request_count: Default::default(), + runtime_handle, + }); // let connector = TrustDnsResolver::default().into_rustls_webpki_https_connector(); let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_webpki_roots() diff --git a/rpxy-lib/src/proxy/proxy_tls.rs b/rpxy-lib/src/proxy/proxy_tls.rs index 5e846f0..6e0a6b6 100644 --- a/rpxy-lib/src/proxy/proxy_tls.rs +++ b/rpxy-lib/src/proxy/proxy_tls.rs @@ -119,7 +119,13 @@ where transport_config_quic .max_concurrent_bidi_streams(self.globals.proxy_config.h3_max_concurrent_bidistream) .max_concurrent_uni_streams(self.globals.proxy_config.h3_max_concurrent_unistream) - .max_idle_timeout(self.globals.proxy_config.h3_max_idle_timeout); + .max_idle_timeout( + self + .globals + .proxy_config + .h3_max_idle_timeout + .map(|v| quinn::IdleTimeout::try_from(v).unwrap()), + ); let mut server_config_h3 = QuicServerConfig::with_crypto(Arc::new(rustls_server_config)); server_config_h3.transport = Arc::new(transport_config_quic); From 5e76c2b055c64120cf30b9834bbd0933d8c40ebe Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 21 Jul 2023 22:42:47 +0900 Subject: [PATCH 04/10] fix: bug of default_app for cleartext http --- rpxy-bin/src/config/parse.rs | 8 ++++---- rpxy-bin/src/config/toml.rs | 7 +++---- rpxy-lib/src/globals.rs | 8 ++++++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/rpxy-bin/src/config/parse.rs b/rpxy-bin/src/config/parse.rs index 109dc4b..09b24b3 100644 --- a/rpxy-bin/src/config/parse.rs +++ b/rpxy-bin/src/config/parse.rs @@ -80,15 +80,15 @@ pub fn build_settings() -> std::result::Result<(ProxyConfig, AppConfigList> for &Application { - type Error = anyhow::Error; - - fn try_into(self) -> std::result::Result, Self::Error> { +impl Application { + pub fn build_app_config(&self, app_name: &str) -> std::result::Result, anyhow::Error> { let server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?; // reverse proxy settings @@ -202,6 +200,7 @@ impl TryInto> for &Application { }; Ok(AppConfig { + app_name: app_name.to_owned(), server_name: server_name_string.to_owned(), reverse_proxy: reverse_proxy_config, tls: tls_config, diff --git a/rpxy-lib/src/globals.rs b/rpxy-lib/src/globals.rs index f97bc3b..6614b5c 100644 --- a/rpxy-lib/src/globals.rs +++ b/rpxy-lib/src/globals.rs @@ -125,7 +125,10 @@ where backends .apps .insert(app_config.server_name.clone().to_server_name_vec(), backend); - info!("Registering application: ({})", &app_config.server_name); + info!( + "Registering application {} ({})", + &app_config.server_name, &app_config.app_name + ); } // default backend application for plaintext http requests @@ -154,6 +157,7 @@ pub struct AppConfig where T: CryptoSource, { + pub app_name: String, pub server_name: String, pub reverse_proxy: Vec, pub tls: Option>, @@ -171,7 +175,7 @@ where let reverse_proxy = self.try_into()?; backend_builder - .app_name(self.server_name.clone()) + .app_name(self.app_name.clone()) .server_name(self.server_name.clone()) .reverse_proxy(reverse_proxy); From 58e22d33afe04115e1594915aca8c80ac8bf01f5 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sun, 23 Jul 2023 01:42:39 +0900 Subject: [PATCH 05/10] feat: hot-reloading of config file --- config-example.toml | 3 ++ rpxy-bin/Cargo.toml | 2 +- rpxy-bin/src/config/mod.rs | 7 ++- rpxy-bin/src/config/parse.rs | 25 ++++++----- rpxy-bin/src/config/service.rs | 24 ++++++++++ rpxy-bin/src/config/toml.rs | 22 +++++---- rpxy-bin/src/constants.rs | 1 + rpxy-bin/src/main.rs | 76 +++++++++++++++++++++++++++----- rpxy-lib/Cargo.toml | 4 +- rpxy-lib/src/constants.rs | 1 + rpxy-lib/src/globals.rs | 14 +++--- rpxy-lib/src/lib.rs | 16 +++---- rpxy-lib/src/proxy/mod.rs | 1 + rpxy-lib/src/proxy/proxy_main.rs | 7 +-- rpxy-lib/src/proxy/proxy_tls.rs | 27 ++++++++---- rpxy-lib/src/proxy/socket.rs | 41 +++++++++++++++++ 16 files changed, 213 insertions(+), 58 deletions(-) create mode 100644 rpxy-bin/src/config/service.rs create mode 100644 rpxy-lib/src/proxy/socket.rs diff --git a/config-example.toml b/config-example.toml index 0382393..605067c 100644 --- a/config-example.toml +++ b/config-example.toml @@ -10,6 +10,9 @@ listen_port = 8080 listen_port_tls = 8443 +# Optional for h2 and http1.1 +tcp_listen_backlog = 1024 + # Optional for h2 and http1.1 max_concurrent_streams = 100 diff --git a/rpxy-bin/Cargo.toml b/rpxy-bin/Cargo.toml index 90094c4..fb6d4aa 100644 --- a/rpxy-bin/Cargo.toml +++ b/rpxy-bin/Cargo.toml @@ -35,7 +35,7 @@ rustls-pemfile = "1.0.3" # config clap = { version = "4.3.17", features = ["std", "cargo", "wrap_help"] } toml = { version = "0.7.6", default-features = false, features = ["parse"] } -# hot_reload = "0.1.2" +hot_reload = "0.1.4" # logging tracing = { version = "0.1.37" } diff --git a/rpxy-bin/src/config/mod.rs b/rpxy-bin/src/config/mod.rs index a71ca6e..09ec2b9 100644 --- a/rpxy-bin/src/config/mod.rs +++ b/rpxy-bin/src/config/mod.rs @@ -1,4 +1,9 @@ mod parse; +mod service; mod toml; -pub use parse::build_settings; +pub use { + self::toml::ConfigToml, + parse::{build_settings, parse_opts}, + service::ConfigTomlReloader, +}; diff --git a/rpxy-bin/src/config/parse.rs b/rpxy-bin/src/config/parse.rs index 09b24b3..56c6f2c 100644 --- a/rpxy-bin/src/config/parse.rs +++ b/rpxy-bin/src/config/parse.rs @@ -7,28 +7,30 @@ use crate::{ use clap::Arg; use rpxy_lib::{AppConfig, AppConfigList, ProxyConfig}; -pub fn build_settings() -> std::result::Result<(ProxyConfig, AppConfigList), anyhow::Error> { +pub fn parse_opts() -> Result { let _ = include_str!("../../Cargo.toml"); let options = clap::command!().arg( Arg::new("config_file") .long("config") .short('c') .value_name("FILE") - .help("Configuration file path like \"./config.toml\""), + .required(true) + .help("Configuration file path like ./config.toml"), ); let matches = options.get_matches(); /////////////////////////////////// - let config = if let Some(config_file_path) = matches.get_one::("config_file") { - ConfigToml::new(config_file_path)? - } else { - // Default config Toml - ConfigToml::default() - }; + let config_file_path = matches.get_one::("config_file").unwrap(); + Ok(config_file_path.to_string()) +} + +pub fn build_settings( + config: &ConfigToml, +) -> std::result::Result<(ProxyConfig, AppConfigList), anyhow::Error> { /////////////////////////////////// // build proxy config - let proxy_config: ProxyConfig = (&config).try_into()?; + let proxy_config: ProxyConfig = config.try_into()?; // For loggings if proxy_config.listen_sockets.iter().any(|addr| addr.is_ipv6()) { info!("Listen both IPv4 and IPv6") @@ -50,7 +52,7 @@ pub fn build_settings() -> std::result::Result<(ProxyConfig, AppConfigList std::result::Result<(ProxyConfig, AppConfigList for ConfigTomlReloader { + type Source = String; + async fn new(source: &Self::Source) -> Result> { + Ok(Self { + config_path: source.clone(), + }) + } + + async fn reload(&self) -> Result, ReloaderError> { + let conf = ConfigToml::new(&self.config_path) + .map_err(|_e| ReloaderError::::Reload("Failed to reload config toml"))?; + Ok(Some(conf)) + } +} diff --git a/rpxy-bin/src/config/toml.rs b/rpxy-bin/src/config/toml.rs index 6868d21..84260c0 100644 --- a/rpxy-bin/src/config/toml.rs +++ b/rpxy-bin/src/config/toml.rs @@ -8,11 +8,12 @@ use rustc_hash::FxHashMap as HashMap; use serde::Deserialize; use std::{fs, net::SocketAddr}; -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct ConfigToml { pub listen_port: Option, pub listen_port_tls: Option, pub listen_ipv6: Option, + pub tcp_listen_backlog: Option, pub max_concurrent_streams: Option, pub max_clients: Option, pub apps: Option, @@ -21,7 +22,7 @@ pub struct ConfigToml { } #[cfg(feature = "http3")] -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct Http3Option { pub alt_svc_max_age: Option, pub request_max_body_size: Option, @@ -31,24 +32,24 @@ pub struct Http3Option { pub max_idle_timeout: Option, } -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct Experimental { #[cfg(feature = "http3")] pub h3: Option, pub ignore_sni_consistency: Option, } -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct Apps(pub HashMap); -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct Application { pub server_name: Option, pub reverse_proxy: Option>, pub tls: Option, } -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct TlsOption { pub tls_cert_path: Option, pub tls_cert_key_path: Option, @@ -56,7 +57,7 @@ pub struct TlsOption { pub client_ca_cert_path: Option, } -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct ReverseProxyOption { pub path: Option, pub replace_path: Option, @@ -65,7 +66,7 @@ pub struct ReverseProxyOption { pub load_balance: Option, } -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct UpstreamParams { pub location: String, pub tls: Option, @@ -112,6 +113,11 @@ impl TryInto for &ConfigToml { }) .collect(); + // tcp backlog + if let Some(backlog) = self.tcp_listen_backlog { + proxy_config.tcp_listen_backlog = backlog; + } + // max values if let Some(c) = self.max_clients { proxy_config.max_clients = c as usize; diff --git a/rpxy-bin/src/constants.rs b/rpxy-bin/src/constants.rs index 4181a26..323615f 100644 --- a/rpxy-bin/src/constants.rs +++ b/rpxy-bin/src/constants.rs @@ -1,2 +1,3 @@ pub const LISTEN_ADDRESSES_V4: &[&str] = &["0.0.0.0"]; pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"]; +pub const CONFIG_WATCH_DELAY_SECS: u32 = 20; diff --git a/rpxy-bin/src/main.rs b/rpxy-bin/src/main.rs index ed0cba5..8466372 100644 --- a/rpxy-bin/src/main.rs +++ b/rpxy-bin/src/main.rs @@ -11,7 +11,12 @@ mod constants; mod error; mod log; -use crate::{config::build_settings, log::*}; +use crate::{ + config::{build_settings, parse_opts, ConfigToml, ConfigTomlReloader}, + constants::CONFIG_WATCH_DELAY_SECS, + log::*, +}; +use hot_reload::{ReloaderReceiver, ReloaderService}; use rpxy_lib::entrypoint; fn main() { @@ -23,17 +28,68 @@ fn main() { let runtime = runtime_builder.build().unwrap(); runtime.block_on(async { - let (proxy_conf, app_conf) = match build_settings() { - Ok(g) => g, - Err(e) => { - error!("Invalid configuration: {}", e); + // Initially load config + let Ok(config_path) = parse_opts() else { + error!("Invalid toml file"); std::process::exit(1); - } }; + let (config_service, config_rx) = + ReloaderService::::new(&config_path, CONFIG_WATCH_DELAY_SECS, false) + .await + .unwrap(); - entrypoint(proxy_conf, app_conf, runtime.handle().clone()) - .await - .unwrap() + tokio::select! { + _ = config_service.start() => { + error!("config reloader service exited"); + } + _ = rpxy_service(config_rx, runtime.handle().clone()) => { + error!("rpxy service existed"); + } + } }); - warn!("rpxy exited!"); +} + +async fn rpxy_service( + mut config_rx: ReloaderReceiver, + runtime_handle: tokio::runtime::Handle, +) -> Result<(), anyhow::Error> { + // Initial loading + config_rx.changed().await?; + let config_toml = config_rx.borrow().clone().unwrap(); + let (mut proxy_conf, mut app_conf) = match build_settings(&config_toml) { + Ok(v) => v, + Err(e) => { + error!("Invalid configuration: {e}"); + return Err(anyhow::anyhow!(e)); + } + }; + + // Continuous monitoring + loop { + tokio::select! { + _ = entrypoint(&proxy_conf, &app_conf, &runtime_handle) => { + error!("rpxy entrypoint exited"); + break; + } + _ = config_rx.changed() => { + if config_rx.borrow().is_none() { + error!("Something wrong in config reloader receiver"); + break; + } + let config_toml = config_rx.borrow().clone().unwrap(); + match build_settings(&config_toml) { + Ok((p, a)) => { + (proxy_conf, app_conf) = (p, a) + }, + Err(e) => { + error!("Invalid configuration. Configuration does not updated: {e}"); + continue; + } + }; + info!("Configuration updated. Force to re-bind TCP/UDP sockets"); + } + else => break + } + } + Ok(()) } diff --git a/rpxy-lib/Cargo.toml b/rpxy-lib/Cargo.toml index 48cb437..e36bae6 100644 --- a/rpxy-lib/Cargo.toml +++ b/rpxy-lib/Cargo.toml @@ -30,7 +30,7 @@ tokio = { version = "1.29.1", default-features = false, features = [ "macros", ] } async-trait = "0.1.72" -hot_reload = "0.1.2" # reloading certs +hot_reload = "0.1.4" # reloading certs # Error handling anyhow = "1.0.72" @@ -63,6 +63,8 @@ quinn = { path = "../quinn/quinn", optional = true } # Tentative to support rust h3 = { path = "../h3/h3/", optional = true } # h3-quinn = { path = "./h3/h3-quinn/", optional = true } h3-quinn = { path = "../h3-quinn/", optional = true } # Tentative to support rustls-0.21 +# for UDP socket wit SO_REUSEADDR +socket2 = { version = "0.5.3", features = ["all"] } # cookie handling for sticky cookie chrono = { version = "0.4.26", default-features = false, features = [ diff --git a/rpxy-lib/src/constants.rs b/rpxy-lib/src/constants.rs index 72cce78..9d7fb5e 100644 --- a/rpxy-lib/src/constants.rs +++ b/rpxy-lib/src/constants.rs @@ -1,5 +1,6 @@ // pub const LISTEN_ADDRESSES_V4: &[&str] = &["0.0.0.0"]; // pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"]; +pub const TCP_LISTEN_BACKLOG: u32 = 1024; // pub const HTTP_LISTEN_PORT: u16 = 8080; // pub const HTTPS_LISTEN_PORT: u16 = 8443; pub const PROXY_TIMEOUT_SEC: u64 = 60; diff --git a/rpxy-lib/src/globals.rs b/rpxy-lib/src/globals.rs index 6614b5c..44808dd 100644 --- a/rpxy-lib/src/globals.rs +++ b/rpxy-lib/src/globals.rs @@ -36,11 +36,12 @@ where } /// Configuration parameters for proxy transport and request handlers -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub struct ProxyConfig { pub listen_sockets: Vec, // when instantiate server pub http_port: Option, // when instantiate server pub https_port: Option, // when instantiate server + pub tcp_listen_backlog: u32, // when instantiate server pub proxy_timeout: Duration, // when serving requests at Proxy pub upstream_timeout: Duration, // when serving requests at Handler @@ -74,6 +75,7 @@ impl Default for ProxyConfig { listen_sockets: Vec::new(), http_port: None, https_port: None, + tcp_listen_backlog: TCP_LISTEN_BACKLOG, // TODO: Reconsider each timeout values proxy_timeout: Duration::from_secs(PROXY_TIMEOUT_SEC), @@ -104,7 +106,7 @@ impl Default for ProxyConfig { } /// Configuration parameters for backend applications -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub struct AppConfigList where T: CryptoSource, @@ -152,7 +154,7 @@ where } /// Configuration parameters for single backend application -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub struct AppConfig where T: CryptoSource, @@ -234,7 +236,7 @@ where } /// Configuration parameters for single reverse proxy corresponding to the path -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub struct ReverseProxyConfig { pub path: Option, pub replace_path: Option, @@ -244,7 +246,7 @@ pub struct ReverseProxyConfig { } /// Configuration parameters for single upstream destination from a reverse proxy -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub struct UpstreamUri { pub inner: hyper::Uri, } @@ -259,7 +261,7 @@ impl TryInto for &UpstreamUri { } /// Configuration parameters on TLS for a single backend application -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub struct TlsConfig where T: CryptoSource, diff --git a/rpxy-lib/src/lib.rs b/rpxy-lib/src/lib.rs index 7820db6..4fd64ce 100644 --- a/rpxy-lib/src/lib.rs +++ b/rpxy-lib/src/lib.rs @@ -25,19 +25,19 @@ pub mod reexports { /// Entrypoint that creates and spawns tasks of reverse proxy services pub async fn entrypoint( - proxy_config: ProxyConfig, - app_config_list: AppConfigList, - runtime_handle: tokio::runtime::Handle, + proxy_config: &ProxyConfig, + app_config_list: &AppConfigList, + runtime_handle: &tokio::runtime::Handle, ) -> Result<()> where T: CryptoSource + Clone + Send + Sync + 'static, { // build global let globals = Arc::new(Globals { - proxy_config, - backends: app_config_list.try_into()?, + proxy_config: proxy_config.clone(), + backends: app_config_list.clone().try_into()?, request_count: Default::default(), - runtime_handle, + runtime_handle: runtime_handle.clone(), }); // let connector = TrustDnsResolver::default().into_rustls_webpki_https_connector(); let connector = hyper_rustls::HttpsConnectorBuilder::new() @@ -71,8 +71,8 @@ where })); // wait for all future - if let (Ok(_), _, _) = futures.await { - error!("Some proxy services are down"); + if let (Ok(Err(e)), _, _) = futures.await { + error!("Some proxy services are down: {:?}", e); }; Ok(()) diff --git a/rpxy-lib/src/proxy/mod.rs b/rpxy-lib/src/proxy/mod.rs index 73a4002..749239c 100644 --- a/rpxy-lib/src/proxy/mod.rs +++ b/rpxy-lib/src/proxy/mod.rs @@ -4,5 +4,6 @@ mod proxy_client_cert; mod proxy_h3; mod proxy_main; mod proxy_tls; +mod socket; pub use proxy_main::{Proxy, ProxyBuilder, ProxyBuilderError}; diff --git a/rpxy-lib/src/proxy/proxy_main.rs b/rpxy-lib/src/proxy/proxy_main.rs index e5a02a5..166f048 100644 --- a/rpxy-lib/src/proxy/proxy_main.rs +++ b/rpxy-lib/src/proxy/proxy_main.rs @@ -1,4 +1,4 @@ -// use super::proxy_handler::handle_request; +use super::socket::bind_tcp_socket; use crate::{ certs::CryptoSource, error::*, globals::Globals, handler::HttpMessageHandler, log::*, utils::ServerNameBytesExp, }; @@ -7,7 +7,6 @@ use hyper::{client::connect::Connect, server::conn::Http, service::service_fn, B use std::{net::SocketAddr, sync::Arc}; use tokio::{ io::{AsyncRead, AsyncWrite}, - net::TcpListener, runtime::Handle, time::{timeout, Duration}, }; @@ -94,7 +93,9 @@ where async fn start_without_tls(self, server: Http) -> Result<()> { let listener_service = async { - let tcp_listener = TcpListener::bind(&self.listening_on).await?; + let tcp_socket = bind_tcp_socket(&self.listening_on)?; + let tcp_listener = tcp_socket.listen(self.globals.proxy_config.tcp_listen_backlog)?; + // let tcp_listener = TcpListener::bind(&self.listening_on).await?; info!("Start TCP proxy serving with HTTP request for configured host names"); while let Ok((stream, _client_addr)) = tcp_listener.accept().await { self.clone().client_serve(stream, server.clone(), _client_addr, None); diff --git a/rpxy-lib/src/proxy/proxy_tls.rs b/rpxy-lib/src/proxy/proxy_tls.rs index 6e0a6b6..5512eff 100644 --- a/rpxy-lib/src/proxy/proxy_tls.rs +++ b/rpxy-lib/src/proxy/proxy_tls.rs @@ -1,6 +1,9 @@ +#[cfg(feature = "http3")] +use super::socket::bind_udp_socket; use super::{ crypto_service::{CryptoReloader, ServerCrypto, ServerCryptoBase, SniServerCryptoMap}, proxy_main::{LocalExecutor, Proxy}, + socket::bind_tcp_socket, }; use crate::{certs::CryptoSource, constants::*, error::*, log::*, utils::BytesName}; use hot_reload::{ReloaderReceiver, ReloaderService}; @@ -10,10 +13,7 @@ use quinn::{crypto::rustls::HandshakeData, Endpoint, ServerConfig as QuicServerC #[cfg(feature = "http3")] use rustls::ServerConfig; use std::sync::Arc; -use tokio::{ - net::TcpListener, - time::{timeout, Duration}, -}; +use tokio::time::{timeout, Duration}; impl Proxy where @@ -26,7 +26,8 @@ where server: Http, mut server_crypto_rx: ReloaderReceiver, ) -> Result<()> { - let tcp_listener = TcpListener::bind(&self.listening_on).await?; + let tcp_socket = bind_tcp_socket(&self.listening_on)?; + let tcp_listener = tcp_socket.listen(self.globals.proxy_config.tcp_listen_backlog)?; info!("Start TCP proxy serving with HTTPS request for configured host names"); let mut server_crypto_map: Option> = None; @@ -130,7 +131,17 @@ where let mut server_config_h3 = QuicServerConfig::with_crypto(Arc::new(rustls_server_config)); server_config_h3.transport = Arc::new(transport_config_quic); server_config_h3.concurrent_connections(self.globals.proxy_config.h3_max_concurrent_connections); - let endpoint = Endpoint::server(server_config_h3, self.listening_on)?; + + // To reuse address + let udp_socket = bind_udp_socket(&self.listening_on)?; + let runtime = quinn::default_runtime() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No async runtime found"))?; + let endpoint = Endpoint::new( + quinn::EndpointConfig::default(), + Some(server_config_h3), + udp_socket, + runtime, + )?; let mut server_crypto: Option> = None; loop { @@ -199,10 +210,10 @@ where #[cfg(not(feature = "http3"))] { tokio::select! { - _= self.cert_service(tx) => { + _= cert_reloader_service.start() => { error!("Cert service for TLS exited"); }, - _ = self.listener_service(server, rx) => { + _ = self.listener_service(server, cert_reloader_rx) => { error!("TCP proxy service for TLS exited"); }, else => { diff --git a/rpxy-lib/src/proxy/socket.rs b/rpxy-lib/src/proxy/socket.rs new file mode 100644 index 0000000..8858732 --- /dev/null +++ b/rpxy-lib/src/proxy/socket.rs @@ -0,0 +1,41 @@ +use crate::{error::*, log::*}; +#[cfg(feature = "http3")] +use socket2::{Domain, Protocol, Socket, Type}; +use std::net::SocketAddr; +#[cfg(feature = "http3")] +use std::net::UdpSocket; +use tokio::net::TcpSocket; + +pub(super) fn bind_tcp_socket(listening_on: &SocketAddr) -> Result { + let tcp_socket = if listening_on.is_ipv6() { + TcpSocket::new_v6() + } else { + TcpSocket::new_v4() + }?; + tcp_socket.set_reuseaddr(true)?; + tcp_socket.set_reuseport(true)?; + if let Err(e) = tcp_socket.bind(*listening_on) { + error!("Failed to bind TCP socket: {}", e); + return Err(RpxyError::Io(e)); + }; + Ok(tcp_socket) +} + +#[cfg(feature = "http3")] +pub(super) fn bind_udp_socket(listening_on: &SocketAddr) -> Result { + let socket = if listening_on.is_ipv6() { + Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP)) + } else { + Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)) + }?; + socket.set_reuse_address(true)?; + socket.set_reuse_port(true)?; + + if let Err(e) = socket.bind(&(*listening_on).into()) { + error!("Failed to bind UDP socket: {}", e); + return Err(RpxyError::Io(e)); + }; + let udp_socket: UdpSocket = socket.into(); + + Ok(udp_socket) +} From 16290f3173c28f7b5ea01e1f3976af715a25e637 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sun, 23 Jul 2023 01:48:33 +0900 Subject: [PATCH 06/10] refactor --- rpxy-bin/src/config/parse.rs | 18 ------------------ rpxy-bin/src/main.rs | 1 + rpxy-lib/src/lib.rs | 19 +++++++++++++++++++ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/rpxy-bin/src/config/parse.rs b/rpxy-bin/src/config/parse.rs index 56c6f2c..02f5207 100644 --- a/rpxy-bin/src/config/parse.rs +++ b/rpxy-bin/src/config/parse.rs @@ -31,24 +31,6 @@ pub fn build_settings( /////////////////////////////////// // build proxy config let proxy_config: ProxyConfig = config.try_into()?; - // For loggings - if proxy_config.listen_sockets.iter().any(|addr| addr.is_ipv6()) { - info!("Listen both IPv4 and IPv6") - } else { - info!("Listen IPv4") - } - if proxy_config.http_port.is_some() { - info!("Listen port: {}", proxy_config.http_port.unwrap()); - } - if proxy_config.https_port.is_some() { - info!("Listen port: {} (for TLS)", proxy_config.https_port.unwrap()); - } - if proxy_config.http3 { - info!("Experimental HTTP/3.0 is enabled. Note it is still very unstable."); - } - if !proxy_config.sni_consistency { - info!("Ignore consistency between TLS SNI and Host header (or Request line). Note it violates RFC."); - } /////////////////////////////////// // backend_apps diff --git a/rpxy-bin/src/main.rs b/rpxy-bin/src/main.rs index 8466372..fabfdf3 100644 --- a/rpxy-bin/src/main.rs +++ b/rpxy-bin/src/main.rs @@ -53,6 +53,7 @@ async fn rpxy_service( mut config_rx: ReloaderReceiver, runtime_handle: tokio::runtime::Handle, ) -> Result<(), anyhow::Error> { + info!("Start rpxy service"); // Initial loading config_rx.changed().await?; let config_toml = config_rx.borrow().clone().unwrap(); diff --git a/rpxy-lib/src/lib.rs b/rpxy-lib/src/lib.rs index 4fd64ce..72f8a8a 100644 --- a/rpxy-lib/src/lib.rs +++ b/rpxy-lib/src/lib.rs @@ -32,6 +32,25 @@ pub async fn entrypoint( where T: CryptoSource + Clone + Send + Sync + 'static, { + // For initial message logging + if proxy_config.listen_sockets.iter().any(|addr| addr.is_ipv6()) { + info!("Listen both IPv4 and IPv6") + } else { + info!("Listen IPv4") + } + if proxy_config.http_port.is_some() { + info!("Listen port: {}", proxy_config.http_port.unwrap()); + } + if proxy_config.https_port.is_some() { + info!("Listen port: {} (for TLS)", proxy_config.https_port.unwrap()); + } + if proxy_config.http3 { + info!("Experimental HTTP/3.0 is enabled. Note it is still very unstable."); + } + if !proxy_config.sni_consistency { + info!("Ignore consistency between TLS SNI and Host header (or Request line). Note it violates RFC."); + } + // build global let globals = Arc::new(Globals { proxy_config: proxy_config.clone(), From e2b2ae0729df86efcf2341475d93bcd2e222cc02 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sun, 23 Jul 2023 01:48:51 +0900 Subject: [PATCH 07/10] refactor --- rpxy-bin/src/config/parse.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rpxy-bin/src/config/parse.rs b/rpxy-bin/src/config/parse.rs index 02f5207..3f0d4b8 100644 --- a/rpxy-bin/src/config/parse.rs +++ b/rpxy-bin/src/config/parse.rs @@ -2,7 +2,6 @@ use super::toml::ConfigToml; use crate::{ cert_file_reader::CryptoFileSource, error::{anyhow, ensure}, - log::*, }; use clap::Arg; use rpxy_lib::{AppConfig, AppConfigList, ProxyConfig}; From c58da361cac4a011bfc2e3ea34af265959762a1a Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sun, 23 Jul 2023 01:54:39 +0900 Subject: [PATCH 08/10] update todo and changelog --- CHANGELOG.md | 10 ++++++++++ TODO.md | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dea263..8511a16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## 0.4.0 (unreleased) +### Improvement + +- Feat: Continuous watching on a specified config file and hot-reloading the file when updated +- Feat: Enabled to specify TCP listen backlog in the config file +- Refactor: Split `rpxy` into `rpxy-lib` and `rpxy-bin` +- Refactor: lots of minor improvements + +### Bugfix + +- Fix bug to apply default backend application ## 0.3.0 diff --git a/TODO.md b/TODO.md index 81af1d5..3425d11 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,8 @@ - upstream/upstream group: information on targeted destinations for each set of (a domain + a path) - load-balance: load balancing mod for a domain + path - - Split `rpxy` source codes into `rpxy-lib` and `rpxy-bin` to make the core part (reverse proxy) isolated from the misc part like toml file loader. This is in order to make the configuration-related part more flexible (related to [#33](https://github.com/junkurihara/rust-rpxy/issues/33)) + - **Almost done in version 0.4.0**: + Split `rpxy` source codes into `rpxy-lib` and `rpxy-bin` to make the core part (reverse proxy) isolated from the misc part like toml file loader. This is in order to make the configuration-related part more flexible (related to [#33](https://github.com/junkurihara/rust-rpxy/issues/33)) - Cache option for the response with `Cache-Control: public` header directive ([#55](https://github.com/junkurihara/rust-rpxy/issues/55)) - Consideration on migrating from `quinn` and `h3-quinn` to other QUIC implementations ([#57](https://github.com/junkurihara/rust-rpxy/issues/57)) From 302766987484be8ed55094f4f5518cef064c2182 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sun, 23 Jul 2023 02:19:38 +0900 Subject: [PATCH 09/10] deps --- rpxy-bin/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpxy-bin/Cargo.toml b/rpxy-bin/Cargo.toml index fb6d4aa..f4bdc68 100644 --- a/rpxy-bin/Cargo.toml +++ b/rpxy-bin/Cargo.toml @@ -33,7 +33,7 @@ async-trait = "0.1.72" rustls-pemfile = "1.0.3" # config -clap = { version = "4.3.17", features = ["std", "cargo", "wrap_help"] } +clap = { version = "4.3.19", features = ["std", "cargo", "wrap_help"] } toml = { version = "0.7.6", default-features = false, features = ["parse"] } hot_reload = "0.1.4" From 411fbaf29683b0a5fc8c942d6c0c3ee06ae6c820 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Mon, 24 Jul 2023 21:53:01 +0900 Subject: [PATCH 10/10] feat: add option to activate continuous monitoring on config file --- ...d_push.yml => docker_build_push_amd64.yml} | 6 +- .github/workflows/docker_build_push_arm64.yml | 64 ++++++++++++++++++ CHANGELOG.md | 2 + README.md | 16 ++++- docker/Dockerfile.arm64-slim | 45 +++++++++++++ rpxy-bin/src/config/parse.rs | 43 ++++++++---- rpxy-bin/src/main.rs | 66 +++++++++++++++---- rpxy-lib/src/proxy/socket.rs | 4 ++ 8 files changed, 216 insertions(+), 30 deletions(-) rename .github/workflows/{docker_build_push.yml => docker_build_push_amd64.yml} (93%) create mode 100644 .github/workflows/docker_build_push_arm64.yml create mode 100644 docker/Dockerfile.arm64-slim diff --git a/.github/workflows/docker_build_push.yml b/.github/workflows/docker_build_push_amd64.yml similarity index 93% rename from .github/workflows/docker_build_push.yml rename to .github/workflows/docker_build_push_amd64.yml index 305aa45..fa016a0 100644 --- a/.github/workflows/docker_build_push.yml +++ b/.github/workflows/docker_build_push_amd64.yml @@ -1,4 +1,4 @@ -name: Build and Publish Docker +name: Build and Publish Docker x86_64 on: push: @@ -39,7 +39,7 @@ jobs: tags: | ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest file: ./docker/Dockerfile - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 - name: Release build and push x86_64-slim if: ${{ env.BRANCH == 'main' }} @@ -61,4 +61,4 @@ jobs: tags: | ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:nightly file: ./docker/Dockerfile - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 diff --git a/.github/workflows/docker_build_push_arm64.yml b/.github/workflows/docker_build_push_arm64.yml new file mode 100644 index 0000000..b31581c --- /dev/null +++ b/.github/workflows/docker_build_push_arm64.yml @@ -0,0 +1,64 @@ +name: Build and Publish Docker Aarch64 + +on: + push: + branches: + - main + - develop + +jobs: + build_and_push: + runs-on: ubuntu-latest + env: + IMAGE_NAME: rpxy + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: GitHub Environment + run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Release build and push + if: ${{ env.BRANCH == 'main' }} + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + file: ./docker/Dockerfile + platforms: linux/arm64 + + - name: Release build and push x86_64-slim + if: ${{ env.BRANCH == 'main' }} + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:slim, ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest-slim + file: ./docker/Dockerfile.arm64-slim + platforms: linux/arm64 + + - name: Nightly build and push + if: ${{ env.BRANCH == 'develop' }} + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:nightly + file: ./docker/Dockerfile + platforms: linux/arm64 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8511a16..3a11094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Feat: Continuous watching on a specified config file and hot-reloading the file when updated - Feat: Enabled to specify TCP listen backlog in the config file +- Feat: Add a GitHub action to build `arm64` docker image. +- Bench: Add benchmark result on `amd64` architecture. - Refactor: Split `rpxy` into `rpxy-lib` and `rpxy-bin` - Refactor: lots of minor improvements diff --git a/README.md b/README.md index 561141a..a3d8795 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,20 @@ You can run `rpxy` with a configuration file like % ./target/release/rpxy --config config.toml ``` +If you specify `-w` option along with the config file path, `rpxy` tracks the change of `config.toml` in the real-time manner and apply the change immediately without restarting the process. + +The full help messages are given follows. + +```bash: +usage: rpxy [OPTIONS] --config + +Options: + -c, --config Configuration file path like ./config.toml + -w, --watch Activate dynamic reloading of the config file via continuous monitoring + -h, --help Print help + -V, --version Print version +``` + That's all! ## Basic Configuration @@ -217,7 +231,7 @@ Since it is currently a work-in-progress project, we are frequently adding new o ## Using Docker Image -You can also use [docker image](https://hub.docker.com/r/jqtype/rpxy) instead of directly executing the binary. There are only two docker-specific environment variables. +You can also use [docker image](https://hub.docker.com/r/jqtype/rpxy) instead of directly executing the binary. There are only several docker-specific environment variables. - `HOST_USER` (default: `user`): User name executing `rpxy` inside the container. - `HOST_UID` (default: `900`): `UID` of `HOST_USER`. diff --git a/docker/Dockerfile.arm64-slim b/docker/Dockerfile.arm64-slim new file mode 100644 index 0000000..83b2d16 --- /dev/null +++ b/docker/Dockerfile.arm64-slim @@ -0,0 +1,45 @@ +######################################## +FROM messense/rust-musl-cross:aarch64-musl as builder + +ENV TARGET_DIR=aarch64-unknown-linux-musl +ENV CFLAGS=-Ofast + +WORKDIR /tmp + +COPY . /tmp/ + +ENV RUSTFLAGS "-C link-arg=-s" + +RUN echo "Building rpxy from source" && \ + cargo build --release && \ + musl-strip --strip-all /tmp/target/${TARGET_DIR}/release/rpxy + +######################################## +FROM alpine:latest as runner +LABEL maintainer="Jun Kurihara" + +ENV TARGET_DIR=aarch64-unknown-linux-musl +ENV RUNTIME_DEPS logrotate ca-certificates su-exec + +RUN apk add --no-cache ${RUNTIME_DEPS} && \ + update-ca-certificates && \ + find / -type d -path /proc -prune -o -type f -perm /u+s -exec chmod u-s {} \; && \ + find / -type d -path /proc -prune -o -type f -perm /g+s -exec chmod g-s {} \; && \ + mkdir -p /rpxy/bin &&\ + mkdir -p /rpxy/log + +COPY --from=builder /tmp/target/${TARGET_DIR}/release/rpxy /rpxy/bin/rpxy +COPY ./docker/run.sh /rpxy +COPY ./docker/entrypoint.sh /rpxy + +RUN chmod +x /rpxy/run.sh && \ + chmod +x /rpxy/entrypoint.sh + +ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt +ENV SSL_CERT_DIR=/etc/ssl/certs + +EXPOSE 80 443 + +CMD ["/rpxy/entrypoint.sh"] + +ENTRYPOINT ["/rpxy/entrypoint.sh"] diff --git a/rpxy-bin/src/config/parse.rs b/rpxy-bin/src/config/parse.rs index 3f0d4b8..15ff240 100644 --- a/rpxy-bin/src/config/parse.rs +++ b/rpxy-bin/src/config/parse.rs @@ -3,25 +3,44 @@ use crate::{ cert_file_reader::CryptoFileSource, error::{anyhow, ensure}, }; -use clap::Arg; +use clap::{Arg, ArgAction}; use rpxy_lib::{AppConfig, AppConfigList, ProxyConfig}; -pub fn parse_opts() -> Result { +/// Parsed options +pub struct Opts { + pub config_file_path: String, + pub watch: bool, +} + +/// Parse arg values passed from cli +pub fn parse_opts() -> Result { let _ = include_str!("../../Cargo.toml"); - let options = clap::command!().arg( - Arg::new("config_file") - .long("config") - .short('c') - .value_name("FILE") - .required(true) - .help("Configuration file path like ./config.toml"), - ); + let options = clap::command!() + .arg( + Arg::new("config_file") + .long("config") + .short('c') + .value_name("FILE") + .required(true) + .help("Configuration file path like ./config.toml"), + ) + .arg( + Arg::new("watch") + .long("watch") + .short('w') + .action(ArgAction::SetTrue) + .help("Activate dynamic reloading of the config file via continuous monitoring"), + ); let matches = options.get_matches(); /////////////////////////////////// - let config_file_path = matches.get_one::("config_file").unwrap(); + let config_file_path = matches.get_one::("config_file").unwrap().to_owned(); + let watch = matches.get_one::("watch").unwrap().to_owned(); - Ok(config_file_path.to_string()) + Ok(Opts { + config_file_path, + watch, + }) } pub fn build_settings( diff --git a/rpxy-bin/src/main.rs b/rpxy-bin/src/main.rs index fabfdf3..8fe00dc 100644 --- a/rpxy-bin/src/main.rs +++ b/rpxy-bin/src/main.rs @@ -28,32 +28,69 @@ fn main() { let runtime = runtime_builder.build().unwrap(); runtime.block_on(async { - // Initially load config - let Ok(config_path) = parse_opts() else { + // Initially load options + let Ok(parsed_opts) = parse_opts() else { error!("Invalid toml file"); std::process::exit(1); }; - let (config_service, config_rx) = - ReloaderService::::new(&config_path, CONFIG_WATCH_DELAY_SECS, false) - .await - .unwrap(); - tokio::select! { - _ = config_service.start() => { - error!("config reloader service exited"); + if !parsed_opts.watch { + if let Err(e) = rpxy_service_without_watcher(&parsed_opts.config_file_path, runtime.handle().clone()).await { + error!("rpxy service existed: {e}"); + std::process::exit(1); } - _ = rpxy_service(config_rx, runtime.handle().clone()) => { - error!("rpxy service existed"); + } else { + let (config_service, config_rx) = ReloaderService::::new( + &parsed_opts.config_file_path, + CONFIG_WATCH_DELAY_SECS, + false, + ) + .await + .unwrap(); + + tokio::select! { + Err(e) = config_service.start() => { + error!("config reloader service exited: {e}"); + std::process::exit(1); + } + Err(e) = rpxy_service_with_watcher(config_rx, runtime.handle().clone()) => { + error!("rpxy service existed: {e}"); + std::process::exit(1); + } } } }); } -async fn rpxy_service( - mut config_rx: ReloaderReceiver, +async fn rpxy_service_without_watcher( + config_file_path: &str, runtime_handle: tokio::runtime::Handle, ) -> Result<(), anyhow::Error> { info!("Start rpxy service"); + let config_toml = match ConfigToml::new(config_file_path) { + Ok(v) => v, + Err(e) => { + error!("Invalid toml file: {e}"); + std::process::exit(1); + } + }; + let (proxy_conf, app_conf) = match build_settings(&config_toml) { + Ok(v) => v, + Err(e) => { + error!("Invalid configuration: {e}"); + return Err(anyhow::anyhow!(e)); + } + }; + entrypoint(&proxy_conf, &app_conf, &runtime_handle) + .await + .map_err(|e| anyhow::anyhow!(e)) +} + +async fn rpxy_service_with_watcher( + mut config_rx: ReloaderReceiver, + runtime_handle: tokio::runtime::Handle, +) -> Result<(), anyhow::Error> { + info!("Start rpxy service with dynamic config reloader"); // Initial loading config_rx.changed().await?; let config_toml = config_rx.borrow().clone().unwrap(); @@ -92,5 +129,6 @@ async fn rpxy_service( else => break } } - Ok(()) + + Err(anyhow::anyhow!("rpxy or continuous monitoring service exited")) } diff --git a/rpxy-lib/src/proxy/socket.rs b/rpxy-lib/src/proxy/socket.rs index 8858732..48f72e9 100644 --- a/rpxy-lib/src/proxy/socket.rs +++ b/rpxy-lib/src/proxy/socket.rs @@ -6,6 +6,8 @@ use std::net::SocketAddr; use std::net::UdpSocket; use tokio::net::TcpSocket; +/// Bind TCP socket to the given `SocketAddr`, and returns the TCP socket with `SO_REUSEADDR` and `SO_REUSEPORT` options. +/// This option is required to re-bind the socket address when the proxy instance is reconstructed. pub(super) fn bind_tcp_socket(listening_on: &SocketAddr) -> Result { let tcp_socket = if listening_on.is_ipv6() { TcpSocket::new_v6() @@ -22,6 +24,8 @@ pub(super) fn bind_tcp_socket(listening_on: &SocketAddr) -> Result { } #[cfg(feature = "http3")] +/// Bind UDP socket to the given `SocketAddr`, and returns the UDP socket with `SO_REUSEADDR` and `SO_REUSEPORT` options. +/// This option is required to re-bind the socket address when the proxy instance is reconstructed. pub(super) fn bind_udp_socket(listening_on: &SocketAddr) -> Result { let socket = if listening_on.is_ipv6() { Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))