diff --git a/AGENT.MD b/AGENT.MD index e97c02e..cb13d41 100644 --- a/AGENT.MD +++ b/AGENT.MD @@ -107,4 +107,9 @@ session ticket key 支持持久化到文件:新增 `paze_tls_config_set_session_ - `pssh ssh -A`:开 `auth-agent@openssh.com` 通道,事件循环桥接本地 agent socket 与通道(`ssh_agent_fd`)。 - 限制:PazeSSH 仅 POSIX 可用;Windows agent 为命名管道(无 select fd)运行时明确禁用并提示;无本地 agent 时报 `cannot connect local agent`。 +### 11. 自动重连(实用版)(✅ 已完成 2026-08-14) + +- `pssh ssh -o ReconnectInterval=N`:断线(keepalive 发送失败判定)由外层循环自动重连并重新执行命令/重开 shell(pssh_cmd_ssh 返回 -2)。 +- 说明:依赖 ServerAliveInterval 心跳探测(建议两者搭配);恢复的是"重连+重跑",不恢复进行中 PTY 状态。 + diff --git a/CHANGELOG.md b/CHANGELOG.md index be0c454..c1b1723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ## [开发主线] — 2026-08-14 +### 新增 —— 自动重连(实用版) + +- `pssh ssh -o ReconnectInterval=N`:断线(keepalive 心跳发送失败判定)时由外层循环自动重连并**重新执行命令/重开 shell**(`pssh_cmd_ssh` 返回 -2 触发)。 +- 说明:依赖 `ServerAliveInterval` 心跳探测(建议配 `-o ServerAliveInterval=N -o ReconnectInterval=N`);恢复的是"重连+重跑",不能恢复进行中的 PTY 状态。 + ### 新增 —— agent 转发(-A) - `pssh ssh -A`:打开 `auth-agent@openssh.com` 通道,事件循环把本地 agent socket 与通道双向桥接(`ssh_agent_fd` 暴露 POSIX fd)。 diff --git a/STATUS.md b/STATUS.md index 893bc52..5f369e8 100644 --- a/STATUS.md +++ b/STATUS.md @@ -44,7 +44,7 @@ - 通道:session(exec/shell 真实进程执行)、direct-tcpip(出站桥接) - **SFTP 子系统 v3**:OPEN/READ/WRITE/OPENDIR/READDIR/STAT/REALPATH 等,与 OpenSSH sftp / scp -s 互操作(Git OpenSSH 实测通过) - 客户端命令: - - `pssh ssh` — 远程 shell / 执行命令,`-L`/`-R`/`-D` 端口转发,`-J` 代理跳板(单跳)、`-W host:port` stdio 转发、`-N` 仅转发、`-A` agent 转发(POSIX;Windows agent 为命名管道不支持);config 读取 `-F` / `~/.ssh/config`(OpenSSH 兼容) / `~/.pssh/config.conf`;`-o ServerAliveInterval=N` 空闲心跳保活(keepalive@openssh.com) + - `pssh ssh` — 远程 shell / 执行命令,`-L`/`-R`/`-D` 端口转发,`-J` 代理跳板(单跳)、`-W host:port` stdio 转发、`-N` 仅转发、`-A` agent 转发(POSIX;Windows agent 为命名管道不支持);config 读取 `-F` / `~/.ssh/config`(OpenSSH 兼容) / `~/.pssh/config.conf`;`-o ServerAliveInterval=N` 空闲心跳保活(keepalive@openssh.com)、`-o ReconnectInterval=N` 断线自动重连(重新执行命令/重开 shell) - `pssh scp` — SFTP 优先(OpenSSH 9.x 默认),回退 legacy SCP;`-r` 递归、`-p` 保留时间戳 - `pssh keygen` / `keyscan` / `copy-id` — 密钥工具 diff --git a/apps/pssh/main.c b/apps/pssh/main.c index 7d8bf48..71dd818 100644 --- a/apps/pssh/main.c +++ b/apps/pssh/main.c @@ -18,9 +18,14 @@ #ifdef _WIN32 #include +#define PSSH_SLEEP_MS(ms) Sleep(ms) +#else +#include +#define PSSH_SLEEP_MS(ms) usleep((ms) * 1000) #endif int pssh_cmd_ssh(int argc, char **argv); +int pssh_ssh_reconnect_interval(void); /* apps/ssh/main.c */ int pssh_cmd_keygen(int argc, char **argv); int pssh_cmd_keyscan(int argc, char **argv); int pssh_cmd_copyid(int argc, char **argv); @@ -95,7 +100,20 @@ int main(int argc, char **argv) { usage(); return 0; } - if (strcmp(cmd, "ssh") == 0) return pssh_cmd_ssh(argc - 1, argv + 1); + if (strcmp(cmd, "ssh") == 0) { + /* 断线自动重连(实用版):每 ReconnectInterval 秒重新调用 pssh_cmd_ssh + * (重连并重新执行命令/重开 shell)。-2 表示断线请求重连。 */ + for (;;) { + int r = pssh_cmd_ssh(argc - 1, argv + 1); + if (r == -2 && pssh_ssh_reconnect_interval() > 0) { + fprintf(stderr, "pssh: connection lost, reconnecting in %d s...\n", + pssh_ssh_reconnect_interval()); + PSSH_SLEEP_MS(pssh_ssh_reconnect_interval() * 1000); + continue; + } + return r; + } + } if (strcmp(cmd, "keygen") == 0) return pssh_cmd_keygen(argc - 1, argv + 1); if (strcmp(cmd, "keyscan") == 0) return pssh_cmd_keyscan(argc - 1, argv + 1); if (strcmp(cmd, "copy-id") == 0 || strcmp(cmd, "copyid") == 0) diff --git a/apps/ssh/main.c b/apps/ssh/main.c index 9151ce2..1524865 100644 --- a/apps/ssh/main.c +++ b/apps/ssh/main.c @@ -55,6 +55,12 @@ typedef struct { /* -v / -c 选项(跨 connect_target 传递) */ static int g_verbose = 0; static const char *g_cipher = NULL; +/* -o ReconnectInterval=N:断线自动重连间隔秒(0=禁用)。断线时 pssh_cmd_ssh 返回 -2, + * 由 pssh main 循环重连。 */ +static int g_reconnect = 0; + +/* 供外层(pssh main)查询自动重连间隔秒 */ +int pssh_ssh_reconnect_interval(void) { return g_reconnect; } /* 选项缺少参数时的用法提示;缺参返回 -1 */ static int opt_missing(int argc, int i, const char *opt, const char *hint) { @@ -343,6 +349,7 @@ int pssh_cmd_ssh(int argc, char **argv) { int no_command = 0; int agent_fwd = 0; /* -A:agent 转发(POSIX;Windows agent 为命名管道不支持) */ char stdio_host[256]; + int reconnect_flag = 0; /* 断线标记(供自动重连) */ const char *user_cfg = NULL; /* -F :显式指定 config 文件 */ for (int i = 1; i < argc; i++) { @@ -445,6 +452,9 @@ int pssh_cmd_ssh(int argc, char **argv) { else if (strncmp(kv, "ServerAliveInterval=", 20) == 0) { server_alive = atoi(kv + 20); } + else if (strncmp(kv, "ReconnectInterval=", 18) == 0) { + g_reconnect = atoi(kv + 18); /* 断线自动重连间隔秒 */ + } } else if (a[0] == '-') { continue; } else if (!host) { @@ -625,7 +635,7 @@ int pssh_cmd_ssh(int argc, char **argv) { time_t now = time(NULL); if (now - last_alive >= server_alive) { last_alive = now; - if (ssh_keepalive_send(s) < 0) { fprintf(stderr, "pssh: keepalive send failed\n"); break; } + if (ssh_keepalive_send(s) < 0) { fprintf(stderr, "pssh: keepalive send failed\n"); reconnect_flag = 1; break; } } } fd_set rfds; @@ -786,5 +796,6 @@ done: ssh_session_free(jump); } ssh_session_free(s); + if (reconnect_flag && g_reconnect > 0) return -2; /* 通知上层自动重连 */ return exit_code; }