From 4497e2a871928f00a26c164f6574c8da71e00a04 Mon Sep 17 00:00:00 2001 From: Andrew Pimenov Date: Sun, 9 Aug 2026 03:58:54 +0300 Subject: [PATCH] docs: sync readme and architecture with the final module and flag set readme's sample console output showed a multi-line summary table (connection/egress/vpn/latency) the tool has never printed; the real stdout is three lines (banner, verdict, file paths). also documented: the tcptrace/ndt7 extras install command, bare-asn --target syntax, ndt7's interactive consent prompt, tcp-trace's cascade fallback, and that --watch writes one artifact per session rather than per tick. architecture's storage section had the peeringdb cache path wrong (.cache/peeringdb/*.json vs the real .cache/pdb-net-*.json) and never mentioned the watch_*.json artifact from task 39. the file-size exceptions note only covered exporter.py; interpret.py, cli.py, models.py, bgp.py, ip_geo.py, netinfo.py, speed.py, reputation.py and probes/traceroute.py all cleared ~200 lines too and are now listed with the reason each stayed one file. test count updated from ~120 (written before a single module existed) to the real ~385. --- ARCHITECTURE.md | 24 ++++++++++++++++++------ ARCHITECTURE.ru.md | 24 ++++++++++++++++++------ README.md | 26 ++++++++++++++------------ README.ru.md | 30 ++++++++++++++++-------------- 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9cbb5f8..bef78e6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -20,17 +20,28 @@ src/netcheck/ traceparse.py Pure text -> TraceHop parsers for Windows, Linux and BSD/macOS output. interpret.py Pure verdict engine: thresholds -> Finding[], VPN scoring, bufferbloat grade. speed.py Cascading speedtest, throughput math, cfL4 header parsing, bufferbloat probe. - exporter.py JSON and Markdown rendering, atomic writes into ./logs/. Deliberately over - the file-size guideline to keep both output formats in one place. + exporter.py JSON and Markdown rendering, atomic writes into ./logs/. compare.py --compare: diff two saved JSON reports. watch.py --watch: periodic re-run loop with a live Rich dashboard. probes/ latency.py Ping fan-out and jitter/loss/mdev statistics. - traceroute.py Cascade: mtr --json -> icmp_win -> icmplib -> system binary text. + traceroute.py Cascade: mtr --json -> icmp_win -> icmplib -> system binary text, plus the opt-in tcp_trace tier. icmp_win.py ctypes IcmpSendEcho2 / Icmp6SendEcho2 engine (Windows only). dns_leak.py Per-adapter resolver enumeration, echo probes, ECS detection. ``` +**File-size exceptions.** The ~200-line guideline (Global Constraints) held for most modules, but these grew past it during implementation and were kept as single files rather than split, because each one is a single cohesive responsibility that splitting would only scatter across more files with a thinner reason to draw the line anywhere in particular: + +- `exporter.py` (~475 lines) — JSON and Markdown are two renderers of the same report shape; keeping them together is what guarantees they can never drift on which sections exist. +- `cli.py` (~465 lines) — the Typer app, every flag, and the full `diagnose()` phase pipeline; splitting flag parsing from what the flags toggle would just add an import hop. +- `models.py` (~330 lines) — every shared dataclass plus `to_jsonable()`; this is deliberately the one place that defines the report's vocabulary. +- `interpret.py` (~320 lines) — threshold bands, the latency/path/speed finding generators, VPN scoring and bufferbloat grading all reason over the same `Finding[]` shape. +- `bgp.py` (~305 lines) — four independent providers (RIPEstat, CAIDA ASRank, Team Cymru, PeeringDB) plus the disk cache they share. +- `ip_geo.py` (~280 lines) — six independent provider normalizers plus the field-wise merge that reconciles them. +- `netinfo.py`, `speed.py` (~260 lines each) — OS-specific capability probing and the four-tier speedtest cascade both fan out into several backends that don't split cleanly by single responsibility. +- `reputation.py` (~250 lines) — FireHOL netsets, Shodan InternetDB and DNSBL decoding are three unrelated reputation sources sharing one result shape. +- `probes/traceroute.py` (~225 lines) — the four-tier cascade plus the opt-in `tcp_trace` tier. + ## Data flow ``` @@ -78,9 +89,10 @@ Two hard concurrency constraints: ## Storage -- `./logs/report__.{md,json}` — one pair per run. Compact ISO timestamps because Windows forbids `:` in filenames. Falls back to `report_unknown_…` when the ASN lookup fails entirely. Written temp-file-plus-`os.replace()`, always atomic. Gitignored: reports contain the external IP, city, coordinates and ISP name. +- `./logs/report__.{md,json}` — one pair per run. `` is the target's subject in target mode (an ASN, IP or domain) and the local egress ASN in auto mode. Compact ISO timestamps because Windows forbids `:` in filenames. Falls back to `report_unknown_…` when the ASN lookup fails entirely. Written temp-file-plus-`os.replace()`, always atomic. Gitignored: reports contain the external IP, city, coordinates and ISP name. +- `./logs/watch__.json` — one time-series artifact per `--watch` session, not one report per tick. - `./.cache/firehol/*.netset` — downloaded blocklists, refreshed per `providers.firehol_refresh_hours`. -- `./.cache/peeringdb/*.json` — PeeringDB responses, valid for `providers.peeringdb_cache_hours`. +- `./.cache/pdb-net-.json`, `./.cache/pdb-netixlan-.json` — PeeringDB responses, valid for `providers.peeringdb_cache_hours`. - `./config.yaml` — all non-secret settings. `./.env` — the three optional API keys and nothing else. ## Configuration @@ -89,4 +101,4 @@ Two hard concurrency constraints: ## Tests -`uv run pytest -q`. Roughly 120 tests covering the parsers, normalizers, verdict engine, DNSBL decoding, throughput math, ping statistics, serialization and the report diff. Glue is deliberately untested: live HTTP, real ICMP, Typer wiring, `psutil`/`ctypes` calls and the `--watch` timing loop. Traceroute fixtures live in `tests/fixtures/traceroute/{windows,linux,darwin}/` and include a cp866-encoded Russian Windows sample, because that encoding is exactly what breaks naive parsers. +`uv run pytest -q`. Roughly 385 tests covering the parsers, normalizers, verdict engine, DNSBL decoding, throughput math, ping statistics, serialization and the report diff. Glue is deliberately untested: live HTTP, real ICMP, Typer wiring, `psutil`/`ctypes` calls and the `--watch` timing loop. Traceroute fixtures live in `tests/fixtures/traceroute/{windows,linux,darwin}/` and include a cp866-encoded Russian Windows sample, because that encoding is exactly what breaks naive parsers. diff --git a/ARCHITECTURE.ru.md b/ARCHITECTURE.ru.md index a73fe63..a639d21 100644 --- a/ARCHITECTURE.ru.md +++ b/ARCHITECTURE.ru.md @@ -20,17 +20,28 @@ src/netcheck/ traceparse.py Чистые парсеры текста в TraceHop для Windows, Linux и BSD/macOS. interpret.py Чистый движок вердиктов: пороги -> Finding[], скоринг VPN, оценка bufferbloat. speed.py Каскадный замер скорости, математика пропускной способности, разбор cfL4, bufferbloat. - exporter.py Рендер JSON и Markdown, атомарная запись в ./logs/. Намеренно превышает - лимит размера файла, чтобы оба формата вывода жили в одном месте. + exporter.py Рендер JSON и Markdown, атомарная запись в ./logs/. compare.py --compare: сравнение двух сохранённых JSON-отчётов. watch.py --watch: цикл периодических прогонов с живой панелью Rich. probes/ latency.py Веер пингов и статистика джиттера/потерь/mdev. - traceroute.py Каскад: mtr --json -> icmp_win -> icmplib -> системный бинарник. + traceroute.py Каскад: mtr --json -> icmp_win -> icmplib -> системный бинарник, плюс опциональный уровень tcp_trace. icmp_win.py Движок ctypes IcmpSendEcho2 / Icmp6SendEcho2 (только Windows). dns_leak.py Перечисление резолверов по адаптерам, echo-пробы, детект ECS. ``` +**Исключения по размеру файла.** Лимит ~200 строк (Общие ограничения) выдержан для большинства модулей, но эти выросли за него в процессе реализации и остались одним файлом, а не были разбиты, потому что каждый — единая цельная ответственность, и разбиение только раскидало бы её по файлам без чёткой причины проводить границу где-то конкретно: + +- `exporter.py` (~475 строк) — JSON и Markdown — два рендера одной и той же формы отчёта; то, что они вместе, гарантирует, что они никогда не разойдутся в том, какие разделы существуют. +- `cli.py` (~465 строк) — приложение Typer, все флаги и весь конвейер фаз `diagnose()`; разделение разбора флагов и того, что они переключают, добавило бы только лишний импорт. +- `models.py` (~330 строк) — все общие датаклассы плюс `to_jsonable()`; это намеренно единственное место, определяющее словарь отчёта. +- `interpret.py` (~320 строк) — пороговые полосы, генераторы находок по задержке/маршруту/скорости, скоринг VPN и оценка bufferbloat — всё рассуждает над одной формой `Finding[]`. +- `bgp.py` (~305 строк) — четыре независимых источника (RIPEstat, CAIDA ASRank, Team Cymru, PeeringDB) плюс общий для них дисковый кэш. +- `ip_geo.py` (~280 строк) — шесть независимых нормализаторов провайдеров плюс пополевое слияние, которое их примиряет. +- `netinfo.py`, `speed.py` (~260 строк каждый) — определение возможностей по ОС и каскад замера скорости из четырёх уровней оба веерятся по нескольким бэкендам, которые не разбиваются чисто по единой ответственности. +- `reputation.py` (~250 строк) — netset'ы FireHOL, Shodan InternetDB и декодирование DNSBL — три несвязанных источника репутации с одной общей формой результата. +- `probes/traceroute.py` (~225 строк) — каскад из четырёх уровней плюс опциональный уровень `tcp_trace`. + ## Поток данных ``` @@ -78,9 +89,10 @@ src/netcheck/ ## Хранение -- `./logs/report__.{md,json}` — пара файлов на прогон. Компактный ISO-таймстамп, потому что Windows запрещает `:` в именах файлов. При полном провале определения ASN — `report_unknown_…`. Запись через временный файл и `os.replace()`, всегда атомарно. В gitignore: отчёты содержат внешний IP, город, координаты и название провайдера. +- `./logs/report__.{md,json}` — пара файлов на прогон. `` — это цель в режиме target (ASN, IP или домен) и локальный ASN исходящего адреса в авто-режиме. Компактный ISO-таймстамп, потому что Windows запрещает `:` в именах файлов. При полном провале определения ASN — `report_unknown_…`. Запись через временный файл и `os.replace()`, всегда атомарно. В gitignore: отчёты содержат внешний IP, город, координаты и название провайдера. +- `./logs/watch__.json` — один тайм-ряд на сессию `--watch`, а не отдельный отчёт на каждый тик. - `./.cache/firehol/*.netset` — скачанные блоклисты, обновляются по `providers.firehol_refresh_hours`. -- `./.cache/peeringdb/*.json` — ответы PeeringDB, действительны `providers.peeringdb_cache_hours`. +- `./.cache/pdb-net-.json`, `./.cache/pdb-netixlan-.json` — ответы PeeringDB, действительны `providers.peeringdb_cache_hours`. - `./config.yaml` — все несекретные настройки. `./.env` — три опциональных API-ключа и ничего больше. ## Конфигурация @@ -89,4 +101,4 @@ src/netcheck/ ## Тесты -`uv run pytest -q`. Около 120 тестов: парсеры, нормализаторы, движок вердиктов, декодирование DNSBL, математика пропускной способности, статистика пингов, сериализация и сравнение отчётов. Клей намеренно не тестируется: живой HTTP, настоящий ICMP, обвязка Typer, вызовы `psutil`/`ctypes` и цикл `--watch`. Фикстуры трассировок лежат в `tests/fixtures/traceroute/{windows,linux,darwin}/` и включают образец русской Windows в cp866 — именно эта кодировка ломает наивные парсеры. +`uv run pytest -q`. Около 385 тестов: парсеры, нормализаторы, движок вердиктов, декодирование DNSBL, математика пропускной способности, статистика пингов, сериализация и сравнение отчётов. Клей намеренно не тестируется: живой HTTP, настоящий ICMP, обвязка Typer, вызовы `psutil`/`ctypes` и цикл `--watch`. Фикстуры трассировок лежат в `tests/fixtures/traceroute/{windows,linux,darwin}/` и включают образец русской Windows в cp866 — именно эта кодировка ломает наивные парсеры. diff --git a/README.md b/README.md index bdf9837..b03c005 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,13 @@ Cross-platform CLI for deep network diagnostics: who your ISP actually is, wheth ```console $ netcheck --quick netcheck 0.1.0 · auto mode · Windows - -Connection Wi-Fi 2 · 192.168.1.34 · MTU 1500 · gw 192.168.1.1 -Egress 203.0.113.44 (AS64500 Example Telecom, Amsterdam NL) -VPN none confidence 0.10 -Latency 1.1.1.1 12.4 ms avg · jitter 1.9 ms · loss 0% -Verdict 🟢 healthy — no problems found - +Verdict: ok (100/100) — No problems found on this connection. Report written to logs/report_AS64500_20260808T191200Z.md logs/report_AS64500_20260808T191200Z.json ``` +Everything else — egress ASN, VPN verdict, latency, path, speed — lives in the two files it just wrote, not on the console; the terminal only tells you the headline and where to look. + ## Install Requirements: @@ -35,6 +31,12 @@ uv sync uv run netcheck ``` +Want `--tcp-trace` and `--ndt7` too? They're optional extras, not installed by default: + +```bash +uv sync --extra tcptrace --extra ndt7 +``` + Without `uv`: ```bash @@ -61,14 +63,14 @@ No configuration is required. Copy `.env.example` to `.env` only if you have opt |---|---| | `--quick` | ~10 s express run: reference-host pings only, no speedtest | | `--full` | ~60–120 s: full speedtest and full MTR cycles | -| `--target AS64500` / `--target 1.2.3.4` / `--target example.com` | Investigate a specific network instead of your own connection | +| `--target AS64500` / `--target 64500` / `--target 1.2.3.4` / `--target example.com` | Investigate a specific network instead of your own connection — the `AS` prefix on a bare ASN is optional | | `--target-host ` | Add an extra host to the ping and traceroute fan-out | | `--speedtest-server ` | Pin the speedtest to a specific server | -| `--watch` | Continuous monitoring with a live dashboard — for catching intermittent drops | -| `--compare a.json b.json` | Diff two earlier reports: before/after a VPN switch, before/after an ISP call | +| `--watch` | Continuous monitoring with a live dashboard — for catching intermittent drops. Writes one `logs/watch_*.json` time series per session, not one report per tick | +| `--compare a.json b.json` | Diff two earlier reports: before/after a VPN switch, before/after an ISP call. Read-only — no probing, no network calls | | `--dnsbl` | Opt in to classic DNSBL reputation zones | -| `--ndt7` | Opt in to M-Lab NDT7 (publishes your measurement, including your IP, as CC0 open data) | -| `--tcp-trace` | Opt in to a scapy TCP-SYN traceroute through ICMP-filtering middleboxes (needs Npcap or root) | +| `--ndt7` | Opt in to M-Lab NDT7 (publishes your measurement, including your IP, as CC0 open data). On an interactive terminal it prints the consent notice and asks first; on a non-interactive one it proceeds without asking | +| `--tcp-trace` | Opt in to a scapy TCP-SYN traceroute through ICMP-filtering middleboxes (needs the `tcptrace` extra plus Npcap on Windows or root on Unix). Falls through to the normal cascade when it cannot run | Every run writes two files into `./logs/`: a Markdown report to read and a JSON dump containing every raw provider response, untruncated. `./logs/` is gitignored — reports contain your external IP, city, coordinates and ISP name. diff --git a/README.ru.md b/README.ru.md index d37c652..3368321 100644 --- a/README.ru.md +++ b/README.ru.md @@ -6,18 +6,14 @@ ```console $ netcheck --quick -netcheck 0.1.0 · авто-режим · Windows - -Соединение Wi-Fi 2 · 192.168.1.34 · MTU 1500 · шлюз 192.168.1.1 -Внешний IP 203.0.113.44 (AS64500 Example Telecom, Амстердам NL) -VPN нет уверенность 0.10 -Задержка 1.1.1.1 12.4 мс сред. · джиттер 1.9 мс · потери 0% -Вердикт 🟢 всё в порядке — проблем не найдено - -Отчёт записан в logs/report_AS64500_20260808T191200Z.md +netcheck 0.1.0 · auto mode · Windows +Verdict: ok (100/100) — No problems found on this connection. +Report written to logs/report_AS64500_20260808T191200Z.md logs/report_AS64500_20260808T191200Z.json ``` +Всё остальное — ASN, вердикт по VPN, задержки, маршрут, скорость — попадает в два только что записанных файла, а не в консоль; терминал показывает только заголовок и куда смотреть дальше. + ## Установка Требования: @@ -35,6 +31,12 @@ uv sync uv run netcheck ``` +Нужны `--tcp-trace` и `--ndt7`? Это опциональные extras, по умолчанию не ставятся: + +```bash +uv sync --extra tcptrace --extra ndt7 +``` + Без `uv`: ```bash @@ -61,14 +63,14 @@ netcheck |---|---| | `--quick` | Экспресс-прогон ~10 с: только пинги эталонных хостов, без замера скорости | | `--full` | 60–120 с: полный замер скорости и полный MTR | -| `--target AS64500` / `--target 1.2.3.4` / `--target example.com` | Исследовать конкретную сеть вместо своего подключения | +| `--target AS64500` / `--target 64500` / `--target 1.2.3.4` / `--target example.com` | Исследовать конкретную сеть вместо своего подключения — префикс `AS` перед голым номером ASN необязателен | | `--target-host ` | Добавить хост в веер пингов и трассировок | | `--speedtest-server ` | Зафиксировать конкретный сервер замера скорости | -| `--watch` | Непрерывный мониторинг с живой панелью — чтобы поймать плавающие обрывы | -| `--compare a.json b.json` | Сравнить два прошлых отчёта: до/после смены VPN, до/после звонка провайдеру | +| `--watch` | Непрерывный мониторинг с живой панелью — чтобы поймать плавающие обрывы. Пишет один тайм-ряд `logs/watch_*.json` за сессию, а не отдельный отчёт на каждый тик | +| `--compare a.json b.json` | Сравнить два прошлых отчёта: до/после смены VPN, до/после звонка провайдеру. Только чтение — без проб и сетевых запросов | | `--dnsbl` | Включить проверку по классическим зонам DNSBL | -| `--ndt7` | Включить M-Lab NDT7 (замер публикуется как открытые данные CC0, включая ваш IP) | -| `--tcp-trace` | Включить TCP-SYN-трассировку через scapy сквозь middlebox'ы, режущие ICMP (нужен Npcap или root) | +| `--ndt7` | Включить M-Lab NDT7 (замер публикуется как открытые данные CC0, включая ваш IP). В интерактивном терминале сначала печатает уведомление о согласии и спрашивает подтверждение; в неинтерактивном — продолжает без вопроса | +| `--tcp-trace` | Включить TCP-SYN-трассировку через scapy сквозь middlebox'ы, режущие ICMP (нужны extras `tcptrace` и Npcap на Windows или root на Unix). Если не может запуститься — откатывается на обычный каскад | Каждый прогон пишет в `./logs/` два файла: Markdown-отчёт для чтения и JSON-дамп со всеми сырыми ответами провайдеров без усечения. `./logs/` в gitignore — отчёты содержат ваш внешний IP, город, координаты и название провайдера.