todo/DPI/dpi-analysis-pipeline.md
loop-uh c35421bbdf
Some checks failed
Published content check / validate (push) Failing after 5s
Инспекторы ТСПУ: разбор шести чекеров блокировок по их исходному коду
Новая заметка DPI/tspu-inspectors-checkers-2026: DPI Detector, dpi-checkers (веб-чекер, dpi-ch, DWC), rkn-block-checker, ByeByeVPN, tspu-checker и ТСПУ Probe. Для каждого разобрана механика измерения, а не пересказан README: как именно создаётся нагрузка в тесте l4-25 (X-Pad по 4 КБ в keep-alive против POST 64 КБ против curl --range), почему «16–20 КБ» — это счётчик пакетов, как устроена «сибирская» проверка по числу одновременных рукопожатий и как классификатор DPI Detector превращает стадию соединения в ярлык вердикта.

Отдельно показаны границы применимости: у tspu-checker проверка Split DNS сравнивает величины разной природы, а тест WireGuard формирует ключ в формате, который wg отвергает (проверено локально); у ТСПУ Probe сертификат сверяется по CN без SAN; у rkn-block-checker весь контроль DNS держится на единственном DoH-эндпоинте. Добавлены таблица «симптом → механизм → лечение», порядок запуска инструментов и предупреждение о сканировании только своих хостов.

Три иллюстрации: вывод DPI Detector, веб-чекер TCP 16-20 и ТСПУ Probe на Android. Раздел DPI получил подраздел «Диагностика», обратные ссылки проставлены из dpi-analysis-pipeline, curl-impersonate и dpi-tls-june-2026.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 19:09:45 +03:00

244 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

---
date: 2026-06-07
tags:
- dpi
- tspu
- ja3
- ja4
- tls
- ml
- rkn
link: https://habr.com/ru/articles/1009560/
aliases:
- Конвейер анализа DPI
- Воронка проверок ТСПУ
- Как DPI анализирует соединение
img:
description: "Как ТСПУ анализирует соединение слой за слоем: TTL, JA3/JA4, сертификаты, метаанализ потока и ML — и какие приёмы обхода закрывают каждый слой."
---
> [!mirror] Резервное зеркало
> Актуальная версия этой страницы — на основной вики: [wiki.zapret.moe/DPI/dpi-analysis-pipeline](https://wiki.zapret.moe/DPI/dpi-analysis-pipeline)
# 🔍 Как 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 КБ, 9599 %, 15 мс) читайте как **порядок величин и авторские оценки**, а не как точные константы системы. Реальная реализация различается от оператора к оператору и меняется со временем.
---
## TL;DR — анализ идёт «воронкой»
DPI не выносит вердикт по одному пакету. Он накапливает данные и **отсеивает на каждом уровне**: дешёвые проверки сначала, дорогой ML — в конце и только для выживших.
```text
трафик
пакеты 15 ┌───▼───┐ TTL, TCP-опции (MSS/WS/SACK/TS) → отпечаток ОС
(TCP/IP) └───┬───┘
байты 05 ┌───▼───┐ протокольный маркер: 16 03 01… / SSH-2.0- / …
(сигнатура) └───┬───┘
байты 6300 ┌───▼───┐ ClientHello → JA3 / JA4 (фингерпринт клиента)
(TLS hello) └───┬───┘
байты ┌───▼───┐ сертификат*: CT-логи, ASN, CN/SAN ↔ SNI
3003000 └───┬───┘ (* в TLS 1.3 сертификат шифруется →
│ пассивно остаются только SNI и ASN)
байты ┌───▼───┐ метаанализ потока: размеры, тайминги,
300016000 └───┬───┘ соотношение 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, фактически проявлялось уже в сборках 108109, начало 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-классификаторов **в лабораторных условиях** достигает **9599 % для 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 (под оговорку)
> Активная инспекция добавляет «**15 мс** для большинства соединений». Само по себе это иногда косвенный признак присутствия DPI на маршруте.
---
## Контрмеры по слоям: кто какой этаж закрывает
Главный практический вывод обеих заметок совпадает: **ни один инструмент в одиночку не закрывает все слои.** Каждый приём бьёт по своему этажу воронки:
| Слой воронки | Чем закрывается |
| --- | --- |
| TCP/IP (TTL, опции ОС) | корректный стек ОС; для Zapret — аккуратные fake с верным TTL |
| Протокольный маркер (байты 05) | TLS-обёртка (REALITY/Trojan) — выглядит как обычный TLS |
| JA3 / JA4 (байты 6300) | **uTLS-фингерпринт** (`fingerprint: firefox/edge/…`) |
| SNI / ASN / сертификат (3003000)¹ | **REALITY** (правильный SNI-донор + чужой ASN + защита от активного зондирования) |
| Метаанализ потока (300016 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 байт», пороги байтовых стадий (05 / 6300 / …), «9599 % в лаборатории», «15 мс задержки», соотношения 1:101: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/tspu-inspectors-checkers-2026|Инспекторы ТСПУ: шесть чекеров блокировок]] — чем практически проверить, какой этаж этой воронки сработал у вашего провайдера
- 🧊 **Парная заметка:** [[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]]