# 🧊 Как DPI «замораживает» VLESS+REALITY: разбор схемы ограничений (июнь 2026) > [!quote] Первоисточник > Этот разбор основан на статье **Петра Осетрова** (@hyperion_cs) на Хабре: > 👉 **[«О схеме ограничений РКН в июне 2026-го» — habr.com/ru/articles/1044396](https://habr.com/ru/articles/1044396/)** > > Все числовые параметры (пороги, тайминги, списки фингерпринтов) взяты из этой статьи. Ниже — простое объяснение «на пальцах» плюс расширенные технические детали по uTLS и mux от себя. > [!info] Дисклеймер > Материал носит **исследовательский и образовательный** характер. Задача — понять, *по каким признакам* современный DPI отличает обходные средства от обычного трафика, и почему одной волшебной галочкой проблему больше не решить. > [!warning] Важно про статус этих данных > Описанная схема — это **результат реверс-инжиниринга**: автор сам называет её «Siberian» и добавил одноимённый чекер в dpi-checkers. Это наблюдения **одного исследователя**; сам автор оговаривает, что **параметры метода различаются от оператора к оператору и меняются со временем**. > > Параллельно, в тот же период, задокументированы и **другие** механизмы блокировки — заморозка соединения после ~16 КБ / ~25 пакетов (метод «tcp 16-20»), CIDR-whitelist по подсетям назначения, SNI-whitelist (см. [net4people/bbs #490](https://github.com/net4people/bbs/issues/490)). Поэтому всё ниже — **снимок одной наблюдаемой волны на июнь 2026**, а не универсальный закон. Конкретные числа читайте как «по наблюдениям автора», а не как точные константы системы. --- ## TL;DR — если совсем коротко Летом 2026 года перестала надёжно работать связка [[Zapret/about|xray]] + **VLESS + REALITY**. Важно: REALITY **не взломали**. Его шифрование по-прежнему неотличимо от настоящего TLS. Сломалось другое: DPI перестал заглядывать *внутрь* пакета (это бесполезно) и начал смотреть на **поведение** соединения снаружи. Блокировка включается, только когда **совпали сразу три признака** (это важно — именно «И», а не «ИЛИ»). Нарушьте любой один — и правило на вас не сработает. --- ## Объясняю на пальцах: что вообще произошло Представьте таможню на границе (это наш DPI). Раньше таможенник открывал каждый чемодан и смотрел, что внутри. REALITY — это чемодан с идеальной маскировкой: снаружи и внутри он выглядит как чемодан обычного туриста, едущего на `www.microsoft.com`. Подделку не видно, потому что для этого нужен секретный ключ, которого у таможни нет. Поэтому годами всё работало: «внутри чисто — проходи». Теперь таможенник **перестал открывать чемоданы** (всё равно бесполезно) и начал смотреть на **поведение пассажира**: - **Откуда приехал?** — если из района, где живут одни «контрабандисты» (популярные хостинги), это подозрительно. - **Как выглядит?** — если одет в форму, в которой обычно ходят нарушители (фингерпринт Chrome), это подозрительно. - **Как себя ведёт?** — если бегает туда-сюда через границу 10 раз в минуту (лавина соединений), это подозрительно. И только когда **все три** совпали — пассажира «тормозят». Криптография тут уже ни при чём: ловят не *содержимое*, а *манеру*. --- ## Алгоритм блокировки: три сигнала и один триггер Цензор оценивает **`ClientHello`** — самый первый пакет TLS-рукопожатия — у **каждого** TLS-соединения. Среди прочего он смотрит на такие атрибуты: > [!quote] Что оценивает цензор (из первоисточника) > - **IP-адрес сервера** (точнее — его *подсеть*); > - **SNI** (Server Name Indication — то самое замаскированное имя); > - **фингерпринт «браузера»** (точнее — того, под кого мимикрируют; у REALITY это делает uTLS). К этим статическим атрибутам добавляется ещё и **поведение** — частота и параллелизм соединений. «Заморозка» включается, **только когда совпали сразу несколько признаков** (логическое «И»): подозрительная подсеть + подозрительный фингерпринт + аномальная частота к одному SNI. Разберём их по очереди. > [!note] Почему сигналов «три», хотя атрибутов перечислено больше > SNI в списке выше — **не отдельный сигнал**, а *ключ агрегации* для поведенческого Сигнала 3: частота соединений считается раздельно по каждому SNI. Поэтому ключевых триггеров остаётся три — **подсеть**, **фингерпринт** и **частота к одному SNI**. ### Сигнал 1. Подсеть сервера — «откуда» вы подключаетесь DPI смотрит на IP-адрес вашего сервера и проверяет, не входит ли он в список «подозрительных» подсетей. В этот список массово попали диапазоны популярных хостингов, в том числе **российских**: - **Selectel** - **Яндекс.Облако** Логика простая: именно там дешевле и проще всего поднять свой узел, поэтому там их больше всего. Важный нюанс: схема завязана **не на конкретные IP** отдельных узлов, а на **целые подсети и даже автономные системы (AS)**. > [!quote] Из первоисточника > Схема не завязана напрямую на конкретные IP промежуточных узлов (напр., ваших серверов). Это, в общем-то, неплохо — значит, узлы не улетели в некий чёрный список (особенно в свете того, что некоторые приложения «стучат»). Плохая новость в том, что затронуты целые подсети и даже AS, в т.ч. популярных российских дата-центров (Selectel, Яндекс.Облако и др.). Это заметно отличается от прежних методов, которые задевали только зарубежных провайдеров (tcp 16-20, aka l4-25, и пр.). Что это значит на практике: ваш конкретный IP в чёрный список **не попадает** (его не «сожгли») — под подозрением сразу весь диапазон хостинга. Хорошая сторона — сменить IP в пределах того же провайдера не поможет, но и «гореть» вашему адресу не за что. Плохая — отсюда же берётся сопутствующий ущерб для легальных соседей по подсети (см. раздел про обычный Chrome ниже). > [!tip] Что это значит для вас > Сервер в «народном» дата-центре — первый красный флаг. Чем менее предсказуемы провайдер и подсеть, тем лучше. Практику выбора см. в [[VPS/VPS|VPS]]. ### Сигнал 2. TLS-фингерпринт клиента — «чем» вы подключаетесь Когда ваша программа начинает TLS-соединение, она первым делом отправляет пакет **`ClientHello`**. В нём — список поддерживаемых шифров, расширений, их порядок и прочие технические мелочи. Этот набор у каждой реализации TLS свой, как почерк. По нему можно понять, **какая именно программа** сгенерировала пакет (подробнее — в разделе про uTLS ниже). #### Как выглядит этот «почерк» на самом деле Возьмём эталонный `ClientHello` из [RFC 8448](https://www.rfc-editor.org/rfc/rfc8448) — это пример прямо из стандарта, поэтому каждый байт можно сверить. **1. Текстовая форма** — то, как пакет разбирает Wireshark/`tshark` (дерево полей): ```text 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 с аннотациями): ```text 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 и опознаёт, кто перед ним. > [!note] Мелкий, но важный нюанс про версию > Обратите внимание: в поле версии стоит `0x0303` (TLS 1.2), хотя это TLS 1.3. Так сделано **нарочно** — ради совместимости со старыми серверами. Настоящую версию несёт расширение `supported_versions` (`0x0304`). Поэтому JA4 берёт версию TLS именно оттуда, а не из легаси-поля. И вот ловушка: xray умеет притворяться чужим почерком, и по умолчанию **почти все ставили Chrome** — он самый популярный, прятаться логичнее в толпе. В итоге сочетание «почерк Chrome + признаки прокси» само стало маркером. | 🚩 Подозрительные фингерпринты | ✅ «Лояльные» фингерпринты | | --- | --- | | Chrome | Firefox | | Safari | Edge | | iOS | Android OkHttp | | | 360 Browser | | | QQ Browser | > [!warning] Парадокс > Самый популярный браузер планеты оказался самым «палевным» — именно потому, что под него массово маскируются. Безопаснее то, под что маскируются реже. ### Сигнал 3. Частота и параллелизм — «как» вы подключаетесь Третий признак — поведенческий, и именно он чаще всего вас и выдаёт. Точная формулировка триггера из первоисточника: > **более 3 параллельных попыток установить TLS-соединение, с задержкой между ними менее ~350-400 мс, за последние 60 секунд** — к одному и тому же SNI. Разберём по словам: - **более 3 соединений** — то есть 4 и больше; - **задержка между ними < ~350-400 мс** — то есть они идут залпом, почти одновременно; - **за последние 60 секунд** — окно, в котором DPI это считает; - **к одному SNI** — все на одно и то же замаскированное имя (например, `www.microsoft.com`). Откуда берётся такой залп? Это типичный почерк прокси. Вы открываете браузер, в нём десяток вкладок — и каждая инициирует своё соединение. Все они мультиплексируются на **один** замаскированный SNI. Живой человек, реально открывший `www.microsoft.com`, так себя не ведёт — он не создаёт 10 одновременных коннектов на этот домен. > [!tip] Что это значит для вас > Лавина одновременных соединений на один 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) схемы: блокировка по подсети неизбежно задевает добросовестных соседей по хостингу. > [!summary] Коротко > Обычный Chrome спасает **чистая подсеть назначения**, а не сам браузер. Где подсеть «грязная» (ваш VPS или легальный сайт на том же хостинге) — там при Chrome и залпе соединений ловят всех подряд, включая невиновных. --- ## Интересный нюанс: ловушка для тех, кто «дёргается» Самая хитрая часть схемы — реакция на попытку быстро поменять настройки. Допустим, вас «заморозили». Вы решаете: «наверное, дело в фингерпринте» — и тут же переключаете Chrome на Firefox, переподключаясь. Логично же? Но для DPI это **отдельный сигнал**: только что соединение получило деградацию — и человек моментально сменил TLS-почерк. Так ведёт себя именно тот, кто *осознанно обходит* ограничение. В ответ прилетает **расширенная блокировка на 600 секунд** — и уже **на все** TLS-соединения к этому узлу. Важно: на эти 10 минут замораживается **любой** TLS, **независимо и от фингерпринта, и от SNI** — сменить почерк или замаскированное имя уже не поможет. Сам TCP-коннект при этом проходит (рукопожатие на уровне TCP устанавливается), душится именно TLS поверх него. > [!danger] Мораль > Система ловит не только сам обход, но и **паттерн адаптации**. Нервно крутить настройки под нагрузкой — делать себе хуже. Правильная стратегия: не реагировать рефлекторно, а **изначально** не попадать в цепочку. --- ## 🔬 Технические детали: 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 list** | 13 шифров, сразу с 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 было ещё одним отличием.) > [!important] Важный нюанс про REALITY > В REALITY uTLS **встроен и обязателен**: поле `fingerprint` помечено как Required, а режим отключения uTLS (`unsafe`) официально не поддерживается — REALITY использует эту библиотеку для манипуляции низкоуровневыми параметрами TLS. Поэтому «голый» `crypto/tls`-почерк от REALITY **никогда не уходит** — он всегда отправляет валидный фингерпринт браузера. Проблема REALITY в июне 2026 — не в Go-почерке, а в том, что почерк хоть и корректный, но **слишком массовый** (`chrome`). Об этом — ниже. ### Что делает uTLS [uTLS](https://github.com/refraction-networking/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"`. ```json "realitySettings": { "fingerprint": "firefox", "serverName": "www.microsoft.com", "password": "<публичный ключ сервера>", "shortId": "<short id>" } ``` ### Почему это перестало спасать само по себе uTLS делает почерк **корректным**, но не делает его **редким**. Если все массово ставят `chrome`, то «JA3 Chrome + поведение прокси» становится статистически заметным. DPI не говорит «фингерпринт поддельный» (он валидный!) — он говорит «слишком много именно такого фингерпринта летит на этот странный сервер залпами». Поэтому в июне 2026 совет сместился: брать **не самый массовый** профиль — `firefox`/`edge` и пр. > [!note] Про `random` и `randomized` — это РАЗНЫЕ режимы > Их легко перепутать, но ведут себя они по-разному: > - **`random`** — xray случайно берёт один из готовых **реальных** пресетов браузера (Chrome 131, Firefox 148 и т.п.). Почерк всегда валидный — это безопасный вариант. > - **`randomized`** (и `randomizednoalpn`) — **синтетическая** генерация: расширения и шифры добавляются/тасуются случайно. Может получиться комбинация, **которой не существует ни у одного реального браузера** — а это само по себе аномалия. > > Вывод: бездумно ставить `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* не срабатывает. ```json "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 — так трафик идёт по нашему замаскированному каналу, а не утекает мимо. > [!warning] У mux есть и обратная сторона > - **Head-of-line blocking**: все логические потоки делят одно TCP-соединение. Потеря одного пакета тормозит сразу все потоки внутри. На нестабильном канале это ощутимо бьёт по скорости. > - **Слишком большой `concurrency`** склеивает весь трафик в один долгоживущий коннект — тоже своего рода аномалия (реальные браузеры всё же открывают несколько соединений). Истина посередине: умеренные значения вроде `4–8`. > - Для **скоростных загрузок** mux иногда наоборот мешает — один коннект упирается в лимиты. Поэтому его часто включают выборочно. > [!danger] 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 (см. большой список в [[Zapret/vless-sni|vless-sni]]), нагрузка не концентрируется на одном имени, и порог «>3 соединений с интервалом <~350-400 мс за 60 с» на каждое отдельное имя достигается труднее. mux + несколько SNI вместе очень эффективно сглаживают поведенческий след. --- ## ✅ Что делать: рвём цепочку (по шагам) Поскольку триггер — это «И» трёх условий, для безопасности достаточно стабильно нарушать **хотя бы одно** звено. На практике стоит закрыть несколько — для запаса прочности. ### Шаг 1. Сменить фингерпринт на «лояльный» Самое дешёвое действие. В `realitySettings` поменять `fingerprint` с `chrome` на `firefox` или `edge`: ```json "realitySettings": { "fingerprint": "firefox" } ``` ### Шаг 2. Включить `mux` и снизить параллелизм Сглаживаем залп соединений: ```json "mux": { "enabled": true, "concurrency": 8 } ``` И дополнительно — **разнести трафик по нескольким SNI** ([[Zapret/vless-sni|список SNI]]). > [!warning] Если у вас XTLS Vision > При стандартном для REALITY flow `xtls-rprx-vision` TCP-mux **не применяется** (см. раздел про mux и Vision выше), так что `concurrency: 8` тут не сработает. Тогда против Сигнала 3 работают только **разные SNI + сдержанный паттерн**, либо переход на **XHTTP+XMUX** (см. FAQ «Можно ли вылечить обе болезни сразу?»). ### Шаг 3. Увести сервер в «невидимую» подсеть Уходим из массовых диапазонов народных хостингов (Selectel, Яндекс.Облако и т.п.). Менее предсказуемый провайдер/подсеть → выпадаем из списка «подозрительных». Подбор — в [[VPS/VPS|VPS]]. ### Шаг 4. Не дёргаться под нагрузкой Поймали деградацию — **не меняйте фингерпринт на лету**. Лучше переждать окно (120 с) или сменить узел целиком, чем спровоцировать расширенную блокировку на 600 секунд. > [!note] «Смени фингерпринт» — не единственный и не универсальный рецепт > Совет про `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](https://github.com/hyperion-cs/dpi-checkers)** (на момент разбора — версия **v0.7.0**). Он прогоняет вашу инфраструктуру по описанным критериям: попадает ли подсеть в подозрительные, как выглядит фингерпринт, ловится ли поведенческий паттерн. Проверки задаются в YAML, например фильтр по организации хостинга: ```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](https://github.com/XTLS/Xray-core/issues/1782) **закрыт как «not planned»**. - **Сканеры своего браузера живы:** [browserleaks.com/tls](https://browserleaks.com/tls) (самый подробный), [tls.peet.ws](https://tls.peet.ws/) (JSON-API). Но они выдают хеши/сводку, **а не raw-байты**, нужные `Fingerprinter`. Итого: **готового пайплайна для обычного пользователя нет** — это ручная работа разработчика (pcap → `FingerprintClientHello` → свой Go-код). В стандартном клиенте доступны только пресеты + `random`/`randomized`. > [!warning] Парадокс уникальности > Если зальёте *свой уникальный* отпечаток — он станет маркером именно **вас**. Сила браузерного фингерпринта в том, что он *массовый*; уникальный — наоборот, деанонимизирует. ### Альтернатива 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`) | > [!tip] Закрывает сразу две оси — но **двумя рычагами**, не «одной галочкой» > Подлинный стек браузера даёт **массовый настоящий фингерпринт** — это **Сигнал 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 рабочий вариант. - Это **клиентский** путь: почерк генерится **на устройстве пользователя**, до цензора — ровно то, о чём [[mtproxy/ja4-sni-client-side|«JA4 и SNI меняет только клиент»]]. Серверная сторона тут по-прежнему ничего не решает. > [!warning] Не «серебряная пуля» > Подлинный почерк снимает только **Сигнал 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 «отдать настоящий серт на пробу» от неё **не спасает** (это другая ось). > [!warning] «REALITY бесполезен» — это гипербола > Сверка SNI↔IP **дорога цензору**: легитимных рассогласований масса (CDN, ECH, сплит-DNS, корпоративные прокси), поэтому массовая проверка даёт ложные срабатывания на обычном трафике — тот же «сопутствующий ущерб», что и с подсетями. Корректнее формулировать как «вектор реален и REALITY **слабеет**», а не «мёртв». И главное: это **мнение/прогноз практиков**, а **не** подтверждённая часть «сибирской» схемы — в первоисточнике три сигнала (подсеть, фингерпринт, частота), **сверки SNI↔IP там нет**. Это **родственная** логика той, что осторожно отмечена для MTProto в [[mtproxy/ja4-sni-client-side|проверке 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 — нет, в любой топологии. > [!quote] Оговорка > Точный состав списка подозрительных подсетей публично неизвестен (реверс-инжиниринг одного автора). Вывод «зарубежные тоже в списке» следует из формулировки первоисточника («затронуты подсети/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 и сдержанного паттерна. ### 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. **Что такое TLS-в-TLS** (с этим борется Vision). Открываете `https://google.com` через туннель — получаются два вложенных слоя TLS: ```text [ внешний TLS / REALITY-туннель : вы ↔ ваш VPS ] └── внутри: [ внутренний TLS : браузер ↔ google.com ] ← «TLS в TLS» ``` Даже ОДНО соединение уже имеет эту вложенность: размеры записей внутреннего рукопожатия «просвечивают» сквозь внешний слой. Vision добавляет padding и переключается на splice, чтобы внутренний почерк не просвечивал. Это есть и при `reality`, и при `tls`. **А «один большой поток с кучей рукопожатий» — это `mux`** (а не `security: tls`!): ```text [ ОДНО внешнее TLS-соединение ] ├── подпоток 1 → сайт A (внутри свой TLS) ├── подпоток 2 → сайт B └── подпоток 3 → сайт C ``` Много потоков в одном коннекте → меньше внешних соединений → бьёт по Сигналу 3. Но Vision на mux-поток **не накладывается** — ему нужен «сырой» отдельный TCP под splice, поэтому Vision и mux **взаимоисключающие**. > [!summary] Развязка > - **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](https://github.com/XTLS/Xray-core/discussions/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 (медленнее) | > [!warning] Честная оговорка > XHTTP прячет TLS-в-TLS **слабее**, чем кажется. По [USENIX Security 2024](https://www.usenix.org/conference/usenixsecurity24/presentation/xue-fingerprinting) («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](https://github.com/XTLS/Xray-core/discussions/4270), [issue #2565](https://github.com/XTLS/Xray-core/issues/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](https://www.usenix.org/conference/usenixsecurity24/presentation/xue-fingerprinting). TLS-рукопожатие — это характерная **хореография**: всплеск пакетов от клиента → пауза в один **RTT** (round-trip) → ответный всплеск от сервера → снова пауза → и т.д. Когда такое рукопожатие идёт **внутри** туннеля, эта последовательность «всплеск — пауза RTT — всплеск» **просвечивает наружу** узнаваемым ритмом в начале соединения. И вот суть, почему обфускация не спасает: - **padding** меняет *размеры* пакетов — но **не меняет число round-trip'ов** и не убирает структуру всплесков. Ритм остаётся. - **mux** перемешивает потоки — но при малой нагрузке (один-два активных потока) перемешивать нечего, и начальное рукопожатие сохраняет свой round-trip-узор. Поэтому детектор смотрит не на байты, а на **последовательность всплесков, их направления и паузы между ними** — и опознаёт «внутри идёт TLS-рукопожатие». Это устойчиво к padding'у и частично к mux'у: в той работе обфусцированные прокси в стандартных конфигурациях ловятся с TPR >70%, а агрессивный padding (как у XTLS-Vision) сбивает это лишь до **~51%** — труднее, но не спасает. > [!summary] Что нужно для настоящей защиты > По выводам статьи — не *дополнять* трафик, а **переформировывать** его: уменьшать размер всплесков и число 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**. > [!warning] Подвохи — почему это не дефолт > - **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). Гонка щита и меча перешла на уровень статистики и поведения. Выигрывает тот, чей трафик **скучный** — максимально похожий на обычного пользователя и снаружи, и внутри. --- ## 📚 См. также - 🔗 **Первоисточник:** [habr.com/ru/articles/1044396](https://habr.com/ru/articles/1044396/) — Пётр Осетров (@hyperion_cs) - 🔗 [uTLS — refraction-networking/utls](https://github.com/refraction-networking/utls) - 🔗 [dpi-checkers — hyperion-cs/dpi-checkers](https://github.com/hyperion-cs/dpi-checkers) - 🔗 [xray-core — XTLS/Xray-core](https://github.com/XTLS/Xray-core) - 🔍 **Парная заметка:** [[dpi-analysis-pipeline|Как DPI анализирует соединение: воронка проверок (от SYN до ML)]] — общий конвейер, в который вписана эта схема - 🦎 **Парная заметка:** [[statistical-morphing-concept|Адаптивная мимикрия: статистический морфинг трафика]] — концепт, как *можно было бы* обойти поведенческий сигнал - 🕵️ **Полевой кейс:** [[DPI/browser-ja4-fingerprint-block|Блокировка сайта по JA4-отпечатку браузера]] — «Сигнал 2» в дикой природе: сайт не открывается только в Chrome/Edge - [[Zapret/about|Что такое обход DPI: VPN, Tor, DPI]] - [[Zapret/premium|premium]] — рабочие конфиги - [[Zapret/vless-sni|Список SNI для VLESS]] - [[VLESS-localhost-protection-guide|Защита VLESS на стороне localhost]] - [[VLESS-SOCKS5-vulnerability|Уязвимость VLESS + SOCKS5]]