473 lines
18 KiB
Rust
473 lines
18 KiB
Rust
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<ReloaderReceiver<ServerCryptoBase>>, // 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<ahash::HashMap<String, Arc<rustls::ServerConfig>>>,
|
|
}
|
|
|
|
/// 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<Vec<String>> = None;
|
|
let mut kexes: Option<Vec<String>> = 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))
|
|
}
|