#include #include #include #include #include #include #include #include "FreeRTOS.h" #include "lftpd.h" #include "private/lftpd_status.h" #include "private/lftpd_inet.h" #include "private/lftpd_log.h" #include "private/lftpd_string.h" #include "private/lftpd_io.h" #include "net_socket.h" #include "net_config.h" #include "lftpd_auth.h" #include "ftp_cfg.h" #include "ff.h" typedef struct { const char *command; int (*handler)(lftpd_client_t *client, const char *arg); } command_t; static int cmd_cwd(lftpd_client_t *client, const char *arg); static int cmd_dele(lftpd_client_t *client, const char *arg); static int cmd_epsv(lftpd_client_t *client, const char *arg); static int cmd_feat(lftpd_client_t *client, const char *arg); static int cmd_list(lftpd_client_t *client, const char *arg); static int cmd_nlst(lftpd_client_t *client, const char *arg); static int cmd_noop(lftpd_client_t *client, const char *arg); static int cmd_pass(lftpd_client_t *client, const char *arg); static int cmd_pasv(lftpd_client_t *client, const char *arg); static int cmd_pwd(lftpd_client_t *client, const char *arg); static int cmd_quit(lftpd_client_t *client, const char *arg); static int cmd_retr(lftpd_client_t *client, const char *arg); static int cmd_size(lftpd_client_t *client, const char *arg); static int cmd_stor(lftpd_client_t *client, const char *arg); static int cmd_syst(lftpd_client_t *client, const char *arg); static int cmd_type(lftpd_client_t *client, const char *arg); static int cmd_user(lftpd_client_t *client, const char *arg); static command_t commands[] = { { "CWD", cmd_cwd }, { "DELE", cmd_dele }, { "EPSV", cmd_epsv }, { "FEAT", cmd_feat }, { "LIST", cmd_list }, { "NLST", cmd_nlst }, { "NOOP", cmd_noop }, { "PASS", cmd_pass }, { "PASV", cmd_pasv }, { "PWD", cmd_pwd }, { "QUIT", cmd_quit }, { "RETR", cmd_retr }, { "SIZE", cmd_size }, { "STOR", cmd_stor }, { "SYST", cmd_syst }, { "TYPE", cmd_type }, { "USER", cmd_user }, { NULL, NULL }, }; /* N-19 FTP 鉴权:全局失败计数/锁定状态(跨会话持久——单连接服务器,全局 * 计数即可;成功清零、锁定期满自动放行,见 lftpd_auth.c) */ static ftp_auth_state_t s_auth_state; static int send_response(int socket, int code, bool include_code, bool multiline_start, const char *format, ...) { va_list args; static char message[256]; /* static: off-stack, single-client ftpTask */ static char response[512]; /* static: off-stack, single-client ftpTask */ va_start(args, format); vsnprintf(message, sizeof(message), format, args); va_end(args); if (include_code) { if (multiline_start) { snprintf(response, sizeof(response), "%d-%s%s", code, message, CRLF); } else { snprintf(response, sizeof(response), "%d %s%s", code, message, CRLF); } } else { snprintf(response, sizeof(response), "%s%s", message, CRLF); } return lftpd_inet_write_string(socket, response); } #define send_simple_response(socket, code, format, ...) \ send_response(socket, code, true, false, format, ##__VA_ARGS__) #define send_multiline_response_begin(socket, code, format, ...) \ send_response(socket, code, true, true, format, ##__VA_ARGS__) #define send_multiline_response_line(socket, format, ...) \ send_response(socket, 0, false, false, format, ##__VA_ARGS__) #define send_multiline_response_end(socket, code, format, ...) \ send_response(socket, code, true, false, format, ##__VA_ARGS__) static int send_list(int socket, const char *path) { static const char *directory_format = "drw-rw-rw- 1 owner group %13lu Jan 01 1970 %s\r\n"; static const char *file_format = "-rw-rw-rw- 1 owner group %13lu Jan 01 1970 %s\r\n"; void *dp = lftpd_io_opendir(path); if (dp == NULL) { return -1; } static char name[256]; /* static: off-stack, single-client ftpTask */ uint32_t size; int is_dir; /* N30-PROBE(临时实验):整份目录一次性发送(验证"背靠背多小段 * 毒化芯片 TX"假设——每行一个 net_send 时 LIST#4 必挂) */ static char batch[1536]; /* static: off-stack */ int blen = 0; int trunc = 0; while (lftpd_io_readdir(dp, name, sizeof(name), &size, &is_dir) == 0) { int n; if (is_dir) { n = snprintf(batch + blen, sizeof(batch) - blen, directory_format, (unsigned long)size, name); } else { n = snprintf(batch + blen, sizeof(batch) - blen, file_format, (unsigned long)size, name); } if (n < 0 || (size_t)n >= sizeof(batch) - blen) { trunc = 1; break; } blen += n; } lftpd_io_closedir(dp); /* lftpd_inet_write 成功返回字节数、失败返回 -1(非 0=成功语义) */ if (blen > 0) { if (lftpd_inet_write(socket, batch, blen) < 0) { return -1; } } if (trunc) { return -1; } return 0; } static int send_nlst(int socket, const char *path) { void *dp = lftpd_io_opendir(path); if (dp == NULL) { return -1; } static char name[256]; /* static: off-stack, single-client ftpTask */ uint32_t size; int is_dir; /* N30-PROBE(临时实验):同 send_list,一次性批量发送 */ static char batch[1536]; /* static: off-stack */ int blen = 0; int trunc = 0; while (lftpd_io_readdir(dp, name, sizeof(name), &size, &is_dir) == 0) { if (!is_dir) { int n = snprintf(batch + blen, sizeof(batch) - blen, "%s\r\n", name); if (n < 0 || (size_t)n >= sizeof(batch) - blen) { trunc = 1; break; } blen += n; } } lftpd_io_closedir(dp); /* lftpd_inet_write 成功返回字节数、失败返回 -1(非 0=成功语义) */ if (blen > 0) { if (lftpd_inet_write(socket, batch, blen) < 0) { return -1; } } if (trunc) { return -1; } return 0; } static int send_file(int socket, const char *path) { int fh = lftpd_io_open_read(path); if (fh < 0) { lftpd_log_error("failed to open file for read"); return -1; } static unsigned char buffer[1024]; /* static: 1KB off-stack, single-client ftpTask */ int read_len; while ((read_len = lftpd_io_read(fh, buffer, sizeof(buffer))) > 0) { lftpd_task_wd_beat(); /* 看门狗:每分片喂心跳(文件传输分片处,RETR) */ unsigned char *p = buffer; while (read_len > 0) { int write_len = lftpd_inet_write(socket, p, read_len); if (write_len < 0) { lftpd_log_error("write error"); lftpd_io_close(fh); return -1; } p += write_len; read_len -= write_len; } } lftpd_io_close(fh); return 0; } static int receive_file(int socket, const char *path) { int fh = lftpd_io_open_write(path); if (fh < 0) { lftpd_log_error("failed to open file for write"); return -1; } static unsigned char buffer[1024]; /* static: 1KB off-stack, single-client ftpTask */ int err; int total = 0; while ((err = lftpd_inet_read(socket, buffer, sizeof(buffer))) > 0) { lftpd_task_wd_beat(); /* 看门狗:每分片喂心跳(文件传输分片处,STOR) */ total += err; if (lftpd_io_write(fh, buffer, err) != err) { err = -1; break; } } lftpd_io_close(fh); if (err < 0) { /* 异常结束(连接重置/超时/预算耗尽):删除截断文件防残留并返回错误码 * → STOR 回报 450 而非误报 226(TRAP-NET-18 待办 #3) */ lftpd_io_unlink(path); return err; } /* EOF 正常结束:total==0 是合法空文件上传(如以空内容 STOR 清台账), * 回报 226 成功(2026-10-02 台架:0 字节 STOR 曾被误判故障并删文件) */ return 0; } static int cmd_cwd(lftpd_client_t *client, const char *arg) { if (arg == NULL || strlen(arg) == 0) { send_simple_response(client->socket, 550, STATUS_550); return -1; } char *path = lftpd_io_canonicalize_path(client->directory, arg); /* 根目录始终有效,跳过 stat 检查(FatFS FF_FS_RPATH=0 下 stat 根目录可能失败) */ if (strcmp(path, "/") != 0) { uint32_t size; int is_dir; if (lftpd_io_stat(path, &size, &is_dir) != 0 || !is_dir) { send_simple_response(client->socket, 550, STATUS_550); vPortFree(path); return -1; } } strncpy(client->directory, path, sizeof(client->directory) - 1); client->directory[sizeof(client->directory) - 1] = '\0'; vPortFree(path); send_simple_response(client->socket, 250, STATUS_250); return 0; } static int cmd_dele(lftpd_client_t *client, const char *arg) { /* N-19:写命令门——[ftp] write=0 时拒绝(鉴权门已保证此处必为已登录态) */ const ftp_cfg_t *cfg = ftp_cfg_get(); if ((cfg == NULL) || (cfg->write_en == 0u)) { send_simple_response(client->socket, 550, STATUS_550); return 0; } if (arg == NULL || strlen(arg) == 0) { send_simple_response(client->socket, 550, STATUS_550); return -1; } char *path = lftpd_io_canonicalize_path(client->directory, arg); uint32_t size; int is_dir; if (lftpd_io_stat(path, &size, &is_dir) != 0) { send_simple_response(client->socket, 550, STATUS_550); vPortFree(path); return -1; } if (is_dir) { send_simple_response(client->socket, 550, STATUS_550); vPortFree(path); return -1; } lftpd_io_unlink(path); vPortFree(path); send_simple_response(client->socket, 250, STATUS_250); return 0; } static int cmd_epsv(lftpd_client_t *client, const char *arg) { if (client->data_socket >= 0) { lftpd_inet_close(client->data_socket); client->data_socket = -1; } int listener_socket = lftpd_inet_listen(0); if (listener_socket < 0) { send_simple_response(client->socket, 425, STATUS_425); return -1; } int port = lftpd_inet_get_socket_port(listener_socket); send_simple_response(client->socket, 229, STATUS_229, port); lftpd_log_debug("EPSV listener on port %d", port); client->data_socket = listener_socket; return 0; } static int cmd_feat(lftpd_client_t *client, const char *arg) { send_multiline_response_begin(client->socket, 211, STATUS_211); send_multiline_response_line(client->socket, "EPSV"); send_multiline_response_line(client->socket, "PASV"); send_multiline_response_line(client->socket, "SIZE"); send_multiline_response_line(client->socket, "NLST"); send_multiline_response_end(client->socket, 211, STATUS_211); return 0; } static int cmd_list(lftpd_client_t *client, const char *arg) { if (client->data_socket == -1) { send_simple_response(client->socket, 425, STATUS_425); return -1; } send_simple_response(client->socket, 150, STATUS_150); int data_sock = lftpd_inet_accept_timeout(client->data_socket, 5000); if (data_sock < 0) { lftpd_log_error("%s accept failed, sk=%d", __func__, client->data_socket); send_simple_response(client->socket, 425, STATUS_425); lftpd_inet_close(client->data_socket); client->data_socket = -1; return -1; } int err = send_list(data_sock, client->directory); /* 传输结束必须关闭数据连接:对端以 EOF 判定"数据传完"(LIST/RETR); * 不关会让客户端一直读到超时(曾试"优雅断开+复用监听器"方案,因该语义限制 * 与芯片行为未能稳定工作,见 链路延迟测试说明.md §8.2)。 */ lftpd_inet_close(data_sock); client->data_socket = -1; if (err == 0) { send_simple_response(client->socket, 226, STATUS_226); } else { send_simple_response(client->socket, 550, STATUS_550); } return 0; } static int cmd_nlst(lftpd_client_t *client, const char *arg) { if (client->data_socket == -1) { send_simple_response(client->socket, 425, STATUS_425); return -1; } send_simple_response(client->socket, 150, STATUS_150); int data_sock = lftpd_inet_accept_timeout(client->data_socket, 5000); if (data_sock < 0) { lftpd_log_error("%s accept failed, sk=%d", __func__, client->data_socket); send_simple_response(client->socket, 425, STATUS_425); lftpd_inet_close(client->data_socket); client->data_socket = -1; return -1; } int err = send_nlst(data_sock, client->directory); /* 传输结束必须关闭数据连接:对端以 EOF 判定"数据传完"(LIST/RETR); * 不关会让客户端一直读到超时(曾试"优雅断开+复用监听器"方案,因该语义限制 * 与芯片行为未能稳定工作,见 链路延迟测试说明.md §8.2)。 */ lftpd_inet_close(data_sock); client->data_socket = -1; if (err == 0) { send_simple_response(client->socket, 226, STATUS_226); } else { send_simple_response(client->socket, 550, STATUS_550); } return 0; } static int cmd_noop(lftpd_client_t *client, const char *arg) { send_simple_response(client->socket, 200, STATUS_200); return 0; } static int cmd_pass(lftpd_client_t *client, const char *arg) { const ftp_cfg_t *cfg = ftp_cfg_get(); ftp_auth_creds_t creds; ftp_auth_result_t res; if (cfg == NULL) { /* fail-closed:配置不可用即拒绝(s_cfg 静态常驻,正常不会走到) */ send_simple_response(client->socket, 530, STATUS_530); return 0; } creds.p_user = cfg->user; creds.p_pwd_hash = cfg->pwd_hash; creds.write_en = cfg->write_en; res = ftp_auth_check(&creds, client->auth_user_ok, (arg != NULL) ? arg : "", &s_auth_state, (uint32_t)xTaskGetTickCount()); if (res == FTP_AUTH_OK) { client->auth_logged_in = 1; send_simple_response(client->socket, 230, STATUS_230); return 0; } /* 预期事件(爆破/配错口令):用 INFO 而非 ERROR——[ERR] 是 @test Forbidden 标记 */ if (res == FTP_AUTH_LOCKED) { lftpd_log_info("auth locked after %u failures", (unsigned)FTP_AUTH_FAIL_MAX); } else { lftpd_log_info("auth failed for user '%s'", client->auth_user); } send_simple_response(client->socket, 530, STATUS_530); return 0; } static int cmd_pasv(lftpd_client_t *client, const char *arg) { if (client->data_socket >= 0) { lftpd_inet_close(client->data_socket); client->data_socket = -1; } /* N34 阶段1-6:原 3×200ms 应用层重试已删(lftpd_inet_listen 单次化)—— * 失败直接 425,FTP 客户端自带传输重试语义;服务端不再叠阻塞重试 */ int listener_socket = lftpd_inet_listen(0); if (listener_socket < 0) { send_simple_response(client->socket, 425, STATUS_425); return -1; } int port = lftpd_inet_get_socket_port(listener_socket); { struct net_sockaddr_in local_addr; int addrlen = sizeof(local_addr); int err = net_getsockname(client->socket, (struct net_sockaddr *)&local_addr, &addrlen); if (err != 0) { lftpd_log_error("error getting client IP info"); send_simple_response(client->socket, 425, STATUS_425); lftpd_inet_close(listener_socket); return -1; } uint32_t ip = net_htonl(local_addr.sin_addr.s_addr); send_simple_response(client->socket, 227, STATUS_227, (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip >> 0) & 0xff, (port >> 8) & 0xff, (port >> 0) & 0xff); } lftpd_log_debug("PASV listener on port %d", port); client->data_socket = listener_socket; return 0; } static int cmd_pwd(lftpd_client_t *client, const char *arg) { send_simple_response(client->socket, 257, "\"%s\"", client->directory); return 0; } static int cmd_quit(lftpd_client_t *client, const char *arg) { send_simple_response(client->socket, 221, STATUS_221); return -1; } static int cmd_retr(lftpd_client_t *client, const char *arg) { if (client->data_socket == -1) { send_simple_response(client->socket, 425, STATUS_425); return -1; } send_simple_response(client->socket, 150, STATUS_150); int data_sock = lftpd_inet_accept_timeout(client->data_socket, 5000); if (data_sock < 0) { lftpd_log_error("%s accept failed, sk=%d", __func__, client->data_socket); send_simple_response(client->socket, 425, STATUS_425); lftpd_inet_close(client->data_socket); client->data_socket = -1; return -1; } char *path = lftpd_io_canonicalize_path(client->directory, arg); lftpd_log_debug("send '%s'", path); int err = send_file(data_sock, path); /* 传输结束必须关闭数据连接:对端以 EOF 判定"数据传完"(LIST/RETR); * 不关会让客户端一直读到超时(曾试"优雅断开+复用监听器"方案,因该语义限制 * 与芯片行为未能稳定工作,见 链路延迟测试说明.md §8.2)。 */ lftpd_inet_close(data_sock); client->data_socket = -1; if (err == 0) { lftpd_log_info("DOWNLOAD '%s' OK", path); send_simple_response(client->socket, 226, STATUS_226); } else { lftpd_log_error("DOWNLOAD '%s' FAILED", path); send_simple_response(client->socket, 450, STATUS_450); } vPortFree(path); return 0; } static int cmd_size(lftpd_client_t *client, const char *arg) { if (arg == NULL) { send_simple_response(client->socket, 550, STATUS_550); return 0; } char *path = lftpd_io_canonicalize_path(client->directory, arg); lftpd_log_debug("size %s", path); uint32_t size; int is_dir; if (lftpd_io_stat(path, &size, &is_dir) == 0 && !is_dir) { send_simple_response(client->socket, 213, "%lu", (unsigned long)size); } else { send_simple_response(client->socket, 550, STATUS_550); } vPortFree(path); return 0; } static int cmd_stor(lftpd_client_t *client, const char *arg) { /* N-19:写命令门——[ftp] write=0 时拒绝(鉴权门已保证此处必为已登录态) */ const ftp_cfg_t *cfg = ftp_cfg_get(); if ((cfg == NULL) || (cfg->write_en == 0u)) { send_simple_response(client->socket, 550, STATUS_550); return 0; } if (client->data_socket == -1) { send_simple_response(client->socket, 425, STATUS_425); return -1; } send_simple_response(client->socket, 150, STATUS_150); int data_sock = lftpd_inet_accept_timeout(client->data_socket, 5000); if (data_sock < 0) { lftpd_log_error("%s accept failed, sk=%d", __func__, client->data_socket); send_simple_response(client->socket, 425, STATUS_425); lftpd_inet_close(client->data_socket); client->data_socket = -1; return -1; } char *path = lftpd_io_canonicalize_path(client->directory, arg); lftpd_log_debug("receive '%s'", path); int err = receive_file(data_sock, path); /* 传输结束必须关闭数据连接:对端以 EOF 判定"数据传完"(LIST/RETR); * 不关会让客户端一直读到超时(曾试"优雅断开+复用监听器"方案,因该语义限制 * 与芯片行为未能稳定工作,见 链路延迟测试说明.md §8.2)。 */ lftpd_inet_close(data_sock); client->data_socket = -1; if (err == 0) { uint32_t size = 0; int is_dir = 0; lftpd_io_stat(path, &size, &is_dir); lftpd_log_info("UPLOAD '%s' %lu bytes OK", path, (unsigned long)size); send_simple_response(client->socket, 226, STATUS_226); } else { lftpd_log_error("UPLOAD '%s' FAILED", path); send_simple_response(client->socket, 450, STATUS_450); } vPortFree(path); return 0; } static int cmd_syst(lftpd_client_t *client, const char *arg) { send_simple_response(client->socket, 215, "UNIX Type: L8"); return 0; } static int cmd_type(lftpd_client_t *client, const char *arg) { send_simple_response(client->socket, 200, STATUS_200); return 0; } static int cmd_user(lftpd_client_t *client, const char *arg) { const ftp_cfg_t *cfg = ftp_cfg_get(); client->auth_logged_in = 0; client->auth_user[0] = '\0'; client->auth_user_ok = 0; if ((arg != NULL) && (cfg != NULL)) { strncpy(client->auth_user, arg, sizeof(client->auth_user) - 1); client->auth_user[sizeof(client->auth_user) - 1] = '\0'; client->auth_user_ok = (uint8_t)ftp_auth_user_match(cfg->user, client->auth_user); } /* 不论用户名对错一律 331:不泄露"用户是否存在";错用户名在 cmd_pass * 仍走一次同耗时哈希比较(时间拉平,见 lftpd_auth.h 说明 2) */ send_simple_response(client->socket, 331, STATUS_331); return 0; } static int handle_control_channel(lftpd_client_t *client) { int err = send_simple_response(client->socket, 220, STATUS_220); if (err != 0) { lftpd_log_error("error sending welcome message"); goto cleanup; } static char read_buffer[512]; /* static: off-stack, single-client ftpTask */ while (err == 0) { int line_len = lftpd_inet_read_line(client->socket, read_buffer, sizeof(read_buffer)); if (line_len != 0) { lftpd_log_error("error reading next command"); goto cleanup; } int index; char *p = strchr(read_buffer, ' '); if (p != NULL) { index = (int)(p - read_buffer); } else { index = (int)strlen(read_buffer); } if (index >= 5) { err = send_simple_response(client->socket, 500, STATUS_500); continue; } char command_tmp[4 + 1]; memset(command_tmp, 0, sizeof(command_tmp)); memcpy(command_tmp, read_buffer, (size_t)index); for (int i = 0; command_tmp[i]; i++) { command_tmp[i] = (char)toupper((int)command_tmp[i]); } /* N-19:未鉴权命令门——仅 QUIT/FEAT/SYST/NOOP 放行(客户端兼容需要, * 且无信息量);其余一律 530。USER/PASS 本身也在放行名单外, * 但它们在 gate 之前不可能通过 → 单独放行见下(USER/PASS 例外) */ if (client->auth_logged_in == 0) { if ((strcmp(command_tmp, "USER") != 0) && (strcmp(command_tmp, "PASS") != 0) && (strcmp(command_tmp, "QUIT") != 0) && (strcmp(command_tmp, "FEAT") != 0) && (strcmp(command_tmp, "SYST") != 0) && (strcmp(command_tmp, "NOOP") != 0)) { send_simple_response(client->socket, 530, STATUS_530); continue; } } bool matched = false; for (int i = 0; commands[i].command; i++) { if (strcmp(commands[i].command, command_tmp) == 0) { static char arg_buf[512]; /* static: off-stack, single-client ftpTask */ const char *arg = NULL; if (index < (int)strlen(read_buffer)) { strncpy(arg_buf, read_buffer + index + 1, sizeof(arg_buf) - 1); arg_buf[sizeof(arg_buf) - 1] = '\0'; arg = lftpd_string_trim(arg_buf); } lftpd_log_info("CMD %s %s", command_tmp, arg ? arg : ""); err = commands[i].handler(client, arg); matched = true; break; } } if (!matched) { send_simple_response(client->socket, 502, STATUS_502); } } cleanup: /* 数据监听器在复用模式下会跨命令保留,会话结束必须显式释放(否则每次会话泄漏一个 Socket) */ if (client->data_socket >= 0) { lftpd_inet_close(client->data_socket); client->data_socket = -1; } /* 控制通道不得 close:单连接模型下它即监听器(fd 与监听一体),EOF/断连时 * net 层已 auto_relisten 回 LISTEN;此处 close 会连监听一起毁掉并触发 * close→重建竞态(2026-09-24 台架 F1:快速重连在芯片沉降窗内被拒)。 * 直接返回外层循环,在同一监听 fd 上再次 accept。 */ return 0; } int lftpd_start(const char *directory, int port, lftpd_t *lftpd) { memset(lftpd, 0, sizeof(lftpd_t)); lftpd->directory = directory; lftpd->port = port; lftpd->server_socket = -1; /* memset 清零后显式置"无监听器"(0 是合法 fd) */ /* N-19:出厂默认口令在用告警(台架锚点;改了 [ftp] pwd_hash 即消失) */ if (ftp_cfg_is_default_pwd() != 0) { lftpd_log_info("using factory default password"); } while (true) { lftpd_task_wd_beat(); /* 看门狗:事件循环每轮喂心跳(ftpTask 主循环节拍点) */ if (lftpd->server_socket < 0) { /* 首次启动 / 网络重建后:全量创建监听器(socket+bind+listen) */ lftpd->server_socket = lftpd_inet_listen(port); if (lftpd->server_socket < 0) { lftpd_log_error("error creating listener"); /* 看门狗:3s 重试等待拆短片喂心跳(单次 3s 挂起超 IWDG 最坏窗 * ~1.7s → 误复位;与 time_sync.c 监听重试同模式) */ uint32_t wd_left = 3000U; while (wd_left >= LFTPD_WAIT_SLICE_MS) { lftpd_task_wd_beat(); vTaskDelay(pdMS_TO_TICKS(LFTPD_WAIT_SLICE_MS)); wd_left -= LFTPD_WAIT_SLICE_MS; } continue; } } /* 后续会话:单连接模型下控制通道 fd 即监听器 fd,会话断连后 net 层已 * auto_relisten 回 LISTEN(同 fd,实测 ~50ms,D10 机制),直接复用; * 不得 close→重建——close 会毁掉已就绪的监听器,重建的 open 落在芯片 * 沉降窗内返回假 OK → 窗内快速重连被拒(2026-09-24 台架 F1 发现; * 铁律:EOF 时应用层不得 net_close 监听 fd)。 */ lftpd_log_info("waiting for connection..."); int client_socket = lftpd_inet_accept(lftpd->server_socket); if (client_socket < 0) { lftpd_log_error("error accepting client socket"); lftpd_inet_close(lftpd->server_socket); lftpd->server_socket = -1; /* 监听器已不可用(网络重建):下轮重建 */ continue; } { struct net_sockaddr_in addr; int addrlen = sizeof(addr); /* N34 E13 §6 修复:原用 getsockname 打印的是**本机** IP:21(且 * ntoa 段序反转成 "100.1.168.192")——改 getpeername 打印真实 * 对端(现场排障需要"谁连进来");未连接则退回无地址形态 */ if (net_getpeername(client_socket, (struct net_sockaddr *)&addr, &addrlen) == 0) { char ip_str[16]; net_inet_ntoa(addr.sin_addr.s_addr, ip_str); /* 对端端口:直接取 getpeername 结果(原 get_socket_port * 走 getsockname,打印的是本机监听端口) */ int remote_port = (int)net_ntohs(addr.sin_port); lftpd_log_info("connection received from %s:%d...", ip_str, remote_port); } else { lftpd_log_info("connection received..."); } } static lftpd_client_t client; /* static: off-stack session context (single session) */ memset(&client, 0, sizeof(client)); strncpy(client.directory, directory, sizeof(client.directory) - 1); client.directory[sizeof(client.directory) - 1] = '\0'; client.socket = client_socket; client.data_socket = -1; lftpd->client = &client; handle_control_channel(&client); lftpd->client = NULL; /* 控制 Socket 不关闭:断连时 net 层已 auto_relisten(同 fd), * 外层循环直接复用同一监听 fd 再次 accept */ } } int lftpd_stop(lftpd_t *lftpd) { if (lftpd->server_socket >= 0) { lftpd_inet_close(lftpd->server_socket); } if (lftpd->client) { lftpd_inet_close(lftpd->client->socket); } return 0; }