Files
site/WESP_REL/app/services/traceroute_analyze.py
T
2026-07-17 12:57:18 +03:00

130 lines
3.7 KiB
Python

"""
Разбор текстового вывода traceroute / tracepath (IPv4) для отчёта в админке.
Форматы ориентируем на типичный вывод Linux: номер хопа, хост/IP, строки с * ms.
"""
from __future__ import annotations
import re
from typing import Any
_IPV4 = re.compile(r"\b(\d{1,3}(?:\.\d{1,3}){3})\b")
_MS = re.compile(r"(\d+(?:\.\d+)?)\s*ms")
_HOP_START = re.compile(r"^\s*(\d+)\s+")
def _octets(ip: str) -> tuple[int, int, int, int] | None:
parts = ip.split(".")
if len(parts) != 4:
return None
try:
a, b, c, d = (int(p) for p in parts)
return (a, b, c, d)
except ValueError:
return None
def is_private_ipv4(ip: str) -> bool:
o = _octets(ip)
if not o:
return False
a, b, c, d = o
if a == 10:
return True
if a == 172 and 16 <= b <= 31:
return True
if a == 192 and b == 168:
return True
if a == 127:
return True
if a == 169 and b == 254:
return True
if a == 100 and 64 <= b <= 127:
return True
return False
def parse_traceroute_text(text: str, *, max_chars: int = 512_000) -> dict[str, Any]:
"""
Возвращает:
hops: [{ num, ip, delays, avg_ms, network_type }]
stats: { total_hops, private_count, public_count, max_delay_ms, total_time_ms }
"""
raw = (text or "")[:max_chars]
lines = raw.splitlines()
hops: list[dict[str, Any]] = []
current: dict[str, Any] | None = None
for raw_line in lines:
line = raw_line.rstrip()
if not line.strip():
continue
hop_m = _HOP_START.match(line)
if hop_m:
if current and current.get("ip"):
hops.append(_finalize_hop(current))
hop_num = int(hop_m.group(1))
ips = _IPV4.findall(line)
ip = ips[0] if ips else ""
delays = [float(x) for x in _MS.findall(line)]
current = {"num": hop_num, "ip": ip, "delays": delays}
elif current is not None:
delays = [float(x) for x in _MS.findall(line)]
if delays:
current["delays"].extend(delays)
ips = _IPV4.findall(line)
if ips and not current.get("ip"):
current["ip"] = ips[0]
if current and current.get("ip"):
hops.append(_finalize_hop(current))
private_count = 0
public_count = 0
max_delay = 0.0
total_time = 0.0
for h in hops:
nt = h.get("network_type")
if nt == "private":
private_count += 1
elif nt == "public":
public_count += 1
for d in h.get("delays") or []:
if d > max_delay:
max_delay = d
if h.get("delays"):
total_time += sum(h["delays"])
return {
"hops": hops,
"stats": {
"total_hops": len(hops),
"private_count": private_count,
"public_count": public_count,
"max_delay_ms": round(max_delay, 3) if max_delay else 0.0,
"total_time_ms": round(total_time, 3) if total_time else 0.0,
},
}
def _finalize_hop(current: dict[str, Any]) -> dict[str, Any]:
delays: list[float] = list(current.get("delays") or [])
ip = str(current.get("ip") or "").strip()
avg: float | None = None
if delays:
avg = round(sum(delays) / len(delays), 3)
if ip and _octets(ip):
nt = "private" if is_private_ipv4(ip) else "public"
else:
nt = "unknown"
return {
"num": int(current.get("num") or 0),
"ip": ip,
"delays": delays,
"avg_ms": avg,
"network_type": nt,
}