纠正一个传出去的错误结论:不是「运营商封了 UDP」

我在给部署脚本的指示里写过:

    实测这条线路上运营商对 UDP 有限制 ——
    12 个 Cloudflare edge IP:TCP 12/12 通、UDP 0/12 通

它照这个把注释写进了 relay/deploy.sh。

**但这个测量是无效的。** relay-stability 后来自己纠正了:
那个 UDP 探测包是手搓的简化包,格式不对,
Cloudflare 本来就不该回应它 —— 「0/12 无回应」什么都证明不了。

用 cloudflared --loglevel debug 跑临时实例,真相是:

    UDP Connectivity  region1/region2  PASS  QUIC connection successful
    SUMMARY: Environment is healthy. cloudflared will use 'quic'
    Registered tunnel connection        (握手 1 秒)

**UDP 完全通。**

真因是:QUIC 建连要 6+ 个包往返,而 TCP 的 SYN 只有 3 个包、
内核还会按 1s/2s/4s 激进重传。约 5.4%/包 丢包下:

    (1 - 0.054)^6 ≈ 72%     <- 和实测的 72% 对得上

所以 QUIC 的弱点在「连不上」,不在「连上之后不好」;
一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。

结论的**用途**没变(还是要两条协议一起跑),但留着错误的根因注释会:
  * 误导后人以为 UDP 被封 -> 可能做出「干脆别用 QUIC」的错误决定
  * 掩盖真正可优化的方向(QUIC 握手对丢包敏感是链路物理属性,
    只能靠 HTTP/2 兜底来绕,不是「运营商限制」)

改动:relay/deploy.sh 两处、_build/test_deploy_sh.sh 一处。

教训记一笔:**手搓协议包去探测可达性非常容易得出错误结论** ——
那个包格式不对,对方根本没义务回应。要么用官方工具的自检
(cloudflared 自己会打 UDP Connectivity PASS/FAIL),
要么别下结论。
This commit is contained in:
xmz7891
2026-10-07 18:22:19 +08:00
parent cc5891e6d0
commit e611b07fb1
8 changed files with 353 additions and 67 deletions
+141
View File
@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
r'''纠正一个我自己传出去的**错误结论**。
== 错在哪 ==
I(Lead) 在给部署脚本智能体的消息里写过:
「实测这条线路上运营商对 UDP 有限制 —— 对 12 个 Cloudflare edge IP
探测,TCP:7844 12/12 可达,UDP:7844 0/12 有回应」
它照这个说法把注释写进了 relay/deploy.sh。
**但这个测量是无效的。** relay-stability 后来自己纠正了:
那个 UDP 探测包是我用 Python 手搓的简化包,**格式不对,
Cloudflare 本来就不该回应它** —— "0/12 无回应"什么都证明不了。
用 `cloudflared --loglevel debug` 跑临时实例,真相是:
INF | UDP Connectivity region1.v2.argotunnel.com PASS QUIC connection successful
INF | UDP Connectivity region2.v2.argotunnel.com PASS QUIC connection successful
INF | SUMMARY: Environment is healthy. cloudflared will use 'quic' as primary protocol
INF Registered tunnel connection (握手 1 秒)
**UDP 完全通,握手成功时只要 1 秒。**
== 真因 ==
QUIC 握手需要 6+ 个包的往返;
TCP 的 SYN 只有 3 个包,而且内核会按 1s/2s/4s 激进重传。
在约 5.4%/包 的丢包下:
成功率 ≈ (1 - p)^6 = (1 - 0.054)^6 ≈ 72% <- 和实测的 72% 对得上
所以 QUIC 的弱点是「**连不上**」,不是「连上之后不好」;
一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。
== 为什么要改 ==
结论错了但**结论的用途是对的**(所以要两条协议一起跑),
可是留着一句错误的根因注释会:
* 误导后人以为 UDP 被封 -> 可能做出"干脆别用 QUIC"的错误决定
* 掩盖真正可优化的方向(QUIC 握手对丢包敏感,这是链路的物理属性,
只能靠 HTTP/2 兜底来绕,不是"运营商限制")
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
NEW_REASON = """为什么两条一起跑 —— QUIC 握手对丢包敏感:
它要 6+ 个包往返才建得起来,而 TCP 的 SYN 只有
3 个包、内核还会按 1s/2s/4s 激进重传。
在约 5% 丢包下 (1-p)^6 只有 ~72% 能建成 ——
所以刻意让 HTTP/2 占几条连接兜住那 28%。
(UDP 本身是通的:cloudflared 自检 PASS,
握手成功时只要 1 秒。不是"运营商封了 UDP"。)"""
NEW_BLOCK = """# 根因也查清了:**QUIC 握手对丢包敏感**。
#
# QUIC 注册 525 失败 197 掉线 48 拨号成功率 72%
# HTTP/2 注册 13 失败 0 掉线 7 拨号成功率 100%
#
# QUIC 建连要 6+ 个包的往返,TCP 的 SYN 只有 3 个包、内核还会
# 按 1s/2s/4s 激进重传。约 5.4%/包的丢包下:
#
# (1 - 0.054)^6 ≈ 72% <- 和实测的 72% 对得上
#
# 注意:**这不是"运营商封了 UDP"**。曾经这么以为过,但那个判断
# 来自一个格式不对的手搓 UDP 探测包(Cloudflare 本来就不该回应它)。
# 用 cloudflared --loglevel debug 实测:
#
# INF | UDP Connectivity region1.v2.argotunnel.com PASS
# INF | UDP Connectivity region2.v2.argotunnel.com PASS
# INF | SUMMARY: Environment is healthy ...
# INF Registered tunnel connection (握手 1 秒)
#
# UDP 是通的。QUIC 的弱点在"连不上",不在"连上之后不好"——
# 一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。
# 所以对策是「多 QUIC + 少量 HTTP/2 兜底」,而不是放弃 QUIC。"""
def patch(path: Path, old: str, new: str, label: str) -> bool:
if not path.is_file():
print(" [跳过] %s 不存在" % path.name)
return True
text = path.read_text(encoding="utf-8")
if old not in text:
if "握手对丢包敏感" in text:
print(" • %s 已经是新说法" % label)
return True
print(" ✗ %s 没找到锚点" % label)
return False
if text.count(old) != 1:
print(" ! %s 锚点出现 %d 次" % (label, text.count(old)))
return False
path.write_text(text.replace(old, new, 1), encoding="utf-8", newline="\n")
print(" ✓ %s" % label)
return True
def main() -> int:
ok = True
ok &= patch(
ROOT / "relay" / "deploy.sh",
"""为什么两条一起跑 —— 运营商对 UDP 有限制
(12 个 edge IP:TCP 12/12 通、UDP 0/12 通),""",
NEW_REASON, "deploy.sh --help 里的说法")
ok &= patch(
ROOT / "relay" / "deploy.sh",
"""# 根因也查清了:**运营商对 UDP 有限制**。
#
# 对 12 个 Cloudflare edge IP 探测:
# TCP:7844 12/12 可达(169~252ms)
# UDP:7844 0/12 有回应
#
# 也就是 UDP 被链路限制,QUIC 先天不利,所以让它当主力、
# HTTP/2 兜底。""",
NEW_BLOCK, "deploy.sh 的根因注释")
ok &= patch(
ROOT / "_build" / "test_deploy_sh.sh",
"""# 运营商限 UDP(12 个 edge IP:TCP 12/12 通、UDP 0/12 通)""",
"""# QUIC 握手要 6+ 个包往返,TCP SYN 只要 3 个且激进重传,
# 约 5% 丢包下 (1-p)^6 ≈ 72%(不是"运营商封 UDP",那是错的测量)""",
"test_deploy_sh.sh 的说明")
print()
if not ok:
print(" 有锚点没匹配")
return 1
print(" 纠正完成")
return 0
if __name__ == "__main__":
sys.exit(main())