# 🔍 Как DPI анализирует соединение: воронка проверок (от SYN до ML) > [!quote] Первоисточник > Эта заметка основана на статье **Александра Мурзина** (@cyberscoper) на Хабре: > 👉 **[«Как работает DPI: разбор по слоям» — habr.com/ru/articles/1009560](https://habr.com/ru/articles/1009560/)** > > Это заметка-«карта»: общий **конвейер анализа** ТСПУ от первого SYN-пакета до ML-классификации. Глубокий разбор **одной** его стадии (поведенческой схемы июня 2026) — в парной заметке [[dpi-tls-june-2026|Как DPI «замораживает» VLESS+REALITY]]. Вместе они дают и общую картину, и детальный срез. > [!info] Дисклеймер > Материал **исследовательский и образовательный**. Цель — собрать в одну картину то, *по каким признакам и в каком порядке* DPI разбирает соединение, чтобы было понятно, какой обходной приём какой слой закрывает. > [!warning] Важно про статус этих данных > Описанная «воронка» — это **аналитическая модель**, собранная по наблюдениям сообщества ([ntc.party](https://ntc.party/)) и реверс-инжинирингу, а не опубликованная спецификация ТСПУ. Конкретные числа (5 пакетов, 83 байта, 16 КБ, 95–99 %, 1–5 мс) читайте как **порядок величин и авторские оценки**, а не как точные константы системы. Реальная реализация различается от оператора к оператору и меняется со временем. --- ## TL;DR — анализ идёт «воронкой» DPI не выносит вердикт по одному пакету. Он накапливает данные и **отсеивает на каждом уровне**: дешёвые проверки сначала, дорогой ML — в конце и только для выживших. ```text трафик │ пакеты 1–5 ┌───▼───┐ TTL, TCP-опции (MSS/WS/SACK/TS) → отпечаток ОС (TCP/IP) └───┬───┘ │ байты 0–5 ┌───▼───┐ протокольный маркер: 16 03 01… / SSH-2.0- / … (сигнатура) └───┬───┘ │ байты 6–300 ┌───▼───┐ ClientHello → JA3 / JA4 (фингерпринт клиента) (TLS hello) └───┬───┘ │ байты ┌───▼───┐ сертификат*: CT-логи, ASN, CN/SAN ↔ SNI 300–3000 └───┬───┘ (* в TLS 1.3 сертификат шифруется → │ пассивно остаются только SNI и ASN) байты ┌───▼───┐ метаанализ потока: размеры, тайминги, 3000–16000 └───┬───┘ соотношение in/out, длительность │ после 16 КБ ┌───▼───┐ поведенческий ML-классификатор └───┬───┘ │ вердикт → пропустить / RST / drop / throttle ``` Чем дальше по воронке — тем дороже проверка и тем труднее её обмануть. Обходные средства бьются за то, чтобы **отсеяться пораньше** (выглядеть скучно уже на ранних слоях). > [!note] Как это связано с «сибирской» схемой > Поведенческая схема июня 2026 (подсеть + фингерпринт + частота) — это, по сути, **нижние этажи** этой воронки: фингерпринт = слой JA3/JA4, частота/параллелизм = слой метаанализа потока. Подробный разбор именно её — в [[dpi-tls-june-2026]]. Здесь — как она вписана в общий конвейер. --- ## Этап 0. TCP/IP — ещё до всякого TLS DPI начинает «нюхать» соединение раньше, чем дойдёт до шифрования — на уровне самих TCP/IP-заголовков. **TTL — отпечаток ОС и детектор fake-пакетов.** Разные ОС ставят разный стартовый TTL: - **Windows → TTL 128**, **Linux → TTL 64** (macOS/iOS тоже 64). - Каждый промежуточный хоп уменьшает TTL на 1: пакет с `TTL 63` прошёл один хоп, `TTL 62` — два. Отсюда — два следствия. Во-первых, по TTL грубо угадывается ОС клиента. Во-вторых (важнее для обхода): на TTL ловятся **fake-пакеты** инструментов вроде [[Zapret/about|Zapret/GoodbyeDPI]] — те шлют поддельный пакет с маленьким TTL, чтобы он дошёл до DPI, но «умер» по дороге к серверу. Если DPI умеет сверять TTL, он отличает такой fake от настоящего пакета. Это прямая связь нашей «воронки» с миром Zapret. **TCP-опции — пассивный отпечаток ОС (p0f).** Набор и **порядок** TCP-опций в SYN-пакете — `MSS`, `Window Scale`, `SACK`, `Timestamps` — различается между операционными системами и их версиями. Это классический пассивный fingerprinting (техника p0f): по одному только SYN можно сказать «это похоже на Windows 11» или «это похоже на Linux». > [!quote] Авторская оценка (под оговорку) > «Первые **пять пакетов** соединения проходят без активного вмешательства» — DPI сначала собирает контекст. И: «если первый содержательный пакет после рукопожатия короче **83 байт** — подозрительно». Эти числа — иллюстрация логики, а не подтверждённые константы. --- ## Этап 1. Протокольный маркер — первые байты данных Как только пошли данные, DPI смотрит на **первые байты** — у многих протоколов начало узнаваемо: | Протокол | Маркер начала | | --- | --- | | **TLS ClientHello** | `16 03 01 xx xx 01 00 xx xx xx 03 03 …` | | **SSH** | ASCII-строка `SSH-2.0-…` | | **OpenVPN** | свой характерный заголовок | | **WireGuard** | характерный UDP-заголовок (тип сообщения) | | **Shadowsocks** (без обфускации) | **статистически случайные байты** — отсутствие маркера само по себе примета | Разбор TLS-маркера: `16` = Content Type Handshake; `03 01` = версия **record-слоя** (легаси, всегда «TLS 1.0» ради совместимости); `01` = Handshake Type ClientHello; `03 03` = `legacy_version`. > [!important] Поправка к первоисточнику > Источник пишет, что `03 03` — это «TLS 1.3 внутри ClientHello». Это неточность: `0x0303` — это `legacy_version` (формально **TLS 1.2**), поставленный ради совместимости. **Настоящая** версия TLS 1.3 объявляется не здесь, а в расширении `supported_versions` (`0x0304`). Полный побайтовый разбор реального `ClientHello` — в [[dpi-tls-june-2026#Как выглядит этот «почерк» на самом деле|байт-дампе RFC 8448 из парной заметки]]. Любопытно, что **Shadowsocks** проходит этот этап «наоборот»: у него нет маркера вообще, поток выглядит как энтропийный шум. На фоне веба, где почти всё — TLS с узнаваемым началом, «совершенно случайные байты с первого пакета» — сами по себе аномалия (по этому признаку, кстати, и ловят многие обфусцированные протоколы). --- ## Этап 2. JA3 / JA4 — отпечаток клиента по ClientHello Если маркер сказал «это TLS», DPI сворачивает `ClientHello` в фингерпринт. Точное определение **JA3**: ```text JA3 = MD5( SSLVersion, Ciphers, # список шифров, без GREASE-значений Extensions, # список расширений, без GREASE-значений EllipticCurves, EllipticCurvePointFormats ) ``` **JA4** (FoxIO, 2023) — новее и устойчивее: менее чувствителен к перетасовке порядка расширений (сортирует их), разделяет клиентскую и серверную части, даёт более стабильный отпечаток. По этому слою DPI говорит «это Chrome 13x» или «это голый Go-клиент». Здесь же и кроется ловушка массового `fingerprint: chrome`. **Глубокий разбор** — что такое фингерпринт побайтово, чем JA3 отличается от JA4, почему «голый» Go палится и что делает uTLS — в [[dpi-tls-june-2026#🔬 Технические детали: uTLS — как работает TLS-почерк|разделе про uTLS парной заметки]]. > [!note] Нюанс про GREASE в JA3 > То, что JA3 **исключает** GREASE-значения, — палка о двух концах. С одной стороны, отпечаток не «плавает» из-за случайных GREASE. С другой — Chrome **тасует порядок расширений** на каждом соединении (официально с Chrome 110, фактически проявлялось уже в сборках 108–109, начало 2023), а порядок в JA3 учитывается → у Chrome JA3 всё равно «плавает». Именно поэтому и появился JA4 с сортировкой. --- ## Этап 3. Сертификат — проверка TLS-личности сервера Дальше DPI смотрит на `ServerHello` и **сертификат** сервера и сверяет три вещи: - **Certificate Transparency.** Существует ли такой сертификат в публичных CT-логах? Выпущен ли он реально на этот домен? - **ASN сервера.** IP-адрес должен лежать в «правильной» автономной системе для этого бренда. Apple держит серверы в **AS714**, Microsoft — в **AS8075**. Если SNI говорит `icloud.com`, а IP — из чужой AS, это рассогласование. - **SNI ↔ CN/SAN.** Имя в `ClientHello` (SNI) должно совпадать с именем в сертификате. Если SNI = `icloud.com`, а приехал сертификат Let's Encrypt на случайный домен — не совпало. > [!important] Главная оговорка этапа: в TLS 1.3 сертификат не виден > Две из трёх проверок выше **пассивно работают в основном на TLS 1.2**. В **TLS 1.3** сообщение `Certificate` идёт **зашифрованным** (после `ServerHello`, под ключами рукопожатия) — пассивный наблюдатель его **не читает** ([RFC 8446](https://www.rfc-editor.org/rfc/rfc8446)). А сегодня почти весь веб — это TLS 1.3. Поэтому: > - **CT-логи** и **сверка CN/SAN ↔ SNI** для пассивного DPI на TLS 1.3 фактически **недоступны** — для них нужен **активный** MITM/перехват (это уже не «пассивная воронка»). На TLS 1.2 — работают. > - Надёжно пассивно в TLS 1.3 виден только **SNI в `ClientHello`** (если не включён ECH) и **IP/ASN** назначения. > - «Бренд = одна AS» — упрощение: Apple/Microsoft/Google массово раздают трафик через **сторонние CDN**, и тогда IP легитимно лежит в чужой AS (напр., egress-узлы iCloud Private Relay идут через сторонние CDN — Akamai, Cloudflare, Fastly, — то есть не из AS714). Грубая сверка «чужая AS → палево» даёт ложные срабатывания; реальный DPI держит allow-list **всех** AS/CDN бренда. > [!tip] Почему REALITY проходит этот этап > Это ровно тот слой, который закрывает **REALITY** — и работает он **только поверх TLS 1.3**, что здесь принципиально. REALITY не выпускает свой сертификат: сервер ретранслирует рукопожатие клиента к реальному `dest` (популярному сайту). Дальше важно различать **двух адресатов**: > - **Для постороннего наблюдателя/DPI** соединение неотличимо от настоящего TLS к `dest`: правильные SNI, IP и ASN, а сам `Certificate` — **зашифрован** (TLS 1.3), так что наблюдатель его и не читает. Именно на шифровании сертификата и держится незаметность — DPI нечего сверять с CT. > - **Для «своего» клиента** REALITY-сервер на лету **подменяет** сертификат на временный доверенный (привязан к ECDH-секрету/AuthKey) — по нему клиент опознаёт «свой» сервер. Если же клиенту приходит **настоящий** сертификат `dest`, это сигнал «меня редиректят/MITM-ят», и клиент уходит в режим обычного веб-браузера. > > Классический же VLESS/Trojan со *своим* сертификатом слабее — но пассивно в TLS 1.3 его выдаёт не чтение сертификата, а **IP/ASN хостинга** и **SNI на домен без трафика и репутации**; сам Let's Encrypt-сертификат виден лишь при **активном** зондировании или на TLS 1.2. Подробнее про заимствование личности — в [[dpi-tls-june-2026#Имеет ли смысл держать на VPS свою страничку и использовать её как SNI?|парной заметке]]. --- ## Этап 4. Метаанализ потока — поведение, а не содержимое Если по сертификату не придрались, DPI переходит к **форме трафика** — её шифрование не скрывает. Здесь смотрят на статистику: - **Соотношение in/out.** У браузера трафик асимметричен: маленькие запросы, большие ответы — типично **1:10 … 1:20**. Туннель, по которому льётся всё подряд, это соотношение нарушает. - **Межпакетные интервалы.** Браузер делает паузы (парсинг, отрисовка, реакция человека) — **десятки–сотни миллисекунд**. Туннель часто гонит данные ровным потоком без «человеческих» пауз. - **Длительность соединения.** Браузер живёт минутами (keep-alive), потом закрывается. **VLESS держит соединение открытым часами** — это аномалия. Это и есть тот уровень, на котором работает **Сигнал 3** «сибирской» схемы (частота и параллелизм соединений к одному SNI). Разбор поведенческого сигнала и лекарства от него (`mux`, разные SNI) — в [[dpi-tls-june-2026#Сигнал 3. Частота и параллелизм — «как» вы подключаетесь|парной заметке]]. --- ## Этап 5. ML-классификация — после ~16 КБ Для соединений, доживших до этого этапа, DPI скармливает накопленную статистику (размеры пакетов, тайминги, энтропию, ритм всплесков) **ML-классификатору**. > [!quote] Заявленная точность (под оговорку) > «Точность ML-классификаторов **в лабораторных условиях** достигает **95–99 % для Shadowsocks и VMess**. В продакшне — ниже». Лабораторные числа всегда оптимистичны: в реальном трафике с шумом, ложными срабатываниями и ценой ошибки порог срабатывания держат заметно выше. > [!note] Не путать «16 КБ → ML» и метод «tcp 16-20» > Совпадение порога обманчиво. В [[dpi-tls-june-2026]] упоминается отдельный метод **«tcp 16-20» (l4-25)** — он *тупо замораживает* соединение после ~16 КБ / ~25 пакетов, без всякого ML. Здесь же 16 КБ — это момент, когда у DPI **накопилось достаточно** данных, чтобы запустить статистику. Это два разных механизма с похожим порогом, а не одно и то же. Связь с фундаментальным пределом обфускации: даже padding и mux не убирают **round-trip-узор** вложенного TLS-рукопожатия (детект по [USENIX Security 2024](https://www.usenix.org/conference/usenixsecurity24/presentation/xue-fingerprinting)). Почему именно — разобрано в [[dpi-tls-june-2026#Почему padding и mux в принципе не прячут TLS-в-TLS (детект по round-trip'ам)|парной заметке]]. --- ## Что DPI делает с вердиктом Опознав обход, DPI применяет одну из трёх мер: | Метод | Что делает | Как ощущается | | --- | --- | --- | | **TCP RST injection** | вбрасывает поддельный `RST` в поток | соединение резко рвётся | | **Drop пакетов** | молча не пропускает пакеты | «зависание», потом таймаут | | **Throttling** | намеренное замедление + потери | «как будто плохой интернет» | «Заморозка на 120/600 секунд» из «сибирской» схемы — это вариант **throttling**: соединение не рвут демонстративно, а тихо душат, чтобы было похоже на плохую связь, а не на цензуру. > [!quote] Про задержку самого DPI (под оговорку) > Активная инспекция добавляет «**1–5 мс** для большинства соединений». Само по себе это иногда косвенный признак присутствия DPI на маршруте. --- ## Контрмеры по слоям: кто какой этаж закрывает Главный практический вывод обеих заметок совпадает: **ни один инструмент в одиночку не закрывает все слои.** Каждый приём бьёт по своему этажу воронки: | Слой воронки | Чем закрывается | | --- | --- | | TCP/IP (TTL, опции ОС) | корректный стек ОС; для Zapret — аккуратные fake с верным TTL | | Протокольный маркер (байты 0–5) | TLS-обёртка (REALITY/Trojan) — выглядит как обычный TLS | | JA3 / JA4 (байты 6–300) | **uTLS-фингерпринт** (`fingerprint: firefox/edge/…`) | | SNI / ASN / сертификат (300–3000)¹ | **REALITY** (правильный SNI-донор + чужой ASN + защита от активного зондирования) | | Метаанализ потока (3000–16 000) | **mux** / разнесение по SNI / `xPaddingBytes` | | ML (после 16 КБ) | нормализация формы (**XHTTP+XMUX**) — и то лишь *статистически* | ¹ В TLS 1.3 сам сертификат пассивно не виден (зашифрован), поэтому пассивно на этом этаже остаются **SNI и ASN/IP**; REALITY закрывает их (чужой знаменитый SNI-донор + правильный ASN) и защищает от **активного** зондирования — а не «прячет чтение сертификата», которого пассивный DPI в TLS 1.3 и так не делает (см. callout в Этапе 3). Видно, что слои **ортогональны**: можно идеально закрыть JA3 и провалиться по подсети; можно спрятать сертификат через REALITY и спалиться по поведению потока. Устойчивость даёт не «волшебная галочка», а закрытие воронки **сверху донизу** — ровно к этому и приходит разбор в [[dpi-tls-june-2026#🎯 Главный вывод|парной заметке]]. --- ## ⚠️ Честная оговорка про числа Чтобы не выдавать модель за спецификацию — что здесь **проверяемый факт**, а что **авторская оценка**: - ✅ **Твёрдо:** TTL 128/64 и декремент по хопам; TCP-опции как отпечаток ОС (p0f); сигнатура `16 03 01…`; `SSH-2.0-`; формула JA3 (5 полей, без GREASE); JA4/FoxIO 2023 (сортировка шифров и расширений); ASN-факты (AS714 Apple, AS8075 Microsoft); методы RST/drop/throttle. - ⚠️ **Иллюстративно / под оговорку:** «5 пакетов без вмешательства», «короче 83 байт», пороги байтовых стадий (0–5 / 6–300 / …), «95–99 % в лаборатории», «1–5 мс задержки», соотношения 1:10–1:20. Это порядок величин и логика, а не подтверждённые константы ТСПУ. - 🪧 **С важной оговоркой:** проверки **CT-логов** и **CN/SAN ↔ SNI** (Этап 3) — реальные техники, но **пассивно доступны в основном на TLS 1.2**; в TLS 1.3 сертификат зашифрован, и для них нужен активный MITM. Связка «бренд = одна AS» ломается на CDN. - 🩹 **Поправлено:** `0x0303` — это `legacy_version` (TLS 1.2), а не «TLS 1.3 внутри ClientHello» (настоящая версия — в `supported_versions`); из формулы JA3 убрано ошибочное «без padding (21)» — каноничный JA3 исключает только GREASE. --- ## 📚 См. также - 🔗 **Первоисточник:** [habr.com/ru/articles/1009560](https://habr.com/ru/articles/1009560/) — Александр Мурзин (@cyberscoper) - 🧊 **Парная заметка:** [[dpi-tls-june-2026|Как DPI «замораживает» VLESS+REALITY: схема июня 2026]] — глубокий разбор поведенческой стадии - 🦎 **Парная заметка:** [[statistical-morphing-concept|Адаптивная мимикрия: статистический морфинг трафика]] — концепт-ответ на поведенческий детект (Этап 4) - 🕵️ **Полевой кейс:** [[browser-ja4-fingerprint-block|Блокировка сайта по JA4-отпечатку браузера]] — как слой JA3/JA4 этой воронки бьёт по обычному Chrome - 🔗 [JA4 — FoxIO-LLC/ja4](https://github.com/FoxIO-LLC/ja4) - 🔗 [USENIX Security 2024 — Fingerprinting Obfuscated Proxy Traffic](https://www.usenix.org/conference/usenixsecurity24/presentation/xue-fingerprinting) (round-trip-узор вложенного TLS — Этап 5) - 🔗 [USENIX Security 2023 — How the GFW Detects and Blocks Fully Encrypted Traffic (Wu et al.)](https://www.usenix.org/conference/usenixsecurity23/presentation/wu-mingshi) (детект «бесмаркерного» трафика по энтропии — Этап 1) - 🔗 [IMC 2020 — How China Detects and Blocks Shadowsocks](https://gfw.report/publications/imc20/en/) (энтропия первого пакета + активное зондирование — Этапы 1 и 5) - [[Zapret/about|Что такое обход DPI: VPN, Tor, DPI]] - [[Zapret/vless-sni|Список SNI для VLESS]]