The only feature in this batch adding a real dependency, approved up front and landed the same way tcptrace/ndt7 already do: an optional extra (uv sync --extra quic), imported lazily inside measure_quic() so a missing install degrades to a clear skip warning instead of a crash. Verified the actually-installed aioquic 1.3.0 API directly rather than assuming. connect() takes local_port but no local_host/source-address parameter at all, so --quic structurally cannot honor --interface - _quic_section() says so explicitly rather than silently measuring the OS-default path under a report claiming a forced adapter. Its context manager doesn't yield until the handshake finishes, so unlike the TLS RTT probe's two-connection subtraction trick, handshake time here is timed directly. HandshakeCompleted's fields matched what was expected; dropped a planned quic_version field since aioquic has no clean public API for it rather than reach into a private attribute. quic_verdict(quic_ok, tcp_ok) is the actual payoff: QUIC failing while plain TCP to the same host succeeds means the network specifically drops or throttles UDP/443, silently degrading Chrome/YouTube's HTTP/3 preference in a way no TCP-based diagnostic would ever catch. Both failing is ordinary unreachability already covered elsewhere, so it stays silent rather than duplicating that finding. Opt-in --quic, excluded from --full - an optionally-installed extra must never become a silent requirement. Added the quic extra to the release workflow's wheel-install verification. Live-tested against real cloudflare.com/google.com: real handshakes, ALPN h3 negotiated, correct end-to-end timing. This completes the 16-feature extension batch from plans/zesty-zooming-wilkinson.md (gitignored, local planning doc).
23 KiB
netsleuth
Кроссплатформенная CLI-утилита для глубокой диагностики сети: кто на самом деле ваш провайдер, действительно ли трафик идёт через VPN, где деградирует маршрут и что реально выдаёт канал.
$ 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— лучшие данные по маршруту
git clone https://github.com/Horizontal42/netsleuth
cd netsleuth
uv sync
uv run netsleuth
Нужны --tcp-trace, --ndt7 или --quic? Это опциональные extras, по умолчанию не ставятся:
uv sync --extra tcptrace --extra ndt7 --extra quic
Без uv:
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) — по заголовку CloudflareCF-RAYопределяет, какая точка (PoP) реально обслуживает трафик, и отмечает международную петлю маршрута, если она в другой стране, чем клиент. - Детект ECMP / нескольких маршрутов (
--ecmp) — трассирует пару эталонных хостов несколько раз и сравнивает, какой следующий хоп появлялся на каждом TTL, ловя балансировку нагрузки по хопам, из-за которой трассировки выглядят непоследовательными, и отмечая, если одна из веток балансировки заметно хуже другой. - RTT QUIC/HTTP3-рукопожатия (
--quic, нужен extraquic) — измеряет время 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.
Полезное
| Флаг | Что делает |
|---|---|
--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.
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.
Благодарности
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 — выбрана потому что источники репутации по умолчанию (Shodan InternetDB, свободные зеркала Spamhaus) разрешены только для некоммерческого использования; эта лицензия удерживает любой форк в тех же рамках.