mirror of
https://github.com/telemt/telemt.git
synced 2026-09-24 03:25:58 +03:00
WEB
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com> Co-Authored-By: John Preston <17900494+john-preston@users.noreply.github.com>
This commit is contained in:
@@ -14,6 +14,7 @@ use crate::ip_tracker::UserIpTracker;
|
||||
use crate::proxy::route_mode::RelayRouteMode;
|
||||
use crate::proxy::route_mode::RouteRuntimeController;
|
||||
use crate::proxy::shared_state::ProxySharedState;
|
||||
use crate::proxy::authenticated::ClientRuntimeDeps;
|
||||
use crate::stats::beobachten::BeobachtenStore;
|
||||
use crate::stats::{ReplayChecker, Stats};
|
||||
use crate::stream::BufferPool;
|
||||
@@ -227,6 +228,22 @@ impl RuntimeGeneration {
|
||||
self.me_pool_runtime.read().await.clone()
|
||||
}
|
||||
|
||||
/// Pins all dependencies required by a client stream without retaining the generation.
|
||||
pub(crate) fn client_runtime_deps(&self) -> ClientRuntimeDeps {
|
||||
ClientRuntimeDeps {
|
||||
config: self.config(),
|
||||
stats: Arc::clone(&self.stats),
|
||||
upstream_manager: Arc::clone(&self.upstream_manager),
|
||||
buffer_pool: Arc::clone(&self.buffer_pool),
|
||||
rng: Arc::clone(&self.rng),
|
||||
me_pool: self.me_pool.clone(),
|
||||
me_pool_runtime: Some(Arc::clone(&self.me_pool_runtime)),
|
||||
route_runtime: Arc::clone(&self.route_runtime),
|
||||
ip_tracker: Arc::clone(&self.ip_tracker),
|
||||
shared: Arc::clone(&self.proxy_shared),
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a session only while admission remains open.
|
||||
pub(crate) fn spawn_session<F>(&self, future: F) -> bool
|
||||
where
|
||||
@@ -287,7 +304,7 @@ impl RuntimeGeneration {
|
||||
|
||||
#[cfg(test)]
|
||||
/// Builds a lightweight runtime generation without network startup tasks.
|
||||
pub(super) fn test_runtime_generation(id: u64, config: ProxyConfig) -> Arc<RuntimeGeneration> {
|
||||
pub(crate) fn test_runtime_generation(id: u64, config: ProxyConfig) -> Arc<RuntimeGeneration> {
|
||||
let (config_tx, config_rx) = watch::channel(Arc::new(config.clone()));
|
||||
let (_admission_tx, admission_rx) = watch::channel(true);
|
||||
let stats = Arc::new(Stats::new());
|
||||
|
||||
@@ -607,6 +607,46 @@ pub(crate) fn print_proxy_links(host: &str, port: u16, config: &ProxyConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints WEB links only for profiles selected by the existing link policy.
|
||||
pub(crate) fn print_web_proxy_links(config: &ProxyConfig) {
|
||||
if !config.web.enabled || config.general.links.show.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(runtime) = config.web.runtime.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let shown = config
|
||||
.general
|
||||
.links
|
||||
.show
|
||||
.resolve_users(&config.access.users);
|
||||
let mut heading_printed = false;
|
||||
for profile in &runtime.profiles {
|
||||
if !shown.iter().any(|user| user.as_str() == profile.user) {
|
||||
continue;
|
||||
}
|
||||
if !heading_printed {
|
||||
print_maestro_line("WEB proxy links");
|
||||
heading_printed = true;
|
||||
}
|
||||
let Some(secret) = config.access.users.get(&profile.user) else {
|
||||
continue;
|
||||
};
|
||||
let prefix = match profile.secret_mode {
|
||||
crate::config::WebSecretMode::Plain => "",
|
||||
crate::config::WebSecretMode::Dd => "dd",
|
||||
};
|
||||
print_maestro_line(format!(
|
||||
"User: {} ({:?})",
|
||||
profile.user, profile.secret_mode
|
||||
));
|
||||
print_maestro_line(format!(
|
||||
"WEB: tg://webproxy?server={}&secret={prefix}{secret}",
|
||||
profile.host,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn write_beobachten_snapshot(path: &str, payload: &str) -> std::io::Result<()> {
|
||||
if let Some(parent) = std::path::Path::new(path).parent()
|
||||
&& !parent.as_os_str().is_empty()
|
||||
|
||||
@@ -6,11 +6,13 @@ use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::OwnedSemaphorePermit;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::config::RstOnCloseMode;
|
||||
use crate::config::{ListenerTransport, RstOnCloseMode};
|
||||
use crate::proxy::ClientHandler;
|
||||
use crate::transport::socket::set_linger_zero;
|
||||
use crate::web::manager::WebProcessRuntime;
|
||||
|
||||
use super::bind::BoundTcpListener;
|
||||
use super::plan::ListenerBindSpec;
|
||||
@@ -19,11 +21,15 @@ use crate::maestro::helpers::{
|
||||
expected_handshake_close_description, is_expected_handshake_eof, peer_close_description,
|
||||
};
|
||||
|
||||
/// One bound listener and all connection tasks accepted through its lifecycle.
|
||||
pub(super) struct ListenerSlot {
|
||||
pub(super) spec: ListenerBindSpec,
|
||||
listener: Arc<TcpListener>,
|
||||
cancellation: CancellationToken,
|
||||
task: Option<JoinHandle<()>>,
|
||||
connections: TaskTracker,
|
||||
web_runtime: Option<Arc<WebProcessRuntime>>,
|
||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
||||
}
|
||||
|
||||
enum PermitWait {
|
||||
@@ -181,6 +187,8 @@ async fn run_accept_loop(
|
||||
listener: Arc<TcpListener>,
|
||||
spec: ListenerBindSpec,
|
||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
||||
web_runtime: Option<Arc<WebProcessRuntime>>,
|
||||
connections: TaskTracker,
|
||||
cancellation: CancellationToken,
|
||||
) {
|
||||
loop {
|
||||
@@ -191,6 +199,26 @@ async fn run_accept_loop(
|
||||
};
|
||||
match accepted {
|
||||
Ok((stream, peer_addr)) => {
|
||||
if spec.transport == ListenerTransport::Web {
|
||||
let Some(web_runtime) = web_runtime.as_ref() else {
|
||||
error!(addr = %spec.addr, "WEB listener has no process runtime");
|
||||
return;
|
||||
};
|
||||
let Some(connection_permit) = web_runtime.try_http_connection() else {
|
||||
drop(stream);
|
||||
continue;
|
||||
};
|
||||
connections.spawn(crate::web::http::serve_connection(
|
||||
stream,
|
||||
peer_addr,
|
||||
spec.web_client_ip_source,
|
||||
Arc::clone(&spec.web_trusted_proxy_cidrs),
|
||||
Arc::clone(web_runtime),
|
||||
cancellation.clone(),
|
||||
connection_permit,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
let runtime = active_runtime.load_full();
|
||||
if !*runtime.admission_rx.borrow() {
|
||||
debug!(peer = %peer_addr, "Admission gate closed, dropping connection");
|
||||
@@ -232,12 +260,16 @@ impl ListenerSlot {
|
||||
pub(super) fn start(
|
||||
bound: BoundTcpListener,
|
||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
||||
web_runtime: Option<Arc<WebProcessRuntime>>,
|
||||
) -> Self {
|
||||
let cancellation = CancellationToken::new();
|
||||
let connections = TaskTracker::new();
|
||||
let task = tokio::spawn(run_accept_loop(
|
||||
bound.listener.clone(),
|
||||
bound.spec.clone(),
|
||||
active_runtime,
|
||||
active_runtime.clone(),
|
||||
web_runtime.clone(),
|
||||
connections.clone(),
|
||||
cancellation.clone(),
|
||||
));
|
||||
Self {
|
||||
@@ -245,6 +277,9 @@ impl ListenerSlot {
|
||||
listener: bound.listener,
|
||||
cancellation,
|
||||
task: Some(task),
|
||||
connections,
|
||||
web_runtime,
|
||||
active_runtime,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,15 +290,36 @@ impl ListenerSlot {
|
||||
format!("listener {} task failed: {error_value}", self.spec.addr)
|
||||
})?;
|
||||
}
|
||||
self.connections.close();
|
||||
let connection_stop_timeout = Duration::from_secs(
|
||||
self.active_runtime
|
||||
.load()
|
||||
.config()
|
||||
.web
|
||||
.timeouts
|
||||
.shutdown_secs,
|
||||
);
|
||||
tokio::time::timeout(connection_stop_timeout, self.connections.wait())
|
||||
.await
|
||||
.map_err(|_| {
|
||||
format!(
|
||||
"listener {} connection shutdown timed out",
|
||||
self.spec.addr
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn restart(&mut self, active_runtime: Arc<ArcSwap<RuntimeGeneration>>) {
|
||||
self.active_runtime = active_runtime.clone();
|
||||
self.cancellation = CancellationToken::new();
|
||||
self.connections = TaskTracker::new();
|
||||
self.task = Some(tokio::spawn(run_accept_loop(
|
||||
self.listener.clone(),
|
||||
self.spec.clone(),
|
||||
active_runtime,
|
||||
self.web_runtime.clone(),
|
||||
self.connections.clone(),
|
||||
self.cancellation.clone(),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ use tokio::net::TcpListener;
|
||||
use tokio::net::UnixListener;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::config::ProxyConfig;
|
||||
use crate::config::{ListenerTransport, ProxyConfig};
|
||||
use crate::startup::{COMPONENT_LISTENERS_BIND, StartupTracker};
|
||||
use crate::transport::find_listener_processes;
|
||||
use crate::transport::socket::{activate_listener_socket, bind_listener_socket};
|
||||
|
||||
use super::plan::{ListenerBindSpec, listener_bind_plan};
|
||||
use crate::maestro::helpers::print_proxy_links;
|
||||
use crate::maestro::helpers::{print_proxy_links, print_web_proxy_links};
|
||||
|
||||
/// Owns sockets bound before process accept loops start.
|
||||
pub(crate) struct BoundListeners {
|
||||
@@ -57,7 +57,8 @@ fn default_link_port(config: &ProxyConfig) -> u16 {
|
||||
config
|
||||
.server
|
||||
.listeners
|
||||
.first()
|
||||
.iter()
|
||||
.find(|listener| listener.transport == ListenerTransport::Mtproxy)
|
||||
.and_then(|listener| listener.port)
|
||||
.unwrap_or(config.server.port)
|
||||
}
|
||||
@@ -110,7 +111,7 @@ impl PreparedTcpListener {
|
||||
}
|
||||
|
||||
fn log_listener_profile(spec: &ListenerBindSpec) {
|
||||
info!(addr = %spec.addr, "Listening on TCP endpoint");
|
||||
info!(addr = %spec.addr, transport = ?spec.transport, "Listening on TCP endpoint");
|
||||
if let Some(client_mss) = spec.options.client_mss {
|
||||
info!(
|
||||
addr = %spec.addr,
|
||||
@@ -135,7 +136,11 @@ fn print_configured_links(
|
||||
detected_ip_v4: Option<IpAddr>,
|
||||
detected_ip_v6: Option<IpAddr>,
|
||||
) {
|
||||
print_web_proxy_links(config);
|
||||
for listener in &config.server.listeners {
|
||||
if listener.transport != ListenerTransport::Mtproxy {
|
||||
continue;
|
||||
}
|
||||
let port = listener.port.unwrap_or(config.server.port);
|
||||
let addr = SocketAddr::new(listener.ip, port);
|
||||
if !plan.contains_key(&addr) || config.general.links.public_host.is_some() {
|
||||
@@ -160,7 +165,12 @@ fn print_configured_links(
|
||||
}
|
||||
}
|
||||
|
||||
if config.general.links.show.is_empty() || config.general.links.public_host.is_none() {
|
||||
if config.general.links.show.is_empty()
|
||||
|| config.general.links.public_host.is_none()
|
||||
|| !plan
|
||||
.values()
|
||||
.any(|spec| spec.transport == ListenerTransport::Mtproxy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let host = config
|
||||
|
||||
@@ -5,11 +5,13 @@ use std::sync::Arc;
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
use crate::config::ProxyConfig;
|
||||
use crate::config::ListenerTransport;
|
||||
use crate::maestro::generation::RuntimeGeneration;
|
||||
|
||||
use super::accept::ListenerSlot;
|
||||
use super::bind::{BoundListeners, BoundTcpListener, PreparedTcpListener, prepare_listener};
|
||||
use super::plan::{ListenerBindSpec, listener_bind_plan};
|
||||
use crate::web::manager::WebProcessRuntime;
|
||||
#[cfg(unix)]
|
||||
use super::unix::UnixAcceptHandle;
|
||||
|
||||
@@ -17,16 +19,19 @@ use super::unix::UnixAcceptHandle;
|
||||
pub(crate) struct ListenerManager {
|
||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
||||
slots: BTreeMap<SocketAddr, ListenerSlot>,
|
||||
web_runtime: Option<Arc<WebProcessRuntime>>,
|
||||
#[cfg(unix)]
|
||||
unix: Option<UnixAcceptHandle>,
|
||||
}
|
||||
|
||||
/// Socket changes prepared without activating or stopping accept loops.
|
||||
pub(crate) struct PreparedListenerTransition {
|
||||
target_specs: BTreeMap<SocketAddr, ListenerBindSpec>,
|
||||
additions: Vec<PreparedTcpListener>,
|
||||
removals: Vec<SocketAddr>,
|
||||
}
|
||||
|
||||
/// Activated additions and stopped removals awaiting runtime publication.
|
||||
pub(crate) struct PendingListenerTransition {
|
||||
target_specs: BTreeMap<SocketAddr, ListenerBindSpec>,
|
||||
additions: Vec<BoundTcpListener>,
|
||||
@@ -39,10 +44,22 @@ impl ListenerManager {
|
||||
bound: BoundListeners,
|
||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
||||
) -> Self {
|
||||
let has_web = bound
|
||||
.listeners
|
||||
.iter()
|
||||
.any(|listener| listener.spec.transport == ListenerTransport::Web);
|
||||
let web_runtime = has_web.then(|| WebProcessRuntime::start(active_runtime.clone()));
|
||||
let mut slots = BTreeMap::new();
|
||||
for listener in bound.listeners {
|
||||
let addr = listener.spec.addr;
|
||||
slots.insert(addr, ListenerSlot::start(listener, active_runtime.clone()));
|
||||
slots.insert(
|
||||
addr,
|
||||
ListenerSlot::start(
|
||||
listener,
|
||||
active_runtime.clone(),
|
||||
web_runtime.clone(),
|
||||
),
|
||||
);
|
||||
}
|
||||
#[cfg(unix)]
|
||||
let unix = bound
|
||||
@@ -51,6 +68,7 @@ impl ListenerManager {
|
||||
Self {
|
||||
active_runtime,
|
||||
slots,
|
||||
web_runtime,
|
||||
#[cfg(unix)]
|
||||
unix,
|
||||
}
|
||||
@@ -61,6 +79,7 @@ impl ListenerManager {
|
||||
Self {
|
||||
active_runtime,
|
||||
slots: BTreeMap::new(),
|
||||
web_runtime: None,
|
||||
#[cfg(unix)]
|
||||
unix: None,
|
||||
}
|
||||
@@ -72,6 +91,20 @@ impl ListenerManager {
|
||||
desired: &ProxyConfig,
|
||||
) -> Result<Option<PreparedListenerTransition>, String> {
|
||||
let target_specs = listener_bind_plan(desired)?;
|
||||
let web_inventory_changed = self
|
||||
.slots
|
||||
.iter()
|
||||
.filter(|(_, slot)| slot.spec.transport == ListenerTransport::Web)
|
||||
.map(|(addr, slot)| (*addr, slot.spec.clone()))
|
||||
.collect::<BTreeMap<_, _>>()
|
||||
!= target_specs
|
||||
.iter()
|
||||
.filter(|(_, spec)| spec.transport == ListenerTransport::Web)
|
||||
.map(|(addr, spec)| (*addr, spec.clone()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
if web_inventory_changed {
|
||||
return Err("WEB listener inventory is process-owned; process restart required".to_string());
|
||||
}
|
||||
let current_addresses: BTreeSet<_> = self.slots.keys().copied().collect();
|
||||
let target_addresses: BTreeSet<_> = target_specs.keys().copied().collect();
|
||||
if current_addresses == target_addresses
|
||||
@@ -164,7 +197,11 @@ impl ListenerManager {
|
||||
let addr = listener.spec.addr;
|
||||
self.slots.insert(
|
||||
addr,
|
||||
ListenerSlot::start(listener, self.active_runtime.clone()),
|
||||
ListenerSlot::start(
|
||||
listener,
|
||||
self.active_runtime.clone(),
|
||||
self.web_runtime.clone(),
|
||||
),
|
||||
);
|
||||
}
|
||||
debug_assert_eq!(
|
||||
@@ -191,6 +228,9 @@ impl ListenerManager {
|
||||
errors.push(error_value);
|
||||
}
|
||||
self.slots.clear();
|
||||
if let Some(web_runtime) = self.web_runtime.take() {
|
||||
web_runtime.shutdown().await;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
self.unix = None;
|
||||
@@ -214,6 +254,7 @@ mod tests {
|
||||
fn listener_config(addr: SocketAddr) -> ListenerConfig {
|
||||
ListenerConfig {
|
||||
ip: addr.ip(),
|
||||
transport: crate::config::ListenerTransport::Mtproxy,
|
||||
port: Some(addr.port()),
|
||||
client_mss: None,
|
||||
synlimit: SynLimitMode::Off,
|
||||
@@ -229,6 +270,8 @@ mod tests {
|
||||
announce_ip: None,
|
||||
proxy_protocol: None,
|
||||
reuse_allow: false,
|
||||
web_client_ip_source: crate::config::WebClientIpSource::XForwardedFor,
|
||||
web_trusted_proxy_cidrs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,12 +280,15 @@ mod tests {
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let spec = ListenerBindSpec {
|
||||
addr,
|
||||
transport: crate::config::ListenerTransport::Mtproxy,
|
||||
options: ListenOptions {
|
||||
reuse_port: false,
|
||||
..Default::default()
|
||||
},
|
||||
proxy_protocol: false,
|
||||
tls_response_fragment_size: None,
|
||||
web_client_ip_source: crate::config::WebClientIpSource::XForwardedFor,
|
||||
web_trusted_proxy_cidrs: Arc::from([]),
|
||||
};
|
||||
(
|
||||
BoundTcpListener {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::{ProxyConfig, ServerConfig, SynLimitMode};
|
||||
use crate::config::{
|
||||
ListenerTransport, ProxyConfig, ServerConfig, SynLimitMode, WebClientIpSource,
|
||||
};
|
||||
use crate::transport::ListenOptions;
|
||||
|
||||
use super::tcp_mss_runtime_profile;
|
||||
@@ -10,9 +13,12 @@ use super::tcp_mss_runtime_profile;
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct ListenerBindSpec {
|
||||
pub(super) addr: SocketAddr,
|
||||
pub(super) transport: ListenerTransport,
|
||||
pub(super) options: ListenOptions,
|
||||
pub(super) proxy_protocol: bool,
|
||||
pub(super) tls_response_fragment_size: Option<u16>,
|
||||
pub(super) web_client_ip_source: WebClientIpSource,
|
||||
pub(super) web_trusted_proxy_cidrs: Arc<[ipnetwork::IpNetwork]>,
|
||||
}
|
||||
|
||||
fn listener_port_or_legacy(listener: &crate::config::ListenerConfig, server: &ServerConfig) -> u16 {
|
||||
@@ -40,16 +46,24 @@ pub(crate) fn listener_bind_plan(
|
||||
if addr.is_ipv6() && config.network.ipv6 == Some(false) {
|
||||
continue;
|
||||
}
|
||||
let configured_client_mss = listener
|
||||
.effective_client_mss(&config.server)
|
||||
.map_err(|error| format!("invalid client MSS for listener {addr}: {error}"))?;
|
||||
let configured_client_mss = if listener.transport == ListenerTransport::Web {
|
||||
None
|
||||
} else {
|
||||
listener
|
||||
.effective_client_mss(&config.server)
|
||||
.map_err(|error| format!("invalid client MSS for listener {addr}: {error}"))?
|
||||
};
|
||||
let listener_bulk_mss = (listener.transport != ListenerTransport::Web)
|
||||
.then_some(bulk_client_mss)
|
||||
.flatten();
|
||||
#[cfg(target_os = "linux")]
|
||||
let (client_mss, tls_response_fragment_size) =
|
||||
tcp_mss_runtime_profile(configured_client_mss, bulk_client_mss);
|
||||
tcp_mss_runtime_profile(configured_client_mss, listener_bulk_mss);
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let (client_mss, tls_response_fragment_size) = (configured_client_mss, None);
|
||||
let spec = ListenerBindSpec {
|
||||
addr,
|
||||
transport: listener.transport,
|
||||
options: ListenOptions {
|
||||
reuse_port: listener.reuse_allow,
|
||||
ipv6_only: listener.ip.is_ipv6(),
|
||||
@@ -61,6 +75,8 @@ pub(crate) fn listener_bind_plan(
|
||||
.proxy_protocol
|
||||
.unwrap_or(config.server.proxy_protocol),
|
||||
tls_response_fragment_size,
|
||||
web_client_ip_source: listener.web_client_ip_source,
|
||||
web_trusted_proxy_cidrs: Arc::from(listener.web_trusted_proxy_cidrs.clone()),
|
||||
};
|
||||
if plan.insert(addr, spec).is_some() {
|
||||
return Err(format!("duplicate effective listener endpoint: {addr}"));
|
||||
@@ -80,6 +96,23 @@ fn any_synlimit_enabled(config: &ProxyConfig) -> bool {
|
||||
|
||||
/// Returns whether an endpoint-only change can use coordinated process rebind.
|
||||
pub(crate) fn listener_rebind_supported(old: &ProxyConfig, desired: &ProxyConfig) -> bool {
|
||||
let Ok(old_plan) = listener_bind_plan(old) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(desired_plan) = listener_bind_plan(desired) else {
|
||||
return false;
|
||||
};
|
||||
let old_web = old_plan
|
||||
.iter()
|
||||
.filter(|(_, spec)| spec.transport == ListenerTransport::Web)
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let desired_web = desired_plan
|
||||
.iter()
|
||||
.filter(|(_, spec)| spec.transport == ListenerTransport::Web)
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
if old_web != desired_web {
|
||||
return false;
|
||||
}
|
||||
if any_synlimit_enabled(old) || any_synlimit_enabled(desired) {
|
||||
return false;
|
||||
}
|
||||
@@ -107,6 +140,7 @@ mod tests {
|
||||
fn listener(ip: &str, port: u16) -> ListenerConfig {
|
||||
ListenerConfig {
|
||||
ip: ip.parse().unwrap(),
|
||||
transport: crate::config::ListenerTransport::Mtproxy,
|
||||
port: Some(port),
|
||||
client_mss: None,
|
||||
synlimit: SynLimitMode::Off,
|
||||
@@ -122,6 +156,8 @@ mod tests {
|
||||
announce_ip: None,
|
||||
proxy_protocol: None,
|
||||
reuse_allow: false,
|
||||
web_client_ip_source: crate::config::WebClientIpSource::XForwardedFor,
|
||||
web_trusted_proxy_cidrs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ mod tests {
|
||||
fn listener_with_synlimit(synlimit: SynLimitMode) -> ListenerConfig {
|
||||
ListenerConfig {
|
||||
ip: "127.0.0.1".parse().unwrap(),
|
||||
transport: crate::config::ListenerTransport::Mtproxy,
|
||||
port: Some(443),
|
||||
client_mss: None,
|
||||
synlimit,
|
||||
@@ -146,6 +147,8 @@ mod tests {
|
||||
announce_ip: None,
|
||||
proxy_protocol: None,
|
||||
reuse_allow: false,
|
||||
web_client_ip_source: crate::config::WebClientIpSource::XForwardedFor,
|
||||
web_trusted_proxy_cidrs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -406,6 +406,16 @@ pub(crate) fn resolve_reload_config(
|
||||
fields.push("logging".to_string());
|
||||
effective.logging = old.logging.clone();
|
||||
}
|
||||
if serde_json::to_value(&old.web.limits).ok()
|
||||
!= serde_json::to_value(&desired.web.limits).ok()
|
||||
{
|
||||
fields.push("web.limits".to_string());
|
||||
effective.web.limits = old.web.limits.clone();
|
||||
if effective.rebuild_runtime_web().is_err() {
|
||||
fields.push("web".to_string());
|
||||
effective.web = old.web.clone();
|
||||
}
|
||||
}
|
||||
let runtime_changed = !configs_equal(old, &effective);
|
||||
ResolvedReloadConfig {
|
||||
effective,
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::*;
|
||||
fn test_listener(port: u16) -> crate::config::ListenerConfig {
|
||||
crate::config::ListenerConfig {
|
||||
ip: "127.0.0.1".parse().unwrap(),
|
||||
transport: crate::config::ListenerTransport::Mtproxy,
|
||||
port: Some(port),
|
||||
client_mss: None,
|
||||
synlimit: crate::config::SynLimitMode::Off,
|
||||
@@ -18,6 +19,8 @@ fn test_listener(port: u16) -> crate::config::ListenerConfig {
|
||||
announce_ip: None,
|
||||
proxy_protocol: None,
|
||||
reuse_allow: false,
|
||||
web_client_ip_source: crate::config::WebClientIpSource::XForwardedFor,
|
||||
web_trusted_proxy_cidrs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +83,7 @@ fn listener_announcement_is_runtime_owned_when_bind_identity_is_stable() {
|
||||
let mut old = ProxyConfig::default();
|
||||
old.server.listeners.push(crate::config::ListenerConfig {
|
||||
ip: "0.0.0.0".parse().unwrap(),
|
||||
transport: crate::config::ListenerTransport::Mtproxy,
|
||||
port: Some(443),
|
||||
client_mss: None,
|
||||
synlimit: crate::config::SynLimitMode::Off,
|
||||
@@ -95,6 +99,8 @@ fn listener_announcement_is_runtime_owned_when_bind_identity_is_stable() {
|
||||
announce_ip: None,
|
||||
proxy_protocol: None,
|
||||
reuse_allow: false,
|
||||
web_client_ip_source: crate::config::WebClientIpSource::XForwardedFor,
|
||||
web_trusted_proxy_cidrs: Vec::new(),
|
||||
});
|
||||
let mut desired = old.clone();
|
||||
desired.server.listeners[0].announce = Some("proxy.example".to_string());
|
||||
@@ -143,6 +149,27 @@ fn runtime_only_change_does_not_require_process_rebind() {
|
||||
assert!(deferred_process_fields(&old, &new).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_allocation_limits_are_deferred_until_restart() {
|
||||
let mut old = ProxyConfig::default();
|
||||
old.rebuild_runtime_user_auth().unwrap();
|
||||
old.rebuild_runtime_web().unwrap();
|
||||
let mut desired = old.clone();
|
||||
desired.web.limits.max_sessions_global += 1;
|
||||
|
||||
let resolved = resolve_reload_config(&old, &desired);
|
||||
|
||||
assert_eq!(
|
||||
resolved.deferred_process_fields,
|
||||
vec!["web.limits".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.effective.web.limits.max_sessions_global,
|
||||
old.web.limits.max_sessions_global
|
||||
);
|
||||
assert!(!resolved.runtime_changed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_middle_proxy_requires_a_prepared_pool() {
|
||||
assert!(strict_middle_proxy_unavailable(true, false, false));
|
||||
|
||||
Reference in New Issue
Block a user