Files
NetMesh/docs/research/issue-2121-network-stress-primary-sources.md
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

20 KiB
Raw Blame History

Issue #2121Mosh 网络压力测试的一手资料与验收标准

研究日期2026-07-15

结论

官方 Mosh 明确承诺已建立的会话可跨临时断网继续、可处理显著丢包、乱序和重复包,并支持固定为 IPv6 的会话。官方还公开测试过 100 ms RTT、两个方向各 29% 独立丢包(约 50% 往返丢包) 的场景。

但官方没有给出“断网 65 秒后必须几秒恢复”“单向丢包必须承受多少”“必须连续运行 30 分钟”之类的合格线。因此,本报告把结论分成三类:

  • 官方保证:论文、官方手册或官方说明直接陈述的能力。
  • 源码推断:从官方 Mosh 1.4.0 实现能确定的具体行为,但不是面向用户的时限承诺。
  • 项目门槛Netcatty/MoshCatty 为上线质量自行设定的压力值。它们可以比官方公开实验更严格,但不能写成“官方标准”。

对 #2121 最重要的判断是:65 秒双向黑洞应当恢复前提是黑洞发生前会话已经建立原客户端、原服务端和密钥都没有丢失而且服务端没有配置短于测试时长的网络超时。65 秒这个数字不是官方协议上限;它只是有意跨过源码中的 40 秒服务端脱离目标地址和 60 秒客户端省电刷新节点。

本文定义的是上线前的目标验收线,不是“当前已经全部完成”的声明。当前实际跑过的参数、次数和剩余缺口以同目录的 issue-2121-mosh-upstream-audit.md 为准。现有脚本已经覆盖官方 29% 双向丢包的短时基线、组合乱序/重复、65 秒断网、IPv6 最小 MTU 和 1800 次长期输入;下文更高强度的单向 30% 丢包 1000 次、25% 乱序/10% 重复 1000 次仍是后续目标,不能标成已完成。

资料和版本口径

本报告只使用以下一手资料:

  1. Mosh 原始论文,尤其是 2.12.3 节和第 5 页的高丢包实验。
  2. Mosh 官方说明官方 README
  3. Ubuntu 24.04 当前官方包所基于的上游版本 mosh-1.4.0 / bc73a263 的源码和手册。
  4. RFC 8200IPv6RFC 8201IPv6 路径 MTU 发现RFC 8085UDP 使用指南

Mosh/SSP 没有 IETF RFC 或 Internet-Draft。RFC 只规定 IPv6、UDP 和 MTU 等底层行为,不能替代 Mosh 论文和官方源码。

总验收原则

所有测试都应满足这些共同条件,否则“看到最终标记”不足以证明协议正确:

  1. 服务端使用未修改的 Ubuntu 官方 mosh-server 1.4.0;被测对象只在客户端。
  2. 会话运行在真实 PTY 中。先确认一条基线输入和一条基线输出都成功,再开始施加网络故障。
  3. 清除 MOSH_SERVER_NETWORK_TMOUT,或把它设得明显长于整个测试。测试期间不重启客户端、服务端或远端 shell。
  4. 每条输入带唯一、不可猜测的编号;远端测试程序把实际收到的编号写入独立日志。验收时检查 不漏、不重、顺序正确。只在客户端画面中搜索标记不能排除同一命令被执行两次。
  5. 服务端输出也带连续编号和最终状态摘要。恢复后既检查远端实际接收日志,也检查客户端最终权威画面。
  6. 保存服务端日志、客户端日志、进程退出状态和抓包。每个用例结束后确认没有遗留 mosh-server
  7. 网络参数通过隔离的 network namespace 和 tc netem 施加,不能修改测试主机承载 SSH 的真实网卡。

建议让远端测试程序按行接收 INPUT <seq> <nonce>,将每条记录追加到测试日志,并回显 ACK <seq> <sha256>。结束时输出 FINAL <count> <sha256-of-all-inputs>。这样能把“显示最终画面”“输入完整”“输入没有重复执行”分开验证。

1. 65 秒双向断网后恢复

官方保证

  • 官方 README 说明:客户端睡眠/唤醒或临时失去互联网连接时,会话仍保持,并在网络恢复后继续。来源
  • mosh-server 手册说明:如果没有设置 MOSH_SERVER_NETWORK_TMOUT,服务端会无限期等待客户端再次出现;如果要配置,官方建议使用一周或 30 天这样的高值。来源

官方没有承诺固定恢复秒数,也没有把 65 秒定义为特殊边界。

源码推断

  • 服务端 40 秒没听到客户端后会清除当前回包目标并记录“detached”但不会因此退出。network.hnetwork.cc
  • 收到新的、认证成功的客户端包后,服务端重新记录来源地址和端口并继续原会话。network.cc
  • 客户端在 10 秒没有成功往返后尝试更换本地 UDP 端口;这有助于重新建立 NAT 映射。network.hnetwork.cc
  • 客户端断开超过 60 秒后只会降低状态栏刷新频率,不会自动退出。terminaloverlay.cc
  • 只有“服务端从启动起从未收到过客户端”的情况,才会在 60 秒后退出。故障必须在首个状态往返成功后开始。mosh-server.ccmosh-server.cc

可执行验收标准

以下数字是 项目门槛

  1. 会话完成基线往返后,两个方向同时 loss 100%,持续至少 65 秒。
  2. 黑洞期间向客户端 PTY 写入至少一条唯一输入;远端在断网期间当然不会立刻收到,这不算失败。
  3. 第 30、45、65 秒分别确认客户端和服务端 PID 仍存活,远端 shell PID 没有变化。
  4. 恢复原网络后,黑洞期间写入的输入必须在 10 秒内到达远端且只执行一次;客户端最终画面必须出现对应确认。
  5. 恢复后再发送一条新输入,仍必须在 10 秒内完成往返。
  6. 不允许通过重新走 SSH、创建新 mosh-server 或新 shell 来“假装恢复”。

若只在恢复后才发送新命令,该测试只能证明“进程没死”,不能证明断网期间排队的用户输入被正确保留。

2. 单向丢包

官方保证

  • 官方说明称 Mosh 支持丢失“显著比例”数据包的链路。来源
  • 论文把“从丢包或乱序中恢复”列为 SSP 设计目标;传输的是从编号旧状态到编号新状态的幂等操作。论文 2.12.3 节
  • 论文的定量实验是 100 ms RTT、两个方向各 29% 独立丢包,约等于 50% 往返丢包关闭本地预测时Mosh 的中位协议延迟为 222 ms、平均 329 ms。论文第 5 页

这不是永久单向中断的保证。某一方向 100% 永久丢包时,该方向的新信息不可能传过去,任何协议都不能保证实时进展。

源码推断

  • 每个方向独立同步自己的状态;发送端根据已确认基线重建当前状态,不要求每个中间状态都到达。transportsender-impl.h
  • 接收端去重已经见过的新状态;如果一个乱序状态引用的旧状态尚未到达或已丢弃,就先忽略,等待发送端从双方共同确认的基线重发。networktransport-impl.h
  • 官方实现专门限制长期单向连接造成的接收状态队列增长;这说明长期非对称链路是被考虑的异常场景,但不是“任意长、任意速率都保证无上限缓存”。networktransport-impl.h

可执行验收标准

先跑一项接近官方论文的基线,再跑非对称用例:

  1. 论文基线100 ms RTT两个方向各 29% 独立丢包;连续 5 分钟或 1000 条唯一输入,以较晚完成者为准。
  2. 上行受损:客户端到服务端 30% 独立丢包,反向 0%;持续 10 分钟并发送 1000 条唯一输入。
  3. 下行受损:交换方向,其他条件不变。
  4. 完全单向黑洞:每个方向分别做一次 30 秒的 100% 丢包,恢复后检查收敛;不要求黑洞期间跨故障方向实时传输。

这些持续时间、30% 和 10 秒恢复线都是 项目门槛。通过条件是:客户端和服务端不退出;远端输入日志不漏、不重、顺序正确;恢复正常网络后 10 秒内显示最终状态;没有持续增长的未确认状态队列。

3. 乱序和重复包

官方保证

论文明确说明 SSP 用幂等的编号状态处理乱序和重复数据包,并把“从丢包或乱序中恢复”列为协议目标。论文 2.1、2.2 节

RFC 8085 也要求需要可靠性或顺序的 UDP 应用自行处理重复和乱序;这不是 UDP 本身提供的能力。RFC 8085 第 3.3 节

源码推断

  • 相同 Mosh 分片再次到达时不会重复计数;完整逻辑消息只在所有分片齐全后组装。transportfragment.cc
  • 已收到过的状态号直接忽略;仍可重建的乱序状态按编号插入历史,不会把当前最新画面倒退。networktransport-impl.h
  • 较旧的加密包不会再改变 RTT 和漫游目标,但其有效载荷仍可交给上层状态同步处理。network.cc

可执行验收标准

以下为 项目门槛:两个方向都施加 100 ms 基础延迟、20 ms 抖动、25% 乱序和 10% 重复,持续 10 分钟并交换 1000 条唯一输入;可以再叠加 5% 独立丢包。

验收必须同时满足:

  • 远端每个输入编号恰好出现一次;不能只检查“最终有这个标记”。
  • 服务端输出的最终摘要和客户端最终画面一致。
  • 乱序旧包不能让画面回退到较早状态。
  • 日志中不能出现协议解析失败、认证失败、断言失败或进程重启。

4. IPv6

官方保证

  • 官方手册允许 --family=inet6 / -6,并说明会话启动时选择一个 IPv4 或 IPv6 服务端地址,在该会话生命周期内保持这一地址族。mosh.1
  • 同一手册也明确称 IPv6、双栈和多地址服务器支持“有限”。因此不能把“所有双栈切换和跨地址族漫游都透明”当成官方保证。mosh.1

源码推断

客户端和服务端都用 AF_UNSPEC 解析数值地址,并根据 AF_INET6 建立 UDP socket 和计算 IPv6 数据包预算。network.ccnetwork.cc

可执行验收标准

  1. 客户端和服务端测试接口只配置 IPv6不配置 IPv4服务端绑定明确的 IPv6 地址,客户端也直接使用该数值 IPv6 地址。
  2. 抓包必须确认终端会话的全部 Mosh 流量都是 IPv6 UDP而不是 SSH 或 IPv4 后备路径。
  3. 完成基线、连续输入、窗口变化、大画面和正常退出;远端输入日志与客户端最终画面一致。
  4. 在具备公网 IPv6 路由时,再从不同公网前缀、至少跨一个路由跳数重复一次。

前三项通过只证明 IPv6 协议路径::1 或同机 network namespace 不能证明公网 IPv6 路由、防火墙和运营商路径正常;公网项必须单独记录,不能用本机结果代替。

5. IPv6 最小 MTU 与“分片”

标准要求

  • RFC 8200 规定 IPv6 链路最小 MTU 为 1280 字节。IPv6 路由器不会替源节点分片;源节点可以使用 Fragment Header但能调整报文大小的应用应避免依赖它。RFC 8200 第 4.5、5 节
  • RFC 8085 要求 UDP 应用避免产生超过路径 MTU 的 IP 包;如果不知道路径 MTUIPv6 应回退到 1280并从中扣除 IPv6、扩展头和 UDP 头。应用层大消息应拆成可独立接收和重传的 UDP 数据报。RFC 8085 第 3.2 节

源码推断

官方 Mosh 没有让大终端状态直接依赖 IPv6 分片:

  • IPv6 总预算固定为 1280 字节,并保守预留 40 字节基础头、16 字节扩展头和 8 字节 UDP 头,得到 1216 字节的应用数据报预算。network.hnetwork.cc
  • Mosh 再扣除自己的序列号、时间戳和加密开销,把一条大的 SSP 指令拆成多个 Mosh 分片,每个分片放在独立 UDP 数据报中。transportsender-impl.htransportfragment.cc

因此这里应验证的是 Mosh 应用层分片成功,同时不触发 IPv6 网络层分片。把接口 MTU 强行设为 1279 或更小已经违反 IPv6 最低链路要求,不属于官方保证范围。

可执行验收标准

以下为 项目门槛

  1. 两端 IPv6 接口 MTU 都设为 1280只使用 IPv6。
  2. 把 PTY 设为至少 200×80输出一帧确定性、难压缩的随机可见字符保证单个逻辑画面变化明显大于一个 UDP 数据报;最后显示该画面的摘要标记。
  3. 开启官方服务端详细日志,证明至少一条逻辑状态出现 frag 1 或更高编号,即实际走过 Mosh 应用层分片。
  4. 抓包确认所有 IPv6 包长度不超过 1280且没有 IPv6 Fragment Header不得出现 EMSGSIZE、oversize datagram 或丢失最终状态。
  5. 客户端最终画面摘要与服务端生成摘要相同,正常退出。

seq 1 4000 这类高度规律输出可能压缩得很好,而且 Mosh 只保证最新屏幕状态,不保证每一行滚屏历史。它能做大输出冒烟检查,但不能单独证明近 MTU 分片路径已被覆盖。

6. 长时间高延迟

官方保证和公开证据

  • 官方称 Mosh 适合蜂窝、远距离和高延迟链路,并能在高延迟时预测显示按键。README
  • 论文的真实轨迹覆盖 6 名用户约 40 小时、9986 次按键EV-DO 链路平均 RTT 约 500 ms。论文还列出约 273 ms RTT 的 MITSingapore 路径结果。论文第 4、5 页
  • 发送间隔约为平滑 RTT 的一半,并被限制在 20250 ms以免持续输出填满网络队列。论文 2.3 节transportsender.htransportsender-impl.h

论文的 40 小时是收集到的真实使用轨迹,不等于官方做过一个连续 40 小时、固定 600 ms RTT 的耐久测试。

可执行验收标准

以下为 项目门槛

  1. 连续 30 分钟保持 600 ms RTT两个方向各 300 ms每个方向 100 ms 抖动和 1% 独立丢包。
  2. 每秒发送一条唯一输入,共至少 1800 条;每 30 秒制造一次大画面更新。
  3. 客户端、服务端和远端 shell 全程不重启;输入日志 1800 条不漏、不重、顺序正确;最终权威画面一致。
  4. 故障参数移除后 10 秒内完成一次新的输入输出往返。
  5. 从 5 分钟热身结束后开始记录客户端 RSS。结束时不应出现持续单调增长最终值不得超过热身值的 25% 或 32 MiB取更宽松者。这是 MoshCatty 客户端自身的门槛,不包括 Netcatty/xterm.js 的滚屏缓存。

如果还要验收“用户感觉是否足够快”,必须在真实 Netcatty 页面另做预测回显、下划线和光标视觉检查。无头日志只能证明会话正确,不能证明视觉体验。

目标验收矩阵(不是当前完成清单)

场景 官方直接承诺 官方定量基线 本项目合格线
65 秒双向黑洞 临时断网后恢复;服务端默认无限等待 黑洞中输入保留;恢复后 10 秒内同一会话继续
单向丢包 支持显著丢包 双向各 29%、100 ms RTT 每个方向单独 30% 持续 10 分钟;另做 30 秒单向全黑洞
乱序、重复 SSP 设计目标,幂等编号状态 无百分比 25% 乱序、10% 重复、1000 条输入,恰好执行一次
IPv6 可固定使用 IPv6但双栈支持有限 IPv6-only 协议路径通过;公网路径单独通过
IPv6 MTU 源码按 1280 字节预算避免网络层分片 RFC 最小 MTU 1280 MTU 1280大画面触发 Mosh 分片,无 IPv6 Fragment Header
长期高延迟 面向高延迟链路 真实轨迹平均约 500 ms RTT 600 ms RTT 连续 30 分钟1800 条输入正确,资源无持续增长

什么证据还不够

  • 只看到 AFTER_OUTAGE:没有证明断网期间写入的数据被保留。
  • 只看到 1000 个标记都出现过:没有统计每个标记是否出现且执行恰好一次。
  • 只跑 ::1 或同机 namespace没有证明公网 IPv6 路由可用。
  • 只输出大量连续数字:没有证明难压缩的大状态走过 Mosh 分片,也没有证明 IPv6 包未分片。
  • 只跑 30 分钟但每 30 秒才做一次命令:可以证明会话存活,不能充分覆盖持续用户输入和队列压力。
  • 只看无头客户端日志:不能替代 Windows 正式安装版中预测字符、下划线、光标和重复显示的视觉验收。

最终判断

官方资料足以支持这些期望会话建立后65 秒双向断网不应杀死会话有限丢包、乱序和重复包应最终收敛IPv6-only 和 1280 字节 MTU 是官方实现有意支持的路径;长延迟下应保持正确并避免队列被持续输出填满。

官方资料不支持虚构统一的恢复时限或百分比。本文给出的 10 秒恢复、30% 单向丢包、25% 乱序、10% 重复、30 分钟和 600 ms RTT都是为了 Netcatty/MoshCatty 上线质量而设的 项目门槛。只有远端实际输入日志、客户端最终权威画面、进程身份和抓包同时满足,才应把某一项记为完成。