WEB Carrier Counters + Status

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-08-31 21:39:09 +03:00
parent 66f2b8889f
commit 718ce0847e
51 changed files with 2252 additions and 453 deletions
+12
View File
@@ -248,6 +248,18 @@ impl ListenerManager {
);
}
/// Publishes one generation through the process-owned WEB policy fence.
pub(crate) fn activate_runtime_generation(
&self,
generation: Arc<RuntimeGeneration>,
) -> Arc<RuntimeGeneration> {
if let Some(runtime) = &self.web_runtime {
runtime.activate_generation(generation)
} else {
self.active_runtime.swap(generation)
}
}
/// Stops every accept task and applies one deadline to the complete WEB ingress.
pub(crate) async fn shutdown(&mut self) -> Result<(), String> {
self.web_control.publish(
+12 -3
View File
@@ -159,7 +159,13 @@ impl ReloadSupervisor {
.mark_phase(command.reload_id, ReloadPhase::Preparing)
.await;
let old_runtime = self.active_runtime.load_full();
let resolved = resolve_reload_config(&old_runtime.config(), &command.config);
let resolved = match resolve_reload_config(&old_runtime.config(), &command.config) {
Ok(resolved) => resolved,
Err(error) => {
self.control.fail(command.reload_id, error).await;
return;
}
};
self.control
.set_deferred_fields(command.reload_id, resolved.deferred_process_fields.clone())
.await;
@@ -292,8 +298,11 @@ impl ReloadSupervisor {
} else {
None
};
old_runtime.stop_accepting_sessions();
let replaced = self.active_runtime.swap(new_runtime.clone());
let replaced = {
let listener_manager = self.listener_manager.lock().await;
old_runtime.stop_accepting_sessions();
listener_manager.activate_runtime_generation(new_runtime.clone())
};
self.web_trace
.apply_policy(new_runtime.id, &new_runtime.config().web.debug);
config_watcher_activation.send_replace(true);
+32 -9
View File
@@ -5,7 +5,9 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::{RwLock, Semaphore, watch};
use crate::config::{ProxyConfig, ServerConfig, web_debug_fits_limits};
use crate::config::{
ProxyConfig, ServerConfig, WEB_CARRIER_LEARNING_MIN_ENTRIES, web_debug_fits_limits,
};
use crate::crypto::SecureRandom;
use crate::ip_tracker::UserIpTracker;
use crate::network::probe::{decide_network_capabilities, run_probe};
@@ -337,7 +339,7 @@ pub(crate) struct ResolvedReloadConfig {
pub(crate) fn resolve_reload_config(
old: &ProxyConfig,
desired: &ProxyConfig,
) -> ResolvedReloadConfig {
) -> Result<ResolvedReloadConfig, String> {
let mut effective = desired.clone();
let mut fields = Vec::new();
let listener_identity_matches = listeners_have_same_bind_identity(&old.server, &desired.server);
@@ -424,21 +426,39 @@ pub(crate) fn resolve_reload_config(
{
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();
}
if effective.web.carrier_negotiation_enabled()
&& effective.web.carrier_learning
&& effective.web.limits.max_carrier_learning_entries
< WEB_CARRIER_LEARNING_MIN_ENTRIES
{
if old.web.carrier_learning != desired.web.carrier_learning {
fields.push("web.carrier_learning".to_string());
effective.web.carrier_learning = old.web.carrier_learning;
} else {
fields.push("web.carriers".to_string());
effective.web.carriers = old.web.carriers.clone();
}
}
if !web_debug_fits_limits(&effective.web.debug, &effective.web.limits) {
fields.push("web.debug".to_string());
effective.web.debug = old.web.debug.clone();
}
effective
.validate_effective_web()
.map_err(|error| format!("effective WEB configuration is invalid: {error}"))?;
effective
.rebuild_runtime_user_auth()
.map_err(|error| format!("effective user runtime preparation failed: {error}"))?;
effective
.rebuild_runtime_web()
.map_err(|error| format!("effective WEB runtime preparation failed: {error}"))?;
let runtime_changed = !configs_equal(old, &effective);
ResolvedReloadConfig {
Ok(ResolvedReloadConfig {
effective,
deferred_process_fields: fields,
runtime_changed,
}
})
}
fn listeners_have_same_bind_identity(old: &ServerConfig, desired: &ServerConfig) -> bool {
@@ -469,8 +489,11 @@ fn listener_process_fields_equal(old: &ServerConfig, desired: &ServerConfig) ->
}
/// Returns process-owned fields that cannot change in the current generation.
pub(crate) fn deferred_process_fields(old: &ProxyConfig, new: &ProxyConfig) -> Vec<String> {
resolve_reload_config(old, new).deferred_process_fields
pub(crate) fn deferred_process_fields(
old: &ProxyConfig,
new: &ProxyConfig,
) -> Result<Vec<String>, String> {
resolve_reload_config(old, new).map(|resolved| resolved.deferred_process_fields)
}
fn configs_equal(old: &ProxyConfig, new: &ProxyConfig) -> bool {
+70 -17
View File
@@ -31,7 +31,7 @@ fn process_socket_and_logging_changes_are_deferred() {
new.server.listen_backlog = new.server.listen_backlog.saturating_add(1);
new.general.disable_colors = !new.general.disable_colors;
let fields = deferred_process_fields(&old, &new);
let fields = deferred_process_fields(&old, &new).unwrap();
assert!(fields.contains(&"server.listeners".to_string()));
assert!(fields.contains(&"general.disable_colors".to_string()));
}
@@ -43,7 +43,7 @@ fn global_mss_profiles_are_deferred_with_the_listener_socket_group() {
desired.server.client_mss = Some("92".to_string());
desired.server.client_mss_bulk = Some("1400".to_string());
let resolved = resolve_reload_config(&old, &desired);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert_eq!(
resolved.deferred_process_fields,
@@ -64,7 +64,7 @@ fn mixed_reload_retains_process_state_and_applies_runtime_state() {
desired.server.client_mss = Some("92".to_string());
desired.censorship.tls_domain = "reload.example".to_string();
let resolved = resolve_reload_config(&old, &desired);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert_eq!(resolved.effective.server.client_mss, old.server.client_mss);
assert_eq!(
@@ -105,7 +105,7 @@ fn listener_announcement_is_runtime_owned_when_bind_identity_is_stable() {
let mut desired = old.clone();
desired.server.listeners[0].announce = Some("proxy.example".to_string());
let resolved = resolve_reload_config(&old, &desired);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert!(resolved.deferred_process_fields.is_empty());
assert_eq!(
@@ -128,7 +128,7 @@ fn process_field_labels_are_stable_ordered_and_unique() {
.saturating_add(1);
desired.general.disable_colors = !desired.general.disable_colors;
let resolved = resolve_reload_config(&old, &desired);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert_eq!(
resolved.deferred_process_fields,
@@ -146,7 +146,7 @@ fn runtime_only_change_does_not_require_process_rebind() {
let old = ProxyConfig::default();
let mut new = old.clone();
new.censorship.tls_domain = "reload.example".to_string();
assert!(deferred_process_fields(&old, &new).is_empty());
assert!(deferred_process_fields(&old, &new).unwrap().is_empty());
}
#[test]
@@ -157,7 +157,7 @@ fn web_allocation_limits_are_deferred_until_restart() {
let mut desired = old.clone();
desired.web.limits.max_sessions_global += 1;
let resolved = resolve_reload_config(&old, &desired);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert_eq!(
resolved.deferred_process_fields,
@@ -170,6 +170,65 @@ fn web_allocation_limits_are_deferred_until_restart() {
assert!(!resolved.runtime_changed);
}
#[test]
fn enabling_learning_is_deferred_when_retained_capacity_is_too_small() {
let mut old = ProxyConfig::default();
old.web.limits.max_carrier_learning_entries = 1;
old.web.carriers = crate::config::WebCarriers::Disabled;
old.web.carrier_learning = false;
old.rebuild_runtime_user_auth().unwrap();
old.rebuild_runtime_web().unwrap();
let mut desired = old.clone();
desired.web.limits.max_carrier_learning_entries = 3;
desired.web.carriers = crate::config::WebCarriers::Enabled(vec![
crate::config::WebCarrier::Websocket,
crate::config::WebCarrier::Https,
]);
desired.web.carrier_learning = true;
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert_eq!(
resolved.deferred_process_fields,
vec!["web.limits".to_string(), "web.carrier_learning".to_string()]
);
assert_eq!(
resolved.effective.web.limits.max_carrier_learning_entries,
1
);
assert!(resolved.effective.web.carrier_negotiation_enabled());
assert!(!resolved.effective.web.carrier_learning);
}
#[test]
fn enabling_carriers_is_deferred_for_dormant_learning_with_small_capacity() {
let mut old = ProxyConfig::default();
old.web.limits.max_carrier_learning_entries = 1;
old.web.carriers = crate::config::WebCarriers::Disabled;
old.web.carrier_learning = true;
old.rebuild_runtime_user_auth().unwrap();
old.rebuild_runtime_web().unwrap();
let mut desired = old.clone();
desired.web.limits.max_carrier_learning_entries = 3;
desired.web.carriers = crate::config::WebCarriers::Enabled(vec![
crate::config::WebCarrier::Websocket,
crate::config::WebCarrier::Https,
]);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert_eq!(
resolved.deferred_process_fields,
vec!["web.limits".to_string(), "web.carriers".to_string()]
);
assert_eq!(
resolved.effective.web.limits.max_carrier_learning_entries,
1
);
assert!(!resolved.effective.web.carrier_negotiation_enabled());
assert!(resolved.effective.web.carrier_learning);
}
#[test]
fn web_debug_prefix_dependent_on_new_capacity_is_deferred_with_limits() {
let mut old = ProxyConfig::default();
@@ -179,7 +238,7 @@ fn web_debug_prefix_dependent_on_new_capacity_is_deferred_with_limits() {
desired.web.limits.max_body_bytes = 4 * 1024 * 1024;
desired.web.debug.body_prefix_bytes = 3 * 1024 * 1024;
let resolved = resolve_reload_config(&old, &desired);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert_eq!(
resolved.deferred_process_fields,
@@ -206,7 +265,7 @@ fn endpoint_only_listener_move_is_runtime_rebindable() {
let mut desired = old.clone();
desired.server.listeners[0].port = Some(8443);
let resolved = resolve_reload_config(&old, &desired);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert!(resolved.deferred_process_fields.is_empty());
assert_eq!(resolved.effective.server.listeners[0].port, Some(8443));
@@ -221,7 +280,7 @@ fn synlimited_endpoint_move_remains_restart_only() {
let mut desired = old.clone();
desired.server.listeners[0].port = Some(8443);
let resolved = resolve_reload_config(&old, &desired);
let resolved = resolve_reload_config(&old, &desired).unwrap();
assert_eq!(
resolved.deferred_process_fields,
@@ -253,11 +312,5 @@ fn deferred_listener_identity_cannot_create_an_effective_decoy_loop() {
];
assert!(desired.validate_web_decoy_listener_separation().is_ok());
let resolved = resolve_reload_config(&old, &desired);
assert!(
resolved
.effective
.validate_web_decoy_listener_separation()
.is_err()
);
assert!(resolve_reload_config(&old, &desired).is_err());
}