netsleuth/README.ru.md
Andrew Pimenov e27ccdfab5 give --watch a per-tick verdict and optional webhook alerts
watch.py never imported interpret and had no notion of a verdict at all -
summarize_cycle() now runs the same latency_findings/speed_findings/
overall_verdict pipeline the one-shot report uses, so every tick carries
status/score/finding_ids and the dashboard caption shows a verdict line.
Real improvement on its own, independent of whether a webhook exists.

New alerting.py carries the whole notification contract as pure functions:
should_fire() only fires on an actual transition, into a state the user
opted into via watch.webhook_on, debounced by
watch.webhook_min_interval_seconds (floored at 30 in a validator) so a
flapping link can't spam an endpoint every tick. Recovery is its own
opt-in "recovered" token, separate from configuring "crit", since wanting
outage alerts doesn't imply wanting a second ping when they clear.
build_payload() is flat JSON, not shaped for Slack/Discord - that's the
receiver's problem. post_webhook() swallows every failure and returns a
bool; a dead endpoint must never interrupt the loop watching it.

New --webhook URL flag overrides watch.webhook_url per run.

Verified the tick sequence end-to-end outside the Rich Live loop -
sending real OS signals to test --watch's Ctrl-C handling in this shell
wasn't reliable, so exercised should_fire/build_payload/render_dashboard/
to_report in the same order run_watch calls them.

Part of plans/zesty-zooming-wilkinson.md (gitignored).
2026-08-12 22:26:31 +03:00

124 lines
19 KiB
Markdown
Raw 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.

# netsleuth
[English](README.md)
Кроссплатформенная CLI-утилита для глубокой диагностики сети: кто на самом деле ваш провайдер, действительно ли трафик идёт через VPN, где деградирует маршрут и что реально выдаёт канал.
```console
$ netsleuth --quick
netsleuth 0.1.0 · auto mode · Windows
Verdict: ok (100/100) — No problems found on this connection.
Report written to logs/report_AS64500_20260808T191200Z.md
```
Всё остальное — ASN, вердикт по VPN, задержки, маршрут, скорость — попадает в только что записанный отчёт, а не в консоль; терминал показывает только заголовок и куда смотреть дальше. По умолчанию это один Markdown-файл на английском; `--format` управляет тем, что именно пишется (см. «Полезное» ниже).
## Установка
Требования:
- Python 3.14 или новее
- Windows, Linux или macOS
- Доступ в интернет (netsleuth — сетевой инструмент; без сети он деградирует корректно, но сделает немного)
- Опционально: нативный бинарник Ookla `speedtest` в `PATH` — лучший уровень замера скорости
- Опционально: `mtr` в `PATH` — лучшие данные по маршруту
```bash
git clone https://github.com/Horizontal42/netsleuth
cd netsleuth
uv sync
uv run netsleuth
```
Нужны `--tcp-trace` и `--ndt7`? Это опциональные extras, по умолчанию не ставятся:
```bash
uv sync --extra tcptrace --extra ndt7
```
Без `uv`:
```bash
python -m pip install -r requirements.txt
python -m pip install -e .
netsleuth
```
Настройка не требуется. Копируйте `.env.example` в `.env` только если у вас есть опциональные API-ключи.
### Опциональные инструменты для разных платформ
Ничего из этого не обязательно — без них netsleuth просто корректно деградирует — но каждый инструмент ставится по-разному в зависимости от ОС:
- **`uv`** сам по себе: `curl -LsSf https://astral.sh/uv/install.sh | sh` на Linux/macOS; `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` на Windows.
- **`mtr`** (лучшие данные о маршруте): `sudo apt install mtr` (Debian/Ubuntu), `sudo dnf install mtr` (Fedora), `sudo pacman -S mtr` (Arch), `brew install mtr` (macOS). Под Windows сборки нет — этот уровень каскада traceroute просто пропускается, и автоматически используется Windows ICMP API, никаких действий не требуется.
- **Ookla `speedtest`** (лучший уровень замера скорости): ставьте официальный CLI-бинарник Ookla с их сайта или из пакетного менеджера вашей ОС — не путайте с не связанным с ним community-пакетом `speedtest-cli` на Python: netsleuth вызывает именно бинарник Ookla и разбирает его вывод в `--format=json`.
- **Npcap/libpcap** (нужны только для extra `--tcp-trace`): на Windows ставьте Npcap; на Linux libpcap обычно уже есть в дистрибутиве, но для отправки сырых пакетов не от root нужно `sudo setcap cap_net_raw+eip $(readlink -f $(which python3))` (либо просто запускать под `sudo`); на macOS libpcap встроен, но scapy всё равно требует запуска от root (`sudo netsleuth --tcp-trace`).
## Что он делает
- **Проверка captive portal** — выполняется первой, до всего остального: три пробы в стиле `generate_204` ловят логин-стену публичного wifi до того, как она сделает все остальные замеры похожими на сломанное соединение.
- **Идентификация** — внешний IPv4 *и* IPv6, обратный DNS, ASN, организация, гео и тип адреса: домашний, мобильный, хостинговый или корпоративный. Проверяется по шести независимым источникам, поэтому один rate-limit не обнуляет результат. Также определяет carrier-grade NAT (RFC 6598) и трансляцию NAT64/464XLAT (RFC 7050), если они есть.
- **Оценка VPN/прокси** — взвешенный вердикт (`none` / `likely` / `confirmed`) из наличия туннельного интерфейса, аномалии MTU, расхождения шлюза и внешнего IP, флагов Cloudflare WARP, флагов proxy/hosting от провайдеров данных, типа сети в PeeringDB, расхождения таймзоны ОС и гео, а также несовпадения ASN DNS-резолвера.
- **Тест утечки DNS** — перечисляет резолверы *по каждому сетевому адаптеру*, поэтому ловит классический случай, когда туннельный адаптер чистый, а на Wi-Fi-адаптере остался резолвер провайдера. Также определяет утечку EDNS Client Subnet.
- **BGP-разведка** — аплинки, пиры, клиенты, анонсируемые префиксы, стабильность маршрутов, присутствие на IX и размер клиентского конуса по CAIDA ASRank.
- **Репутация** — локально кэшируемые списки FireHOL (ни один запрос не покидает машину), экспозиция по Shodan InternetDB и опционально классические зоны DNSBL.
- **Задержки и маршрут** — статистика пингов по каждому хосту с настоящими джиттером/потерями/mdev/p95/p99 и трассировка, которой никогда не нужны права администратора. Первый хоп диагностируется отдельно от остального маршрута, поэтому потери или медлительность там правильно относятся к вашему роутеру/Wi-Fi, а не к провайдеру. Каждый хоп размечен своим ASN, именем AS и страной, а маршрут, уходящий крюком через третью страну, отмечается отдельно.
- **Скорость** — каскадный замер (бинарник Ookla → Cloudflare → fast.com → опционально M-Lab NDT7) с раздельными оценками bufferbloat на приём и передачу и отметкой, если сервер замера оказался в другой стране.
- **TLS handshake RTT** (`--tls`) — TCP RTT, время TLS 1.3-рукопожатия и время до первого байта к эталонным сервисам; отдельно отмечает сервер, чьё рукопожатие непропорционально медленнее сети (упирается в CPU TLS-терминатора, а не в сеть). Также фиксирует SHA-256-отпечаток сертификата, издателя и дни до истечения, отмечает провал валидации и сверяет с опциональным закреплённым отпечатком (`tls.pinned_fingerprints` в `config.yaml`), чтобы поймать корпоративный/провайдерский TLS-перехватывающий middlebox или настоящий MITM.
- **DNS: системный vs DoH** (`--dns-advanced`) — сравнивает ответы и задержку системного резолвера с Cloudflare/Google/Quad9 DoH, а также проверяет фиктивный IP резолвера на признаки прозрачного DNS-прокси, перехватывающего 53 порт.
- **Диверсификация маршрута / Anycast** (`--path-diversity`) — по заголовку Cloudflare `CF-RAY` определяет, какая точка (PoP) реально обслуживает трафик, и отмечает международную петлю маршрута, если она в другой стране, чем клиент.
- **Бенчмарк префиксов AS** (`--prefix-bench`) — пингует первый хост нескольких анонсируемых префиксов вашей собственной AS, чтобы найти точку с наименьшей задержкой. Ограничено 256 префиксами и выключено по умолчанию — это выборочная проба своей же сети, а не её сканирование.
- **Самопроверка DPI / портов** (`--my-server <host>`) — одиночная TCP-проба по 6 фиксированным портам **сервера, которым вы владеете**, с классификацией инъекции TCP RST, тихой фильтрации или чистого ответа. Это не портсканер: один хост за запуск, фиксированный короткий список портов, и цель никогда не берётся из вашего собственного внешнего IP или из данных BGP — почему именно так, см. [ARCHITECTURE.ru.md](ARCHITECTURE.ru.md).
## Полезное
| Флаг | Что делает |
|---|---|
| `--quick` | Экспресс-прогон ~10 с: только пинги эталонных хостов, без замера скорости |
| `--full` | 60–120 с: полный замер скорости и полный MTR |
| `--target AS64500` / `--target 64500` / `--target 1.2.3.4` / `--target example.com` | Исследовать конкретную сеть вместо своего подключения — префикс `AS` перед голым номером ASN необязателен |
| `--target-host <ip\|domain>` | Добавить хост в веер пингов и трассировок |
| `--speedtest-server <id\|url>` | Зафиксировать конкретный сервер замера скорости |
| `--watch` | Непрерывный мониторинг с живой панелью — чтобы поймать плавающие обрывы. Пишет один тайм-ряд `logs/watch_*.json` за сессию, а не отдельный отчёт на каждый тик. Каждый тик получает вердикт (тот же движок оценки, что и в одноразовом отчёте), показанный в подписи панели |
| `--webhook <url>` | Вместе с `--watch` отправляет POST с JSON-алертом на этот URL при переходе вердикта (в `warn`/`crit`, либо при восстановлении, если `watch.webhook_on` включает `"recovered"`). С дебаунсом через `watch.webhook_min_interval_seconds` (пол 30с), чтобы флапающий канал не мог его заспамить. Переопределяет `watch.webhook_url` в `config.yaml` |
| `--compare a.json b.json` | Сравнить два прошлых отчёта: до/после смены VPN, до/после звонка провайдеру. Только чтение — без проб и сетевых запросов. Читает отчёты, полученные с `--format json` |
| `--trend N` | Спарклайн-тренд по последним N сохранённым JSON-отчётам для этой сети. Только чтение — сеть определяется по самому новому отчёту уже на диске, проб не требуется |
| `--no-auto-diff` | Пропустить однострочную сводку vs-прошлый-прогон, которую прогон иначе печатает автоматически, когда пишет JSON и есть более ранний JSON-отчёт для той же сети |
| `--dnsbl` | Включить проверку по классическим зонам DNSBL |
| `--tls` | Замерить TCP/TLS RTT рукопожатия и TTFB к эталонным сервисам. Входит в `--full` |
| `--dns-advanced` | Сравнить системный DNS с DoH и проверить прозрачный DNS-прокси. Входит в `--full` |
| `--path-diversity` | Сравнить гео клиента с реальной точкой Cloudflare, обслуживающей трафик. Входит в `--full` |
| `--prefix-bench` | Пропинговать первый хост нескольких анонсируемых префиксов своей AS в поисках точки с наименьшей задержкой. **Не** входит в `--full` — включается явно |
| `--my-server <host>` | Проверить сервер, **которым вы владеете**, на блокировку портов / инъекцию RST. Только один хост — CIDR отклоняется сразу. **Не** входит в `--full` — включается явно |
| `--ndt7` | Включить M-Lab NDT7 (замер публикуется как открытые данные CC0, включая ваш IP). В интерактивном терминале сначала печатает уведомление о согласии и спрашивает подтверждение; в неинтерактивном — продолжает без вопроса |
| `--tcp-trace` | Включить TCP-SYN-трассировку через scapy сквозь middlebox'ы, режущие ICMP (нужны extras `tcptrace` и Npcap на Windows или root на Unix). Если не может запуститься — откатывается на обычный каскад |
| `--format md,ru-md,json,prom,csv,all,none` | Выбрать, какие артефакты отчёта писать (по умолчанию: `md`). Повторяемый флаг и через запятую (`--format md,json`); любое использование заменяет дефолт, а не дополняет его. `prom`/`csv` — экспорт метрик для скриптинга/мониторинга, те же числа, что в `json`, но плоские. `all` пишет все форматы, `none` — ничего. Игнорируется с `--watch`/`--compare` |
| `--ru` | Сокращение для `--format ru-md` |
| `--json` | Сокращение для `--format json` |
| `--interface <имя\|ip>` | Пустить все исходящие пробы (идентификация, BGP/репутация, задержки, трассировка, спидтест) через конкретный адаптер вместо маршрута ОС по умолчанию — например, сравнить VPN-адаптер с "сырым" подключением. Принимает имя адаптера или один из его IP. Сразу падает с ошибкой и списком всех адаптеров, если имя не резолвится или адаптер выключен; если адаптер есть, но маршрута в интернет нет — прогон продолжается и честно это показывает, а не падает. У Windows `tracert` нет опции адреса-источника для IPv4, поэтому этот уровень трассировки при форсированном интерфейсе пропускается (отмечено в отчёте) — все остальные бэкенды, включая Windows ICMP API, флаг соблюдают |
Каждый прогон пишет в `./logs/` выбранные артефакты: Markdown-отчёт на английском, его полный перевод на русский (`.ru.md` — те же разделы, те же таблицы, те же числа, со взаимными ссылками, если пишутся оба), JSON-дамп со всеми сырыми ответами провайдеров без усечения и/или экспорт метрик `.prom`/`.csv` (те же числа, что в JSON-дампе, но плоские — для Prometheus/Grafana или таблицы). По умолчанию — только английский Markdown; чтобы поменять дефолт навсегда, задайте `output.formats` в `config.yaml`, либо используйте `--format` на конкретном прогоне. `./logs/` в gitignore — отчёты содержат ваш внешний IP, город, координаты и название провайдера.
netsleuth никогда не требует администратора или root. Там, где привилегии дали бы данные лучше, он деградирует до непривилегированного метода и пишет об этом в отчёте.
## Для разработчиков
Стек: Python 3.14+, Typer, Rich, httpx, pydantic-settings, dnspython, psutil, icmplib, ctypes.
```bash
uv sync --all-extras --group dev
uv run pytest -q
uv run netsleuth --quick
uv export --no-hashes --format requirements-txt -o requirements.txt # после изменения зависимостей
```
Архитектура, потоки данных и обоснование выбора каждого источника: [ARCHITECTURE.ru.md](ARCHITECTURE.ru.md).
## Благодарности
netsleuth читает данные из RIPEstat, CAIDA ASRank, Team Cymru, PeeringDB, Cloudflare, ip-api.com, freeipapi.com, ipinfo.io, ipwho.is, Shodan InternetDB, проекта блоклистов FireHOL, Netflix fast.com и M-Lab. Все они используются в рамках бесплатных/безключевых тарифов.
## Лицензия
[PolyForm Noncommercial 1.0.0](LICENSE) — выбрана потому что источники репутации по умолчанию (Shodan InternetDB, свободные зеркала Spamhaus) разрешены только для некоммерческого использования; эта лицензия удерживает любой форк в тех же рамках.