🤖 AIGC disclosure: created with “AI 辅助生成 + 人工实测”, published after community-terms review.
#!/usr/bin/env python3
# mtr-report.py (FIXED) — MTR 报告解析与存证(仅标准库)
# 修复点(R7 审计 P1):
# 1) 原 NUM 正则不匹配真实 mtr 输出:跳号后是 "N.|--"(点号后无空格、带竖线),
# 原式要求 "\d+\.\s+--?" → 永远 miss;且 Loss 列带 "%",原式 ([\d.]+) 会吃掉 % 前的
# 数字后在 % 处断掉 → 整行不匹配。
# 2) 删除从未使用的 LINE 正则(死代码,且本身括号不配对)。
# 3) 兼容 "???"(超时跳)主机名与反解主机名(-z 建议照旧)。
# 用法: mtr -rwzbc 100 目标IP > raw.txt && python3 mtr-report.py raw.txt 目标IP
import sys, re, json, datetime
NUM = re.compile(
r"^\s*(\d+)\.\s*\|?--\s*" # 跳号 + 可选竖线 + 双横线("1.|--" / "1. --")
r"(\S+.*?)\s+" # 主机(含 "???")
r"([\d.]+)%\s+" # Loss%(必须带百分号)
r"(\d+)\s+" # Snt
r"([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s*$" # Last Avg Best Wrst StDev
)
def parse(path):
hops = []
for ln in open(path, encoding="utf-8", errors="replace"):
m = NUM.match(ln.rstrip())
if not m:
continue
hops.append(dict(
hop=int(m.group(1)), host=m.group(2).strip(),
loss=float(m.group(3)), snt=int(m.group(4)),
last=float(m.group(5)), avg=float(m.group(6)),
best=float(m.group(7)), wrst=float(m.group(8)),
stdev=float(m.group(9))))
return hops
def judge(hops):
flags = []
last = hops[-1] if hops else None
for h in hops:
if h["loss"] > 0 and h is not last:
after = [x for x in hops if x["hop"] > h["hop"]]
if after and all(x["loss"] == 0 for x in after):
flags.append((h["hop"], "ICMP_RATE_LIMITED",
"中间跳丢包且后续正常:疑似路由器限速 ICMP,非故障"))
if h["avg"] > 0 and h["stdev"] > h["avg"] * 0.5:
flags.append((h["hop"], "HIGH_JITTER",
f"StDev {h['stdev']} > Avg {h['avg']}×0.5:显著抖动"))
verdict = "PATH_OK"
if last and last["loss"] > 0:
verdict = "REAL_LOSS"
flags.append((last["hop"], "REAL_LOSS",
f"末跳持续丢包 {last['loss']}%:真丢包,可开工单"))
return verdict, flags
def main():
if len(sys.argv) < 3:
print(__doc__); sys.exit(1)
src, target = sys.argv[1], sys.argv[2]
out = sys.argv[3] if len(sys.argv) > 3 else "mtr-evidence"
hops = parse(src)
if not hops:
print("未解析到任何跳(确认输入是 mtr --report/-rwzbc 格式)"); sys.exit(2)
verdict, flags = judge(hops)
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
meta = dict(target=target, generated=ts, hops=len(hops),
verdict=verdict, source_file=src)
with open(f"{out}-{ts}.json", "w", encoding="utf-8") as f:
json.dump(dict(meta=meta, hops=hops,
flags=[dict(hop=a, tag=b, note=c) for a, b, c in flags]),
f, ensure_ascii=False, indent=1)
md = ["# MTR 存证档案", "",
f"- 目标: `{target}` · 生成: {ts} · 判读: **{verdict}**", "",
"| 跳 | 主机 | Loss% | Snt | Avg | Wrst | StDev |",
"|---|---|---|---|---|---|---|"]
for h in hops:
md.append(f"| {h['hop']} | {h['host']} | {h['loss']} | {h['snt']} "
f"| {h['avg']} | {h['wrst']} | {h['stdev']} |")
if flags:
md += ["", "## 自动判读"]
md += [f"- 第 {a} 跳 [{b}] {c}" for a, b, c in flags]
with open(f"{out}-{ts}.md", "w", encoding="utf-8") as f:
f.write("\n".join(md) + "\n")
print(f"verdict={verdict} · 已写 {out}-{ts}.json / {out}-{ts}.md")
if __name__ == "__main__":
main()
──────────────────────
【实测记录·修复版】对 mtr -rwzbc 100 样例输出解析通过(6 跳、含 ??? 超时跳、100.0% 丢包行);REAL_LOSS / ICMP_RATE_LIMITED / HIGH_JITTER 判定经构造样例验证。
【修复说明】R7 审计发现原版正则与真实 mtr 输出失配(跳号列为 "N.|--" 且 Loss 列带 %),已修正为 \d+\.\s*\|?-- 与 ([\d.]+)% 捕获,并删除未使用的 LINE 死代码。
【工单口径】把 .md 档案直接贴工单:结论行(verdict)+ 逐跳表 + 判读。
【已知边界】吃 -z(不反解)输出最稳;带反解建议 mtr --report-wide -z 重跑。