Files
tglock/docs/ARCHITECTURE_V2.md
T
Никита Sonic 945e794eb3 fix(proxy): LAN-режим отклонял настоящие адреса Telegram (#42) (#43)
@alexsagaidak: с компьютера прокси работает, с телефона — нет. Адрес машины
вписан верно, порт верный, брандмауэр выключен, порт добавлен в исключения.

Причина нашлась в проверке «это Telegram?». Она сравнивала два первых октета:

    (149, 154) => Some(...)
    (91, 108)  => Some(...)

То есть «телеграмом» считались целиком четыре /16 — десятки тысяч чужих
адресов, — а IPv6 не распознавался вообще ни один. Telegram владеет
149.154.160.0/20, шестью /22 в 91.108, 91.105.192.0/23, 185.76.151.0/24 и пятью
блоками IPv6.

Ошибка в обе стороны, и на loopback она не видна. Там allow_direct включён, и
неопознанный адрес всё равно релеится напрямую — соединение просто работает.
На сетевом слушателе allow_direct выключен, и тот же адрес получает SOCKS5
0x02. Отсюда ровно то, что описал репортёр: на компьютере работает, с телефона
нет. Заодно чужие адреса внутри этих /16 уходили в MTProto-туннель и умирали
там.

Список сетей теперь опубликованный самим Telegram
(core.telegram.org/resources/cidr.txt), проверка по маске префикса, IPv4 и
IPv6, с тестами на края блоков и на соседей за границей.

Отдельно разрешены имена веб-инфраструктуры: telegram.org, t.me, telegram.me,
telesco.pe, cdn-telegram.org. Это не MTProto, а обычный HTTPS — клиент ходит
туда за конфигурацией, превью и файлами CDN, и на телефоне эти запросы идут
через тот же прокси. Заворачивать их в туннель нельзя, поэтому они идут прямым
релеем. Совпадение по границе метки, так что telegram.org.example.com —
посторонний домен. В ограниченном режиме имя разрешается заранее и адреса
внутри локальной сети отбрасываются: назначение выбирает чужое устройство, и
DNS-ответ не должен открывать доступ к 192.168.х этой машины.

И главное для разбора следующего такого случая: отказ перестал быть
молчаливым. Появился счётчик «Отклонено» в диагностике и в строке статуса CLI,
отклонённый адрес один раз называется в журнале, и отдельно отмечается первое
подключение с каждого сетевого адреса. Без этого «с телефона не работает»
неразличимо распадалось на два случая: телефон не дошёл до машины — и дошёл,
но попросил адрес, который мы не пускаем. Теперь первый виден как ноль
соединений и ноль отказов, второй — как соединения есть, отказы растут.

Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
2026-08-19 09:07:50 +03:00

13 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 недоступен в принципе: сервер без графического окружения, контейнер, машина без монитора. Для случаев, где 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 (https://core.telegram.org/resources/cidr.txt), он лежит в 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 и один раз называет адрес в журнале; повторы того же адреса склеиваются, чтобы не забить журнал одной строкой. Отдельно отмечается первое подключение с каждого сетевого адреса.

Без этого «с телефона не работает» неразличимо распадалось на два случая: телефон не дошёл до машины (сеть, брандмауэр, изоляция клиентов на роутере) — и дошёл, но попросил адрес, который мы не пропускаем. Первый виден как ноль соединений и ноль отказов, второй — как соединения есть, отказы растут.

Учёт состояния

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-ускорения. Начиная с 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-соединений будут добавлены после измерения, что они не создают лишнюю нагрузку и не ухудшают стабильность.