# 🕸️ WSS для Telegram: MTProto внутри WebSocket
![[telegram-wss-transport-header.webp]]
> [!info] О чём заметка
> Разбор транспорта, который в 2026 году появился сразу в нескольких инструментах для Telegram: обычный MTProto-поток кладут внутрь **WebSocket поверх TLS** (WSS) и отправляют на веб-эндпоинты Telegram вида `kws2.web.telegram.org/apiws` — те же, по которым работает браузерный Telegram Web. Здесь — что это такое, как устроено рукопожатие, что именно едет внутри кадров и чем четыре известные реализации отличаются друг от друга. Про то, почему у части пользователей не грузятся стикеры и почему звонки этот транспорт не переносит, — в парной заметке [[mtproxy/telegram-wss-limits|Ограничения WSS: что не работает и почему]].
> [!warning] Откуда взяты данные
> Всё ниже — разбор исходного кода четырёх проектов по состоянию на **6 августа 2026** (коммиты указаны в разделе о каждой реализации), а не официальная документация Telegram. Telegram не публиковал спецификацию своих веб-релеев: адреса, путь `/apiws` и правила выбора датацентра восстановлены по коду клиентов и прокси. Любая деталь может измениться на стороне Telegram без предупреждения — сверяйся с актуальным кодом, прежде чем строить на этом что-то серьёзное.
---
## TL;DR
1. **WSS-транспорт для Telegram** — это тот же обфусцированный MTProto, но упакованный в бинарные кадры WebSocket поверх настоящего TLS и отправленный на порт 443 доменов `kwsN.web.telegram.org` (`kwsN-1` — для медиа-трафика).
2. Задача, которую он решает, — **не DPI-фингерпринт, а блокировка по IP**: если провайдер режет диапазоны датацентров Telegram, соединение к веб-релею на 443 внешне неотличимо от «пользователь открыл web.telegram.org в браузере».
3. Внутри ничего не поменялось: остались **64-байтный obfuscated2-init и AES-256-CTR**, те же теги протокола `0xefefefef` / `0xdddddddd` / `0xeeeeeeee` и номер датацентра в байтах 60–61.
4. Реализации делятся на два класса: **мост рядом с клиентом** (`tg-ws-proxy` от Flowseal, модуль `telegram_proxy` в ZapretGUI — оба на Python, слушают локальный порт как SOCKS5/MTProxy) и **нативный транспорт внутри клиента** (форки ZaStoGram для Android и для Desktop, C++ прямо в сетевом слое).
5. Покрытие датацентров у всех разное и нигде не полное: Desktop-форк работает только с DC2/DC4, Android-форк объявляет DC1–DC5 (три зашитых адреса релеев), мосты по умолчанию настроены на DC2/DC4 и уводят остальное в запасные маршруты.
6. Звонки через WSS не идут ни в одной реализации — WebSocket живёт поверх TCP, а голос и видео у Telegram ходят по UDP; их трафик проходит мимо туннеля напрямую, и закрывается он пакетным обходом, а не прокси.
7. Два клиентских форка разошлись по приоритетам: Android аккуратнее в транспортном слое (ограничение очередей, строгая проверка кадров, предсказуемое медиа), Desktop гибче и разговорчивее (свой релей для любого датацентра, живой индикатор транспорта и пинга).
---
## Зачем понадобился ещё один транспорт
Обход блокировок Telegram распадается на две разные задачи, и их постоянно путают. Первая — когда провайдер видит **как** выглядит соединение: анализирует TLS-рукопожатие, сравнивает почерк клиента с известными, ловит характерный первый пакет. Против этого работают инструменты вроде [[Zapret2/Zapret2|zapret]], подменяющие поведение пакетов, и клиентские правки TLS-почерка (подробно — в [[mtproxy/ja4-sni-client-side|заметке про JA4 и SNI на стороне клиента]]).
Вторая задача — когда провайдеру **всё равно, как выглядит трафик**, потому что он режет сами адреса. Диапазоны датацентров Telegram (`149.154.160.0/20`, `91.105.192.0/23` и соседние) известны и компактны; заблокировать их целиком дешевле, чем разбирать протокол. В таком случае никакая фрагментация пакета не помогает: пакет просто не доезжает.
WSS-транспорт бьёт именно во вторую задачу. Идея простая: у Telegram, кроме «обычных» адресов датацентров, есть веб-инфраструктура, обслуживающая браузерную версию мессенджера, — она живёт на 443 порту, за нормальными TLS-сертификатами и доменами `web.telegram.org`. Блокировать её тем же топорным способом дороже: это тот же домен, что и сайт, которым пользуются миллионы людей. Если завернуть MTProto в WebSocket и отправить туда, то на уровне IP и SNI трафик выглядит как визит на сайт Telegram.
Проще говоря: не меняем «почерк» соединения, а меняем **адрес, куда стучимся** — на такой, который провайдеру неудобно резать целиком.
---
## Как выглядит соединение: пять слоёв
Готовое WSS-подключение — это матрёшка из пяти уровней, и путаница обычно начинается с того, что два из них шифруют одни и те же байты.
```
TCP :443 обычное TCP-соединение к IP релея
└─ TLS 1.2+ настоящий TLS, SNI = kws2.web.telegram.org
└─ HTTP/1.1 Upgrade GET /apiws → ответ 101 Switching Protocols
└─ WebSocket frames бинарные кадры (opcode 0x2), клиент маскирует их
└─ MTProto obfuscated2 64-байтный init + AES-256-CTR
└─ сам MTProto шифрование клиент↔Telegram на auth_key
```
Внешний слой — TLS — нужен для маскировки: наблюдателю виден HTTPS к домену Telegram и больше ничего. Внутренний слой — obfuscated2 — остался ровно тем же, что и в обычном TCP-подключении Telegram: он превращает MTProto-поток в равномерный шум без заголовков, чтобы DPI не опознал протокол по сигнатуре. То, что шум едет внутри TLS и внутри WebSocket, ему не мешает.
Самый нижний слой — собственно MTProto с ключом авторизации — не трогает никто. Это важно для понимания рисков: **мост между клиентом и Telegram видит транспортную обёртку, но не содержимое переписки**; переписка расшифровывается только на устройстве и на серверах Telegram.
---
## Рукопожатие по шагам
Все четыре реализации делают одно и то же, различаясь мелочами. Порядок такой.
**Шаг 1. TCP к IP релея.** Соединение открывается не по DNS-имени, а по зашитому в код адресу — чаще всего `149.154.167.220` (для DC2/DC4), в Android-форке к нему добавлены `149.154.174.100` (DC1/DC3) и `149.154.170.100` (DC5). Имя `kwsN.web.telegram.org` при этом всё равно передаётся — но только выше, в TLS и HTTP.
**Шаг 2. TLS.** SNI и проверка имени сертификата выставляются в доменное имя релея. Здесь реализации расходятся: клиентские форки проверяют цепочку сертификатов по-настоящему (`SSL_VERIFY_PEER` в Android, `QSslSocket::VerifyPeer` в Desktop), а `tg-ws-proxy` проверку отключает намеренно — для него TLS не граница доверия, а обёртка, потому что полезная нагрузка и так зашифрована между клиентом и Telegram.
**Шаг 3. HTTP Upgrade.** Отправляется обычный запрос апгрейда до WebSocket:
```http
GET /apiws HTTP/1.1
Host: kws2.web.telegram.org
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: <16 случайных байт в base64>
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: binary
Origin: https://web.telegram.org
User-Agent: Mozilla/5.0 ... Chrome/131.0.0.0 ...
```
Заголовки `Origin` и браузерный `User-Agent` — часть маскировки: клиентские форки притворяются вкладкой браузера. `tg-ws-proxy` в этом месте скромнее и `Origin` не шлёт вовсе.
**Шаг 4. Проверка ответа.** Сервер отвечает `101 Switching Protocols`. Оба клиентских форка честно считают `Sec-WebSocket-Accept` — SHA-1 от отправленного ключа и константы `258EAFA5-E914-47DA-95CA-C5AB0DC85B11` из RFC 6455 — и рвут соединение при несовпадении. `tg-ws-proxy` довольствуется кодом 101.
**Шаг 5. Обмен кадрами.** Дальше идут бинарные кадры (opcode `0x2`), исходящие — с обязательной 4-байтной маской, как требует RFC 6455 от клиента. Служебные `ping` получают ответ `pong`, `close` считается обрывом.
> [!note] Почему адрес и имя расходятся
> Соединение открывается на IP-адрес, а имя `kwsN.web.telegram.org` фигурирует только в SNI и в заголовке `Host`. Это не хитрость ради хитрости: реализации не хотят зависеть от DNS, который может не отвечать или быть подменён. Android-форк дополнительно умеет откатиться на резолв доменного имени, если жёстко зашитый IP не отвечает, и запоминает удачный вариант на 30 минут. Модуль в ZapretGUI идёт дальше и прописывает нужные имена прямо в `hosts` Windows — приём рабочий, но он же однажды сломал их собственную диагностику, которая после этого проверяла не DNS, а собственную запись в `hosts`.
---
## Что едет внутри кадров
Внутри WebSocket-кадров лежит обычный обфусцированный MTProto — тот же, что уходил бы в голый TCP. Первым отправляется **64-байтный init-пакет**: 8 байт пропускаются, следующие 48 дают ключ и IV для AES-256-CTR (в обратном порядке — ключ для встречного направления), байты 56–59 содержат тег протокола, байты 60–61 — номер датацентра со знаком, где минус означает медиа-подключение. Затем весь пакет шифруется сам собой, и наружу уходит структура, статистически неотличимая от случайных байт.
Теги протокола те же, что в обычном MTProxy: `0xefefefef` — abridged (минимальный заголовок длины), `0xeeeeeeee` — intermediate, `0xdddddddd` — padded intermediate с добавлением случайного мусора к каждому пакету. Подробный разбор режимов — в [[Zapret/mtproto/01-protocol|описании протокола MTProxy]].
При генерации init-пакета отбраковываются «плохие» случайные значения: если первые байты совпадут с сигнатурой TLS-рукопожатия (`0x16030102`), с текстом `GET `, `POST`, `HEAD` или с чужим тегом протокола, пакет генерируется заново. Иначе DPI опознал бы поток по случайному совпадению с известным заголовком. Эта проверка живёт в коде всех реализаций и в WSS-режиме тоже работает.
Отдельная деталь, которую все четыре проекта реализовали одинаково и осознанно: **один MTProto-пакет отправляется одним WebSocket-сообщением**. Соблазн отдать поток «как получится», кусками по мере готовности сокета, велик — но тогда границы кадров перестают совпадать с границами пакетов, и трафик перестаёт быть похожим на то, что делает браузерный Telegram Web. В Desktop-форке для этого пришлось завести отдельную очередь исходящих сообщений: раньше заголовок, тело и паддинг писались в общий поток, и нарезка зависела от того, когда сработает epoll.
Причём 64-байтный init отправляется **отдельным** сообщением, а первый MTProto-пакет — следующим. Тот же приём есть и в Android-форке, и в мостах.
---
## Откуда берётся номер датацентра
Датацентр не передаётся в URL и не задаётся параметром — он читается из уже упомянутых байт 60–61 расшифрованного init-пакета. Дальше номер превращается в имя релея по простому правилу:
| Что нужно | Домен | Пример |
|---|---|---|
| Обычное соединение с DC N | `kwsN.web.telegram.org` | `kws2.web.telegram.org` |
| Медиа-соединение (загрузка файлов) | `kwsN-1.web.telegram.org` | `kws4-1.web.telegram.org` |
| Тестовый бэкенд | путь `/apiws_test` | поддержан только в `tg-ws-proxy` |
Медийные подключения Telegram помечает отрицательным номером датацентра — именно этот знак реализации превращают в суффикс `-1`. Ошибка здесь стоит дорого: в обоих клиентских форках был баг, когда в WSS-режиме в init-пакет всё ещё писался маркер датацентра, как для MTProxy-секрета, и релей отвечал отказом `-444`, потому что имя хоста уже само определяет и датацентр, и класс трафика.
Список работающих релеев — не полный, и это главное практическое ограничение всей схемы: подтверждённо принимают апгрейд до WebSocket релеи DC2 и DC4, остальные в независимых проверках отвечали редиректом 302 либо принимали соединение и молчали. Android-форк тем не менее строит маршруты и для DC1, DC3, DC5 — как это соотносится с проверками, разобрано ниже, в разделе про его реализацию, а последствия для пользователя — в [[mtproxy/telegram-wss-limits|заметке про ограничения]].
---
## Две архитектуры: мост снаружи или транспорт внутри
Все известные реализации делятся на два лагеря, и разница между ними определяет почти всё остальное — от установки до того, что видно в логах.
| | **Мост рядом с клиентом** | **Нативный транспорт в клиенте** |
|---|---|---|
| Примеры | `tg-ws-proxy`, модуль `telegram_proxy` в ZapretGUI | ZaStoGram (Android), ZaStoGram Desktop |
| Как клиент его видит | обычный SOCKS5 или MTProxy на `127.0.0.1` | никак — это внутренний способ подключения |
| Что нужно от пользователя | поставить программу, добавить прокси в Telegram | поставить сам форк, включить тумблер |
| Клиент можно любой | да, включая официальный | нет, только этот форк |
| Криптография | поток расшифровывается мостом и шифруется заново | сквозная, без промежуточных ключей |
| Гибкость маршрутов | высокая: запасные пути, внешние SOCKS5, Cloudflare | низкая: только зашитые релеи |
Ключевое отличие — в третьей строке снизу. Мост не может просто переслать байты: клиент зашифровал их своим ключом, выведенным из секрета прокси, а Telegram такого секрета не знает. Поэтому мост расшифровывает транспортную обёртку и зашифровывает поток заново — уже как обычный клиент без прокси. Ещё раз: **речь только о транспортном слое**; MTProto-шифрование переписки на `auth_key` мост не трогает и расшифровать не может.
Нативный транспорт этой ступени не имеет вовсе: клиент сам открывает WebSocket и сам кладёт туда свой обфусцированный поток.
### Мост: tg-ws-proxy (Flowseal)
[Flowseal/tg-ws-proxy](https://github.com/Flowseal/tg-ws-proxy) — Python на asyncio, с собственной минимальной реализацией WebSocket-клиента (без сторонних библиотек) и приложением в системном трее для Windows, macOS и Linux. По умолчанию слушает `127.0.0.1:1443` как MTProto-прокси, то есть в Telegram его добавляют обычной ссылкой `tg://proxy?server=127.0.0.1&port=1443&secret=dd…`. В Docker-образе хост по умолчанию `0.0.0.0`, а `.deb`-пакет ставит systemd-юнит — тот же код разворачивают и на VPS как сетевой сервис.
Отличает проект развитая система запасных путей. Если прямой WebSocket не открылся, соединение уходит по цепочке: **Cloudflare Worker → домен за Cloudflare → прямой TCP на 443**. Первый вариант — бесплатный JS-воркер, который через `cloudflare:sockets` открывает TCP к нужному IP и мостит его в WebSocket; второй — собственный домен пользователя с A-записями `kws1`…`kws203`, направленными на адреса датацентров, в режиме Cloudflare `Flexible`. Список доменов по умолчанию хранится в исходниках в закодированном виде и обновляется раз в час; адрес `raw.githubusercontent.com` при этом закреплён за конкретным IP, чтобы обновление проходило и при испорченном DNS.
Ещё две особенности: пул из четырёх заранее открытых WebSocket-соединений на каждый датацентр (Telegram открывает подключения пачками, и прогретый пул экономит время на рукопожатиях) и запасной вариант с подменой SNI на `sprinthost.ru` — если прямое соединение с настоящим именем не проходит, попытка повторяется с чужим именем в TLS, тогда как заголовок `Host` остаётся честным.
Поддержан и вход по FakeTLS (`ee`-секрет) — с проверкой HMAC и допуском по времени ±120 секунд, а при провале проверки соединение прозрачно перебрасывается на настоящий сайт-прикрытие. Это защита от активного зондирования, и работает она только в консольном режиме: в трей-версии FakeTLS не выведен.
### Мост, встроенный в GUI: ZapretGUI
В [ZapretGUI](https://github.com/youtubediscord/zapret) есть отдельный раздел «Telegram Proxy» — модуль `src/telegram_proxy/` (по состоянию на коммит `a3056476`, ветка релизов 21.1.5.x). Подход к WSS взят у `tg-ws-proxy` — на это прямо указано в комментарии к коду, — но реализация своя: Python-модуль, работающий в том же процессе, что и GUI, без отдельного бинарника. Слушает `127.0.0.1:1353`, умеет два режима входа — SOCKS5 и MTProxy — и подставляет пользователю готовую ссылку `tg://socks?…` или `tg://proxy?…`, которую Telegram открывает по нажатию кнопки.
Ключевое архитектурное отличие от предыдущего проекта — **внешний SOCKS5 как штатный запасной маршрут**. Когда у датацентра нет своего рабочего релея (а это все, кроме DC2 и DC4), трафик уходит на внешние SOCKS5-серверы проекта, выбираемые пресетом по стране. Отсюда же взялась основная работа последних месяцев: логика переключения между серверами несколько раз переписывалась, потому что Telegram открывает соединения залпом, и короткая серия отказов ошибочно читалась как «сервер умер».
Модуль соседствует с основной функцией программы, но решает другую задачу: движок `winws2` работает с DPI на уровне пакетов, а `telegram_proxy` — с блокировкой по IP. Встроенная диагностика это прямо учитывает: она проверяет доступность релея, TCP+TLS до адресов всех датацентров, апгрейд до WebSocket для `kws1`…`kws5`, блокировку по SNI против блокировки по IP, живость локального порта — и заодно смотрит, запущен ли параллельно сам `winws2`.
### Нативный транспорт: ZaStoGram для Android
Форк официального Android-клиента ([youtubediscord/ZaStoGram](https://github.com/youtubediscord/ZaStoGram), база — Telegram 12.9.2, версия приложения 1.1.2, HEAD `ab53c8d0` от 6 августа 2026) несёт WSS прямо в нативном сетевом слое `tgnet`. Появились файлы `jni/tgnet/wss/WssSocket.cpp` (779 строк) и общий интерфейс транспорта `jni/tgnet/transport/TransportSocket.h`, которых в апстриме DrKLO нет вовсе. Реализация самодостаточная: собственная машина состояний `TcpConnecting → TlsHandshake → HttpWrite → HttpRead → Ready` поверх OpenSSL и неблокирующих сокетов, без Qt и без сторонних WebSocket-библиотек.
**Покрытие датацентров шире, чем у всех остальных реализаций**, — но это заявка, а не подтверждённый факт. Функция `OfficialRoute` строит маршрут для DC1–DC5, держа три зашитых адреса релеев:
| Датацентр | Зашитый адрес релея | Домен (обычный / медиа) |
|---|---|---|
| DC1, DC3 | `149.154.174.100` | `kws1` / `kws1-1`, `kws3` / `kws3-1` |
| DC2, DC4 | `149.154.167.220` | `kws2` / `kws2-1`, `kws4` / `kws4-1` |
| DC5 | `149.154.170.100` | `kws5` / `kws5-1` |
Важная деталь истории: первая версия этого кода поддерживала только DC2 и DC4 — ровно как Desktop-форк сегодня. Расширение до DC1–DC5 внесли в тот же день, 5 августа 2026, коммитом «Fix media downloads over WSS». Следов проверки живым соединением в репозитории нет: тест `Tools/check_wss_official_default.py` статически сверяет текст кода, а не факт успешного апгрейда к `kws1`/`kws3`/`kws5`. Иначе говоря, DC1/DC3/DC5 добавлены по аналогии с работающими DC2/DC4, и это стоит держать в голове, читая заявление о покрытии.
Тестовый бэкенд и CDN-датацентры отсекаются явной проверкой (`dcId < 1 || dcId > 5 || testBackend`). Тумблер «Use WSS transport» (в интерфейсе — Data and Storage → Proxy Settings, раздел «Telegram WSS транспорт») **на новой установке выключен**. В более ранней версии было автовключение при первом запуске, но его удалили при переписывании транспорта, так что сейчас WSS требует осознанного включения.
Совмещение с прокси запрещено полностью и в обе стороны: включение WSS гасит и обычный прокси, и «прокси для звонков», а выбор любой строки прокси гасит WSS. Проверка продублирована в семи местах кода, включая принудительное восстановление инварианта при каждой перерисовке списка прокси, — то есть это сознательная защита, а не единственная ветка условия.
Разбор входящих кадров у этой реализации строгий: отклоняются ненулевые RSV-биты, маскированные кадры от сервера (RFC 6455 запрещает серверу маскировать), контрольные кадры длиннее 125 байт и фрагментированные, continuation-кадр без начатого сообщения, любой неизвестный опкод — вплоть до текстовых кадров, потому что подпротокол объявлен как `binary`. Каждый отказ получает собственный строковый код: их 29 штук, от `wss_tls_verify_failed` до `wss_accept_mismatch`, и они попадают в отладочный лог.
Есть и то, чего нет у Desktop-версии, — **защита от переполнения очереди на отправку**. Лимитов два: 4 МБ на готовые WebSocket-кадры внутри сокета и ещё 4 МБ на MTProto-пакеты, ждущие своей очереди уровнем выше. При превышении соединение обрывается с кодом `ENOBUFS` — управляемый отказ вместо неограниченного роста памяти при аплоаде в медленную сеть.
Обратная сторона ручной реализации — цена отладки. Вечером 5 августа и в ночь на 6-е потребовалось шесть последовательных исправлений: границы WebSocket-сообщений (заголовок, тело и паддинг писались в общий поток, и нарезка зависела от того, когда сработает системный вызов), зависание TLS-рукопожатия из-за edge-triggered epoll (пришлось перевести именно WSS-сокет на level-triggered), маркер датацентра в init-пакете, маршрут аплоада на медийный релей вместо обычного (сервер отвечал `-404`), возврат таблицы адресов релеев и, наконец, нарушение контракта OpenSSL — буфер для повторного `SSL_write` дописывался новыми кадрами и переезжал в памяти, что ломало отправку под нагрузкой.
> [!warning] README форка расходится с кодом
> В README ZaStoGram сказано, что «route, DNS, TLS SNI и hostname verification используют один официальный hostname; ручной таблицы relay IP нет». На момент разбора (6 августа 2026) это неверно: таблицу зашитых адресов убрали одним коммитом и вернули следующим тем же вечером, а документацию не обновили. Проверять такие утверждения стоит по коду `WssSocket.cpp`, а не по описанию.
История фичи поучительна сама по себе. Сначала в форк добавили локальный WSS-шлюз с произвольными host/port/path, режимом «SOCKS внутри WebSocket» и мостом на `127.0.0.1` для мини-приложений. Через неделю вырезали SOCKS-внутри-WSS, а ещё через пять недель заменили всю конструкцию тонким транспортом к официальным релеям. В README это зафиксировано как намеренное решение: ни собственного шлюза, ни SOCKS-апстрима, ни локального релея в новой версии нет. Пользователи, у которых была настроена кастомная конфигурация, теряют её при обновлении — миграция сохраняет только сам факт «WSS был включён».
### Нативный транспорт: ZaStoGram Desktop
Форк Telegram Desktop ([youtubediscord/ZaStoGram_desktop](https://github.com/youtubediscord/ZaStoGram_desktop), версия 7.0.8 от 3 августа 2026, HEAD `729dfd39`) решает ту же задачу, но опирается на Qt. WSS живёт в `mtproto/proxy/wss/socket.cpp` поверх `QSslSocket`, а выбор сокета вынесен в фабрику: в зависимости от настроек и секрета создаётся `WssSocket`, `TlsSocket` (FakeTLS для MTProxy) или обычный `TcpSocket`. Протокольный слой при этом не знает, какой транспорт под ним, — поэтому обфускация для всех трёх одинаковая.
Первое отличие от Android-версии — **транспорт по умолчанию Wss**: без единой настройки соединения к DC2 и DC4 идут через веб-релеи, к остальным датацентрам — обычным TCP. Покрытие уже намеренно ограничено двумя датацентрами, с комментарием в коде, что публичные веб-сокеты существуют только для DC2/DC4.
Второе — **экспертный режим с произвольным релеем**: можно указать свои host, port, path и SNI-домен, и такой маршрут работает для любого датацентра, обходя ограничение DC2/DC4. Это единственный способ во всей четвёрке реализаций подставить собственный сервер вместо инфраструктуры Telegram. Рядом с полями висит предупреждение «No certificate check is done — use only a relay you trust», и оно устарело: проверку сертификата вернули 4 июля 2026, текст в интерфейсе поправить забыли. Ошибка безобидная — пугает сильнее, чем есть риска, — но полагаться на неё как на описание поведения нельзя.
Третье — **живая индикация**. В настройках строка «Connection type» показывает реальный транспорт и задержку: `Default (WSS used, ping: 87 ms)`. Android такого не умеет вовсе: там подпись «Telegram WSS transport: Official» отражает лишь состояние тумблера, а фактическое состояние сокета живёт в C++ и наружу не отдаётся. Кроме того, Desktop показывает осмысленное предупреждение, когда датацентр вне покрытия: «WSS unavailable for this DC. Add a proxy.»
Совмещение с прокси разрешено выборочно: поверх удалённого SOCKS5 — можно, поверх MTProxy — нет (транспорт откатывается на TCP, потому что MTProxy сам является TCP-транспортом), поверх локального SOCKS5 на `127.0.0.1` или на порту `1353` — тоже нет. Последнее исключение сделано ровно под мосты из предыдущих разделов: заворачивать WebSocket в локальный инструмент, который сам уже строит WebSocket, бессмысленно.
Цена опоры на Qt видна в двух местах. Хорошая сторона: целый класс низкоуровневых ошибок исключён по построению — буферами записи и ожиданием готовности сокета занимается давно отлаженный код Qt, поэтому ни бага с переездом буфера, ни потери события записи здесь возникнуть не могло. Плохая: приложение не управляет очередью на отправку и потому не может её ограничить — предохранителя на переполнение в WSS-транспорте Desktop нет вообще. Разбор входящих кадров тоже мягче: RSV-биты, маска от сервера, состояние фрагментации и длина контрольных кадров не проверяются, а пять текстовых сообщений об ошибках сводятся к одному машинному коду.
---
## Android или Desktop: где сделано лучше
Единого победителя нет — форки разошлись по приоритетам, и выбор зависит от того, что важнее в конкретном сценарии.
| Ось сравнения | Android (ZaStoGram) | Desktop (ZaStoGram Desktop) |
|---|---|---|
| Покрытие датацентров | DC1–DC5 (DC1/3/5 добавлены по аналогии, без следов проверки в репозитории) | DC2/DC4, плюс любой датацентр через свой релей |
| Дефолт | выключен, включается вручную | включён из коробки |
| Свой релей | нет — только инфраструктура Telegram | есть: host, port, path, SNI |
| Ограничение очереди на отправку | два бюджета по 4 МБ, обрыв с `ENOBUFS` | отсутствует |
| Строгость разбора кадров | RSV, маска сервера, фрагментация, контрольные кадры | только максимальный размер и close |
| Диагностика отказов | 29 отдельных кодов | 5 сообщений в одном коде ошибки |
| Минимальная версия TLS | задана явно: 1.2 | наследуется от Qt |
| Индикация для пользователя | статичная метка, живого статуса нет | транспорт и пинг в реальном времени |
| Автоматический откат на TCP | нет: попытки повторяются, пока тумблер включён | нет для прямого WSS; поверх SOCKS5 — блокировка на 30 минут после обрыва |
| CDN-загрузки | отключены флагом `cdn_supported=false` | разрешены, но идут мимо туннеля прямым TCP |
| Сочетание с прокси | запрещено полностью | разрешено поверх удалённого SOCKS5 |
**По качеству транспортного слоя аккуратнее сделан Android.** Это единственная из двух реализаций, которая считает свои буферы и отказывает управляемо, строго валидирует RFC 6455 и даёт содержательную диагностику. Заплатили за это ручной работой с epoll и OpenSSL — отсюда та самая ночь из шести исправлений, каждое из которых чинило реальный, воспроизводимый баг.
**По предсказуемости медиа тоже впереди Android.** Флаг `cdn_supported=false` убирает CDN из уравнения целиком: файлы, стикеры и реакции всегда идут через покрытые релеями датацентры. В Desktop CDN-редиректы разрешены безусловно, и такое соединение выходит из-под туннеля — обычным TCP к адресу CDN. В сети, где режут именно прямые адреса Telegram (то есть ровно там, где WSS и включают), это выглядит как зависшие загрузки при работающей переписке. Пользователь хотя бы видит подсказку про недоступный датацентр, но автоматического решения нет.
**По гибкости и обратной связи впереди Desktop.** Свой релей — это выход, когда зашитые адреса заблокированы целиком: у Android в такой ситуации остаётся только выключить WSS. Живой индикатор транспорта с пингом отвечает на главный вопрос пользователя — работает ли обход прямо сейчас, — на который Android не отвечает никак.
**Что одинаково слабо у обоих.** Ни один не мимикрирует под TLS-почерк браузера в WSS-соединении: рукопожатие уходит с отпечатком системной криптобиблиотеки, а не Chrome, хотя в обоих проектах такая инфраструктура есть — но подключена она только к их же ветке MTProxy FakeTLS (что это и почему важно — в [[mtproxy/ja4-sni-client-side|разборе JA4 и SNI]]). Заголовок `User-Agent` в обоих зашит с Chrome 131 — версией конца 2024 года. Ни один не откатывается на обычный TCP сам: если релей молчит, клиент будет пытаться, пока пользователь не вмешается вручную.
Практический вывод: на Android имеет смысл включать WSS осознанно и оставлять включённым, пока работает медиа; на Desktop — знать, что он уже включён по умолчанию, и при зависших загрузках проверять, не ушёл ли датацентр за пределы DC2/DC4.
---
## Чем это отличается от MTProxy и VPN
Легко решить, что WSS — это «MTProxy, но лучше», и ошибиться. Разница в том, где стоит точка выхода.
Классический [[Zapret/mtproto/00-overview|MTProxy]] — это **ваш сервер** (или чужой публичный), к которому клиент подключается по произвольному порту и который дальше сам идёт к Telegram. Он позволяет ротировать адреса, ставить FakeTLS, делить порт 443 с настоящим сайтом — но требует VPS, а его адрес рано или поздно попадает в списки блокировок.
WSS-транспорт не требует ничего своего: клиент идёт напрямую на инфраструктуру Telegram, просто через её веб-подъезд. Отсюда и плюс, и минус. Плюс — не надо ничего поднимать и платить, а домен назначения принадлежит Telegram. Минус — вы не управляете точкой выхода: если конкретный релей начнёт отвечать редиректом или замолчит, сделать с этим нечего, кроме как переключиться на запасной маршрут.
От VPN обе схемы отличаются одинаково: они уводят только трафик Telegram и не трогают остальную систему, не держат туннель и не сажают батарею. Звонки не переносит ни одна из них — голос и видео ходят по UDP мимо любого TCP-прокси, — но это вопрос не «работают или нет», а «чем их закрывать»: трафик звонка идёт напрямую, и его задача решается пакетным обходом ([[Zapret2/Zapret2|zapret]] перехватывает в том числе UDP и STUN) или VPN.
| | MTProxy на своём VPS | WSS-транспорт | VPN |
|---|---|---|---|
| Нужен свой сервер | да | нет | обычно да |
| Куда идёт трафик | ваш IP | инфраструктура Telegram | ваш IP |
| Управление точкой выхода | полное | никакого | полное |
| Работает вне Telegram | нет | нет | да |
| Переносит звонки | нет, идут напрямую | нет, идут напрямую | да |
| Что блокируют в первую очередь | IP сервера | сами релеи и подходы к ним | IP сервера, протокол |
Отсюда практическая связка: WSS или MTProxy отвечают за переписку и медиа, пакетный обход — за звонки и за те датацентры, до которых веб-релеи не дотягиваются. Они не конкуренты и работают параллельно.
---
## Что дальше
Схема выглядит изящно, но у неё большой список оговорок: работают не все датацентры, стикеры и реакции ходят через CDN мимо релеев, звонки не проксируются вовсе, а бесплатные запасные пути через Cloudflare упираются в лимиты. Всё это разобрано отдельно — с симптомами, причинами и тем, что показывает диагностика: [[mtproxy/telegram-wss-limits|Ограничения WSS: что не работает и почему]].
---
## 📚 См. также
- [[mtproxy/telegram-wss-limits|Ограничения WSS: что не работает и почему]] — парная заметка: датацентры, медиа, звонки, Cloudflare, типичные диагнозы
- [[Zapret/mtproto/00-overview|MTProto Proxy — полный гайд]] — обзорный раздел про классический MTProxy: протокол, реализации, цензура
- [[Zapret/mtproto/01-protocol|Протокол MTProxy: 3 режима и FakeTLS]] — что такое abridged/intermediate/padded intermediate и откуда берутся теги `0xef`/`0xdd`/`0xee`
- [[mtproxy/ja4-sni-client-side|Кто может менять JA4 и SNI]] — почему обход по «почерку» соединения делается на клиенте, а не на релее
- [[mtproxy/faketls-relay-diagnosis|Релей или клиент: диагностика MTProxy]] — как понять, кто виноват, когда рабочий ключ не подключается
- [[mtproxy/tsrman-tg-android-faketls|tsrman/tg — Telegram для Android со сменой JA4]] — другой подход к тому же: не смена транспорта, а смена TLS-почерка
- 🔗 [Flowseal/tg-ws-proxy](https://github.com/Flowseal/tg-ws-proxy) — исходники моста на Python, документация по Cloudflare Worker и FakeTLS
- 🔗 [youtubediscord/ZaStoGram](https://github.com/youtubediscord/ZaStoGram) — исходники Android-форка: WSS-транспорт в `TMessagesProj/jni/tgnet/wss/`, готовые APK — в [релизах](https://github.com/youtubediscord/ZaStoGram/releases)
- 🔗 [youtubediscord/ZaStoGram_desktop](https://github.com/youtubediscord/ZaStoGram_desktop) — исходники Desktop-форка: WSS-транспорт в `Telegram/SourceFiles/mtproto/proxy/wss/`
- 🔗 [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455) — спецификация WebSocket: рукопожатие, маскирование, формат кадров
---
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны на GitHub: [исходник этой заметки](https://github.com/youtubediscord/todo/blob/main/mtproxy/telegram-wss-transport.md) · [весь репозиторий](https://github.com/youtubediscord/todo/tree/main).