mirror of
https://github.com/by-sonic/tglock.git
synced 2026-08-04 15:12:40 +03:00
a87e26c581
Android грузит приложение как нативную библиотеку и входит через JNI-символ, а не через main. Поэтому Tauri-приложение переехало из src/main.rs в src/gui.rs внутри библиотеки, с #[cfg_attr(mobile, tauri::mobile_entry_point)], а main.rs стал обёрткой. Модуль объявлен как #[cfg(feature = "gui")], так что свойство «--no-default-features даёт ядро без Tauri и WebView» сохранилось — проверено сборкой и 51 тестом headless-варианта. Переход на rustls — вынужденный, но выгодный. native-tls на Linux и Android тянет OpenSSL, а openssl-sys не кросскомпилируется под aarch64-linux-android: сборка падала на нём. rustls на чистом Rust, корневые сертификаты webpki вшиты в бинарь. Побочно: на Linux исчезла зависимость от системного libssl, а образу Docker больше не нужен даже ca-certificates. На этом переходе тест живой сети поймал баг, который прошёл бы в релиз: rustls 0.23 отказывается угадывать криптопровайдер и ПАНИКУЕТ на первом TLS- рукопожатии. Компиляция чистая, все офлайновые тесты зелёные — они ходят через локальный маршрут без TLS. То есть в сборку ушло бы приложение, не способное подключиться ни к чему. Провайдер (ring, кросскомпилируется под Android) устанавливается в ensure_crypto_provider, регрессию держит тест a_crypto_provider_is_available_for_tls. Живой тест против шести боевых дата-центров Telegram проходит. Прочее: - open::that заменён на tauri-plugin-opener: у крейта open нет реализации под Android, а плагин работает на обеих платформах. - Библиотека переименована в tglock_lib: одинаковые имена lib и bin давали коллизию выходных файлов, которую cargo обещает сделать ошибкой. - Программный рендер WebView не применяется на Android — там GPU есть всегда, и переопределение только замедлило бы интерфейс. - .gitignore: строка /gen скрывала весь сгенерированный Android-проект. Теперь игнорируются только артефакты сборки, local.properties и keystore. Foreground service. Прокси — поток в процессе приложения, и без сервиса система выгрузит его через минуты после сворачивания. Сервис стартует из MainActivity, а не по команде из ядра: JNI-мост между Rust и Kotlin осознанно не делался, чтобы не добавлять слой, который нельзя проверить. Плата — уведомление висит, пока открыто приложение, даже при выключенной защите. НЕ ПРОВЕРЕНО: вся Android-часть в рантайме. Устройств не подключено, эмулятора и системных образов в SDK нет. APK собирается, но приложение никто ни разу не запускал: ни интерфейс, ни сервис, ни тип specialUse на Android 14+, ни запрос разрешения на уведомления. Поэтому Android-артефакт сознательно НЕ добавлен в release.yml — публиковать нечего, пока никто не запустил это на живом устройстве.
146 lines
11 KiB
Markdown
146 lines
11 KiB
Markdown
# 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.
|
||
|
||
## Учёт состояния
|
||
|
||
`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`.
|
||
|
||
Стек — **rustls** с корневыми сертификатами webpki, вшитыми в бинарь. Выбран не
|
||
из вкуса: `native-tls` на Linux и Android тянет OpenSSL, а `openssl-sys` не
|
||
кросскомпилируется под `aarch64-linux-android` без сборки OpenSSL вручную.
|
||
Побочные выгоды: на Linux исчезла зависимость от системного `libssl`, а образу
|
||
Docker больше не нужен даже `ca-certificates`.
|
||
|
||
Криптопровайдер rustls выбирается явно (`ring`). Без этого rustls 0.23
|
||
отказывается угадывать и **паникует на первом TLS-рукопожатии** — это не
|
||
ошибка компиляции и её не видят офлайновые тесты, потому что они ходят через
|
||
локальный маршрут без TLS. Провайдер устанавливается в `ensure_crypto_provider`
|
||
перед подключением, а тест `a_crypto_provider_is_available_for_tls` не даёт
|
||
регрессии вернуться.
|
||
|
||
Cloudflare Worker принимается только как пользовательская настройка. TGLock
|
||
не загружает и не скрывает публичные списки чужих доменов.
|
||
|
||
## Cloudflare Worker contract
|
||
|
||
Worker должен принимать WebSocket на:
|
||
|
||
```text
|
||
/apiws?dst=<telegram-ip>&dc=<dc-id>
|
||
```
|
||
|
||
и проксировать бинарные frames в TCP `<telegram-ip>: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-соединений будут добавлены после измерения,
|
||
что они не создают лишнюю нагрузку и не ухудшают стабильность.
|