Files
STM32F4-Base/MDK-ARM/tcp_reconnect_probe.py
T
wandering1 925058a5ae fix(NET)+docs: lftpd 对端地址修复、E6 裁决收口、陷阱编号迁移重排 TRAP-NET-01~21
- lftpd:net_inet_ntoa 段序改 MSB 首 + 新增 net_getpeername,FTP 日志打印
  真实对端地址(真机实证 192.168.1.2:55524,登记 TRAP-NET-21)
- E6:NET_U15_PROBE 命中=0(含压测)→ 阶段 3 项 4 不立项,U15 登记观察项;
  插桩留仓(宏=0 零成本),soak 驱动 test/host/e6_soak.py 入库
- 编号:全库 Trap NN → TRAP-NET-<nn> 迁移并重排连续 01~21(单调映射,
  沿革见 CH395F_Trap_Records.md 头注);死号 12/13/19/21 引用全部清除;
  GD5F 域引用补正为 TRAP-GD5F-NN
- 陷阱文档全新化:索引 21 行行行真陷阱;删误判存档/发现时间/git 考古/
  FTP 附录;台架纪律教训沉淀第六章方法论
- RAM:Code 211348→211456(+108 lftpd 修复),RW+ZI 净增 +4B;
  优化方案/台账受管区/tsv 同笔同步
- ftp_abort_probe.py 挪 test/host/;引用关系图重生成
- 回归:@build 0/0;@test PASS;ftp_abort 10/10;relisten :7003 20/20;
  ci_local L0+L0.5 全绿;E6 窗口零复位,7 条 WD stall 登记压测假象备查
2026-10-13 20:54:31 +08:00

82 lines
3.2 KiB
Python

# -*- coding: utf-8 -*-
# TRAP-NET-17 / N-6 trustworthy reproduction probe: rapid reconnect to a device listener.
#
# Failure under test (original report): after a rapid reconnect (<~1s) on a
# relistened socket, the chip completes the handshake but the MCU never gets
# standalone accept -> the socket stays LISTENING -> later the chip auto-closes it
# -> THE LISTENER DIES PERMANENTLY (all later SYNs refused, no self-recovery).
#
# Judgement is purely client-side (no firmware probes):
# * every round must (re)connect successfully within --timeout
# * on failure the probe keeps retrying for --recover-s to distinguish
# "transient" from "permanently dead listener"
#
# Usage: tcp_reconnect_probe.py [--host 192.168.1.100] [--port 21] [--rounds 50]
# [--gap-ms 600] [--timeout 3] [--recover-s 10]
# [--banner] (read one line after connect, e.g. FTP 220)
# Output:
# TS <epoch>
# ROUND <n> gap=<ms> connect=<OK|FAIL:reason> recovered=<na|yes|no>
# SUMMARY rounds=<n> ok=<n> fail=<n> unrecovered=<n>
# Exit: 0 all rounds connected / 1 any failure / 2 usage
import argparse, socket, sys, time
def log(m):
sys.stdout.write(m + "\n"); sys.stdout.flush()
def try_connect(host, port, timeout, banner):
s = socket.create_connection((host, port), timeout=timeout)
if banner:
s.settimeout(timeout)
try:
s.recv(256) # e.g. FTP "220 ..." greeting
except socket.timeout:
pass
return s
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="192.168.1.100")
ap.add_argument("--port", type=int, default=21)
ap.add_argument("--rounds", type=int, default=50)
ap.add_argument("--gap-ms", type=int, default=600)
ap.add_argument("--timeout", type=float, default=3.0)
ap.add_argument("--recover-s", type=float, default=10.0)
ap.add_argument("--banner", action="store_true")
a = ap.parse_args()
nok = nfail = nunrec = 0
for n in range(1, a.rounds + 1):
log("TS %d" % int(time.time()))
res = "OK"
recovered = "na"
try:
s = try_connect(a.host, a.port, a.timeout, a.banner)
s.close()
except Exception as e:
res = "FAIL:%s" % type(e).__name__
# keep probing: transient (relisten window) or permanently dead?
t0 = time.time()
recovered = "no"
while time.time() - t0 < a.recover_s:
time.sleep(0.5)
try:
s = try_connect(a.host, a.port, a.timeout, a.banner)
s.close()
recovered = "yes"
break
except Exception:
pass
ok = (res == "OK")
nok += 1 if ok else 0
nfail += 0 if ok else 1
if not ok and recovered != "yes":
nunrec += 1
log("ROUND %d gap=%d connect=%s recovered=%s" % (n, a.gap_ms, res, recovered))
if a.gap_ms > 0 and n < a.rounds:
time.sleep(a.gap_ms / 1000.0)
log("SUMMARY rounds=%d ok=%d fail=%d unrecovered=%d" % (a.rounds, nok, nfail, nunrec))
return 0 if nfail == 0 else 1
sys.exit(main())