#define DBG_TAG "[LFT_INET]" #include "private/lftpd_inet.h" #include "dbg_log.h" #include "net_socket.h" #include /* * E-02 头阻塞修复 + recv pending 化(M3 台架实证): * netTask 是全部 socket 操作的唯一执行者;lftpd 若用**阻塞** net_recv 等待对端 * 下一行/下一块数据 → 只要客户端连上却不发完整行,netTask 就被长期占用, * 其余任务(含远端显示)的网络操作整体冻结(实测一次空闲 FTP 登录让 LCDMEM * 整帧等待 17.7s)。 * 现形态:每次等待 = 一次挂起登记(netTask 零占用,完成按主循环节拍检查), * 调用方单次超时 = 剩余总预算(总等待 ≤ LFTPD_TOTAL_BUDGET_MS)。 */ #define LFTPD_TOTAL_BUDGET_MS 30000U /* * 看门狗心跳钩子弱默认实现:空(未接应用层的构建——如模拟器——不喂心跳, * 看门狗为 MCU 构建专属概念)。MCU 构建下 App/util/task_watchdog.c 提供 * 同名强符号(路由到 task_wd_beat(TASK_WD_FTP)),链接器取强符号 * (与 wd_platform_* 钩子同机制)。 */ #if defined(__CC_ARM) __weak #endif void lftpd_task_wd_beat(void) { } int lftpd_inet_listen(int port) { /* 应用层阻塞重试退役(原 5 轮 500ms/2000ms,最坏 10s 阻塞 * ftpTask)——TRAP-NET-09 的启动形态已由就绪屏障消除(FTP_READY 等 NET_READY, * lftpd_start 外层本就有监听失败重建循环);芯片忙窗失败属罕见,PASV 数据 * 监听交给 FTP 客户端自身的重试语义(主流 FTP 服务器 passive listen 失败 * 亦直接 425,不在服务端叠应用层阻塞重试) */ lftpd_task_wd_beat(); /* 看门狗:入口喂心跳 */ int s = net_socket(NET_AF_INET, NET_SOCK_STREAM); if (s < 0) { DBG_ERROR("create socket"); return -1; } struct net_sockaddr_in addr; addr.sin_family = NET_AF_INET; addr.sin_port = net_htons((uint16_t)port); addr.sin_addr.s_addr = 0; if (net_bind(s, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) { DBG_ERROR("bind port %d", port); net_close(s); return -1; } if (net_listen(s) >= 0) { return s; } DBG_ERROR("listen port %d failed", port); net_close(s); return -1; } int lftpd_inet_get_socket_port(int socket) { struct net_sockaddr_in addr; int addrlen = sizeof(addr); if (net_getsockname(socket, (struct net_sockaddr *)&addr, &addrlen) < 0) { DBG_ERROR("error getting socket port number"); return -1; } return (int)net_ntohs(addr.sin_port); } int lftpd_inet_accept(int socket) { /* 网络重建(net_init)后旧 Socket 已失效:检测到代次变化即返回 -1, * 由 lftpd_start 外层循环重建监听(网络未就绪时其 listen 会重试) */ uint32_t gen = net_get_generation(); while (1) { int s = net_accept(socket, NULL, NULL); if (s >= 0) { return s; } if (net_get_generation() != gen) { /* 运维改 IP 的正常后果,不是缺陷 → info(级别划分见存储架构说明 §10.3) */ DBG_INFO("network re-init: abort accept, relisten"); return -1; } lftpd_task_wd_beat(); /* 看门狗:自旋轮次喂心跳(无限等待,可能长时间无客户端) */ vTaskDelay(pdMS_TO_TICKS(100)); } } int lftpd_inet_accept_timeout(int socket, int timeout_ms) { int elapsed = 0; while (elapsed < timeout_ms) { int s = net_accept(socket, NULL, NULL); if (s >= 0) { return s; } /* F-NET-15 M2(自愈专项实验 2026-10-12):镜像门控退役——原直读 p_sock->state 在 * relisten 忙期(CLOSED 瞬态)误判"已关闭",扔掉 ~4.9s 等待预算;armed 门控 * 认"监听器在服役(LISTENING)或待重建且 RX 有残留(closed-with-data 救回窗)", * 与 :7003 的 N-32 P1 同型修复 */ if (net_listener_armed(socket) == 0) { /* **故意留 error**:控制连接还活着、我方数据监听却已不在服役 → * 这是本侧异常,不是对端行为 */ DBG_ERROR("data listener disarmed, accept aborted"); return -1; } lftpd_task_wd_beat(); /* 看门狗:每轮喂心跳(数据连接等待最长 5s) */ vTaskDelay(pdMS_TO_TICKS(100)); elapsed += 100; } /* 客户端连上控制通道却没来建数据连接(中止/被防火墙拦)= 对端行为 → info */ DBG_INFO("accept timeout (%dms) - peer never opened data channel", timeout_ms); return -1; } int lftpd_inet_read_line(int socket, char *buffer, size_t buffer_len) { TickType_t t0 = xTaskGetTickCount(); memset(buffer, 0, buffer_len); int total_read_len = 0; while (total_read_len < (int)buffer_len) { /* 单次预算 = 剩余总预算(pending:等待期 netTask 零占用,调用方 * 在自己上下文睡任务通知) */ TickType_t elapsed = xTaskGetTickCount() - t0; uint32_t rem = (elapsed >= (TickType_t)pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) ? 0U : (uint32_t)((TickType_t)LFTPD_TOTAL_BUDGET_MS - elapsed); if (rem == 0U) { return -1; /* 总预算耗尽(语义同旧 30s 超时) */ } lftpd_task_wd_beat(); /* 看门狗:每片前喂心跳(长等待无数据期保持节奏) */ if (rem > LFTPD_WAIT_SLICE_MS) { rem = LFTPD_WAIT_SLICE_MS; /* 分片:单次 30s 挂起拆为 ≤200ms 片 */ } int read_len = net_recv_to(socket, buffer + total_read_len, (int)(buffer_len - total_read_len - 1), rem); if (read_len == 0) { return -1; /* 对端关闭 */ } if (read_len == -2) { continue; /* 本次预算等满无数据 → 下轮 rem=0 退出 */ } if (read_len < 0) { return read_len; /* 真错误 */ } total_read_len += read_len; char *p = strstr(buffer, "\r\n"); if (p != NULL) { *p = '\0'; return 0; } } return -1; } int lftpd_inet_write_string(int socket, const char *message) { const char *p = message; int length = (int)strlen(message); while (length > 0) { int write_len = net_send(socket, p, length, 0); /* write_len == 0:发送超时且零进展(对端未消费数据);不判为致命则 * length 永不减少,将在此空转(任务死锁)。返回错误交会话层拆除 * 连接(lftpd.c handle_control_channel 的 cleanup 路径)。 */ if (write_len <= 0) { DBG_ERROR("write error, ret=%d", write_len); return -1; } p += write_len; length -= write_len; } return 0; } int lftpd_inet_write(int socket, const void *buf, int len) { const char *p = (const char *)buf; int remaining = len; while (remaining > 0) { int n = net_send(socket, p, remaining, 0); /* n == 0:发送超时且零进展(对端停滞/断链),判为致命:否则 remaining * 永不减少 → 死循环。调用方(lftpd.c send_file)收到 < 0 后终止传输 * 并向上报 450 错误,会话保持可用。 */ if (n <= 0) { DBG_ERROR("write error, ret=%d", n); return -1; } p += n; remaining -= n; } return len; } int lftpd_inet_read(int socket, void *buf, int len) { /* pending 读:总预算 30s 不变,等待期 netTask 零占用 * (空闲数据连接不再冻结全网) */ TickType_t t0 = xTaskGetTickCount(); for (;;) { TickType_t elapsed = xTaskGetTickCount() - t0; uint32_t rem = (elapsed >= (TickType_t)pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) ? 0U : (uint32_t)((TickType_t)LFTPD_TOTAL_BUDGET_MS - elapsed); if (rem == 0U) { return -1; /* 总预算耗尽(语义同旧 30s 超时) */ } lftpd_task_wd_beat(); /* 看门狗:每片前喂心跳(长等待无数据期保持节奏) */ if (rem > LFTPD_WAIT_SLICE_MS) { rem = LFTPD_WAIT_SLICE_MS; /* 分片:单次 30s 挂起拆为 ≤200ms 片 */ } int n = net_recv_to(socket, buf, len, rem); if (n >= 0) { return n; /* >0 数据 / 0 EOF */ } if (n == -1) { return -1; /* 真错误 */ } /* n == -2:本次预算等满无数据 → 下轮 rem=0 退出 */ } } int lftpd_inet_close(int socket) { return net_close(socket); }