Files
STM32F4-Base/test/host/pasv_probe.py
T
wandering1 7efa58d9a1
CI / 静态门禁 (pre-commit) (push) Failing after 25s
CI / 主机侧单测 (MSVC) (push) Successful in 48s
network: 台架 IP 迁到 192.168.31.100,并把重复写死的值收进单一真源
分两段(用户裁定 2026-11-02):先做地址迁移,再把同类字面量收敛成
单一真源,以后再改地址只动一处。

一、IP 迁移 192.168.1.100 → 192.168.31.100(gw 同步 → .31.1)
- 工厂默认:Drivers/BSP/NETCFG/net_cfg.c 的 NET_CFG_DEF_IP/_GW
- 注释/文档示例、主机与板级测试、台架 py 脚本、两份 sim_config.ini
- 字节序教学点同步换算 hex:0x6401A8C0 → 0x641FA8C0(内存
  C0 A8 1F 64),旧版示例引文与历史 Trap 记录按冻结纪律不动
- UI 黄金基准:网口设置 4 态 + shot_info 共 5 张 .bin 由
  check_content_pages.py --update 重刷(渲染值 IP 变了,属预期变更)
- 保留判定:客户点表引用(IEC104/继电保护规划)、芯片手册摘录、
  日期冻结的变更记录行、已退役台架档案 MDK-ARM/retracted_bench_*

二、单一真源收口(A+B+C+D,用户批准全做)
- 新建 test/host/bench_defaults.py:台架 py 默认值唯一定义点
  (DEVICE_IP/DEVICE_GW/PC_IP/NET_BCAST),22 个脚本改为引用;
  MDK-ARM 两探针与 scripts/board_ui_nav.py 经 sys.path shim 引入
- test/board/net_test_task.h 新增 TEST_PC_PEER_IP,板测三处
  PHASE*_REMOTE_IP 改引该宏(保留 -D 覆盖);Phase1 断言改
  memcmp 数组比较,数组各只出现一次
- simulator/tests/check_content_pages.py 从 net_cfg.c 正则解析
  NET_CFG_DEF_* 推导期望值(解析失败即报错、不静默兜底),
  脚本内台架 IP 字面量清零
- 装饰性注释去具体值(net_cfg.h/net_socket.h/net_socket.c/
  net_socket_sim.c);字节序教学 hex 对刻意保留为唯一写值点
- 刻意不动:主机单测夹具 IP(逐文件钉住的任意值是正确测试
  实践)、文档正文 IP(那是文档内容本身)

顺带:scripts/gen_task_table.py 规范串清掉「,B5」——编号清退提交
72b4b094 改了受管区没重跑生成器,task-table 门禁因此一直红

验证:产品 @build 0/0;CH395F 套件开关构建 0/0 后还原产品态;
6 套主机单测 rc=0;content_pages(含宏推导路径)/菜单 8 态/
推流位比对全 PASS;bom-drift 99 无漂移;doc-links/coding-standard/
cppcheck 绿;py_compile 23 文件 + --help 冒烟(含 shim 绝对路径)
2026-11-02 14:46:42 +08:00

126 lines
4.2 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
pasv_probe.py — decisive probe for the "storm" signature (self-heal audit E8).
Per FTP round prints: device PASV port, PC data-socket SOURCE port, round
result, and timing. Hypothesis under test: the CH395F reuses the same
ephemeral PASV port for the next data listener while the previous data
connection is still in chip-side TIME_WAIT with the same 4-tuple
(PC source port reused too) -> chip reports CONNECT then RSTs ~10ms later
(serial: "sock4 standalone accept" + "DISCONNECTED chip st=0x00/0x00").
Usage:
python pasv_probe.py --rounds 5 --delay 2 # reproduce (fast rounds)
python pasv_probe.py --rounds 3 --delay 35 # TIME_WAIT window test
"""
import argparse
import re
import socket
import sys
import time
import bench_defaults # 台架连接默认值单一真源
IP_DEFAULT = bench_defaults.DEVICE_IP
def ftp_stor_round(ip, user, pwd, fname, payload):
"""One login -> PASV -> STOR -> verify transfer. Returns (ok, pasv_port,
pc_src_port, detail)."""
c = socket.create_connection((ip, 21), timeout=10)
c.settimeout(10)
pc_src = None
pasv_port = None
try:
def readline():
buf = b""
while not buf.endswith(b"\n"):
ch = c.recv(1)
if not ch:
break
buf += ch
return buf.decode(errors="replace").strip()
def cmd(line):
c.sendall((line + "\r\n").encode())
return readline()
greet = readline()
if not greet.startswith("220"):
return False, None, None, "no greeting: " + greet
r = cmd("USER " + user)
if not r.startswith("331"):
return False, None, None, "USER: " + r
r = cmd("PASS " + pwd)
if not r.startswith("230"):
return False, None, None, "PASS: " + r
r = cmd("PASV")
m = re.search(r"\((\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\)", r)
if not r.startswith("227") or not m:
return False, None, None, "PASV: " + r
g = [int(x) for x in m.groups()]
pasv_port = g[4] * 256 + g[5]
d = socket.create_connection((ip, pasv_port), timeout=8)
pc_src = d.getsockname()[1]
r = cmd("STOR " + fname)
if not r.startswith(("125", "150")):
d.close()
return False, pasv_port, pc_src, "STOR: " + r
d.sendall(payload)
d.close()
# drain until final 2xx (multi-line or single)
final = ""
t0 = time.time()
while time.time() - t0 < 10:
r = readline()
if not r:
break
final = r
if r[:1] in ("2", "4", "5"):
break
ok = final.startswith("2")
cmd("QUIT")
return ok, pasv_port, pc_src, ("OK" if ok else "final: " + final)
finally:
c.close()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--ip", default=IP_DEFAULT)
ap.add_argument("--rounds", type=int, default=5)
ap.add_argument("--delay", type=float, default=2.0,
help="seconds between rounds")
ap.add_argument("--user", default="admin")
ap.add_argument("--pwd", default="admin")
ap.add_argument("--size", type=int, default=1024)
args = ap.parse_args()
payload = bytes((i * 7 + 3) & 0xFF for i in range(args.size))
ports, srcs, oks = [], [], []
for i in range(args.rounds):
t = time.strftime("%H:%M:%S")
ok, port, src, detail = ftp_stor_round(args.ip, args.user, args.pwd,
"PP%d.BIN" % i, payload)
ports.append(port)
srcs.append(src)
oks.append(ok)
print("[%s] round %d: ok=%-5s pasv_port=%-6s pc_src=%-6s %s"
% (t, i + 1, ok, port, src, detail))
if i + 1 < args.rounds:
time.sleep(args.delay)
print("\nports :", ports)
print("pc_src:", srcs)
print("ok :", oks)
same_port = len(set(p for p in ports if p)) == 1
same_src = len(set(s for s in srcs if s)) == 1
print("PASV port constant: %s | PC source port constant: %s"
% (same_port, same_src))
return 0 if all(oks) else 1
if __name__ == "__main__":
sys.exit(main())