不变量与安全边界
这些是「改代码时不许破」的约束。每一条都对应一个曾经踩过或差点踩到的坑。
1. 裸 TCP 隧道流必须直接丢弃
判据:流头里 Cf-Cloudflared-Proxy-Src 非空 ⇒ TypeTCP(ssh / rdp / tcp / smb / bastion / warp-routing)。模块的注入点在 HTTP 链上,承载不了裸 TCP,于是:
RST_STREAM(REFUSED_STREAM) + ERR 日志,不回 502、不进任何 location、立刻释放流绝不能静默降级成 HTTP —— 那会把「连不上」变成「看起来连上了但字节被当成 HTTP 解析」, 是最难查的一类错误。
2. 写路径:先排空,再造新字节
nginx 没设 SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER,OpenSSL 要求 WANT_WRITE 之后的重试 同指针同长度。cfd_h2_flush() 若先 nghttp2_session_send()(会 append/realloc)再写尾巴, 就会被 bad write retry 打死整条会话,并让之后所有新流都报 client conn not usable。
3. socketpair 的对端不许碰(L4 不变式)
虚拟连接的 c->fd 是 socketpair(AF_UNIX) 的 sv[0],sv[1] 保持打开、永不读写,只在连接池 销毁时由 cleanup 关闭。这不是随手一写:
nginx 读侧有一条「对端提前关闭」的漏网路径(src/http/ngx_http_request.c 里对 rev->fd 做 MSG_PEEK,读到 0 就当客户端断开 → 499 → terminate)。因为 sv[1] 既没有写入者、也没有关闭者, 那次 MSG_PEEK 恒为 EAGAIN,「对端已经关了」永远不成立 —— 挂位中的请求(例如正在 proxy_pass 等源站的请求)就是靠这个不变式活着的。
所以:为了「叫醒读侧」而 close() / shutdown() / 往 sv[1] 写一个字节,都会让这条路径立刻变成 真漏网。 唤醒一律走事件与回调(inj_pump() / c->read->handler(c->read))。
4. 日志里不许出现秘密
token、TunnelSecret 一律不进日志;只打来源、字节数、账号标签。栈上的凭据副本用完 ngx_memzero。
5. 不许「静默改路由」
任何「这个 service 我不认识,那就当 http 吧」都是禁止的。不支持就 502 + ERR(配置层), 或者传输层丢弃(非 HTTP)。http_status:NNN 也不允许被本地 inject_listen 覆盖 —— 它表达的是「不进源站」,本地端口表达的是「源站在哪」,两者不是一回事。
6. 本地配置永远优先
| 项 | 顺序 |
|---|---|
| 注入端口 | 本地 inject_listen → 远程规则 service 端口 → 503 |
Host | 本地 host_override → 远程 httpHostHeader → 访客 hostname |
| 是否直接应答状态码 | 远程 http_status:NNN(压过本地端口) |
7. 失败必须按隧道隔离
凭据坏、注入目标不存在、临时隧道申请失败 ⇒ 只跳过那一条,其余隧道照常;nginx 其余功能照常 应答 HTTP(token 在 worker 启动期校验,fail-soft)。这保证「一条配错」不会变成「整机 502」。
8. 每 worker 一份状态,每隧道一条连接
配置是 COW 拷贝,运行期状态(连接、流表、背压记账、定时器)不能跨 worker 共享;每条隧道在每个 worker 各建一条连接(快速隧道例外:只由 0 号 worker 建)。
9. 有意的偏离必须写下来并给出退回开关
目前只有一处运行期偏离:连接级窗口早还默认 on,cfd_tunnel_conn_window_early_release off 一键退回严格对齐。其余偏离都在配置/协议入口(互斥、e、嵌套未知键、只走 h2), 理由与 cloudflared 侧证据见 与 cloudflared 的差异。
10. 交付物必须可复现、可对账
.so重编后逐字节相同(同一工具链);- 交付指纹(
.somd5、.debsha256、包内文档 md5)由脚本写入并逐项核对; - 回归跑的就是交付的那份字节(包内
.so解出来后配官方 nginx 再跑一遍端到端)。
安全边界(措辞要准)
| 威胁 | 默认(127.0.0.1) | 配了 inject_source_addr 192.0.2.1 |
|---|---|---|
云端访客伪造 CF-Connecting-IP | 不能 | 不能 |
本机任意进程伪造 CF-Connecting-IP | 能(同一台机器上连到注入端口即可) | 不能(非特权进程 bind() 不属于本机的源地址会 EADDRNOTAVAIL) |
本机 root / CAP_NET_ADMIN 用 IP_FREEBIND 伪造 | 能 | 能 |
也就是说:这条指令收窄的是「本机任何用户都能冒充实访客 IP」,不是「本机已被攻破也安全」。 真正的边界始终是那台机器的 root。