Files
tglock/docs/ARCHITECTURE_V2.md
T
Никита Sonic 9bacc488a5 docs: синхронизировать документацию с состоянием кода (#27)
Аудит репозитория после #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>
2026-07-30 13:34:43 +03:00

8.5 KiB
Raw Blame History

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-порт автоматически принимает два типа клиентов:

  • MTProto proxy с постоянным 16-байтовым secret — основной режим;
  • SOCKS5 без авторизации — режим совместимости.

MTProto init проверяется по secret и transport tag. Из него извлекаются DC, признак media-соединения и тип транспорта. Для upstream создаётся новый стандартный 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-соединений строится список маршрутов:

  1. сохранённый успешный маршрут;
  2. точный Telegram IP с kwsN или kwsN-1 в TLS SNI и WebSocket Host;
  3. дополнительный Telegram IP, если он определён;
  4. системный DNS;
  5. явно настроенный пользователем Cloudflare Worker.

Поддерживаются DC15 и media/CDN DC203. DC203 использует WebSocket-host DC2, но подключается к собственному IP.

После ошибки маршрут получает exponential cooldown от 30 секунд до 30 минут. Успешный маршрут становится первым для следующего соединения того же DC и типа трафика.

TLS policy

Проверка сертификатов и hostname никогда не отключается. При подключении к заданному Telegram IP TCP destination отделён от URI host: TLS продолжает проверять сертификат настоящего kws*.web.telegram.org.

Cloudflare Worker принимается только как пользовательская настройка. TGLock не загружает и не скрывает публичные списки чужих доменов.

Cloudflare Worker contract

Worker должен принимать WebSocket на:

/apiws?dst=<telegram-ip>&dc=<dc-id>

и проксировать бинарные frames в TCP <telegram-ip>:443. Рекомендуется добавить собственную авторизацию до стабильного релиза; поэтому Worker остаётся расширенной опцией alpha-версии.

Current limitations

  • SNI camouflage не включена: небезопасное отключение hostname verification из референсной реализации не переносится.
  • Голосовые звонки по UDP не поддерживаются.
  • GUI не запускается там, где WebView не может создать окно — нет 3D-ускорения, дефолтный видеодрайвер, отсутствует монитор, виртуальная машина. Это ограничение Tauri, а не транспорта; программный рендер как обходной путь не проверен. Рабочий вариант для таких машин — tglock-cli (issues #10, #17).
  • Windows-инсталлятор не подписан, macOS-сборка не нотарифицирована. Часть антивирусов реагирует на неподписанный установщик, открывающий локальный сокет; проверяемый ответ — сборка из исходников либо публичные логи GitHub Actions.
  • Работоспособность медиа зависит от конкретного DC аккаунта и доступности Telegram/Cloudflare у провайдера.
  • Пулы заранее открытых WebSocket-соединений будут добавлены после измерения, что они не создают лишнюю нагрузку и не ухудшают стабильность.