mirror of
https://github.com/Horizontal42/netsleuth.git
synced 2026-11-11 01:02:10 +03:00
rename package netcheck to netsleuth (name taken on pypi)
This commit is contained in:
parent
0336be4080
commit
483989c1bc
45 changed files with 163 additions and 163 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
[project]
|
[project]
|
||||||
name = "netcheck"
|
name = "netsleuth"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Cross-platform CLI for deep network diagnostics: ISP/VPN/ASN identity, latency, path and bandwidth"
|
description = "Cross-platform CLI for deep network diagnostics: ISP/VPN/ASN identity, latency, path and bandwidth"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "PolyForm-Noncommercial-1.0.0"
|
license = "PolyForm-Noncommercial-1.0.0"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
authors = [{ name = "netcheck contributors" }]
|
authors = [{ name = "netsleuth contributors" }]
|
||||||
keywords = ["network", "diagnostics", "traceroute", "bgp", "vpn", "speedtest"]
|
keywords = ["network", "diagnostics", "traceroute", "bgp", "vpn", "speedtest"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typer>=0.12",
|
"typer>=0.12",
|
||||||
|
|
@ -24,7 +24,7 @@ tcptrace = ["scapy>=2.5"]
|
||||||
ndt7 = ["websockets>=12"]
|
ndt7 = ["websockets>=12"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
netcheck = "netcheck.cli:main"
|
netsleuth = "netsleuth.cli:main"
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
|
|
@ -38,9 +38,9 @@ requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/netcheck"]
|
packages = ["src/netsleuth"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
filterwarnings = ["error::DeprecationWarning:netcheck.*"]
|
filterwarnings = ["error::DeprecationWarning:netsleuth.*"]
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ colorama==0.4.6 ; sys_platform == 'win32'
|
||||||
# pytest
|
# pytest
|
||||||
# typer
|
# typer
|
||||||
dnspython==2.8.0
|
dnspython==2.8.0
|
||||||
# via netcheck
|
# via netsleuth
|
||||||
exceptiongroup==1.3.1 ; python_full_version < '3.11'
|
exceptiongroup==1.3.1 ; python_full_version < '3.11'
|
||||||
# via
|
# via
|
||||||
# anyio
|
# anyio
|
||||||
|
|
@ -33,12 +33,12 @@ httpcore==1.0.9
|
||||||
# via httpx
|
# via httpx
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
# via
|
# via
|
||||||
# netcheck
|
# netsleuth
|
||||||
# pytest-httpx
|
# pytest-httpx
|
||||||
hyperframe==6.1.0
|
hyperframe==6.1.0
|
||||||
# via h2
|
# via h2
|
||||||
icmplib==3.0.4
|
icmplib==3.0.4
|
||||||
# via netcheck
|
# via netsleuth
|
||||||
idna==3.18
|
idna==3.18
|
||||||
# via
|
# via
|
||||||
# anyio
|
# anyio
|
||||||
|
|
@ -54,15 +54,15 @@ packaging==26.3
|
||||||
pluggy==1.6.0
|
pluggy==1.6.0
|
||||||
# via pytest
|
# via pytest
|
||||||
psutil==7.2.2
|
psutil==7.2.2
|
||||||
# via netcheck
|
# via netsleuth
|
||||||
pydantic==2.13.4
|
pydantic==2.13.4
|
||||||
# via
|
# via
|
||||||
# netcheck
|
# netsleuth
|
||||||
# pydantic-settings
|
# pydantic-settings
|
||||||
pydantic-core==2.46.4
|
pydantic-core==2.46.4
|
||||||
# via pydantic
|
# via pydantic
|
||||||
pydantic-settings==2.15.0
|
pydantic-settings==2.15.0
|
||||||
# via netcheck
|
# via netsleuth
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via
|
# via
|
||||||
# pytest
|
# pytest
|
||||||
|
|
@ -76,17 +76,17 @@ pytest-httpx==0.36.2
|
||||||
python-dotenv==1.2.2
|
python-dotenv==1.2.2
|
||||||
# via pydantic-settings
|
# via pydantic-settings
|
||||||
pyyaml==6.0.3
|
pyyaml==6.0.3
|
||||||
# via netcheck
|
# via netsleuth
|
||||||
rich==15.0.0
|
rich==15.0.0
|
||||||
# via
|
# via
|
||||||
# netcheck
|
# netsleuth
|
||||||
# typer
|
# typer
|
||||||
shellingham==1.5.4
|
shellingham==1.5.4
|
||||||
# via typer
|
# via typer
|
||||||
tomli==2.4.1 ; python_full_version < '3.11'
|
tomli==2.4.1 ; python_full_version < '3.11'
|
||||||
# via pytest
|
# via pytest
|
||||||
typer==0.27.1
|
typer==0.27.1
|
||||||
# via netcheck
|
# via netsleuth
|
||||||
typing-extensions==4.16.0
|
typing-extensions==4.16.0
|
||||||
# via
|
# via
|
||||||
# anyio
|
# anyio
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from netcheck.cli import main
|
from netsleuth.cli import main
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
@ -5,8 +5,8 @@ from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from netcheck.config import Providers
|
from netsleuth.config import Providers
|
||||||
from netcheck.models import BgpEvent
|
from netsleuth.models import BgpEvent
|
||||||
|
|
||||||
# These calls return the whole routing history of an ASN; on a large ISP that is
|
# These calls return the whole routing history of an ASN; on a large ISP that is
|
||||||
# tens of megabytes unless the window and row count are pinned.
|
# tens of megabytes unless the window and row count are pinned.
|
||||||
|
|
@ -110,7 +110,7 @@ import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Awaitable, Callable
|
from typing import Awaitable, Callable
|
||||||
|
|
||||||
from netcheck.models import IxpPresence
|
from netsleuth.models import IxpPresence
|
||||||
|
|
||||||
|
|
||||||
def parse_asrank(payload: dict) -> tuple[int | None, int | None, int | None]:
|
def parse_asrank(payload: dict) -> tuple[int | None, int | None, int | None]:
|
||||||
|
|
@ -174,7 +174,7 @@ async def cached_json(
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
from netcheck.models import BgpIntel
|
from netsleuth.models import BgpIntel
|
||||||
|
|
||||||
|
|
||||||
def build_bgp_intel(
|
def build_bgp_intel(
|
||||||
|
|
@ -13,25 +13,25 @@ import httpx
|
||||||
import typer
|
import typer
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from netcheck import __version__
|
from netsleuth import __version__
|
||||||
from netcheck.compare import diff_reports, load_report, render_diff
|
from netsleuth.compare import diff_reports, load_report, render_diff
|
||||||
from netcheck.config import Settings, load_settings
|
from netsleuth.config import Settings, load_settings
|
||||||
from netcheck.exporter import build_report, egress_asn, render_markdown, report_filename, write_report
|
from netsleuth.exporter import build_report, egress_asn, render_markdown, report_filename, write_report
|
||||||
from netcheck.interpret import (
|
from netsleuth.interpret import (
|
||||||
assess_vpn,
|
assess_vpn,
|
||||||
gather_vpn_signals,
|
gather_vpn_signals,
|
||||||
latency_findings,
|
latency_findings,
|
||||||
path_findings,
|
path_findings,
|
||||||
speed_findings,
|
speed_findings,
|
||||||
)
|
)
|
||||||
from netcheck.ip_geo import dual_stack_mismatch, gather_identity
|
from netsleuth.ip_geo import dual_stack_mismatch, gather_identity
|
||||||
from netcheck.models import Finding, IpGeo, LocalNet, ModuleResult, SpeedResult
|
from netsleuth.models import Finding, IpGeo, LocalNet, ModuleResult, SpeedResult
|
||||||
from netcheck.netinfo import collect_local_net, detect_capabilities, is_tunnel_iface
|
from netsleuth.netinfo import collect_local_net, detect_capabilities, is_tunnel_iface
|
||||||
from netcheck.orchestration import gather_modules, run_module, utc_now_iso
|
from netsleuth.orchestration import gather_modules, run_module, utc_now_iso
|
||||||
from netcheck.probes.dns_leak import collect_dns_leak
|
from netsleuth.probes.dns_leak import collect_dns_leak
|
||||||
from netcheck.probes.latency import ping_fanout, tcp_connect_rtt
|
from netsleuth.probes.latency import ping_fanout, tcp_connect_rtt
|
||||||
from netcheck.probes.traceroute import traceroute
|
from netsleuth.probes.traceroute import traceroute
|
||||||
from netcheck.speed import NDT7_CONSENT_NOTICE
|
from netsleuth.speed import NDT7_CONSENT_NOTICE
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
# A legacy console codepage (e.g. cp1251) cannot encode the emoji badges and
|
# A legacy console codepage (e.g. cp1251) cannot encode the emoji badges and
|
||||||
|
|
@ -150,7 +150,7 @@ async def _identity(client: httpx.AsyncClient, settings: Settings, options: Opti
|
||||||
async def _bgp_section(client, settings: Settings, asn: str | None, raw: dict):
|
async def _bgp_section(client, settings: Settings, asn: str | None, raw: dict):
|
||||||
if not asn:
|
if not asn:
|
||||||
return ModuleResult(name="bgp", status="skipped", warnings=["no ASN was resolved for this target"])
|
return ModuleResult(name="bgp", status="skipped", warnings=["no ASN was resolved for this target"])
|
||||||
from netcheck.bgp import collect_bgp
|
from netsleuth.bgp import collect_bgp
|
||||||
|
|
||||||
key = settings.peeringdb_api_key.get_secret_value() if settings.peeringdb_api_key else None
|
key = settings.peeringdb_api_key.get_secret_value() if settings.peeringdb_api_key else None
|
||||||
intel, payloads = await collect_bgp(
|
intel, payloads = await collect_bgp(
|
||||||
|
|
@ -161,7 +161,7 @@ async def _bgp_section(client, settings: Settings, asn: str | None, raw: dict):
|
||||||
|
|
||||||
|
|
||||||
async def _reputation_section(client, settings: Settings, options: Options, geo: IpGeo, raw: dict):
|
async def _reputation_section(client, settings: Settings, options: Options, geo: IpGeo, raw: dict):
|
||||||
from netcheck.reputation import (
|
from netsleuth.reputation import (
|
||||||
build_reputation,
|
build_reputation,
|
||||||
fetch_abuseipdb,
|
fetch_abuseipdb,
|
||||||
fetch_internetdb,
|
fetch_internetdb,
|
||||||
|
|
@ -225,7 +225,7 @@ async def _traces(hosts, caps, settings: Settings, cycles: int, options: Options
|
||||||
async def _speed_section(client, settings: Settings, options: Options, idle_rtt_ms: float | None):
|
async def _speed_section(client, settings: Settings, options: Options, idle_rtt_ms: float | None):
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from netcheck.speed import (
|
from netsleuth.speed import (
|
||||||
measure_with_bufferbloat,
|
measure_with_bufferbloat,
|
||||||
run_speed_cascade,
|
run_speed_cascade,
|
||||||
tier_cloudflare,
|
tier_cloudflare,
|
||||||
|
|
@ -285,7 +285,7 @@ async def diagnose(settings: Settings, options: Options) -> tuple[dict, str, str
|
||||||
timeout=timeouts.http_seconds,
|
timeout=timeouts.http_seconds,
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
http2=True,
|
http2=True,
|
||||||
headers={"User-Agent": f"netcheck/{__version__}"},
|
headers={"User-Agent": f"netsleuth/{__version__}"},
|
||||||
) as client:
|
) as client:
|
||||||
# Phase 1 — local facts and identity. Blocking: everything below needs the ASN.
|
# Phase 1 — local facts and identity. Blocking: everything below needs the ASN.
|
||||||
modules["connection"] = await run_module(
|
modules["connection"] = await run_module(
|
||||||
|
|
@ -463,12 +463,12 @@ def run(
|
||||||
"if it cannot run, the cascade falls through to the normal tiers.[/yellow]"
|
"if it cannot run, the cascade falls through to the normal tiers.[/yellow]"
|
||||||
)
|
)
|
||||||
if watch:
|
if watch:
|
||||||
from netcheck.watch import run_watch
|
from netsleuth.watch import run_watch
|
||||||
|
|
||||||
asyncio.run(run_watch(settings, options))
|
asyncio.run(run_watch(settings, options))
|
||||||
raise typer.Exit(0)
|
raise typer.Exit(0)
|
||||||
|
|
||||||
console.print(f"netcheck {__version__} · {options.mode} mode · {platform.system()}")
|
console.print(f"netsleuth {__version__} · {options.mode} mode · {platform.system()}")
|
||||||
report, _markdown, _markdown_ru, json_path, md_path, ru_md_path = asyncio.run(diagnose(settings, options))
|
report, _markdown, _markdown_ru, json_path, md_path, ru_md_path = asyncio.run(diagnose(settings, options))
|
||||||
interpretation = report["interpretation"]
|
interpretation = report["interpretation"]
|
||||||
console.print(
|
console.print(
|
||||||
|
|
@ -5,7 +5,7 @@ from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from netcheck.exporter import badge
|
from netsleuth.exporter import badge
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -124,7 +124,7 @@ def _block(title: str, changes: list[Change]) -> list[str]:
|
||||||
|
|
||||||
|
|
||||||
def render_diff(diff: ReportDiff, emoji: bool = True) -> str:
|
def render_diff(diff: ReportDiff, emoji: bool = True) -> str:
|
||||||
lines = ["# netcheck compare", ""]
|
lines = ["# netsleuth compare", ""]
|
||||||
lines += _block("Identity", diff.identity)
|
lines += _block("Identity", diff.identity)
|
||||||
lines += _block("Latency", diff.latency)
|
lines += _block("Latency", diff.latency)
|
||||||
lines += _block("Speed", diff.speed)
|
lines += _block("Speed", diff.speed)
|
||||||
|
|
@ -134,7 +134,7 @@ class Watch(BaseModel):
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_prefix="NETCHECK_",
|
env_prefix="NETSLEUTH_",
|
||||||
env_nested_delimiter="__",
|
env_nested_delimiter="__",
|
||||||
env_file_encoding="utf-8",
|
env_file_encoding="utf-8",
|
||||||
extra="ignore",
|
extra="ignore",
|
||||||
|
|
@ -6,8 +6,8 @@ import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from netcheck.interpret import overall_verdict
|
from netsleuth.interpret import overall_verdict
|
||||||
from netcheck.models import Finding, ModuleResult, to_jsonable
|
from netsleuth.models import Finding, ModuleResult, to_jsonable
|
||||||
|
|
||||||
SCHEMA_VERSION = 1
|
SCHEMA_VERSION = 1
|
||||||
SECTION_ORDER = (
|
SECTION_ORDER = (
|
||||||
|
|
@ -101,7 +101,7 @@ def write_report(
|
||||||
return json_path, md_path, ru_md_path
|
return json_path, md_path, ru_md_path
|
||||||
|
|
||||||
|
|
||||||
from netcheck.interpret import bufferbloat_consequence
|
from netsleuth.interpret import bufferbloat_consequence
|
||||||
|
|
||||||
SPARK_CHARS = "▁▂▃▅▇"
|
SPARK_CHARS = "▁▂▃▅▇"
|
||||||
_BADGES = {"ok": "🟢", "info": "🟢", "warn": "🟡", "crit": "🔴"}
|
_BADGES = {"ok": "🟢", "info": "🟢", "warn": "🟡", "crit": "🔴"}
|
||||||
|
|
@ -210,7 +210,7 @@ def _header(report: dict, emoji: bool, lang: str = "en") -> list[str]:
|
||||||
summary = interp.get("summary_text", "") if lang == "en" else _ru_summary(report, status)
|
summary = interp.get("summary_text", "") if lang == "en" else _ru_summary(report, status)
|
||||||
status_word = status if lang == "en" else _SEVERITY_WORDS_RU.get(status, status)
|
status_word = status if lang == "en" else _SEVERITY_WORDS_RU.get(status, status)
|
||||||
return [
|
return [
|
||||||
"# netcheck report",
|
"# netsleuth report",
|
||||||
"",
|
"",
|
||||||
f"- {_t(lang, 'Mode', 'Режим')}: `{meta.get('mode', 'auto')}`{target}",
|
f"- {_t(lang, 'Mode', 'Режим')}: `{meta.get('mode', 'auto')}`{target}",
|
||||||
f"- {_t(lang, 'Started', 'Начало')}: {meta.get('started_at', '?')} · "
|
f"- {_t(lang, 'Started', 'Начало')}: {meta.get('started_at', '?')} · "
|
||||||
|
|
@ -2,8 +2,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from netcheck.config import Band, BufferbloatBands, Thresholds, VpnBands
|
from netsleuth.config import Band, BufferbloatBands, Thresholds, VpnBands
|
||||||
from netcheck.models import (
|
from netsleuth.models import (
|
||||||
CfTrace,
|
CfTrace,
|
||||||
DnsLeak,
|
DnsLeak,
|
||||||
Finding,
|
Finding,
|
||||||
|
|
@ -15,7 +15,7 @@ from netcheck.models import (
|
||||||
TraceResult,
|
TraceResult,
|
||||||
VpnAssessment,
|
VpnAssessment,
|
||||||
)
|
)
|
||||||
from netcheck.netinfo import is_tunnel_iface, mtu_anomaly
|
from netsleuth.netinfo import is_tunnel_iface, mtu_anomaly
|
||||||
|
|
||||||
_SEVERITY_ORDER = {"ok": 0, "info": 1, "warn": 2, "crit": 3}
|
_SEVERITY_ORDER = {"ok": 0, "info": 1, "warn": 2, "crit": 3}
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from netcheck.models import CfTrace, IpGeo
|
from netsleuth.models import CfTrace, IpGeo
|
||||||
|
|
||||||
_AS_PREFIX_RE = re.compile(r"^AS(\d+)\s*(?P<name>.*)$", re.IGNORECASE)
|
_AS_PREFIX_RE = re.compile(r"^AS(\d+)\s*(?P<name>.*)$", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
@ -214,7 +214,7 @@ import asyncio
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from netcheck.config import Providers
|
from netsleuth.config import Providers
|
||||||
|
|
||||||
|
|
||||||
async def _json(client: httpx.AsyncClient, url: str, **kwargs) -> dict:
|
async def _json(client: httpx.AsyncClient, url: str, **kwargs) -> dict:
|
||||||
|
|
@ -12,7 +12,7 @@ import subprocess
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from netcheck.models import Capabilities, LocalNet
|
from netsleuth.models import Capabilities, LocalNet
|
||||||
|
|
||||||
_TUNNEL_PATTERNS = (
|
_TUNNEL_PATTERNS = (
|
||||||
re.compile(r"^(tun|tap|utun|ppp|wg|nordlynx|proton|ipsec|gpd)\d*", re.IGNORECASE),
|
re.compile(r"^(tun|tap|utun|ppp|wg|nordlynx|proton|ipsec|gpd)\d*", re.IGNORECASE),
|
||||||
|
|
@ -7,7 +7,7 @@ from typing import Any, Awaitable
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from netcheck.models import ModuleResult, ProbeError
|
from netsleuth.models import ModuleResult, ProbeError
|
||||||
|
|
||||||
_BLOCKED_STATUS = {401, 403, 451}
|
_BLOCKED_STATUS = {401, 403, 451}
|
||||||
_RETRYABLE_STATUS_FLOOR = 500
|
_RETRYABLE_STATUS_FLOOR = 500
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
|
||||||
from netcheck.models import AdapterLeakResult, DnsLeak
|
from netsleuth.models import AdapterLeakResult, DnsLeak
|
||||||
|
|
||||||
_LEAK_NOTE = (
|
_LEAK_NOTE = (
|
||||||
"This test only sees resolvers configured at the OS level. A browser using DoH "
|
"This test only sees resolvers configured at the OS level. A browser using DoH "
|
||||||
|
|
@ -99,8 +99,8 @@ import asyncio
|
||||||
import dns.asyncresolver
|
import dns.asyncresolver
|
||||||
import dns.exception
|
import dns.exception
|
||||||
|
|
||||||
from netcheck.bgp import parse_cymru_origin
|
from netsleuth.bgp import parse_cymru_origin
|
||||||
from netcheck.models import LocalNet
|
from netsleuth.models import LocalNet
|
||||||
|
|
||||||
MYADDR_NAME = "o-o.myaddr.l.google.com"
|
MYADDR_NAME = "o-o.myaddr.l.google.com"
|
||||||
AKAHELP_NAME = "whoami.ds.akahelp.net"
|
AKAHELP_NAME = "whoami.ds.akahelp.net"
|
||||||
|
|
@ -107,7 +107,7 @@ def _handle():
|
||||||
return iphlpapi, handle
|
return iphlpapi, handle
|
||||||
|
|
||||||
|
|
||||||
def echo_once(dest: str, ttl: int, timeout_ms: int, payload: bytes = b"netcheck") -> IcmpReply:
|
def echo_once(dest: str, ttl: int, timeout_ms: int, payload: bytes = b"netsleuth") -> IcmpReply:
|
||||||
iphlpapi, handle = _handle()
|
iphlpapi, handle = _handle()
|
||||||
try:
|
try:
|
||||||
options = _IpOptionInformation(Ttl=ttl, Tos=0, Flags=0, OptionsSize=0, OptionsData=None)
|
options = _IpOptionInformation(Ttl=ttl, Tos=0, Flags=0, OptionsSize=0, OptionsData=None)
|
||||||
|
|
@ -4,9 +4,9 @@ import asyncio
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from netcheck.models import Capabilities, PingResult
|
from netsleuth.models import Capabilities, PingResult
|
||||||
from netcheck.netinfo import choose_latency_backend
|
from netsleuth.netinfo import choose_latency_backend
|
||||||
from netcheck.stats import rtt_stats
|
from netsleuth.stats import rtt_stats
|
||||||
|
|
||||||
|
|
||||||
def summarize_ping(
|
def summarize_ping(
|
||||||
|
|
@ -57,7 +57,7 @@ def _resolve(host: str) -> str | None:
|
||||||
|
|
||||||
async def _icmp_samples(host: str, count: int, interval: float, timeout: float, backend: str) -> list[float | None]:
|
async def _icmp_samples(host: str, count: int, interval: float, timeout: float, backend: str) -> list[float | None]:
|
||||||
if backend == "icmp_win":
|
if backend == "icmp_win":
|
||||||
from netcheck.probes.icmp_win import ping_samples_win
|
from netsleuth.probes.icmp_win import ping_samples_win
|
||||||
|
|
||||||
return await asyncio.to_thread(ping_samples_win, host, count, interval, timeout)
|
return await asyncio.to_thread(ping_samples_win, host, count, interval, timeout)
|
||||||
from icmplib import async_ping
|
from icmplib import async_ping
|
||||||
|
|
@ -8,9 +8,9 @@ import shutil
|
||||||
import threading
|
import threading
|
||||||
from typing import Awaitable, Callable, TypeVar
|
from typing import Awaitable, Callable, TypeVar
|
||||||
|
|
||||||
from netcheck.models import Capabilities, TraceHop, TraceResult
|
from netsleuth.models import Capabilities, TraceHop, TraceResult
|
||||||
from netcheck.probes.icmp_win import IcmpReply, classify_status
|
from netsleuth.probes.icmp_win import IcmpReply, classify_status
|
||||||
from netcheck.traceparse import build_trace_result, finalize_hop
|
from netsleuth.traceparse import build_trace_result, finalize_hop
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ def _run_in_daemon_thread(func: Callable[..., _T], *args: object) -> "asyncio.Fu
|
||||||
else:
|
else:
|
||||||
result.set_result(value)
|
result.set_result(value)
|
||||||
|
|
||||||
threading.Thread(target=_target, name="netcheck-icmp-win", daemon=True).start()
|
threading.Thread(target=_target, name="netsleuth-icmp-win", daemon=True).start()
|
||||||
return asyncio.wrap_future(result)
|
return asyncio.wrap_future(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -129,7 +129,7 @@ async def _tier_mtr(target: str, binary: str, cycles: int, max_hops: int, timeou
|
||||||
|
|
||||||
|
|
||||||
async def _tier_icmp_win(target: str, max_hops: int, timeout: float) -> TraceResult:
|
async def _tier_icmp_win(target: str, max_hops: int, timeout: float) -> TraceResult:
|
||||||
from netcheck.probes.icmp_win import trace_hops_win
|
from netsleuth.probes.icmp_win import trace_hops_win
|
||||||
|
|
||||||
replies = await _run_in_daemon_thread(trace_hops_win, target, max_hops, int(timeout * 1000))
|
replies = await _run_in_daemon_thread(trace_hops_win, target, max_hops, int(timeout * 1000))
|
||||||
hops = hops_from_win_replies(replies)
|
hops = hops_from_win_replies(replies)
|
||||||
|
|
@ -12,8 +12,8 @@ import dns.asyncresolver
|
||||||
import dns.exception
|
import dns.exception
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from netcheck.config import Providers
|
from netsleuth.config import Providers
|
||||||
from netcheck.models import DnsblHit, InternetDbResult, Reputation
|
from netsleuth.models import DnsblHit, InternetDbResult, Reputation
|
||||||
|
|
||||||
|
|
||||||
def parse_netset(text: str) -> list[str]:
|
def parse_netset(text: str) -> list[str]:
|
||||||
|
|
@ -3,8 +3,8 @@ from __future__ import annotations
|
||||||
import re
|
import re
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
from netcheck.models import CfL4Stats
|
from netsleuth.models import CfL4Stats
|
||||||
from netcheck.stats import percentile
|
from netsleuth.stats import percentile
|
||||||
|
|
||||||
_CFL4_RE = re.compile(r'cfL4\s*;\s*desc\s*=\s*"?\??(?P<query>[^",]*)"?')
|
_CFL4_RE = re.compile(r'cfL4\s*;\s*desc\s*=\s*"?\??(?P<query>[^",]*)"?')
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ import asyncio
|
||||||
import time
|
import time
|
||||||
from typing import Awaitable, Callable
|
from typing import Awaitable, Callable
|
||||||
|
|
||||||
from netcheck.models import SpeedResult, TierAttempt
|
from netsleuth.models import SpeedResult, TierAttempt
|
||||||
|
|
||||||
NDT7_CONSENT_NOTICE = (
|
NDT7_CONSENT_NOTICE = (
|
||||||
"M-Lab NDT7 publishes every measurement as public CC0 open data, including your "
|
"M-Lab NDT7 publishes every measurement as public CC0 open data, including your "
|
||||||
|
|
@ -106,7 +106,7 @@ import json
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from netcheck.config import Speedtest
|
from netsleuth.config import Speedtest
|
||||||
|
|
||||||
|
|
||||||
async def tier_ookla(binary: str, server: str | None, timeout: float) -> SpeedResult:
|
async def tier_ookla(binary: str, server: str | None, timeout: float) -> SpeedResult:
|
||||||
|
|
@ -213,9 +213,9 @@ async def tier_ndt7(client: httpx.AsyncClient, cfg: Speedtest, timeout: float) -
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from netcheck.config import BufferbloatBands
|
from netsleuth.config import BufferbloatBands
|
||||||
from netcheck.interpret import grade_bufferbloat
|
from netsleuth.interpret import grade_bufferbloat
|
||||||
from netcheck.stats import rtt_stats
|
from netsleuth.stats import rtt_stats
|
||||||
|
|
||||||
|
|
||||||
async def probe_while(
|
async def probe_while(
|
||||||
|
|
@ -2,8 +2,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from netcheck.models import TraceHop, TraceResult
|
from netsleuth.models import TraceHop, TraceResult
|
||||||
from netcheck.stats import rtt_stats
|
from netsleuth.stats import rtt_stats
|
||||||
|
|
||||||
IPV4_RE = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
|
IPV4_RE = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
|
||||||
IPV6_RE = re.compile(r"\b(?:[0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}\b")
|
IPV6_RE = re.compile(r"\b(?:[0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}\b")
|
||||||
|
|
@ -11,14 +11,14 @@ from rich.console import Console
|
||||||
from rich.live import Live
|
from rich.live import Live
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
|
|
||||||
from netcheck import __version__
|
from netsleuth import __version__
|
||||||
from netcheck.config import Settings
|
from netsleuth.config import Settings
|
||||||
from netcheck.exporter import atomic_write, compact_timestamp, dump_json, sanitize_name, sparkline
|
from netsleuth.exporter import atomic_write, compact_timestamp, dump_json, sanitize_name, sparkline
|
||||||
from netcheck.ip_geo import gather_identity
|
from netsleuth.ip_geo import gather_identity
|
||||||
from netcheck.models import PingResult, SpeedResult, to_jsonable
|
from netsleuth.models import PingResult, SpeedResult, to_jsonable
|
||||||
from netcheck.netinfo import detect_capabilities
|
from netsleuth.netinfo import detect_capabilities
|
||||||
from netcheck.orchestration import run_module, utc_now_iso
|
from netsleuth.orchestration import run_module, utc_now_iso
|
||||||
from netcheck.probes.latency import ping_fanout
|
from netsleuth.probes.latency import ping_fanout
|
||||||
|
|
||||||
WATCH_SCHEMA_VERSION = 1
|
WATCH_SCHEMA_VERSION = 1
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ def write_session(session: WatchSession, logs_dir: Path) -> Path:
|
||||||
|
|
||||||
|
|
||||||
def render_dashboard(session: WatchSession) -> Table:
|
def render_dashboard(session: WatchSession) -> Table:
|
||||||
table = Table(title=f"netcheck --watch · {session.asn or 'unknown ASN'} · cycle {len(session.cycles)}")
|
table = Table(title=f"netsleuth --watch · {session.asn or 'unknown ASN'} · cycle {len(session.cycles)}")
|
||||||
for column in ("Host", "Avg ms", "Jitter", "Loss", "Trend"):
|
for column in ("Host", "Avg ms", "Jitter", "Loss", "Trend"):
|
||||||
table.add_column(column)
|
table.add_column(column)
|
||||||
latest = session.cycles[-1]["hosts"] if session.cycles else {}
|
latest = session.cycles[-1]["hosts"] if session.cycles else {}
|
||||||
|
|
@ -137,7 +137,7 @@ def render_dashboard(session: WatchSession) -> Table:
|
||||||
async def run_watch(settings: Settings, options) -> Path:
|
async def run_watch(settings: Settings, options) -> Path:
|
||||||
# Imported here, not at module import time: cli imports watch lazily for --watch,
|
# Imported here, not at module import time: cli imports watch lazily for --watch,
|
||||||
# and a module-level import back into cli would close the cycle.
|
# and a module-level import back into cli would close the cycle.
|
||||||
from netcheck.cli import _speed_section
|
from netsleuth.cli import _speed_section
|
||||||
|
|
||||||
caps = detect_capabilities()
|
caps = detect_capabilities()
|
||||||
session = WatchSession(
|
session = WatchSession(
|
||||||
|
|
@ -149,12 +149,12 @@ async def run_watch(settings: Settings, options) -> Path:
|
||||||
if options.extra_host:
|
if options.extra_host:
|
||||||
hosts.append(("target-host", options.extra_host))
|
hosts.append(("target-host", options.extra_host))
|
||||||
console = Console()
|
console = Console()
|
||||||
console.print(f"netcheck {__version__} · watching every {session.interval_seconds}s · Ctrl-C to stop")
|
console.print(f"netsleuth {__version__} · watching every {session.interval_seconds}s · Ctrl-C to stop")
|
||||||
|
|
||||||
async with httpx.AsyncClient(
|
async with httpx.AsyncClient(
|
||||||
timeout=settings.timeouts.http_seconds,
|
timeout=settings.timeouts.http_seconds,
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
headers={"User-Agent": f"netcheck/{__version__}"},
|
headers={"User-Agent": f"netsleuth/{__version__}"},
|
||||||
) as client:
|
) as client:
|
||||||
try:
|
try:
|
||||||
geo, _cf, _flags, _raw = await gather_identity(client, settings.providers)
|
geo, _cf, _flags, _raw = await gather_identity(client, settings.providers)
|
||||||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.bgp import (
|
from netsleuth.bgp import (
|
||||||
classify_stability,
|
classify_stability,
|
||||||
parse_announced_prefixes,
|
parse_announced_prefixes,
|
||||||
parse_as_overview,
|
parse_as_overview,
|
||||||
|
|
@ -11,8 +11,8 @@ from netcheck.bgp import (
|
||||||
parse_bgp_updates,
|
parse_bgp_updates,
|
||||||
ripestat,
|
ripestat,
|
||||||
)
|
)
|
||||||
from netcheck.config import Providers
|
from netsleuth.config import Providers
|
||||||
from netcheck.models import BgpEvent
|
from netsleuth.models import BgpEvent
|
||||||
|
|
||||||
|
|
||||||
def test_as_overview_extracts_holder_and_block(api_fixture):
|
def test_as_overview_extracts_holder_and_block(api_fixture):
|
||||||
|
|
@ -99,7 +99,7 @@ async def test_ripestat_does_not_bound_non_bulk_calls(httpx_mock):
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from netcheck.bgp import (
|
from netsleuth.bgp import (
|
||||||
cached_json,
|
cached_json,
|
||||||
parse_asrank,
|
parse_asrank,
|
||||||
parse_cymru_origin,
|
parse_cymru_origin,
|
||||||
|
|
@ -211,7 +211,7 @@ async def test_cached_json_survives_a_corrupt_cache_file(tmp_path: Path):
|
||||||
assert await cached_json(tmp_path, "k", ttl_hours=24, fetch=fetch) == {"value": "fresh"}
|
assert await cached_json(tmp_path, "k", ttl_hours=24, fetch=fetch) == {"value": "fresh"}
|
||||||
|
|
||||||
|
|
||||||
from netcheck.bgp import build_bgp_intel
|
from netsleuth.bgp import build_bgp_intel
|
||||||
|
|
||||||
|
|
||||||
def test_build_bgp_intel_merges_every_source(api_fixture):
|
def test_build_bgp_intel_merges_every_source(api_fixture):
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.compare import (
|
from netsleuth.compare import (
|
||||||
Change,
|
Change,
|
||||||
diff_reports,
|
diff_reports,
|
||||||
finding_changes,
|
finding_changes,
|
||||||
|
|
@ -101,7 +101,7 @@ def test_diff_reports_assembles_every_part(before, after):
|
||||||
|
|
||||||
def test_render_diff_shows_before_and_after_values(before, after):
|
def test_render_diff_shows_before_and_after_values(before, after):
|
||||||
text = render_diff(diff_reports(before, after))
|
text = render_diff(diff_reports(before, after))
|
||||||
assert text.startswith("# netcheck compare")
|
assert text.startswith("# netsleuth compare")
|
||||||
assert "203.0.113.44" in text
|
assert "203.0.113.44" in text
|
||||||
assert "198.51.100.7" in text
|
assert "198.51.100.7" in text
|
||||||
assert "-188.2" in text
|
assert "-188.2" in text
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import textwrap
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.config import load_settings
|
from netsleuth.config import load_settings
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
|
@ -48,15 +48,15 @@ def test_yaml_overrides_defaults(yaml_file, tmp_path):
|
||||||
|
|
||||||
def test_dotenv_overrides_yaml(yaml_file, tmp_path):
|
def test_dotenv_overrides_yaml(yaml_file, tmp_path):
|
||||||
env_file = tmp_path / ".env"
|
env_file = tmp_path / ".env"
|
||||||
env_file.write_text("NETCHECK_PROBING__PING_COUNT=13\n", encoding="utf-8")
|
env_file.write_text("NETSLEUTH_PROBING__PING_COUNT=13\n", encoding="utf-8")
|
||||||
s = load_settings(config_path=yaml_file, env_file=env_file)
|
s = load_settings(config_path=yaml_file, env_file=env_file)
|
||||||
assert s.probing.ping_count == 13
|
assert s.probing.ping_count == 13
|
||||||
|
|
||||||
|
|
||||||
def test_environment_overrides_dotenv(yaml_file, tmp_path, monkeypatch):
|
def test_environment_overrides_dotenv(yaml_file, tmp_path, monkeypatch):
|
||||||
env_file = tmp_path / ".env"
|
env_file = tmp_path / ".env"
|
||||||
env_file.write_text("NETCHECK_PROBING__PING_COUNT=13\n", encoding="utf-8")
|
env_file.write_text("NETSLEUTH_PROBING__PING_COUNT=13\n", encoding="utf-8")
|
||||||
monkeypatch.setenv("NETCHECK_PROBING__PING_COUNT", "99")
|
monkeypatch.setenv("NETSLEUTH_PROBING__PING_COUNT", "99")
|
||||||
s = load_settings(config_path=yaml_file, env_file=env_file)
|
s = load_settings(config_path=yaml_file, env_file=env_file)
|
||||||
assert s.probing.ping_count == 99
|
assert s.probing.ping_count == 99
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from netcheck.probes.dns_leak import (
|
from netsleuth.probes.dns_leak import (
|
||||||
build_adapter_result,
|
build_adapter_result,
|
||||||
build_dns_leak,
|
build_dns_leak,
|
||||||
detect_ecs_leak,
|
detect_ecs_leak,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.reputation import decode_dnsbl, reverse_ip, summarize_dnsbl
|
from netsleuth.reputation import decode_dnsbl, reverse_ip, summarize_dnsbl
|
||||||
|
|
||||||
|
|
||||||
def test_reverse_ip_builds_the_dnsbl_query_label():
|
def test_reverse_ip_builds_the_dnsbl_query_label():
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.exporter import (
|
from netsleuth.exporter import (
|
||||||
SCHEMA_VERSION,
|
SCHEMA_VERSION,
|
||||||
SECTION_ORDER,
|
SECTION_ORDER,
|
||||||
atomic_write,
|
atomic_write,
|
||||||
|
|
@ -19,7 +19,7 @@ from netcheck.exporter import (
|
||||||
sanitize_name,
|
sanitize_name,
|
||||||
write_report,
|
write_report,
|
||||||
)
|
)
|
||||||
from netcheck.models import (
|
from netsleuth.models import (
|
||||||
Finding,
|
Finding,
|
||||||
IpGeo,
|
IpGeo,
|
||||||
LocalNet,
|
LocalNet,
|
||||||
|
|
@ -196,7 +196,7 @@ def test_egress_asn_is_read_out_of_the_assembled_report():
|
||||||
def test_write_report_names_the_file_after_the_target_in_target_mode(tmp_path: Path):
|
def test_write_report_names_the_file_after_the_target_in_target_mode(tmp_path: Path):
|
||||||
report = build_report({**meta(), "mode": "target", "target": "AS15169"}, modules(), [], {})
|
report = build_report({**meta(), "mode": "target", "target": "AS15169"}, modules(), [], {})
|
||||||
json_path, md_path, ru_md_path = write_report(
|
json_path, md_path, ru_md_path = write_report(
|
||||||
report, "# netcheck report\n", "# netcheck report\n", tmp_path
|
report, "# netsleuth report\n", "# netsleuth report\n", tmp_path
|
||||||
)
|
)
|
||||||
assert json_path.name == "report_AS15169_20260808T191200Z.json"
|
assert json_path.name == "report_AS15169_20260808T191200Z.json"
|
||||||
assert md_path.name == "report_AS15169_20260808T191200Z.md"
|
assert md_path.name == "report_AS15169_20260808T191200Z.md"
|
||||||
|
|
@ -221,18 +221,18 @@ def test_atomic_write_replaces_an_existing_file(tmp_path: Path):
|
||||||
def test_write_report_emits_both_artifacts_with_matching_names(tmp_path: Path):
|
def test_write_report_emits_both_artifacts_with_matching_names(tmp_path: Path):
|
||||||
report = build_report(meta(), modules(), [], {})
|
report = build_report(meta(), modules(), [], {})
|
||||||
json_path, md_path, ru_md_path = write_report(
|
json_path, md_path, ru_md_path = write_report(
|
||||||
report, "# netcheck report\n", "# netcheck report\n", tmp_path
|
report, "# netsleuth report\n", "# netsleuth report\n", tmp_path
|
||||||
)
|
)
|
||||||
assert json_path.name == "report_AS64500_20260808T191200Z.json"
|
assert json_path.name == "report_AS64500_20260808T191200Z.json"
|
||||||
assert md_path.name == "report_AS64500_20260808T191200Z.md"
|
assert md_path.name == "report_AS64500_20260808T191200Z.md"
|
||||||
assert ru_md_path.name == "report_AS64500_20260808T191200Z.ru.md"
|
assert ru_md_path.name == "report_AS64500_20260808T191200Z.ru.md"
|
||||||
assert json.loads(json_path.read_text(encoding="utf-8"))["schema_version"] == SCHEMA_VERSION
|
assert json.loads(json_path.read_text(encoding="utf-8"))["schema_version"] == SCHEMA_VERSION
|
||||||
assert md_path.read_text(encoding="utf-8").startswith("# netcheck report")
|
assert md_path.read_text(encoding="utf-8").startswith("# netsleuth report")
|
||||||
assert ru_md_path.read_text(encoding="utf-8").startswith("# netcheck report")
|
assert ru_md_path.read_text(encoding="utf-8").startswith("# netsleuth report")
|
||||||
|
|
||||||
|
|
||||||
from netcheck.exporter import SPARK_CHARS, badge, first_loss_jump, render_markdown, sparkline
|
from netsleuth.exporter import SPARK_CHARS, badge, first_loss_jump, render_markdown, sparkline
|
||||||
from netcheck.models import (
|
from netsleuth.models import (
|
||||||
AdapterLeakResult,
|
AdapterLeakResult,
|
||||||
BgpIntel,
|
BgpIntel,
|
||||||
CfTrace,
|
CfTrace,
|
||||||
|
|
@ -466,7 +466,7 @@ def test_markdown_has_every_section_from_the_spec_in_order():
|
||||||
"## Problems & recommendations",
|
"## Problems & recommendations",
|
||||||
"## Run diagnostics",
|
"## Run diagnostics",
|
||||||
]
|
]
|
||||||
assert text.startswith("# netcheck report")
|
assert text.startswith("# netsleuth report")
|
||||||
|
|
||||||
|
|
||||||
def test_markdown_header_states_mode_timestamp_and_verdict():
|
def test_markdown_header_states_mode_timestamp_and_verdict():
|
||||||
|
|
@ -542,7 +542,7 @@ def test_markdown_without_emoji_uses_text_badges():
|
||||||
assert "🟡" not in text
|
assert "🟡" not in text
|
||||||
|
|
||||||
|
|
||||||
from netcheck.exporter import unavailable
|
from netsleuth.exporter import unavailable
|
||||||
|
|
||||||
|
|
||||||
def dead_modules() -> dict[str, ModuleResult]:
|
def dead_modules() -> dict[str, ModuleResult]:
|
||||||
|
|
@ -603,7 +603,7 @@ def test_unavailable_translates_the_skip_reason_into_russian():
|
||||||
|
|
||||||
|
|
||||||
def test_diagnostics_translates_module_warnings_into_russian():
|
def test_diagnostics_translates_module_warnings_into_russian():
|
||||||
from netcheck.exporter import _diagnostics
|
from netsleuth.exporter import _diagnostics
|
||||||
|
|
||||||
report = build_report(
|
report = build_report(
|
||||||
meta(),
|
meta(),
|
||||||
|
|
@ -666,7 +666,7 @@ def test_russian_markdown_is_non_empty_and_contains_cyrillic():
|
||||||
text = render_markdown(full_report(), lang="ru")
|
text = render_markdown(full_report(), lang="ru")
|
||||||
assert text.strip()
|
assert text.strip()
|
||||||
assert _has_cyrillic(text)
|
assert _has_cyrillic(text)
|
||||||
assert text.startswith("# netcheck report")
|
assert text.startswith("# netsleuth report")
|
||||||
|
|
||||||
|
|
||||||
def _structural_shape(text: str) -> tuple[int, int, int]:
|
def _structural_shape(text: str) -> tuple[int, int, int]:
|
||||||
|
|
@ -723,7 +723,7 @@ def test_render_markdown_default_call_is_byte_identical_to_before_the_lang_featu
|
||||||
"## Problems & recommendations",
|
"## Problems & recommendations",
|
||||||
"## Run diagnostics",
|
"## Run diagnostics",
|
||||||
]
|
]
|
||||||
assert text.startswith("# netcheck report")
|
assert text.startswith("# netsleuth report")
|
||||||
assert "[Русский]" not in text
|
assert "[Русский]" not in text
|
||||||
assert "[English]" not in text
|
assert "[English]" not in text
|
||||||
assert "cloudflare-dns" in text
|
assert "cloudflare-dns" in text
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import struct
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.probes.icmp_win import (
|
from netsleuth.probes.icmp_win import (
|
||||||
IP_DEST_HOST_UNREACHABLE,
|
IP_DEST_HOST_UNREACHABLE,
|
||||||
IP_REQ_TIMED_OUT,
|
IP_REQ_TIMED_OUT,
|
||||||
IP_SUCCESS,
|
IP_SUCCESS,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import time
|
import time
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from netcheck.probes.icmp_win import IP_REQ_TIMED_OUT, IcmpReply, trace_hops_win
|
from netsleuth.probes.icmp_win import IP_REQ_TIMED_OUT, IcmpReply, trace_hops_win
|
||||||
|
|
||||||
|
|
||||||
def _fake_gethostbyname(host: str) -> str:
|
def _fake_gethostbyname(host: str) -> str:
|
||||||
|
|
@ -19,14 +19,14 @@ def test_trace_hops_win_total_elapsed_is_bounded_by_the_total_budget_not_hops_ti
|
||||||
# to the 100ms total budget.
|
# to the 100ms total budget.
|
||||||
call_timeouts: list[int] = []
|
call_timeouts: list[int] = []
|
||||||
|
|
||||||
def fake_echo_once(dest: str, ttl: int, timeout_ms: int, payload: bytes = b"netcheck") -> IcmpReply:
|
def fake_echo_once(dest: str, ttl: int, timeout_ms: int, payload: bytes = b"netsleuth") -> IcmpReply:
|
||||||
call_timeouts.append(timeout_ms)
|
call_timeouts.append(timeout_ms)
|
||||||
time.sleep(timeout_ms / 1000.0)
|
time.sleep(timeout_ms / 1000.0)
|
||||||
return IcmpReply(address=None, status=IP_REQ_TIMED_OUT, rtt_ms=None, ttl=0)
|
return IcmpReply(address=None, status=IP_REQ_TIMED_OUT, rtt_ms=None, ttl=0)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("netcheck.probes.icmp_win.socket.gethostbyname", side_effect=_fake_gethostbyname),
|
patch("netsleuth.probes.icmp_win.socket.gethostbyname", side_effect=_fake_gethostbyname),
|
||||||
patch("netcheck.probes.icmp_win.echo_once", side_effect=fake_echo_once),
|
patch("netsleuth.probes.icmp_win.echo_once", side_effect=fake_echo_once),
|
||||||
):
|
):
|
||||||
started = time.monotonic()
|
started = time.monotonic()
|
||||||
trace_hops_win("example.invalid", max_hops=30, timeout_ms=100)
|
trace_hops_win("example.invalid", max_hops=30, timeout_ms=100)
|
||||||
|
|
@ -43,13 +43,13 @@ def test_trace_hops_win_total_elapsed_is_bounded_by_the_total_budget_not_hops_ti
|
||||||
def test_trace_hops_win_per_hop_timeout_is_capped_even_for_a_huge_total_budget():
|
def test_trace_hops_win_per_hop_timeout_is_capped_even_for_a_huge_total_budget():
|
||||||
call_timeouts: list[int] = []
|
call_timeouts: list[int] = []
|
||||||
|
|
||||||
def fake_echo_once(dest: str, ttl: int, timeout_ms: int, payload: bytes = b"netcheck") -> IcmpReply:
|
def fake_echo_once(dest: str, ttl: int, timeout_ms: int, payload: bytes = b"netsleuth") -> IcmpReply:
|
||||||
call_timeouts.append(timeout_ms)
|
call_timeouts.append(timeout_ms)
|
||||||
return IcmpReply(address=None, status=IP_REQ_TIMED_OUT, rtt_ms=None, ttl=0)
|
return IcmpReply(address=None, status=IP_REQ_TIMED_OUT, rtt_ms=None, ttl=0)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("netcheck.probes.icmp_win.socket.gethostbyname", side_effect=_fake_gethostbyname),
|
patch("netsleuth.probes.icmp_win.socket.gethostbyname", side_effect=_fake_gethostbyname),
|
||||||
patch("netcheck.probes.icmp_win.echo_once", side_effect=fake_echo_once),
|
patch("netsleuth.probes.icmp_win.echo_once", side_effect=fake_echo_once),
|
||||||
):
|
):
|
||||||
# A single-hop trace with a huge total budget (e.g. subprocess_seconds=60s
|
# A single-hop trace with a huge total budget (e.g. subprocess_seconds=60s
|
||||||
# divided across a small max_hops) must still not hand the whole budget
|
# divided across a small max_hops) must still not hand the whole budget
|
||||||
|
|
@ -60,16 +60,16 @@ def test_trace_hops_win_per_hop_timeout_is_capped_even_for_a_huge_total_budget()
|
||||||
|
|
||||||
|
|
||||||
def test_trace_hops_win_happy_path_hop_count_and_ok_break_are_unchanged():
|
def test_trace_hops_win_happy_path_hop_count_and_ok_break_are_unchanged():
|
||||||
from netcheck.probes.icmp_win import IP_SUCCESS
|
from netsleuth.probes.icmp_win import IP_SUCCESS
|
||||||
|
|
||||||
def fake_echo_once(dest: str, ttl: int, timeout_ms: int, payload: bytes = b"netcheck") -> IcmpReply:
|
def fake_echo_once(dest: str, ttl: int, timeout_ms: int, payload: bytes = b"netsleuth") -> IcmpReply:
|
||||||
if ttl < 3:
|
if ttl < 3:
|
||||||
return IcmpReply(address=f"10.0.0.{ttl}", status=IP_REQ_TIMED_OUT, rtt_ms=None, ttl=0)
|
return IcmpReply(address=f"10.0.0.{ttl}", status=IP_REQ_TIMED_OUT, rtt_ms=None, ttl=0)
|
||||||
return IcmpReply(address="203.0.113.1", status=IP_SUCCESS, rtt_ms=5.0, ttl=64)
|
return IcmpReply(address="203.0.113.1", status=IP_SUCCESS, rtt_ms=5.0, ttl=64)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("netcheck.probes.icmp_win.socket.gethostbyname", side_effect=_fake_gethostbyname),
|
patch("netsleuth.probes.icmp_win.socket.gethostbyname", side_effect=_fake_gethostbyname),
|
||||||
patch("netcheck.probes.icmp_win.echo_once", side_effect=fake_echo_once),
|
patch("netsleuth.probes.icmp_win.echo_once", side_effect=fake_echo_once),
|
||||||
):
|
):
|
||||||
hops = trace_hops_win("example.invalid", max_hops=30, timeout_ms=60_000)
|
hops = trace_hops_win("example.invalid", max_hops=30, timeout_ms=60_000)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.config import Band, Thresholds
|
from netsleuth.config import Band, Thresholds
|
||||||
from netcheck.models import PingResult, TraceHop, TraceResult
|
from netsleuth.models import PingResult, TraceHop, TraceResult
|
||||||
from netcheck.interpret import latency_findings, path_findings, severity_for, worst
|
from netsleuth.interpret import latency_findings, path_findings, severity_for, worst
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
|
@ -156,9 +156,9 @@ def test_path_findings_report_an_incomplete_trace():
|
||||||
assert [f.id for f in path_findings(trace)] == ["path.incomplete"]
|
assert [f.id for f in path_findings(trace)] == ["path.incomplete"]
|
||||||
|
|
||||||
|
|
||||||
from netcheck.config import VpnBands
|
from netsleuth.config import VpnBands
|
||||||
from netcheck.models import AdapterLeakResult, CfTrace, DnsLeak, IpGeo, LocalNet, Signal
|
from netsleuth.models import AdapterLeakResult, CfTrace, DnsLeak, IpGeo, LocalNet, Signal
|
||||||
from netcheck.interpret import SIGNAL_WEIGHTS, assess_vpn, gather_vpn_signals, score_vpn
|
from netsleuth.interpret import SIGNAL_WEIGHTS, assess_vpn, gather_vpn_signals, score_vpn
|
||||||
|
|
||||||
|
|
||||||
def sig(name: str, observed: bool = True) -> Signal:
|
def sig(name: str, observed: bool = True) -> Signal:
|
||||||
|
|
@ -303,9 +303,9 @@ def test_assess_vpn_returns_a_complete_assessment():
|
||||||
assert assessment.confidence >= 0.75
|
assert assessment.confidence >= 0.75
|
||||||
|
|
||||||
|
|
||||||
from netcheck.config import BufferbloatBands
|
from netsleuth.config import BufferbloatBands
|
||||||
from netcheck.models import Finding, SpeedResult
|
from netsleuth.models import Finding, SpeedResult
|
||||||
from netcheck.interpret import bufferbloat_consequence, grade_bufferbloat, overall_verdict, speed_findings
|
from netsleuth.interpret import bufferbloat_consequence, grade_bufferbloat, overall_verdict, speed_findings
|
||||||
|
|
||||||
|
|
||||||
def test_bufferbloat_grades_follow_the_configured_bands():
|
def test_bufferbloat_grades_follow_the_configured_bands():
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ from __future__ import annotations
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.config import Providers
|
from netsleuth.config import Providers
|
||||||
from netcheck.ip_geo import (
|
from netsleuth.ip_geo import (
|
||||||
classify_ip_type,
|
classify_ip_type,
|
||||||
gather_identity,
|
gather_identity,
|
||||||
normalize_freeipapi,
|
normalize_freeipapi,
|
||||||
|
|
@ -130,8 +130,8 @@ def test_ip_type_classification(mobile, proxy, hosting, known, expected):
|
||||||
assert classify_ip_type(mobile, proxy, hosting, known) == expected
|
assert classify_ip_type(mobile, proxy, hosting, known) == expected
|
||||||
|
|
||||||
|
|
||||||
from netcheck.models import IpGeo
|
from netsleuth.models import IpGeo
|
||||||
from netcheck.ip_geo import dual_stack_mismatch, merge_geo
|
from netsleuth.ip_geo import dual_stack_mismatch, merge_geo
|
||||||
|
|
||||||
|
|
||||||
def test_merge_takes_the_first_non_empty_value_in_priority_order():
|
def test_merge_takes_the_first_non_empty_value_in_priority_order():
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.models import (
|
from netsleuth.models import (
|
||||||
AdapterLeakResult,
|
AdapterLeakResult,
|
||||||
BgpEvent,
|
BgpEvent,
|
||||||
BgpIntel,
|
BgpIntel,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from netcheck.models import Capabilities
|
from netsleuth.models import Capabilities
|
||||||
from netcheck.netinfo import choose_latency_backend, choose_trace_backend, degradation_note
|
from netsleuth.netinfo import choose_latency_backend, choose_trace_backend, degradation_note
|
||||||
|
|
||||||
|
|
||||||
def caps(**kw) -> Capabilities:
|
def caps(**kw) -> Capabilities:
|
||||||
|
|
@ -69,7 +69,7 @@ def test_degradation_note_on_windows_mentions_the_api_rather_than_sysctl():
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.netinfo import iface_for_ip, is_tunnel_iface, mtu_anomaly
|
from netsleuth.netinfo import iface_for_ip, is_tunnel_iface, mtu_anomaly
|
||||||
|
|
||||||
|
|
||||||
def test_iface_for_ip_matches_the_owning_adapter():
|
def test_iface_for_ip_matches_the_owning_adapter():
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import asyncio
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.models import ModuleResult, ProbeError
|
from netsleuth.models import ModuleResult, ProbeError
|
||||||
from netcheck.orchestration import classify_exception, gather_modules, run_module
|
from netsleuth.orchestration import classify_exception, gather_modules, run_module
|
||||||
|
|
||||||
|
|
||||||
def test_classify_timeout_errors():
|
def test_classify_timeout_errors():
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.probes.latency import summarize_ping
|
from netsleuth.probes.latency import summarize_ping
|
||||||
|
|
||||||
|
|
||||||
def test_summarize_builds_a_complete_ping_result():
|
def test_summarize_builds_a_complete_ping_result():
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from netcheck.reputation import (
|
from netsleuth.reputation import (
|
||||||
NetsetIndex,
|
NetsetIndex,
|
||||||
build_reputation,
|
build_reputation,
|
||||||
captcha_risk,
|
captcha_risk,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.speed import bufferbloat_delta, mbps, parse_server_timing_cfl4, throughput_from_samples
|
from netsleuth.speed import bufferbloat_delta, mbps, parse_server_timing_cfl4, throughput_from_samples
|
||||||
|
|
||||||
|
|
||||||
def test_mbps_converts_bytes_and_seconds():
|
def test_mbps_converts_bytes_and_seconds():
|
||||||
|
|
@ -86,8 +86,8 @@ def test_bufferbloat_delta_needs_both_a_baseline_and_samples():
|
||||||
assert bufferbloat_delta(12.0, []) is None
|
assert bufferbloat_delta(12.0, []) is None
|
||||||
|
|
||||||
|
|
||||||
from netcheck.models import SpeedResult, TierAttempt
|
from netsleuth.models import SpeedResult, TierAttempt
|
||||||
from netcheck.speed import NDT7_CONSENT_NOTICE, run_speed_cascade
|
from netsleuth.speed import NDT7_CONSENT_NOTICE, run_speed_cascade
|
||||||
|
|
||||||
|
|
||||||
async def test_cascade_stops_at_the_first_tier_that_returns_a_download_figure():
|
async def test_cascade_stops_at_the_first_tier_that_returns_a_download_figure():
|
||||||
|
|
@ -171,8 +171,8 @@ def test_the_ndt7_consent_notice_states_what_gets_published():
|
||||||
assert "public" in NDT7_CONSENT_NOTICE.lower()
|
assert "public" in NDT7_CONSENT_NOTICE.lower()
|
||||||
|
|
||||||
|
|
||||||
from netcheck.config import BufferbloatBands
|
from netsleuth.config import BufferbloatBands
|
||||||
from netcheck.speed import measure_with_bufferbloat, probe_while
|
from netsleuth.speed import measure_with_bufferbloat, probe_while
|
||||||
|
|
||||||
|
|
||||||
async def test_probe_while_collects_samples_for_the_whole_duration_of_the_work():
|
async def test_probe_while_collects_samples_for_the_whole_duration_of_the_work():
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import math
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.stats import percentile, rtt_stats
|
from netsleuth.stats import percentile, rtt_stats
|
||||||
|
|
||||||
|
|
||||||
def test_all_samples_present():
|
def test_all_samples_present():
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.traceparse import parse_linux
|
from netsleuth.traceparse import parse_linux
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
|
@ -73,7 +73,7 @@ def test_linux_parser_tolerates_empty_and_garbage_input():
|
||||||
assert parse_linux("bash: traceroute: command not found\n") == []
|
assert parse_linux("bash: traceroute: command not found\n") == []
|
||||||
|
|
||||||
|
|
||||||
from netcheck.traceparse import WINDOWS_SUB_MS, parse_windows
|
from netsleuth.traceparse import WINDOWS_SUB_MS, parse_windows
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
|
@ -143,7 +143,7 @@ def test_windows_parser_tolerates_empty_input():
|
||||||
assert parse_windows("") == []
|
assert parse_windows("") == []
|
||||||
|
|
||||||
|
|
||||||
from netcheck.traceparse import parse_darwin
|
from netsleuth.traceparse import parse_darwin
|
||||||
|
|
||||||
|
|
||||||
def test_darwin_basic_hops_match_the_gnu_shape(trace_fixture):
|
def test_darwin_basic_hops_match_the_gnu_shape(trace_fixture):
|
||||||
|
|
@ -185,7 +185,7 @@ def test_darwin_parser_tolerates_empty_input():
|
||||||
assert parse_darwin("") == []
|
assert parse_darwin("") == []
|
||||||
|
|
||||||
|
|
||||||
from netcheck.traceparse import build_trace_result, parse_traceroute
|
from netsleuth.traceparse import build_trace_result, parse_traceroute
|
||||||
|
|
||||||
|
|
||||||
def test_dispatcher_routes_by_os_name(trace_fixture):
|
def test_dispatcher_routes_by_os_name(trace_fixture):
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import threading
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.models import Capabilities, TraceHop, TraceResult
|
from netsleuth.models import Capabilities, TraceHop, TraceResult
|
||||||
from netcheck.probes.icmp_win import IP_REQ_TIMED_OUT, IP_SUCCESS, IP_TTL_EXPIRED_TRANSIT, IcmpReply
|
from netsleuth.probes.icmp_win import IP_REQ_TIMED_OUT, IP_SUCCESS, IP_TTL_EXPIRED_TRANSIT, IcmpReply
|
||||||
from netcheck.probes.traceroute import _run_in_daemon_thread, hops_from_win_replies, run_cascade, tier_order
|
from netsleuth.probes.traceroute import _run_in_daemon_thread, hops_from_win_replies, run_cascade, tier_order
|
||||||
|
|
||||||
|
|
||||||
def caps(**kw) -> Capabilities:
|
def caps(**kw) -> Capabilities:
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from netcheck.models import PingResult, SpeedResult
|
from netsleuth.models import PingResult, SpeedResult
|
||||||
from netcheck.watch import (
|
from netsleuth.watch import (
|
||||||
WATCH_SCHEMA_VERSION,
|
WATCH_SCHEMA_VERSION,
|
||||||
WatchSession,
|
WatchSession,
|
||||||
is_speedtest_cycle,
|
is_speedtest_cycle,
|
||||||
|
|
|
||||||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -208,7 +208,7 @@ wheels = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "netcheck"
|
name = "netsleuth"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue