Compare commits

..

6 Commits

Author SHA1 Message Date
Roman Martynov
d0f02f59f8 Merge 4d83d02a8f into bbc69f945e 2026-03-22 11:04:18 +03:00
Alexey
bbc69f945e Update release.yml 2026-03-22 11:04:09 +03:00
sintanial
4d83d02a8f 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
2026-03-21 16:26:51 +03:00
sintanial
fea8bc63fd Merge branch 'main' of https://github.com/telemt/telemt 2026-03-20 23:27:02 +03:00
sintanial
d8f7173f15 Merge branch 'main' of https://github.com/telemt/telemt 2026-03-01 15:18:47 +03:00
sintanial
b23d433e19 Merge branch 'main' of https://github.com/telemt/telemt 2026-03-01 13:48:59 +03:00
4 changed files with 43 additions and 23 deletions

View File

@@ -130,14 +130,12 @@ jobs:
pkg-config \ pkg-config \
curl curl
# 💾 cache toolchain
- uses: actions/cache@v4 - uses: actions/cache@v4
if: matrix.target == 'aarch64-unknown-linux-musl' if: matrix.target == 'aarch64-unknown-linux-musl'
with: with:
path: ~/.musl-aarch64 path: ~/.musl-aarch64
key: musl-toolchain-aarch64-v1 key: musl-toolchain-aarch64-v1
# 🔥 надёжная установка
- name: Install aarch64 musl toolchain - name: Install aarch64 musl toolchain
if: matrix.target == 'aarch64-unknown-linux-musl' if: matrix.target == 'aarch64-unknown-linux-musl'
run: | run: |
@@ -145,27 +143,19 @@ jobs:
TOOLCHAIN_DIR="$HOME/.musl-aarch64" TOOLCHAIN_DIR="$HOME/.musl-aarch64"
ARCHIVE="aarch64-linux-musl-cross.tgz" ARCHIVE="aarch64-linux-musl-cross.tgz"
URL="https://github.com/telemt/telemt/releases/download/toolchains/$ARCHIVE"
if [ -x "$TOOLCHAIN_DIR/bin/aarch64-linux-musl-gcc" ]; then if [ -x "$TOOLCHAIN_DIR/bin/aarch64-linux-musl-gcc" ]; then
echo "✅ musl toolchain already installed" echo "✅ MUSL toolchain already installed"
else else
echo "⬇️ downloading musl toolchain..." echo "⬇️ Downloading musl toolchain from Telemt GitHub Releases..."
download() { curl -fL \
url="$1" --retry 5 \
echo "→ trying $url" --retry-delay 3 \
curl -fL \ --connect-timeout 10 \
--retry 5 \ --max-time 120 \
--retry-delay 3 \ -o "$ARCHIVE" "$URL"
--connect-timeout 10 \
--max-time 120 \
-o "$ARCHIVE" "$url" && return 0
return 1
}
download "https://musl.cc/$ARCHIVE" || \
download "https://more.musl.cc/$ARCHIVE" || \
{ echo "❌ failed to download musl toolchain"; exit 1; }
mkdir -p "$TOOLCHAIN_DIR" mkdir -p "$TOOLCHAIN_DIR"
tar -xzf "$ARCHIVE" --strip-components=1 -C "$TOOLCHAIN_DIR" tar -xzf "$ARCHIVE" --strip-components=1 -C "$TOOLCHAIN_DIR"

View File

@@ -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 { if config.general.upstream_unhealthy_fail_threshold == 0 {
return Err(ProxyError::Config( return Err(ProxyError::Config(
"general.upstream_unhealthy_fail_threshold must be > 0".to_string(), "general.upstream_unhealthy_fail_threshold must be > 0".to_string(),
@@ -1625,6 +1631,26 @@ mod tests {
let _ = std::fs::remove_file(path); 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] #[test]
fn rpc_proxy_req_every_out_of_range_is_rejected() { fn rpc_proxy_req_every_out_of_range_is_rejected() {
let toml = r#" let toml = r#"

View File

@@ -191,6 +191,7 @@ pub async fn run() -> std::result::Result<(), Box<dyn std::error::Error>> {
config.general.upstream_connect_retry_attempts, config.general.upstream_connect_retry_attempts,
config.general.upstream_connect_retry_backoff_ms, config.general.upstream_connect_retry_backoff_ms,
config.general.upstream_connect_budget_ms, config.general.upstream_connect_budget_ms,
config.timeouts.tg_connect,
config.general.upstream_unhealthy_fail_threshold, config.general.upstream_unhealthy_fail_threshold,
config.general.upstream_connect_failfast_hard_errors, config.general.upstream_connect_failfast_hard_errors,
stats.clone(), stats.clone(),

View File

@@ -34,8 +34,6 @@ const NUM_DCS: usize = 5;
/// Timeout for individual DC ping attempt /// Timeout for individual DC ping attempt
const DC_PING_TIMEOUT_SECS: u64 = 5; 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. /// Interval between upstream health-check cycles.
const HEALTH_CHECK_INTERVAL_SECS: u64 = 30; const HEALTH_CHECK_INTERVAL_SECS: u64 = 30;
/// Timeout for a single health-check connect attempt. /// Timeout for a single health-check connect attempt.
@@ -319,6 +317,8 @@ pub struct UpstreamManager {
connect_retry_attempts: u32, connect_retry_attempts: u32,
connect_retry_backoff: Duration, connect_retry_backoff: Duration,
connect_budget: 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, unhealthy_fail_threshold: u32,
connect_failfast_hard_errors: bool, connect_failfast_hard_errors: bool,
no_upstreams_warn_epoch_ms: Arc<AtomicU64>, no_upstreams_warn_epoch_ms: Arc<AtomicU64>,
@@ -332,6 +332,7 @@ impl UpstreamManager {
connect_retry_attempts: u32, connect_retry_attempts: u32,
connect_retry_backoff_ms: u64, connect_retry_backoff_ms: u64,
connect_budget_ms: u64, connect_budget_ms: u64,
tg_connect_timeout_secs: u64,
unhealthy_fail_threshold: u32, unhealthy_fail_threshold: u32,
connect_failfast_hard_errors: bool, connect_failfast_hard_errors: bool,
stats: Arc<Stats>, stats: Arc<Stats>,
@@ -347,6 +348,7 @@ impl UpstreamManager {
connect_retry_attempts: connect_retry_attempts.max(1), connect_retry_attempts: connect_retry_attempts.max(1),
connect_retry_backoff: Duration::from_millis(connect_retry_backoff_ms), connect_retry_backoff: Duration::from_millis(connect_retry_backoff_ms),
connect_budget: Duration::from_millis(connect_budget_ms.max(1)), 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), unhealthy_fail_threshold: unhealthy_fail_threshold.max(1),
connect_failfast_hard_errors, connect_failfast_hard_errors,
no_upstreams_warn_epoch_ms: Arc::new(AtomicU64::new(0)), no_upstreams_warn_epoch_ms: Arc::new(AtomicU64::new(0)),
@@ -797,8 +799,8 @@ impl UpstreamManager {
break; break;
} }
let remaining_budget = self.connect_budget.saturating_sub(elapsed); let remaining_budget = self.connect_budget.saturating_sub(elapsed);
let attempt_timeout = let attempt_timeout = Duration::from_secs(self.tg_connect_timeout_secs)
Duration::from_secs(DIRECT_CONNECT_TIMEOUT_SECS).min(remaining_budget); .min(remaining_budget);
if attempt_timeout.is_zero() { if attempt_timeout.is_zero() {
last_error = Some(ProxyError::ConnectionTimeout { last_error = Some(ProxyError::ConnectionTimeout {
addr: target.to_string(), addr: target.to_string(),
@@ -1901,6 +1903,7 @@ mod tests {
1, 1,
100, 100,
1000, 1000,
10,
1, 1,
false, false,
Arc::new(Stats::new()), Arc::new(Stats::new()),