sniff_live_fixed.py.bak 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_backup")
  16. from encode6bitbuf_final import encode6, decode6 # noqa: E402
  17. def sniff(port_lo, port_hi, out_path, duration):
  18. from scapy.all import sniff as sc_sniff, TCP, IP, Raw # type: ignore
  19. flows = {}
  20. t0 = time.time()
  21. fh = open(out_path, "w", buffering=1) # 行缓冲实时落盘
  22. n = [0]
  23. def on_pkt(pkt):
  24. if TCP not in pkt or Raw not in pkt or IP not in pkt:
  25. return
  26. sport, dport = pkt[TCP].sport, pkt[TCP].dport
  27. if not (port_lo <= sport <= port_hi or port_lo <= dport <= port_hi):
  28. return
  29. payload = bytes(pkt[Raw].load)
  30. if not payload:
  31. return
  32. # 方向判定修正(2026-09-23): macOS 客户端临时端口 49152+ 全部 >=8001,
  33. # 旧规则 "dport>=port_lo" 会把服务器下行帧误标 CS。
  34. # 正确: 服务器端口固定 8001-8099 → 该侧端口在范围内的=服务器侧:
  35. # 客户端→服务器(dport 在 8001-8099)= 真上行 "CS"
  36. # 服务器→客户端(sport 在 8001-8099)= 真下行 "SC"
  37. if port_lo <= dport <= port_hi:
  38. d = "CS"
  39. elif port_lo <= sport <= port_hi:
  40. d = "SC"
  41. else:
  42. return
  43. key = (pkt[IP].src, sport, pkt[IP].dst, dport, d)
  44. buf = flows.get(key, b"") + payload
  45. pos = 0
  46. while len(buf) - pos >= 48:
  47. try:
  48. h = struct.unpack("<9i", decode6(buf[pos:pos + 48]))
  49. except Exception:
  50. break
  51. blen = h[7]
  52. if blen < 0 or blen > 300000 or len(buf) - pos < 48 + blen:
  53. break
  54. body = buf[pos + 48: pos + 48 + blen]
  55. fh.write(json.dumps({
  56. "t": round(time.time() - t0, 2), "dir": d,
  57. "flow": f"{pkt[IP].src}:{sport}>{pkt[IP].dst}:{dport}",
  58. "pre": h[0], "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(),
  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)