diff --git a/README.md b/README.md index 889831c..a27e44c 100644 --- a/README.md +++ b/README.md @@ -56,17 +56,23 @@ ## 🤔 Что это и зачем -TGLock — это **локальный SOCKS5-прокси** на твоём компьютере. Он перехватывает соединения Telegram, заворачивает их в WebSocket и отправляет через `web.telegram.org`. Провайдер видит обычный HTTPS — Telegram работает как раньше. +TGLock — это **локальный прокси** на твоём компьютере: принимает и MTProto, и SOCKS5. Он перехватывает соединения Telegram, заворачивает их в WebSocket и отправляет на веб-инфраструктуру Telegram — по нескольким маршрутам сразу, переключаясь на следующий, если текущий перестал отвечать. Провайдер видит обычный HTTPS. **Кому подойдёт:** -- 📱 Telegram заблокировали в России или он стал открываться через раз -- 🐌 Голосовые/видеозвонки рвутся, сообщения уходят с задержкой, фото не грузятся +- 📱 Telegram открывается через раз, сообщения уходят с задержкой, фото и видео не грузятся - 🛡 GoodbyeDPI, Zapret или ByeDPI больше не помогают — провайдер шейпит **по IP** -- 🍎 Нужен инструмент для **macOS** (а на маке нет нормальных GUI-альтернатив) -- 💻 Хочется решение для **Windows, macOS или Linux** без подписок и серверов +- 🍎 Нужен графический интерфейс под **macOS** +- 💻 Нужно решение для **Windows, macOS или Linux** без подписок и без своего сервера +- 🖥 Нужен вариант **для сервера или машины без монитора** — для этого есть [`tglock-cli`](#-без-графического-интерфейса-tglock-cli) -**Чем отличается от VPN:** TGLock работает **только с Telegram**. Остальной трафик идёт напрямую — ничего не замедляется, ничего не логируется, мобильный/домашний трафик не расходуется впустую. +**Чего TGLock не делает** — честно, чтобы не тратить твоё время: + +- ❌ **Голосовые и видеозвонки.** Они идут по UDP, а TGLock проксирует только TCP. Со звонками ничего не изменится +- ❌ **Всё, кроме Telegram.** YouTube, Discord, Instagram, ChatGPT работать не начнут: TGLock разворачивает только MTProto — протокол, который больше нигде не используется +- ❌ **Android и iOS.** Своего приложения нет. Телефон можно подключить к TGLock на компьютере через [LAN-режим](#-lan-режим--один-прокси-на-всю-квартиру) + +**Чем отличается от VPN:** TGLock работает **только с Telegram**. Остальной трафик идёт напрямую — ничего не замедляется, мобильный трафик не расходуется впустую. --- @@ -76,12 +82,13 @@ TGLock — это **локальный SOCKS5-прокси** на твоём к | Платформа | Файл | Размер | |---|---|---| -| **Windows 10/11** (x64) | `.exe` installer | | -| **macOS** (Apple Silicon + Intel) | universal `.dmg` | | -| **Linux** (x86_64) | `.AppImage` / `.deb` | | -| **Сервер / без монитора** (любая ОС) | `tglock-cli-*` | | +| **Windows 10/11** (x64) | `_x64-setup.exe` | ~2 МБ | +| **macOS** (Apple Silicon + Intel) | universal `.dmg` | ~7 МБ | +| **Linux** (x86_64) | `.deb` | ~3 МБ | +| **Linux** (x86_64, портативно) | `.AppImage` | ~79 МБ | +| **Сервер / без монитора** (любая ОС) | `tglock-cli-*` | ~2 МБ | -> **🖥 `tglock-cli`** — тот же туннель без графического интерфейса, одним бинарём. Нужен, если окно не создаётся: сервер, контейнер, виртуалка, машина без монитора или без 3D-ускорения. Подробности — [ниже](#-без-графического-интерфейса-tglock-cli). +> **🖥 `tglock-cli`** — тот же туннель без графического интерфейса, одним бинарём. Нужен там, где окно просто не создаётся: сервер, контейнер, виртуалка, машина без монитора или без 3D-ускорения. Подробности — [ниже](#-без-графического-интерфейса-tglock-cli). > **🍎 macOS:** пока сборка не нотарифицирована Apple, при первом запуске может понадобиться: > ```bash @@ -107,9 +114,13 @@ Telegram → Настройки → **Продвинутые** → Тип сое ### 🏠 LAN-режим — один прокси на всю квартиру -В окне TGLock включи галочку **LAN** — приложение начнёт слушать на `0.0.0.0`. Все устройства в твоей домашней сети (телефон, планшет, ноутбук, телевизор) смогут подключиться к `<твой-IP>:1080` и тоже получить рабочий Telegram. IP отобразится прямо в интерфейсе TGLock — копируй и вписывай в настройки Telegram на остальных устройствах. +В окне TGLock включи галочку **LAN** — приложение начнёт слушать на `0.0.0.0`. Все устройства в домашней сети (телефон, планшет, ноутбук, телевизор) смогут подключиться к `:1080` и тоже получить рабочий Telegram. -Удобно, если дома один комп всегда включён — он становится «домашним Telegram-роутером». +Ссылку `tg://proxy` с уже подставленным адресом TGLock открывает сам при включении — её достаточно переслать себе в Telegram и открыть на телефоне. Если нужен адрес руками: `ipconfig` на Windows, `ip a` на Linux, `ifconfig` на macOS. + +Удобно, если дома один компьютер всегда включён — он становится «домашним Telegram-роутером». + +В LAN-режиме TGLock пропускает **только адреса Telegram**. Открытым SOCKS5-прокси для всего интернета он при этом не становится — иначе им бы воспользовались не только твои устройства. ### 🖥 Без графического интерфейса: `tglock-cli` @@ -193,7 +204,7 @@ docker run -d --name tglock -p 1080:1080 -v tglock-data:/data tglock ``` Telegram Desktop / mobile (через LAN) ▼ - SOCKS5 (127.0.0.1:1080 или 0.0.0.0:1080) + MTProto или SOCKS5 (127.0.0.1:1080 либо 0.0.0.0:1080) ▼ TGLock — читает первые 64 байта obfuscated2 init-пакета, @@ -201,17 +212,21 @@ Telegram Desktop / mobile (через LAN) достаёт номер DC ▼ WSS → kws{dc}.web.telegram.org + каскад маршрутов, см. ниже ▼ Telegram Data Center ``` -1. **Локальный SOCKS5-прокси** перехватывает соединения Telegram Desktop. -2. Из первых 64 байт `obfuscated2`-пакета **расшифровывается номер DC** — AES-256-CTR, ключ в байтах `[8..40]`, IV в `[40..56]`, DC ID — `i32` в `[60..64]`. -3. Трафик заворачивается в **WebSocket** к `kws{dc}.web.telegram.org` — это **тот же домен**, через который работает Telegram Web в браузере. -4. Провайдер видит **TLS-handshake к `web.telegram.org`** — это легитимный HTTPS. DPI не видит MTProto. IP-шейпинг не работает, потому что `web.telegram.org` не блокируется в принципе. -5. Весь остальной трафик (не-Telegram) проходит **напрямую** — без замедления. +1. **Локальный прокси** принимает соединения Telegram: и MTProto (по ссылке `tg://proxy`), и SOCKS5. +2. Из первых 64 байт `obfuscated2`-пакета **расшифровывается номер DC** — AES-256-CTR, ключ в байтах `[8..40]`, IV в `[40..56]`, индекс DC — `i16` в `[60..62]`. Отрицательное значение означает медиа-соединение. +3. Трафик заворачивается в **WebSocket** к `kws{dc}.web.telegram.org` — это тот же домен, через который работает Telegram Web в браузере. +4. **Маршрут выбирается каскадом**, и это главное отличие 2.0 от первой версии. Один домен может резолвиться в недоступный адрес, поэтому по очереди пробуются: закреплённые IP Telegram, их дублёры `kwsN-1`, системный DNS и — если ты его настроил — твой собственный Cloudflare Worker. Упавший маршрут уходит в cooldown с удвоением задержки, удачный запоминается для этого DC. Системный DNS и файл `hosts` при этом **не изменяются**: TCP-соединение идёт на закреплённый IP, а TLS SNI и заголовок `Host` остаются настоящими, поэтому сертификат Telegram проверяется как обычно. +5. Провайдер видит **TLS-handshake к `web.telegram.org`** — легитимный HTTPS, MTProto в нём не виден. +6. Весь остальной трафик (не-Telegram) проходит **напрямую** — без замедления. -📖 **Подробный технический разбор архитектуры** — см. [HABR.md](HABR.md) (≈7 мин чтения, история v1 → v2, AES-decrypt, bias `select!` для Pong, кроссплатформенная сборка). +> Интерфейс различает три состояния и не выдаёт одно за другое: **«Защита включена»** — локальный порт открыт, туннеля пока нет; **«Ищем новый маршрут»** — попытки были неудачными, идёт перебор; **«Telegram на связи»** — есть установленный туннель, то есть WebSocket-рукопожатие уже прошло. Смешивание первого и третьего состояния и было основной причиной жалоб «прокси подключён, а Telegram не работает». + +📖 **Подробный технический разбор** — [HABR.md](HABR.md) (история v1 → v2, AES-decrypt, bias `select!` для Pong, кроссплатформенная сборка). Архитектура 2.0 и её ограничения — [docs/ARCHITECTURE_V2.md](docs/ARCHITECTURE_V2.md), разбор всех issue и того, что в них было обещано зря — [docs/ISSUE_AUDIT.md](docs/ISSUE_AUDIT.md). --- @@ -225,10 +240,13 @@ Telegram Desktop / mobile (через LAN) | Нужен сервер / подписка | ❌ | ❌ | ✅ ($) | **❌** | | Только Telegram | ❌ | ❌ | ❌ | **✅** | | LAN-шаринг | ❌ | сложно | ✅ | **✅ (галочка)** | -| Размер | ~200 КБ | ~5 МБ | ~80 МБ | **компактное desktop-приложение** | -| Цена | 0 ₽ | 0 ₽ | $3–10/мес | **0 ₽** | +| Режим без GUI | ✅ | ✅ | ❌ | **✅ (`tglock-cli`)** | +| Размер | ~200 КБ | ~5 МБ | ~80 МБ | **2 МБ установщик, 2 МБ CLI** | +| Цена | 0 ₽ | 0 ₽ | свой сервер | **0 ₽** | -> **⚠ Когда TGLock не подойдёт:** если заблокирован не только Telegram, а ещё YouTube, Discord, Instagram, ChatGPT, Spotify — нужен полноценный VPN. Тут поможет **[🌹 RoseVPN](https://t.me/rosevpnru_bot)** (см. блок ниже). +> **⚠ Когда TGLock не подойдёт:** если заблокирован не только Telegram, а ещё YouTube, Discord, Instagram или ChatGPT — обходить каждый сервис отдельно смысла нет, нужен полноценный VPN. TGLock эту задачу не решает и решать не будет: он разворачивает только MTProto. +> +> Звонки тоже не заработают — они по UDP, а TGLock проксирует только TCP. --- @@ -237,13 +255,17 @@ Telegram Desktop / mobile (через LAN)
Telegram заблокировали в России — это правда? -Полностью Telegram в РФ не заблокирован, но провайдеры **замедляют** трафик через DPI и **шейпят по IP-диапазонам** Telegram DC (149.154.160–175, 91.108.4–8, 91.108.56–59 и др.). У части пользователей мессенджер открывается через раз, голосовые звонки рвутся, видео не грузится, фото уходят минутами. TGLock решает именно эту проблему — заворачивает Telegram-трафик в HTTPS к `web.telegram.org`, который не блокируется. +Полностью Telegram в РФ не заблокирован, но провайдеры **замедляют** трафик через DPI и **шейпят по IP-диапазонам** Telegram DC (149.154.160–175, 91.108.4–8, 91.108.56–59 и др.). У части пользователей мессенджер открывается через раз, видео не грузится, фото уходят минутами. + +TGLock решает именно это — заворачивает Telegram-трафик в HTTPS к веб-инфраструктуре Telegram, которая под шейпинг не попадает. **Голосовые и видеозвонки он не лечит:** они идут по UDP, а TGLock проксирует только TCP.
Это безопасно? Что с моими данными? -TGLock — **локальный прокси**. Он работает только на твоём компьютере и не отправляет данные третьим сторонам. Соединение идёт напрямую к серверам Telegram через их же домен `web.telegram.org` — тот же, что использует Telegram Web в браузере. Кода ~350 строк, всё открыто на GitHub — можно прочитать и собрать самому. +TGLock — **локальный прокси**. Он работает только на твоём компьютере и не отправляет данные третьим сторонам. Соединение идёт к серверам Telegram через их же домен `web.telegram.org` — тот же, что использует Telegram Web в браузере. Единственное исключение — если ты сам укажешь в настройках свой Cloudflare Worker как резервный маршрут; по умолчанию это поле пустое, и никакой сторонней инфраструктуры в схеме нет. + +Кода — около 2900 строк Rust (из них ~1100 приходится на тесты) и ~380 строк TypeScript на интерфейс. Всё открыто, можно прочитать и собрать самому. Бинарники в релизах собираются из этого же исходника в GitHub Actions — логи сборки публичные.
@@ -251,15 +273,18 @@ TGLock — **локальный прокси**. Он работает тольк GoodbyeDPI, Zapret и ByeDPI **фрагментируют пакеты**, чтобы DPI не распознал MTProto. Это работает, пока провайдер блокирует *по содержимому*. Но если шейпинг идёт **по IP** (а так делают большинство крупных РФ-провайдеров с 2024–2026 — Ростелеком, МТС, Билайн, Мегафон), фрагментация не помогает: пакеты всё равно идут на «нехороший» IP и троттлятся. -TGLock же отправляет трафик на **`web.telegram.org`** — обычный HTTPS-домен, который не блокируется в принципе. +TGLock же отправляет трафик на **`web.telegram.org`** — обычный HTTPS-домен, который под IP-шейпинг Telegram DC не попадает.
Работает ли на iPhone или Android? -Напрямую — нет, TGLock сам по себе только для desktop. Но если включить **LAN-режим** на компьютере, в настройках Telegram на телефоне можно указать SOCKS5-прокси с IP компа. Telegram на мобиле начнёт ходить через ПК. Удобно, если дома один компьютер всегда включён. +Своего приложения под Android и iOS нет — TGLock только для desktop. Есть два обходных пути: -Для полностью мобильного решения нужен VPN — например, **[🌹 RoseVPN](https://t.me/rosevpnru_bot)** с приложением Karing для iOS/Android. +1. **LAN-режим на компьютере.** Включи галочку LAN, и в настройках Telegram на телефоне укажи прокси с IP компьютера. Работает, пока компьютер включён и телефон в той же сети. +2. **`tglock-cli` на своём VPS.** Headless-бинарь запускается как systemd-сервис, слушает `0.0.0.0` и пропускает только адреса Telegram — тогда телефон работает откуда угодно, а не только из дома. См. [раздел про CLI](#-без-графического-интерфейса-tglock-cli). + +Поддержка Android обсуждается в [#9](https://github.com/by-sonic/tglock/issues/9), сроков нет: Tauri 2 умеет собирать под Android, но перехват трафика там делается через `VpnService` — это другая архитектура, а не пересборка того же кода.
@@ -271,11 +296,10 @@ TGLock же отправляет трафик на **`web.telegram.org`** — о
Apple ругается «приложение не проверено / нельзя открыть» -Подпись Apple Developer ID стоит $99 в год — для бесплатного open-source это перебор. Сними блокировку Gatekeeper руками — открой Терминал и выполни: +Сборка пока не подписана и не нотарифицирована — Apple Developer ID стоит $99 в год. Сними карантин Gatekeeper руками: перенеси приложение из `.dmg` в «Программы» и выполни в Терминале ```bash -xattr -cr ~/Downloads/tglock-macos-arm64 -chmod +x ~/Downloads/tglock-macos-arm64 +xattr -cr /Applications/TGLock.app ``` После этого приложение запустится двойным кликом из Finder. @@ -301,19 +325,27 @@ chmod +x ~/Downloads/tglock-macos-arm64
А что если провайдер заблокирует и web.telegram.org? -Тогда TGLock перестанет работать у этого конкретного провайдера. Но **публичная блокировка веб-версии Telegram** — это большой шаг, и Роскомнадзор пока на него не идёт. Если всё же случится — используй **[🌹 RoseVPN](https://t.me/rosevpnru_bot)**, там домен фронтирования автоматически меняется (SNI rotation, Reality), и пробивает даже агрессивный DPI. +Это реальный риск, и TGLock 2.0 к нему подготовлен настолько, насколько может. + +Маршрут не один: пробуются закреплённые IP Telegram, дублёры `kwsN-1` и системный DNS. Пока жив хотя бы один — туннель поднимается. Дополнительно можно указать в настройках **свой Cloudflare Worker** — тогда соединение пойдёт через твой домен на `workers.dev`, и заблокировать его провайдеру придётся отдельно. + +Но честно: если веб-версию Telegram заблокируют целиком, подход исчерпает себя. TGLock держится на том, что `web.telegram.org` доступен, и никакой запас маршрутов этого не отменяет. В таком случае нужен полноценный VPN — это уже другая задача.
Можно ли использовать TGLock как обычный SOCKS5 для других приложений? -Не рекомендуется. TGLock детектирует Telegram-трафик по IP получателя и оборачивает в WebSocket только его. Остальное идёт напрямую — без шифрования и аутентификации, как обычный SOCKS5-релей. Для других приложений возьми правильный SOCKS5-сервер (или VPN). +Смысла нет, и по умолчанию это запрещено. + +TGLock определяет Telegram по IP получателя и заворачивает в WebSocket только его. Не-Telegram адреса он релеит напрямую — без шифрования, то есть никакой пользы для обхода в этом нет. + +Поэтому такой релей разрешён **только когда прокси слушает `127.0.0.1`**, где до него дотянутся лишь процессы твоего компьютера. На `0.0.0.0` и любом сетевом адресе не-Telegram запросы отклоняются: иначе LAN-режим сделал бы из твоей машины открытый прокси для всего интернета. В `tglock-cli` это можно переопределить флагом `--allow-direct` — но на сетевом адресе ты получишь именно открытый SOCKS5, так что делай это осознанно.
Где скачать новые версии? Будут ли обновления? -Все релизы — на странице **[GitHub Releases](https://github.com/by-sonic/tglock/releases)**. При пуше тега `v*` GitHub Actions автоматически собирает бинарники для всех 4 платформ и публикует. Подпишись на репозиторий (кнопка **Watch** → **Custom** → **Releases**), чтобы получать уведомления о новых версиях. +Все релизы — на странице **[GitHub Releases](https://github.com/by-sonic/tglock/releases)**. При пуше тега `v*` GitHub Actions собирает и публикует установщики под Windows x64, macOS (universal) и Linux x64, плюс headless `tglock-cli` под те же три платформы. Подпишись на репозиторий (кнопка **Watch** → **Custom** → **Releases**), чтобы получать уведомления.
--- @@ -323,11 +355,14 @@ chmod +x ~/Downloads/tglock-macos-arm64 | Технология | Зачем | |---|---| | **Rust** | Один бинарник, нативная скорость, без runtime-зависимостей | -| **Tauri 2** | Нативная кроссплатформенная оболочка с современным web-интерфейсом | +| **Tauri 2** | Нативная оболочка для GUI. Опциональна: за фичей `gui`, в CLI не входит | | **TypeScript + Vite** | Интерфейс, внутренняя навигация и строгая типизация | -| **tokio** | Async I/O для тысяч одновременных соединений | +| **tokio** | Async I/O, обработка сигналов для корректной остановки сервиса | | **tokio-tungstenite** | WebSocket-клиент с TLS поверх `native-tls` | | **aes** + **ctr** | Расшифровка MTProto `obfuscated2` init-пакета | +| **clap** | Разбор аргументов `tglock-cli` | + +Ядро (`src/lib.rs`: разбор MTProto, каскад маршрутов, прокси) не зависит ни от Tauri, ни от оконной системы — поэтому один и тот же код обслуживает и графический интерфейс, и headless-режим. --- @@ -352,32 +387,34 @@ cargo build --release --locked --no-default-features --bin tglock-cli Ни Node.js, ни фронтенда, ни `libwebkit2gtk` для этого не нужно — при выключенной фиче `gui` Tauri и системный WebView в сборку не попадают вообще. Именно так CLI собирается на голом сервере. -### Кросс-компиляция через GitHub Actions +### Проверки, которые гоняет CI -Хочешь собрать свой релиз? Форкни репозиторий, поставь тег `v1.0.1`, и `.github/workflows/release.yml` сам соберёт бинарники под Windows x64, macOS ARM64, macOS Intel и Linux x64. +```bash +cargo fmt --check +cargo clippy --all-targets -- -D warnings +cargo clippy --no-default-features --lib --bins --all-targets -- -D warnings +cargo test --all-targets +cargo test --no-default-features --lib --bins +``` + +Тестов 59: разбор `obfuscated2`, каскад маршрутов и его cooldown, протокольные отказы SOCKS5, устойчивость секрета к перезапуску, плюс сквозной тест туннеля против мок-сервера, который реализует сторону Telegram и проверяет, что до неё доходит ровно тот открытый текст, который отправил клиент. Единственный тест с пометкой `#[ignore]` — тот, что требует живой сети. + +### Свой релиз через GitHub Actions + +Форкни репозиторий и поставь тег `v*` — `.github/workflows/release.yml` соберёт установщики под Windows x64, macOS (universal, Apple Silicon + Intel) и Linux x64, а также `tglock-cli` под те же три платформы, и опубликует их в релизе. --- -## 🌹 Нужен VPN на всё подряд? +## 🤝 Как помочь -Если у тебя заблокирован **не только Telegram**, а ещё YouTube, Discord, Instagram, ChatGPT, Spotify — обходить каждое приложение отдельно нет смысла. Возьми VPN, который умеет всё сразу. +Проект живой, PR и баг-репорты разбираются. -

- - Подключить RoseVPN — Telegram-бот - -

+- **Нашёл баг** — [открой issue](https://github.com/by-sonic/tglock/issues/new). Полезнее всего: ОС и версия, что показывает вкладка диагностики (маршрут, DC, число сбоев) и провайдер. Для `tglock-cli` — вывод из консоли. +- **Хочешь фичу** — тоже issue. Если её нет в планах, так и будет написано, без месяцев тишины. +- **Присылаешь PR** — перед отправкой прогони проверки выше, они те же, что в CI. Небольшие PR ревьюятся быстрее. +- **Не работает после релиза** — это регрессия, пиши сразу, такие вещи в приоритете. -**Что внутри RoseVPN:** - -- 🔥 **Hysteria2 + VLESS-Reality fallback** — обходит TSPU и агрессивный DPI -- 🛡 **Без логов трафика** — приватность по умолчанию -- 🎁 **Бесплатный пробный период** — без карты, без регистрации -- 📱 **Karing-клиент** с автонастройкой — установка в 2 тапа -- 💻 **Windows, macOS, iOS, Android** — везде нативные приложения -- 🔄 **SNI-ротация** на случай новых блокировок - -Подключение — через Telegram-бот **[@rosevpnru_bot](https://t.me/rosevpnru_bot)**. +Известные ограничения, о которых не нужно открывать issue: звонки (UDP), сервисы кроме Telegram (только MTProto), Android и iOS (обсуждается в [#9](https://github.com/by-sonic/tglock/issues/9)). --- @@ -388,5 +425,5 @@ cargo build --release --locked --no-default-features --bin tglock-cli ---

- by sonic · @rosevpnru_bot · Issues & feedback + by sonic · Issues & feedback · Releases

diff --git a/src/proxy.rs b/src/proxy.rs index 82fe689..9497273 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -242,11 +242,9 @@ async fn handle_socks5( }); stats.last_dc.store(dc, Ordering::Relaxed); - stats.ws.fetch_add(1, Ordering::Relaxed); let r = ws_tunnel(s, dc, media, &init, None, stats).await; - stats.ws.fetch_sub(1, Ordering::Relaxed); if r.is_err() { stats.ws_failures.fetch_add(1, Ordering::Relaxed); } @@ -274,7 +272,6 @@ async fn handle_mtproto( .ok_or("invalid MTProto init or secret")?; stats.last_dc.store(parsed.dc, Ordering::Relaxed); - stats.ws.fetch_add(1, Ordering::Relaxed); let result = ws_tunnel( stream, parsed.dc, @@ -284,7 +281,6 @@ async fn handle_mtproto( stats, ) .await; - stats.ws.fetch_sub(1, Ordering::Relaxed); if result.is_err() { stats.ws_failures.fetch_add(1, Ordering::Relaxed); } @@ -401,6 +397,28 @@ fn dc_from_ip(ip: Ipv4Addr) -> Option { // -- WebSocket tunnel ------------------------------------------------------- +/// Keeps `Stats::ws` equal to the number of *established* tunnels. +/// +/// Counting attempts instead would let the interface announce «Telegram на +/// связи» while the WebSocket handshake is still failing over between routes, +/// which takes seconds per route. Reporting a working tunnel that does not +/// exist yet is the whole reason users saw «прокси подключён, а Telegram не +/// работает». +struct EstablishedTunnel<'a>(&'a Stats); + +impl<'a> EstablishedTunnel<'a> { + fn new(stats: &'a Stats) -> Self { + stats.ws.fetch_add(1, Ordering::Relaxed); + Self(stats) + } +} + +impl Drop for EstablishedTunnel<'_> { + fn drop(&mut self) { + self.0.ws.fetch_sub(1, Ordering::Relaxed); + } +} + async fn ws_tunnel( tcp: TcpStream, dc: u16, @@ -412,6 +430,7 @@ async fn ws_tunnel( use futures_util::{SinkExt, StreamExt}; let (mut ws, connected) = stats.transport.connect(dc, media).await?; + let _tunnel = EstablishedTunnel::new(stats); stats .last_route .store(connected.route.kind.ui_code(), Ordering::Relaxed); @@ -900,6 +919,48 @@ mod tests { let _ = server.await.unwrap(); } + #[tokio::test] + async fn a_tunnel_counts_only_after_the_handshake_succeeds() { + // Accepts TCP and then stays silent, so the WebSocket handshake never + // completes: the proxy is mid-attempt and no tunnel exists. + let silent = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let relay_port = silent.local_addr().unwrap().port(); + let held = tokio::spawn(async move { + let accepted = silent.accept().await; + tokio::time::sleep(Duration::from_secs(30)).await; + drop(accepted); + }); + + let stats = Stats::new(); + stats.transport.force_local_route(relay_port); + let (port, server) = start_proxy(stats.clone(), false).await; + + let init = unambiguous_client_init(&stats.secret, 2); + let mut client = TcpStream::connect(("127.0.0.1", port)).await.unwrap(); + client.write_all(&init).await.unwrap(); + + wait_until("the init to be parsed", || { + stats.last_dc.load(Ordering::Relaxed) == 2 + }) + .await; + tokio::time::sleep(Duration::from_millis(300)).await; + + assert_eq!( + stats.ws.load(Ordering::Relaxed), + 0, + "a handshake still in flight must not be reported as a working tunnel" + ); + assert_eq!( + stats.last_route.load(Ordering::Relaxed), + 0, + "no route may be announced before a tunnel is established" + ); + + stats.stop(); + held.abort(); + let _ = server.await.unwrap(); + } + #[tokio::test] async fn counts_a_failure_when_no_route_answers() { let dead = TcpListener::bind("127.0.0.1:0").await.unwrap();