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
+1 -1
View File
@@ -37,7 +37,7 @@ use super::load::{LoadedConfig, ProxyConfig};
#[allow(unused_imports)]
use crate::config::{
CidrRateLimitKey, LogLevel, MeBindStaleMode, MeFloorMode, MeSocksKdfPolicy, MeTelemetryLevel,
MeWriterPickMode, WebDebugConfig, web_debug_fits_limits,
MeWriterPickMode, WEB_CARRIER_LEARNING_MIN_ENTRIES, WebDebugConfig, web_debug_fits_limits,
};
#[cfg(test)]
use crate::config::{ListenerConfig, SynLimitMode};
+10 -3
View File
@@ -345,15 +345,22 @@ pub(super) fn overlay_hot_fields(old: &ProxyConfig, new: &ProxyConfig) -> ProxyC
let process_limits = cfg.web.limits.clone();
cfg.web = new.web.clone();
cfg.web.limits = process_limits;
if cfg.web.carrier_negotiation_enabled()
&& cfg.web.carrier_learning
&& cfg.web.limits.max_carrier_learning_entries < WEB_CARRIER_LEARNING_MIN_ENTRIES
{
if old.web.carrier_learning != new.web.carrier_learning {
cfg.web.carrier_learning = old.web.carrier_learning;
} else {
cfg.web.carriers = old.web.carriers.clone();
}
}
if !web_debug_fits_limits(&cfg.web.debug, &cfg.web.limits) {
cfg.web.debug = old.web.debug.clone();
}
if cfg.rebuild_runtime_user_auth().is_err() {
cfg.runtime_user_auth = None;
}
if cfg.rebuild_runtime_web().is_err() {
cfg.web = old.web.clone();
}
cfg
}
+41
View File
@@ -123,6 +123,47 @@ fn web_debug_policy_is_hot_while_debug_capacity_is_process_owned() {
);
}
#[test]
fn hot_overlay_defers_learning_that_requires_new_process_capacity() {
let mut old = sample_config();
old.web.limits.max_carrier_learning_entries = 1;
old.web.carriers = crate::config::WebCarriers::Disabled;
old.web.carrier_learning = false;
let mut new = old.clone();
new.web.limits.max_carrier_learning_entries = 3;
new.web.carriers = crate::config::WebCarriers::Enabled(vec![
crate::config::WebCarrier::Websocket,
crate::config::WebCarrier::Https,
]);
new.web.carrier_learning = true;
let applied = overlay_hot_fields(&old, &new);
assert_eq!(applied.web.limits.max_carrier_learning_entries, 1);
assert!(applied.web.carrier_negotiation_enabled());
assert!(!applied.web.carrier_learning);
}
#[test]
fn hot_overlay_defers_carriers_for_dormant_learning_with_small_capacity() {
let mut old = sample_config();
old.web.limits.max_carrier_learning_entries = 1;
old.web.carriers = crate::config::WebCarriers::Disabled;
old.web.carrier_learning = true;
let mut new = old.clone();
new.web.limits.max_carrier_learning_entries = 3;
new.web.carriers = crate::config::WebCarriers::Enabled(vec![
crate::config::WebCarrier::Websocket,
crate::config::WebCarrier::Https,
]);
let applied = overlay_hot_fields(&old, &new);
assert_eq!(applied.web.limits.max_carrier_learning_entries, 1);
assert!(!applied.web.carrier_negotiation_enabled());
assert!(applied.web.carrier_learning);
}
#[test]
fn web_debug_prefix_requiring_deferred_capacity_is_not_hot_applied() {
let old = sample_config();
+11 -1
View File
@@ -161,7 +161,17 @@ fn reload_config_with_resolver(
}
let old_cfg = config_tx.borrow().clone();
let applied_cfg = overlay_hot_fields(&old_cfg, &new_cfg);
let mut applied_cfg = overlay_hot_fields(&old_cfg, &new_cfg);
if let Err(error) = applied_cfg
.validate_effective_web()
.and_then(|_| applied_cfg.rebuild_runtime_web())
{
error!(
"config reload: effective WEB validation failed: {}; keeping old config",
error
);
return Some(next_manifest);
}
let old_hot = HotFields::from_config(&old_cfg);
let applied_hot = HotFields::from_config(&applied_cfg);
let non_hot_changed = !config_equal(&applied_cfg, &new_cfg);
+5
View File
@@ -216,6 +216,11 @@ impl ProxyConfig {
runtime_web::rebuild(self)
}
/// Validates the mixed effective WEB snapshot after restart fields are retained.
pub(crate) fn validate_effective_web(&mut self) -> Result<()> {
validate_web::validate(self)
}
/// Revalidates decoy separation after restart-only listener fields are resolved.
pub(crate) fn validate_web_decoy_listener_separation(&self) -> Result<()> {
validate_web::validate_decoy_listener_separation(self)
+1 -1
View File
@@ -16,7 +16,7 @@ pub(super) fn validate(config: &WebConfig) -> Result<Vec<WebCarrier>> {
let candidates = config.carrier_candidates();
if config.carrier_negotiation_enabled()
&& config.carrier_learning
&& config.limits.max_carrier_learning_entries < 3
&& config.limits.max_carrier_learning_entries < WEB_CARRIER_LEARNING_MIN_ENTRIES
{
return config_error(
"web.limits.max_carrier_learning_entries must be >= 3 when carrier learning is enabled",
+1
View File
@@ -63,6 +63,7 @@ pub(crate) use web::{
};
#[allow(unused_imports)]
pub use web_carrier::{WebCarrier, WebCarriers};
pub(crate) use web_carrier::WEB_CARRIER_LEARNING_MIN_ENTRIES;
pub(crate) use web_debug::web_debug_fits_limits;
pub use web_debug::{WebDebugBodyCapture, WebDebugConfig};
+3
View File
@@ -1,5 +1,8 @@
use serde::{Deserialize, Serialize};
/// Minimum restart-owned entries required for one complete learning sample.
pub(crate) const WEB_CARRIER_LEARNING_MIN_ENTRIES: usize = 3;
/// Carrier selected for one newly issued WEB relay session.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]