协议层
协议层在 src/ 下,模块与阶段 1 的独立客户端编译的是同一份源码 —— 所以协议行为只有一处实现、 一套测试。
与边缘的连接
| 项 | 值 |
|---|---|
| 传输 | TCP + TLS(nginx 自己的 OpenSSL) |
| ALPN | 必须 h2;没协商到就失败,不做 h1.1 回退 |
| SNI / 证书校验名 | 常量 h2.cftunnel.com(CFD_EDGE_SNI),与 cfd_tunnel_edge 指向哪台边缘无关 |
| 连接地址 | cfd_tunnel_edge,默认 region1.v2.argotunnel.com:7844 |
| h2 角色 | 应用层用 nghttp2 的 server API 收流(对端是边缘);会话内 :method 等由边缘给 |
| 控制流 | 会话建立后第一条流跑 Cap’n Proto RPC |
为什么 SNI 是常量:实测
region1.v2.argotunnel.com:7844返回的证书是CN=CloudFlare Origin Certificate、SAN=*.cftunnel.com,与 cloudflared 的edgeH2TLSServerName完全对应。旧版把两者合成一个名字(默认h2.cftunnel.com既当目标 又当 SNI),而它在公网 DNS 上查不到 —— 于是默认配置根本连不上。
控制流上的 RPC
Cap’n Proto 编解码由 capnp_* 实现,模板字节由 tools/gen-template.py 从 Go 官方 capnp 的 参照字节生成(src/capnp_templates.h,regen-templates.sh --check 逐字节对账)。
| RPC | 方向 | 用途 |
|---|---|---|
registerConnection | 模块 → 边缘 | 上报凭据、connIndex、客户端版本、features |
ConnectionDetails | 边缘 → 模块 | 三层嵌套结果(含 locationName),解析后打 NOTICE |
| heartbeat | 双向 | 心跳判死(默认不发,见 ping_interval) |
update-configuration | 边缘 → 模块 | 远程 ingress 规则(remote_config on 才收) |
features 的裁剪是对齐的一部分:remote_config off 时连 allow_remote_config 都不宣告—— 宣告了却不处理是最坏的不对齐。
凭据
| 来源 | 解析 |
|---|---|
| credentials JSON | {AccountTag, TunnelID, TunnelSecret} |
| 内联 token | base64(JSON {a,t,s[,e]}),等价 --token,不 trim |
| token 文件 | 先 TrimSpace 再按 token 解析,等价 --token-file |
三种都并进同一个 cfd_creds_t,之后的注册/心跳/注入路径完全共用。token 的解析口径逐条对齐 cloudflared 的 ParseToken()(标准 base64、必须有 = 填充、只忽略 \r\n、字段 a/t/s、 未知字符串键忽略、重复键后者胜),详见 凭据。
快速隧道 provisioning
| 项 | 行为 |
|---|---|
| 请求 | POST https://api.trycloudflare.com/tunnel(cfd_tunnel_quick_service 可改) |
| 传输 | 自带的 TCP + TLS 客户端(cfd_tls_client),必须 ALPN=h2 |
| 预算 | cfd_tunnel_quick_timeout,默认 15 s(cloudflared 的 httpTimeout 是常量) |
| 失败 | 不重试(cloudflared 同样只申请一次) |
| 代理 | 认 HTTPS_PROXY 等(项目扩展;cloudflared 的 http.Transport 里 Proxy == nil) |
| 结果 | 拿到的凭据当普通凭据用,地址写进 error_log |
远程配置
| 项 | 行为 |
|---|---|
| 触发 | 边缘在连接上新开一条流下发 {"version":…,"config":{…}} |
| 应答 | 200 + {"lastAppliedVersion":N,"err":null|原因},解析失败 502 |
| 版本 | 从 -1 起,version <= 已应用 整个忽略 |
| 时序 | 读到完整的一个 JSON 值就应答,不等 END_STREAM |
| 换代 | 规则表是运行期 malloc 对象,换代为原子替换,旧世代整块释放,worker 退出时清空 |
细则见 远程配置下发。