Skip to content

fix(tunnel): 协议不写死 HTTP/2,按 auto→http2 候选回退(修「启动超时(30s)」) - #133

Open
Xiao-Jiang-233 wants to merge 1 commit into
shaobeichen:mainfrom
Xiao-Jiang-233:fix/tunnel-protocol-fallback
Open

Xiao-Jiang-233 wants to merge 1 commit into
shaobeichen:mainfrom
Xiao-Jiang-233:fix/tunnel-protocol-fallback

Conversation

@Xiao-Jiang-233

Copy link
Copy Markdown

问题

命名隧道(可能也包括快速隧道)开启后一直卡在「启动中」,30 秒后报:

❌ cloudflared 启动超时(30s)——请检查 Tunnel Token 是否有效、域名 Service 是否指向本机代理端口,以及是否开着代理/VPN(Clash 等 TUN 模式会掐断隧道连接)

但 Token、ingress Service 都是对的,退出代理也没用——因为真正被挡住的不是「隧道」,而是「HTTP/2」。

根因

插件为了躲开国内常被屏蔽的 UDP 7844,无条件写死 --protocol http2(lib/tunnel.mjs 两处)。可是反过来的网络同样存在:代理 TUN(Clash 等)或企业网关会掐掉到 Cloudflare 边缘的 TCP,却不拦 UDP/QUIC。

这种机器上 cloudflared 自己的连通性预检是「QUIC 通、HTTP/2 不通」:

INF | UDP Connectivity  region1.v2.argotunnel.com  PASS  QUIC connection successful
INF | UDP Connectivity  region2.v2.argotunnel.com  PASS  QUIC connection successful
INF | TCP Connectivity  region1.v2.argotunnel.com  FAIL  HTTP/2 connection is blocked or unreachable
INF | TCP Connectivity  region2.v2.argotunnel.com  FAIL  HTTP/2 connection is blocked or unreachable
INF precheck complete hard_fail=false suggested_protocol=quic

但只要 --protocol http2 写死,cloudflared 就只会一次次 TLS handshake with edge error: EOF 重试,永远等不到 Registered tunnel connection,于是插件只能给出那句指不到真因的超时提示(该机器上 Token 完全有效、公网域名 ingress 也正确——把协议改成 auto/quic 后注册耗时不到 1 秒)。

改动

  • 新增 PROTOCOL_CANDIDATES = ['auto', 'http2'] 与 spawnCloudflaredWithFallback():
    • 逐个协议拉起 cloudflared,单个协议 timeoutMs(默认 20s)内没注册成功就杀掉换下一个;
    • 进程自己退出(参数错误、Token 无效等)立刻换下一个——真错误不会被超时掩盖;
    • 全部失败时抛最后一条原因,并带上 cloudflared 输出的关键行(复用 firstMeaningfulErrorLine),不再只给一句「超时」。
  • 命名隧道与快速隧道都改走该 helper,不再写死 --protocol;
  • 失败路径会 kill 掉上一次尝试的子进程,不留孤儿 cloudflared;
  • 两个入口支持 internals.spawn 注入,协议回退逻辑可在任意平台做单测(与仓库里 internals.createProxy 等既有约定一致)。

为什么是 auto 打头而不是 quic:auto 先跑预检,UDP 7844 被丢包的国内网络会自动落到 HTTP/2;预检判不出可用协议时进程只会无限重试,所以再显式 http2 兜底一次。两种被拦方向都能自愈,最坏情况从「永远起不来」变成「多等一个 20s 窗口」。

验证

  • 真实环境(Windows + Clash TUN:到边缘 TCP 被拦、QUIC 通,cloudflared 2026.9.1):
    • 改前:30s 超时(原样复现 issue 里的报错);
    • 改后:--protocol auto 注册成功,约 1s。
  • 新增 test/tunnel-protocol-fallback.test.js:候选顺序(前一个协议失败→换下一个→成功)、全失败时错误信息保留 cloudflared 的真因。npm test 通过。
  • 本机 Windows 上另有 6 条既有用例失败(download / proxy / smoke / tunnel-args 里 spawn 无扩展名假二进制),与本次改动无关:用 git stash 回到改动前同样失败,CI(Linux)不受影响。

兼容性

对外行为不变:仍是「就绪判据 = 出现 Registered tunnel connection」,设置页、状态机、快速隧道 URL 解析都没动;只是把「一个写死的协议」换成「按候选顺序试」。

现象:命名/快速隧道开启后一直卡在「启动中」,30 秒后报
「cloudflared 启动超时(30s)——请检查 Tunnel Token / 域名 Service / 关代理」,
但 Token、ingress Service 都是对的,关掉代理也没用。

根因:插件无条件 --protocol http2(为躲开国内被屏蔽的 UDP 7844)。
但反过来的网络也存在:代理 TUN(Clash 等)或企业网关会掐掉到 Cloudflare 边缘的
TCP,却不拦 UDP/QUIC。这时 cloudflared 自己的预检其实是:
  UDP Connectivity  region1/2.v2.argotunnel.com  PASS  QUIC connection successful
  TCP Connectivity  region1/2.v2.argotunnel.com  FAIL  HTTP/2 connection is blocked or unreachable
  suggested_protocol=quic
可因为写死了 http2,进程只会一次次 "TLS handshake with edge error: EOF" 重试,永远
等不到 Registered tunnel connection,最后只剩一句指不到真因的超时提示。
同一台机器改用 --protocol auto(或 quic)注册耗时不到 1 秒。

改动:
- 新增 PROTOCOL_CANDIDATES = ['auto', 'http2'] 与 spawnCloudflaredWithFallback():
  逐个协议拉起 cloudflared,单个协议 timeoutMs(默认 20s)内没注册成功就杀掉换下一个;
  进程自己退出(参数错误、Token 无效等)立刻换下一个,真错误不被超时掩盖;
  全部失败时抛最后一条原因,并带上 cloudflared 输出的关键行(firstMeaningfulErrorLine)。
- 命名隧道与快速隧道都改走该 helper,不再写死 --protocol。
- 失败路径 kill 掉上一次尝试的子进程,避免留下孤儿 cloudflared。
- 两个入口支持 internals.spawn 注入,协议回退逻辑可在任意平台做单测
  (新增 test/tunnel-protocol-fallback.test.js:候选顺序、失败信息保留真因)。

验证:
- 真实环境(Clash TUN:到边缘的 TCP 被拦、QUIC 通,cloudflared 2026.9.1):
  改前 30s 超时;改后 --protocol auto 注册成功,约 1s。
- npm test:新增 2 条用例通过;本机 Windows 下失败的 6 条(download/proxy/smoke/
  tunnel-args 里 spawn 无扩展名假二进制)与本次改动无关,改动前同样失败。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant