为什么动了 Relay?
sucaddy 的 Relay 模块最初的设计思路很朴素:把边缘节点的流量通过中继节点转发,实现跨网段互通。代码里手写了一套 AES-256-GCM 加密,用 FIFO 队列管理连接,单连接模式跑通信。
用着用着问题就出来了:
- FIFO 死连接:旧连接不释放,把队列占满,新连接挤不进去
- 没有多路复用:一个连接只能传一个流,并发能力为零
- 断线重连有间隙:1 秒空窗期间请求直接丢,实测成功率只有 67%
每次排查这些问题都让我怀疑人生——这些功能 SSH 原生不就自带吗?为什么我要自己写一遍,还写得这么烂?
方案对比:自建 vs 成熟方案
| 维度 | 自建 Relay | SSH 反向隧道 |
|---|---|---|
| 加密 | 手写 60 行 AES-256-GCM | SSH 内置 AES-256-GCM |
| 多路复用 | ❌ | ✅ SSH channels |
| 成功率 | 67% | 100% |
| 延迟 | 3.5-4.8s | 2.3-2.6s |
| 代码量 | ~350 行 | 0 行 |
| 保活 | 自己写心跳 | autossh + systemd |
结论很明显:删掉它。
具体怎么干的
SSH 反向隧道拓扑
边缘节点 → 中心节点 :端口A → 边缘节点本地服务(直连回内网)
边缘节点 → 中心节点 :端口B → 边缘节点出墙链路
- 中心节点上用 systemd + autossh 保活,断线自动重连
- 边缘节点用 cron keepalive 防止 NAT 超时
- 一条命令搞定:
ssh -R [端口]:[目标地址]:[目标端口] user@host
NaiveProxy 新增 tcp:ADDR 路由
在 NaiveProxy 里加了一个新的路由类型:route: tcp:ADDR。用户认证成功后,直接转发到本地指定端口(比如 SSH 隧道的入口)。大约 25 行代码,替代了整个 relay 入口模块。
全面清理
既然 Relay 不要了,依赖它的模块也得一起走:
- pkg/relay/ — relay.go, crypto.go(整个目录删)
- pkg/mesh/ — mesh.go, router.go, tun.go, tun_linux.go, tun_darwin.go
- pkg/discover/ — Bootstrap 发现机制(仅 Mesh 用)
- pkg/conn/ — 连接管理器(仅 Mesh 用)
连 cmd 层的启动逻辑都简化了,只剩协议启动 + 信号等待。config 也去掉了 Mesh/Bootstrap/Topology/Links/Relay 字段。
最终代码量:~3500 行 → ~1500 行,净减 2000 行。
v5.1 最终架构
精简后的架构反而更清晰了:
- 中心节点:NaiveProxy(TLS + 伪装页),多用户认证,不同用户走不同 chain
- 边缘节点:SS 实例直出或 chain 转发,各司其职
- 中间层:armbian 跑 SOCKS5/HTTP 代理,chain 转发到对应出口
- 路由器:iStoreOS 不跑 sucaddy,纯 SSH + Git 搞定一切
一条回内网的链路
用户 → NaiveProxy(认证) → tcp:本地端口 → SSH 反向隧道 → armbian → 内网路由器
实测延迟 0.6s,比之前 relay 模式快了近 3 秒。
部署
v5.1.0 打 tag,全节点更新:
- 韩国节点(中心):NaiveProxy + 伪装页 + 二进制分发
- 成都节点:多 SS 实例,分别走直出和 chain
- HK 节点、Blog 节点:v5.1.0
- armbian:v5.1.0 + SSH 隧道中继
- iStoreOS:卸载 sucaddy(二进制、配置、init.d、缓存、日志全清)
顺手修了几个 bug
NaiveProxy HTTP 路由
认证用户的非 CONNECT 请求被错误地路由到伪装页,导致 curl --proxy user@host http://内网IP/ 返回的是 HTML 而不是目标页面。修复方法是加 authenticateHTTP(),认证通过的 HTTP 请求走转发而非伪装。
Blog → 内网 Git 隧道
Blog 服务器要 push 代码到内网路由器上的 Git 仓库。通过 armbian 建了一条 SSH 隧道:Blog → 127.0.0.1:端口 → armbian SSH → 内网 Git,systemd 保活。
教训
别重复造轮子,尤其那个轮子你造得还不如别人的。
SSH 反向隧道成熟、稳定、多路复用、加密一流,还自带生态工具链(autossh、systemd)。我之前写的那套 relay,除了浪费周末时间之外,没有任何价值。
有时候,最好的代码就是没有代码。
最后更新:2026-04-30