A column that pins TLS 1.3 rewrote `supported_versions` and nothing else, so the hello advertised one version while still carrying the other's extensions and cipher suites: Chrome 107 and Safari 15.5 kept `extended_master_secret`, `renegotiation_info`, `ec_point_formats` and `session_ticket` plus the whole TLS 1.2 cipher list, and a 1.2 hello kept `supported_versions`, ALPS and the padding a hello that short has no use for. That shape exists nowhere. Read off the `curl-impersonate v2.2.2` bundles pinned the same way (`--tlsv1.3 --tls-max 1.3`, `--tls-max 1.2`) through a local ClientHello sniffer, the bundles drop exactly those, and our pinned 1.3 hellos are now byte-identical to theirs — same cipher list, same extension list in the same order, same 512-byte size. `standby-rezka.tv` answers the hybrid with `alert_illegal_parameter`; it answers the bundle's 200. `fingerprint::pinned_drop` holds the per-profile table (Firefox keeps `extended_master_secret` and `renegotiation_info` at 1.3, so it is not one rule for all three), and `apply_profile` applies it through `suppress_extensions` — an extension merely missing from `extension_order` is still emitted, only later, and one the profile supplies verbatim would come from `raw_extensions` — plus a cipher-suite filter. The 1.2 builder loses the padding extension too, matching the bundles' 198/210-byte 1.2 hellos against our 185/199. Two gaps stay documented: `compress_certificate` cannot be offered in a 1.2 hello (rustls offers it only when 1.3 is offered as well), so those constants stop one extension short of the bundles, and Firefox's 1.3 hello still omits ECH. JA4's version field now falls back to the ClientHello's legacy version when a hello carries no `supported_versions`: a 1.2-only browser hello is not a TLS 1.0 client, which is what the field said before. The harness gains `dump13`/`dump12` so the pinned shapes can be dumped without a network. |
||
|---|---|---|
| .cargo | ||
| .github/workflows | ||
| crates | ||
| docs | ||
| vendor | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.yml | ||
| Cross.toml | ||
| domains.txt | ||
| install.ps1 | ||
| install.sh | ||
| README.md | ||
| tcp16.json | ||
| whitelist_sni.txt | ||
"Маяк у гаснущего горизонта свободного интернета"
Сквозь цифровые сумерки. Смотритель маяка, Runni
🦀 DPI Detector (Native Rust Engine)
Нативный инструмент для анализа цензуры и блокировок трафика на чистом Rust:
- Никаких внешних зависимостей: без сторонних рантаймов, без C/C++ библиотек и системного OpenSSL — только
rustls,ringиRustCrypto. - Легковесный: статический бинарник ≈3.9 МБ (4–6 МБ на роутерных сборках, UPX — ≈1.4 МБ), потребление оперативной памяти в работе — 3–6 МБ RAM.
- Максимальная кросс-платформенность: нативная поддержка роутеров (MIPS, ARM), Windows (от Windows 7 до 11), Linux musl, macOS и Android (Termux).
- Интерактивный TUI + Batch: полноценное стрелочное меню в терминале и быстрый тихий запуск при передаче аргументов командной строки.
🚀 Быстрый запуск в 1 строку (Без установки)
🐧 Linux, macOS, роутеры Keenetic, OpenWrt & Entware
curl -fsSL https://raw.githubusercontent.com/Runnin4ik/dpi-detector/rust/install.sh | sh
📱 Android (Termux)
curl -fsSL https://raw.githubusercontent.com/Runnin4ik/dpi-detector/rust/install.sh | sh
Скрипт автоматически определит Termux, установит нативный Android-бинарник в $PREFIX/bin/dpi-detector и запустит его. Для повторного запуска достаточно ввести dpi-detector.
🪟 Windows (PowerShell)
irm https://raw.githubusercontent.com/Runnin4ik/dpi-detector/rust/install.ps1 | iex
📦 Готовые бинарные сборки
Все сборки полностью статичны и не требуют внешних библиотек:
| Платформа / Устройство | Архитектура | Бинарник в релизах |
|---|---|---|
| Windows 10, 11, Server | x86_64 | dpi-detector-windows-x86_64.exe |
| Windows 7, 8, Server 2008/2012 | x86_64 | dpi-detector-windows-7-x86_64.exe |
| Linux (PC, Серверы, VPS) | x86_64 musl | dpi-detector-linux-x86_64 |
| Роутеры ARM64 (Keenetic Hopper/Titan 2, RPi, OpenWrt) | aarch64 musl | dpi-detector-linux-arm64 |
| Роутеры ARMv7 (Keenetic Titan 1/Hero, Asus RT-AX58U) | armv7hf musl | dpi-detector-linux-armv7 (и -upx) |
| Роутеры MIPS LE (Keenetic Viva/Giga, MT7621, OpenWrt) | mipsel musl | dpi-detector-linux-mipsel (и -upx) |
| Роутеры MIPS BE (Atheros, Qualcomm, OpenWrt) | mips musl | dpi-detector-linux-mips (и -upx) |
| macOS Apple Silicon (M1, M2, M3, M4) | aarch64 | dpi-detector-macos-arm64 |
| macOS Intel | x86_64 | dpi-detector-macos-intel |
| Android ARM64 (Смартфоны, планшеты, Termux, ADB) | aarch64 bionic | dpi-detector-android-arm64 |
| Android ARM32 (Старые устройства, Android TV) | armv7 bionic | dpi-detector-android-armv7 |
🛠️ Установка на роутеры (Keenetic / OpenWrt / Entware)
При запуске на роутере скрипт install.sh:
- Автоматически находит директорию
/opt/bin(Entware) и устанавливает бинарник туда (сохраняется после перезагрузки). - Контролирует свободное место: если на накопителе осталось
< 7 МБ, скрипт автоматически скачивает компактную версию со сжатием UPX (~1.4 МБ вместо ~3.9 МБ), защищая роутер от переполнения Flash илиtmpfs. - Заменяет файл атомарно (
.tmp.$$->dpi-detector) с проверкой архитектуры через--version(при сбое UPX автоматически переключается на стандартный бинарник). - Поддерживает принудительный выбор версии:
DPI_UPX=1(компактная) илиDPI_UPX=0(стандартная). - Поддерживает запуск напрямую через SSH:
# Установка и запуск меню
curl -fsSL https://raw.githubusercontent.com/Runnin4ik/dpi-detector/rust/install.sh | sh
# Повторный запуск после установки
/opt/bin/dpi-detector
⚙️ Параметры командной строки (CLI)
Использование: dpi-detector [ОПЦИИ]
Опции:
-t, --tests <TESTS> Строка выбора тестов (например, '012', '1', '2')
--json Вывод в машиночитаемом JSON
-v, --verbose Подробное / отладочное логирование
-l, --lang <LANG> Язык интерфейса (ru, en, zh, fa, auto). По умолчанию auto
--profile <PROFILE> Региональный профиль цензуры (ru, ir, cn, global) [по умолчанию: ru]
--legend Показать легенду статусов и выйти
-p, --proxy <URL> URL SOCKS5-прокси (socks5://127.0.0.1:1080)
-c, --concurrency <N> Лимит параллельных запросов
-d, --domain <DOMAIN> Конкретные домены для проверки (флаг можно повторять: -d vk.com -d ya.ru)
-o, --output <PATH> Путь к файлу отчёта
--domains <PATH> Путь к файлу со списком доменов
--tcp16 <PATH> Путь к файлу целей TCP16
--ascii Только ASCII для старых консолей (без Unicode-глифов и рамок)
--fingerprint <PROFILE> Профиль отпечатка (rustls|custom|chrome|safari): ClientHello, User-Agent и заголовки, преамбула HTTP/2. custom — форма Firefox 133, chrome — Chrome 107 / Edge 99-101, safari — Safari 15.5-18.4 из curl-impersonate; все предлагают h2
--burst <N> Fingerprint/Сибирская блокировка (тест 6): одновременных запросов за раунд [по умолчанию: 4]
--burst-timeout <SECONDS> Тест 6: таймаут одного рукопожатия, секунды [по умолчанию: 8]
--burst-profiles <LIST> Fingerprint для теста 6: all|rustls,custom(firefox133),chrome(chrome107),safari(safari155) [по умолчанию: all]
--burst-tls <VERSION> Версия TLS для теста 6: 1.2|1.3 [по умолчанию: 1.3]
--burst-alpn <PROTOCOL> ALPN для теста 6: h2 (предлагает h2 с откатом на http/1.1)|http/1.1 (только http/1.1) [по умолчанию: h2]
--trace [<PATH>] Тест 6: трасса по строке на попытку (раунд, отпечаток, домен, статус, деталь, мс); без пути — в stderr, с путём — в указанный файл [по умолчанию: выключено]
-h, --help Показать справку
-V, --version Показать версию
Справка и все сообщения локализованы: dpi-detector --lang en --help, --lang zh --help, --lang fa --help. Язык интерфейса задаётся --lang, таблицы, легенда и предупреждения следуют за ним; Farsi выводится латиницей (Finglish). Статусные бейджи и имена протоколов (OK, BLOCKED, TLS RST, SNI, ClientHello) остаются латиницей в любом языке. Машинный вывод --json от языка не зависит.
Номера доступных тестов:
0— Информация о сети и системе (внешний IP, провайдер, AS, страна, тип NAT)1— Доступность DNS-серверов (проверка UDP 53, DoH Cloudflare/Google/Quad9/Yandex/AdGuard)2— Доступность сайтов (HTTP, TLS 1.2, TLS 1.3, классификация блокировок DPI)3— Доступность CDN и хостингов (тест 16 KB) (детект сброса соединений после 14–34 КБ данных)4— Поиск белых SNI (перебор рабочих SNI для заблокированных AS: цель отбирается по 16 KB-обрыву, TLS RST и TLS DROP)5— Доступность Telegram (проверка доступности и скорости датацентров DC1–DC5)6— Fingerprint/Сибирская блокировка (N одновременных TLS-рукопожатий на один домен выбранными отпечатками: сколько ответили и что именно перестало отвечать)7— Легенда статусов (справка по вердиктам)
🧬 Тест 6: Fingerprint / Сибирская блокировка
Тест отвечает на вопрос «блокируют ли именно мою форму TLS-рукопожатия»: на каждый домен отправляются N одновременных попыток выбранными отпечатками, и видно, какие формы ответили, а какие нет.
При выборе теста 6 открывается свой экран настроек: число одновременных рукопожатий, таймаут одного рукопожатия в секундах, версия TLS (1.2/1.3), протокол HTTP в ALPN (h2 — как у браузеров, с откатом на http/1.1, или http/1.1 — только он), домен (пустое поле — весь список, как в тесте 2; вставленный URL приводится к имени хоста) и Fingerprint (по умолчанию все). Поле домена подсвечивается: серый фон — строка выбрана, синий — в поле идёт ввод. Q/Esc отменяет тест.
Попытка не заканчивается рукопожатием: сразу после него тем же профилем (и тем же User-Agent/заголовками) уходит GET / по тому протоколу, который выбрал ALPN. Попытка считается удачной, только если вернулся ответ — обрыв на запросе, редирект на чужой хост, HTTP BLOCK или таймаут чтения видны как своя строка, а не как «ответили». Редирект внутри своего сайта вердикта не меняет: основной домен, его поддомены и два поддомена одного домена — одна и та же площадка, поэтому holod.media → www.holod.media, m.holod.media → holod.media и m.youtube.com → www.youtube.com читаются как OK, а на чужой домен (www.instagram.com → www.facebook.com) — как REDIR с именем хоста в детали.
В таблице M/N по каждому отпечатку: зелёный — все попытки прошли целиком, жёлтый — часть, красный — никто. В колонке «Детали» — все вердикты строки, сгруппированные по статусу, самые частые первыми (TLS ALERT ×8 · TLS RST ×4), так что двенадцать потерянных рукопожатий не схлопываются в одну «победившую» ошибку.
Во время прогона печатается одна живая строка Тестируем: CHROME 107 3/4 12/35 · 00:04 — какая форма сейчас в эфире, который это раунд из скольких, сколько хостов раунда готово и сколько идёт время.
Из неинтерактивного запуска настройки задаются флагами --burst, --burst-timeout, --burst-profiles, --burst-tls, --burst-alpn; в --json результат лежит в ключе results.fingerprint_burst.
Трасса попыток (--trace)
--trace [<PATH>] пишет по строке на каждую попытку — время UTC, раунд, форма, домен, номер попытки, статус, деталь и миллисекунды:
13:19:30Z r1/1 chrome www.google.com #1 tls_alert alert_unexpected_message 719 ms
13:19:30Z r1/1 chrome standby-rezka.tv #1 tls_alert alert_illegal_parameter 1031 ms
Без пути трасса идёт в stderr, с путём — в указанный файл: --trace C:\temp\trace.log и Get-Content -Wait C:\temp\trace.log во втором окне дают разбор прямо во время работы интерактивного меню. Видно, какая именно из N попыток, с какой задержкой и по какой причине не прошла — тишина до нашего таймаута, сброс за десятки миллисекунд или алерт от сервера.
Отдельно стоит знать про разбор провалившейся колонки: если попытки умирают с задержкой ровно в таймаут (8000 мс при --burst-timeout 8) у всех форм сразу, а быстрые хосты в том же раунде успевают ответить, — это провал пути, а не блокировка отпечатка. В замерах через VPN-тоннель целые раунды из 35 доменов умирали так двадцать-сорок секунд и так же сами восстанавливались, причём жертвой оказывалась то одна форма, то другая: при последовательных раундах такое окно целиком попадает в одну колонку и выглядит как её блокировка.
Профили отпечатка (--fingerprint)
Профиль задаёт три слоя сразу — форму TLS-рукопожатия, User-Agent с набором и порядком заголовков и преамбулу HTTP/2, — и все три взяты из одного набора curl-impersonate v2.2.2, поэтому клиент, выглядящий как curl_chrome107 в одном слое, выглядит так же и в остальных.
rustls(по умолчанию) — контрольная форма: никого не изображает.custom— Firefox 133,chrome— Chrome 107 / Edge 99-101,safari— Safari 15.5–18.4.
Два отличия от настоящих браузеров, о которых стоит знать при сравнении: ECH не отправляется (его синтез ломает рукопожатие с Google и Cloudflare), а accept-encoding всегда identity — тесты 2–4 считают байты до обрыва, и согласованное сжатие сделало бы эти числа зависящими от сжимаемости ответа. Свой user_agent в config.yml перебивает профиль.
🏗️ Сборка из исходников
Для самостоятельной сборки потребуется установленный Rust (версии 1.80+):
git clone https://github.com/Runnin4ik/dpi-detector.git -b rust
cd dpi-detector
cargo build --release -p dpi-detector
Скомпилированный бинарник будет находиться в target/release/dpi-detector (dpi-detector.exe на Windows).
Для статической сборки под Windows без внешних зависимостей CRT:
RUSTFLAGS="-C target-feature=+crt-static" cargo build --release -p dpi-detector
💬 Обратная связь
- Чат: t.me/DPI_detector
- Баги и предложения: Issues
- Лицензия: MIT