# 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 недоступен в принципе: сервер без графического окружения, контейнер, машина без монитора. Для случаев, где WebView есть, но нет 3D-ускорения, GUI дополнительно просит программный рендер — см. Current limitations. ## 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. ### Что считается адресом Telegram Список сетей — опубликованный самим Telegram (), он лежит в `telegram_net` и проверяется по маске префикса. До 2.0.0-beta.9 сравнивались два первых октета, то есть «телеграмом» считались целиком `149.154.0.0/16`, `91.108.0.0/16`, `91.105.0.0/16` и `185.76.0.0/16`, а IPv6 не распознавался вовсе. Ошибка была в обе стороны: - чужие адреса внутри этих `/16` уходили в MTProto-туннель и умирали там; - настоящие адреса дата-центров по IPv6 отклонялись как посторонние. Второе и давало «на компьютере работает, с телефона нет» (#42): на loopback неопознанный адрес всё равно релеился напрямую, поэтому там дефект не проявлялся, а на сетевом слушателе тот же адрес получал отказ. Назначение делится на три вида: | Вид | Что это | Что делаем | |---|---|---| | Дата-центр | IP из опубликованных сетей, v4 или v6 | заворачиваем в WebSocket | | Веб Telegram | имя из `telegram.org`, `t.me`, `telegram.me`, `telesco.pe`, `cdn-telegram.org` | пропускаем как есть — это обычный HTTPS, а не MTProto | | Всё остальное | — | напрямую на loopback, отказ на сетевом адресе | Имена сопоставляются по границе метки, поэтому `telegram.org.example.com` — посторонний домен. В ограниченном режиме имя разрешается заранее, и адреса внутри локальной сети (`127.0.0.0/8`, `10/8`, `172.16/12`, `192.168/16`, `100.64/10`, `fc00::/7`, `fe80::/10`) отбрасываются: назначение выбирает чужое устройство, и DNS-ответ не должен превращать TGLock в дверь во внутреннюю сеть этой машины. ### Отказ перестаёт быть молчаливым Отклонённый запрос увеличивает счётчик `blocked` и один раз называет адрес в журнале; повторы того же адреса склеиваются, чтобы не забить журнал одной строкой. Отдельно отмечается первое подключение с каждого сетевого адреса. Без этого «с телефона не работает» неразличимо распадалось на два случая: телефон не дошёл до машины (сеть, брандмауэр, изоляция клиентов на роутере) — и дошёл, но попросил адрес, который мы не пропускаем. Первый виден как ноль соединений и ноль отказов, второй — как соединения есть, отказы растут. Третий случай нашёлся, когда репортёр #42 прислал диагностику: у него было ноль отказов и работающие туннели, то есть оба счётчика говорили «всё хорошо». Клиент, который дошёл до прокси, но не сумел договориться, не попадал ни в один из них. Соединение просто закрывалось: `active` дёргался вверх и обратно. Теперь такие клиенты считает `unknown_clients`, и журнал называет адрес и причину. Их две: - MTProto-init не разбирается под текущим секретом. Почти всегда это ссылка `tg://proxy` от прошлого запуска: секрет — её половина, и клиент со сохранённой старой ссылкой попадает ровно сюда. Со стороны Telegram это и есть «прокси настроен неверно и будет отключён» (#37). - SOCKS5-приветствие не разбирается. Сюда же попадает MTProto-соединение, ушедшее в SOCKS5-ветку по неоднозначному первому байту, если полный init не успел прийти за `PROTOCOL_PROBE_TIMEOUT`. Счётчик `ws_failures` от них отличается тем, что растёт после успешного рукопожатия с клиентом: там договорились с клиентом, но не смогли с Telegram. ## Учёт состояния `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. Поддерживаются DC1–5 и 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 на: ```text /apiws?dst=&dc= ``` и проксировать бинарные frames в TCP `:443`. Рекомендуется добавить собственную авторизацию до стабильного релиза; поэтому Worker остаётся расширенной опцией alpha-версии. ## Current limitations - SNI camouflage не включена: небезопасное отключение hostname verification из референсной реализации не переносится. - Голосовые звонки по UDP не поддерживаются. - **GUI зависит от WebView, а тот — от 3D-ускорения.** Начиная с 2.0.0-beta.2 приложение перед стартом Tauri само просит программный рендер: на Windows через `WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS`, на Linux через `WEBKIT_DISABLE_COMPOSITING_MODE` и `WEBKIT_DISABLE_DMABUF_RENDERER`. Уже заданные оператором значения не перезаписываются, а `TGLOCK_FORCE_GPU=1` возвращает аппаратное ускорение. Для машин без монитора остаётся `tglock-cli`, которому WebView не нужен вовсе (issues #10, #17). - Windows-инсталлятор не подписан, macOS-сборка не нотарифицирована, и подписывать их не планируется: сертификат — ежегодный платёж, а проект бесплатный. Часть антивирусов будет реагировать на неподписанный установщик, который открывает локальный сокет и прописывается прокси-сервером — это тот же профиль, по которому ищут прокси-трояны. Вместо доверия предлагаются проверяемые пути: `sha256` каждого артефакта публикуется GitHub на странице релиза, сборка идёт в GitHub Actions из публичного коммита с открытым логом, CLI собирается одной командой. Подробно — в разделе README про антивирус. - Работоспособность медиа зависит от конкретного DC аккаунта и доступности Telegram/Cloudflare у провайдера. - Пулы заранее открытых WebSocket-соединений будут добавлены после измерения, что они не создают лишнюю нагрузку и не ухудшают стабильность.