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 登记压测假象备查
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* @details 直接调用 ch395f_* 命令,循环执行受控时序矩阵(延迟/关闭次序/载荷/Socket 号),
|
||||
* 每步打印芯片状态与中断寄存器,定位"关→开→监听→连接"复用时序中
|
||||
* 芯片行为的确切规律。配套主机脚本 test/host/ch395f_reuse_host.py。
|
||||
* 定案结论与实验矩阵演进 → CH395F_Trap_Records.md Trap 25。
|
||||
* 定案结论与实验矩阵演进 → CH395F_Trap_Records.md TRAP-NET-20。
|
||||
*
|
||||
* 存档状态:本文件在主分支**不注册编译**(未加入 uvprojx),仅作实验工具存档;
|
||||
* 运行方法:在实验分支(如 n30-chip-bench)把本 .c 注册进 uvprojx Test 组、
|
||||
@@ -99,7 +99,7 @@ static int bench_chip_init(void) {
|
||||
return -1;
|
||||
}
|
||||
ch395f_reset();
|
||||
/* 等 PHY link(Trap 09:link up 前 open 必失败;B9 修正:判据是 != DISCONN,
|
||||
/* 等 PHY link(TRAP-NET-09:link up 前 open 必失败;B9 修正:判据是 != DISCONN,
|
||||
* 旧写法 !=0 对 01H=DISCONN 也成立) */
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (ch395f_get_phy_status() != CH395F_PHY_DISCONN) {
|
||||
@@ -151,7 +151,7 @@ static void bench_round(unsigned idx) {
|
||||
idx, (unsigned)sock, (unsigned)port, (unsigned)v->delay_ms,
|
||||
(unsigned)v->tx_len, (unsigned)v->close_mode, (unsigned)HAL_GetTick());
|
||||
|
||||
/* 1. open + listen(Trap 09:close 后需延时重开,失败重试 ×3) */
|
||||
/* 1. open + listen(TRAP-NET-09:close 后需延时重开,失败重试 ×3) */
|
||||
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_sour_port(sock, port);
|
||||
uint8_t ost = CH395F_ERR_SUCCESS;
|
||||
@@ -288,7 +288,7 @@ static void bench_round(unsigned idx) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. TX 探针(1KB 分块,Trap 20) */
|
||||
/* 3. TX 探针(1KB 分块,TRAP-NET-16) */
|
||||
if (v->tx_len > 0U) {
|
||||
uint16_t sent = 0;
|
||||
while (sent < v->tx_len) {
|
||||
@@ -345,7 +345,7 @@ static void bench_round(unsigned idx) {
|
||||
ch395f_close_socket(sock);
|
||||
}
|
||||
|
||||
/* 5. 关闭后拆链观察:1s 内每 50ms 记状态 + 中断(Trap 14 滞后中断取证) */
|
||||
/* 5. 关闭后拆链观察:1s 内每 50ms 记状态 + 中断(TRAP-NET-12 滞后中断取证) */
|
||||
for (int k = 0; k < 20; k++) {
|
||||
uint16_t glob = ch395f_get_glob_int_status_all();
|
||||
uint8_t late = 0;
|
||||
@@ -618,7 +618,7 @@ static void bench_e3(void) {
|
||||
* A:读空 RX -> CLOSE -> 排空本 socket 事件 -> 轮询等 byte0=00H
|
||||
* B:CLEAR_RECV_BUF -> CLOSE -> 排空事件 -> 等 CLOSED
|
||||
* C:什么都不做 -> CLOSE -> 等 CLOSED
|
||||
* 每轮先"污染会话"(主机连上发 3000B 后 RST,Trap 23 形态),复位后立即
|
||||
* 每轮先"污染会话"(主机连上发 3000B 后 RST,TRAP-NET-18 形态),复位后立即
|
||||
* open+listen,主机立即上传 4096B 图案载荷;芯片侧按图案自证完整性
|
||||
* (PASS 口径 = 消费者在 DISCONNECT/TIM_OUT 前实际读到的字节数与图案)。 */
|
||||
#define E4_SOCK 3U
|
||||
|
||||
@@ -620,9 +620,9 @@ static void phase2_tc202(void)
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
* TC-NET-204(硬件层):Trap 07 定案对照——三种关闭次序 A/B/C
|
||||
* TC-NET-204(硬件层):TRAP-NET-07 定案对照——三种关闭次序 A/B/C
|
||||
*
|
||||
* 背景:Trap 07(2025-07)断言"`disconnect → close` 会让 Socket 卡 `FIN_WAIT_2`
|
||||
* 背景:TRAP-NET-07(2025-07)断言"`disconnect → close` 会让 Socket 卡 `FIN_WAIT_2`
|
||||
* 数分钟、无法重开,因为 close 不再发 RST"。该断言有两处可疑:
|
||||
* ① 手册无 RST 语义——§5.35 只说"关闭 Socket 前 CH395 会自动断开 TCP 连接",
|
||||
* 全文 RST 只出现在错误码 CH395_ERR_RST 与 RST/RSTI 引脚;
|
||||
@@ -636,12 +636,12 @@ static void phase2_tc202(void)
|
||||
*
|
||||
* 受控对照(每模式 x PHASE204_LOOP_COUNT 轮,同一 Socket、同一 PC 回显服务):
|
||||
* 模式 A:close only(现行做法)
|
||||
* 模式 B:tcp_disconnect → close(Trap 07 声称会卡死的序列)
|
||||
* 模式 B:tcp_disconnect → close(TRAP-NET-07 声称会卡死的序列)
|
||||
* 模式 C:tcp_disconnect only,不 close(检验 FUN_PARA bit3=1 下芯片会不会自己关)
|
||||
* 观测量:
|
||||
* ① 回到 SOCKET_CLOSED(byte0=00H) 的耗时;② 该瞬间 byte1 的读数(验证"无意义"口径)
|
||||
* ③ 重开 + 重连 + 回显是否成功(Trap 07 的"无法重新打开")
|
||||
* ④ 重开会话首次读中断是否命中上一条连接残留的 DISCONNECT(Trap 14 轴)
|
||||
* ③ 重开 + 重连 + 回显是否成功(TRAP-NET-07 的"无法重新打开")
|
||||
* ④ 重开会话首次读中断是否命中上一条连接残留的 DISCONNECT(TRAP-NET-12 轴)
|
||||
* ⚠ 关闭与重开之间**不主动清中断**(与 2025-07 现场一致),残留事件由重开后的
|
||||
* 首次中断读取现场暴露(读即清,无法窥视)。
|
||||
* =================================================================== */
|
||||
@@ -659,7 +659,7 @@ static void phase2_tc202(void)
|
||||
* 入口参数:sock - Socket 号;lport - 本地端口;mode/round - 仅用于日志定位
|
||||
* 返回值:1=会话成功(含回显比对通过) 0=任一步失败 uint8_t
|
||||
* 说明:等待 CONNECT 期间读到的其它中断(含上一条连接残留的 DISCONNECT)
|
||||
* 现场打印,用于 Trap 14"滞后锁存"取证。 */
|
||||
* 现场打印,用于 TRAP-NET-12"滞后锁存"取证。 */
|
||||
static uint8_t tc204_session(uint8_t sock, uint16_t lport, uint8_t mode, uint8_t round)
|
||||
{
|
||||
uint8_t tx[64];
|
||||
@@ -767,7 +767,7 @@ static uint8_t tc204_session(uint8_t sock, uint16_t lport, uint8_t mode, uint8_t
|
||||
* 入口参数:sock - Socket 号;mode - 0=A close / 1=B disconnect→close / 2=C disconnect only
|
||||
* round - 日志定位
|
||||
* 返回值:1=观察窗内 byte0 回到 CLOSED 0=未回到 uint8_t
|
||||
* 说明:关闭与轮询之间不读中断(读即清会抹掉 Trap 14 的滞后事件现场)。 */
|
||||
* 说明:关闭与轮询之间不读中断(读即清会抹掉 TRAP-NET-12 的滞后事件现场)。 */
|
||||
static uint8_t tc204_close_seq(uint8_t sock, uint8_t mode, uint8_t round)
|
||||
{
|
||||
/* 状态采样里程碑(ms):抓 byte0/byte1 的演变 */
|
||||
@@ -863,7 +863,7 @@ static void phase2_tc204_once(uint8_t mode, uint8_t round)
|
||||
TEST_CHECK(!closed, "TC204 m2 r%u stays OPEN after disconnect (FUN_PARA bit3)",
|
||||
(unsigned)round);
|
||||
if (!closed) {
|
||||
/* Trap 07 的"无法重新打开"现场:不 close 直接 open,看芯片返回什么 */
|
||||
/* TRAP-NET-07 的"无法重新打开"现场:不 close 直接 open,看芯片返回什么 */
|
||||
uint8_t r = ch395f_open_socket(sock);
|
||||
DBG_INFO("TC204 m2 r%u open-without-close ret=0x%02X", (unsigned)round, r);
|
||||
/* 收尾:显式 close 并测耗时(证明 close 才是把 Socket 拉回 CLOSED 的动作) */
|
||||
@@ -874,7 +874,7 @@ static void phase2_tc204_once(uint8_t mode, uint8_t round)
|
||||
(unsigned)round, (unsigned)PHASE204_CLOSE_WAIT_MS);
|
||||
}
|
||||
|
||||
/* 3. 重开 + 重连 + 回显(Trap 07 的"无法重新打开"判据;Trap 09 沉降 200ms) */
|
||||
/* 3. 重开 + 重连 + 回显(TRAP-NET-07 的"无法重新打开"判据;TRAP-NET-09 沉降 200ms) */
|
||||
HAL_Delay(200);
|
||||
s2 = tc204_session(sock, (uint16_t)(lport + 64U), mode, round);
|
||||
TEST_CHECK(s2, "TC204 m%u r%u reopen+reconnect+echo OK", (unsigned)mode, (unsigned)round);
|
||||
@@ -915,7 +915,7 @@ static void phase2_tc204(void)
|
||||
* DISCONNECT —— 预留兼容路径(SYN_SENT 阶段实测基本不出现)
|
||||
* UNREACH(全局位) —— ARP 失败、对端不在线(仅作备注,不影响判定)
|
||||
*
|
||||
* 已知陷阱(Trap 14):上一会话 close 的 DISCONNECT 会滞后锁存在
|
||||
* 已知陷阱(TRAP-NET-12):上一会话 close 的 DISCONNECT 会滞后锁存在
|
||||
* SINT_SN(wait-CLOSED 的 STATUS 查询不清中断)。防御双保险:
|
||||
* ① 步 2 前预清两级中断;
|
||||
* ② 判负向后用 GET_SOCKET_STATUS 反核实,ESTABLISHED 即改判环境错误。
|
||||
@@ -944,7 +944,7 @@ static void phase2_tc203(void)
|
||||
"TC203 step1 cfg proto/ip/port");
|
||||
if (ch395f_get_cmd_status() != CH395F_ERR_SUCCESS) return;
|
||||
|
||||
/* 预清陈旧事件(Trap 14):上一会话 close 的 DISCONNECT 可能滞后锁存
|
||||
/* 预清陈旧事件(TRAP-NET-12):上一会话 close 的 DISCONNECT 可能滞后锁存
|
||||
* 在 SINT_SN 中(wait-CLOSED 轮询的 STATUS 是纯查询、不清中断),
|
||||
* 若不清除,本用例首次读中断会误吃旧事件导致负向误判。 */
|
||||
(void)ch395f_get_glob_int_status_all();
|
||||
@@ -996,7 +996,7 @@ static void phase2_tc203(void)
|
||||
|
||||
elapsed = HAL_GetTick() - tick;
|
||||
|
||||
/* 反核实(Trap 14 第二道防线):负向事件与实际状态矛盾时以状态为准。
|
||||
/* 反核实(TRAP-NET-12 第二道防线):负向事件与实际状态矛盾时以状态为准。
|
||||
* 若收到"负向事件"但 socket 实际已 ESTABLISHED,说明刚才读到的是
|
||||
* 上一会话遗留的陈旧 DISCONNECT,真实结果是连接成功 —— 环境错误。
|
||||
* ⚠ §5.22:byte0 只有 00H(CLOSED)/05H(OPEN),TCP 态在 **byte1**;
|
||||
@@ -1061,7 +1061,7 @@ static void phase2_run(void)
|
||||
DBG_INFO("--- TC-NET-202: close/reconnect x%d ---", PHASE2_LOOP_COUNT);
|
||||
phase2_tc202();
|
||||
|
||||
DBG_INFO("--- TC-NET-204: close A/B/C control (Trap 07) x3 modes x%d ---",
|
||||
DBG_INFO("--- TC-NET-204: close A/B/C control (TRAP-NET-07) x3 modes x%d ---",
|
||||
PHASE204_LOOP_COUNT);
|
||||
phase2_tc204();
|
||||
|
||||
@@ -1702,7 +1702,7 @@ static void phase15_run(void) {
|
||||
|
||||
/* 二段关闭实证(2026-10-13 本 TC 二跑定案):LISTEN 态 Socket 的
|
||||
* CLOSE_SOCKET 返 SUCCESS 但只拆监听(TCP 01->00),byte0 恒 05;
|
||||
* 再发一次 CLOSE 才落 00。生产 net 层靠 Trap 09 open 重试吸收此行为,
|
||||
* 再发一次 CLOSE 才落 00。生产 net 层靠 TRAP-NET-09 open 重试吸收此行为,
|
||||
* sock_reset 状态机在 WAIT_CLOSE 阶段自行补发(见 ch395f.c)。 */
|
||||
uint8_t close_st = ch395f_close_socket(sn);
|
||||
TEST_CHECK(close_st == CH395F_ERR_SUCCESS,
|
||||
|
||||
@@ -1132,7 +1132,7 @@ void net_latency_phase13_run(int32_t sockfd)
|
||||
* ui_loop uiTask 主循环实测周期(本地,不需要 peer,120 样本)
|
||||
* prod_cycle_mcu ui_net_sink 处理一帧 LCDMEM 的 MCU 净成本
|
||||
* (取 1bpp 位图 + 组帧 + net_send 队列/芯片;不含 10ms 拾取拍)
|
||||
* relisten peer 断开后自动重监听耗时(量化 D10:Trap 09 重试含 osDelay(500))
|
||||
* relisten peer 断开后自动重监听耗时(量化 D10:TRAP-NET-09 重试含 osDelay(500))
|
||||
*
|
||||
* 端口分工:peer 走 :7003(生产 socket,uiTask 持有),本 Phase 不碰 :8080。
|
||||
* 段阈值(首轮 M3 实测后按 §4.2 重锁):
|
||||
|
||||
@@ -293,7 +293,7 @@ static void phase7_test_timeout(void)
|
||||
|
||||
/* TC 704:多 Socket 并发 TCP Client。
|
||||
* Socket 0~7 由索引寻址 + 缓冲显式分配即可全部使用(FUN_PARA bit0 是保留位,
|
||||
* 与 Socket 个数无关,见 Trap 18「定案更正」);本用例同时打开 Socket 0~7,
|
||||
* 与 Socket 个数无关,见 TRAP-NET-15);本用例同时打开 Socket 0~7,
|
||||
* 各自连 PC echo server,每路独立收发 PHASE7_EXCH_SIZE 字节回显并比对,
|
||||
* 验证 8 个 Socket 均能独立工作。依赖 PC 端 tcp_server 支持并发连接。 */
|
||||
#ifndef PHASE7_MULTI_COUNT
|
||||
@@ -539,7 +539,7 @@ static void phase8_run(int32_t sockfd) {
|
||||
uint32_t sent = 0;
|
||||
while (sent < PHASE8_FILE_SIZE) {
|
||||
/* 每次填充一个滑动窗口(≤ s_tx_buf 容量);net_send 内部按硬件发送 FIFO
|
||||
* (NET_SEND_CHUNK_MAX=1KB,见 Trap 20) 自动拆分写入,无需此处逐 1KB 分块 */
|
||||
* (NET_SEND_CHUNK_MAX=1KB,见 TRAP-NET-16) 自动拆分写入,无需此处逐 1KB 分块 */
|
||||
uint32_t chunk = (PHASE8_FILE_SIZE - sent) > (uint32_t)TEST_BUF_SIZE
|
||||
? (uint32_t)TEST_BUF_SIZE : (PHASE8_FILE_SIZE - sent);
|
||||
phase8_fill(s_tx_buf, chunk, sent);
|
||||
|
||||
+13
-1
@@ -22,6 +22,15 @@
|
||||
| `ftp_speed_test.py` | FTP 传输速度测试 |
|
||||
| `ftp_capture.py` | FTP 流量抓取 |
|
||||
| `ftp_regression.py` | FTP 回归套件 |
|
||||
| `ftp_abort_probe.py` | FTP 中止场景探针(TRAP-NET-18;RST→450 / FIN→226,`--check-abort`;2026-10-13 自 `MDK-ARM/` 迁入) |
|
||||
| `ftp_integrity_test.py` | FTP 上传 SHA-256 完整性多轮回归 |
|
||||
| `n30_repro.py` | 背靠背 STOR 复现器(TRAP-NET-20 常备回归腿) |
|
||||
| `relisten_stress.py` | 监听快速复连压力(TRAP-NET-17 / N34 E1:`--port --n --gap-ms [--hold-ms]`) |
|
||||
| `e11_keepalive_probe.py` | keepalive 黑洞判死探针(N34 E11) |
|
||||
| `e6_soak.py` | E6 24h 挂机驱动(常态 + 每小时/每 4h E1 压测注入;判据读串口 `U15 probe` 计数) |
|
||||
| `pasv_probe.py` | PASV 数据通道时序探针 |
|
||||
| `ch395f_reuse_host.py` | Socket 复用/中止形态 PC 对端(bench 配套) |
|
||||
| `ftp_auth_bench.py` | FTP 鉴权/锁定台架腿(N-19) |
|
||||
| `iec104_master.py` | **IEC 60870-5-104 主站客户端(B6/B7)**:连设备 :2404 跑 STARTDT → 总召(actcon/四组背景扫描/actterm)→ 经 C_SE 两段写临时压低 OC1 定值确定性触发 YX16 变位(COT=3)→ 恢复 → YK11 两段式 → C_RD 读回;**B7 持久化模式 `--b7-pre/--b7-post/--b7-mark/--b7-check`**(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、定值与压板态跨复位保持(含非默认 7.5 往返);纯标准库,输出纯 ASCII 可直接重定向归档为台架证据(判据 exit 0/1/2) |
|
||||
|
||||
### 时间 / 时延
|
||||
@@ -106,7 +115,10 @@
|
||||
- **PYTHONPATH/路径敏感**:脚本按仓库根相对路径读写,**从仓库根执行**
|
||||
- 本目录**被 pre-commit 门禁的 cppcheck / 代码规范排除**(`test/` 豁免);
|
||||
但各模块的 `run_unit_test.py` 自身**作为独立钩子**参与门禁(见上,共 7 个)
|
||||
- 台架探针族(TCP 复连 / FTP 中止等)在 `MDK-ARM/*.py`,与本目录分工不同
|
||||
- 台架探针族分工(2026-10-13 起):**N34/Trap 常备回归腿在本目录**(`relisten_stress` /
|
||||
`ftp_abort_probe` / `n30_repro` / `e02_probe` / `e6_soak` 等);`MDK-ARM/*.py` 只余
|
||||
历史一次性探针(`ftp_freeze_probe` / `tcp_reconnect_probe` / `ftp_retr_abort_probe` /
|
||||
`bench_verdict`)
|
||||
- 全部主机侧单测(`watchdog/`、`sys_clock/`、`cfg_file/`、`phasor/`、`adc_input/`、
|
||||
`vol_lock/`、`time_sync/`)都需要本机装有 MSVC(VS2019/2022/2026 的 C++ 工具链);
|
||||
无则脚本报环境错误(退出码 2)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# 用法: python ch395f_reuse_host.py [--host IP] [--cycles N] [--mode legacy|e4]
|
||||
# legacy: 按 idx 轮转连接 5000+idx 端口,记录收发结果与时刻。
|
||||
# e4: N34 E4 sock_reset A/B/C 矩阵配套(2026-10-13):每轮两次连接——
|
||||
# ①污染:连上发 3000B 后 RST(SO_LINGER 0,Trap 23 中止形态);
|
||||
# ①污染:连上发 3000B 后 RST(SO_LINGER 0,TRAP-NET-18 中止形态);
|
||||
# ②载荷:立即重连发 4096B 图案(pat[i]=(seed+i)&0xFF, seed=(rg*97+13)&0xFF)后 FIN。
|
||||
# 端口 5100+rg,rg=0..(3*rounds-1),变体顺序 A/B/C(与固件 bench 表一致)。
|
||||
import argparse
|
||||
@@ -103,7 +103,7 @@ def e4_round(host, rg, rounds_per_variant, first_budget):
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
|
||||
except OSError:
|
||||
pass
|
||||
s.close() # RST: abort mid-session like ftp_abort_probe (Trap 23 form)
|
||||
s.close() # RST: abort mid-session like ftp_abort_probe (TRAP-NET-18 form)
|
||||
time.sleep(0.05)
|
||||
|
||||
s = connect_retry(host, port, 20)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
:8888 应能再接受新连接(3s 重试窗内);新连接发完整 8 字节 TIME 包 →
|
||||
设备应落 "wall clock updated"。
|
||||
|
||||
⚠ 定案口径(Trap 07 定案更正 2026-10-11 + 实验方案 E13 §5):CH395F close
|
||||
⚠ 定案口径(TRAP-NET-07 定案更正 2026-10-11 + 实验方案 E13 §5):CH395F close
|
||||
**无 RST 语义**。若对端内核存活(app 挂起但 socket 不关,内核自动 ACK FIN),
|
||||
芯片停在 FIN_WAIT_2、监听恢复受限于对端 close——探针第 3 步的 close 正是
|
||||
模拟"对端配合"这一可恢复边界;不配合场景的兜底是芯片 TIMEOUT/keepalive
|
||||
@@ -67,7 +67,7 @@ def main():
|
||||
time.sleep(remain)
|
||||
|
||||
# 3b. 模拟守规矩对端:设备已发 FIN(drop_conn),本端 close 配合拆链。
|
||||
# CH395F close 无 RST 语义(Trap 07 定案更正):不配合的内核存活对端
|
||||
# CH395F close 无 RST 语义(TRAP-NET-07 定案更正):不配合的内核存活对端
|
||||
# 会把芯片停在 FIN_WAIT_2,监听恢复受限于对端 close——不在本探针判据内。
|
||||
try:
|
||||
ts.close()
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""E6 24h soak driver (U15 mismatch frequency, N34 experiment plan E6).
|
||||
|
||||
Protocol (docs/网络/N34-自愈审计/实验方案.md §E6):
|
||||
24h normal operation + periodic E1 stress injection:
|
||||
- every hour : relisten_stress :7003 n=10 gap=100 (UI-like churn)
|
||||
- every 4th hour : full E1 (:7003 n=50 gap=0 + :21 n=50 gap=100)
|
||||
Device-side confirmed hits are counted by the NET_U15_PROBE firmware and
|
||||
appear in the serial log as "U15 probe: sockN rx=... hits=N" (first hit
|
||||
immediately, then <=1/hour). Read the verdict from serial.log afterwards.
|
||||
|
||||
Usage: python e6_soak.py [--hours 24]
|
||||
Exit: 0 = soak completed; nonzero = driver error (device untouched).
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
STRESS = os.path.join(ROOT, "test", "host", "relisten_stress.py")
|
||||
|
||||
|
||||
def log(msg):
|
||||
print("[%s] %s" % (time.strftime("%m-%d %H:%M:%S"), msg), flush=True)
|
||||
|
||||
|
||||
def burst(args_list, tag):
|
||||
t0 = time.time()
|
||||
try:
|
||||
r = subprocess.run([sys.executable, STRESS] + args_list,
|
||||
capture_output=True, text=True, timeout=900)
|
||||
out = (r.stdout or "") + (r.stderr or "")
|
||||
tail = [ln for ln in out.splitlines() if "ok=" in ln or "latency" in ln]
|
||||
dt = time.time() - t0
|
||||
log("%s rc=%d %.0fs %s" % (tag, r.returncode, dt, " | ".join(tail[-2:])))
|
||||
except Exception as e: # driver hiccup must not abort the soak
|
||||
log("%s ERROR %r" % (tag, e))
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--hours", type=int, default=24)
|
||||
a = ap.parse_args()
|
||||
|
||||
log("E6 soak start: %d h, hourly :7003x10, every-4h E1 full" % a.hours)
|
||||
t_start = time.time()
|
||||
for h in range(1, a.hours + 1):
|
||||
# sleep first: hour boundary cadence
|
||||
remain = t_start + h * 3600.0 - time.time()
|
||||
if remain > 0:
|
||||
time.sleep(remain)
|
||||
burst(["--port", "7003", "--n", "10", "--gap-ms", "100"], "hour%02d :7003x10" % h)
|
||||
if h % 4 == 0:
|
||||
burst(["--port", "7003", "--n", "50", "--gap-ms", "0"], "hour%02d E1 :7003x50g0" % h)
|
||||
burst(["--port", "21", "--n", "50", "--gap-ms", "100"], "hour%02d E1 :21x50g100" % h)
|
||||
log("E6 soak done: %.1f h elapsed" % ((time.time() - t_start) / 3600.0))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,137 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Trustworthy abort/robustness probe for the production FTP path (no firmware probes).
|
||||
#
|
||||
# Scenario: client starts a STOR upload, sends part of the data, then ABORTS by
|
||||
# resetting the data connection (SO_LINGER 0 -> RST), which is the common real-world
|
||||
# pattern (user cancels an upload, client crashes, link drops). Then it checks:
|
||||
# 1. the control channel still answers (NOOP on a fresh connection),
|
||||
# 2. a FOLLOW-UP normal upload+download still passes the SHA-256 check,
|
||||
# 3. the device's relisten actually happened (observable via a successful follow-up).
|
||||
#
|
||||
# Usage: ftp_abort_probe.py [--ip ..] [--port 21] [--rounds 10] [--send 8192] [--gap 2]
|
||||
# [--graceful] [--check-abort]
|
||||
# Output: TS <epoch> / ABORT <n> sent=<b> ctrl=<reply> alive=<yes|no> followup=<OK|FAIL:x>
|
||||
# abort5xx=<OK|FAIL|-> (only with --check-abort: RST expects 4xx/5xx on the
|
||||
# aborted STOR = no false 226 (TRAP-NET-18 hardening); graceful FIN expects 2xx
|
||||
# = protocol boundary, still reported as success)
|
||||
# SUMMARY rounds=<n> pass=<n> fail=<n>
|
||||
# Exit 0 all rounds pass, 1 otherwise.
|
||||
import argparse, hashlib, io, random, re, socket, struct, sys, time
|
||||
from ftplib import FTP, all_errors
|
||||
|
||||
def log(m):
|
||||
sys.stdout.write(m + "\n"); sys.stdout.flush()
|
||||
|
||||
def rd(sock):
|
||||
try:
|
||||
return sock.recv(4096).decode("latin1").strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def raw_login(ip, port, timeout=10.0):
|
||||
s = socket.create_connection((ip, port), timeout=timeout)
|
||||
rd(s) # 220 banner
|
||||
# N-19: anonymous login is rejected (530) -> use the factory default account
|
||||
s.sendall(b"USER admin\r\n"); rd(s)
|
||||
s.sendall(b"PASS admin\r\n"); rd(s)
|
||||
s.sendall(b"TYPE I\r\n"); rd(s)
|
||||
return s
|
||||
|
||||
def pasv_data_socket(ctl):
|
||||
ctl.sendall(b"PASV\r\n")
|
||||
r = rd(ctl)
|
||||
m = re.search(r"\((\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\)", r)
|
||||
if not m:
|
||||
raise RuntimeError("PASV failed: " + r)
|
||||
host = ".".join(m.group(i) for i in (1, 2, 3, 4))
|
||||
port = (int(m.group(5)) << 8) | int(m.group(6))
|
||||
return socket.create_connection((host, port), timeout=10.0)
|
||||
|
||||
def followup_ok(ip, port, size=4096):
|
||||
"""normal upload + download + sha256 -> True/False"""
|
||||
data = bytes(random.getrandbits(8) for _ in range(size))
|
||||
want = hashlib.sha256(data).hexdigest()
|
||||
try:
|
||||
f = FTP(); f.connect(ip, port, timeout=15.0); f.login("admin", "admin")
|
||||
f.storbinary("STOR /ab_ok.bin", io.BytesIO(data))
|
||||
buf = bytearray(); f.retrbinary("RETR /ab_ok.bin", buf.extend)
|
||||
try:
|
||||
f.delete("/ab_ok.bin")
|
||||
except all_errors:
|
||||
pass
|
||||
f.quit()
|
||||
return hashlib.sha256(bytes(buf)).hexdigest() == want
|
||||
except all_errors:
|
||||
return False
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--ip", default="192.168.1.100")
|
||||
ap.add_argument("--port", type=int, default=21)
|
||||
ap.add_argument("--rounds", type=int, default=10)
|
||||
ap.add_argument("--send", type=int, default=8192, help="bytes sent before the RST")
|
||||
ap.add_argument("--gap", type=float, default=2.0)
|
||||
ap.add_argument("--graceful", action="store_true",
|
||||
help="close the data connection with FIN instead of RST")
|
||||
ap.add_argument("--check-abort", action="store_true",
|
||||
help="also assert the aborted STOR's control reply: RST -> 4xx/5xx "
|
||||
"(no false 226, TRAP-NET-18 hardening); graceful FIN -> 2xx (boundary)")
|
||||
a = ap.parse_args()
|
||||
|
||||
npass = nfail = 0
|
||||
for n in range(1, a.rounds + 1):
|
||||
log("TS %d" % int(time.time()))
|
||||
ctl_reply = "NA"
|
||||
alive = "no"
|
||||
try:
|
||||
ctl = raw_login(a.ip, a.port)
|
||||
ds = pasv_data_socket(ctl)
|
||||
ctl.sendall(("STOR /ab%04d.bin\r\n" % n).encode())
|
||||
ctl_reply = rd(ctl) # 150
|
||||
chunk = bytes(random.getrandbits(8) for _ in range(a.send))
|
||||
ds.sendall(chunk)
|
||||
if a.graceful:
|
||||
ds.shutdown(socket.SHUT_RDWR) # graceful FIN (client cancel)
|
||||
else:
|
||||
# abort: RST the data connection (linger 0)
|
||||
ds.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
|
||||
ds.close()
|
||||
time.sleep(0.5)
|
||||
ctl_reply = rd(ctl) # 426/550/226
|
||||
try:
|
||||
ctl.sendall(b"QUIT\r\n"); rd(ctl)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
ctl.close()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
ctl_reply = "EXC:%s" % type(e).__name__
|
||||
# control-channel liveness on a fresh connection
|
||||
try:
|
||||
c2 = raw_login(a.ip, a.port)
|
||||
c2.sendall(b"NOOP\r\n")
|
||||
alive = "yes" if rd(c2).startswith("2") else "no"
|
||||
c2.sendall(b"QUIT\r\n"); c2.close()
|
||||
except Exception:
|
||||
alive = "no"
|
||||
fu = followup_ok(a.ip, a.port)
|
||||
abort_ok = True
|
||||
if a.check_abort:
|
||||
code = ctl_reply[0] if (ctl_reply and ctl_reply[0].isdigit()) else "?"
|
||||
# RST: device must report 4xx/5xx on the aborted STOR (450); graceful
|
||||
# FIN still gets 226 (STOR has no length declaration - protocol boundary)
|
||||
abort_ok = (code == "2") if a.graceful else (code in ("4", "5"))
|
||||
ok = (alive == "yes" and fu and abort_ok)
|
||||
npass += 1 if ok else 0
|
||||
nfail += 0 if ok else 1
|
||||
log("ABORT %d sent=%d ctrl=%s alive=%s followup=%s abort5xx=%s" %
|
||||
(n, a.send, ctl_reply.replace("\r\n", "|")[:40], alive,
|
||||
"OK" if fu else "FAIL", "OK" if abort_ok else "FAIL" if a.check_abort else "-"))
|
||||
if a.gap > 0 and n < a.rounds:
|
||||
time.sleep(a.gap)
|
||||
log("SUMMARY rounds=%d pass=%d fail=%d" % (a.rounds, npass, nfail))
|
||||
return 0 if nfail == 0 else 1
|
||||
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user