Compare commits

...

34 Commits

Author SHA1 Message Date
Roman Martynov a1e401b00d
Merge 4d83d02a8f into b9b1271f14 2026-03-25 16:40:44 +01:00
Alexey b9b1271f14
Merge pull request #584 from Dimasssss/patch-3
Update CONFIG_PARAMS, QUICK_START_GUIDE and FAQ
2026-03-25 17:44:59 +03:00
Dimasssss 3c734bd811
Update FAQ.en.md 2026-03-25 17:42:16 +03:00
Dimasssss 6391df0583
Update FAQ.ru.md 2026-03-25 17:42:07 +03:00
Dimasssss 6a781c8bc3
Update QUICK_START_GUIDE.en.md 2026-03-25 17:40:45 +03:00
Dimasssss 138652af8e
Update QUICK_START_GUIDE.ru.md 2026-03-25 17:40:16 +03:00
Dimasssss 59157d31a6
Update CONFIG_PARAMS.en.md 2026-03-25 17:37:01 +03:00
Alexey c43de1bd2a
Update release.yml 2026-03-24 22:36:25 +03:00
Alexey 101efe45b7
Update Dockerfile 2026-03-24 22:36:20 +03:00
Alexey 11df61c6ac
Update release.yml 2026-03-24 22:18:34 +03:00
Alexey 08684bcbd2
Update Cargo.toml 2026-03-24 22:03:12 +03:00
Alexey 744fb4425f
TLS Validator: Unknown SNI as WARN in Log: merge pull request #579 from telemt/flow
TLS Validator: Unknown SNI as WARN in Log
2026-03-24 22:01:09 +03:00
Alexey 80cb1bc221
Merge branch 'main' into flow 2026-03-24 22:00:51 +03:00
Alexey 8461556b02
Update release.yml 2026-03-24 22:00:32 +03:00
Alexey cfd516edf3
Update Cargo.toml 2026-03-24 21:41:14 +03:00
Alexey 803c2c0492
Update release.yml 2026-03-24 21:40:53 +03:00
Alexey b762bd029f
Merge branch 'main' into flow 2026-03-24 21:18:54 +03:00
Alexey 761679d306
Update test.yml 2026-03-24 21:18:13 +03:00
Alexey 41668b153d
Update test.yml 2026-03-24 21:14:12 +03:00
Alexey 1d2f88ad29
Merge branch 'main' into flow 2026-03-24 21:11:11 +03:00
Alexey 80917f5abc
Update test.yml 2026-03-24 21:10:56 +03:00
Alexey dc61d300ab
Bump 2026-03-24 21:02:43 +03:00
Alexey ae16080de5
TLS Validator: Unknown SNI as WARN in Log 2026-03-24 21:01:41 +03:00
Alexey b8ca1fc166
Update Dockerfile 2026-03-24 20:55:32 +03:00
Alexey f9986944df
Update release.yml 2026-03-24 20:53:56 +03:00
Alexey cb877c2bc3
Update release profile settings for better optimization: merge pull request #574 from vladon/main
Update release profile settings for better optimization
2026-03-24 14:10:04 +03:00
Vladislav Yaroslavlev 4426082c17
Update release profile settings for better optimization 2026-03-24 14:01:49 +03:00
Alexey 22097f8c7c
Update Dockerfile 2026-03-24 11:46:49 +03:00
Alexey 1450af60a0
Update Dockerfile 2026-03-24 11:41:53 +03:00
Alexey f1cc8d65f2
Update release.yml 2026-03-24 11:12:03 +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
15 changed files with 453 additions and 156 deletions

View File

@ -5,27 +5,69 @@ on:
tags: tags:
- '[0-9]+.[0-9]+.[0-9]+' - '[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch: workflow_dispatch:
inputs:
tag:
description: 'Release tag (example: 3.3.15)'
required: true
type: string
concurrency: concurrency:
group: release-${{ github.ref }} group: release-${{ github.ref_name }}-${{ github.event.inputs.tag || 'auto' }}
cancel-in-progress: true cancel-in-progress: true
permissions: permissions:
contents: read contents: read
packages: write
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
BINARY_NAME: telemt BINARY_NAME: telemt
jobs: jobs:
# ========================== prepare:
# GNU / glibc name: Prepare
# ==========================
build-gnu:
name: GNU ${{ matrix.target }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
version: ${{ steps.vars.outputs.version }}
prerelease: ${{ steps.vars.outputs.prerelease }}
steps:
- name: Resolve version
id: vars
shell: bash
run: |
set -euo pipefail
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.tag }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
VERSION="${VERSION#refs/tags/}"
if [ -z "${VERSION}" ]; then
echo "Release version is empty" >&2
exit 1
fi
if [[ "${VERSION}" == *-* ]]; then
PRERELEASE=true
else
PRERELEASE=false
fi
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "prerelease=${PRERELEASE}" >> "${GITHUB_OUTPUT}"
# ==========================
# GNU / glibc
# ==========================
build-gnu:
name: GNU ${{ matrix.asset }}
runs-on: ubuntu-latest
needs: prepare
container: container:
image: rust:slim-bookworm image: rust:slim-bookworm
@ -35,8 +77,15 @@ jobs:
include: include:
- target: x86_64-unknown-linux-gnu - target: x86_64-unknown-linux-gnu
asset: telemt-x86_64-linux-gnu asset: telemt-x86_64-linux-gnu
cpu: baseline
- target: x86_64-unknown-linux-gnu
asset: telemt-x86_64-v3-linux-gnu
cpu: v3
- target: aarch64-unknown-linux-gnu - target: aarch64-unknown-linux-gnu
asset: telemt-aarch64-linux-gnu asset: telemt-aarch64-linux-gnu
cpu: generic
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -62,64 +111,88 @@ jobs:
- uses: actions/cache@v4 - uses: actions/cache@v4
with: with:
path: | path: |
~/.cargo/registry /usr/local/cargo/registry
~/.cargo/git /usr/local/cargo/git
target target
key: gnu-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} key: gnu-${{ matrix.asset }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
gnu-${{ matrix.asset }}-
gnu-
- name: Build - name: Build
shell: bash
run: | run: |
set -euo pipefail
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
export CC=aarch64-linux-gnu-gcc export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++ export CXX=aarch64-linux-gnu-g++
export RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" export RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C lto=fat -C panic=abort"
else else
export CC=clang export CC=clang
export CXX=clang++ export CXX=clang++
export RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=lld"
if [ "${{ matrix.cpu }}" = "v3" ]; then
CPU_FLAGS="-C target-cpu=x86-64-v3"
else
CPU_FLAGS="-C target-cpu=x86-64"
fi
export RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=lld -C lto=fat -C panic=abort ${CPU_FLAGS}"
fi fi
cargo build --release --target ${{ matrix.target }} cargo build --release --target ${{ matrix.target }} -j "$(nproc)"
- name: Package - name: Package
shell: bash
run: | run: |
set -euo pipefail
mkdir -p dist mkdir -p dist
cp target/${{ matrix.target }}/release/${{ env.BINARY_NAME }} dist/telemt cp "target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" dist/telemt
cd dist cd dist
tar -czf ${{ matrix.asset }}.tar.gz \ tar -czf "${{ matrix.asset }}.tar.gz" \
--owner=0 --group=0 --numeric-owner \ --owner=0 --group=0 --numeric-owner \
telemt telemt
sha256sum ${{ matrix.asset }}.tar.gz > ${{ matrix.asset }}.sha256 sha256sum "${{ matrix.asset }}.tar.gz" > "${{ matrix.asset }}.tar.gz.sha256"
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.asset }} name: ${{ matrix.asset }}
path: dist/* path: dist/*
# ========================== # ==========================
# MUSL # MUSL
# ========================== # ==========================
build-musl: build-musl:
name: MUSL ${{ matrix.target }} name: MUSL ${{ matrix.asset }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare
container: container:
image: rust:slim-bookworm image: rust:slim-bookworm
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- target: x86_64-unknown-linux-musl - target: x86_64-unknown-linux-musl
asset: telemt-x86_64-linux-musl asset: telemt-x86_64-linux-musl
cpu: baseline
- target: x86_64-unknown-linux-musl
asset: telemt-x86_64-v3-linux-musl
cpu: v3
- target: aarch64-unknown-linux-musl - target: aarch64-unknown-linux-musl
asset: telemt-aarch64-linux-musl asset: telemt-aarch64-linux-musl
cpu: generic
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install deps - name: Install deps
run: | run: |
apt-get update apt-get update
@ -127,89 +200,104 @@ jobs:
musl-tools \ musl-tools \
pkg-config \ pkg-config \
curl curl
- 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'
shell: bash
run: | run: |
set -e set -euo pipefail
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" 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 cached" echo "MUSL toolchain cached"
else else
echo "⬇️ Downloading MUSL toolchain..."
curl -fL \ curl -fL \
--retry 5 \ --retry 5 \
--retry-delay 3 \ --retry-delay 3 \
--connect-timeout 10 \ --connect-timeout 10 \
--max-time 120 \ --max-time 120 \
-o "$ARCHIVE" "$URL" -o "${ARCHIVE}" "${URL}"
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}"
fi fi
echo "$TOOLCHAIN_DIR/bin" >> $GITHUB_PATH echo "${TOOLCHAIN_DIR}/bin" >> "${GITHUB_PATH}"
- name: Add rust target - name: Add rust target
run: rustup target add ${{ matrix.target }} run: rustup target add ${{ matrix.target }}
- uses: actions/cache@v4 - uses: actions/cache@v4
with: with:
path: | path: |
/usr/local/cargo/registry /usr/local/cargo/registry
/usr/local/cargo/git /usr/local/cargo/git
target target
key: musl-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} key: musl-${{ matrix.asset }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
musl-${{ matrix.asset }}-
musl-
- name: Build - name: Build
shell: bash
run: | run: |
set -euo pipefail
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-musl" ]; then if [ "${{ matrix.target }}" = "aarch64-unknown-linux-musl" ]; then
export CC=aarch64-linux-musl-gcc export CC=aarch64-linux-musl-gcc
export CC_aarch64_unknown_linux_musl=aarch64-linux-musl-gcc export CC_aarch64_unknown_linux_musl=aarch64-linux-musl-gcc
export RUSTFLAGS="-C target-feature=+crt-static -C linker=aarch64-linux-musl-gcc" export RUSTFLAGS="-C target-feature=+crt-static -C linker=aarch64-linux-musl-gcc -C lto=fat -C panic=abort"
else else
export CC=musl-gcc export CC=musl-gcc
export CC_x86_64_unknown_linux_musl=musl-gcc export CC_x86_64_unknown_linux_musl=musl-gcc
export RUSTFLAGS="-C target-feature=+crt-static"
if [ "${{ matrix.cpu }}" = "v3" ]; then
CPU_FLAGS="-C target-cpu=x86-64-v3"
else
CPU_FLAGS="-C target-cpu=x86-64"
fi
export RUSTFLAGS="-C target-feature=+crt-static -C lto=fat -C panic=abort ${CPU_FLAGS}"
fi fi
cargo build --release --target ${{ matrix.target }} cargo build --release --target ${{ matrix.target }} -j "$(nproc)"
- name: Package - name: Package
shell: bash
run: | run: |
set -euo pipefail
mkdir -p dist mkdir -p dist
cp target/${{ matrix.target }}/release/${{ env.BINARY_NAME }} dist/telemt cp "target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" dist/telemt
cd dist cd dist
tar -czf ${{ matrix.asset }}.tar.gz \ tar -czf "${{ matrix.asset }}.tar.gz" \
--owner=0 --group=0 --numeric-owner \ --owner=0 --group=0 --numeric-owner \
telemt telemt
sha256sum ${{ matrix.asset }}.tar.gz > ${{ matrix.asset }}.sha256 sha256sum "${{ matrix.asset }}.tar.gz" > "${{ matrix.asset }}.tar.gz.sha256"
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.asset }} name: ${{ matrix.asset }}
path: dist/* path: dist/*
# ========================== # ==========================
# Release # Release
# ========================== # ==========================
release: release:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build-gnu, build-musl] needs: [prepare, build-gnu, build-musl]
permissions: permissions:
contents: write contents: write
@ -219,25 +307,30 @@ jobs:
with: with:
path: artifacts path: artifacts
- name: Flatten - name: Flatten artifacts
shell: bash
run: | run: |
mkdir dist set -euo pipefail
mkdir -p dist
find artifacts -type f -exec cp {} dist/ \; find artifacts -type f -exec cp {} dist/ \;
- name: Create Release - name: Create GitHub Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: ${{ needs.prepare.outputs.version }}
target_commitish: ${{ github.sha }}
files: dist/* files: dist/*
generate_release_notes: true generate_release_notes: true
prerelease: ${{ contains(github.ref, '-') }} prerelease: ${{ needs.prepare.outputs.prerelease == 'true' }}
overwrite_files: true
# ========================== # ==========================
# Docker (FROM RELEASE) # Docker
# ========================== # ==========================
docker: docker:
name: Docker (from release) name: Docker
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: release needs: [prepare, release]
permissions: permissions:
contents: read contents: read
@ -246,28 +339,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install gh
run: apt-get update && apt-get install -y gh
- name: Extract version
id: vars
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Download binary
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir dist
gh release download ${{ steps.vars.outputs.VERSION }} \
--repo ${{ github.repository }} \
--pattern "telemt-x86_64-linux-musl.tar.gz" \
--dir dist
tar -xzf dist/telemt-x86_64-linux-musl.tar.gz -C dist
chmod +x dist/telemt
- uses: docker/setup-qemu-action@v3 - uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3 - uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3 - uses: docker/login-action@v3
@ -276,14 +349,57 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Probe release assets
shell: bash
env:
VERSION: ${{ needs.prepare.outputs.version }}
run: |
set -euo pipefail
for asset in \
telemt-x86_64-linux-musl.tar.gz \
telemt-x86_64-linux-musl.tar.gz.sha256 \
telemt-aarch64-linux-musl.tar.gz \
telemt-aarch64-linux-musl.tar.gz.sha256
do
curl -fsIL \
--retry 10 \
--retry-delay 3 \
"https://github.com/${GITHUB_REPOSITORY}/releases/download/${VERSION}/${asset}" \
> /dev/null
done
- name: Compute image tags
id: meta
shell: bash
env:
VERSION: ${{ needs.prepare.outputs.version }}
run: |
set -euo pipefail
IMAGE="$(echo "ghcr.io/${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')"
TAGS="${IMAGE}:${VERSION}"
if [[ "${VERSION}" != *-* ]]; then
TAGS="${TAGS}"$'\n'"${IMAGE}:latest"
fi
{
echo "tags<<EOF"
printf '%s\n' "${TAGS}"
echo "EOF"
} >> "${GITHUB_OUTPUT}"
- name: Build & Push - name: Build & Push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
push: true push: true
pull: true
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags: | tags: ${{ steps.meta.outputs.tags }}
ghcr.io/${{ github.repository }}:${{ steps.vars.outputs.VERSION }}
ghcr.io/${{ github.repository }}:latest
build-args: | build-args: |
BINARY=dist/telemt TELEMT_REPOSITORY=${{ github.repository }}
TELEMT_VERSION=${{ needs.prepare.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -54,14 +54,20 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
~/.cargo/bin
~/.cargo/registry ~/.cargo/registry
~/.cargo/git ~/.cargo/git
target target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-nextest-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-nextest-
${{ runner.os }}-cargo- ${{ runner.os }}-cargo-
- run: cargo test --verbose - name: Install cargo-nextest
run: cargo install --locked cargo-nextest || true
- name: Run tests with nextest
run: cargo nextest run -j "$(nproc)"
# ========================== # ==========================
# Clippy # Clippy
@ -88,11 +94,13 @@ jobs:
~/.cargo/registry ~/.cargo/registry
~/.cargo/git ~/.cargo/git
target target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-clippy-
${{ runner.os }}-cargo- ${{ runner.os }}-cargo-
- run: cargo clippy -- --cap-lints warn - name: Run clippy
run: cargo clippy -j "$(nproc)" -- --cap-lints warn
# ========================== # ==========================
# Udeps # Udeps
@ -108,20 +116,24 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with:
components: rust-src
- name: Cache cargo - name: Cache cargo
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
~/.cargo/bin
~/.cargo/registry ~/.cargo/registry
~/.cargo/git ~/.cargo/git
target target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-udeps-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-cargo-udeps-
${{ runner.os }}-cargo- ${{ runner.os }}-cargo-
- name: Install cargo-udeps - name: Install cargo-udeps
run: cargo install cargo-udeps || true run: cargo install --locked cargo-udeps || true
# тоже не валит билд - name: Run udeps
- run: cargo udeps || true run: cargo udeps -j "$(nproc)" || true

2
Cargo.lock generated
View File

@ -2793,7 +2793,7 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
[[package]] [[package]]
name = "telemt" name = "telemt"
version = "3.3.30" version = "3.3.31"
dependencies = [ dependencies = [
"aes", "aes",
"anyhow", "anyhow",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "telemt" name = "telemt"
version = "3.3.30" version = "3.3.31"
edition = "2024" edition = "2024"
[features] [features]
@ -83,4 +83,6 @@ name = "crypto_bench"
harness = false harness = false
[profile.release] [profile.release]
lto = "thin" lto = "fat"
codegen-units = 1

View File

@ -1,47 +1,74 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG BINARY ARG TELEMT_REPOSITORY=telemt/telemt
ARG TELEMT_VERSION=latest
# ========================== # ==========================
# Stage: minimal # Minimal Image
# ========================== # ==========================
FROM debian:12-slim AS minimal FROM debian:12-slim AS minimal
RUN apt-get update && apt-get install -y --no-install-recommends \ ARG TARGETARCH
binutils \ ARG TELEMT_REPOSITORY
curl \ ARG TELEMT_VERSION
ca-certificates \
&& rm -rf /var/lib/apt/lists/* \ RUN set -eux; \
\ apt-get update; \
&& curl -fL \ apt-get install -y --no-install-recommends \
binutils \
ca-certificates \
curl \
tar; \
rm -rf /var/lib/apt/lists/*
RUN set -eux; \
case "${TARGETARCH}" in \
amd64) ASSET="telemt-x86_64-linux-musl.tar.gz" ;; \
arm64) ASSET="telemt-aarch64-linux-musl.tar.gz" ;; \
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \
esac; \
VERSION="${TELEMT_VERSION#refs/tags/}"; \
if [ -z "${VERSION}" ] || [ "${VERSION}" = "latest" ]; then \
BASE_URL="https://github.com/${TELEMT_REPOSITORY}/releases/latest/download"; \
else \
BASE_URL="https://github.com/${TELEMT_REPOSITORY}/releases/download/${VERSION}"; \
fi; \
curl -fL \
--retry 5 \ --retry 5 \
--retry-delay 3 \ --retry-delay 3 \
--connect-timeout 10 \ --connect-timeout 10 \
--max-time 120 \ --max-time 120 \
-o /tmp/upx.tar.xz \ -o "/tmp/${ASSET}" \
https://github.com/telemt/telemt/releases/download/toolchains/upx-amd64_linux.tar.xz \ "${BASE_URL}/${ASSET}"; \
&& tar -xf /tmp/upx.tar.xz -C /tmp \ curl -fL \
&& mv /tmp/upx*/upx /usr/local/bin/upx \ --retry 5 \
&& chmod +x /usr/local/bin/upx \ --retry-delay 3 \
&& rm -rf /tmp/upx* --connect-timeout 10 \
--max-time 120 \
COPY ${BINARY} /telemt -o "/tmp/${ASSET}.sha256" \
"${BASE_URL}/${ASSET}.sha256"; \
RUN strip /telemt || true cd /tmp; \
RUN upx --best --lzma /telemt || true sha256sum -c "${ASSET}.sha256"; \
tar -xzf "${ASSET}" -C /tmp; \
test -f /tmp/telemt; \
install -m 0755 /tmp/telemt /telemt; \
strip --strip-unneeded /telemt || true; \
rm -f "/tmp/${ASSET}" "/tmp/${ASSET}.sha256" /tmp/telemt
# ========================== # ==========================
# Debug image # Debug Image
# ========================== # ==========================
FROM debian:12-slim AS debug FROM debian:12-slim AS debug
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN set -eux; \
ca-certificates \ apt-get update; \
tzdata \ apt-get install -y --no-install-recommends \
curl \ ca-certificates \
iproute2 \ tzdata \
busybox \ curl \
&& rm -rf /var/lib/apt/lists/* iproute2 \
busybox; \
rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
@ -54,7 +81,7 @@ ENTRYPOINT ["/app/telemt"]
CMD ["config.toml"] CMD ["config.toml"]
# ========================== # ==========================
# Production (REAL distroless) # Production Distroless on MUSL
# ========================== # ==========================
FROM gcr.io/distroless/static-debian12 AS prod FROM gcr.io/distroless/static-debian12 AS prod
@ -68,4 +95,4 @@ USER nonroot:nonroot
EXPOSE 443 9090 9091 EXPOSE 443 9090 9091
ENTRYPOINT ["/app/telemt"] ENTRYPOINT ["/app/telemt"]
CMD ["config.toml"] CMD ["config.toml"]

View File

@ -50,6 +50,8 @@ This document lists all configuration keys accepted by `config.toml`.
| me_d2c_flush_batch_max_bytes | `usize` | `131072` | `4096..=2_097_152`. | Max ME->client payload bytes coalesced before flush. | | me_d2c_flush_batch_max_bytes | `usize` | `131072` | `4096..=2_097_152`. | Max ME->client payload bytes coalesced before flush. |
| me_d2c_flush_batch_max_delay_us | `u64` | `500` | `0..=5000`. | Max microsecond wait for coalescing more ME->client frames (`0` disables timed coalescing). | | me_d2c_flush_batch_max_delay_us | `u64` | `500` | `0..=5000`. | Max microsecond wait for coalescing more ME->client frames (`0` disables timed coalescing). |
| me_d2c_ack_flush_immediate | `bool` | `true` | — | Flushes client writer immediately after quick-ack write. | | me_d2c_ack_flush_immediate | `bool` | `true` | — | Flushes client writer immediately after quick-ack write. |
| me_quota_soft_overshoot_bytes | `u64` | `65536` | `0..=16_777_216`. | Extra per-route quota allowance (bytes) tolerated before writer-side quota enforcement drops route data. |
| me_d2c_frame_buf_shrink_threshold_bytes | `usize` | `262144` | `4096..=16_777_216`. | Threshold for shrinking oversized ME->client frame-aggregation buffers after flush. |
| direct_relay_copy_buf_c2s_bytes | `usize` | `65536` | `4096..=1_048_576`. | Copy buffer size for client->DC direction in direct relay. | | direct_relay_copy_buf_c2s_bytes | `usize` | `65536` | `4096..=1_048_576`. | Copy buffer size for client->DC direction in direct relay. |
| direct_relay_copy_buf_s2c_bytes | `usize` | `262144` | `8192..=2_097_152`. | Copy buffer size for DC->client direction in direct relay. | | direct_relay_copy_buf_s2c_bytes | `usize` | `262144` | `8192..=2_097_152`. | Copy buffer size for DC->client direction in direct relay. |
| crypto_pending_buffer | `usize` | `262144` | — | Max pending ciphertext buffer per client writer (bytes). | | crypto_pending_buffer | `usize` | `262144` | — | Max pending ciphertext buffer per client writer (bytes). |
@ -243,6 +245,10 @@ Note: When `server.proxy_protocol` is enabled, incoming PROXY protocol headers a
| Parameter | Type | Default | Constraints / validation | Description | | Parameter | Type | Default | Constraints / validation | Description |
|---|---|---|---|---| |---|---|---|---|---|
| client_handshake | `u64` | `30` | — | Client handshake timeout. | | client_handshake | `u64` | `30` | — | Client handshake timeout. |
| relay_idle_policy_v2_enabled | `bool` | `true` | — | Enables soft/hard middle-relay client idle policy. |
| 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. | | tg_connect | `u64` | `10` | — | Upstream Telegram connect timeout. |
| client_keepalive | `u64` | `15` | — | Client keepalive timeout. | | client_keepalive | `u64` | `15` | — | Client keepalive timeout. |
| client_ack | `u64` | `90` | — | Client ACK timeout. | | client_ack | `u64` | `90` | — | Client ACK timeout. |
@ -255,6 +261,9 @@ Note: When `server.proxy_protocol` is enabled, incoming PROXY protocol headers a
|---|---|---|---|---| |---|---|---|---|---|
| tls_domain | `String` | `"petrovich.ru"` | — | Primary TLS domain used in fake TLS handshake profile. | | tls_domain | `String` | `"petrovich.ru"` | — | Primary TLS domain used in fake TLS handshake profile. |
| tls_domains | `String[]` | `[]` | — | Additional TLS domains for generating multiple links. | | tls_domains | `String[]` | `[]` | — | Additional TLS domains for generating multiple links. |
| unknown_sni_action | `"drop" \| "mask"` | `"drop"` | — | Action for TLS ClientHello with unknown/non-configured SNI. |
| tls_fetch_scope | `String` | `""` | Value is trimmed during load; empty keeps default upstream routing behavior. | Upstream scope tag used for TLS-front metadata fetches. |
| tls_fetch | `Table` | built-in defaults | See `[censorship.tls_fetch]` section below. | TLS-front metadata fetch strategy settings. |
| mask | `bool` | `true` | — | Enables masking/fronting relay mode. | | mask | `bool` | `true` | — | Enables masking/fronting relay mode. |
| mask_host | `String \| null` | `null` | — | Upstream mask host for TLS fronting relay. | | mask_host | `String \| null` | `null` | — | Upstream mask host for TLS fronting relay. |
| mask_port | `u16` | `443` | — | Upstream mask port for TLS fronting relay. | | mask_port | `u16` | `443` | — | Upstream mask port for TLS fronting relay. |
@ -280,6 +289,18 @@ Note: When `server.proxy_protocol` is enabled, incoming PROXY protocol headers a
| mask_timing_normalization_floor_ms | `u64` | `0` | Must be `> 0` when timing normalization is enabled; must be `<= ceiling`. | Lower bound (ms) for masking outcome normalization target. | | mask_timing_normalization_floor_ms | `u64` | `0` | Must be `> 0` when timing normalization is enabled; must be `<= ceiling`. | Lower bound (ms) for masking outcome normalization target. |
| mask_timing_normalization_ceiling_ms | `u64` | `0` | Must be `>= floor`; must be `<= 60000`. | Upper bound (ms) for masking outcome normalization target. | | mask_timing_normalization_ceiling_ms | `u64` | `0` | Must be `>= floor`; must be `<= 60000`. | Upper bound (ms) for masking outcome normalization target. |
## [censorship.tls_fetch]
| Parameter | Type | Default | Constraints / validation | Description |
|---|---|---|---|---|
| profiles | `("modern_chrome_like" \| "modern_firefox_like" \| "compat_tls12" \| "legacy_minimal")[]` | `["modern_chrome_like", "modern_firefox_like", "compat_tls12", "legacy_minimal"]` | Empty list falls back to defaults; values are deduplicated preserving order. | Ordered ClientHello profile fallback chain for TLS-front metadata fetch. |
| strict_route | `bool` | `true` | — | Fails closed on upstream-route connect errors instead of falling back to direct TCP when route is configured. |
| attempt_timeout_ms | `u64` | `5000` | Must be `> 0`. | Timeout budget per one TLS-fetch profile attempt (ms). |
| total_budget_ms | `u64` | `15000` | Must be `> 0`. | Total wall-clock budget across all TLS-fetch attempts (ms). |
| grease_enabled | `bool` | `false` | — | Enables GREASE-style random values in selected ClientHello extensions for fetch traffic. |
| deterministic | `bool` | `false` | — | Enables deterministic ClientHello randomness for debugging/tests. |
| profile_cache_ttl_secs | `u64` | `600` | `0` disables cache. | TTL for winner-profile cache entries used by TLS fetch path. |
### Shape-channel hardening notes (`[censorship]`) ### Shape-channel hardening notes (`[censorship]`)
These parameters are designed to reduce one specific fingerprint source during masking: the exact number of bytes sent from proxy to `mask_host` for invalid or probing traffic. These parameters are designed to reduce one specific fingerprint source during masking: the exact number of bytes sent from proxy to `mask_host` for invalid or probing traffic.

View File

@ -63,9 +63,12 @@ user3 = "00000000000000000000000000000003"
curl -s http://127.0.0.1:9091/v1/users | jq curl -s http://127.0.0.1:9091/v1/users | jq
``` ```
## "Unknown TLS SNI" Error
You probably updated tls_domain, but users are still connecting via old links with the previous domain.
## How to view metrics ## How to view metrics
1. Open the config `nano /etc/telemt.toml` 1. Open the config `nano /etc/telemt/telemt.toml`
2. Add the following parameters 2. Add the following parameters
```toml ```toml
[server] [server]

View File

@ -64,9 +64,12 @@ user3 = "00000000000000000000000000000003"
curl -s http://127.0.0.1:9091/v1/users | jq curl -s http://127.0.0.1:9091/v1/users | jq
``` ```
## Ошибка "Unknown TLS SNI"
Возможно, вы обновили tls_domain, но пользователи всё ещё пытаются подключаться по старым ссылкам с прежним доменом.
## Как посмотреть метрики ## Как посмотреть метрики
1. Открыть конфиг `nano /etc/telemt.toml` 1. Открыть конфиг `nano /etc/telemt/telemt.toml`
2. Добавить следующие параметры 2. Добавить следующие параметры
```toml ```toml
[server] [server]

View File

@ -27,12 +27,12 @@ chmod +x /bin/telemt
**0. Check port and generate secrets** **0. Check port and generate secrets**
The port you have selected for use should be MISSING from the list, when: The port you have selected for use should not be in the list:
```bash ```bash
netstat -lnp netstat -lnp
``` ```
Generate 16 bytes/32 characters HEX with OpenSSL or another way: Generate 16 bytes/32 characters in HEX format with OpenSSL or another way:
```bash ```bash
openssl rand -hex 16 openssl rand -hex 16
``` ```
@ -50,7 +50,7 @@ Save the obtained result somewhere. You will need it later!
**1. Place your config to /etc/telemt/telemt.toml** **1. Place your config to /etc/telemt/telemt.toml**
Create config directory: Create the config directory:
```bash ```bash
mkdir /etc/telemt mkdir /etc/telemt
``` ```
@ -59,7 +59,7 @@ Open nano
```bash ```bash
nano /etc/telemt/telemt.toml nano /etc/telemt/telemt.toml
``` ```
paste your config Insert your configuration:
```toml ```toml
# === General Settings === # === General Settings ===
@ -93,8 +93,9 @@ hello = "00000000000000000000000000000000"
then Ctrl+S -> Ctrl+X to save then Ctrl+S -> Ctrl+X to save
> [!WARNING] > [!WARNING]
> Replace the value of the hello parameter with the value you obtained in step 0. > Replace the value of the hello parameter with the value you obtained in step 0.
> Replace the value of the tls_domain parameter with another website. > Additionally, change the value of the tls_domain parameter to a different website.
> Changing the tls_domain parameter will break all links that use the old domain!
--- ---
@ -105,14 +106,14 @@ useradd -d /opt/telemt -m -r -U telemt
chown -R telemt:telemt /etc/telemt chown -R telemt:telemt /etc/telemt
``` ```
**3. Create service on /etc/systemd/system/telemt.service** **3. Create service in /etc/systemd/system/telemt.service**
Open nano Open nano
```bash ```bash
nano /etc/systemd/system/telemt.service nano /etc/systemd/system/telemt.service
``` ```
paste this Systemd Module Insert this Systemd module:
```bash ```bash
[Unit] [Unit]
Description=Telemt Description=Telemt
@ -147,13 +148,16 @@ systemctl daemon-reload
**6.** For automatic startup at system boot, enter `systemctl enable telemt` **6.** For automatic startup at system boot, enter `systemctl enable telemt`
**7.** To get the link(s), enter **7.** To get the link(s), enter:
```bash ```bash
curl -s http://127.0.0.1:9091/v1/users | jq curl -s http://127.0.0.1:9091/v1/users | jq
``` ```
> Any number of people can use one link. > Any number of people can use one link.
> [!WARNING]
> Only the command from step 7 can provide a working link. Do not try to create it yourself or copy it from anywhere if you are not sure what you are doing!
--- ---
# Telemt via Docker Compose # Telemt via Docker Compose

View File

@ -95,6 +95,7 @@ hello = "00000000000000000000000000000000"
> [!WARNING] > [!WARNING]
> Замените значение параметра hello на значение, которое вы получили в пункте 0. > Замените значение параметра hello на значение, которое вы получили в пункте 0.
> Так же замените значение параметра tls_domain на другой сайт. > Так же замените значение параметра tls_domain на другой сайт.
> Изменение параметра tls_domain сделает нерабочими все ссылки, использующие старый домен!
--- ---

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(),
@ -1905,6 +1911,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

@ -225,6 +225,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

@ -13,7 +13,7 @@ use std::sync::Arc;
use std::sync::{Mutex, OnceLock}; use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
use tracing::{debug, trace, warn}; use tracing::{debug, info, trace, warn};
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
use crate::config::{ProxyConfig, UnknownSniAction}; use crate::config::{ProxyConfig, UnknownSniAction};
@ -28,6 +28,8 @@ use rand::RngExt;
const ACCESS_SECRET_BYTES: usize = 16; const ACCESS_SECRET_BYTES: usize = 16;
static INVALID_SECRET_WARNED: OnceLock<Mutex<HashSet<(String, String)>>> = OnceLock::new(); static INVALID_SECRET_WARNED: OnceLock<Mutex<HashSet<(String, String)>>> = OnceLock::new();
const UNKNOWN_SNI_WARN_COOLDOWN_SECS: u64 = 5;
static UNKNOWN_SNI_WARN_NEXT_ALLOWED: OnceLock<Mutex<Option<Instant>>> = OnceLock::new();
#[cfg(test)] #[cfg(test)]
const WARNED_SECRET_MAX_ENTRIES: usize = 64; const WARNED_SECRET_MAX_ENTRIES: usize = 64;
#[cfg(not(test))] #[cfg(not(test))]
@ -86,6 +88,24 @@ fn auth_probe_saturation_state_lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()) .unwrap_or_else(|poisoned| poisoned.into_inner())
} }
fn unknown_sni_warn_state_lock() -> std::sync::MutexGuard<'static, Option<Instant>> {
UNKNOWN_SNI_WARN_NEXT_ALLOWED
.get_or_init(|| Mutex::new(None))
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn should_emit_unknown_sni_warn(now: Instant) -> bool {
let mut guard = unknown_sni_warn_state_lock();
if let Some(next_allowed) = *guard
&& now < next_allowed
{
return false;
}
*guard = Some(now + Duration::from_secs(UNKNOWN_SNI_WARN_COOLDOWN_SECS));
true
}
fn normalize_auth_probe_ip(peer_ip: IpAddr) -> IpAddr { fn normalize_auth_probe_ip(peer_ip: IpAddr) -> IpAddr {
match peer_ip { match peer_ip {
IpAddr::V4(ip) => IpAddr::V4(ip), IpAddr::V4(ip) => IpAddr::V4(ip),
@ -412,6 +432,25 @@ fn auth_probe_test_lock() -> &'static Mutex<()> {
TEST_LOCK.get_or_init(|| Mutex::new(())) TEST_LOCK.get_or_init(|| Mutex::new(()))
} }
#[cfg(test)]
fn unknown_sni_warn_test_lock() -> &'static Mutex<()> {
static TEST_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
TEST_LOCK.get_or_init(|| Mutex::new(()))
}
#[cfg(test)]
fn clear_unknown_sni_warn_state_for_testing() {
if UNKNOWN_SNI_WARN_NEXT_ALLOWED.get().is_some() {
let mut guard = unknown_sni_warn_state_lock();
*guard = None;
}
}
#[cfg(test)]
fn should_emit_unknown_sni_warn_for_testing(now: Instant) -> bool {
should_emit_unknown_sni_warn(now)
}
#[cfg(test)] #[cfg(test)]
fn clear_warned_secrets_for_testing() { fn clear_warned_secrets_for_testing() {
if let Some(warned) = INVALID_SECRET_WARNED.get() if let Some(warned) = INVALID_SECRET_WARNED.get()
@ -658,12 +697,25 @@ where
if client_sni.is_some() && matched_tls_domain.is_none() && preferred_user_hint.is_none() { if client_sni.is_some() && matched_tls_domain.is_none() && preferred_user_hint.is_none() {
auth_probe_record_failure(peer.ip(), Instant::now()); auth_probe_record_failure(peer.ip(), Instant::now());
maybe_apply_server_hello_delay(config).await; maybe_apply_server_hello_delay(config).await;
debug!( let sni = client_sni.as_deref().unwrap_or_default();
peer = %peer, let log_now = Instant::now();
sni = ?client_sni, if should_emit_unknown_sni_warn(log_now) {
action = ?config.censorship.unknown_sni_action, warn!(
"TLS handshake rejected by unknown SNI policy" peer = %peer,
); sni = %sni,
unknown_sni = true,
unknown_sni_action = ?config.censorship.unknown_sni_action,
"TLS handshake rejected by unknown SNI policy"
);
} else {
info!(
peer = %peer,
sni = %sni,
unknown_sni = true,
unknown_sni_action = ?config.censorship.unknown_sni_action,
"TLS handshake rejected by unknown SNI policy"
);
}
return match config.censorship.unknown_sni_action { return match config.censorship.unknown_sni_action {
UnknownSniAction::Drop => HandshakeResult::Error(ProxyError::UnknownTlsSni), UnknownSniAction::Drop => HandshakeResult::Error(ProxyError::UnknownTlsSni),
UnknownSniAction::Mask => HandshakeResult::BadClient { reader, writer }, UnknownSniAction::Mask => HandshakeResult::BadClient { reader, writer },

View File

@ -1643,6 +1643,32 @@ fn auth_probe_capacity_fresh_full_map_still_tracks_newcomer_with_bounded_evictio
); );
} }
#[test]
fn unknown_sni_warn_cooldown_first_event_is_warn_and_repeated_events_are_info_until_window_expires()
{
let _guard = unknown_sni_warn_test_lock()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_unknown_sni_warn_state_for_testing();
let now = Instant::now();
assert!(
should_emit_unknown_sni_warn_for_testing(now),
"first unknown SNI event must be eligible for WARN emission"
);
assert!(
!should_emit_unknown_sni_warn_for_testing(now + Duration::from_secs(1)),
"events inside cooldown window must be demoted from WARN to INFO"
);
assert!(
should_emit_unknown_sni_warn_for_testing(
now + Duration::from_secs(UNKNOWN_SNI_WARN_COOLDOWN_SECS)
),
"once cooldown expires, next unknown SNI event must be WARN-eligible again"
);
}
#[test] #[test]
fn stress_auth_probe_full_map_churn_keeps_bound_and_tracks_newcomers() { fn stress_auth_probe_full_map_churn_keeps_bound_and_tracks_newcomers() {
let _guard = auth_probe_test_lock() let _guard = auth_probe_test_lock()

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()),