В корне создан раздел 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>
83 KiB
| date | tags | aliases | link | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-08-06 |
|
|
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] Обновление 8–9 августа 2026: измерения вместо чтения кода Разделы #Правило первого кадра, #Мифа про «только DC2 и DC4» не существует и #Рукопожатие: место, где всё ломается тихо опираются на живые соединения с релеями, а не на исходники. Итог:
- длина первого кадра важна: меньше 64 байт — соединение молча умирает (порог измерен с точностью до байта);
- склейка пакетов запрещена: релей разбирает только первый MTProto-пакет кадра, остальные выбрасывает без ошибки. Разрезать один пакет на несколько кадров при этом можно;
- «веб-релеи существуют только для DC2 и DC4» неверно — работают все DC1–DC5, а редирект
302возникает при обращении к чужому ingress-адресу.Промежуточный вывод от 8 августа, будто границы пакетов серверу безразличны, был ошибочным: он опирался на проверки, где в кадре был ровно один пакет, и потеря второго не проявлялась. Полное рукопожатие 9 августа это опровергло.
Отдельно добавлен раздел #Релей может быть закрыт: главное практическое ограничение — о том, что чаще всего мешает не протокол, а недоступность самого релея из конкретной сети, и почему
pingэтого не показывает.
TL;DR
- WSS-транспорт для Telegram — это тот же обфусцированный MTProto, но упакованный в бинарные кадры WebSocket поверх настоящего TLS и отправленный на порт 443 доменов
kwsN.web.telegram.org(kwsN-1— для медиа-трафика). - Задача, которую он решает, — не DPI-фингерпринт, а блокировка по IP: если провайдер режет диапазоны датацентров Telegram, соединение к веб-релею на 443 внешне неотличимо от «пользователь открыл web.telegram.org в браузере».
- Внутри ничего не поменялось: остались 64-байтный obfuscated2-init и AES-256-CTR, те же теги протокола
0xefefefef/0xdddddddd/0xeeeeeeeeи номер датацентра в байтах 60–61. - Требований к нарезке ровно два, и оба нарушаются молча: первый кадр после апгрейда должен содержать не меньше 64 байт (весь init), и в одном кадре не должно быть больше одного MTProto-пакета — остальные релей выбрасывает. Разрезать пакет на несколько кадров при этом можно свободно. См. #Правило первого кадра.
- Реализации делятся на два класса: мост рядом с клиентом (
tg-ws-proxyот Flowseal, модульtelegram_proxyв ZapretGUI — оба на Python, слушают локальный порт как SOCKS5/MTProxy) и нативный транспорт внутри клиента (форки ZaStoGram для Android и для Desktop, C++ прямо в сетевом слое). - Веб-релеи есть у всех датацентров DC1–DC5, а не только у DC2/DC4 — это проверено живыми соединениями 8 августа 2026. Распространённое «работают только DC2 и DC4» родилось из ошибки метода: у каждого датацентра свой адрес ingress, и если стучаться на адрес DC2 с именем
kws5, приходит редирект302— сервер так и говорит, что вы пришли не туда. Подробно — в #Мифа про «только DC2 и DC4» не существует. - Чаще всего мешает не протокол, а доступность релея. В измеренной сети открыт был ровно один ingress из четырёх проверенных путей: релей DC2 работал, релей DC1 не отвечал ни по IPv4, ни по IPv6, и прямые адреса DC1 тоже были закрыты. Пользователь при этом видит «фото грузятся, эмодзи нет».
pingво всех случаях проходил и ничего не доказывал — см. #Релей может быть закрыт: главное практическое ограничение. - Звонки через WSS не идут ни в одной реализации — WebSocket живёт поверх TCP, а голос и видео у Telegram ходят по UDP; их трафик проходит мимо туннеля напрямую, и закрывается он пакетным обходом, а не прокси.
- Два клиентских форка по протоколу больше не расходятся: оба соблюдают правила фрейминга, покрывают DC1–DC5 и откатываются на прямое соединение при закрытом релее. Различия остались вокруг транспорта: 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 идёт дальше и прописывает нужные имена прямо вhostsWindows — приём рабочий, но он же однажды сломал их собственную диагностику, которая после этого проверяла не 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.
При генерации 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]…, весь поток кусками по 7–8 байт |
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 из полезной нагрузки первого кадра и больше к этому вопросу не возвращается: досылка недостающего байта следующим кадром соединение не спасает.
Отказ молчаливый — и это худшая часть. Если первый кадр имеет длину 48–63 байта, сервер не отвечает ничего: соединение просто висит до таймаута. Со стороны клиента это неотличимо от блокировки провайдером или потери пакетов, поэтому баг легко списать на сеть и искать не там. Только при первом кадре в 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-пакет. В байтах 60–61 он нужен 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 DC1–DC5 | есть | есть |
| Откат при закрытом релее | есть | есть |
| Порядок IPv4/IPv6 | по возможностям устройства | наследуется от Qt |
Что остаётся разным по объективным причинам: Android живёт на собственном коде поверх OpenSSL и epoll и потому сам считает буферы и строго валидирует кадры; Desktop опирается на Qt, получая отлаженную работу с сокетом, но теряя контроль над очередью отправки. Это разница инструментов, а не разночтение протокола.
Откуда берётся номер датацентра
Датацентр не передаётся в 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, потому что имя хоста уже само определяет и датацентр, и класс трафика.
Мифа про «только 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; без 4–5 повторов легко объявить живой релей мёртвым. В ходе этой проверки такое случалось дважды и оба раза сначала приводило к неверному диагнозу.
Отдельно про 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-записями kws1…kws203, направленными на адреса датацентров, в режиме 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 для kws1…kws5, блокировку по 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 строит маршрут для DC1–DC5, держа три зашитых адреса релеев — и проверка 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-форк сегодня. Расширение до DC1–DC5 внесли в тот же день, 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-форке, и покрытие расширено до DC1–DC5. Подсказка «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 DC1–DC5 и откат при закрытом релее выполняются в обоих. Различия остались в том, что построено вокруг транспорта.
| Ось сравнения | Android (ZaStoGram) | Desktop (ZaStoGram Desktop) |
|---|---|---|
| Соблюдение правил фрейминга | да | да |
| Покрытие датацентров | DC1–DC5 | DC1–DC5, плюс любой через свой релей |
| Откат при закрытом релее | по датацентру, с 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: исходник этой заметки · весь репозиторий.
