mirror of
https://github.com/telemt/telemt.git
synced 2026-11-01 05:12:02 +03:00
Decoy Fasttrack Drafts
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -85,6 +85,10 @@ pub(super) fn warn_non_hot_changes(old: &ProxyConfig, new: &ProxyConfig, non_hot
|
||||
warned = true;
|
||||
warn!("config reload: server listener settings changed; restart required");
|
||||
}
|
||||
if old.web.decoy_fasttrack_mode != new.web.decoy_fasttrack_mode {
|
||||
warned = true;
|
||||
warn!("config reload: web.decoy_fasttrack_mode changed; restart required");
|
||||
}
|
||||
if old.censorship.tls_domain != new.censorship.tls_domain
|
||||
|| old.censorship.tls_domains != new.censorship.tls_domains
|
||||
|| old.censorship.tls_fetch_scope != new.censorship.tls_fetch_scope
|
||||
|
||||
@@ -343,8 +343,10 @@ pub(super) fn overlay_hot_fields(old: &ProxyConfig, new: &ProxyConfig) -> ProxyC
|
||||
cfg.access.user_max_unique_ips_mode = new.access.user_max_unique_ips_mode;
|
||||
cfg.access.user_max_unique_ips_window_secs = new.access.user_max_unique_ips_window_secs;
|
||||
let process_limits = cfg.web.limits.clone();
|
||||
let decoy_fasttrack_mode = cfg.web.decoy_fasttrack_mode;
|
||||
cfg.web = new.web.clone();
|
||||
cfg.web.limits = process_limits;
|
||||
cfg.web.decoy_fasttrack_mode = decoy_fasttrack_mode;
|
||||
if cfg.web.carrier_negotiation_enabled()
|
||||
&& cfg.web.carrier_learning
|
||||
&& cfg.web.limits.max_carrier_learning_entries < WEB_CARRIER_LEARNING_MIN_ENTRIES
|
||||
|
||||
@@ -123,6 +123,21 @@ fn web_debug_policy_is_hot_while_debug_capacity_is_process_owned() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoy_fasttrack_mode_is_deferred_until_restart() {
|
||||
let old = sample_config();
|
||||
let mut new = old.clone();
|
||||
new.web.decoy_fasttrack_mode = crate::config::WebDecoyFastTrackMode::Enforce;
|
||||
|
||||
let applied = overlay_hot_fields(&old, &new);
|
||||
|
||||
assert_eq!(
|
||||
applied.web.decoy_fasttrack_mode,
|
||||
old.web.decoy_fasttrack_mode
|
||||
);
|
||||
assert_eq!(HotFields::from_config(&old), HotFields::from_config(&applied));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hot_overlay_defers_learning_that_requires_new_process_capacity() {
|
||||
let mut old = sample_config();
|
||||
|
||||
@@ -36,6 +36,7 @@ pub(super) fn rebuild(config: &mut ProxyConfig) -> Result<()> {
|
||||
&mut static_bytes,
|
||||
)?;
|
||||
let mut profiles = Vec::with_capacity(vhost.profiles.len());
|
||||
let mut capability_table = Vec::with_capacity(vhost.profiles.len());
|
||||
let mut capabilities = HashSet::with_capacity(vhost.profiles.len());
|
||||
for profile in &vhost.profiles {
|
||||
let user_id = auth.user_id_by_name(&profile.user).ok_or_else(|| {
|
||||
@@ -84,6 +85,7 @@ pub(super) fn rebuild(config: &mut ProxyConfig) -> Result<()> {
|
||||
.max_streams_per_session
|
||||
.unwrap_or(config.web.limits.max_streams_per_session),
|
||||
});
|
||||
capability_table.push(capability);
|
||||
profiles.push(Arc::clone(&runtime_profile));
|
||||
runtime_profiles.push(runtime_profile);
|
||||
}
|
||||
@@ -91,9 +93,11 @@ pub(super) fn rebuild(config: &mut ProxyConfig) -> Result<()> {
|
||||
vhost.host.clone(),
|
||||
Arc::new(WebRuntimeVhost {
|
||||
host: vhost.host.clone(),
|
||||
decoy_fasttrack_mode: config.web.decoy_fasttrack_mode,
|
||||
decoy,
|
||||
decoy_header_secs: config.web.timeouts.decoy_header_secs,
|
||||
profiles,
|
||||
capabilities: capability_table.into_boxed_slice(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -266,6 +266,7 @@ const WEB_CONFIG_KEYS: &[&str] = &[
|
||||
"carriers",
|
||||
"carrier_learning",
|
||||
"carrier_negotiation_aggressiveness",
|
||||
"decoy_fasttrack_mode",
|
||||
"http_connection_capacity_action",
|
||||
"debug",
|
||||
"limits",
|
||||
|
||||
@@ -6,6 +6,7 @@ const WEB_DEBUG_GROUP_SCRATCH_BYTES: usize = 4 * 1024 * 1024;
|
||||
const WEB_CARRIER_LEARNING_ENTRY_BYTES: usize = 512;
|
||||
const WEB_LANE_STATE_BYTES: usize = 512;
|
||||
const WEB_OVERLOAD_CONNECTION_BYTES: usize = 4 * 1024;
|
||||
const WEB_CAPABILITY_INDEX_ENTRY_BYTES: usize = 32;
|
||||
|
||||
/// Validates process-wide body, header, queue, static, and debug reservations.
|
||||
pub(super) fn validate(limits: &WebLimitsConfig) -> Result<()> {
|
||||
@@ -65,6 +66,12 @@ pub(super) fn validate(limits: &WebLimitsConfig) -> Result<()> {
|
||||
.ok_or_else(|| {
|
||||
ProxyError::Config("web.carrier learning reservation overflowed usize".to_string())
|
||||
})?;
|
||||
let capability_index_reservation = limits
|
||||
.max_profiles
|
||||
.checked_mul(WEB_CAPABILITY_INDEX_ENTRY_BYTES)
|
||||
.ok_or_else(|| {
|
||||
ProxyError::Config("web capability index reservation overflowed usize".to_string())
|
||||
})?;
|
||||
let lane_state_reservation = limits
|
||||
.max_streams_per_session
|
||||
.checked_add(limits.max_tombstones_per_session)
|
||||
@@ -82,6 +89,7 @@ pub(super) fn validate(limits: &WebLimitsConfig) -> Result<()> {
|
||||
.and_then(|value| value.checked_add(status_pages))
|
||||
.and_then(|value| value.checked_add(debug_reservation))
|
||||
.and_then(|value| value.checked_add(carrier_learning_reservation))
|
||||
.and_then(|value| value.checked_add(capability_index_reservation))
|
||||
.and_then(|value| value.checked_add(lane_state_reservation))
|
||||
.and_then(|value| value.checked_add(http_header_reservation))
|
||||
.and_then(|value| value.checked_add(overload_connection_reservation))
|
||||
@@ -111,4 +119,14 @@ mod tests {
|
||||
};
|
||||
assert!(validate(&previous_envelope).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_index_reservation_rejects_size_overflow() {
|
||||
let limits = WebLimitsConfig {
|
||||
max_profiles: usize::MAX,
|
||||
..WebLimitsConfig::default()
|
||||
};
|
||||
let error = validate(&limits).unwrap_err().to_string();
|
||||
assert!(error.contains("web capability index reservation overflowed usize"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,12 @@ fn web_config_builds_canonical_runtime_snapshot() {
|
||||
.get("proxy.example.com")
|
||||
.expect("canonical WEB vhost");
|
||||
assert_eq!(vhost.profiles.len(), 1);
|
||||
assert_eq!(vhost.capabilities.len(), vhost.profiles.len());
|
||||
assert_eq!(vhost.capabilities[0], vhost.profiles[0].capability);
|
||||
assert_eq!(
|
||||
vhost.decoy_fasttrack_mode,
|
||||
WebDecoyFastTrackMode::Off
|
||||
);
|
||||
assert_eq!(vhost.profiles[0].user, "alice");
|
||||
assert_eq!(vhost.profiles[0].secret_mode, WebSecretMode::Dd);
|
||||
assert_eq!(vhost.profiles[0].carrier, WebCarrier::HttpsLanes);
|
||||
@@ -56,6 +62,45 @@ fn web_config_builds_canonical_runtime_snapshot() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_decoy_fasttrack_mode_is_typed_and_defaults_off() {
|
||||
let defaults = ProxyConfig::default();
|
||||
assert_eq!(
|
||||
defaults.web.decoy_fasttrack_mode,
|
||||
WebDecoyFastTrackMode::Off
|
||||
);
|
||||
|
||||
for (token, expected) in [
|
||||
("shadow", WebDecoyFastTrackMode::Shadow),
|
||||
("enforce", WebDecoyFastTrackMode::Enforce),
|
||||
] {
|
||||
let configured = WEB_CONFIG.replace(
|
||||
"carrier = \"https-lanes\"",
|
||||
&format!(
|
||||
"carrier = \"https-lanes\"\ndecoy_fasttrack_mode = \"{token}\""
|
||||
),
|
||||
);
|
||||
let config = load_config_from_temp_toml(&configured);
|
||||
assert_eq!(config.web.decoy_fasttrack_mode, expected);
|
||||
assert_eq!(
|
||||
config
|
||||
.web
|
||||
.runtime
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.vhosts["proxy.example.com"]
|
||||
.decoy_fasttrack_mode,
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
let invalid = WEB_CONFIG.replace(
|
||||
"carrier = \"https-lanes\"",
|
||||
"carrier = \"https-lanes\"\ndecoy_fasttrack_mode = \"automatic\"",
|
||||
);
|
||||
assert!(load_config_error_from_temp_toml(&invalid).contains("decoy_fasttrack_mode"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_http_connection_capacity_policy_is_bounded_and_configurable() {
|
||||
let configured = WEB_CONFIG
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ pub use server::{
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
pub use web::{
|
||||
WebCarrierNegotiationAggressiveness, WebConfig, WebDecoyConfig,
|
||||
WebCarrierNegotiationAggressiveness, WebConfig, WebDecoyConfig, WebDecoyFastTrackMode,
|
||||
WebHttpConnectionCapacityAction, WebLimitsConfig, WebProfileConfig, WebSecretMode,
|
||||
WebTimeoutsConfig, WebVhostConfig,
|
||||
};
|
||||
|
||||
@@ -12,6 +12,9 @@ use super::web_debug::WebDebugConfig;
|
||||
// Serialized WEB defaults remain separate from the runtime data model.
|
||||
mod defaults;
|
||||
use defaults::*;
|
||||
// Decoy fast-track policy remains isolated from the bulky WEB data model.
|
||||
mod fasttrack;
|
||||
pub use fasttrack::WebDecoyFastTrackMode;
|
||||
// Accepted-socket overload policy remains separate from the bulky WEB data model.
|
||||
mod overload;
|
||||
pub use overload::WebHttpConnectionCapacityAction;
|
||||
@@ -416,6 +419,9 @@ pub struct WebConfig {
|
||||
/// Controls the evidence thresholds used by automatic carrier ranking.
|
||||
#[serde(default)]
|
||||
pub carrier_negotiation_aggressiveness: WebCarrierNegotiationAggressiveness,
|
||||
/// Restart-only capability-scan policy for structurally impossible bridge requests.
|
||||
#[serde(default)]
|
||||
pub decoy_fasttrack_mode: WebDecoyFastTrackMode,
|
||||
/// Action applied when accepted HTTP connection capacity is exhausted.
|
||||
#[serde(default)]
|
||||
pub http_connection_capacity_action: WebHttpConnectionCapacityAction,
|
||||
@@ -465,6 +471,7 @@ impl Default for WebConfig {
|
||||
carriers: WebCarriers::default(),
|
||||
carrier_learning: default_web_carrier_learning(),
|
||||
carrier_negotiation_aggressiveness: WebCarrierNegotiationAggressiveness::default(),
|
||||
decoy_fasttrack_mode: WebDecoyFastTrackMode::default(),
|
||||
http_connection_capacity_action: WebHttpConnectionCapacityAction::default(),
|
||||
limits: WebLimitsConfig::default(),
|
||||
debug: WebDebugConfig::default(),
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Capability-scan policy for structurally impossible WEB bridge requests.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum WebDecoyFastTrackMode {
|
||||
/// Preserve the legacy full scan without collecting fast-track decisions.
|
||||
#[default]
|
||||
Off,
|
||||
/// Record eligible requests while preserving the legacy full scan.
|
||||
Shadow,
|
||||
/// Skip the scan only when the public request shape cannot open a bridge.
|
||||
Enforce,
|
||||
}
|
||||
|
||||
impl WebDecoyFastTrackMode {
|
||||
/// Complete fixed mode set in stable API and metric order.
|
||||
pub const ALL: [Self; 3] = [Self::Off, Self::Shadow, Self::Enforce];
|
||||
|
||||
/// Returns the stable serialized mode token.
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Off => "off",
|
||||
Self::Shadow => "shadow",
|
||||
Self::Enforce => "enforce",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,16 @@ pub(crate) struct WebRuntimeConfig {
|
||||
pub(crate) struct WebRuntimeVhost {
|
||||
/// Canonical lowercase ACE hostname.
|
||||
pub(crate) host: String,
|
||||
/// Restart-frozen decoy capability-scan policy.
|
||||
pub(crate) decoy_fasttrack_mode: WebDecoyFastTrackMode,
|
||||
/// Immutable ordinary-site fallback snapshot.
|
||||
pub(crate) decoy: WebRuntimeDecoy,
|
||||
/// Upstream connect and response-head deadline.
|
||||
pub(crate) decoy_header_secs: u64,
|
||||
/// Exact capability profiles accepted by this host.
|
||||
pub(crate) profiles: Vec<Arc<WebRuntimeProfile>>,
|
||||
/// Contiguous capability table aligned one-to-one with `profiles`.
|
||||
pub(crate) capabilities: Box<[[u8; 32]]>,
|
||||
}
|
||||
|
||||
/// Precomputed exact-user capability entry.
|
||||
|
||||
Reference in New Issue
Block a user