From 66725c0fa38b52093760f500c7dcf47642c0423a Mon Sep 17 00:00:00 2001 From: super_agent_bot Date: Wed, 4 Nov 2026 09:15:52 +0800 Subject: [PATCH] =?UTF-8?q?test(net/udp):=20=E9=98=B6=E6=AE=B5=2016=20?= =?UTF-8?q?=E5=88=A4=E6=8D=AE=E6=96=87=E5=AD=97=E6=8C=89=E7=9C=9F=E6=9C=BA?= =?UTF-8?q?=E5=AE=9A=E6=A1=88=E6=94=B9=E5=86=99=EF=BC=8CPC=20=E5=AF=B9?= =?UTF-8?q?=E7=AB=AF=E4=B8=8D=E5=86=8D=E8=A2=AB=20ICMP=20=E6=8B=92?= =?UTF-8?q?=E6=94=B6=E6=89=93=E7=88=86=EF=BC=88#73=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 板级 1603 的标题写的是 "48-byte reply, no header strip in client mode",判据本身 (读回 48 字节且逐字节等于发出去的载荷)在真机上就是对的——错的是那句解释。标题 与两处注释按定案改写,判据一字不动:改判据等于把测出来的事实抹掉。 PC 对端 `test/host/udp_queue_bench.py` 修了一条会把台架自己打死的毛病:Windows 上 对端端口没人听时,发出去的 UDP 会收回 ICMP port-unreachable,CPython 把它变成 `ConnectionResetError [WinError 10054]` 挂在下一次 recv 上(3.14 既没有 `socket.SIO_UDP_CONNRESET` 也拒绝那个数字 ioctl,只能接住这个异常)。原来的写法是 一接到就立刻重发,run #3 从第 148 秒起以约 100 包/秒灌进 CH395F,日志刷出 940,949 字节的「TC 0x02: 无回显,重发」——**板子的真实行为被自己的对端淹掉了**, 看起来却像板子不回包。现在一轮之内跑满预算、按次数限流打印。 回路自检 `test/host/udp_queue_bench_loopback.py` 改后 **27/27 PASSED**。 --- test/board/net_test_task.c | 14 ++++++++++---- test/host/udp_queue_bench.py | 30 ++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/test/board/net_test_task.c b/test/board/net_test_task.c index dd4c262a..eb36cac1 100644 --- a/test/board/net_test_task.c +++ b/test/board/net_test_task.c @@ -996,7 +996,9 @@ void net_layer_test_main(void) * 「台架必验」五项: * 1602 → ① server 形态首包(bind 时 DesIP 已写在全F)+ ⑤ 顺序 * 1603 → TRAP-NET-22 缺陷本体:48 字节小应答在 client 形态下 - * 必须一字节不少(旧实现恒剥 8 字节 → 这里会变成 40) + * 必须一字节不少。期望值 2026-11-04 由真机定过案:把 DesIP 从全F + * 改成具体对端**不会**让芯片停止前置信息头(第一轮实测 n=56、 + * 首字节 0x38),所以 client 形态同样要剥头,剥完才是 48 * 1604 → ② sendto 切形态后能否真恢复收其它源地址 * 1605 → ③ 截断包排空后下一个包是否完整 * 1607 → ④ 挂起 recv 等待期间的 IWDG 余量(无 [ERR][WD] task stall) @@ -1386,10 +1388,14 @@ void net_layer_udp_bench_test_main(void) TEST_CHECK(net_udp_get_mode(fd) == NET_UDP_MODE_CLIENT, "TC-NET-1602e: sendto switched mode to CLIENT (%d)", net_udp_get_mode(fd)); - /* ---------- TC-NET-1603:48 字节小应答(TRAP-NET-22 本体) ---------- */ - DBG_INFO("--- TC-NET-1603: 48-byte reply, no header strip in client mode ---"); + /* ---------- TC-NET-1603:48 字节小应答(TRAP-NET-22 本体) ---------- * + * 期望的是"仍然剥头":本层 Socket 恒以 DesIP=全 F 开出,运行时把 DesIP 改成 + * 具体对端(1602e 那一步)不会让芯片停止前置 8 字节信息头——2026-11-04 08:16 + * 真机第一轮跑出来的正是 n=56 / 首字节 0x38,把这条写死在代码里(详见 + * TRAP-NET-22 的 2026-11-04 追加段)。 */ + DBG_INFO("--- TC-NET-1603: 48-byte reply, header still stripped in client form ---"); n = udp_bench_expect(fd, 0x02, 48, UDP_BENCH_BUF_SIZE, UDP_BENCH_WAIT_MS, NULL, "TC-NET-1603"); - TEST_CHECK(n == 48, "TC-NET-1603a: 48B intact in client mode (n=%d)", n); + TEST_CHECK(n == 48, "TC-NET-1603a: 48B intact in client form, header stripped (n=%d)", n); if (n == 48) { rc = udp_bench_echo(fd, n, NULL, "TC-NET-1603"); TEST_CHECK(rc == 48, "TC-NET-1603b: echo via booked peer (dest NULL) rc=%d", rc); diff --git a/test/host/udp_queue_bench.py b/test/host/udp_queue_bench.py index ad5b4b9f..21f5d731 100644 --- a/test/host/udp_queue_bench.py +++ b/test/host/udp_queue_bench.py @@ -156,12 +156,30 @@ class Peer: return self.check(False, "TC 0x{0:02X} 端口 {1}: 板端 {2:.0f}s 内无回显" .format(tag, port, step_budget)) attempt = 4.0 if first_wait > 0 else 6.0 - try: - got, addr = self._recv_after(sock, min(attempt, left)) - self.note_port(sock, tag) - except (socket.timeout, ConnectionResetError): - # ConnectionResetError = 对端端口当时没开换来的 ICMP 不可达(见 - # new_peer_socket 的说明);这里和「这一轮没收到」同等对待,继续重试。 + round_deadline = time.time() + min(attempt, left) + resets = 0 + while got is None: + remain = round_deadline - time.time() + if remain <= 0: + break + try: + got, addr = self._recv_after(sock, remain) + self.note_port(sock, tag) + except socket.timeout: + break # 这一轮确实等满了,出去重发一次 + except ConnectionResetError: + # ICMP 端口不可达(见 new_peer_socket 的说明)——它只说明「板端把 + # 某个回显发给了本端已经关掉的临时端口」,并不代表本端这一步该重发。 + # 2026-11-04 台架上的教训:把它当成「没收到」立刻重发,会在一秒里 + # 往芯片灌上百个包(日志 148 s 起每秒上百行),把整条梯子彻底冲乱。 + # 正确做法:本轮不再重发,安静地等完本轮预算,节奏仍由 attempt 决定。 + resets += 1 + if resets == 1 or resets % 20 == 0: + self.log("TC 0x{0:02X}: 收到 {1} 次 ICMP 不可达(板端回显打到了本端" + "已关闭的端口),本轮不重发,继续等到 {2:.0f}s" + .format(tag, resets, round_deadline)) + time.sleep(0.2) + if got is None and resets == 0: self.log("TC 0x{0:02X}: 无回显,重发(本步剩余 {1:.0f}s)".format(tag, left)) # 一个数据报进、一个数据报出——唯一例外是本步载荷超过芯片发送缓冲(1KB): # 那是已登记的芯片限制(NET_SEND_CHUNK_MAX 本是 TCP 流的分块口径,UDP 上每块