mod backend; mod constants; mod count; mod error; mod forwarder; mod globals; mod hyper_ext; mod log; mod message_handler; mod name_exp; mod proxy; /* ------------------------------------------------ */ use crate::{ // crypto::build_cert_reloader, error::*, forwarder::Forwarder, globals::Globals, log::*, message_handler::HttpMessageHandlerBuilder, proxy::Proxy, }; use futures::future::join_all; use hot_reload::ReloaderReceiver; use rpxy_certs::ServerCryptoBase; use rustls::crypto::CryptoProvider; use std::sync::Arc; use tokio_util::sync::CancellationToken; /* ------------------------------------------------ */ pub use crate::constants::log_event_names; pub use crate::globals::{AppConfig, AppConfigList, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri}; pub mod reexports { pub use hyper::Uri; } #[derive(derive_builder::Builder)] /// rpxy entrypoint args pub struct RpxyOptions { /// Configuration parameters for proxy transport and request handlers pub proxy_config: ProxyConfig, /// List of application configurations pub app_config_list: AppConfigList, /// Certificate reloader service receiver pub cert_rx: Option>, // TODO: /// Async task runtime handler pub runtime_handle: tokio::runtime::Handle, #[cfg(feature = "acme")] /// ServerConfig used for only ACME challenge for ACME domains pub server_configs_acme_challenge: Arc>>, } /// Entrypoint that creates and spawns tasks of reverse proxy services pub async fn entrypoint( RpxyOptions { proxy_config, app_config_list, cert_rx, // TODO: runtime_handle, #[cfg(feature = "acme")] server_configs_acme_challenge, }: &RpxyOptions, cancel_token: CancellationToken, ) -> RpxyResult<()> { #[cfg(all(feature = "http3-quinn", feature = "http3-s2n"))] warn!("Both \"http3-quinn\" and \"http3-s2n\" features are enabled. \"http3-quinn\" will be used"); #[cfg(all(feature = "native-tls-backend", feature = "rustls-backend"))] warn!("Both \"native-tls-backend\" and \"rustls-backend\" features are enabled. \"rustls-backend\" will be used"); // 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.connection_handling_timeout.is_some() { info!( "Force connection handling timeout: {:?} sec", proxy_config.connection_handling_timeout.unwrap_or_default().as_secs() ); } #[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))] 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."); } #[cfg(feature = "cache")] if proxy_config.cache_enabled { info!("Cache is enabled: cache dir = {:?}", proxy_config.cache_dir.as_ref().unwrap()); } else { info!("Cache is disabled") } let mut ciphers: Option> = None; let mut kexes: Option> = None; for (var, val) in std::env::vars() { match var.as_str() { "CIPHERS" => ciphers = Some(val.split(',').map(str::to_string).collect()), "KEXES" => kexes = Some(val.split(',').map(str::to_string).collect()), _ => {} } } // Ensure multiple provider cannot be enabled without compile error. let _provider; #[cfg(feature = "rustls-backend-aws-lc-rs")] { info!("Using RusTLS provider aws-lc-rs"); let mut prov = rustls::crypto::aws_lc_rs::default_provider(); if let Some(ciphers) = ciphers { prov.cipher_suites.clear(); for cipher in ciphers { match cipher.as_str() { "AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384), "AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256), "CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256), "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), "ECDHE_RSA_WITH_AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), "ECDHE_RSA_WITH_AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), other => { log::error!("Unknown cipher `{other}`") } } } } if let Some(kexes) = kexes { prov.kx_groups.clear(); for kex in kexes { match kex.as_str() { "X25519" => prov.kx_groups.push(rustls::crypto::aws_lc_rs::kx_group::X25519), "SECP256R1" => prov.kx_groups.push(rustls::crypto::aws_lc_rs::kx_group::SECP256R1), "SECP384R1" => prov.kx_groups.push(rustls::crypto::aws_lc_rs::kx_group::SECP384R1), other => { log::error!("Unknown kex `{other}`") } } } } _provider = CryptoProvider::install_default(prov); } #[cfg(feature = "rustls-backend-boring")] { info!("Using RusTLS provider boring"); let mut prov = boring_rustls_provider::provider(); if let Some(ciphers) = ciphers { prov.cipher_suites.clear(); for cipher in ciphers { match cipher.as_str() { "AES_256_GCM_SHA384" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls13( &boring_rustls_provider::tls13::AES_256_GCM_SHA384, )), "AES_128_GCM_SHA256" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls13( &boring_rustls_provider::tls13::AES_128_GCM_SHA256, )), "CHACHA20_POLY1305_SHA256" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls13( &boring_rustls_provider::tls13::CHACHA20_POLY1305_SHA256, )), "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls12( &boring_rustls_provider::tls12::ECDHE_ECDSA_AES256_GCM_SHA384, )), "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls12( &boring_rustls_provider::tls12::ECDHE_ECDSA_AES128_GCM_SHA256, )), "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls12( &boring_rustls_provider::tls12::ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, )), "ECDHE_RSA_WITH_AES_256_GCM_SHA384" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls12( &boring_rustls_provider::tls12::ECDHE_RSA_AES256_GCM_SHA384, )), "ECDHE_RSA_WITH_AES_128_GCM_SHA256" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls12( &boring_rustls_provider::tls12::ECDHE_RSA_AES128_GCM_SHA256, )), "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => prov.cipher_suites.push(rustls::SupportedCipherSuite::Tls12( &boring_rustls_provider::tls12::ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, )), other => { log::error!("Unknown cipher `{other}`") } } } } if let Some(kexes) = kexes { prov.kx_groups.clear(); for kex in kexes { match kex.as_str() { "X25519" => prov.kx_groups.push(boring_rustls_provider::ALL_KX_GROUPS[0]), "SECP256R1" => prov.kx_groups.push(boring_rustls_provider::ALL_KX_GROUPS[2]), "SECP384R1" => prov.kx_groups.push(boring_rustls_provider::ALL_KX_GROUPS[3]), other => { log::error!("Unknown kex `{other}`") } } } } _provider = CryptoProvider::install_default(prov); } #[cfg(feature = "rustls-backend-openssl")] { info!("Using RusTLS provider openssl"); let mut prov = rustls_openssl::default_provider(); if let Some(ciphers) = ciphers { prov.cipher_suites.clear(); for cipher in ciphers { match cipher.as_str() { "AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS13_AES_256_GCM_SHA384), "AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS13_AES_128_GCM_SHA256), "CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256), "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), "ECDHE_RSA_WITH_AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), "ECDHE_RSA_WITH_AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls_openssl::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), other => { log::error!("Unknown cipher `{other}`") } } } } if let Some(kexes) = kexes { prov.kx_groups.clear(); for kex in kexes { match kex.as_str() { "X25519" => prov.kx_groups.push(rustls_openssl::kx_group::X25519), "SECP256R1" => prov.kx_groups.push(rustls_openssl::kx_group::SECP256R1), "SECP384R1" => prov.kx_groups.push(rustls_openssl::kx_group::SECP384R1), other => { log::error!("Unknown kex `{other}`") } } } } _provider = CryptoProvider::install_default(prov); } #[cfg(feature = "post-quantum")] { info!("Using RusTLS provider post_quantum"); _provider = CryptoProvider::install_default(rustls_post_quantum::provider()); } #[cfg(feature = "rustls-backend-ring")] { info!("Using RusTLS provider ring"); let mut prov = rustls::crypto::ring::default_provider(); if let Some(ciphers) = ciphers { prov.cipher_suites.clear(); for cipher in ciphers { match cipher.as_str() { "AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS13_AES_256_GCM_SHA384), "AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS13_AES_128_GCM_SHA256), "CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256), "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), "ECDHE_RSA_WITH_AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), "ECDHE_RSA_WITH_AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), other => { log::error!("Unknown cipher `{other}`") } } } } if let Some(kexes) = kexes { prov.kx_groups.clear(); for kex in kexes { match kex.as_str() { "X25519" => prov.kx_groups.push(rustls::crypto::ring::kx_group::X25519), "SECP256R1" => prov.kx_groups.push(rustls::crypto::ring::kx_group::SECP256R1), "SECP384R1" => prov.kx_groups.push(rustls::crypto::ring::kx_group::SECP384R1), other => { log::error!("Unknown kex `{other}`") } } } } _provider = CryptoProvider::install_default(prov); } #[cfg(feature = "rustls-backend-symcrypt")] { info!("Using RusTLS provider symcrypt"); let mut prov = rustls_symcrypt::default_symcrypt_provider(); if let Some(ciphers) = ciphers { prov.cipher_suites.clear(); for cipher in ciphers { match cipher.as_str() { "AES_256_GCM_SHA384" => prov.cipher_suites.push(rustls_symcrypt::TLS13_AES_256_GCM_SHA384), "AES_128_GCM_SHA256" => prov.cipher_suites.push(rustls_symcrypt::TLS13_AES_128_GCM_SHA256), "CHACHA20_POLY1305_SHA256" => prov.cipher_suites.push(rustls_symcrypt::TLS13_CHACHA20_POLY1305_SHA256), "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls_symcrypt::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls_symcrypt::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls_symcrypt::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), "ECDHE_RSA_WITH_AES_256_GCM_SHA384" => prov .cipher_suites .push(rustls_symcrypt::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), "ECDHE_RSA_WITH_AES_128_GCM_SHA256" => prov .cipher_suites .push(rustls_symcrypt::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => prov .cipher_suites .push(rustls_symcrypt::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), other => { log::error!("Unknown cipher `{other}`") } } } } if let Some(kexes) = kexes { prov.kx_groups.clear(); for kex in kexes { match kex.as_str() { "X25519" => prov.kx_groups.push(rustls_symcrypt::X25519), "SECP256R1" => prov.kx_groups.push(rustls_symcrypt::SECP256R1), "SECP384R1" => prov.kx_groups.push(rustls_symcrypt::SECP384R1), other => { log::error!("Unknown kex `{other}`") } } } } _provider = CryptoProvider::install_default(prov); } #[cfg(feature = "rustls-backend-wolfcrypt")] { info!("Using RusTLS provider wolfcrypt"); _provider = CryptoProvider::install_default(rustls_wolfcrypt_provider::provider()); } // 1. build backends, and make it contained in Arc let app_manager = Arc::new(backend::BackendAppManager::try_from(app_config_list)?); // 2. build global shared context let globals = Arc::new(Globals { proxy_config: proxy_config.clone(), request_count: Default::default(), runtime_handle: runtime_handle.clone(), cert_reloader_rx: cert_rx.clone(), #[cfg(feature = "acme")] server_configs_acme_challenge: server_configs_acme_challenge.clone(), }); // 3. build message handler containing Arc-ed http_client and backends, and make it contained in Arc as well let forwarder = Arc::new(Forwarder::try_new(&globals).await?); let message_handler = Arc::new( HttpMessageHandlerBuilder::default() .globals(globals.clone()) .app_manager(app_manager.clone()) .forwarder(forwarder) .build()?, ); // 4. spawn each proxy for a given socket with copied Arc-ed message_handler. // build hyper connection builder shared with proxy instances let connection_builder = proxy::connection_builder(&globals); // spawn each proxy for a given socket with copied Arc-ed backend, message_handler and connection builder. let addresses = globals.proxy_config.listen_sockets.clone(); let join_handles = addresses.into_iter().map(|listening_on| { let mut tls_enabled = false; if let Some(https_port) = globals.proxy_config.https_port { tls_enabled = https_port == listening_on.port() } let proxy = Proxy { globals: globals.clone(), listening_on, tls_enabled, connection_builder: connection_builder.clone(), message_handler: message_handler.clone(), }; let cancel_token = cancel_token.clone(); globals.runtime_handle.spawn(async move { info!("rpxy proxy service for {listening_on} started"); tokio::select! { _ = cancel_token.cancelled() => { debug!("rpxy proxy service for {listening_on} terminated"); Ok(()) }, proxy_res = proxy.start(cancel_token.child_token()) => { info!("rpxy proxy service for {listening_on} exited"); // cancel other proxy tasks cancel_token.cancel(); proxy_res } } }) }); let join_res = join_all(join_handles).await; let mut errs = join_res.into_iter().filter_map(|res| { if let Ok(Err(e)) = res { error!("Some proxy services are down: {}", e); Some(e) } else { None } }); // returns the first error as the representative error errs.next().map_or(Ok(()), |e| Err(e)) }