From 9bacc488a54ea19c8c1aa3c8f5f5692597a1fcd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20Sonic?= Date: Thu, 30 Jul 2026 13:34:43 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE?= =?UTF-8?q?=D0=BD=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20=D1=81=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=BA=D0=BE=D0=B4=D0=B0=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Аудит репозитория после #25 и #26. Расхождения между тем, что написано, и тем, что есть: - README обещал `tglock-cli-*` в таблице загрузок, но в релизе v2.0.0-beta.1 такого артефакта нет: задача `cli` в release.yml сработает только на следующем теге. Заменено на честную формулировку с командой сборки из main. - ui/main.ts держал начальным значением маршрута строку «Автоматический маршрут», которую бэкенд больше не отдаёт: при коде 0 возвращается «Маршрут ещё не выбран». Иначе до первого опроса статуса интерфейс показывал название несуществующего маршрута. - ARCHITECTURE_V2.md не упоминал ни разделения на библиотеку и два бинаря, ни фичи `gui`, ни правила различения протоколов, ни политики прямого релея, ни того, что GUI не запускается без WebView. Добавлены разделы, а последнее внесено в Current limitations вместе с отсутствием подписи бинарей. - ISSUE_AUDIT.md описывал состояние на 29 июля. Сам аудит оставлен как фиксация на дату, сверху добавлен статус на 30 июля: что закрыто, что осталось открытым и почему, и что исправлено вне списка issues. - HABR.md — черновик статьи, а не документация, но был указан в README как «подробный технический разбор». В нём «два файла, 350 строк, четыре платформы», тогда как сейчас 2872 строки Rust, семь файлов и три платформы плюс headless. Добавлена шапка с поправкой, ссылка в README переписана так, чтобы читателя не отправляли к устаревшим числам за документацией. Проверено: fmt, clippy в обоих вариантах сборки, 47 + 12 тестов, npm run build, все ссылки на файлы в .md существуют. Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com> --- HABR.md | 6 ++++ README.md | 13 +++++++-- docs/ARCHITECTURE_V2.md | 63 +++++++++++++++++++++++++++++++++++++++++ docs/ISSUE_AUDIT.md | 28 ++++++++++++++++++ ui/main.ts | 2 +- 5 files changed, 108 insertions(+), 4 deletions(-) diff --git a/HABR.md b/HABR.md index 86cd88e..aea793d 100644 --- a/HABR.md +++ b/HABR.md @@ -1,5 +1,11 @@ # TGLock v2: переписал обход Telegram с нуля — теперь работает на маке, и один прокси на всю квартиру +> **Это черновик статьи, а не документация.** Цифры в нём описывают код на +> момент написания: «два файла, 350 строк, четыре платформы». Сейчас это 2872 +> строки Rust (из них около 1140 — тесты), семь файлов и три платформы плюс +> headless-бинарь. Актуальное описание — [README](README.md) и +> [docs/ARCHITECTURE_V2.md](docs/ARCHITECTURE_V2.md). + **Простой · 7 мин · Rust · Open source · macOS · Сетевые технологии** **TL;DR:** Полмесяца назад я выложил TGLock — обход блокировки Telegram через WebSocket-туннель. Статья залетела на 183K просмотров. А потом всё сломалось. Соединения рвались через 2 минуты, DC определялся неправильно, маководы плакали в комментах. Переписал с нуля. 350 строк. Работает на macOS, Windows, Linux. Один прокси — все устройства в квартире. Код: [github.com/by-sonic/tglock](https://github.com/by-sonic/tglock). diff --git a/README.md b/README.md index a27e44c..92507f9 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,16 @@ TGLock — это **локальный прокси** на твоём компь | **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-ускорения. +> +> Он есть в `main` и собирается одной командой, а в готовые сборки релиза попадёт начиная со следующего тега: +> +> ```bash +> cargo build --release --locked --no-default-features --bin tglock-cli +> ``` +> +> Подробности — [ниже](#-без-графического-интерфейса-tglock-cli). > **🍎 macOS:** пока сборка не нотарифицирована Apple, при первом запуске может понадобиться: > ```bash @@ -226,7 +233,7 @@ Telegram Desktop / mobile (через LAN) > Интерфейс различает три состояния и не выдаёт одно за другое: **«Защита включена»** — локальный порт открыт, туннеля пока нет; **«Ищем новый маршрут»** — попытки были неудачными, идёт перебор; **«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). +📖 **Архитектура 2.0, различение протоколов и честный список ограничений** — [docs/ARCHITECTURE_V2.md](docs/ARCHITECTURE_V2.md). Разбор всех issue и того, что в них было обещано зря — [docs/ISSUE_AUDIT.md](docs/ISSUE_AUDIT.md). Черновик статьи про переход v1 → v2 лежит в [HABR.md](HABR.md) — цифры там описывают код на момент написания, документацией он не является. --- diff --git a/docs/ARCHITECTURE_V2.md b/docs/ARCHITECTURE_V2.md index 6450bbd..fc39c20 100644 --- a/docs/ARCHITECTURE_V2.md +++ b/docs/ARCHITECTURE_V2.md @@ -1,5 +1,21 @@ # TGLock 2.0 architecture +## Сборка: ядро, GUI и headless + +Крейт собирается в библиотеку и два бинаря: + +- `src/lib.rs` — ядро: `mtproto`, `transport`, `proxy`, `config`. Не зависит ни + от Tauri, ни от оконной системы; +- `src/main.rs` — GUI, доступен только при включённой фиче `gui` + (`required-features`); +- `src/bin/cli.rs` — headless `tglock-cli`. + +Фича `gui` включена по умолчанию и подтягивает `tauri`, `tauri-build` и `open`. +При `--no-default-features` ни Tauri, ни системный WebView, ни фронтенд в сборку +не попадают, и `build.rs` не вызывает `tauri_build`. Это единственный способ +собрать и запустить TGLock там, где WebView не создаёт окно: сервер, контейнер, +виртуалка, машина без монитора или без 3D-ускорения. + ## Local protocols Один TCP-порт автоматически принимает два типа клиентов: @@ -12,6 +28,44 @@ MTProto init проверяется по secret и transport tag. Из него стандартный obfuscated2 init, а последующий поток пере-шифровывается между локальным secret и Telegram. +### Различение протоколов + +Определять протокол по первому байту нельзя. SOCKS5-приветствие начинается с +`0x05`, но MTProto init — это 64 случайных байта, и `is_reserved_init` +исключает только `0xef`, `0xee`, `0xdd`, HTTP-глаголы и заголовок TLS-записи. +Значение `0x05` попадается примерно в одном init из 256, и такое соединение +уходило в SOCKS5-ветку и умирало — снаружи это выглядит как «Telegram +отправляет сообщения через раз». + +Поэтому неоднозначный первый байт разрешается так: не потребляя данные, +ожидается полный 64-байтовый init и делается попытка разобрать его под текущим +secret. Успешный разбор означает MTProto. Настоящий SOCKS5-клиент присылает +короткое приветствие и блокируется на ответе, поэтому 64 байта у него не +появятся и по истечении короткого таймаута он корректно уходит в SOCKS5-ветку. + +## Политика прямого релея + +Не-Telegram адреса релеятся напрямую, без шифрования, и пользы для обхода в +этом нет. Поэтому такой релей разрешён только когда прокси слушает loopback, +где до него дотягиваются лишь локальные процессы. На `0.0.0.0` и любом другом +сетевом адресе не-Telegram запросы отклоняются кодом SOCKS5 `0x02`, иначе +LAN-режим превращал бы машину в открытый прокси. Правило выражено в типе +`config::ListenConfig`, а не в условиях по месту вызова; переопределяется +только явным `--allow-direct` в CLI. + +## Учёт состояния + +`Stats::ws` считает **установленные** туннели: счётчик поднимается после +успешного `transport.connect`, а не перед попыткой. Иначе интерфейс +рапортовал бы «Telegram на связи», пока рукопожатие ещё перебирает маршруты по +несколько секунд каждый. Состояния «порт открыт», «идёт перебор маршрутов» и +«туннель установлен» различимы и в GUI, и в выводе CLI. + +Секрет прокси — половина ссылки `tg://proxy`. Для сервиса его нужно закрепить +файлом (`--secret-file`): под `DynamicUser` и `ProtectHome` домашней папки нет, +путь по умолчанию не определяется, и секрет генерировался бы заново при каждом +старте, отключая всех уже настроенных клиентов. + ## Transport cascade Для каждого DC и отдельно для media-соединений строится список маршрутов: @@ -55,6 +109,15 @@ Worker должен принимать WebSocket на: - SNI camouflage не включена: небезопасное отключение hostname verification из референсной реализации не переносится. - Голосовые звонки по UDP не поддерживаются. +- **GUI не запускается там, где WebView не может создать окно** — нет + 3D-ускорения, дефолтный видеодрайвер, отсутствует монитор, виртуальная + машина. Это ограничение Tauri, а не транспорта; программный рендер как + обходной путь не проверен. Рабочий вариант для таких машин — `tglock-cli` + (issues #10, #17). +- Windows-инсталлятор не подписан, macOS-сборка не нотарифицирована. Часть + антивирусов реагирует на неподписанный установщик, открывающий локальный + сокет; проверяемый ответ — сборка из исходников либо публичные логи + GitHub Actions. - Работоспособность медиа зависит от конкретного DC аккаунта и доступности Telegram/Cloudflare у провайдера. - Пулы заранее открытых WebSocket-соединений будут добавлены после измерения, diff --git a/docs/ISSUE_AUDIT.md b/docs/ISSUE_AUDIT.md index f6c4181..c98cf04 100644 --- a/docs/ISSUE_AUDIT.md +++ b/docs/ISSUE_AUDIT.md @@ -3,6 +3,34 @@ Проверено 29 июля 2026 года: все 15 issues и 5 pull requests, существовавшие в репозитории на момент аудита. +> **Статус на 30 июля 2026.** Аудит ниже оставлен как есть — это фиксация +> состояния на дату проверки. Что с тех пор сделано: +> +> - Разобраны все issues и pull requests. Открытых PR не осталось. +> - Закрыты #1–#5, #8, #11, #13, #14, #19, #23 и #3 — с техническими +> объяснениями в самих issues. +> - #15 реализован заново поверх архитектуры 2.0 в #25: смерджить исходный PR +> было нельзя, он патчил `bypass.rs`, `network.rs` и `ws_proxy.rs`, которых +> больше нет, и правил системный DNS. Взято разделение GUI/CLI и произвольный +> bind-адрес; DNS-менеджмент и проверка root отброшены как ненужные. +> - #12 закрыт: относился к шрифту старого egui-интерфейса. +> - #10 и #17 **оставлены открытыми осознанно.** Появился headless `tglock-cli`, +> который на таких машинах работает, но сам GUI по-прежнему не создаёт окно +> без 3D-ускорения. Это обход, а не исправление. +> - #9 (Android) остаётся в backlog без сроков, #21 ждёт подтверждения на +> пересобранной сборке macOS. +> +> Дополнительно исправлено то, чего в issues не было: коллизия MTProto-init с +> байтом `0x05` (одно соединение из 256 уходило в SOCKS5-ветку и умирало), +> подсчёт туннеля до успешного рукопожатия, неверные подписи маршрутов в +> интерфейсе и генерация нового секрета при каждом старте сервиса. Подробности — +> в [ARCHITECTURE_V2.md](ARCHITECTURE_V2.md). +> +> Из списка «не подтверждённых обещаний» в конце документа закрыты все четыре +> пункта: формулировки про звонки и про «Подключено» приведены в соответствие с +> кодом, LAN-режим ограничен адресами Telegram на уровне типа, Cloudflare Worker +> остаётся исключительно пользовательской настройкой. + ## Выводы Главная причина жалоб «прокси подключён, но Telegram не работает» — приложение diff --git a/ui/main.ts b/ui/main.ts index 807307d..2d8f6f7 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -35,7 +35,7 @@ let status: Status = { activeConnections: 0, tunnels: 0, dataCenter: null, - route: "Автоматический маршрут", + route: "Маршрут ещё не выбран", failures: 0, uptimeSeconds: 0, port: 1080,