Commit graph

127 commits

Author SHA1 Message Date
Andrew Pimenov
51c01bd8c3 fix default gateway detection picking wrong route with multiple adapters
_default_gateway() took whichever 0.0.0.0 line route print printed first,
not the one Windows actually uses (lowest metric). With a VPN adapter
installed (Radmin VPN, Tailscale, ...) its default-ish route can print
before the real one. Verified live: was reporting Radmin VPN's 26.0.0.1
while Ethernet (the interface actually carrying traffic, confirmed via
primary_interface_ip()) has gateway 192.168.3.1; now reports that correctly.
Parsing extracted into pure _parse_windows_default_gateway(), tested against
a captured multi-adapter route print sample.
2026-08-09 13:00:24 +03:00
Andrew Pimenov
4888862305 fix false-positive sustained-loss verdict on long runs of silent hops
path_findings() flagged ICMP rate limiting as real loss whenever two
consecutive traceroute hops both went silent, but real transit paths
routinely have long runs of silent hops (10 in a row, confirmed on a live
--full run) while the destination itself answers cleanly. Only flag loss
that persists all the way to the last hop in the trace; a run that clears
before the end is now ignored regardless of how many hops it spans.
2026-08-09 13:00:19 +03:00
Andrew Pimenov
4ffc39906b add --format/--ru/--json flags to control which report artifacts get written
Every run used to write all three files (en.md, ru.md, json) unconditionally.
Default is now English Markdown only, sourced from output.formats in
config.yaml; --format/--ru/--json pick exactly what a run writes, and any
explicit --format replaces the default rather than adding to it (all/none
supported). write_report() moved from a fixed 3-positional-arg signature to
a {format: text} mapping so writes are conditional instead of always-three.
The en/ru Markdown cross-link header is now only set when both languages are
actually being written.
2026-08-09 13:00:13 +03:00
Andrew Pimenov
29b3fb1bb4 fill in real github url in readmes and license notice 2026-08-09 11:49:11 +03:00
Andrew Pimenov
031f85df5e netsleuth in docs, license, env example and release workflow 2026-08-09 11:43:03 +03:00
Andrew Pimenov
483989c1bc rename package netcheck to netsleuth (name taken on pypi) 2026-08-09 11:43:00 +03:00
Andrew Pimenov
0336be4080 gitignore agent process docs: tasks.md and specs/plans stay local only 2026-08-09 11:37:36 +03:00
Andrew Pimenov
f031a0b48f ci: add release build workflow, gate pypi publish on name conflict 2026-08-09 11:29:38 +03:00
Andrew Pimenov
286e684d41 cli: remember ndt7 consent in a cache marker so repeat runs stop re-prompting 2026-08-09 11:28:18 +03:00
Andrew Pimenov
168cd14f51 ip_geo: russian dual-stack mismatch note alongside english 2026-08-09 11:27:20 +03:00
Andrew Pimenov
ce97958cc9 netinfo: relay powershell adapter names as base64 to dodge codepage corruption 2026-08-09 11:25:29 +03:00
Andrew Pimenov
b4c541aecf docs: sync readme, architecture and tasks with the russian report feature 2026-08-09 11:14:56 +03:00
Andrew Pimenov
2e84c434c0 exporter: translate module skip/warning reasons into russian 2026-08-09 11:11:08 +03:00
Andrew Pimenov
1821674376 cli: write and print the russian sibling report
diagnose() now computes both filenames up front so each markdown
render can link to its sibling, calls render_markdown twice (en/ru),
and passes both strings to write_report. run() prints all three
report paths.
2026-08-09 11:07:19 +03:00
Andrew Pimenov
d53ab9a0d6 exporter: render russian markdown report alongside english
Thread a lang parameter through render_markdown and every section
helper so the same report dict can render an English or Russian
report; inline _t() calls carry the strings, findings fall back from
_ru fields to their English siblings. write_report now emits a third
.ru.md file alongside .json and .md, and render_markdown can stamp a
sibling cross-link line right after the H1.
2026-08-09 11:06:24 +03:00
Andrew Pimenov
4c2da2e831 dns_leak: russian note alongside english 2026-08-09 04:10:44 +03:00
Andrew Pimenov
9e5ea923aa reputation: russian rationale alongside english 2026-08-09 04:10:08 +03:00
Andrew Pimenov
db407595d6 interpret: russian title/detail/advice alongside english findings 2026-08-09 04:09:01 +03:00
Andrew Pimenov
04cf37ef7e models: add optional russian translation fields to finding, reputation, dns leak 2026-08-09 04:07:34 +03:00
Andrew Pimenov
6f47ea7736 license: finalize as PolyForm Noncommercial 1.0.0 2026-08-09 04:05:11 +03:00
Andrew Pimenov
21d38d7acb add TASKS.md tracking outstanding work 2026-08-09 03:59:21 +03:00
Andrew Pimenov
4497e2a871 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.
2026-08-09 03:58:54 +03:00
Andrew Pimenov
5da12ae629 ip_geo: never let cf-trace's own ip leak into a target lookup
cf-trace has no way to describe an arbitrary target — it always
echoes the ip of whoever is making the http request. gather_identity
included it in the merge candidates unconditionally, so 'netcheck
--target 8.8.8.8' spliced this machine's own connecting ip onto
8.8.8.8's asn/city/org fields from the other providers, producing a
report that named an ip that was neither the target nor honestly
labeled as the local egress. exclude cf-trace from the merge whenever
an explicit target ip was requested; it's still fetched and returned
separately for the connection section's own colo/warp fields.
2026-08-09 03:52:25 +03:00
Andrew Pimenov
5e4aa8b101 exporter: surface a skipped module's warning as its placeholder reason
unavailable() only ever read module.errors for the placeholder detail
text, so a deliberately skipped section (speed in --quick or target
mode, which reports its reason as a warning, not an error) always
fell through to the generic 'no data was collected for this section'
line even though the real reason ('speedtest skipped in target mode')
was sitting right there in the json. spec says a skipped speedtest
must say why, not just that it's missing.
2026-08-09 03:49:53 +03:00
Andrew Pimenov
44b1c4a39d cli: treat asn target mode as own-network identity like auto mode
'--target AS<n>' still describes this machine's own egress (an asn
target isn't something ip_geo can look up directly), same as auto
mode, but the ipv4/ipv6 forcing added for the egress_v4 mislabel fix
only checked target_kind is None. widen it to also cover 'asn' so
target-mode runs get the same protocol-pinned identity lookup as
auto mode instead of falling back to the unpinned shared client.
2026-08-09 03:47:59 +03:00
Andrew Pimenov
bfe9529667 exporter: name the report after the target, not always the local egress asn
target mode's filename fell back to the local machine's egress asn
unconditionally, so 'netcheck --target AS15169' wrote report_<local-asn>_*
instead of report_AS15169_*. meta.target already carries the right
subject when one was given; prefer it and fall back to the egress asn
only in auto mode.
2026-08-09 03:47:55 +03:00
Andrew Pimenov
3425e87dbf interpret: label a non-responding hop instead of printing None
a hop with no icmp reply has ip=None; the sustained-loss finding
formatted it straight into the report text as the literal string
'(None)'. seen live in a --quick smoke test report.
2026-08-09 03:43:57 +03:00
Andrew Pimenov
be03dd4f04 cli: force ipv4 for the egress_v4 identity lookup
the shared http client has no address family pinned, so on a dual-stack
host the 'egress_v4' gather could connect over ipv6 (os/happy-eyeballs
preference) and hand back an ipv6 literal labeled as egress_v4, with a
mismatched ip_version pulled from a different provider's own record.
mirror the existing egress_v6 forcing so each slot can only resolve
its own protocol. caught live during the --quick smoke test: the
report was analyzing an unrelated ipv6 asn instead of the real isp.
2026-08-09 03:43:54 +03:00
Andrew Pimenov
10c5939bcb bgp: clamp cache age to zero against windows mtime clock skew
time.time() - path.stat().st_mtime went negative under load when NTFS
stamped a write's mtime slightly ahead of the wall clock read a moment
later, making a ttl_hours=0 cache lookup serve stale data instead of
refetching. surfaced as a flaky failure in test_cached_json_refetches_once_the_entry_expires
during the full suite run before task 40's smoke test.
2026-08-09 03:39:35 +03:00
Andrew Pimenov
0d6d5405b3 watch: cycle scheduling, live dashboard and one time-series artifact per session 2026-08-09 03:37:01 +03:00
Andrew Pimenov
21b71a4056 icmp_win: bound trace_hops_win to a total timeout budget and stop it blocking process exit
subprocess_seconds was being applied per hop instead of as a total trace
budget, so a path with several non-responding hops could block for up to
max_hops * subprocess_seconds. The blocking call also ran on a
ThreadPoolExecutor worker via asyncio.to_thread, which concurrent.futures
joins at process exit - so an orphaned hop stuck past its timeout kept the
whole CLI alive long after diagnose() had already written its report.

Divide the budget across hops (capped at 4s per hop) with an early exit
once the total deadline passes, and dispatch the call on a raw daemon
thread instead so it can never hold the process open even in the worst
case.
2026-08-09 03:33:12 +03:00
Andrew Pimenov
3bf021e1f1 cli: force utf-8 console output on windows to stop compare crashing 2026-08-09 03:26:16 +03:00
Andrew Pimenov
4e4956ceeb compare: diff identity, latency, speed and findings between two reports 2026-08-09 03:24:03 +03:00
Andrew Pimenov
335ff0975e cli: typer app, phase pipeline and opt-in tcp-syn trace tier 2026-08-09 03:24:00 +03:00
Andrew Pimenov
e4f9c3b5b7 exporter: explicit placeholders so a failed section never reads as a healthy one 2026-08-09 02:46:53 +03:00
Andrew Pimenov
879b974e57 exporter: markdown rendering with sparklines and hop bars 2026-08-09 02:44:43 +03:00
Andrew Pimenov
71b1fc95e3 exporter: report assembly, strict json and atomic writes 2026-08-09 02:42:22 +03:00
Andrew Pimenov
62040021af speed: bufferbloat probe running concurrently with saturation 2026-08-09 02:39:24 +03:00
Andrew Pimenov
36b2941667 speed: ookla, cloudflare, fast.com and ndt7 cascade tiers 2026-08-09 02:38:19 +03:00
Andrew Pimenov
5eec7aacab speed: throughput math, cfL4 header parsing, bufferbloat delta 2026-08-09 02:37:14 +03:00
Andrew Pimenov
8d38788671 dns_leak: per-adapter resolver enumeration and echo probes 2026-08-09 02:33:44 +03:00
Andrew Pimenov
0999f85235 dns_leak: per-adapter asn mismatch and ecs leak detection 2026-08-09 02:32:13 +03:00
Andrew Pimenov
b151a934b7 traceroute: mtr to icmp_win to icmplib to system-binary cascade 2026-08-09 02:31:25 +03:00
Andrew Pimenov
c020936d7f icmp_win: guard windows-only imports so the module is importable cross-platform 2026-08-09 02:30:15 +03:00
Andrew Pimenov
717068fa06 icmp_win: unprivileged IcmpSendEcho2 ping and traceroute engine 2026-08-09 02:28:07 +03:00
Andrew Pimenov
4c58f8add8 icmp_win: decode ICMP_ECHO_REPLY buffers for both pointer widths 2026-08-09 02:26:09 +03:00
Andrew Pimenov
c35725a50f latency: ping summarization and unprivileged backend fan-out 2026-08-09 02:25:04 +03:00
Andrew Pimenov
d7b4c49786 reputation: internetdb, captcha risk and reputation assembly 2026-08-09 02:21:57 +03:00
Andrew Pimenov
30e001dc81 reputation: decode dnsbl 127.255.255.x error range as unavailable 2026-08-09 02:20:30 +03:00
Andrew Pimenov
48f7865987 reputation: firehol netset cache with local range index 2026-08-09 02:19:43 +03:00