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

О чём заметка

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

Откуда взяты данные

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

Обновление 8–9 августа 2026: измерения вместо чтения кода

Разделы Правило первого кадра, Мифа про «только DC2 и DC4» не существует и Рукопожатие: место, где всё ломается тихо опираются на живые соединения с релеями, а не на исходники. Итог:

  • длина первого кадра важна: меньше 64 байт — соединение молча умирает (порог измерен с точностью до байта);
  • склейка пакетов запрещена: релей разбирает только первый MTProto-пакет кадра, остальные выбрасывает без ошибки. Разрезать один пакет на несколько кадров при этом можно;
  • «веб-релеи существуют только для DC2 и DC4» неверно — работают все DC1–DC5, а редирект 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 и номер датацентра в байтах 60–61.
  4. Требований к нарезке ровно два, и оба нарушаются молча: первый кадр после апгрейда должен содержать не меньше 64 байт (весь init), и в одном кадре не должно быть больше одного MTProto-пакета — остальные релей выбрасывает. Разрезать пакет на несколько кадров при этом можно свободно. См. Правило первого кадра.
  5. Реализации делятся на два класса: мост рядом с клиентом (tg-ws-proxy от Flowseal, модуль telegram_proxy в ZapretGUI — оба на Python, слушают локальный порт как SOCKS5/MTProxy) и нативный транспорт внутри клиента (форки ZaStoGram для Android и для Desktop, C++ прямо в сетевом слое).
  6. Веб-релеи есть у всех датацентров DC1–DC5, а не только у 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. Два клиентских форка по протоколу больше не расходятся: оба соблюдают правила фрейминга, покрывают DC1–DC5 и откатываются на прямое соединение при закрытом релее. Различия остались вокруг транспорта: Android строже валидирует кадры и ограничивает очереди, Desktop даёт свой релей и живой индикатор с пингом.

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

Обход блокировок Telegram распадается на две разные задачи, и их постоянно путают. Первая — когда провайдер видит как выглядит соединение: анализирует TLS-рукопожатие, сравнивает почерк клиента с известными, ловит характерный первый пакет. Против этого работают инструменты вроде zapret, подменяющие поведение пакетов, и клиентские правки TLS-почерка (подробно — в заметке про JA4 и SNI на стороне клиента).

Вторая задача — когда провайдеру всё равно, как выглядит трафик, потому что он режет сами адреса. Диапазоны датацентров Telegram (149.154.160.0/20, 91.105.192.0/23 и соседние) известны и компактны; заблокировать их целиком дешевле, чем разбирать протокол. В таком случае никакая фрагментация пакета не помогает: пакет просто не доезжает.

WSS-транспорт бьёт именно во вторую задачу. Идея простая: у Telegram, кроме «обычных» адресов датацентров, есть веб-инфраструктура, обслуживающая браузерную версию мессенджера, — она живёт на 443 порту, за нормальными TLS-сертификатами и доменами web.telegram.org. Блокировать её тем же топорным способом дороже: это тот же домен, что и сайт, которым пользуются миллионы людей. Если завернуть MTProto в WebSocket и отправить туда, то на уровне IP и SNI трафик выглядит как визит на сайт Telegram.

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


Как выглядит соединение: пять слоёв

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

TCP :443                       обычное TCP-соединение к IP релея
 └─ TLS 1.2+                   настоящий TLS, SNI = kws2.web.telegram.org
     └─ HTTP/1.1 Upgrade       GET /apiws → ответ 101 Switching Protocols
         └─ WebSocket frames   бинарные кадры (opcode 0x2), клиент маскирует их
             └─ MTProto obfuscated2   64-байтный init + AES-256-CTR
                 └─ сам MTProto       шифрование клиент↔Telegram на auth_key

Внешний слой — TLS — нужен для маскировки: наблюдателю виден HTTPS к домену Telegram и больше ничего. Внутренний слой — obfuscated2 — остался ровно тем же, что и в обычном TCP-подключении Telegram: он превращает MTProto-поток в равномерный шум без заголовков, чтобы DPI не опознал протокол по сигнатуре. То, что шум едет внутри TLS и внутри WebSocket, ему не мешает.

Самый нижний слой — собственно MTProto с ключом авторизации — не трогает никто. Это важно для понимания рисков: мост между клиентом и Telegram видит транспортную обёртку, но не содержимое переписки; переписка расшифровывается только на устройстве и на серверах Telegram.


Рукопожатие по шагам

Все четыре реализации делают одно и то же, различаясь мелочами. Порядок такой.

Шаг 1. TCP к IP релея. Соединение открывается не по DNS-имени, а по зашитому в код адресу — чаще всего 149.154.167.220 (для DC2/DC4), в Android-форке к нему добавлены 149.154.174.100 (DC1/DC3) и 149.154.170.100 (DC5). Имя kwsN.web.telegram.org при этом всё равно передаётся — но только выше, в TLS и HTTP.

Шаг 2. TLS. SNI и проверка имени сертификата выставляются в доменное имя релея. Здесь реализации расходятся: клиентские форки проверяют цепочку сертификатов по-настоящему (SSL_VERIFY_PEER в Android, QSslSocket::VerifyPeer в Desktop), а tg-ws-proxy проверку отключает намеренно — для него TLS не граница доверия, а обёртка, потому что полезная нагрузка и так зашифрована между клиентом и Telegram.

Шаг 3. HTTP Upgrade. Отправляется обычный запрос апгрейда до WebSocket:

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 считается обрывом.

Почему адрес и имя расходятся

Соединение открывается на IP-адрес, а имя kwsN.web.telegram.org фигурирует только в SNI и в заголовке Host. Это не хитрость ради хитрости: реализации не хотят зависеть от DNS, который может не отвечать или быть подменён. Android-форк дополнительно умеет откатиться на резолв доменного имени, если жёстко зашитый IP не отвечает, и запоминает удачный вариант на 30 минут. Модуль в ZapretGUI идёт дальше и прописывает нужные имена прямо в hosts Windows — приём рабочий, но он же однажды сломал их собственную диагностику, которая после этого проверяла не DNS, а собственную запись в hosts.


Что едет внутри кадров

Внутри WebSocket-кадров лежит обычный обфусцированный MTProto — тот же, что уходил бы в голый TCP. Первым отправляется 64-байтный init-пакет: 8 байт пропускаются, следующие 48 дают ключ и IV для AES-256-CTR (в обратном порядке — ключ для встречного направления), байты 56–59 содержат тег протокола, байты 60–61 — номер датацентра со знаком, где минус означает медиа-подключение. Затем весь пакет шифруется сам собой, и наружу уходит структура, статистически неотличимая от случайных байт.

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

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

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


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

Два правила, и оба нарушаются молча

Первое: первый бинарный 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 здесь ни при чём: он обслуживает создание ключей наравне с обычным.

Как распознать это в логах

Три признака, которые встречаются вместе:

  • 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 мс)закрыт

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

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.19Desktop f6bd0740
64 байта в первом кадрепридерживает байты, пока не наберётся заголовоксклейка заголовка с первым пакетом
Один пакет на кадркадр режется по записанной границе пакетаодин вызов записи на пакет
Таблица ingress DC1–DC5естьесть
Откат при закрытом релееестьесть
Порядок IPv4/IPv6по возможностям устройстванаследуется от Qt

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


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

Датацентр не передаётся в URL и не задаётся параметром — он читается из уже упомянутых байт 60–61 расшифрованного init-пакета. Дальше номер превращается в имя релея по простому правилу:

Что нужноДоменПример
Обычное соединение с DC NkwsN.web.telegram.orgkws2.web.telegram.org
Медиа-соединение (загрузка файлов)kwsN-1.web.telegram.orgkws4-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.100kws1, kws3 и их медийные -1апгрейд и MTProto проходят
149.154.167.220kws2, kws4 и их медийные -1апгрейд и MTProto проходят
149.154.170.100kws5 и 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, либо тишину — и делает вывод, что релеев для них не существует.

Как проверять правильно

Релей проверяется парой «адрес + имя», а не адресом отдельно. Для датацентра 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 в ZapretGUIZaStoGram (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 строит маршрут для DC1–DC5, держа три зашитых адреса релеев — и проверка 8 августа 2026 показала, что все три действительно проксируют MTProto, включая медийные kwsN-1:

ДатацентрЗашитый адрес релеяДомен (обычный / медиа)
DC1, DC3149.154.174.100kws1 / kws1-1, kws3 / kws3-1
DC2, DC4149.154.167.220kws2 / kws2-1, kws4 / kws4-1
DC5149.154.170.100kws5 / 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 дописывался новыми кадрами и переезжал в памяти, что ломало отправку под нагрузкой.

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–DC5DC1–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 (см. разбор JA4 и SNI). Заголовок User-Agent в обоих зашит с Chrome 131 — версией конца 2024 года. И ни один не показывает пользователю, что часть датацентров ушла на прямое соединение: обход работает молча.

Практический вывод

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


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

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

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

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

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

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

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


Что дальше

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


📚 См. также


🤖 Эти статьи открыты — можно обучать на них ИИ

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