From 3b9919fa4d8dfecf70e09a471fb6f4e7de31bd9b Mon Sep 17 00:00:00 2001 From: sintanial Date: Sat, 21 Mar 2026 15:29:14 +0300 Subject: [PATCH 1/2] Apply [timeouts] tg_connect to upstream DC TCP connect attempts Wire config.timeouts.tg_connect into UpstreamManager; per-attempt timeout uses the same .max(1) pattern as connect_budget_ms. Reject timeouts.tg_connect = 0 at config load (consistent with general.upstream_connect_budget_ms and related checks). Default when the key is omitted remains default_connect_timeout() via serde. Fixes telemt/telemt#439 --- src/config/load.rs | 26 ++++++++++++++++++++++++++ src/maestro/mod.rs | 1 + src/transport/upstream.rs | 11 +++++++---- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/config/load.rs b/src/config/load.rs index 7892e2c..e37be71 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -346,6 +346,12 @@ impl ProxyConfig { )); } + if config.timeouts.tg_connect == 0 { + return Err(ProxyError::Config( + "timeouts.tg_connect must be > 0".to_string(), + )); + } + if config.general.upstream_unhealthy_fail_threshold == 0 { return Err(ProxyError::Config( "general.upstream_unhealthy_fail_threshold must be > 0".to_string(), @@ -1907,6 +1913,26 @@ mod tests { let _ = std::fs::remove_file(path); } + #[test] + fn tg_connect_zero_is_rejected() { + let toml = r#" + [timeouts] + tg_connect = 0 + + [censorship] + tls_domain = "example.com" + + [access.users] + user = "00000000000000000000000000000000" + "#; + let dir = std::env::temp_dir(); + let path = dir.join("telemt_tg_connect_zero_test.toml"); + std::fs::write(&path, toml).unwrap(); + let err = ProxyConfig::load(&path).unwrap_err().to_string(); + assert!(err.contains("timeouts.tg_connect must be > 0")); + let _ = std::fs::remove_file(path); + } + #[test] fn rpc_proxy_req_every_out_of_range_is_rejected() { let toml = r#" diff --git a/src/maestro/mod.rs b/src/maestro/mod.rs index 5f3fd3a..57306df 100644 --- a/src/maestro/mod.rs +++ b/src/maestro/mod.rs @@ -223,6 +223,7 @@ pub async fn run() -> std::result::Result<(), Box> { config.general.upstream_connect_retry_attempts, config.general.upstream_connect_retry_backoff_ms, config.general.upstream_connect_budget_ms, + config.timeouts.tg_connect, config.general.upstream_unhealthy_fail_threshold, config.general.upstream_connect_failfast_hard_errors, stats.clone(), diff --git a/src/transport/upstream.rs b/src/transport/upstream.rs index 1120eae..5899ee1 100644 --- a/src/transport/upstream.rs +++ b/src/transport/upstream.rs @@ -34,8 +34,6 @@ const NUM_DCS: usize = 5; /// Timeout for individual DC ping attempt const DC_PING_TIMEOUT_SECS: u64 = 5; -/// Timeout for direct TG DC TCP connect readiness. -const DIRECT_CONNECT_TIMEOUT_SECS: u64 = 10; /// Interval between upstream health-check cycles. const HEALTH_CHECK_INTERVAL_SECS: u64 = 30; /// Timeout for a single health-check connect attempt. @@ -319,6 +317,8 @@ pub struct UpstreamManager { connect_retry_attempts: u32, connect_retry_backoff: Duration, connect_budget: Duration, + /// Per-attempt TCP connect timeout to Telegram DC (`[timeouts] tg_connect`, seconds). + tg_connect_timeout_secs: u64, unhealthy_fail_threshold: u32, connect_failfast_hard_errors: bool, no_upstreams_warn_epoch_ms: Arc, @@ -332,6 +332,7 @@ impl UpstreamManager { connect_retry_attempts: u32, connect_retry_backoff_ms: u64, connect_budget_ms: u64, + tg_connect_timeout_secs: u64, unhealthy_fail_threshold: u32, connect_failfast_hard_errors: bool, stats: Arc, @@ -347,6 +348,7 @@ impl UpstreamManager { connect_retry_attempts: connect_retry_attempts.max(1), connect_retry_backoff: Duration::from_millis(connect_retry_backoff_ms), connect_budget: Duration::from_millis(connect_budget_ms.max(1)), + tg_connect_timeout_secs: tg_connect_timeout_secs.max(1), unhealthy_fail_threshold: unhealthy_fail_threshold.max(1), connect_failfast_hard_errors, no_upstreams_warn_epoch_ms: Arc::new(AtomicU64::new(0)), @@ -797,8 +799,8 @@ impl UpstreamManager { break; } let remaining_budget = self.connect_budget.saturating_sub(elapsed); - let attempt_timeout = - Duration::from_secs(DIRECT_CONNECT_TIMEOUT_SECS).min(remaining_budget); + let attempt_timeout = Duration::from_secs(self.tg_connect_timeout_secs) + .min(remaining_budget); if attempt_timeout.is_zero() { last_error = Some(ProxyError::ConnectionTimeout { addr: target.to_string(), @@ -1901,6 +1903,7 @@ mod tests { 1, 100, 1000, + 10, 1, false, Arc::new(Stats::new()), From d06ac222d622f504c78928fdb036a3394b1ba5e1 Mon Sep 17 00:00:00 2001 From: sintanial Date: Sat, 28 Mar 2026 14:23:43 +0300 Subject: [PATCH 2/2] fix: move tg_connect to general, rustfmt upstream, fix UpstreamManager::new tests - Relocate tg_connect from [timeouts] to [general] with validation and docs updates. - Apply rustfmt to per-attempt upstream connect timeout expression in upstream.rs. - Pass tg_connect_timeout_secs in all UpstreamManager::new test call sites. - Wire hot reload and runtime snapshot to general.tg_connect. --- docs/CONFIG_PARAMS.en.md | 2 +- src/api/runtime_zero.rs | 2 +- src/cli.rs | 2 +- src/config/hot_reload.rs | 1 + src/config/load.rs | 8 +++--- src/config/types.rs | 9 ++++--- src/maestro/mod.rs | 2 +- .../tests/client_clever_advanced_tests.rs | 6 +++++ .../tests/client_deep_invariants_tests.rs | 2 ++ .../client_masking_blackhat_campaign_tests.rs | 1 + .../client_masking_budget_security_tests.rs | 1 + ...ient_masking_diagnostics_security_tests.rs | 1 + ...ng_fragmented_classifier_security_tests.rs | 1 + .../client_masking_hard_adversarial_tests.rs | 1 + ...http2_fragmented_preface_security_tests.rs | 1 + ...fig_pipeline_integration_security_tests.rs | 1 + ...sking_prefetch_invariant_security_tests.rs | 1 + ...nt_masking_probe_evasion_blackhat_tests.rs | 1 + ...ent_masking_redteam_expected_fail_tests.rs | 4 +++ ...nt_masking_replay_timing_security_tests.rs | 1 + ...sifier_fuzz_redteam_expected_fail_tests.rs | 1 + ...sking_shape_hardening_adversarial_tests.rs | 1 + ...e_hardening_redteam_expected_fail_tests.rs | 1 + ..._masking_shape_hardening_security_tests.rs | 1 + ...client_masking_stress_adversarial_tests.rs | 1 + src/proxy/tests/client_more_advanced_tests.rs | 3 +++ src/proxy/tests/client_security_tests.rs | 25 +++++++++++++++++++ ...client_timing_profile_adversarial_tests.rs | 1 + ...ent_tls_clienthello_size_security_tests.rs | 1 + ...lienthello_truncation_adversarial_tests.rs | 1 + ...ent_tls_mtproto_fallback_security_tests.rs | 1 + .../tests/direct_relay_security_tests.rs | 5 ++++ src/transport/upstream.rs | 6 ++--- 33 files changed, 81 insertions(+), 15 deletions(-) diff --git a/docs/CONFIG_PARAMS.en.md b/docs/CONFIG_PARAMS.en.md index eda2435..4d4467d 100644 --- a/docs/CONFIG_PARAMS.en.md +++ b/docs/CONFIG_PARAMS.en.md @@ -91,6 +91,7 @@ This document lists all configuration keys accepted by `config.toml`. | upstream_connect_retry_attempts | `u32` | `2` | Must be `> 0`. | Connect attempts for selected upstream before error/fallback. | | upstream_connect_retry_backoff_ms | `u64` | `100` | — | Delay between upstream connect attempts (ms). | | upstream_connect_budget_ms | `u64` | `3000` | Must be `> 0`. | Total wall-clock budget for one upstream connect request (ms). | +| tg_connect | `u64` | `10` | Must be `> 0`. | Per-attempt upstream TCP connect timeout to Telegram DC (seconds). | | upstream_unhealthy_fail_threshold | `u32` | `5` | Must be `> 0`. | Consecutive failed requests before upstream is marked unhealthy. | | upstream_connect_failfast_hard_errors | `bool` | `false` | — | Skips additional retries for hard non-transient connect errors. | | stun_iface_mismatch_ignore | `bool` | `false` | none | Reserved compatibility flag in current runtime revision. | @@ -249,7 +250,6 @@ Note: When `server.proxy_protocol` is enabled, incoming PROXY protocol headers a | relay_client_idle_soft_secs | `u64` | `120` | Must be `> 0`; must be `<= relay_client_idle_hard_secs`. | Soft idle threshold for middle-relay client uplink inactivity (seconds). | | relay_client_idle_hard_secs | `u64` | `360` | Must be `> 0`; must be `>= relay_client_idle_soft_secs`. | Hard idle threshold for middle-relay client uplink inactivity (seconds). | | relay_idle_grace_after_downstream_activity_secs | `u64` | `30` | Must be `<= relay_client_idle_hard_secs`. | Extra hard-idle grace after recent downstream activity (seconds). | -| tg_connect | `u64` | `10` | — | Upstream Telegram connect timeout. | | client_keepalive | `u64` | `15` | — | Client keepalive timeout. | | client_ack | `u64` | `90` | — | Client ACK timeout. | | me_one_retry | `u8` | `12` | none | Fast reconnect attempts budget for single-endpoint DC scenarios. | diff --git a/src/api/runtime_zero.rs b/src/api/runtime_zero.rs index 0ed84a8..160b27a 100644 --- a/src/api/runtime_zero.rs +++ b/src/api/runtime_zero.rs @@ -228,7 +228,7 @@ pub(super) fn build_limits_effective_data(cfg: &ProxyConfig) -> EffectiveLimitsD me_pool_force_close_secs: cfg.general.effective_me_pool_force_close_secs(), timeouts: EffectiveTimeoutLimits { client_handshake_secs: cfg.timeouts.client_handshake, - tg_connect_secs: cfg.timeouts.tg_connect, + tg_connect_secs: cfg.general.tg_connect, client_keepalive_secs: cfg.timeouts.client_keepalive, client_ack_secs: cfg.timeouts.client_ack, me_one_retry: cfg.timeouts.me_one_retry, diff --git a/src/cli.rs b/src/cli.rs index 6dc0e2a..d4f312a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -207,6 +207,7 @@ me_pool_drain_soft_evict_cooldown_ms = 1000 me_bind_stale_mode = "never" me_pool_min_fresh_ratio = 0.8 me_reinit_drain_timeout_secs = 90 +tg_connect = 10 [network] ipv4 = true @@ -233,7 +234,6 @@ ip = "::" [timeouts] client_handshake = 15 -tg_connect = 10 client_keepalive = 60 client_ack = 300 diff --git a/src/config/hot_reload.rs b/src/config/hot_reload.rs index 9bd2927..bef933c 100644 --- a/src/config/hot_reload.rs +++ b/src/config/hot_reload.rs @@ -695,6 +695,7 @@ fn warn_non_hot_changes(old: &ProxyConfig, new: &ProxyConfig, non_hot_changed: b if old.general.upstream_connect_retry_attempts != new.general.upstream_connect_retry_attempts || old.general.upstream_connect_retry_backoff_ms != new.general.upstream_connect_retry_backoff_ms + || old.general.tg_connect != new.general.tg_connect || old.general.upstream_unhealthy_fail_threshold != new.general.upstream_unhealthy_fail_threshold || old.general.upstream_connect_failfast_hard_errors diff --git a/src/config/load.rs b/src/config/load.rs index e37be71..75ae1e9 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -346,9 +346,9 @@ impl ProxyConfig { )); } - if config.timeouts.tg_connect == 0 { + if config.general.tg_connect == 0 { return Err(ProxyError::Config( - "timeouts.tg_connect must be > 0".to_string(), + "general.tg_connect must be > 0".to_string(), )); } @@ -1916,7 +1916,7 @@ mod tests { #[test] fn tg_connect_zero_is_rejected() { let toml = r#" - [timeouts] + [general] tg_connect = 0 [censorship] @@ -1929,7 +1929,7 @@ mod tests { let path = dir.join("telemt_tg_connect_zero_test.toml"); std::fs::write(&path, toml).unwrap(); let err = ProxyConfig::load(&path).unwrap_err().to_string(); - assert!(err.contains("timeouts.tg_connect must be > 0")); + assert!(err.contains("general.tg_connect must be > 0")); let _ = std::fs::remove_file(path); } diff --git a/src/config/types.rs b/src/config/types.rs index cb14747..f973ee2 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -663,6 +663,10 @@ pub struct GeneralConfig { #[serde(default = "default_upstream_connect_budget_ms")] pub upstream_connect_budget_ms: u64, + /// Per-attempt TCP connect timeout to Telegram DC (seconds). + #[serde(default = "default_connect_timeout")] + pub tg_connect: u64, + /// Consecutive failed requests before upstream is marked unhealthy. #[serde(default = "default_upstream_unhealthy_fail_threshold")] pub upstream_unhealthy_fail_threshold: u32, @@ -1007,6 +1011,7 @@ impl Default for GeneralConfig { upstream_connect_retry_attempts: default_upstream_connect_retry_attempts(), upstream_connect_retry_backoff_ms: default_upstream_connect_retry_backoff_ms(), upstream_connect_budget_ms: default_upstream_connect_budget_ms(), + tg_connect: default_connect_timeout(), upstream_unhealthy_fail_threshold: default_upstream_unhealthy_fail_threshold(), upstream_connect_failfast_hard_errors: default_upstream_connect_failfast_hard_errors(), stun_iface_mismatch_ignore: false, @@ -1329,9 +1334,6 @@ pub struct TimeoutsConfig { #[serde(default = "default_relay_idle_grace_after_downstream_activity_secs")] pub relay_idle_grace_after_downstream_activity_secs: u64, - #[serde(default = "default_connect_timeout")] - pub tg_connect: u64, - #[serde(default = "default_keepalive")] pub client_keepalive: u64, @@ -1356,7 +1358,6 @@ impl Default for TimeoutsConfig { relay_client_idle_hard_secs: default_relay_client_idle_hard_secs(), relay_idle_grace_after_downstream_activity_secs: default_relay_idle_grace_after_downstream_activity_secs(), - tg_connect: default_connect_timeout(), client_keepalive: default_keepalive(), client_ack: default_ack_timeout(), me_one_retry: default_me_one_retry(), diff --git a/src/maestro/mod.rs b/src/maestro/mod.rs index 57306df..0f9c06d 100644 --- a/src/maestro/mod.rs +++ b/src/maestro/mod.rs @@ -223,7 +223,7 @@ pub async fn run() -> std::result::Result<(), Box> { config.general.upstream_connect_retry_attempts, config.general.upstream_connect_retry_backoff_ms, config.general.upstream_connect_budget_ms, - config.timeouts.tg_connect, + config.general.tg_connect, config.general.upstream_unhealthy_fail_threshold, config.general.upstream_connect_failfast_hard_errors, stats.clone(), diff --git a/src/proxy/tests/client_clever_advanced_tests.rs b/src/proxy/tests/client_clever_advanced_tests.rs index f462ed8..51beb24 100644 --- a/src/proxy/tests/client_clever_advanced_tests.rs +++ b/src/proxy/tests/client_clever_advanced_tests.rs @@ -94,6 +94,7 @@ async fn adversarial_tls_handshake_timeout_during_masking_delay() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -141,6 +142,7 @@ async fn blackhat_proxy_protocol_slowloris_timeout() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -193,6 +195,7 @@ async fn negative_proxy_protocol_enabled_but_client_sends_tls_hello() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -239,6 +242,7 @@ async fn edge_client_stream_exactly_4_bytes_eof() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -282,6 +286,7 @@ async fn edge_client_stream_tls_header_valid_but_body_1_byte_short_eof() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -328,6 +333,7 @@ async fn integration_non_tls_modes_disabled_immediately_masks() { 1, 1, 1, + 10, 1, false, stats.clone(), diff --git a/src/proxy/tests/client_deep_invariants_tests.rs b/src/proxy/tests/client_deep_invariants_tests.rs index e57f817..7a08ccc 100644 --- a/src/proxy/tests/client_deep_invariants_tests.rs +++ b/src/proxy/tests/client_deep_invariants_tests.rs @@ -47,6 +47,7 @@ async fn invariant_tls_clienthello_truncation_exact_boundary_triggers_masking() 1, 1, 1, + 10, 1, false, stats.clone(), @@ -177,6 +178,7 @@ async fn invariant_direct_mode_partial_header_eof_is_error_not_bad_connect() { 1, 1, 1, + 10, 1, false, stats.clone(), diff --git a/src/proxy/tests/client_masking_blackhat_campaign_tests.rs b/src/proxy/tests/client_masking_blackhat_campaign_tests.rs index 88d4a58..917e799 100644 --- a/src/proxy/tests/client_masking_blackhat_campaign_tests.rs +++ b/src/proxy/tests/client_masking_blackhat_campaign_tests.rs @@ -40,6 +40,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_budget_security_tests.rs b/src/proxy/tests/client_masking_budget_security_tests.rs index d98c780..332451c 100644 --- a/src/proxy/tests/client_masking_budget_security_tests.rs +++ b/src/proxy/tests/client_masking_budget_security_tests.rs @@ -36,6 +36,7 @@ fn build_harness(config: ProxyConfig) -> PipelineHarness { 1, 1, 1, + 10, 1, false, stats.clone(), diff --git a/src/proxy/tests/client_masking_diagnostics_security_tests.rs b/src/proxy/tests/client_masking_diagnostics_security_tests.rs index 0d9ca99..67b797b 100644 --- a/src/proxy/tests/client_masking_diagnostics_security_tests.rs +++ b/src/proxy/tests/client_masking_diagnostics_security_tests.rs @@ -20,6 +20,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_fragmented_classifier_security_tests.rs b/src/proxy/tests/client_masking_fragmented_classifier_security_tests.rs index d7ac4ef..8fa2689 100644 --- a/src/proxy/tests/client_masking_fragmented_classifier_security_tests.rs +++ b/src/proxy/tests/client_masking_fragmented_classifier_security_tests.rs @@ -20,6 +20,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_hard_adversarial_tests.rs b/src/proxy/tests/client_masking_hard_adversarial_tests.rs index 65e66d3..c6b0e98 100644 --- a/src/proxy/tests/client_masking_hard_adversarial_tests.rs +++ b/src/proxy/tests/client_masking_hard_adversarial_tests.rs @@ -34,6 +34,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_http2_fragmented_preface_security_tests.rs b/src/proxy/tests/client_masking_http2_fragmented_preface_security_tests.rs index 3036f95..b5a8b4d 100644 --- a/src/proxy/tests/client_masking_http2_fragmented_preface_security_tests.rs +++ b/src/proxy/tests/client_masking_http2_fragmented_preface_security_tests.rs @@ -20,6 +20,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_prefetch_config_pipeline_integration_security_tests.rs b/src/proxy/tests/client_masking_prefetch_config_pipeline_integration_security_tests.rs index e64dc03..b3fd5cb 100644 --- a/src/proxy/tests/client_masking_prefetch_config_pipeline_integration_security_tests.rs +++ b/src/proxy/tests/client_masking_prefetch_config_pipeline_integration_security_tests.rs @@ -20,6 +20,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_prefetch_invariant_security_tests.rs b/src/proxy/tests/client_masking_prefetch_invariant_security_tests.rs index b49db3c..b57ad51 100644 --- a/src/proxy/tests/client_masking_prefetch_invariant_security_tests.rs +++ b/src/proxy/tests/client_masking_prefetch_invariant_security_tests.rs @@ -47,6 +47,7 @@ fn build_harness(secret_hex: &str, mask_port: u16) -> PipelineHarness { 1, 1, 1, + 10, 1, false, stats.clone(), diff --git a/src/proxy/tests/client_masking_probe_evasion_blackhat_tests.rs b/src/proxy/tests/client_masking_probe_evasion_blackhat_tests.rs index f7229ce..9ab5f78 100644 --- a/src/proxy/tests/client_masking_probe_evasion_blackhat_tests.rs +++ b/src/proxy/tests/client_masking_probe_evasion_blackhat_tests.rs @@ -25,6 +25,7 @@ fn make_test_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_redteam_expected_fail_tests.rs b/src/proxy/tests/client_masking_redteam_expected_fail_tests.rs index 50aa44c..2b6f600 100644 --- a/src/proxy/tests/client_masking_redteam_expected_fail_tests.rs +++ b/src/proxy/tests/client_masking_redteam_expected_fail_tests.rs @@ -48,6 +48,7 @@ fn build_harness(secret_hex: &str, mask_port: u16) -> RedTeamHarness { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -237,6 +238,7 @@ async fn redteam_03_masking_duration_must_be_less_than_1ms_when_backend_down() { 1, 1, 1, + 10, 1, false, Arc::new(Stats::new()), @@ -477,6 +479,7 @@ async fn measure_invalid_probe_duration_ms(delay_ms: u64, tls_len: u16, body_sen 1, 1, 1, + 10, 1, false, Arc::new(Stats::new()), @@ -550,6 +553,7 @@ async fn capture_forwarded_probe_len(tls_len: u16, body_sent: usize) -> usize { 1, 1, 1, + 10, 1, false, Arc::new(Stats::new()), diff --git a/src/proxy/tests/client_masking_replay_timing_security_tests.rs b/src/proxy/tests/client_masking_replay_timing_security_tests.rs index c3339e8..97ed52a 100644 --- a/src/proxy/tests/client_masking_replay_timing_security_tests.rs +++ b/src/proxy/tests/client_masking_replay_timing_security_tests.rs @@ -22,6 +22,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_shape_classifier_fuzz_redteam_expected_fail_tests.rs b/src/proxy/tests/client_masking_shape_classifier_fuzz_redteam_expected_fail_tests.rs index 3a01a69..c4dd4db 100644 --- a/src/proxy/tests/client_masking_shape_classifier_fuzz_redteam_expected_fail_tests.rs +++ b/src/proxy/tests/client_masking_shape_classifier_fuzz_redteam_expected_fail_tests.rs @@ -20,6 +20,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_shape_hardening_adversarial_tests.rs b/src/proxy/tests/client_masking_shape_hardening_adversarial_tests.rs index 48e94a5..2cf98c4 100644 --- a/src/proxy/tests/client_masking_shape_hardening_adversarial_tests.rs +++ b/src/proxy/tests/client_masking_shape_hardening_adversarial_tests.rs @@ -20,6 +20,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_shape_hardening_redteam_expected_fail_tests.rs b/src/proxy/tests/client_masking_shape_hardening_redteam_expected_fail_tests.rs index f91e687..b0bf73e 100644 --- a/src/proxy/tests/client_masking_shape_hardening_redteam_expected_fail_tests.rs +++ b/src/proxy/tests/client_masking_shape_hardening_redteam_expected_fail_tests.rs @@ -20,6 +20,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_shape_hardening_security_tests.rs b/src/proxy/tests/client_masking_shape_hardening_security_tests.rs index f2bec42..7d2380b 100644 --- a/src/proxy/tests/client_masking_shape_hardening_security_tests.rs +++ b/src/proxy/tests/client_masking_shape_hardening_security_tests.rs @@ -20,6 +20,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_masking_stress_adversarial_tests.rs b/src/proxy/tests/client_masking_stress_adversarial_tests.rs index 5c00c63..1c8b599 100644 --- a/src/proxy/tests/client_masking_stress_adversarial_tests.rs +++ b/src/proxy/tests/client_masking_stress_adversarial_tests.rs @@ -34,6 +34,7 @@ fn new_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_more_advanced_tests.rs b/src/proxy/tests/client_more_advanced_tests.rs index 8f9d832..21cf96c 100644 --- a/src/proxy/tests/client_more_advanced_tests.rs +++ b/src/proxy/tests/client_more_advanced_tests.rs @@ -100,6 +100,7 @@ async fn blackhat_proxy_protocol_massive_garbage_rejected_quickly() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -146,6 +147,7 @@ async fn edge_tls_body_immediate_eof_triggers_masking_and_bad_connect() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -195,6 +197,7 @@ async fn security_classic_mode_disabled_masks_valid_length_payload() { 1, 1, 1, + 10, 1, false, stats.clone(), diff --git a/src/proxy/tests/client_security_tests.rs b/src/proxy/tests/client_security_tests.rs index 1b46c6d..7fc1afe 100644 --- a/src/proxy/tests/client_security_tests.rs +++ b/src/proxy/tests/client_security_tests.rs @@ -339,6 +339,7 @@ async fn relay_task_abort_releases_user_gate_and_ip_reservation() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -452,6 +453,7 @@ async fn relay_cutover_releases_user_gate_and_ip_reservation() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -575,6 +577,7 @@ async fn integration_route_cutover_and_quota_overlap_fails_closed_and_releases_s 1, 1, 1, + 10, 1, false, stats.clone(), @@ -744,6 +747,7 @@ async fn proxy_protocol_header_is_rejected_when_trust_list_is_empty() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -820,6 +824,7 @@ async fn proxy_protocol_header_from_untrusted_peer_range_is_rejected_under_load( 1, 1, 1, + 10, 1, false, stats.clone(), @@ -979,6 +984,7 @@ async fn short_tls_probe_is_masked_through_client_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1066,6 +1072,7 @@ async fn tls12_record_probe_is_masked_through_client_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1151,6 +1158,7 @@ async fn handle_client_stream_increments_connects_all_exactly_once() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1243,6 +1251,7 @@ async fn running_client_handler_increments_connects_all_exactly_once() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1332,6 +1341,7 @@ async fn partial_tls_header_stall_triggers_handshake_timeout() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1514,6 +1524,7 @@ async fn valid_tls_path_does_not_fall_back_to_mask_backend() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1622,6 +1633,7 @@ async fn valid_tls_with_invalid_mtproto_falls_back_to_mask_backend() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1728,6 +1740,7 @@ async fn client_handler_tls_bad_mtproto_is_forwarded_to_mask_backend() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1849,6 +1862,7 @@ async fn alpn_mismatch_tls_probe_is_masked_through_client_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1941,6 +1955,7 @@ async fn invalid_hmac_tls_probe_is_masked_through_client_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -2039,6 +2054,7 @@ async fn burst_invalid_tls_probes_are_masked_verbatim() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -2876,6 +2892,7 @@ async fn relay_connect_error_releases_user_and_ip_before_return() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -3436,6 +3453,7 @@ async fn untrusted_proxy_header_source_is_rejected() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -3505,6 +3523,7 @@ async fn empty_proxy_trusted_cidrs_rejects_proxy_header_by_default() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -3601,6 +3620,7 @@ async fn oversized_tls_record_is_masked_in_generic_stream_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -3703,6 +3723,7 @@ async fn oversized_tls_record_is_masked_in_client_handler_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -3819,6 +3840,7 @@ async fn tls_record_len_min_minus_1_is_rejected_in_generic_stream_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -3921,6 +3943,7 @@ async fn tls_record_len_min_minus_1_is_rejected_in_client_handler_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -4026,6 +4049,7 @@ async fn tls_record_len_16384_is_accepted_in_generic_stream_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -4126,6 +4150,7 @@ async fn tls_record_len_16384_is_accepted_in_client_handler_pipeline() { 1, 1, 1, + 10, 1, false, stats.clone(), diff --git a/src/proxy/tests/client_timing_profile_adversarial_tests.rs b/src/proxy/tests/client_timing_profile_adversarial_tests.rs index 69a9ff4..d8df19f 100644 --- a/src/proxy/tests/client_timing_profile_adversarial_tests.rs +++ b/src/proxy/tests/client_timing_profile_adversarial_tests.rs @@ -33,6 +33,7 @@ fn make_test_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_tls_clienthello_size_security_tests.rs b/src/proxy/tests/client_tls_clienthello_size_security_tests.rs index 0c864e7..14c24b7 100644 --- a/src/proxy/tests/client_tls_clienthello_size_security_tests.rs +++ b/src/proxy/tests/client_tls_clienthello_size_security_tests.rs @@ -35,6 +35,7 @@ fn make_test_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_tls_clienthello_truncation_adversarial_tests.rs b/src/proxy/tests/client_tls_clienthello_truncation_adversarial_tests.rs index 79a8640..c757999 100644 --- a/src/proxy/tests/client_tls_clienthello_truncation_adversarial_tests.rs +++ b/src/proxy/tests/client_tls_clienthello_truncation_adversarial_tests.rs @@ -36,6 +36,7 @@ fn make_test_upstream_manager(stats: Arc) -> Arc { 1, 1, 1, + 10, 1, false, stats, diff --git a/src/proxy/tests/client_tls_mtproto_fallback_security_tests.rs b/src/proxy/tests/client_tls_mtproto_fallback_security_tests.rs index 95e49f7..a4d5df8 100644 --- a/src/proxy/tests/client_tls_mtproto_fallback_security_tests.rs +++ b/src/proxy/tests/client_tls_mtproto_fallback_security_tests.rs @@ -50,6 +50,7 @@ fn build_harness(secret_hex: &str, mask_port: u16) -> PipelineHarness { 1, 1, 1, + 10, 1, false, stats.clone(), diff --git a/src/proxy/tests/direct_relay_security_tests.rs b/src/proxy/tests/direct_relay_security_tests.rs index a731830..e139923 100644 --- a/src/proxy/tests/direct_relay_security_tests.rs +++ b/src/proxy/tests/direct_relay_security_tests.rs @@ -1302,6 +1302,7 @@ async fn direct_relay_abort_midflight_releases_route_gauge() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1408,6 +1409,7 @@ async fn direct_relay_cutover_midflight_releases_route_gauge() { 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1529,6 +1531,7 @@ async fn direct_relay_cutover_storm_multi_session_keeps_generic_errors_and_relea 1, 1, 1, + 10, 1, false, stats.clone(), @@ -1761,6 +1764,7 @@ async fn negative_direct_relay_dc_connection_refused_fails_fast() { 1, 100, 5000, + 10, 3, false, stats.clone(), @@ -1851,6 +1855,7 @@ async fn adversarial_direct_relay_cutover_integrity() { 1, 100, 5000, + 10, 3, false, stats.clone(), diff --git a/src/transport/upstream.rs b/src/transport/upstream.rs index 5899ee1..674f0f0 100644 --- a/src/transport/upstream.rs +++ b/src/transport/upstream.rs @@ -317,7 +317,7 @@ pub struct UpstreamManager { connect_retry_attempts: u32, connect_retry_backoff: Duration, connect_budget: Duration, - /// Per-attempt TCP connect timeout to Telegram DC (`[timeouts] tg_connect`, seconds). + /// Per-attempt TCP connect timeout to Telegram DC (`[general] tg_connect`, seconds). tg_connect_timeout_secs: u64, unhealthy_fail_threshold: u32, connect_failfast_hard_errors: bool, @@ -799,8 +799,8 @@ impl UpstreamManager { break; } let remaining_budget = self.connect_budget.saturating_sub(elapsed); - let attempt_timeout = Duration::from_secs(self.tg_connect_timeout_secs) - .min(remaining_budget); + let attempt_timeout = + Duration::from_secs(self.tg_connect_timeout_secs).min(remaining_budget); if attempt_timeout.is_zero() { last_error = Some(ProxyError::ConnectionTimeout { addr: target.to_string(),