mirror of
https://github.com/Horizontal42/netsleuth.git
synced 2026-11-10 22:42:03 +03:00
Filenames only carry a time-of-day now (report_<asn>_HH-MM-SSZ) since the date lives in the directory path; history lookups walk the tree with rglob and sort by full path instead of basename, since the basename alone no longer encodes recency across days.
131 lines
23 KiB
Markdown
131 lines
23 KiB
Markdown
# 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/2026/08/08/report_AS64500_19-12-00Z.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` или `--quic`? Это опциональные extras, по умолчанию не ставятся:
|
||
|
||
```bash
|
||
uv sync --extra tcptrace --extra ndt7 --extra quic
|
||
```
|
||
|
||
Без `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 и страной, а маршрут, уходящий крюком через третью страну, отмечается отдельно. На dual-stack соединении задержка до эталонных хостов IPv6 измеряется наравне с IPv4 и сравнивается бок о бок — ловит туннелированный или плохо пропиренный путь IPv6, невидимый для инструментов, показывающих лишь тот стек, который случайно выбрала ОС.
|
||
- **Скорость** — каскадный замер (бинарник 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) реально обслуживает трафик, и отмечает международную петлю маршрута, если она в другой стране, чем клиент.
|
||
- **Детект ECMP / нескольких маршрутов** (`--ecmp`) — трассирует пару эталонных хостов несколько раз и сравнивает, какой следующий хоп появлялся на каждом TTL, ловя балансировку нагрузки по хопам, из-за которой трассировки выглядят непоследовательными, и отмечая, если одна из веток балансировки заметно хуже другой.
|
||
- **RTT QUIC/HTTP3-рукопожатия** (`--quic`, нужен extra `quic`) — измеряет время QUIC-рукопожатия целиком и проверяет, доступен ли вообще UDP/443: если QUIC не проходит, а обычное TCP-соединение с тем же хостом проходит, сеть именно роняет или дросселирует QUIC, незаметно ухудшая предпочтение Chrome/YouTube к HTTP/3 так, что никакая TCP-диагностика этого не поймает.
|
||
- **Обнаружение MTU пути** (`--pmtud`) — бинарным поиском находит самый большой пакет с флагом Don't Fragment, реально доходящий до цели, отличая путь, который просто уже (роутер корректно говорит «нужна фрагментация»), от чёрной дыры PMTUD (крупные пакеты молча исчезают — классический симптом VPN/PPPoE «SSH подключается и зависает»).
|
||
- **Бенчмарк префиксов 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` — включается явно |
|
||
| `--ecmp` | Трассировать пару эталонных хостов несколько раз (`ecmp.runs`, потолок 5) для детекта балансировки нагрузки по хопам и замера, насколько неравноценны ветки. **Не** входит в `--full` — умножает трафик трассировки к тем же целям, включается явно |
|
||
| `--ipv6` / `--no-ipv6` | Принудительно включить или выключить замер задержки до эталонных хостов IPv6, переопределяя `probing.ipv6` (по умолчанию `auto`: только на dual-stack соединении). На v4-only хосте `auto` — это no-op, можно не трогать |
|
||
| `--pmtud` | Бинарным поиском найти самый большой пакет с DF, доходящий до каждого хоста из `pmtud.targets` (по умолчанию: 2 хоста), для обнаружения чёрной дыры MTU пути. **Не** входит в `--full` — включается явно |
|
||
| `--quic` | Измерить время QUIC/HTTP3-рукопожатия и проверить, не заблокирован ли UDP/443 (нужен `uv sync --extra quic`; иначе деградирует до чёткой заметки «не установлен»). Не поддерживает `--interface` — у aioquic нет опции привязки адреса-источника. **Не** входит в `--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) разрешены только для некоммерческого использования; эта лицензия удерживает любой форк в тех же рамках.
|