🧊 Как DPI «замораживает» VLESS+REALITY: разбор схемы ограничений (июнь 2026)

Первоисточник

Этот разбор основан на статье Петра Осетрова (@hyperion_cs) на Хабре: 👉 «О схеме ограничений РКН в июне 2026-го» — habr.com/ru/articles/1044396

Все числовые параметры (пороги, тайминги, списки фингерпринтов) взяты из этой статьи. Ниже — простое объяснение «на пальцах» плюс расширенные технические детали по uTLS и mux от себя.

Дисклеймер

Материал носит исследовательский и образовательный характер. Задача — понять, по каким признакам современный DPI отличает обходные средства от обычного трафика, и почему одной волшебной галочкой проблему больше не решить.

Важно про статус этих данных

Описанная схема — это результат реверс-инжиниринга: автор сам называет её «Siberian» и добавил одноимённый чекер в dpi-checkers. Это наблюдения одного исследователя; сам автор оговаривает, что параметры метода различаются от оператора к оператору и меняются со временем.

Параллельно, в тот же период, задокументированы и другие механизмы блокировки — заморозка соединения после ~16 КБ / ~25 пакетов (метод «tcp 16-20»), CIDR-whitelist по подсетям назначения, SNI-whitelist (см. net4people/bbs #490). Поэтому всё ниже — снимок одной наблюдаемой волны на июнь 2026, а не универсальный закон. Конкретные числа читайте как «по наблюдениям автора», а не как точные константы системы.


TL;DR — если совсем коротко

Летом 2026 года перестала надёжно работать связка xray + VLESS + REALITY.

Важно: REALITY не взломали. Его шифрование по-прежнему неотличимо от настоящего TLS. Сломалось другое: DPI перестал заглядывать внутрь пакета (это бесполезно) и начал смотреть на поведение соединения снаружи.

Блокировка включается, только когда совпали сразу три признака (это важно — именно «И», а не «ИЛИ»). Нарушьте любой один — и правило на вас не сработает.


Объясняю на пальцах: что вообще произошло

Представьте таможню на границе (это наш DPI).

Раньше таможенник открывал каждый чемодан и смотрел, что внутри. REALITY — это чемодан с идеальной маскировкой: снаружи и внутри он выглядит как чемодан обычного туриста, едущего на www.microsoft.com. Подделку не видно, потому что для этого нужен секретный ключ, которого у таможни нет. Поэтому годами всё работало: «внутри чисто — проходи».

Теперь таможенник перестал открывать чемоданы (всё равно бесполезно) и начал смотреть на поведение пассажира:

  • Откуда приехал? — если из района, где живут одни «контрабандисты» (популярные хостинги), это подозрительно.
  • Как выглядит? — если одет в форму, в которой обычно ходят нарушители (фингерпринт Chrome), это подозрительно.
  • Как себя ведёт? — если бегает туда-сюда через границу 10 раз в минуту (лавина соединений), это подозрительно.

И только когда все три совпали — пассажира «тормозят». Криптография тут уже ни при чём: ловят не содержимое, а манеру.


Алгоритм блокировки: три сигнала и один триггер

Цензор оценивает ClientHello — самый первый пакет TLS-рукопожатия — у каждого TLS-соединения. Среди прочего он смотрит на такие атрибуты:

Что оценивает цензор (из первоисточника)

  • IP-адрес сервера (точнее — его подсеть);
  • SNI (Server Name Indication — то самое замаскированное имя);
  • фингерпринт «браузера» (точнее — того, под кого мимикрируют; у REALITY это делает uTLS).

К этим статическим атрибутам добавляется ещё и поведение — частота и параллелизм соединений. «Заморозка» включается, только когда совпали сразу несколько признаков (логическое «И»): подозрительная подсеть + подозрительный фингерпринт + аномальная частота к одному SNI. Разберём их по очереди.

Почему сигналов «три», хотя атрибутов перечислено больше

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

Сигнал 1. Подсеть сервера — «откуда» вы подключаетесь

DPI смотрит на IP-адрес вашего сервера и проверяет, не входит ли он в список «подозрительных» подсетей. В этот список массово попали диапазоны популярных хостингов, в том числе российских:

  • Selectel
  • Яндекс.Облако

Логика простая: именно там дешевле и проще всего поднять свой узел, поэтому там их больше всего.

Важный нюанс: схема завязана не на конкретные IP отдельных узлов, а на целые подсети и даже автономные системы (AS).

Из первоисточника

Схема не завязана напрямую на конкретные IP промежуточных узлов (напр., ваших серверов). Это, в общем-то, неплохо — значит, узлы не улетели в некий чёрный список (особенно в свете того, что некоторые приложения «стучат»). Плохая новость в том, что затронуты целые подсети и даже AS, в т.ч. популярных российских дата-центров (Selectel, Яндекс.Облако и др.). Это заметно отличается от прежних методов, которые задевали только зарубежных провайдеров (tcp 16-20, aka l4-25, и пр.).

Что это значит на практике: ваш конкретный IP в чёрный список не попадает (его не «сожгли») — под подозрением сразу весь диапазон хостинга. Хорошая сторона — сменить IP в пределах того же провайдера не поможет, но и «гореть» вашему адресу не за что. Плохая — отсюда же берётся сопутствующий ущерб для легальных соседей по подсети (см. раздел про обычный Chrome ниже).

Что это значит для вас

Сервер в «народном» дата-центре — первый красный флаг. Чем менее предсказуемы провайдер и подсеть, тем лучше. Практику выбора см. в VPS.

Сигнал 2. TLS-фингерпринт клиента — «чем» вы подключаетесь

Когда ваша программа начинает TLS-соединение, она первым делом отправляет пакет ClientHello. В нём — список поддерживаемых шифров, расширений, их порядок и прочие технические мелочи. Этот набор у каждой реализации TLS свой, как почерк. По нему можно понять, какая именно программа сгенерировала пакет (подробнее — в разделе про uTLS ниже).

Как выглядит этот «почерк» на самом деле

Возьмём эталонный ClientHello из RFC 8448 — это пример прямо из стандарта, поэтому каждый байт можно сверить.

1. Текстовая форма — то, как пакет разбирает Wireshark/tshark (дерево полей):

TLSv1.3 Record Layer: Handshake Protocol: Client Hello
  Content Type: Handshake (22)               # 0x16
  Version: TLS 1.0 (0x0301)                  # версия RECORD-слоя (легаси, для совместимости)
  Length: 196
  Handshake Protocol: Client Hello
    Handshake Type: Client Hello (1)
    Length: 192
    Version: TLS 1.2 (0x0303)                # legacy_version — НЕ настоящая версия (см. ниже)
    Random: cb34ecb1 e78163ba 1c38c6da …     # 32 байта
    Session ID Length: 0
    Cipher Suites Length: 6
    Cipher Suites (3):
      TLS_AES_128_GCM_SHA256        (0x1301)
      TLS_CHACHA20_POLY1305_SHA256  (0x1303)
      TLS_AES_256_GCM_SHA384        (0x1302)
    Compression Methods: null (0)
    Extensions Length: 145
    Extension: server_name           → "server"            # SNI
    Extension: renegotiation_info
    Extension: supported_groups      → x25519, secp256r1, secp384r1, …
    Extension: session_ticket
    Extension: key_share             → x25519 (32-байтный публичный ключ)
    Extension: supported_versions    → TLS 1.3 (0x0304)     # ← НАСТОЯЩАЯ версия
    Extension: signature_algorithms  → ecdsa_secp256r1_sha256, …
    Extension: psk_key_exchange_modes
    Extension: record_size_limit

2. Байтовая форма — тот же пакет «на проводе» (hex с аннотациями):

16 03 01 00 c4                              TLS-запись: Handshake, ver 0x0301, длина 196
01 00 00 c0                                 ClientHello, длина 192
03 03                                       legacy_version = 0x0303 (TLS 1.2)
cb 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19   ┐
6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83   │ Random (32 байта)
02 4d ec e7                                 ┘
00                                          Session ID length = 0
00 06  13 01 13 03 13 02                    Cipher Suites (6 б): 1301 1303 1302
01 00                                       Compression: 1 метод = null
00 91                                       Extensions, длина = 145 байт
 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72   server_name → "server"
 ff 01 00 01 00                                 renegotiation_info
 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 …    supported_groups
 00 23 00 00                                    session_ticket
 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 …    key_share (x25519)
 00 2b 00 03 02 03 04                           supported_versions → 0x0304 (TLS 1.3)
 00 0d 00 20 00 1e 04 03 05 03 …                signature_algorithms
 00 2d 00 02 01 01                              psk_key_exchange_modes
 00 1c 00 02 40 01                              record_size_limit

Фингерпринт — это набор и порядок вот этих cipher suites и extensions. Заметьте: 73 65 72 76 65 72 в hex — это ASCII-строка server (тот самый SNI, видный открытым текстом). Реальный Chrome выглядел бы иначе: GREASE-значения, расширения ALPN и ECH, post-quantum key_share (X25519MLKEM768), другой порядок полей. Именно этот «слепок» и сворачивают в JA3/JA4 (см. раздел про uTLS), по нему DPI и опознаёт, кто перед ним.

Мелкий, но важный нюанс про версию

Обратите внимание: в поле версии стоит 0x0303 (TLS 1.2), хотя это TLS 1.3. Так сделано нарочно — ради совместимости со старыми серверами. Настоящую версию несёт расширение supported_versions (0x0304). Поэтому JA4 берёт версию TLS именно оттуда, а не из легаси-поля.

И вот ловушка: xray умеет притворяться чужим почерком, и по умолчанию почти все ставили Chrome — он самый популярный, прятаться логичнее в толпе. В итоге сочетание «почерк Chrome + признаки прокси» само стало маркером.

🚩 Подозрительные фингерпринты✅ «Лояльные» фингерпринты
ChromeFirefox
SafariEdge
iOSAndroid OkHttp
360 Browser
QQ Browser

Парадокс

Самый популярный браузер планеты оказался самым «палевным» — именно потому, что под него массово маскируются. Безопаснее то, под что маскируются реже.

Сигнал 3. Частота и параллелизм — «как» вы подключаетесь

Третий признак — поведенческий, и именно он чаще всего вас и выдаёт. Точная формулировка триггера из первоисточника:

более 3 параллельных попыток установить TLS-соединение, с задержкой между ними менее ~350-400 мс, за последние 60 секунд — к одному и тому же SNI.

По этому порогу гуляют устаревшие цифры

Первоисточник правился трижды и без пометок: 6 июня 2026 там стояло «~100 мс», 7 июня — «~20-50 мс», и лишь 9 июня появилось нынешнее «~350-400 мс», которое держится с тех пор. Промежуточный вариант успел разойтись по перепечаткам и попал в англоязычный пересказ в обсуждении Xray-core #6293, снятый за сутки до правки. Если встретите «20-50 мс» — это старый срез. Актуальное значение подтверждается и свежим предложением #6547 от 27 июля 2026, где параметр hMinSocketInterval предлагают выставлять в 400–600 мс именно с запасом над этим порогом.

Разберём по словам:

  • более 3 соединений — то есть 4 и больше;
  • задержка между ними < ~350-400 мс — то есть они идут залпом, почти одновременно;
  • за последние 60 секунд — окно, в котором DPI это считает;
  • к одному SNI — все на одно и то же замаскированное имя (например, www.microsoft.com).

Откуда берётся такой залп? Это типичный почерк прокси. Вы открываете браузер, в нём десяток вкладок — и каждая инициирует своё соединение. Все они мультиплексируются на один замаскированный SNI. Живой человек, реально открывший www.microsoft.com, так себя не ведёт — он не создаёт 10 одновременных коннектов на этот домен.

Что это значит для вас

Лавина одновременных соединений на один SNI — поведенческая аномалия. Лечится мультиплексированием (mux) и распределением по разным SNI (см. раздел про mux ниже).

Что происходит при срабатывании

Когда совпали все три условия, трафик к узлу «замораживается» на 120 секунд. Важная деталь: соединения не рвутся демонстративно (это было бы слишком явной цензурой) — они тихо деградируют. Растут таймауты, падает скорость, всё «как будто само тормозит». Внешне похоже на плохой интернет, а не на блокировку.

          ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
  TLS-     │ подсеть в    │ И │ фингерпринт  │ И │ >3 conn/SNI  │  ➜  ЗАМОРОЗКА
соединение │ списке?      │   │ подозрит.?   │   │ <~350-400мс/60с│   120 сек
  ───────► │ (Selectel,   │   │ (Chrome,     │   │              │
          │  Я.Облако…)  │   │  Safari,iOS) │   │              │
          └──────┬───────┘   └──────┬───────┘   └──────┬───────┘
                 │                  │                  │
         разорвите ЛЮБОЕ одно звено ➜ правило на вас НЕ срабатывает

Почему обычный Chrome работает, хотя «лояльным» его не назвали

Здесь возникает резонный вопрос: если фингерпринт Chrome — «подозрительный», почему миллионы людей спокойно сидят в обычном Chrome и ничего не тормозит? А заодно — почему при этом у части людей вдруг ломаются некоторые российские сайты?

Ответ — снова в логике «И трёх условий». Вспомните: чтобы поймать заморозку, нужны все три сигнала одновременно. У обычного веб-сёрфинга почти всегда не выполняется первый — про подсеть.

Почему Chrome обычно работает. Когда вы просто листаете интернет, вы ходите на реальные сервисы — Google, YouTube, банки, маркетплейсы. Они живут на собственной инфраструктуре и больших CDN, которые в список «подозрительных подсетей» не входят (а часто и вовсе в белых списках). Получается:

  • 🚩 Фингерпринт Chrome — да, подозрительный (Сигнал 2 ✅);
  • лавина параллельных соединений — да, браузер их открывает (Сигнал 3 может сработать);
  • но подсеть назначения — чистая (Сигнал 1 ❌).

Одно звено цепи разорвано → правило не срабатывает. Именно поэтому Chrome для обычного веба «прозрачен»: его спасает не фингерпринт, а адрес назначения. А вот ваш личный VLESS-сервер на Selectel/Яндекс.Облаке этот первый сигнал как раз включает — отсюда и разница.

Почему всё-таки ломаются некоторые ру-сайты. Это оборотная сторона грубой эвристики «по подсети». В списки подозрительных попали диапазоны российских хостингов (Selectel, Яндекс.Облако), а на этих же хостингах живёт масса легальных сайтов и сервисов — тех, кто просто арендует там сервер. DPI не умеет отличить «прокси на Selectel» от «интернет-магазина на Selectel» — для него это одна подсеть.

И если вы в обычном Chrome заходите на такой легальный сайт, расположенный на «помеченном» хостинге, может совпасть всё три:

  • сайт на подозрительной подсети (Сигнал 1 ✅);
  • вы в Chrome (Сигнал 2 ✅);
  • браузер открыл несколько быстрых параллельных соединений к его домену (Сигнал 3 ✅).

→ Получаем ложное срабатывание: легальный сайт начинает тормозить, хотя никто ничего не обходил. Это и есть «сопутствующий ущерб» (collateral damage) схемы: блокировка по подсети неизбежно задевает добросовестных соседей по хостингу.

Коротко

Обычный Chrome спасает чистая подсеть назначения, а не сам браузер. Где подсеть «грязная» (ваш VPS или легальный сайт на том же хостинге) — там при Chrome и залпе соединений ловят всех подряд, включая невиновных.


Интересный нюанс: ловушка для тех, кто «дёргается»

Самая хитрая часть схемы — реакция на попытку быстро поменять настройки.

Допустим, вас «заморозили». Вы решаете: «наверное, дело в фингерпринте» — и тут же переключаете Chrome на Firefox, переподключаясь. Логично же? Но для DPI это отдельный сигнал: только что соединение получило деградацию — и человек моментально сменил TLS-почерк. Так ведёт себя именно тот, кто осознанно обходит ограничение.

В ответ прилетает расширенная блокировка на 600 секунд — и уже на все TLS-соединения к этому узлу. Важно: на эти 10 минут замораживается любой TLS, независимо и от фингерпринта, и от SNI — сменить почерк или замаскированное имя уже не поможет. Сам TCP-коннект при этом проходит (рукопожатие на уровне TCP устанавливается), душится именно TLS поверх него.

Мораль

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


🔬 Технические детали: uTLS — как работает TLS-почерк

Раздел для тех, кто хочет понять механику, а не просто поставить галочку.

Что такое JA3/JA4 и почему по TLS видно программу

ClientHello — это первый, ещё не зашифрованный пакет TLS-рукопожатия. В нём открытым текстом передаются:

  • версия TLS;
  • список cipher suites (наборов шифров) — и в каком порядке;
  • список extensions (расширений: SNI, ALPN, supported_groups, signature_algorithms, key_share и т.д.) — и в каком порядке;
  • эллиптические кривые, форматы точек, GREASE-значения и пр.

Конкретная программа (Chrome, Firefox, Go-приложение, curl) собирает этот набор по-своему: свой порядок шифров, свой набор расширений. Если взять все эти поля и прогнать через хеш, получится отпечаток. По нему DPI с высокой точностью говорит: «это Chrome 124» или «это Go-клиент».

Два основных формата отпечатков:

  • JA3 (старый) — это MD5-хеш от полей ClientHello, взятых в том порядке, в каком они идут в пакете. Минус: Chrome специально тасует порядок расширений от соединения к соединению, поэтому JA3-хеш у него «плавает» и легко обходится.

  • JA4 (новый, более устойчивый) — как раз поэтому он сортирует шифры и расширения перед хешированием и игнорирует GREASE. Это не один хеш, а строка из трёх частей через _ (a_b_c):

    • a (читаемая): протокол (t — TLS-over-TCP, q — QUIC, d — DTLS), версия TLS, есть ли SNI (d = domain / i = IP), число шифров, число расширений и ALPN (первый и последний символ значения первого ALPN, напр. h2);
    • b: усечённый SHA256 от отсортированных cipher suites;
    • c: усечённый SHA256 от отсортированных extensions (без SNI и ALPN), за которыми идут signature algorithms — уже без сортировки.

    За счёт сортировки шифров и расширений JA4 не «обманывается» их перетасовкой; при этом signature algorithms намеренно оставлены несортированными как дополнительный различающий признак.

Вот в чём беда TLS-обхода на Go: стандартная библиотека crypto/tls имеет очень узнаваемый фингерпринт — это прямо признают сами разработчики uTLS («Golang’s ClientHello has a very unique fingerprint»). В реальном вебе такой почерк почти не встречается → мгновенный маркер прокси. Поэтому обычный VLESS/Trojan поверх TLS, если не указать fingerprint, спалится сразу.

Контраст «почерков»: голый Go crypto/tls vs Chrome

Чтобы было видно, насколько они различаются — ключевые отличия в ClientHello (структурно; точные байты зависят от версии):

Признак🦫 Голый Go crypto/tls🌐 Chrome 131+
GREASEнет нигдев 5 местах: первый шифр, первое и последнее расширение, supported_versions, supported_groups, key_share
Порядок расширенийжёстко фиксировантасуется на каждом соединении (с Chrome 110, янв. 2023)
application_settings (ALPS)нетесть
compress_certificate (brotli)нетесть
ECH (encrypted_client_hello)нет (по умолчанию)есть (GREASE-ECH или настоящий)
Число расширений~10–12~17–18
Cipher list13 шифров, сразу с TLS 1.3 AEADначинается с GREASE, характерная браузерная раскладка

Любого из верхних трёх отличий (нет GREASE, фиксированный порядок, нет ALPS/brotli/ECH) уже достаточно, чтобы пассивно отличить Go-клиент от Chrome. Поэтому «голый» Go без uTLS — мгновенный маркер. (Нюанс актуальности: post-quantum key_share X25519MLKEM768 Chrome шлёт с v131, а Go — только с 1.24; на старых версиях Go отсутствие PQ было ещё одним отличием.)

Важный нюанс про REALITY

В REALITY uTLS встроен и обязателен: поле fingerprint помечено как Required, а режим отключения uTLS (unsafe) официально не поддерживается — REALITY использует эту библиотеку для манипуляции низкоуровневыми параметрами TLS. Поэтому «голый» crypto/tls-почерк от REALITY никогда не уходит — он всегда отправляет валидный фингерпринт браузера. Проблема REALITY в июне 2026 — не в Go-почерке, а в том, что почерк хоть и корректный, но слишком массовый (chrome). Об этом — ниже.

Что делает uTLS

uTLS (refraction-networking/utls) — это форк стандартного crypto/tls, который позволяет подменить содержимое ClientHello: порядок шифров, набор расширений, GREASE — по готовому шаблону настоящего браузера. То есть xray генерирует ClientHello, неотличимый от Chrome или Firefox, и JA3/JA4-хеш совпадает с настоящим браузером. (Технически uTLS работает на уровне готовых пресетов-спецификаций; полный побайтовый контроль возможен, но в обычном режиме хватает пресета, чтобы хеши совпали.)

В конфиге xray это поле fingerprint внутри realitySettings. Кстати, поле с публичным ключом сервера в актуальных версиях xray-core называется password (раньше — publicKey; старое имя пока работает для совместимости). uTLS поддерживает пресеты вроде HelloChrome_Auto, HelloFirefox_Auto, HelloEdge_Auto, HelloSafari_Auto, HelloRandomized и др. В xray они задаются короткими именами: chrome, firefox, edge, safari, ios, android, 360, qq, random, randomized. То есть рекомендованные в таблице выше «лояльные» 360 Browser и QQ Browser пишутся как "fingerprint": "360" и "fingerprint": "qq".

"realitySettings": {
  "fingerprint": "firefox",
  "serverName": "www.microsoft.com",
  "password": "<публичный ключ сервера>",
  "shortId": "<short id>"
}

Почему это перестало спасать само по себе

uTLS делает почерк корректным, но не делает его редким. Если все массово ставят chrome, то «JA3 Chrome + поведение прокси» становится статистически заметным. DPI не говорит «фингерпринт поддельный» (он валидный!) — он говорит «слишком много именно такого фингерпринта летит на этот странный сервер залпами». Поэтому в июне 2026 совет сместился: брать не самый массовый профиль — firefox/edge и пр.

Про random и randomized — это РАЗНЫЕ режимы

Их легко перепутать, но ведут себя они по-разному:

  • random — xray случайно берёт один из готовых реальных пресетов браузера (Chrome 131, Firefox 148 и т.п.). Почерк всегда валидный — это безопасный вариант.
  • randomizedrandomizednoalpn) — синтетическая генерация: расширения и шифры добавляются/тасуются случайно. Может получиться комбинация, которой не существует ни у одного реального браузера — а это само по себе аномалия.

Вывод: бездумно ставить randomized не стоит. Лучше явный «лояльный» пресет (firefox/edge) или, на худой конец, random — но не синтетическую рулетку.

uTLS не маскирует тайминги

Ключевой момент: uTLS правит только содержимое ClientHello. Он никак не влияет на Сигнал 3 — на то, сколько соединений и с какой частотой вы открываете. Почерк может быть идеальным, но если вы шлёте на один SNI залп коннектов с интервалом между ними менее ~350-400 мс — вас всё равно поймают по поведению. Вот почему одного uTLS мало, и нужен mux.


🔬 Технические детали: mux — как сгладить лавину соединений

mux (мультиплексирование) — это прямое лекарство от Сигнала 3.

Проблема, которую он решает

Без mux каждая вкладка / каждое приложение = отдельное физическое TCP+TLS-соединение до сервера. Открыли почту, мессенджер, пару сайтов — и вот уже залп одновременных коннектов на один замаскированный SNI. Ровно тот паттерн, который ловит DPI.

Как работает mux в xray

Мультиплексирование загоняет много логических потоков внутрь одного физического соединения. Снаружи DPI видит одно TLS-соединение, по которому идёт постоянный поток данных, — как у обычного пользователя, открывшего тяжёлый сайт. Залпа новых рукопожатий нет → Сигнал 3 не срабатывает.

"mux": {
  "enabled": true,
  "concurrency": 8,
  "xudpConcurrency": 16,
  "xudpProxyUDP443": "reject"
}

Разберём параметры:

  • concurrency — сколько логических потоков пускать в одно физическое соединение (для TCP). Допустимый диапазон — 1…128, по умолчанию 8. Когда лимит исчерпан, открывается новое физическое соединение. Любое отрицательное значение (-1) отключает mux для TCP.
  • xudpConcurrency — то же самое, но для UDP-трафика поверх механизма XUDP (нужно для игр, звонков, QUIC). Диапазон у него другой1…1024 (а не 1…128, как у TCP); при 0 или отсутствии XUDP-трафик идёт по тому же пути, что и обычный TCP.
  • xudpProxyUDP443 — что делать с UDP на 443-м порту (это QUIC/HTTP3). Часто ставят reject, чтобы QUIC падал обратно на TCP — так трафик идёт по нашему замаскированному каналу, а не утекает мимо.

У mux есть и обратная сторона

  • Head-of-line blocking: все логические потоки делят одно TCP-соединение. Потеря одного пакета тормозит сразу все потоки внутри. На нестабильном канале это ощутимо бьёт по скорости.
  • Слишком большой concurrency склеивает весь трафик в один долгоживущий коннект — тоже своего рода аномалия (реальные браузеры всё же открывают несколько соединений). Истина посередине: умеренные значения вроде 4–8.
  • Для скоростных загрузок mux иногда наоборот мешает — один коннект упирается в лимиты. Поэтому его часто включают выборочно.

mux и XTLS Vision: TCP-mux не применяется

Это важно именно для нашей связки. Сейчас стандартный поток для VLESS+REALITY — XTLS Vision ("flow": "xtls-rprx-vision"). Vision работает на «сыром» (raw) TCP-соединении со splice-копированием в ядре Linux, поэтому TCP-mux при Vision штатно не применяется — это поведение самого Xray-core, а не сбой (в формулировке ядра: «MUX is not compatible with XTLS raw connections»).

На практике клиенты при Vision генерируют такой блок: "enabled": true, "concurrency": -1 (TCP-mux выключен), но "xudpConcurrency" оставлен — чтобы мультиплексировать хотя бы UDP/XUDP. Учтите: при "enabled": false XUDP-mux тоже не активируется.

Вывод: не копируйте "concurrency": 8 бездумно поверх Vision-конфига. Если у вас Vision, бороться с Сигналом 3 придётся другими средствами — в первую очередь разнесением по разным SNI и сдержанным паттерном соединений.

Дополнительно: разносите SNI

Триггер Сигнала 3 считается на один SNI. Если раскидать трафик по нескольким разным SNI (см. большой список в vless-sni), нагрузка не концентрируется на одном имени, и порог «>3 соединений с интервалом <~350-400 мс за 60 с» на каждое отдельное имя достигается труднее. mux + несколько SNI вместе очень эффективно сглаживают поведенческий след.


✅ Что делать: рвём цепочку (по шагам)

Поскольку триггер — это «И» трёх условий, для безопасности достаточно стабильно нарушать хотя бы одно звено. На практике стоит закрыть несколько — для запаса прочности.

Шаг 1. Сменить фингерпринт на «лояльный»

Самое дешёвое действие. В realitySettings поменять fingerprint с chrome на firefox или edge:

"realitySettings": {
  "fingerprint": "firefox"
}

Шаг 2. Включить mux и снизить параллелизм

Сглаживаем залп соединений:

"mux": {
  "enabled": true,
  "concurrency": 8
}

И дополнительно — разнести трафик по нескольким SNI (список SNI).

Если у вас XTLS Vision

При стандартном для REALITY flow xtls-rprx-vision TCP-mux не применяется (см. раздел про mux и Vision выше), так что concurrency: 8 тут не сработает. Тогда против Сигнала 3 работают только разные SNI + сдержанный паттерн, либо переход на XHTTP+XMUX (см. FAQ «Можно ли вылечить обе болезни сразу?»).

Шаг 3. Увести сервер в «невидимую» подсеть

Уходим из массовых диапазонов народных хостингов (Selectel, Яндекс.Облако и т.п.). Менее предсказуемый провайдер/подсеть → выпадаем из списка «подозрительных». Подбор — в VPS.

Шаг 4. Не дёргаться под нагрузкой

Поймали деградацию — не меняйте фингерпринт на лету. Лучше переждать окно (120 с) или сменить узел целиком. Оговорка по актуальности: длинную десятиминутную блокировку автор наблюдений в обновлении от 16 июня 2026 отметил как, судя по всему, убранную — но осторожность здесь всё равно дешевле эксперимента.

«Смени фингерпринт» — не единственный и не универсальный рецепт

Совет про firefox/edge — снимок одной волны, и он не гарантирован. Что важно держать в голове:

  • Свежесть пресета uTLS важнее выбора браузера. К концу 2025 — началу 2026 более половины «человеческих» браузерных соединений (по данным Cloudflare Radar) несут post-quantum key_share (X25519MLKEM768) — он стал дефолтом в Chrome 131 (ноя 2024) и Firefox 132 (окт 2024). Пресет без него, выдающий себя за свежий браузер, уже сам по себе аномален — следите, чтобы uTLS/xray были свежими.
  • Не путайте с обходами ДРУГИХ блокировок. Советы «пустой SNI» и «нестандартный порт (47000+)» относятся к более ранней сигнатурной блокировке на порту 443 (нач. 2025) и к ограничению по числу TLS-соединений — «сибирскую» схему (подсеть + фингерпринт + частота) они логически не нарушают. XHTTP помогает лишь как способ снизить число соединений (это уже делает mux), а Shadowsocks-2022 — не обход, а смена протокола (у него нет ни SNI, ни uTLS-почерка браузера). Столкнулись именно с этой схемой — работает только нарушение её трёх условий.

Проверить себя: dpi-checkers

Автор первоисточника ведёт инструмент dpi-checkers (на момент разбора — версия v0.7.0). Он прогоняет вашу инфраструктуру по описанным критериям: попадает ли подсеть в подозрительные, как выглядит фингерпринт, ловится ли поведенческий паттерн. Проверки задаются в YAML, например фильтр по организации хостинга:

checkers:
  webhost:
    infra:
      - name: Selectel
        filter: org("selectel")

❓ Слабые места и частые вопросы

Несколько вопросов, которые закономерно возникают после прочтения.

Фиксированный список фингерпринтов uTLS — это слабое место?

Да. firefox, который у вас «снова заработал», — это снимок конкретной версии (например HelloFirefox_120). И его тоже могут начать ловить. Почему список — структурная слабость:

  • Устаревание. Пресет заморожен во времени, а живой браузер обновляется каждые недели. HelloChrome_120 со временем становится редкой старой версией на фоне Chrome 13x — особенно теперь, когда у свежих браузеров появился post-quantum key_share, которого у старого пресета нет.
  • Перечислимость. Список открытый и небольшой (~50 пресетов в u_common.go). Цензор может просто выписать все JA3/JA4 пресетов uTLS и сверять с распределением реальных браузеров.
  • Несовершенство «попугая». Даже совпав по версии, uTLS иногда отличается в деталях — вплоть до уязвимостей с CVE: например рассогласование GREASE/ECH-шифров в Chrome-пресете давало комбинацию, невозможную у настоящего Chrome (CVE-2026-27017, закрыто в uTLS 1.8.1).

Что вас пока спасает: заблокировать популярный валидный фингерпринт целиком цензору дорого — сломаются реальные пользователи Firefox/Chrome. Слабость возникает, когда отпечаток одновременно коррелирует с прокси и достаточно редкий, чтобы блок был «дешёвым».

Можно отсканировать свой реальный браузер и залить отпечаток в uTLS-клиент?

И да, и нет — важно разделить библиотеку и конечный клиент:

  • В библиотеке uTLS — можно. Fingerprinter.FingerprintClientHello(rawBytes) берёт сырые байты реального ClientHello (захваченные Wireshark’ом — не JA3-хеш, он «лоссовый»), строит ClientHelloSpec, применяемый через HelloCustom.
  • В xray-core и sing-box — НЕЛЬЗЯ. Оба ограничены меню пресетов; подсунуть свой захваченный ClientHello в конфиг нельзя. Это просили напрямую — xray-core issue #1782 закрыт как «not planned».
  • Сканеры своего браузера живы: browserleaks.com/tls (самый подробный), tls.peet.ws (JSON-API). Но они выдают хеши/сводку, а не raw-байты, нужные Fingerprinter.

Итого: готового пайплайна для обычного пользователя нет — это ручная работа разработчика (pcap → FingerprintClientHello → свой Go-код). В стандартном клиенте доступны только пресеты + random/randomized.

Парадокс уникальности

Если зальёте свой уникальный отпечаток — он станет маркером именно вас. Сила браузерного фингерпринта в том, что он массовый; уникальный — наоборот, деанонимизирует.

Альтернатива uTLS-пресетам: реальный стек Chromium (naive/cronet)

Раз uTLS — это попугай (заморожённый пресет, который перечислим и со временем протухает, а ручной скан своего браузера в стандартный клиент не залить), напрашивается другой путь: не имитировать браузер, а взять настоящий сетевой стек браузера. Тогда JA3/JA4 — не копия, а подлинник, и обновляется он вместе с браузером.

Это удобно представить как лестницу аутентичности почерка (от худшего к лучшему):

ПодходПочеркСвежестьМультиплексирование
uTLS-пресет (chrome/firefox)копия, перечислимаяпротухает (нужно ждать новый пресет)нет (нужен отдельный mux)
NaiveProxy (Chromium //net/cronet)подлинный стек Chromiumотстаёт: автор вручную линкует свежий хромиум; на момент обсуждения был Chrome 149, а Naive — на сборке ~148.xда, естественный HTTP/2
XHTTP + Browser Dialer (Xray)подлинный — TLS делает реально установленный в системе браузермоментальная: берётся любой обновлённый браузер из системыда, в паре с XMUX (maxConnections)

Закрывает сразу две оси — но двумя рычагами, не «одной галочкой»

Подлинный стек браузера даёт массовый настоящий фингерпринт — это Сигнал 2 (дорого банить, не «протухает», как пресет). А вот поведенческую ось (Сигнал 3) закрывает не сам почерк, а транспорт: HTTP/2 + XMUX. У NaiveProxy мультиплексирование нативное (HTTP/2 в cronet); у Browser Dialer его нужно включить отдельноXMUX/maxConnections в XHTTP, само по себе оно не появляется. То есть это два рычага в связке (подлинный почерк + mux), а не один инструмент «из коробки» — зато оба живут в одном клиенте, тогда как «uTLS + отдельный mux» собираются из более хрупких частей. Про поведенческую ось — см. раздел про mux в этой заметке; XMUX в xhttp придумали ещё до «сибирской» схемы — изначально ради пулинга соединений под CDN, но как побочный эффект это и сглаживает лавину (Сигнал 3).

Нюансы (по сообщениям сообщества, не как гарантия):

  • NaiveProxy всё ещё не свободен от устаревания: cronet-стек влинкован под конкретную сборку Chromium, и пока автор не пересоберёт под новый — поведение свежего Chrome может отличаться в деталях. То есть Naive уменьшает разрыв с реальным браузером, но не обнуляет его.
  • Browser Dialer использует уже установленный браузер, поэтому переход на новую версию мгновенный и попугайства нет вообще — но цена в том, что нужен живой браузер/вкладка-«диалер» рядом с клиентом (UX-издержка); по сообщениям, на Android рабочий вариант.
  • Это клиентский путь: почерк генерится на устройстве пользователя, до цензора — ровно то, о чём «JA4 и SNI меняет только клиент». Серверная сторона тут по-прежнему ничего не решает.

Не «серебряная пуля»

Подлинный почерк снимает только Сигнал 2 (фингерпринт) и, через XMUX/HTTP-2, Сигнал 3 (частоту). Сигнал 1 (подсеть VPS) остаётся — его убирает только CDN-фронтинг (см. ниже «Что реально помогает против сигнала подсети»). И сам по себе подлинный стек не прячет TLS-в-TLS — это отдельная ось (Vision/padding).

Имеет ли смысл держать на VPS свою страничку и использовать её как SNI?

Для REALITY — нет, это ломает саму идею. REALITY не использует ваш домен — он заимствует TLS-личность чужого популярного сайта: вы указываете target/dest = реальный внешний сайт, REALITY ретранслирует к нему рукопожатие, и наблюдатель видит настоящий сертификат этого сайта. Своя страничка как SNI:

  • теряет заёмную репутацию (а это весь смысл REALITY) — неизвестный myhomepage.tld репутации не имеет;
  • не входит в SNI-whitelist — при белых списках неизвестное имя подозрительнее знаменитого;
  • концентрирует частоту — весь трафик на один свой SNI = ровно та аномалия «много к одному редкому имени», которую ловит Сигнал 3.

Decoy-сайт (своя страница + nginx-fallback) — это про классическую маскировку (Trojan / VLESS + реальный сертификат) и помогает он против активного зондирования, а не против этой пассивной схемы. Если уж REALITY напрямую на VPS — берите чужой знаменитый SNI, не свой.

Не устарел ли сам REALITY? (сверка пары SNI↔IP)

Отдельный довод, который звучит у практиков: REALITY становится уязвим к сверке пары SNI↔IP. Вся идея REALITY — заимствовать чужой знаменитый SNI (например www.microsoft.com), но соединение при этом идёт на ваш VPS-IP, а не на реальные адреса Microsoft. Пассивный цензор, ведущий карту «какой SNI обычно ходит на какие IP/ASN», может ловить рассогласование: в ClientHello заявлен www.microsoft.com, а пакет летит в случайную подсеть хостинга. Активное зондирование тут ни при чём — это пассивная проверка, и фирменная защита REALITY «отдать настоящий серт на пробу» от неё не спасает (это другая ось).

«REALITY бесполезен» — это гипербола

Сверка SNI↔IP дорога цензору: легитимных рассогласований масса (CDN, ECH, сплит-DNS, корпоративные прокси), поэтому массовая проверка даёт ложные срабатывания на обычном трафике — тот же «сопутствующий ущерб», что и с подсетями. Корректнее формулировать как «вектор реален и REALITY слабеет», а не «мёртв». И главное: это мнение/прогноз практиков, а не подтверждённая часть «сибирской» схемы — в первоисточнике три сигнала (подсеть, фингерпринт, частота), сверки SNI↔IP там нет.

Это родственная логика той, что осторожно отмечена для MTProto в проверке SNI на резолвимость: в обоих случаях цензор сверяет заявленное имя с реальностью соединения. Но сами проверки разные: для MTProto — резолвится ли SNI вообще (есть ли A-запись); для REALITY — совпадает ли SNI с IP назначения. И там, и там это пока гипотеза о развитии DPI, а не зафиксированное правило.

И ещё довод против «фирменности» REALITY: его защита от активного зондирования — это fallback (редирект неаутентифицированных проб на dest-бэкенд, «spider mode»), и поведенчески она воспроизводится десятком строк в caddy/nginx (обычный TLS + fallback на реальный сайт). То есть по-настоящему уникальна у REALITY именно заёмная личность чужого удалённого сайта — а её как раз и подтачивает сверка SNI↔IP. (Для ясности: self-steal — это не защита, а топология REALITY, где dest/target смотрит на свой локальный decoy, т.е. ровно тот самый «своя страница + nginx-fallback», только средствами REALITY — противоположность заимствованию чужого сайта.) При этом против самого опасного, Сигнала 1 (подсеть), REALITY не помогал никогда — поэтому общий вывод не меняется: структурный рычаг — CDN-фронтинг (см. ниже), а не выбор «магического» транспорта.

Что реально помогает против сигнала «подозрительная подсеть»?

Все локальные меры (свежий пресет, mux, разные SNI, выбор провайдера) не убирают Сигнал 1 — подсеть VPS остаётся подозрительной. Единственный рычаг, который структурно его убирает, — спрятать назначение за большим CDN:

  • VLESS over XHTTP / WebSocket за Cloudflare. Цензор видит соединение к IP Cloudflare (огромный, де-факто whitelisted), а не к вашему VPS — сигнал подсети исчезает, потому что меняется сам адрес, который видит цензор.
  • Цена честно: CDN терминирует ваш TLS и видит трафик в открытом виде; медленнее (крюк через edge); ToS-серая зона для тяжёлого/видео-трафика.
  • Важно: сам REALITY за CDN так не ставится — ради CDN меняют транспорт (XHTTP/WS).

Что от чего помогает: active probing vs пассивная схема

МераПротив active probingПротив пассивной схемы (подсеть + фингерпринт + частота)
REALITY со знаменитым чужим SNI⚠️ частично — знаменитый SNI размывает частоту, но подсеть не меняется
REALITY со своим decoy-SNI❌ хуже — редкий SNI концентрирует трафик; подсеть та же; не в whitelist
Классика VLESS/Trojan + свой серт + nginx-fallback⚠️ частично❌ — свой домен + та же подсеть
CDN-фронтинг (VLESS+XHTTP/WS за Cloudflare)единственный рычаг, убирающий сигнал подсети (цена: CDN видит трафик, медленнее, ToS)

Прямое подключение vs каскад — спасает ли это от Сигнала 1?

Короткий ответ: нет — решает не топология, а флагнутость подсети первого хопа.

Сигнал 1 смотрит не на вашу домашнюю подсеть (residential-IP в РФ), а на подсеть назначения того соединения, которое видит DPI — то есть на первый хоп, сервер, к которому вы открываете TLS.

  • Прямое юзер → eu-нода. DPI видит соединение к зарубежному серверу. И зарубежные хостинги тоже в списке подозрительных — более того, они там были изначально: старые методы (tcp 16-20 / l4-25) били только по зарубежным провайдерам (Hetzner, DigitalOcean, OVH). Новизна «сибирской» схемы — что к ним добавили российские ДЦ, а не заменили. Так что прямое подключение к VPS на Hetzner/DO спокойно попадает под Сигнал 1.
  • Каскад юзер → ru-нода → eu-нода. DPI видит вход — ru-ноду. Если она на Selectel/Яндекс.Облаке, это теперь тоже подозрительная подсеть, то есть каскад через российский ДЦ стало только хуже. (Хоп ru→eu — исходящий трафик сервера, ваш DPI его как клиентское рукопожатие не оценивает.)

Сигнал 1 не срабатывает не «при прямом» и не «при каскаде», а когда подсеть первого хопа вообще не в списке: residential/мобильные IP, большой CDN (поэтому CDN-фронтинг и есть структурный рычаг), редкий «чистый» провайдер. Hetzner напрямую и Selectel через каскад «горят» одинаково; residential или CDN — нет, в любой топологии.

Оговорка

Точный состав списка подозрительных подсетей публично неизвестен (реверс-инжиниринг одного автора). Вывод «зарубежные тоже в списке» следует из формулировки первоисточника («затронуты подсети/AS, в т.ч. российских ДЦ, в отличие от методов, бивших только по зарубежным»), а не из опубликованной спецификации.

Разве XTLS Vision не маскирует большое количество TLS-соединений?

Частая путаница. Нет — Vision маскирует не количество соединений, а форму TLS-в-TLS внутри одного соединения.

  • Vision (xtls-rprx-vision) решает проблему «TLS-in-TLS detection»: когда прокси несёт внутренние TLS-записи внутри внешнего TLS, возникает узнаваемый вложенный паттерн (характерные размеры записей). Vision добавляет padding в раннюю фазу рукопожатия и затем переключается на прямое splice-копирование в ядре — внешне трафик перестаёт выглядеть как «TLS в TLS».
  • Но Vision работает по-соединению и ничего не объединяет. Каждое проксируемое соединение — это всё равно свой отдельный внешний TLS+TCP-коннект к серверу. Браузер открыл 10 вкладок → 10 внешних TLS-соединений на один SNI. Это ровно территория Сигнала 3.
  • Объединяет много логических потоков в одно физическое соединение — это mux, а не Vision. И именно поэтому они конфликтуют (см. раздел про mux выше): Vision требует «сырого» TCP под splice, а mux оборачивает поток.

Вывод: Vision прячет вложенность TLS, но не уменьшает число соединений. Против поведенческого Сигнала 3 он не помогает — это задача mux (несовместимого с Vision) либо разнесения по разным SNI и сдержанного паттерна.

Именно из этой несовместимости вырос популярный слух «в России без flow работает лучше»: у части домашних провайдеров с ноября 2025 года помогала связка «пустой flow + mux», где отказ от Vision был техническим условием для mux, а не самостоятельным обходом. Разбор случая с первоисточниками — в карте слоёв VLESS-стека, раздел про пустой flow.

Vision, mux и «TLS-в-TLS» — три вещи, которые путают

Самый запутанный узел. Тут сходятся три независимых понятия — разведём их:

ОсьЧто этоЧем управляется
Security (reality/tls)чьей TLS-личностью представляется внешний туннельвыбор security
TLS-в-TLSвложенность: сайт-TLS внутри туннелявозникает сам при HTTPS-сёрфинге → лечит Vision
Число внешних соединенийсколько отдельных TCP+TLS к серверуmux (вкл/выкл) → считает Сигнал 3

Главное: reality vs tls НЕ решает «отдельные коннекты или один поток» — это решает mux. И reality, и tls по умолчанию дают одно внешнее соединение на коннект и оба несут TLS-в-TLS.

К этим трём осям с конца августа 2025 добавилась четвёртая — encryption, то есть шифрование самого протокола VLESS. Маскировку она не заменяет: её собственный внешний вид спрятан внутри туннеля, а снаружи наблюдатель видит всё тот же внешний слой. Зато она снимает старое ограничение «Vision только на прямом TCP» и даёт настраиваемую набивку рукопожатия — RPRX в ноябре 2025 предлагал вложить её внутрь REALITY именно как способ поменять признаки трафика без правки кода. Полная матрица сочетаний — в карте слоёв VLESS-стека.

Что такое TLS-в-TLS (с этим борется Vision). Открываете https://google.com через туннель — получаются два вложенных слоя TLS:

[ внешний TLS / REALITY-туннель : вы ↔ ваш VPS ]
   └── внутри: [ внутренний TLS : браузер ↔ google.com ]   ← «TLS в TLS»

Даже ОДНО соединение уже имеет эту вложенность: размеры записей внутреннего рукопожатия «просвечивают» сквозь внешний слой. Vision добавляет padding и переключается на splice, чтобы внутренний почерк не просвечивал. Это есть и при reality, и при tls.

А «один большой поток с кучей рукопожатий» — это mux (а не security: tls!):

[ ОДНО внешнее TLS-соединение ]
   ├── подпоток 1 → сайт A (внутри свой TLS)
   ├── подпоток 2 → сайт B
   └── подпоток 3 → сайт C

Много потоков в одном коннекте → меньше внешних соединений → бьёт по Сигналу 3. Но Vision на mux-поток не накладывается — ему нужен «сырой» отдельный TCP под splice, поэтому Vision и mux взаимоисключающие.

Развязка

  • Vision лечит TLS-в-TLS (форму внутри одного соединения) — это отдельный детект, не из «сибирской» тройки.
  • mux лечит число соединений (Сигнал 3).
  • Они чинят разные болезни и взаимоисключающи — выбираете что-то одно.
  • security (reality/tls) — только про то, чьим сертификатом представляется туннель; к «коннекты vs поток» отношения не имеет.

Можно ли вылечить обе болезни сразу?

Раз Vision и mux взаимоисключающие — обречены ли мы выбирать? Нет. И тут важное переосмысление: сам конфликт — не «защита против защиты».

Vision склеивает две разные вещи:

  • padding — добивает короткие пакеты ранней фазы рукопожатия → это и есть анти-детект TLS-в-TLS;
  • splice — после рукопожатия копирует поток прямо в ядре Linux → это оптимизация производительности, и только.

С mux несовместим именно splice (ему нужен «сырой» 1:1 поток), а не padding. То есть настоящий трейд-офф — «padding + splice-скорость» против «мультиплексирования», а не «один детект против другого». Splice — это то, чем жертвуют, а не защита. Значит, обе болезни лечатся одновременно — ценой отказа от splice-ускорения.

Практическое воплощение — транспорт XHTTP + XMUX («XHTTP: Beyond REALITY», #4113) — VLESS поверх HTTP/2 или H3:

  • число соединений лечит XMUX (объединяет логические запросы в одно физическое соединение) — ✅;
  • форму TLS-в-TLS размывает (header-padding xPaddingBytes + разнесение upload/download + перемешивание потоков);
  • совместим с REALITY и работает за CDN — а это убирает заодно и Сигнал 1 (подсеть становится CDN’овской).
Vision + REALITY (raw TCP)XHTTP + XMUX (+ REALITY/CDN)
TLS-в-TLS✅ padding + splice (сильнее по форме)⚠️ padding + split + mux (статистически, слабее)
Число соединений (Сигнал 3)❌ (mux нельзя)✅ XMUX
Подсеть (Сигнал 1)✅ если за CDN
Скорость✅ splice❌ всё через userspace (медленнее)

Честная оговорка

XHTTP прячет TLS-в-TLS слабее, чем кажется. По USENIX Security 2024 («Fingerprinting Obfuscated Proxy Traffic with Encapsulated TLS Handshakes») padding + мультиплексирование «многообещающи, но принципиально ограниченны»: они не уменьшают размер всплесков и число round-trip’ов, по которым вложенное рукопожатие и палится. XHTTP делает детект статистически дороже, а не невозможным. Почему именно — разобрано ниже, в пункте про round-trip’ы.

Итог: вылечить обе можно (XHTTP+XMUX), цена — потеря splice-скорости (а не принципиальный запрет) плюс «статистический», а не идеальный анти-TLS-в-TLS. Поэтому XTLS рекомендует держать оба профиля на сервере: Vision+REALITY+TCP для скорости/прямого подключения и XHTTP+XMUX для стелса/CDN (популярная «5-в-1» конфигурация).

А kTLS — вернёт ли он потерянную скорость?

kTLS (Kernel TLS) — функция ядра Linux, переносящая шифрование/расшифровку TLS-записей в ядро. За счёт этого forwarding может работать с zero-copy (sendfile/splice) даже когда прокси сам терминирует TLS. В xray это обсуждается (discussion #4270, issue #2565) как способ получить splice-уровень производительности без XTLS Vision — в обычном TLS-проксировании.

Но важно, чем kTLS не является:

  • Это только производительность, не анти-детект. Он ничего не прячет — ни форму TLS-в-TLS, ни число соединений.
  • Он не отменяет дилемму mux↔splice: ядерное копирование всё равно несовместимо с мультиплексором, который оборачивает поток в свои фреймы.
  • В xray это пока экспериментально / на стадии обсуждения, а не готовая кнопка.

Грубо: kTLS — рычаг «вернуть часть CPU и скорости», ортогональный детекту. На вопрос «как спрятаться» он не отвечает; он про «как сделать это дешевле по ресурсам».

Почему padding и mux в принципе не прячут TLS-в-TLS (детект по round-trip’ам)

Самый глубокий уровень — почему padding и мультиплексирование лишь полумеры против детекта TLS-в-TLS. (Важно: это отдельный вектор, не «сибирский» Сигнал 3 — число параллельных соединений mux по-прежнему уменьшает; речь о том, что форму вложенного рукопожатия полностью спрятать не удаётся.) Объясняет это USENIX Security 2024.

TLS-рукопожатие — это характерная хореография: всплеск пакетов от клиента → пауза в один RTT (round-trip) → ответный всплеск от сервера → снова пауза → и т.д. Когда такое рукопожатие идёт внутри туннеля, эта последовательность «всплеск — пауза RTT — всплеск» просвечивает наружу узнаваемым ритмом в начале соединения.

И вот суть, почему обфускация не спасает:

  • padding меняет размеры пакетов — но не меняет число round-trip’ов и не убирает структуру всплесков. Ритм остаётся.
  • mux перемешивает потоки — но при малой нагрузке (один-два активных потока) перемешивать нечего, и начальное рукопожатие сохраняет свой round-trip-узор.

Поэтому детектор смотрит не на байты, а на последовательность всплесков, их направления и паузы между ними — и опознаёт «внутри идёт TLS-рукопожатие». Это устойчиво к padding’у и частично к mux’у.

Цифры из той работы стоит приводить аккуратно, потому что их постоянно сравнивают между собой некорректно — они относятся к разным конфигурациям и разным моделям. Набивка у vmess поверх WebSocket и TLS снижает TPR (долю верно распознанных прокси-потоков) с 0,859 до 0,687, причём авторы отдельно оговаривают, что схема набивки у vmess узкая — 0–63 байта. Для XTLS-Vision модель переучили так, чтобы она опиралась на направления и порядок пакетов, а не на их индивидуальные размеры; у неё TPR 0,513, но при вчетверо большем допуске ложных срабатываний (0,199 % против 0,054 %). То есть «51 %» — это не «Vision сбил детект с 70 % до 51 %», а результат другой модели на другом пороге. Мультиплексирование в тех же измерениях эффективнее: у голого vmess объединение двух потоков роняет TPR с 0,771 до 0,225 — более чем на 70 %, — а vmess поверх WebSocket и TLS при восьми параллельных потоках опускается до 0,125. Ключевая оговорка: при одном активном прикладном потоке перемешивать нечего, и mux деградирует до уровня обычного соединения.

Что нужно для настоящей защиты

По выводам статьи — не дополнять трафик, а переформировывать его: уменьшать размер всплесков и число round-trip’ов внутри соединения. Готового решения, полностью закрывающего это, в современных клиентах пока нет. Отсюда и фундаментальность гонки: обфускация поднимает стоимость детекта, но не делает его невозможным.

Можно ли mux без head-of-line blocking?

Раз у mux есть болячка HoL (потеря одного пакета тормозит все потоки) — можно ли мультиплексировать без неё? Можно, и это уже сделано — но сначала о корне.

Откуда берётся HoL. TCP — это один упорядоченный надёжный байтовый поток. Mux поверх него гонит все логические потоки через этот единый поток. Потерялся пакет → TCP держит всё, что пришло после него, пока не дождётся повтора — даже данные других потоков, которые уже дошли целыми. Это неизбежно для любого mux поверх одного TCP: ни Mux.Cool, ни HTTP/2 не уйдут — они все сидят на одном TCP-потоке (HTTP/2 убрал HoL на уровне приложения, но не транспорта).

Выход — не использовать единый TCP-поток. Надо дать каждому логическому потоку свою независимую упорядоченность. Это умеет QUIC (= HTTP/3): работает поверх UDP и реализует надёжность/порядок на каждый поток отдельно. Потерянный пакет блокирует только свой поток — остальные едут дальше. Ради этого QUIC и придумали.

В xray это уже есть: XHTTP поддерживает H3-режим (QUIC) → XMUX поверх QUIC = мультиплексирование без TCP-HoL.

Подвохи — почему это не дефолт

  • QUIC = UDP, а UDP легко душат. Цензор может троттлить/резать UDP или QUIC целиком (в РФ бывает) → откат на TCP, и HoL возвращается. Отсюда же xudpProxyUDP443: reject (форсить откат QUIC на TCP).
  • QUIC не прячет детект. Round-trip’ы вложенного рукопожатия (см. пункт выше) просвечивают и через него — QUIC лечит производительность/HoL, а не обнаружимость формы.
  • Цена и совместимость. QUIC — userspace, дороже по CPU; поддержка H3 у CDN/промежуточных узлов неровная.

Итог: HoL неустраним поверх одного TCP — это закон. Обойти = перейти на per-stream-транспорт (QUIC/H3, XHTTP умеет), но вы меняете «HoL + надёжный TCP» на «без HoL, но по UDP, который цензор может прибить» — и форму трафика это всё равно не лечит. Бесплатного обеда нет.


🎯 Главный вывод

Эпоха «поставил REALITY и забыл» закончилась. DPI перешёл от анализа содержимого пакета к анализу поведения соединения. Отсюда три следствия:

  1. Идеальная криптография больше не гарантирует невидимость — она необходима, но недостаточна.
  2. Решает не один параметр, а совокупность косвенных признаков (подсеть + фингерпринт + поведение).
  3. Устойчивость даёт не «волшебная галочка», а грамотный профиль целиком: правильная подсеть, лояльный фингерпринт (uTLS), сдержанный паттерн соединений (mux + разные SNI).

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


📚 См. также