mirror of
https://github.com/Horizontal42/netsleuth.git
synced 2026-11-10 18:02:04 +03:00
Scoped down from the original plan: traceroute and --path-diversity's v6 legs are deferred as a documented follow-up, since extending the trace cascade to a second address family is real additional work that doesn't share the trick that made latency cheap. Latency alone captures the core value (a tunneled or poorly-peered IPv6 path is otherwise invisible to tools that only report whichever stack the OS picked) at a fraction of the risk. Turns out no family-detection code was needed at all. icmp_win's ping_samples_win() calls the IPv4-only socket.gethostbyname(), which raises on a v6 literal - already caught by ping_host()'s existing bare except-fallback to TCP, which handles v6 fine via asyncio.open_connection. icmplib handles v6 natively on Linux/macOS. So v6 hosts ride the existing ping_fanout() completely unmodified, tagged with a "<label>-v6" PingResult label instead of a parallel report section - exporter, metrics.py and compare.py needed zero changes since they already key everything off label. New probing.reference_hosts_v6/probing.ipv6 (auto/on/off) config, --ipv6/--no-ipv6 override, interpret.dual_family_findings() for net.ipv6_unreachable and latency.v6_much_slower. Live-tested with --ipv6 forced on a v6-unreachable host: every row fell back to method=tcp with 100% loss as expected. Caught and fixed a wording bug in the same pass - the unreachable finding said "on this dual-stack connection" unconditionally, which is wrong when --ipv6 forces the check on a host that isn't actually dual-stack. Part of plans/zesty-zooming-wilkinson.md (gitignored).
127 lines
21 KiB
Markdown
127 lines
21 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/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 и страной, а маршрут, уходящий крюком через третью страну, отмечается отдельно. На 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, ловя балансировку нагрузки по хопам, из-за которой трассировки выглядят непоследовательными, и отмечая, если одна из веток балансировки заметно хуже другой.
|
||
- **Бенчмарк префиксов 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, можно не трогать |
|
||
| `--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) разрешены только для некоммерческого использования; эта лицензия удерживает любой форк в тех же рамках.
|