Skip to content

Commit 1e407e9

Browse files
committed
docs(ssh): 补充远程转发断联排查
1 parent 561455b commit 1e407e9

6 files changed

Lines changed: 447 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"file": ".trellis/tasks/05-11-ssh-zellij-disconnect-diagnosis/research/openssh-remote-command-forwarding.md", "reason": "核对文档是否覆盖 SSH 断连排查、连接数量、RemoteForward 与 zellij 场景"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"file": ".trellis/tasks/05-11-ssh-zellij-disconnect-diagnosis/research/openssh-remote-command-forwarding.md", "reason": "OpenSSH RemoteCommand、RemoteForward、连接数量与历史日志排查依据"}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# brainstorm: 排查 SSH zellij 频繁断开
2+
3+
## Goal
4+
5+
定位 Windows 客户端 `192.168.21.108` 通过 SSH 连接服务器 `192.168.27.77` 时频繁断开或需要重连的根因,并形成一个可重复执行的排查路线。当前连接同时使用 `RemoteCommand` 自动进入项目目录并 attach zellij session,以及 `RemoteForward 7890 192.168.21.108:7890` 建立远端代理入口;需要区分客户端主动断开、网络抖动、端口转发异常、zellij attach 退出、VS Code Remote SSH 重连、以及服务端 sshd 策略导致的断开。
6+
7+
## What I Already Know
8+
9+
* SSH 客户端 Host 为 `proj-xhgj-ai-platform`,目标服务器 `192.168.27.77`,用户 `administrator`
10+
* 当前客户端配置包含 `RemoteForward 7890 192.168.21.108:7890``RequestTTY yes``RemoteCommand cd ~/projects/ai/java/xhgj-ai-platform && /home/linuxbrew/.linuxbrew/bin/zellij attach -c proj-xhgj-ai-platform`
11+
* 服务端近期存在大量来自 `192.168.21.108` 的 publickey 登录记录。
12+
* 已观察到多条 `Received disconnect ... disconnected by user``Disconnected from user administrator 192.168.21.108``pam_unix(sshd:session): session closed`,初步更像客户端主动断开或上层远程工具重连。
13+
* VS Code Remote SSH 也会断联,只是使用 sshd + zellij RemoteCommand 的连接断联更频繁;因此 sshd/zellij 可能不是唯一决定因素,而是某个共同网络或客户端问题的放大场景。
14+
* 断联通常发生在连接建立十几分钟之后,不是秒级复现;因此优先检查历史日志与断开时间点附近的日志切片,而不是只依赖实时观察。
15+
* 也存在只有 `session closed` 的日志,需要结合前后文、sshd LogLevel、客户端 verbose 日志进一步判断。
16+
* 一条 `192.168.21.54``kex_exchange_identification: Connection closed by remote host` 不是主要客户端 IP,暂不作为主线证据。
17+
* 仓库已有 `docs/cheatsheet/vscode/remote/ssh-proxy.md`,记录了 RemoteForward、服务端 `127.0.0.1:7890` 监听验证、代理 curl 验证,以及隧道自动断开可加 `ServerAliveInterval`
18+
* 仓库已有 `docs/cheatsheet/terminal/Zellij.md`,记录了 zellij attach/list-sessions/detach 等常用行为。
19+
* Context7 OpenSSH 文档确认:SSH 客户端可使用 `ServerAliveInterval``ServerAliveCountMax`,服务端可使用 `ClientAliveInterval``ClientAliveCountMax`;反向转发使用 `ssh -R` / `RemoteForward``RequestTTY` 适合交互命令;`ExitOnForwardFailure` 可让端口转发失败显式暴露;sshd 配置中也存在 `MaxSessions``MaxStartups` 这类连接/会话限制项。
20+
21+
## Assumptions (Temporary)
22+
23+
* 服务器运行的是 OpenSSH sshd,并可读取 `journalctl``ss``ps``zellij list-sessions` 等命令输出。
24+
* 客户端可能是 Windows OpenSSH、VS Code Remote SSH,或两者叠加;VS Code 可能会建立额外连接、主动重连或关闭旧连接。
25+
* 远端 `127.0.0.1:7890` 默认由 sshd 为当前 SSH 会话监听;旧会话残留或其他进程占用会影响新连接。
26+
* `RemoteCommand` 中的 `zellij attach` 一旦正常退出、异常退出或被远端伪终端关闭,SSH 会话也会随之结束。
27+
* SSH 连接数量可能不是导致已建立连接被服务端主动踢掉的直接原因,但可能通过端口转发冲突、认证中连接限流、VS Code 多连接清理、客户端资源/网络压力等方式提高断联概率。
28+
29+
## Open Questions
30+
31+
* None.
32+
33+
## Requirements (Evolving)
34+
35+
* 收集服务端 sshd 配置中的 keepalive、forwarding、TTY、MaxSessions/MaxStartups 等关键项。
36+
* 统计当前同一客户端、同一用户的 sshd 连接/会话数量,区分已认证会话、认证中连接、VS Code Remote SSH 连接和 zellij attach 连接。
37+
* 收集历史 sshd/journal 日志,先做长窗口统计,再对具体断开时间点做前后 2-5 分钟切片,并按 `received disconnect``timeout``reset``broken pipe``kex``session closed` 分类。
38+
* 检查服务器与客户端之间是否存在网络抖动,至少覆盖连通性、延迟、丢包、TCP 连接状态。
39+
* 检查 `RemoteForward 7890` 是否成功建立,远端 `127.0.0.1:7890` 是否被旧 sshd 或其他进程占用。
40+
* 检查 zellij session 与相关进程是否稳定,确认断开后 session 是否仍存在,以及 attach 命令是否有异常退出迹象。
41+
* 检查 VS Code Remote SSH 或 Windows 客户端是否主动关闭连接、重连或复用/清理连接。
42+
* 评估客户端临时配置:加入 `ServerAliveInterval 30``ServerAliveCountMax 3``TCPKeepAlive yes``ExitOnForwardFailure yes`,并将 `RemoteCommand` 改为 `bash -lc 'cd ... && exec zellij attach -c ...'`
43+
* 在完成诊断后,将 RemoteCommand + zellij attach + RemoteForward 7890 的断连排查流程沉淀到 `docs/cheatsheet/vscode/remote/ssh-proxy.md`
44+
45+
## Acceptance Criteria (Evolving)
46+
47+
* [ ] 能根据历史服务端日志明确区分“客户端主动断开”“网络/TCP 异常”“服务端超时/策略断开”“远程命令退出”中的至少一类主要原因。
48+
* [ ] 能确认 sshd keepalive 与 forwarding 相关配置当前值。
49+
* [ ] 能确认断联时 SSH 连接数量是否异常,以及是否接近 `MaxSessions` / `MaxStartups` 或出现连接风暴。
50+
* [ ] 能确认断开时远端 7890 监听状态和所有者。
51+
* [ ] 能确认 zellij session/proc 在断开前后是否稳定。
52+
* [ ] 能给出下一步最小改动建议,并说明如何验证该建议是否有效。
53+
* [x] 仓库文档包含可复用的排查清单、推荐 SSH 配置、命令输出解读规则和 VS Code/命令行 SSH 对照实验。
54+
55+
## Definition of Done (Team Quality Bar)
56+
57+
* Tests added/updated where code behavior changes are introduced.
58+
* Lint / typecheck / CI green if repository code is changed.
59+
* Docs/notes updated if reusable diagnostic knowledge is added.
60+
* Rollout/rollback considered if changing SSH client/server configuration.
61+
62+
## Research References
63+
64+
* [`research/openssh-remote-command-forwarding.md`](research/openssh-remote-command-forwarding.md) — OpenSSH keepalive、RemoteForward、RequestTTY、RemoteCommand 排查要点。
65+
66+
## Research Notes
67+
68+
### What Similar Tools Do
69+
70+
* SSH 交互命令通常通过 `RequestTTY` / `ssh -t` 获取伪终端;远程命令生命周期就是 SSH 会话生命周期的重要组成部分。
71+
* 反向端口转发常用 `RemoteForward``ssh -R`;诊断重点是远端监听是否创建、绑定地址是否符合预期、失败是否被显式暴露。
72+
* keepalive 常分为客户端侧 `ServerAlive*` 和服务端侧 `ClientAlive*`;客户端侧更适合临时验证“连接空闲或网络中间设备导致断开”的假设。
73+
* `MaxStartups` 更偏向限制未完成认证的新连接,通常表现为新连接被拒绝或 kex 阶段关闭;`MaxSessions` 更偏向单条 SSH 连接内可打开的 session/channel 数,未必直接解释已有连接频繁断开,但必须纳入事实核查。
74+
75+
### Constraints From This Repo/Project
76+
77+
* 现有 SSH 代理文档已经覆盖 `RemoteForward 7890``ServerAliveInterval`,本次选择直接补充到该文档。
78+
* 现有 zellij 文档是使用速查表,尚未覆盖 SSH `RemoteCommand` 场景下的 attach 退出排查。
79+
* 历史日志策略已确定:近 7 天做模式统计,近 24 小时做细看;若用户提供具体断开时间,则优先做断开前后 2-5 分钟切片。
80+
* 本次如果只做排查与 PRD,不需要执行 `pnpm qa`;如果后续新增脚本或修改文档,应按项目规则做相应验证。
81+
82+
### Feasible Approaches Here
83+
84+
**Approach A: 一次性人工诊断**
85+
86+
* How it works: 先执行服务端和客户端诊断命令,基于日志和连接状态给出根因判断,再只修改 SSH 客户端配置做 A/B 验证。
87+
* Pros: 最快定位问题,避免过早写脚本。
88+
* Cons: 诊断经验主要留在本任务 PRD,复用性有限。
89+
90+
**Approach B: 诊断清单 + 文档沉淀** (Chosen)
91+
92+
* How it works: 在人工诊断后,把 RemoteCommand + zellij + RemoteForward 的排查流程补充到现有 cheatsheet。
93+
* Pros: 低成本复用,适合类似服务器连接问题再次发生。
94+
* Cons: 仍依赖人工执行命令和解读日志。
95+
96+
**Approach C: 新增 PowerShell/SSH 诊断脚本**
97+
98+
* How it works: 写脚本自动拉取 sshd 配置、journal 日志、端口监听、zellij session、网络探测结果,并生成摘要。
99+
* Pros: 可重复、输出稳定,适合长期维护多个 SSH 主机。
100+
* Cons: 范围更大,会涉及 pwsh 脚本规范和 `pnpm test:pwsh:all` 验证。
101+
102+
## Expansion Sweep
103+
104+
### Future Evolution
105+
106+
* 后续可扩展为通用“SSH RemoteCommand + terminal multiplexer + port forwarding”诊断文档或脚本。
107+
* 如果该连接用于长期开发,可进一步规划 ControlMaster、VS Code Remote SSH 独立 Host、或代理隧道与交互 session 分离。
108+
109+
### Related Scenarios
110+
111+
* 同类问题可能出现在 tmux、screen、zellij 的自动 attach 场景。
112+
* VS Code Remote SSH 与人工终端 SSH 共用同一个 Host 配置时,RemoteCommand、TTY、RemoteForward 和连接数量可能互相干扰。
113+
114+
### Failure And Edge Cases
115+
116+
* `RemoteForward` 端口已被旧 sshd 占用时,如果没有 `ExitOnForwardFailure yes`,用户可能误以为隧道正常。
117+
* 多个 SSH 连接同时使用同一个 `RemoteForward 7890` 时,只有一个连接能成功监听同一远端地址端口;其他连接可能绑定失败或退化为无代理状态。
118+
* zellij attach 退出、远端 shell 初始化脚本错误、客户端窗口关闭、VS Code 清理旧连接都可能在服务端表现为普通 `session closed`
119+
* 网络中间设备、Wi-Fi 漫游、Windows 睡眠/锁屏、省电策略可能导致客户端侧连接被关闭。
120+
121+
## Technical Approach
122+
123+
先用历史日志和状态命令建立事实时间线:登录时间、断开时间、sshd 断开原因、7890 监听归属、zellij session 状态、SSH 连接/会话数量、网络连通性。因为断联通常在十几分钟之后出现,第一轮以近 24 小时到近 7 天的日志统计为主,再围绕用户提供的断开时间点做精确切片。随后用最小客户端配置变更做对照:加入 keepalive、`ExitOnForwardFailure`,并用 `exec zellij attach` 确保远程命令生命周期更直观。如果仍断开,再拆分实验:去掉 `RemoteForward`、去掉 `RemoteCommand`、不用 VS Code 仅命令行 SSH,以及拆分 VS Code Host 与人工 zellij Host,以二分方式确定触发条件。诊断完成后,把可复用清单和判断框架写入仓库文档。
124+
125+
## Decision (ADR-lite)
126+
127+
**Context**: 这类断连问题横跨 SSH 客户端、sshd、RemoteForward、zellij、VS Code Remote SSH 和网络链路;只给一次性结论会让后续相同问题重复排查。
128+
129+
**Decision**: 采用 Approach B,先完成一次人工诊断,再把排查流程沉淀为仓库文档。
130+
131+
**Consequences**: 成本低于新增自动化脚本,同时能复用现有 SSH 代理和 zellij cheatsheet;缺点是仍需要人工执行命令和解释输出。若后续类似问题频繁发生,再升级为 PowerShell/SSH 诊断脚本。
132+
133+
## Final Requirements Summary
134+
135+
* 文档落点:补充到 `docs/cheatsheet/vscode/remote/ssh-proxy.md`
136+
* 默认日志策略:近 7 天统计、近 24 小时细看、已知断点时间做 2-5 分钟切片。
137+
* 文档必须覆盖:历史日志排查、连接数量/`MaxSessions`/`MaxStartups` 核查、`RemoteForward 7890` 端口冲突、zellij attach 退出语义、VS Code Host 与人工 zellij Host 拆分对照、推荐临时 SSH 配置。
138+
* 不新增自动化脚本,不修改生产 sshd 配置。
139+
140+
## Implementation Plan
141+
142+
* [x] PR1: 补充 SSH 反向代理文档中的断连排查章节。
143+
* [x] PR2: 更新任务验收状态,确认文档内容覆盖 PRD 要点。
144+
145+
## Out of Scope
146+
147+
* 暂不修改生产服务器 sshd 配置,除非诊断证据明确指向服务端 keepalive/forwarding 策略。
148+
* 暂不删除 zellij session 或 kill sshd 进程,除非先确认对应进程属于本次问题且用户同意。
149+
* 暂不把 VS Code Remote SSH 的完整行为自动化分析纳入 MVP;先收集客户端日志或用命令行 SSH 对照。
150+
* 暂不新增自动化诊断脚本;本轮以人工诊断和文档沉淀为边界。
151+
152+
## Technical Notes
153+
154+
* Created task: `.trellis/tasks/05-11-ssh-zellij-disconnect-diagnosis`
155+
* Inspected `.trellis/workflow.md` and current Trellis task conventions.
156+
* Inspected `docs/cheatsheet/vscode/remote/ssh-proxy.md` for existing RemoteForward guidance.
157+
* Inspected `docs/cheatsheet/terminal/Zellij.md` for existing zellij usage notes.
158+
* Used Context7 CLI:
159+
* `npx ctx7@latest library "OpenSSH" "..."`
160+
* `npx ctx7@latest library "Zellij" "..."`
161+
* `npx ctx7@latest docs /openssh/openssh-portable "..."`
162+
163+
## Investigation Findings (2026-05-11)
164+
165+
* 本机 `ssh -G proj-xhgj-ai-platform` 显示当前 Host 仍为 `ServerAliveInterval 0``ExitOnForwardFailure no``RequestTTY true``RemoteCommand zellij attach``RemoteForward 7890 [192.168.21.108]:7890`
166+
* 本机 `~/.ssh/config` 中存在多组指向 `192.168.27.77` 的 Host,很多都配置相同的 `RemoteForward 7890 192.168.21.108:7890` 和 zellij `RemoteCommand`;这会放大多连接抢同一远端端口的问题。
167+
* 服务器当前 `127.0.0.1:7890` 已由 `mihomo.service` 监听,`[::1]:7890` 由用户会话监听;因此 SSH `RemoteForward 7890` 在 IPv4 loopback 上存在明确端口冲突。
168+
* 服务端日志在 2026-05-11 多个时段反复出现 `error: bind [127.0.0.1]:7890: Address already in use``channel_setup_fwd_listener_tcpip: cannot listen to port: 7890`。今天按小时统计与登录次数高度同步,例如 08 点 7 次、09 点 9 次、10 点 3 次、11 点 2 次、12 点至少 1 次。
169+
* 最小复现:`ssh -o ExitOnForwardFailure=yes -R 127.0.0.1:7890:192.168.21.108:7890 administrator@192.168.27.77 true` 会报 `remote port forwarding failed for listen port 7890`,证明显式绑定 `127.0.0.1:7890` 时转发失败是可复现的。
170+
* `last -Fai` 显示近期来自 `192.168.21.108` 的会话既有 1-2 分钟短连接,也有 40 分钟、1 小时、数小时甚至多天连接;这不像单一 sshd keepalive 配置稳定踢人。
171+
* 当前从 `192.168.21.108` 到服务器存在多条 SSH 连接,并且服务端存在一个远端进程连接客户端 `192.168.21.108:7890``CLOSE-WAIT` 状态;说明代理链路或客户端代理连接存在残留连接。
172+
* 本机 `ping -n 20 192.168.27.77` 结果为 0% 丢包,平均约 6ms;当前短时网络连通性正常,但不能排除更长周期 Wi-Fi/睡眠/客户端进程重启。
173+
* 非 root 用户执行 `/usr/sbin/sshd -T``/etc/ssh/sshd_config.d/50-cloud-init.conf` 权限不足未能确认完整有效配置;如需确认 `ClientAlive*` / `MaxSessions` / `MaxStartups` 的实际值,需要在服务端用 sudo 执行。
174+
* 用户确认服务端确实启动了 mihomo,并已先关闭;下一步观察关闭 mihomo 后是否仍会出现十几分钟后断联。
175+
176+
## Current Diagnosis
177+
178+
最明确的已证实问题是 `RemoteForward 7890` 与服务器本机已有 `mihomo.service``127.0.0.1:7890` 监听冲突。由于客户端配置 `ExitOnForwardFailure no`,SSH 登录仍会继续,导致连接处于“会话成功但转发部分失败/半成功”的状态。结合本机存在多个同目标 Host 都声明相同 `RemoteForward 7890`,VS Code Remote SSH 与人工 zellij 连接共用配置时会进一步放大多连接与端口争用。
179+
180+
这不一定是所有断联的唯一原因,但它是当前可复现、可解释大量日志、且最值得优先修复的主因。下一步建议先拆分 Host、移除 VS Code Host 上的 `RemoteCommand` 与重复 `RemoteForward`,并把 zellij Host 的 `RemoteForward` 改为不冲突端口或先禁用。
181+
182+
2026-05-11 当前处理:先关闭服务端 mihomo,保留现有 SSH 配置,明天观察是否仍断联。如果关闭后稳定,基本可确认远端 `127.0.0.1:7890` 端口冲突是主因;如果仍断联,再继续拆分 VS Code Host 与 zellij Host,并补充客户端 keepalive。

0 commit comments

Comments
 (0)