7efa58d9a1
分两段(用户裁定 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 绝对路径)
126 lines
4.2 KiB
Python
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())
|