# -*- coding: utf-8 -*- # Trap 22 / 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 # ROUND gap= connect= recovered= # SUMMARY rounds= ok= fail= unrecovered= # 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())