sniff_live.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. # -*- coding: utf-8 -*-
  2. """
  3. sniff_live.py —— 等待图交互抓包: 监听 996盒子真实客户端 与 游戏网关的 TCP 流,
  4. 完整记录 CS/SC 帧(时间戳+方向+msgId+头字段+body), 用于对照找出"进新手村"缺的帧。
  5. 用法(需要 sudo):
  6. sudo protocol_bot/.venv/bin/python sniff_live.py --out /tmp/cap.jsonl
  7. 过滤: 目标/来源端口 8001-8099 的 TCP 载荷, 按流缓冲, 帧格式=48B encode6头+body。
  8. """
  9. import argparse
  10. import json
  11. import socket
  12. import struct
  13. import sys
  14. import time
  15. sys.path.insert(0, "protocol_bot")
  16. sys.path.insert(0, "protocol_bot_backup")
  17. from encode6bitbuf_final import encode6, decode6 # noqa: E402
  18. def sniff(port_lo, port_hi, out_path, duration):
  19. from scapy.all import sniff as sc_sniff, TCP, IP, Raw # type: ignore
  20. flows = {}
  21. t0 = time.time()
  22. fh = open(out_path, "w", buffering=1) # 行缓冲实时落盘
  23. n = [0]
  24. def on_pkt(pkt):
  25. if TCP not in pkt or Raw not in pkt or IP not in pkt:
  26. return
  27. sport, dport = pkt[TCP].sport, pkt[TCP].dport
  28. if not (port_lo <= sport <= port_hi or port_lo <= dport <= port_hi):
  29. return
  30. payload = bytes(pkt[Raw].load)
  31. if not payload:
  32. return
  33. # 方向判定修正(2026-09-23): macOS 客户端临时端口 49152+ 全部 >=8001,
  34. # 旧规则 "dport>=port_lo" 会把服务器下行帧误标 CS。
  35. # 正确: 服务器端口固定 8001-8099 → 该侧端口在范围内的=服务器侧:
  36. # 客户端→服务器(dport 在 8001-8099)= 真上行 "CS"
  37. # 服务器→客户端(sport 在 8001-8099)= 真下行 "SC"
  38. if port_lo <= dport <= port_hi:
  39. d = "CS"
  40. elif port_lo <= sport <= port_hi:
  41. d = "SC"
  42. else:
  43. return
  44. key = (pkt[IP].src, sport, pkt[IP].dst, dport, d)
  45. buf = flows.get(key, b"") + payload
  46. pos = 0
  47. while len(buf) - pos >= 48:
  48. try:
  49. h = struct.unpack("<9i", decode6(buf[pos:pos + 48]))
  50. except Exception:
  51. break
  52. blen = h[7]
  53. if blen < 0 or blen > 300000 or len(buf) - pos < 48 + blen:
  54. break
  55. body = buf[pos + 48: pos + 48 + blen]
  56. fh.write(json.dumps({
  57. "t": round(time.time() - t0, 2), "dir": d,
  58. "msg": h[1], "recog": h[2], "p1": h[3], "p2": h[4],
  59. "p3": h[5], "p6": h[6], "tail": h[8], "blen": blen,
  60. "body_hex": body.hex(), # 全量不截断(旧 2048 截断吞了进图大帧)
  61. }) + "\n")
  62. n[0] += 1
  63. pos += 48 + blen
  64. flows[key] = buf[pos:]
  65. print(f"[sniff] ports {port_lo}-{port_hi} → {out_path} (实时写入)", flush=True)
  66. try:
  67. sc_sniff(filter=f"tcp portrange {port_lo}-{port_hi}", prn=on_pkt,
  68. store=False, timeout=duration or None)
  69. except KeyboardInterrupt:
  70. pass
  71. fh.close()
  72. print(f"[done] {n[0]} frames → {out_path}")
  73. if __name__ == "__main__":
  74. ap = argparse.ArgumentParser()
  75. ap.add_argument("--out", default="/tmp/cap.jsonl")
  76. ap.add_argument("--lo", type=int, default=8001)
  77. ap.add_argument("--hi", type=int, default=8099)
  78. ap.add_argument("--duration", type=int, default=0)
  79. a = ap.parse_args()
  80. sniff(a.lo, a.hi, a.out, a.duration)