todo/mtproxy/telegram-wss-transport.md
loop-uh 0505fa6b4a
Some checks failed
Published content check / validate (push) Has been cancelled
Заметки о вредоносных сборках вынесены в раздел virus/
В корне создан раздел virus/: каталог вирусных сборок, разборы Bypass
Ultimate, фейкового Flowseal со стилером, зачистки GitHub, приманки Fixit
и заметка про Cactuz. Вместе с ними перенесены 58 иллюстраций в
virus/attachments — все они использовались только этими заметками.

Обновлены Forgejo-футеры перенесённых заметок и ссылки с явным путём
в mtproxy/telegram-wss-*.md и banks-malware-scan-2027.md. Ссылки по имени
файла продолжают работать без правок.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 14:56:51 +03:00

83 KiB
Raw Permalink Blame History

date tags aliases link
2026-08-06
mtproto
telegram
wss
websocket
обход-блокировок
zapret
tdesktop
android
WSS прокси для Telegram
Telegram через WebSocket
tg-ws-proxy что это
Как работает WSS в Telegram
kws2.web.telegram.org и apiws
WSS транспорт Zastogram
Что лучше ZaStoGram Android или Desktop
Почему WSS работает только для DC2 и DC4
https://github.com/Flowseal/tg-ws-proxy

🕸️ WSS для Telegram: MTProto внутри WebSocket

!

[!info] О чём заметка Разбор транспорта, который в 2026 году появился сразу в нескольких инструментах для Telegram: обычный MTProto-поток кладут внутрь WebSocket поверх TLS (WSS) и отправляют на веб-эндпоинты Telegram вида kws2.web.telegram.org/apiws — те же, по которым работает браузерный Telegram Web. Здесь — что это такое, как устроено рукопожатие, что именно едет внутри кадров и чем четыре известные реализации отличаются друг от друга. Про то, почему у части пользователей не грузятся стикеры и почему звонки этот транспорт не переносит, — в парной заметке mtproxy/telegram-wss-limits.

[!warning] Откуда взяты данные Всё ниже — разбор исходного кода четырёх проектов по состоянию на 6 августа 2026 (коммиты указаны в разделе о каждой реализации), а не официальная документация Telegram. Telegram не публиковал спецификацию своих веб-релеев: адреса, путь /apiws и правила выбора датацентра восстановлены по коду клиентов и прокси. Любая деталь может измениться на стороне Telegram без предупреждения — сверяйся с актуальным кодом, прежде чем строить на этом что-то серьёзное.

[!note] Обновление 89 августа 2026: измерения вместо чтения кода Разделы #Правило первого кадра, #Мифа про «только DC2 и DC4» не существует и #Рукопожатие: место, где всё ломается тихо опираются на живые соединения с релеями, а не на исходники. Итог:

  • длина первого кадра важна: меньше 64 байт — соединение молча умирает (порог измерен с точностью до байта);
  • склейка пакетов запрещена: релей разбирает только первый MTProto-пакет кадра, остальные выбрасывает без ошибки. Разрезать один пакет на несколько кадров при этом можно;
  • «веб-релеи существуют только для DC2 и DC4» неверно — работают все DC1DC5, а редирект 302 возникает при обращении к чужому ingress-адресу.

Промежуточный вывод от 8 августа, будто границы пакетов серверу безразличны, был ошибочным: он опирался на проверки, где в кадре был ровно один пакет, и потеря второго не проявлялась. Полное рукопожатие 9 августа это опровергло.

Отдельно добавлен раздел #Релей может быть закрыт: главное практическое ограничение — о том, что чаще всего мешает не протокол, а недоступность самого релея из конкретной сети, и почему ping этого не показывает.


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 и номер датацентра в байтах 6061.
  4. Требований к нарезке ровно два, и оба нарушаются молча: первый кадр после апгрейда должен содержать не меньше 64 байт (весь init), и в одном кадре не должно быть больше одного MTProto-пакета — остальные релей выбрасывает. Разрезать пакет на несколько кадров при этом можно свободно. См. #Правило первого кадра.
  5. Реализации делятся на два класса: мост рядом с клиентом (tg-ws-proxy от Flowseal, модуль telegram_proxy в ZapretGUI — оба на Python, слушают локальный порт как SOCKS5/MTProxy) и нативный транспорт внутри клиента (форки ZaStoGram для Android и для Desktop, C++ прямо в сетевом слое).
  6. Веб-релеи есть у всех датацентров DC1DC5, а не только у DC2/DC4 — это проверено живыми соединениями 8 августа 2026. Распространённое «работают только DC2 и DC4» родилось из ошибки метода: у каждого датацентра свой адрес ingress, и если стучаться на адрес DC2 с именем kws5, приходит редирект 302 — сервер так и говорит, что вы пришли не туда. Подробно — в #Мифа про «только DC2 и DC4» не существует.
  7. Чаще всего мешает не протокол, а доступность релея. В измеренной сети открыт был ровно один ingress из четырёх проверенных путей: релей DC2 работал, релей DC1 не отвечал ни по IPv4, ни по IPv6, и прямые адреса DC1 тоже были закрыты. Пользователь при этом видит «фото грузятся, эмодзи нет». ping во всех случаях проходил и ничего не доказывал — см. #Релей может быть закрыт: главное практическое ограничение.
  8. Звонки через WSS не идут ни в одной реализации — WebSocket живёт поверх TCP, а голос и видео у Telegram ходят по UDP; их трафик проходит мимо туннеля напрямую, и закрывается он пакетным обходом, а не прокси.
  9. Два клиентских форка по протоколу больше не расходятся: оба соблюдают правила фрейминга, покрывают DC1DC5 и откатываются на прямое соединение при закрытом релее. Различия остались вокруг транспорта: Android строже валидирует кадры и ограничивает очереди, Desktop даёт свой релей и живой индикатор с пингом.

Зачем понадобился ещё один транспорт

Обход блокировок Telegram распадается на две разные задачи, и их постоянно путают. Первая — когда провайдер видит как выглядит соединение: анализирует TLS-рукопожатие, сравнивает почерк клиента с известными, ловит характерный первый пакет. Против этого работают инструменты вроде Zapret2/Zapret2, подменяющие поведение пакетов, и клиентские правки TLS-почерка (подробно — в mtproxy/ja4-sni-client-side).

Вторая задача — когда провайдеру всё равно, как выглядит трафик, потому что он режет сами адреса. Диапазоны датацентров 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:

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 (в обратном порядке — ключ для встречного направления), байты 5659 содержат тег протокола, байты 6061 — номер датацентра со знаком, где минус означает медиа-подключение. Затем весь пакет шифруется сам собой, и наружу уходит структура, статистически неотличимая от случайных байт.

Теги протокола те же, что в обычном MTProxy: 0xefefefef — abridged (минимальный заголовок длины), 0xeeeeeeee — intermediate, 0xdddddddd — padded intermediate с добавлением случайного мусора к каждому пакету. Подробный разбор режимов — в Zapret/mtproto/01-protocol.

При генерации init-пакета отбраковываются «плохие» случайные значения: если первые байты совпадут с сигнатурой TLS-рукопожатия (0x16030102), с текстом GET , POST, HEAD или с чужим тегом протокола, пакет генерируется заново. Иначе DPI опознал бы поток по случайному совпадению с известным заголовком. Эта проверка живёт в коде всех реализаций и в WSS-режиме тоже работает.

Все четыре проекта отправляют один MTProto-пакет одним WebSocket-сообщением, а 64-байтный init — отдельным сообщением перед ним. Объясняли это обычно похожестью на браузерный Telegram Web, и объяснение было неверным, но само правило — верным: релей действительно разбирает лишь первый пакет кадра. Подробности и цифры — в следующем разделе.


Правило первого кадра

[!tip] Два правила, и оба нарушаются молча Первое: первый бинарный WebSocket-кадр после апгрейда обязан содержать не менее 64 байт — весь obfuscated2-заголовок. Второе: в одном кадре должно ехать не больше одного MTProto-пакета. Релей разбирает только первый пакет кадра и выбрасывает всё, что идёт следом, — без ошибки и без закрытия соединения.

Разрезать пакет на несколько кадров при этом можно как угодно, хоть по байту. Ограничение только на склейку.

Порог измерен с точностью до байта на kws2.web.telegram.org (три независимых серии, суммарно около 350 попыток, 8 августа 2026; сетевые сбои отделены от протокольного вердикта повторами, иначе результат превращается в шум):

Как нарезан исходящий поток Доля успеха
[init 64][пакет] — «канонический» способ 100%
[init 64 + пакет] одним кадром 100%
[init 64][полпакета][полпакета] 100%
[init 64][дальше по 1 байту в кадре] 100%
[init 65 = 64 + первый байт пакета][остаток] 100%
[init 63][недостающий байт][пакет] 0%
[init 30]…, [init 40]…, весь поток кусками по 78 байт 0%

Точка перелома — ровно между 63 и 64 байтами: 63 → 0 из 10 успешных, 64 → 8 из 8.

Второе правило измерено отдельно, полным рукопожатием (9 августа 2026). Клиент перед вторым шагом отправляет подтверждение предыдущего сообщения, то есть подряд идут два пакета — msgs_ack и req_DH_params:

Как отправлены два пакета Ответ сервера
двумя кадрами server_DH_params_ok, 652 байта
склеены в один кадр тишина

Воспроизведено на kws2-1 и kws4-1 одинаково. Первый пакет кадра (msgs_ack) сервер принимает, второй просто не существует для него.

Три следствия, важных для того, кто пишет свою реализацию:

Правило действует на уровне отдельного кадра, а не сообщения и не TCP-записи. Отправить все кадры одной записью в сокет не помогает. Настоящая WebSocket-фрагментация (FIN=0 плюс continuation-кадры, то есть одно логическое сообщение) — тоже не помогает. Релей достаёт init из полезной нагрузки первого кадра и больше к этому вопросу не возвращается: досылка недостающего байта следующим кадром соединение не спасает.

Отказ молчаливый — и это худшая часть. Если первый кадр имеет длину 4863 байта, сервер не отвечает ничего: соединение просто висит до таймаута. Со стороны клиента это неотличимо от блокировки провайдером или потери пакетов, поэтому баг легко списать на сеть и искать не там. Только при первом кадре в 32 байта и меньше приходит явный Close с текстом 404 — релей не смог выбрать датацентр.

Практический вывод: «отдавать поток как получится» не работает. Кадр — это единица разбора на стороне релея, а не просто способ нарезки. Транспорт волен резать пакет на сколько угодно кадров, но обязан выпускать кадр ровно на границе пакета и никогда не класть в один кадр два пакета. Плюс отдельная гарантия для самой первой записи: не меньше 64 байт.

Обе ошибки не диагностируются по поведению сети: соединение живо, TLS в порядке, кадры уходят — просто ответа нет. Ровно поэтому их легко принять за блокировку у провайдера.


Рукопожатие: место, где всё ломается тихо

Отдельно стоит разобрать создание ключа авторизации, потому что именно здесь наблюдается отказ, который по логам легко принять за проблему сети.

Ключ создаётся обычным MTProto-рукопожатием: клиент шлёт req_pq_multi (около 100 байт вместе с init), получает resPQ, затем отправляет req_DH_paramsа это уже около 340 байт, потому что внутри лежит 256-байтовый блок, зашифрованный RSA. Дальше сервер отвечает server_DH_params_ok, и стороны договариваются о ключе.

Наблюдение (Android-форк, версия 1.1.15, 9 августа 2026): для датацентра, ключа к которому у клиента ещё нет, рукопожатие по WSS не проходит дальше первого шага. В сетевом логе это выглядит так:

upgrade_ok domain=kws4-1.web.telegram.org
frame_queued                        <- ушёл req_pq
read                                <- пришёл resPQ, 100 байт
frame_queued                        <- ушёл req_DH_params
wss_disconnect reason=2 phase=post_handshake_no_appdata

Шесть попыток подряд с нарастающей паузой — и ни одного ответа на второй шаг. Ключ не создаётся, а все запросы к этому датацентру остаются в очереди навсегда.

Что при этом проверено и отброшено: размер второго кадра ни при чём. Независимый клиент, доведённый до второго шага, получил от релея ответ на кадр в 341 байт — и от kws4-1, и от kws2-1. То есть релей такие кадры передаёт, и сервер на них отвечает.

Причина установлена: перед вторым шагом клиент отправляет msgs_ack, и транспорт, склеивающий очередь в один кадр, отправлял подтверждение и req_DH_params вместе. Релей обработал только первое, второе выбросил — отсюда тишина. Достаточно выпускать кадр на границе пакета, и рукопожатие проходит. Медийный релей kwsN-1 здесь ни при чём: он обслуживает создание ключей наравне с обычным.

[!warning] Как распознать это в логах Три признака, которые встречаются вместе:

  • wss_disconnect ... phase=post_handshake_no_appdata — соединение поднялось, апгрейд прошёл, приложение молчит;
  • handshake: begin для одного и того же датацентра, повторяющийся с нарастающей паузой;
  • шквал строк «нет ключа для датацентра» — в разобранном случае 98 926 записей за 61 секунду.

Последний признак опаснее, чем кажется: такой поток вытесняет из файла всю остальную диагностику, и причину становится физически не по чему искать. Прежде чем разбирать сетевую проблему, стоит убедиться, что лог не забит одной повторяющейся строкой.

Практическое следствие, пока дефект не исправлен: WSS работает для датацентров, ключи к которым уже есть, и не поднимает ключ для нового. Аккаунт продолжает работать, переписка идёт, а медиа из «чужих» датацентров не грузится вовсе — при том что скорость и связь выглядят нормальными. Это и есть типичная жалоба «файлы качаются, а чужие картинки нет».


Релей может быть закрыт: главное практическое ограничение

Все предыдущие разделы описывают, как транспорт устроен и что требует сервер. Но самое частое препятствие лежит ниже протокола: релей конкретного датацентра может быть просто недоступен из вашей сети, и тогда никакие правила фрейминга не помогут.

Замеры 9 августа 2026 в одной российской сети (провайдер с DPI), порт 443:

Куда Что это ICMP (ping) TCP 443
149.154.167.220 релей DC2 и DC4 отвечает открыт
149.154.174.100 релей DC1 и DC3 отвечает закрыт
2001:b28:f23d:8005:7:0:109:338 тот же релей по IPv6 отвечает закрыт
149.154.175.50 прямой адрес DC1, мимо релея отвечает (178 мс) закрыт

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

[!warning] ping ничего не доказывает Во всех четырёх строках ICMP проходит, а TCP на 443 — только в одной. DPI режет соединения по порту, оставляя пинг живым, поэтому «сервер пингуется, значит доступен» — неверный вывод, на котором легко потерять часы.

Проверять надо именно TCP. В Windows:

tnc 149.154.174.100 -Port 443

и смотреть строку TcpTestSucceeded. Обратите внимание на синтаксис: адрес идёт без порта, порт задаётся отдельным параметром. Написание вида Test-NetConnection - 1.2.3.4:443 заставляет PowerShell искать хост с таким именем и висеть до таймаута — легко принять это за блокировку.

Почему запасного пути нет. У каждого датацентра ровно один ingress-адрес IPv4, и подставить чужой нельзя: адрес DC2 с именем kws1 отвечает редиректом 302 (см. #Мифа про «только DC2 и DC4» не существует). Так что если этот единственный адрес закрыт, WSS для датацентра мёртв.

IPv6 помогает не всем. AAAA-записи есть у всех релеев, причём у DC1 и DC5 их по два против одного IPv4 — то есть шестой протокол даёт и обход, и резервирование. Но в измеренной сети IPv6 был заблокирован целиком, включая релей DC2, который по IPv4 прекрасно работал. Отсюда важное правило реализации: нельзя предпочитать IPv6 вслепую — наличие у устройства глобального IPv6-адреса не означает, что маршрут до релея существует, и слепое предпочтение заменяет рабочий путь заведомо мёртвым. IPv6 должен быть дополнительным кандидатом, а не заменой.

Что делать в клиенте. Не оставлять датацентр без содержимого навсегда. Считайте неудачи, не дошедшие даже до установленного TCP, отдельно по датацентру: несколько подряд означают, что релей недоступен, и для этого датацентра нужно временно вернуться к обычному соединению, оставив WSS там, где он работает. Иначе транспорт работает по принципу «всё или ничего», и половина медиа не грузится при внешне исправном клиенте.

Когда WSS вообще не тот инструмент. Он придуман против блокировки по IP: адреса датацентров режут, а web.telegram.org трогать дороже. Если же в конкретной сети закрыты и релеи, и прямые адреса, задача другая — нужен туннель через доступный сервер (MTProxy или VPN), который проксирует все датацентры одинаково. Проверять это стоит до того, как искать баги в транспорте.


Как написать WSS-транспорт правильно

Сводка того, что следует из измерений, — в виде готового чек-листа. Оба форка ZaStoGram приведены к этому виду 8 августа 2026 (Android — версия 1.1.13, Desktop — коммит 7f3ceffb), и расхождений по протокольной части между ними больше нет.

1. Первый кадр — не короче 64 байт. Это единственное требование к нарезке, и нарушается оно незаметно. Не полагайтесь на то, что «так получается само»: заведите в транспорте буфер, который придерживает начальные байты, пока их меньше заголовка. В Android-форке это WssSocket::write, в Desktop — склейка prefix с первым пакетом в write(prefix, buffer).

2. Один кадр — не больше одного MTProto-пакета. Держать байтовый поток внутри можно и удобно, но выпускать кадр обязательно на границе пакета: всё, что попадёт в кадр вторым, сервер не увидит. Резать один пакет на несколько кадров при этом разрешено.

3. Адрес выбирайте по датацентру, а не один на всех. Ingress-адреса не взаимозаменяемы. Минимальная верная таблица: DC1 и DC3 → 149.154.174.100, DC2 и DC4 → 149.154.167.220, DC5 → 149.154.170.100. Ещё надёжнее — резолвить kwsN.web.telegram.org и не хардкодить ничего; хардкод имеет смысл только как обход подмены DNS, и тогда обязательно нужен откат на имя.

4. Host и TLS SNI — всегда имя релея, даже когда подключаетесь по IP. Имя определяет и датацентр, и класс трафика; медиа — это суффикс -1 в имени, а не отдельный адрес.

5. Не пишите номер датацентра в init-пакет. В байтах 6061 он нужен MTProxy-секретам; при WSS имя хоста уже всё сказало, а маркер приводит к отказу. Обе реализации на этом обожглись.

6. Держите откат на имя хоста и запоминайте, что сработало. Заблокированный или устаревший адрес не должен убивать транспорт: попробуйте имя, а результат запомните на десятки минут, иначе каждое новое соединение будет заново упираться в мёртвый адрес.

7. Различайте отказ сети и отказ протокола. Одиночный таймаут TCP к диапазонам Telegram ничего не доказывает — нужны повторы. А молчание релея после успешного апгрейда почти наверняка означает нарушение пункта 1, а не проблемы со связью.

8. Проверяйте создание ключа отдельно от передачи данных. Транспорт, прекрасно работающий на датацентре с готовым ключом, может не проходить рукопожатие на новом — см. #Рукопожатие: место, где всё ломается тихо. Проверка «переписка работает» этот случай не ловит, потому что переписка идёт через датацентр аккаунта, а медиа живёт в других.

9. Не оставляйте датацентр без содержимого, если его релей закрыт. Считайте отдельно по датацентру неудачи, не дошедшие даже до установленного TCP: несколько подряд означают недоступный релей, а не сломанный протокол. Для такого датацентра временно возвращайтесь к обычному соединению, оставив WSS там, где он работает. Без этого транспорт получается «всё или ничего», и половина медиа не грузится при внешне исправном клиенте.

10. Не предпочитайте IPv6 вслепую. AAAA-записи у релеев есть, и адресов там больше, чем у IPv4, — но глобальный IPv6-адрес у устройства не гарантирует маршрут до релея. В измеренной сети IPv6 был закрыт целиком, и предпочтение шестого протокола заменило единственный рабочий путь мёртвым. IPv6 — дополнительный кандидат, а не замена.

11. Не давайте одному состоянию заливать лог. Очередь запросов перебирается на каждом проходе цикла событий, и любая строка внутри этого перебора превращается в тысячи записей в секунду. Пишите такие сообщения не чаще раза в секунду и указывайте число свёрнутых повторов — иначе диагностика утонет ровно в тот момент, когда она нужнее всего.

По состоянию на 9 августа 2026 оба форка ZaStoGram приведены к этому списку полностью, и расхождений по протоколу между ними нет:

Пункт Android 1.1.19 Desktop f6bd0740
64 байта в первом кадре придерживает байты, пока не наберётся заголовок склейка заголовка с первым пакетом
Один пакет на кадр кадр режется по записанной границе пакета один вызов записи на пакет
Таблица ingress DC1DC5 есть есть
Откат при закрытом релее есть есть
Порядок IPv4/IPv6 по возможностям устройства наследуется от Qt

Что остаётся разным по объективным причинам: Android живёт на собственном коде поверх OpenSSL и epoll и потому сам считает буферы и строго валидирует кадры; Desktop опирается на Qt, получая отлаженную работу с сокетом, но теряя контроль над очередью отправки. Это разница инструментов, а не разночтение протокола.


Откуда берётся номер датацентра

Датацентр не передаётся в URL и не задаётся параметром — он читается из уже упомянутых байт 6061 расшифрованного 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, потому что имя хоста уже само определяет и датацентр, и класс трафика.


Мифа про «только DC2 и DC4» не существует

Почти во всех источниках — включая раннюю версию этой заметки, каталог маршрутов ZapretGUI и комментарий прямо в коде Desktop-форка — сказано, что веб-релеи есть только у DC2 и DC4, а kws1, kws3 и kws5 отвечают редиректом 302 либо молчат. Проверка живыми соединениями 8 августа 2026 этого не подтверждает.

Каждый из трёх адресов принимает апгрейд и реально проксирует MTProto — в ответ приходит настоящий res_pq (конструктор 0x05162463, auth_key_id = 0, nonce совпадает с отправленным):

Адрес ingress Обслуживает Результат проверки
149.154.174.100 kws1, kws3 и их медийные -1 апгрейд и MTProto проходят
149.154.167.220 kws2, kws4 и их медийные -1 апгрейд и MTProto проходят
149.154.170.100 kws5 и kws5-1 апгрейд и MTProto проходят

Откуда же взялся редирект 302? Из ошибки метода. Ingress-адреса не универсальны: каждый обслуживает только свои датацентры. Если постучаться на 149.154.167.220 (это DC2/DC4) с заголовком Host: kws5.web.telegram.org, придёт 302 Found с Location: https://core.telegram.org — и заодно с заголовком X-Redirect-Host: kws5.web.telegram.org, которым сервер прямым текстом сообщает, куда следовало обратиться. Обратный случай ещё коварнее: 149.154.174.100 с именем kws2 не редиректит, а принимает соединение и молчит до таймаута.

Именно так и рождается ложный вывод: инструмент, который держит один зашитый адрес на все датацентры (а так устроены и мосты, и Desktop-форк), при попытке достучаться до DC1/DC3/DC5 получает либо 302, либо тишину — и делает вывод, что релеев для них не существует.

[!warning] Как проверять правильно Релей проверяется парой «адрес + имя», а не адресом отдельно. Для датацентра N берите ingress, обслуживающий именно этот датацентр, и ставьте Host и TLS SNI равными kwsN.web.telegram.org. Проще всего не хардкодить адреса вовсе, а резолвить имя: DNS отдаёт правильный ingress сам.

Второй источник ложных выводов — сеть. Одиночный таймаут TCP на 443 к диапазонам Telegram — обычное дело там, где стоит DPI; без 45 повторов легко объявить живой релей мёртвым. В ходе этой проверки такое случалось дважды и оба раза сначала приводило к неверному диагнозу.

Отдельно про DNS: имена kwsN и медийные kwsN-1 резолвятся в один и тот же адрес — суффикс -1 различает класс трафика на стороне релея, а не машину. У всех имён есть и AAAA-записи, то есть IPv6-путь существует, хотя проверить его не удалось.

Практический смысл поправки: покрытие WSS не ограничено двумя датацентрами. Ограничение реализаций, которые держат один адрес на всё, — их собственное, а не свойство инфраструктуры Telegram.


Две архитектуры: мост снаружи или транспорт внутри

Все известные реализации делятся на два лагеря, и разница между ними определяет почти всё остальное — от установки до того, что видно в логах.

Мост рядом с клиентом Нативный транспорт в клиенте
Примеры 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 — 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-записями kws1kws203, направленными на адреса датацентров, в режиме Cloudflare Flexible. Список доменов по умолчанию хранится в исходниках в закодированном виде и обновляется раз в час; адрес raw.githubusercontent.com при этом закреплён за конкретным IP, чтобы обновление проходило и при испорченном DNS.

Ещё две особенности: пул из четырёх заранее открытых WebSocket-соединений на каждый датацентр (Telegram открывает подключения пачками, и прогретый пул экономит время на рукопожатиях) и запасной вариант с подменой SNI на sprinthost.ru — если прямое соединение с настоящим именем не проходит, попытка повторяется с чужим именем в TLS, тогда как заголовок Host остаётся честным.

Поддержан и вход по FakeTLS (ee-секрет) — с проверкой HMAC и допуском по времени ±120 секунд, а при провале проверки соединение прозрачно перебрасывается на настоящий сайт-прикрытие. Это защита от активного зондирования, и работает она только в консольном режиме: в трей-версии FakeTLS не выведен.

Мост, встроенный в GUI: ZapretGUI

В ZapretGUI есть отдельный раздел «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 для kws1kws5, блокировку по SNI против блокировки по IP, живость локального порта — и заодно смотрит, запущен ли параллельно сам winws2.

Нативный транспорт: ZaStoGram для Android

Форк официального Android-клиента (zastogram/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 строит маршрут для DC1DC5, держа три зашитых адреса релеев — и проверка 8 августа 2026 показала, что все три действительно проксируют MTProto, включая медийные kwsN-1:

Датацентр Зашитый адрес релея Домен (обычный / медиа)
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-форк сегодня. Расширение до DC1DC5 внесли в тот же день, 5 августа 2026, коммитом «Fix media downloads over WSS», причём по аналогии, без проверки живым соединением: тест Tools/check_wss_official_default.py статически сверяет текст кода, а не факт успешного апгрейда. Догадка оказалась верной — измерения 8 августа подтвердили работоспособность всех трёх адресов (см. #Мифа про «только DC2 и DC4» не существует).

Ещё одна поправка к раннему описанию: с версии 1.1.12 (8 августа 2026) исходящий трафик по WSS перестал быть привязан к границам MTProto-пакетов. Отдельная очередь сообщений outgoingWssMessages удалена, байты идут через общий outgoingByteStream — так же, как у любого TCP-транспорта, — и режутся на кадры по 64 КБ. Гарантию «первый кадр не короче 64 байт» держит сам транспорт: WssSocket::write копит начальные байты и не выпускает кадр, пока их меньше заголовка. Обратная сторона: описанный ниже второй бюджет в 4 МБ на MTProto-пакеты вместе с очередью тоже исчез, вместо него — порог 256 КБ на уже сериализованный вывод сокета.

Тестовый бэкенд и CDN-датацентры отсекаются явной проверкой (dcId < 1 || dcId > 5 || testBackend). Тумблер «Use WSS transport» (в интерфейсе — Data and Storage → Proxy Settings, раздел «Telegram WSS транспорт») включён по умолчанию с версии 1.1.20; до этого требовалось включать вручную. Осознанный выбор пользователя сохраняется: если тумблер уже трогали, берётся его значение.

Совмещение с прокси запрещено полностью и в обе стороны: включение 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 (zastogram/ZaStoGram_desktop, версия 7.0.8 от 3 августа 2026, HEAD 729dfd39) решает ту же задачу, но опирается на Qt. WSS живёт в mtproto/proxy/wss/socket.cpp поверх QSslSocket, а выбор сокета вынесен в фабрику: в зависимости от настроек и секрета создаётся WssSocket, TlsSocket (FakeTLS для MTProxy) или обычный TcpSocket. Протокольный слой при этом не знает, какой транспорт под ним, — поэтому обфускация для всех трёх одинаковая.

Первое отличие от Android-версии — транспорт по умолчанию Wss: без единой настройки соединения идут через веб-релеи, а к непокрытым датацентрам — обычным TCP.

Долгое время покрытие было ограничено DC2/DC4, с комментарием в коде «web sockets exist only for DC2 / DC4». Посылка оказалась неверной, а причина — технической: форк держал один зашитый адрес 149.154.167.220, обслуживающий только эти два датацентра, и попытка достучаться через него до kws1/kws5 давала редирект 302 (см. #Мифа про «только DC2 и DC4» не существует). Коммитом 7f3ceffb от 8 августа 2026 адрес заменён таблицей «датацентр → ingress», как в Android-форке, и покрытие расширено до DC1DC5. Подсказка «WSS unavailable for this DC» опирается на ту же функцию маршрута, поэтому для новых датацентров она пропала автоматически.

Фрейминг у Desktop-версии, наоборот, случайно оказался правильным: write(prefix, buffer) склеивает 64-байтный init с первым пакетом в один кадр, поэтому требование «первый кадр не короче 64 байт» выполняется само собой.

Второе — экспертный режим с произвольным релеем: можно указать свои 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: чем отличаются

По протоколу форки больше не расходятся: правило первого кадра, один пакет на кадр, таблица ingress DC1DC5 и откат при закрытом релее выполняются в обоих. Различия остались в том, что построено вокруг транспорта.

Ось сравнения Android (ZaStoGram) Desktop (ZaStoGram Desktop)
Соблюдение правил фрейминга да да
Покрытие датацентров DC1DC5 DC1DC5, плюс любой через свой релей
Откат при закрытом релее по датацентру, с 1.1.18 по датацентру, с f6bd0740
Дефолт включён, с 1.1.20 включён
Свой релей нет — только инфраструктура Telegram есть: host, port, path, SNI
Ограничение очереди на отправку 4 МБ на кадры плюс 256 КБ на сериализованный вывод отсутствует
Строгость разбора кадров RSV, маска сервера, фрагментация, контрольные кадры максимальный размер и close
Диагностика отказов 29 отдельных кодов 5 сообщений в одном коде
Минимальная версия TLS задана явно: 1.2 наследуется от Qt
Индикация для пользователя статичная метка транспорт и пинг в реальном времени
CDN-загрузки отключены флагом cdn_supported=false разрешены, идут мимо туннеля прямым TCP
Сочетание с прокси запрещено полностью разрешено поверх удалённого SOCKS5

Транспортный слой аккуратнее у Android. Он считает свои буферы и отказывает управляемо, строго валидирует RFC 6455 и даёт содержательную диагностику — 29 отдельных кодов вместо пяти сообщений в одном. Плата за это — ручная работа с epoll и OpenSSL: почти все найденные за август баги транспорта жили именно здесь, и каждый чинил реальный воспроизводимый отказ.

Предсказуемость медиа тоже выше у Android. Флаг cdn_supported=false убирает CDN из уравнения: файлы, стикеры и реакции идут через датацентры, а не через CDN-адреса. В Desktop CDN-редиректы разрешены, и такое соединение выходит из-под туннеля обычным TCP — ровно в той сети, где прямые адреса Telegram и режут.

Гибкость и обратная связь — за Desktop. Свой релей остаётся выходом, когда все зашитые адреса заблокированы: у Android в такой ситуации выбор беднее. Живой индикатор транспорта с пингом отвечает на главный вопрос пользователя — работает ли обход прямо сейчас, — на который Android не отвечает никак.

Что одинаково слабо у обоих. Ни один не мимикрирует под TLS-почерк браузера: рукопожатие уходит с отпечатком системной криптобиблиотеки, а не Chrome, хотя инфраструктура для подмены в обоих проектах есть — она подключена только к их же ветке MTProxy FakeTLS (см. mtproxy/ja4-sni-client-side). Заголовок User-Agent в обоих зашит с Chrome 131 — версией конца 2024 года. И ни один не показывает пользователю, что часть датацентров ушла на прямое соединение: обход работает молча.

[!tip] Практический вывод Транспорт включён по умолчанию в обоих клиентах, и специально его настраивать не нужно. Если медиа частично не грузится — проверьте по TCP доступность релеев своей сети (#Релей может быть закрыт: главное практическое ограничение). Открыт не весь список — значит для этой сети правильнее туннель: MTProxy или VPN проводят все датацентры через один сервер, тогда как WSS работает только там, где открыт релей.


Чем это отличается от MTProxy и VPN

Легко решить, что WSS — это «MTProxy, но лучше», и ошибиться. Разница в том, где стоит точка выхода.

Классический Zapret/mtproto/00-overview — это ваш сервер (или чужой публичный), к которому клиент подключается по произвольному порту и который дальше сам идёт к Telegram. Он позволяет ротировать адреса, ставить FakeTLS, делить порт 443 с настоящим сайтом — но требует VPS, а его адрес рано или поздно попадает в списки блокировок.

WSS-транспорт не требует ничего своего: клиент идёт напрямую на инфраструктуру Telegram, просто через её веб-подъезд. Отсюда и плюс, и минус. Плюс — не надо ничего поднимать и платить, а домен назначения принадлежит Telegram. Минус — вы не управляете точкой выхода: если конкретный релей начнёт отвечать редиректом или замолчит, сделать с этим нечего, кроме как переключиться на запасной маршрут.

От VPN обе схемы отличаются одинаково: они уводят только трафик Telegram и не трогают остальную систему, не держат туннель и не сажают батарею. Звонки не переносит ни одна из них — голос и видео ходят по UDP мимо любого TCP-прокси, — но это вопрос не «работают или нет», а «чем их закрывать»: трафик звонка идёт напрямую, и его задача решается пакетным обходом (Zapret2/Zapret2 перехватывает в том числе UDP и STUN) или VPN.

MTProxy на своём VPS WSS-транспорт VPN
Нужен свой сервер да нет обычно да
Куда идёт трафик ваш IP инфраструктура Telegram ваш IP
Управление точкой выхода полное никакого полное
Работает вне Telegram нет нет да
Переносит звонки нет, идут напрямую нет, идут напрямую да
Что блокируют в первую очередь IP сервера сами релеи и подходы к ним IP сервера, протокол

Отсюда практическая связка: WSS или MTProxy отвечают за переписку и медиа, пакетный обход — за звонки и за те датацентры, до которых веб-релеи не дотягиваются. Они не конкуренты и работают параллельно.


Что дальше

Схема выглядит изящно, но у неё большой список оговорок: работают не все датацентры, стикеры и реакции ходят через CDN мимо релеев, звонки не проксируются вовсе, а бесплатные запасные пути через Cloudflare упираются в лимиты. Всё это разобрано отдельно — с симптомами, причинами и тем, что показывает диагностика: mtproxy/telegram-wss-limits.


📚 См. также

  • mtproxy/telegram-wss-limits — парная заметка: датацентры, медиа, звонки, Cloudflare, типичные диагнозы
  • Zapret/mtproto/00-overview — обзорный раздел про классический MTProxy: протокол, реализации, цензура
  • Zapret/mtproto/01-protocol — что такое abridged/intermediate/padded intermediate и откуда берутся теги 0xef/0xdd/0xee
  • mtproxy/ja4-sni-client-side — почему обход по «почерку» соединения делается на клиенте, а не на релее
  • mtproxy/faketls-relay-diagnosis — как понять, кто виноват, когда рабочий ключ не подключается
  • mtproxy/tsrman-tg-android-faketls — другой подход к тому же: не смена транспорта, а смена TLS-почерка
  • 🔗 Flowseal/tg-ws-proxy — исходники моста на Python, документация по Cloudflare Worker и FakeTLS
  • virus/github-removes-clean-zapret-keeps-malware-august-2026 — почему исходники ZaStoGram с 11 августа 2026 доступны только в Forgejo: GitHub-организация youtubediscord удалена
  • 🔗 zastogram/ZaStoGram — исходники Android-форка: WSS-транспорт в TMessagesProj/jni/tgnet/wss/, готовые APK — в релизах
  • 🔗 zastogram/ZaStoGram_desktop — исходники Desktop-форка: WSS-транспорт в Telegram/SourceFiles/mtproto/proxy/wss/
  • 🔗 RFC 6455 — спецификация WebSocket: рукопожатие, маскирование, формат кадров

[!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: исходник этой заметки · весь репозиторий.