ZaStoGram/Tools/check_wss_transport_mode.py
loop-uh f7159aabd3
Some checks failed
Build three ZaStoGram APKs / build (arm64-v8a, ZaStoGram-standalone-arm64-v8a, Arm64, arm64) (push) Failing after 3m7s
Build three ZaStoGram APKs / build (armeabi-v7a, ZaStoGram-standalone-armeabi-v7a, Armv7, armv7) (push) Failing after 2m25s
Build three ZaStoGram APKs / build (x86, ZaStoGram-standalone-x86, X86, x86) (push) Failing after 2m0s
ZaStoGram source guards / guards (push) Failing after 15s
WSS: у DC2/DC4 два адреса входа — 149.154.167.220 и 149.154.167.99
DNS отдаёт для kws2/kws4 (и для web.telegram.org) адрес .99, а клиент
держал только зашитый .220. 16.10.2026 оба адреса приняли все четыре имени
DC2/DC4, по 25 кругов без разницы сверх шума; пользователи tg-ws-proxy уже
сообщают о блокировке .220 по IP.

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

Гарды check_wss_official_default и check_wss_transport_mode переведены на
список адресов; устаревшее условие про CDN (supportsCdnFileRedirects теперь
смотрит на isWssTransportActive) поправлено.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:09:08 +03:00

209 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[1]
SHARED_CONFIG = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java"
CONNECTIONS_JAVA = ROOT / "TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java"
PROXY_LIST = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java"
PROXY_SETTINGS = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java"
BOT_WEBVIEW = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/web/BotWebViewContainer.java"
MINI_BRIDGE = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger/WssMiniAppProxyBridge.java"
WRAPPER_CPP = ROOT / "TMessagesProj/jni/TgNetWrapper.cpp"
MANAGER_CPP = ROOT / "TMessagesProj/jni/tgnet/ConnectionsManager.cpp"
MANAGER_H = ROOT / "TMessagesProj/jni/tgnet/ConnectionsManager.h"
SOCKET_CPP = ROOT / "TMessagesProj/jni/tgnet/ConnectionSocket.cpp"
SOCKET_H = ROOT / "TMessagesProj/jni/tgnet/ConnectionSocket.h"
SOCKET_STATE_H = ROOT / "TMessagesProj/jni/tgnet/ConnectionSocketStateMachine.h"
CONNECTION_CPP = ROOT / "TMessagesProj/jni/tgnet/Connection.cpp"
TRANSPORT_H = ROOT / "TMessagesProj/jni/tgnet/transport/TransportSocket.h"
WSS_H = ROOT / "TMessagesProj/jni/tgnet/wss/WssSocket.h"
WSS_CPP = ROOT / "TMessagesProj/jni/tgnet/wss/WssSocket.cpp"
OLD_WSS_H = ROOT / "TMessagesProj/jni/tgnet/WssTransport.h"
OLD_WSS_CPP = ROOT / "TMessagesProj/jni/tgnet/WssTransport.cpp"
CMAKE = ROOT / "TMessagesProj/jni/CMakeLists.txt"
STRINGS = ROOT / "TMessagesProj/src/main/res/values/strings.xml"
STRINGS_RU = ROOT / "TMessagesProj/src/main/res/values-ru/strings.xml"
def text(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="replace")
def require(condition: bool, message: str) -> None:
if not condition:
print(f"FAIL: {message}", file=sys.stderr)
sys.exit(1)
def main() -> None:
shared_config = text(SHARED_CONFIG)
connections = text(CONNECTIONS_JAVA)
proxy_list = text(PROXY_LIST)
proxy_settings = text(PROXY_SETTINGS)
bot_webview = text(BOT_WEBVIEW)
wrapper = text(WRAPPER_CPP)
manager_cpp = text(MANAGER_CPP)
manager_h = text(MANAGER_H)
socket_cpp = text(SOCKET_CPP)
socket_h = text(SOCKET_H)
socket_state_h = text(SOCKET_STATE_H)
socket_state_cpp = text(ROOT / "TMessagesProj/jni/tgnet/ConnectionSocketStateMachine.cpp")
connection_cpp = text(CONNECTION_CPP)
transport_h = text(TRANSPORT_H)
wss_h = text(WSS_H)
wss_cpp = text(WSS_CPP)
cmake = text(CMAKE)
require("boolean wssTransportEnabled" in shared_config,
"SharedConfig must persist only the client WSS checkbox")
require("setWssTransportEnabled(boolean enabled)" in shared_config,
"SharedConfig must expose the WSS checkbox setter")
require("PROXY_SCHEMA_V4" in shared_config,
"proxy schema V4 must keep proxy rows independent from WSS")
require("Consume obsolete per-row WSS metadata" in shared_config,
"schema V3 WSS metadata must be migrated, not reused")
for forbidden in ("currentWssSocksProxy", "saveWssSocksProxy", "clearWssSocksProxy", "isWssTransport()"):
require(forbidden not in shared_config, f"SharedConfig must not retain {forbidden}")
require("public static void setWssTransportEnabled()" in connections,
"Java must expose a boolean-only WSS setter")
require("native_setWssTransportEnabled(int currentAccount, boolean enabled)" in connections,
"Java JNI declaration must carry only account and enabled")
require("setWssTransportSettings" not in connections,
"Java must not carry gateway or upstream-proxy settings")
require('native_setWssTransportEnabled", "(IZ)V"' in wrapper,
"JNI registration must use the boolean-only WSS signature")
require("void setWssTransportEnabled(bool enabled)" in manager_h,
"native manager must expose the boolean-only WSS setting")
require("bool wssEnabled" in manager_h,
"native manager must store only the WSS toggle")
require("setWssTransportSettings" not in manager_cpp,
"native manager must not store WSS gateway/proxy configuration")
require("R.string.UseWssTransport" in proxy_list and "TextCheckCell" in proxy_list,
"proxy UI must expose WSS as a checkbox")
require("disableLegacyProxyForWss" in proxy_list
and "ConnectionsManager.setProxySettings(false" in proxy_list,
"enabling WSS must disable the legacy proxy")
require("SharedConfig.setWssTransportEnabled(false)" not in proxy_list,
"selecting a proxy must only suspend WSS, not clear the user's WSS preference")
require("SharedConfig.wssTransportEnabled && !useProxySettings" in proxy_list,
"the WSS checkbox must show WSS as off while a proxy is in use")
for forbidden in ("TYPE_WSS", "createWssGateway", "WSS_TRANSPORT_OPTIONS", "wssCustomGatewayRow"):
require(forbidden not in proxy_settings + proxy_list,
f"proxy screens must not retain {forbidden}")
require("class Socket" in transport_h and "virtual bool open" in transport_h,
"tgnet must expose an abstract transport socket boundary")
require("class Socket final : public transport::Socket" in wss_h,
"WSS must be its own transport socket module")
require("int socketFd = -1" in wss_h and "::socket(" in wss_cpp and "::connect(" in wss_cpp,
"WSS module must own its native descriptor and TCP connect")
require("SSL_VERIFY_PEER" in wss_cpp and "SSL_set1_host" in wss_cpp,
"WSS TLS must verify the peer and hostname")
require("Sec-WebSocket-Protocol: binary" in wss_cpp and "Sec-WebSocket-Key" in wss_cpp,
"WSS module must perform a real binary WebSocket upgrade")
require("kws2.web.telegram.org" not in wss_cpp or '"kws2"' in wss_cpp,
"official DC2 route must be generated inside the WSS module")
require('prefix = "kws" + std::to_string(dcId)' in wss_cpp and '"/apiws"' in wss_cpp,
"WSS module must use the official DC1-DC5 relay catalog")
require("officialRelayIpsForDc" in wss_cpp
and "result.relayHostFallback = result.domain" in wss_cpp
and "result.connectHost = result.viaFallback" in wss_cpp
and "bool viaFallback = false" in wss_h,
"WSS module must own direct Telegram ingress and hostname fallback routing")
for forbidden in ("buildSocks5Greeting", "upstreamSocksEnabled", "customRoute"):
require(forbidden not in wss_cpp + wss_h,
f"WSS socket must not contain proxy/gateway feature {forbidden}")
require('#include "wss/WssSocket.h"' in socket_h + socket_state_h,
"ConnectionSocket state must depend on the dedicated WSS socket")
require("std::unique_ptr<tgnet::transport::Socket>" in socket_h + socket_state_h,
"ConnectionSocket state must hold WSS through the transport abstraction")
require("tgnet::wss::OfficialRoute" in socket_cpp and "tgnet::wss::CreateSocket" in socket_cpp,
"ConnectionSocket must select and instantiate the WSS module")
require("manager.wssEnabled" in socket_cpp and "proxyAddress->empty()" in socket_cpp,
"WSS selection must be independent and must not stack over a proxy")
require("currentWssTransport->open" in socket_cpp
and "currentWssTransport->onEvent" in socket_cpp
and "currentWssTransport->write" in socket_cpp,
"ConnectionSocket must delegate WSS I/O to the socket module")
require("if (!isCurrentTransportWss())" in socket_cpp
and "eventMask.events |= EPOLLET" in socket_cpp,
"WSS must stay level-triggered while TCP transports keep edge-triggered epoll")
require("enum class IoWait" in wss_h
and "SSL_ERROR_WANT_READ" in wss_cpp
and "SSL_ERROR_WANT_WRITE" in wss_cpp
and "ioWait == IoWait::Write" in wss_cpp,
"WSS must preserve OpenSSL read/write wait direction")
require("wss_socket tcp_connected" in wss_cpp
and "wss_socket tls_ready" in wss_cpp
and "wss_socket timeout" in wss_cpp,
"WSS diagnostics must identify TCP, TLS, and timeout phases")
require("getDiagnosticSnapshot" in socket_h
and 'append("wss_url"' in socket_cpp
and 'append("wss_relay"' in socket_cpp
and "collectConnectionDiagnostics" in manager_h + manager_cpp
and "native_getDatacenterConnectionDiagnostics" in connections
and 'native_getDatacenterConnectionDiagnostics", "(II)Ljava/lang/String;"' in wrapper,
"message diagnostics must expose the live native WSS route through a bounded JNI snapshot")
require('append("connect"' in socket_cpp
and 'append("target"' in socket_cpp
and "currentSecret=" not in socket_cpp[socket_cpp.index("std::string ConnectionSocket::getDiagnosticSnapshot"):socket_cpp.index("bool ConnectionSocket::matchesMtProxyEndpointKey")]
and 'append("endpoint_key"' not in socket_cpp
and 'append("probe_key"' not in socket_cpp,
"connection snapshots must identify endpoints without exposing proxy secrets")
# Измерено 2026-08-09: релей разбирает только ПЕРВЫЙ MTProto-пакет кадра и
# молча выбрасывает остальные. msgs_ack, склеенный со следующим шагом
# рукопожатия, оставлял датацентр без ключа навсегда.
require("flushWssStream" in socket_cpp
and "outgoingByteStream->get" in socket_cpp
and "outgoingWssPacketSizes" in socket_cpp
and "noteWssPacketBoundary" in socket_cpp
and "noteWssPacketBoundary" in connection_cpp,
"WSS frames must be cut exactly at MTProto packet boundaries")
require("queuedOutputBytes" in wss_cpp
and "readFailed" in wss_cpp
and "EPOLLRDHUP" in wss_cpp
and "dispatchWssPayloads(payloads)" in socket_cpp
and socket_cpp.index("dispatchWssPayloads(payloads)") < socket_cpp.index("wss_transport_failed"),
"WSS must deliver frames drained before EOF so transport errors reach MTProto")
# Measured against the official relays: a first binary frame of 63 bytes is
# never answered, 64 always is, and the failure is completely silent.
require("kObfuscationHeaderSize = 64" in wss_cpp
and "openingFrameSent" in wss_cpp
and "openingFrame.size() < kObfuscationHeaderSize" in wss_cpp
and "openingFrame" in wss_h,
"WSS must never emit a first frame shorter than the 64-byte obfuscation header")
require("std::deque<std::vector<uint8_t>> pendingOutput" in wss_h
and "pendingOutput.front()" in wss_cpp
and "pendingOutput.push_back(std::move(frame))" in wss_cpp,
"WSS TLS retries must keep immutable frame buffers during upload bursts")
require("The relay owns the socket address" in socket_cpp
and "ipv6 = false;" in socket_cpp,
"WSS relay address family must not inherit the ignored DC target family")
require("lastPushPingTime != 0" in manager_cpp,
"push ping timeout must not spin after clearing its timestamp")
for forbidden in ("wssSocksHost", "wssFallbackProxy", "WssRouteConfig"):
require(forbidden not in socket_cpp + socket_h,
f"ConnectionSocket must not contain obsolete WSS proxy field {forbidden}")
require("tgnet/wss/WssSocket.cpp" in cmake and "tgnet/WssTransport.cpp" not in cmake,
"CMake must compile only the dedicated WSS socket module")
require(not OLD_WSS_H.exists() and not OLD_WSS_CPP.exists(),
"the old mixed WssTransport implementation must be removed")
require(not MINI_BRIDGE.exists() and "WssMiniAppProxyBridge" not in bot_webview,
"WSS must not create a MiniApp/local proxy bridge")
for path in (STRINGS, STRINGS_RU):
source = text(path)
require('name="UseWssTransport"' in source, f"{path.name} must define UseWssTransport")
require('name="WssTransportInfo"' in source, f"{path.name} must define WssTransportInfo")
print("WSS transport architecture guard passed.")
if __name__ == "__main__":
main()