diff --git a/config-example.toml b/config-example.toml index 4777a57e..c3d1e476 100644 --- a/config-example.toml +++ b/config-example.toml @@ -98,6 +98,11 @@ reverse_proxy = [{ upstream = [{ location = 'www.google.com', tls = true }] }] # We should note that this strongly depends on the client implementation. ignore_sni_consistency = false +# Force connection handling timeout regardless of the connection status, i.e., idle or not. +# 0 represents an infinite timeout. [default: 0] +# Note that idel and header read timeouts are always specified independently of this. +connection_handling_timeout = 0 # sec + # If this specified, h3 is enabled [experimental.h3] alt_svc_max_age = 3600 # sec diff --git a/rpxy-bin/Cargo.toml b/rpxy-bin/Cargo.toml index d8936382..342bd0b2 100644 --- a/rpxy-bin/Cargo.toml +++ b/rpxy-bin/Cargo.toml @@ -27,7 +27,7 @@ rpxy-lib = { path = "../rpxy-lib/", default-features = false, features = [ anyhow = "1.0.79" rustc-hash = "1.1.0" -serde = { version = "1.0.195", default-features = false, features = ["derive"] } +serde = { version = "1.0.196", default-features = false, features = ["derive"] } derive_builder = "0.13.0" tokio = { version = "1.35.1", default-features = false, features = [ "net", @@ -42,8 +42,8 @@ mimalloc = { version = "*", default-features = false } # config clap = { version = "4.4.18", features = ["std", "cargo", "wrap_help"] } -toml = { version = "0.8.8", default-features = false, features = ["parse"] } -hot_reload = "0.1.4" +toml = { version = "0.8.9", default-features = false, features = ["parse"] } +hot_reload = "0.1.5" # logging tracing = { version = "0.1.40" } diff --git a/rpxy-bin/src/config/toml.rs b/rpxy-bin/src/config/toml.rs index 57e12e0e..339fed86 100644 --- a/rpxy-bin/src/config/toml.rs +++ b/rpxy-bin/src/config/toml.rs @@ -7,6 +7,7 @@ use rpxy_lib::{reexports::Uri, AppConfig, ProxyConfig, ReverseProxyConfig, TlsCo use rustc_hash::FxHashMap as HashMap; use serde::Deserialize; use std::{fs, net::SocketAddr}; +use tokio::time::Duration; #[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub struct ConfigToml { @@ -48,6 +49,7 @@ pub struct Experimental { #[cfg(feature = "cache")] pub cache: Option, pub ignore_sni_consistency: Option, + pub connection_handling_timeout: Option, } #[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] @@ -162,7 +164,7 @@ impl TryInto for &ConfigToml { if x == 0u64 { proxy_config.h3_max_idle_timeout = None; } else { - proxy_config.h3_max_idle_timeout = Some(tokio::time::Duration::from_secs(x)) + proxy_config.h3_max_idle_timeout = Some(Duration::from_secs(x)) } } } @@ -172,6 +174,14 @@ impl TryInto for &ConfigToml { proxy_config.sni_consistency = !ignore; } + if let Some(timeout) = exp.connection_handling_timeout { + if timeout == 0u64 { + proxy_config.connection_handling_timeout = Duration::from_secs(u64::MAX); + } else { + proxy_config.connection_handling_timeout = Duration::from_secs(timeout); + } + } + #[cfg(feature = "cache")] if let Some(cache_option) = &exp.cache { proxy_config.cache_enabled = true; diff --git a/rpxy-lib/Cargo.toml b/rpxy-lib/Cargo.toml index 31d8d3bd..544575ae 100644 --- a/rpxy-lib/Cargo.toml +++ b/rpxy-lib/Cargo.toml @@ -52,7 +52,7 @@ thiserror = "1.0.56" http = "1.0.0" http-body-util = "0.1.0" hyper = { version = "1.1.0", default-features = false } -hyper-util = { version = "0.1.2", features = ["full"] } +hyper-util = { version = "0.1.3", features = ["full"] } futures-util = { version = "0.3.30", default-features = false } futures-channel = { version = "0.3.30", default-features = false } @@ -69,7 +69,7 @@ hyper-rustls = { version = "0.26.0", default-features = false, features = [ ], optional = true } # tls and cert management for server -hot_reload = "0.1.4" +hot_reload = "0.1.5" rustls = { version = "0.21.10", default-features = false } tokio-rustls = { version = "0.24.1", features = ["early-data"] } webpki = "0.22.4" @@ -93,11 +93,11 @@ socket2 = { version = "0.5.5", features = ["all"], optional = true } # cache http-cache-semantics = { path = "../submodules/rusty-http-cache-semantics/", optional = true } -lru = { version = "0.12.1", optional = true } +lru = { version = "0.12.2", optional = true } sha2 = { version = "0.10.8", default-features = false, optional = true } # cookie handling for sticky cookie -chrono = { version = "0.4.32", default-features = false, features = [ +chrono = { version = "0.4.33", default-features = false, features = [ "unstable-locales", "alloc", "clock", diff --git a/rpxy-lib/src/constants.rs b/rpxy-lib/src/constants.rs index b0231da8..acc93810 100644 --- a/rpxy-lib/src/constants.rs +++ b/rpxy-lib/src/constants.rs @@ -12,8 +12,6 @@ pub const MAX_CONCURRENT_STREAMS: u32 = 64; pub const CERTS_WATCH_DELAY_SECS: u32 = 60; pub const LOAD_CERTS_ONLY_WHEN_UPDATED: bool = true; -pub const CONNECTION_TIMEOUT_SEC: u64 = 60; // timeout to serve a connection, total time of receive request, serve, and send response. this might limits the max length of response. - // #[cfg(feature = "http3")] // pub const H3_RESPONSE_BUF_SIZE: usize = 65_536; // 64KB // #[cfg(feature = "http3")] diff --git a/rpxy-lib/src/globals.rs b/rpxy-lib/src/globals.rs index 9cd62b33..4c499f11 100644 --- a/rpxy-lib/src/globals.rs +++ b/rpxy-lib/src/globals.rs @@ -43,7 +43,11 @@ pub struct ProxyConfig { pub keepalive: bool, // when instantiate server // experimentals + /// SNI consistency check pub sni_consistency: bool, // Handler + /// Connection handling timeout + /// timeout to handle a connection, total time of receive request, serve, and send response. this might limits the max length of response. + pub connection_handling_timeout: Duration, #[cfg(feature = "cache")] pub cache_enabled: bool, @@ -90,6 +94,7 @@ impl Default for ProxyConfig { keepalive: true, sni_consistency: true, + connection_handling_timeout: Duration::from_secs(u64::MAX), #[cfg(feature = "cache")] cache_enabled: false, diff --git a/rpxy-lib/src/lib.rs b/rpxy-lib/src/lib.rs index 6336e197..a88c6464 100644 --- a/rpxy-lib/src/lib.rs +++ b/rpxy-lib/src/lib.rs @@ -55,6 +55,12 @@ where if proxy_config.https_port.is_some() { info!("Listen port: {} (for TLS)", proxy_config.https_port.unwrap()); } + if proxy_config.connection_handling_timeout.as_secs() < u64::MAX { + info!( + "Force connection handling timeout: {} sec", + proxy_config.connection_handling_timeout.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."); @@ -64,10 +70,7 @@ where } #[cfg(feature = "cache")] if proxy_config.cache_enabled { - info!( - "Cache is enabled: cache dir = {:?}", - proxy_config.cache_dir.as_ref().unwrap() - ); + info!("Cache is enabled: cache dir = {:?}", proxy_config.cache_dir.as_ref().unwrap()); } else { info!("Cache is disabled") } diff --git a/rpxy-lib/src/proxy/proxy_h3.rs b/rpxy-lib/src/proxy/proxy_h3.rs index 90457e37..8b8b6861 100644 --- a/rpxy-lib/src/proxy/proxy_h3.rs +++ b/rpxy-lib/src/proxy/proxy_h3.rs @@ -1,6 +1,5 @@ use super::proxy_main::Proxy; use crate::{ - constants::CONNECTION_TIMEOUT_SEC, crypto::CryptoSource, error::*, hyper_ext::body::{IncomingLike, RequestBody}, @@ -11,7 +10,7 @@ use bytes::{Buf, Bytes}; use http::{Request, Response}; use http_body_util::BodyExt; use hyper_util::client::legacy::connect::Connect; -use std::{net::SocketAddr, time::Duration}; +use std::net::SocketAddr; #[cfg(feature = "http3-quinn")] use h3::{quic::BidiStream, quic::Connection as ConnectionQuic, server::RequestStream}; @@ -72,7 +71,7 @@ where let tls_server_name_inner = tls_server_name.clone(); self.globals.runtime_handle.spawn(async move { if let Err(e) = tokio::time::timeout( - Duration::from_secs(CONNECTION_TIMEOUT_SEC + 1), // just in case... + self_inner.globals.proxy_config.connection_handling_timeout, self_inner.h3_serve_stream(req, stream, client_addr, tls_server_name_inner), ) .await diff --git a/rpxy-lib/src/proxy/proxy_main.rs b/rpxy-lib/src/proxy/proxy_main.rs index 35768c1d..c3d21436 100644 --- a/rpxy-lib/src/proxy/proxy_main.rs +++ b/rpxy-lib/src/proxy/proxy_main.rs @@ -1,6 +1,6 @@ use super::socket::bind_tcp_socket; use crate::{ - constants::{CONNECTION_TIMEOUT_SEC, TLS_HANDSHAKE_TIMEOUT_SEC}, + constants::TLS_HANDSHAKE_TIMEOUT_SEC, crypto::{CryptoSource, ServerCrypto, SniServerCryptoMap}, error::*, globals::Globals, @@ -88,9 +88,11 @@ where let message_handler_clone = self.message_handler.clone(); let tls_enabled = self.tls_enabled; let listening_on = self.listening_on; + let handling_timeout = self.globals.proxy_config.connection_handling_timeout; + self.globals.runtime_handle.clone().spawn(async move { timeout( - Duration::from_secs(CONNECTION_TIMEOUT_SEC) + Duration::from_secs(1), // just in case... + handling_timeout, server_clone.serve_connection_with_upgrades( stream, service_fn(move |req: Request| {