Пульс блокировок: как мы мониторим VPN-протоколы по регионам РФ в реальном времени
Полгода назад я пытался объяснить маме по телефону, почему у неё в Казани «интернет сломался», а у меня в Питере всё работает. Проблема была не в интернете — у неё лёг VLESS, на который я её посадил. А я об этом узнал только из её звонка.
Потом знакомый из Новосибирска написал, что у него XHTTP тоже отвалился. Я полез в чаты — там каша: у кого-то работает, у кого-то нет, кто-то перезагрузил роутер и «починилось», кто-то поменял порт и тоже «починилось». Системных данных — ноль. Одни анекдоты.
Тогда я задумался: а почему вообще нет публичной карты, показывающей какие VPN-протоколы работают в каком регионе? GoodbyeDPI есть, zapret есть, DPI-detector есть — а живого мониторинга нет. Собственно, я его и написал. Называется BlockPulse, код открыт, работает прямо сейчас.
Что происходит с блокировками VPN в 2026
Для тех, кто не следил за ситуацией вплотную: с декабря 2025-го ТСПУ (технические средства противодействия угрозам) научилось ловить VLESS. Не везде одновременно и не одинаково — сначала прилетело Татарстану и Удмуртии, потом поползло на Свердловскую область, потом на Москву. К марту 2026 массовые жалобы были уже отовсюду, но «отовсюду» — это очень размытое понятие.
Способ детекции, насколько можно судить по косвенным признакам, — поведенческий анализ плюс TLS-fingerprinting. VLESS+Reality маскируется под обычный HTTPS, но паттерн трафика всё-таки отличается от настоящего браузера: длины пакетов, интервалы между ними, соотношение upload/download. Для DPI-системы, которая видит миллионы потоков, эти аномалии статистически различимы.
С 15 апреля 2026 Роскомнадзор запустил систему «белого списка» — примерно 75 тысяч IP-адресов корпоративных VPN, которые не подвергаются фильтрации ТСПУ. Остальные — фильтруются, и фильтрация с каждым месяцем становится агрессивнее.
При этом XHTTP (он же SplitHTTP — более новый транспорт в Xray-core) до сих пор проходит в большинстве регионов. Hysteria2 (UDP-based, на QUIC) — тоже в основном жив, хотя менее стабилен из-за периодических блокировок UDP.
И вот ключевой момент: ситуация отличается от региона к региону и от провайдера к провайдеру. ТСПУ — это не одна кнопка в кабинете РКН. Это оборудование, физически распределённое по операторам связи, и конфигурации фильтрации на нём отличаются. То, что лежит на Ростелекоме в Казани, спокойно работает на МТС в Москве.
Идея: crowdsourced мониторинг
Идея простая до безобразия:
Пользователь запускает маленький скрипт на своём устройстве
Скрипт пробует подключиться к тестовым серверам через порты разных VPN-протоколов (TLS handshake)
Если ТСПУ блокирует протокол — handshake не проходит, получаем timeout или RST
Результат вместе с регионом пользователя (по GeoIP) уходит на API
Данные агрегируются и отображаются на интерактивной карте
Идеологически ничего нового. Но почему-то в удобном, автоматизированном виде этого до сих пор не существовало. Есть отдельные замеры в чатах, есть DPI-detector (который показывает сигнатуры, а не доступность), есть GoodbyeDPI/zapret (которые обходят, а не мониторят). А вот «какой протокол работает в моём регионе прямо сейчас» — не было.
Что конкретно тестируем
Пять проб на каждый запуск:
Протокол | Порт | Транспорт | Что проверяем |
|---|---|---|---|
VLESS Reality | 443 | TCP+TLS | Стандартный VLESS, маскировка под HTTPS (SNI: www.samsung.com) |
XHTTP #1 | 2083 | TCP+TLS | XHTTP Reality (SNI: www.microsoft.com) |
XHTTP #2 | 8743 | TCP+TLS | XHTTP Reality (SNI: github.com) |
XHTTP #3 | 47832 | TCP+TLS | XHTTP Reality, высокий порт (SNI: www.google.com) |
Hysteria2 | 29080 | UDP | QUIC-based, salamander obfuscation |
Для TCP-протоколов проба — полноценный TLS handshake с отключённой проверкой сертификата (нам не нужен валидный cert, нужно только узнать, пропускает ли сеть). Для Hysteria2 — UDP-пакет и ожидание ответа.
Таймаут: 8 секунд для TCP, 5 для UDP. Если за это время handshake не прошёл — считаем протокол заблокированным в данной сети.
Почему три варианта XHTTP — не случайность. ТСПУ может фильтровать по-разному:
По порту: блокировать всё кроме 443
По SNI: чёрный список конкретных доменов
По паттерну: анализ формы трафика
Три XHTTP на разных портах с разными SNI позволяют различить эти случаи. Если XHTTP на 2083 работает, а на 47832 — нет, значит фильтрация по порту. Если все три лежат — по паттерну.
Архитектура
Никакого Kubernetes, никакого Kafka — один Docker-контейнер, SQLite, Python.
┌──────────────┐ POST /api/probe ┌────────────────┐
│ CLI probe │ ───────────────────> │ API сервер │
│ (Python, │ │ (aiohttp) │
│ stdlib) │ │ │
└──────────────┘ │ SQLite DB │
│ │
┌──────────────┐ GET /api/pulse │ Telegram bot │
│ React SPA │ <────────────────── │ (aiogram) │
│ (Vite+TS) │ └────────────────┘
└──────────────┘ │
│ каждые 5 мин
▼
server-side probe
CLI probe — один файл на Python, ноль зависимостей (только stdlib: socket, ssl, urllib). Запускается одной командой:
# Linux / macOS — с авто-установкой Python
curl -sL https://blockpulse.ru/probe/install.sh | bash
# Windows PowerShell
irm https://blockpulse.ru/probe/install.ps1 | iex
# Или напрямую (если Python есть)
curl -sL https://blockpulse.ru/probe.py | python3
Скрипт-установщик проверяет наличие Python, если нет — ставит через apt/brew/winget, скачивает probe, запускает, отправляет результаты.
API получает probe-результат, делает GeoIP-lookup через ip-api.com (бесплатно, русские названия регионов), валидирует HMAC-подпись и складывает в SQLite. Rate limiting по IP не даёт спамить.
React SPA — фронтенд на Vite + React 19 + TypeScript + Tailwind CSS 4 + MapLibre GL. Интерактивная карта России: кликни на регион — увидишь детализацию по протоколам. Четыре страницы: главная с лендингом, карта-дашборд, инструкция по участию, описание проекта.
Telegram-бот — /check показывает текущую сводку, /probe даёт команду для запуска. Подписка на уведомления при изменении статуса в твоём регионе.
Server-side probe — бот сам прогоняет проверки каждые 5 минут со своего IP. Это даёт базовый фон данных, даже если пользователей пока мало.
Код probe
Ядро пробы — примерно 60 строк:
def probe_tls(ip, port, sni, timeout=8):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
start = time.monotonic()
try:
raw = socket.create_connection((ip, port), timeout=timeout)
except socket.timeout:
return False, 0, "connect_timeout"
except ConnectionRefusedError:
return False, 0, "refused"
except ConnectionResetError:
return False, 0, "reset"
except OSError as e:
return False, 0, str(e)[:60]
try:
wrapped = ctx.wrap_socket(raw, server_hostname=sni or ip)
ms = round((time.monotonic() - start) * 1000)
wrapped.close()
return True, ms, ""
except ssl.SSLError as e:
return False, 0, "tls:" + str(getattr(e, "reason", e))
finally:
raw.close()
ssl.CERT_NONE — мы не проверяем сертификат, нам важен сам факт, что TLS handshake прошёл. Если ТСПУ вмешалось — мы получим timeout (фильтрация) или reset (активный сброс).
Разница в типах ошибок информативна:
timeout — скорее всего ТСПУ дропает пакеты (пассивная фильтрация)
reset — ТСПУ активно рвёт соединение (RST injection)
tls:WRONG_VERSION_NUMBER — MITM или протокол на этом порту другой
refused — порт закрыт на самом сервере (не блокировка сети)
Каждый probe-результат подписывается HMAC-SHA256 с серверным секретом — это защищает от инъекции фейковых данных. Перед отправкой проверяется формат, допустимые протоколы, допустимые target’ы.
Безопасность
Отдельный пункт, потому что для open-source проекта это важно:
HMAC-подпись: каждый probe подписан секретом, без валидной подписи API отклоняет данные
Rate limiting: максимум 10 проб в минуту с одного IP, дальше 429
GeoIP-валидация: IP проверяется через внешний GeoIP-сервис, нельзя вручную подставить регион
Input sanitization: все входные данные проходят через whitelist (известные протоколы, target’ы)
Provider API: для интеграции сторонних VPN-сервисов — отдельная система ключей (Bearer token, SHA-256)
Никаких секретов в коде: все credentials через .env, в репозитории только .env.example
Ограничения
Проба TLS handshake — это не полная проверка VPN-протокола. Бывает так, что handshake проходит, а через 10-15 секунд ТСПУ обнаруживает характерный паттерн VPN-трафика и рвёт соединение. Наш тест этого не увидит — мы проверяем начальное соединение, а не длительную сессию.
Hysteria2 (UDP) тестируется упрощённо. Полноценный тест требует QUIC Client Hello, что сильно усложняет probe. Сейчас это скорее индикатор «UDP вообще ходит на этом порту», чем точная проверка протокола.
GeoIP не идеален. ip-api.com иногда определяет провайдера неточно, а регион — по месту регистрации блока адресов, а не по физическому расположению пользователя. Для уровня детализации «Москва vs Казань vs Новосибирск» — хватает, для «какой район Москвы» — нет.
Server-side probe — это данные с одного IP и одного провайдера. Реальная карта станет полезной, когда наберётся хотя бы пара десятков пользователей из разных регионов и сетей. Поэтому open source и поэтому эта статья.
Как использовать
Посмотреть карту: blockpulse.ru — интерактивная карта. Можно кликать на регионы, смотреть детализацию по протоколам.
Помочь собрать данные:
# Linux/macOS — автоустановка
curl -sL https://blockpulse.ru/probe/install.sh | bash
# Windows PowerShell
irm https://blockpulse.ru/probe/install.ps1 | iex
Скрипт использует только стандартную библиотеку Python (socket, ssl, urllib). Ноль зависимостей. Один файл, можно прочитать перед запуском: blockpulse.ru/probe.py
Telegram-бот: @vpnstatuschecker_bot — сводка, подписка на регион, команда для запуска probe.
Для VPN-провайдеров: есть Provider API с отдельными ключами — интеграция мониторинга в свой сервис. Пишите через бота.
Что дальше
Нормальный QUIC probe для Hysteria2
Детекция конкретного ISP (сейчас только регион, но данные собираются)
Временные графики: паттерн блокировок по времени суток
Public API для интеграции в VPN-клиенты (автовыбор протокола на основе реальных данных)
Больше тестовых серверов в разных странах
Мобильное приложение для постоянного фонового мониторинга
Если есть идеи или хотите контрибьютить — welcome. Данные открыты, API открыт, код открыт.
Код: GitHub Сайт: blockpulse.ru Telegram: @vpnstatuschecker_bot