摘要

  • UDP 没有建立连接的握手。RFC 9298 因而要求代理在不等待目的端数据包的情况下答复;成功的准确含义是代理已朝请求的目标打开 socket,并愿意代理 UDP 载荷。
  • 名称解析、socket 创建、数据报出站、回包入站和内层应用确认是五类不同事实。前一类证据不能自动升级为后一类结论。
  • David Schinazi 在这份 RFC 中提供的关键价值,是把成功边界写得足够窄。真正的运营质量取决于控制面是否保留这种克制,而不是把一个方便的绿色状态扩写成端到端保证。

先给“成功”标出发言人

设想一条自动探测记录:客户端向代理提交 CONNECT-UDP 请求,HTTP 很快返回成功,平台随即把远端服务标成“健康”。这看起来顺理成章,却在中间偷换了主语。

代理确实完成了一件事。按照 RFC 9298,客户端用含有 target_host 和 target_port 的绝对 URI Template 指定目标。代理解析请求,检查策略,并朝该目标打开 UDP socket。不同 HTTP 版本使用 Extended CONNECT 或 Upgrade,UDP 载荷则通过 RFC 9297 所定义的 HTTP Datagrams 与 Capsule Protocol 承载。

但 UDP 与 TCP 不同。TCP 的握手能在建立会话时提供远端参与的信号;UDP 没有这一步。操作系统创建 UDP socket,只是在本地形成状态并设定发送方向,不要求远端返回任何东西。RFC 9298 因此直说:代理无从知道目的端是否可达,必须在不等待目的端数据包的情况下回应。

文档随后把成功限定为两个动作:代理已经向请求目标打开 socket;代理愿意转发 UDP 载荷。它没有说目的端在线,没有说端口上存在预期应用,也没有说第一个包抵达、被接受或会得到回包。

这不是协议少做了一项检查。对于无连接传输,它拒绝伪造一个并不存在的通用握手。准确的状态应当显示“代理已准备”,而不是“目的端已确认”。

DNS 给出的是一次映射,不是远端在场证明

当 target_host 是 DNS 名称时,RFC 9298 要求代理先解析再答复。如果解析失败,请求必须被拒绝,并可用 RFC 9209 的 Proxy-Status 说明 dns_error 等原因。

这一规则确实比“先接受、后找地址”更强。它证明在特定时点,特定解析路径得到一个可供代理使用的结果。但 DNS 回答的问题是名称如何映射到地址,而不是该地址上的应用是否存活。

运营记录应把输入名称、返回集合、实际选择的 IP、地址族、解析时刻、缓存或 TTL 情况写清楚,再单独记录 socket 最终绑定的对端。如果 DNS 之后发生切换,旧 socket 不会因为名称改变就自动转向新地址。只展示名称,会把两个时间状态错误地画成连续状态。

RFC 9209 还提供目的端不可用、IP 被禁止、IP 不可路由等负面词汇。它们描述代理已知的信息或执行的政策。没有这些错误,并不能反推可达。防火墙可能静默丢弃,ICMP 可能被过滤,应用也可能对不合规载荷保持沉默。

所以,“没收到错误”不是积极回执。“解析成功”也只是解析层回执。把它们放在正确层级,才不会让沉默被误判为健康。

socket 的远端地址,不等于远端身份

代理可以使用 connected UDP socket,让内核只交付匹配预期五元组的数据包。如果使用非 connected socket,RFC 9298 要求代理逐包核验源 IP 和 UDP 源端口,不匹配的包必须丢弃。

这项要求防止其他来源轻易把数据注入隧道,却不是应用认证。来自正确地址和端口的数据报仍可能是重放、垃圾、错误协议或未经认证的回应。QUIC、DNS 或业务协议必须自己定义对端身份、消息关联和“接受”的含义。

socket 生命周期与 HTTP 请求流绑定。流开着,代理就应保持 socket;当操作系统告知 socket 已不可用,例如收到 ICMP Destination Unreachable,代理必须关闭请求流。代理也可因空闲关闭,但 RFC 建议空闲期限不低于两分钟。

这些规则能生成清楚的本地证据:何时创建、采用何种 socket 模式、对端五元组、归属哪条请求流、何时关闭、关闭原因为何。它们不能生成一张覆盖整个存续期的远端健康证书。socket 仍开着,只说明没有被观察到的关闭条件触发,并不说明下一包一定可用。

隧道已接受,数据报仍可能合规丢失

RFC 9298 把 Context ID 0 留给 UDP 载荷;非零 Context ID 服务于扩展,并且其命名空间只属于当前请求。这个设计允许协议继续扩展,也把重排和未知上下文带来的行为写进规范。

如果 HTTP/3 Datagram 带着尚未登记的 Context ID 抵达,接收方可以静默丢弃,也可以短暂缓存等待登记。数据报甚至可能先于对应请求到达,处理方式同样是丢弃或短暂缓存。客户端还可以在收到 CONNECT-UDP 响应之前乐观发送,但文档明确警告:如果请求失败,或代理不愿缓存,先发的数据可能根本不被处理。

大小也会制造隧道成功与载荷成功之间的断层。Context ID 0 的 UDP Proxying Payload 不能超过 65,527 字节;实际路径 MTU 往往更小。代理不得在转发时自行引入 IP 分片,遇到过大的 HTTP Datagram 可能只能静默丢弃。

因此,“请求已接受”“代理观察到出站包”“代理观察到入站包”“内层握手完成”“应用处理成功”必须分别呈现。把第一项当作最后一项,会抹掉恰恰用于定位丢包、重排、MTU 和应用拒绝的证据。

把 David Schinazi 放回协作网络中

RFC 9298 于 2022 年 8 月以 IETF Standards Track RFC 发布,作者为 David Schinazi。为本文在 2026 年 9 月 2 日抓取的 IETF Datatracker 文字简介称,他是 Google 的高级首席软件工程师与高级经理,主要从事 Privacy Proxy、MASQUE 和 OHTTP;此前曾共同领导 QUIC、负责 Chrome Networking,并在 Apple 参与多类网络技术。任职、角色与 RFC 数量都是时间截面,可能继续变化。

IETF 官方肖像只为人物身份与配图提供公开依据,并不证明他控制任何部署。协议周边也不能归为一人所有。RFC 9297 由 Schinazi 与 Lucas Pardue 合著;后来把家族扩展到 IP 代理、并更新 masque well-known URI 的 RFC 9484 有五位作者。MASQUE 工作组本身是 IETF 的集体协作机制。

人物文章真正要抓住的,是 Schinazi 在 RFC 9298 中写出的证据边界。他没有借目的端的名字放大代理响应,而是把代理能知道什么、必须做什么、不能证明什么清楚分开。

这与 Heng Lu 所说的 Running-Code Primacy 相通:系统的权威来自它实际运行并可验证的功能,而非一个标签向相邻层借来的含义。Minimum Initial Specification 则解释为何共同层应保持狭窄:CONNECT-UDP 负责建立可互操作的转发表面,应用层继续承担认证、确认、重试和结果定义。

一条不越权的五段证据链

第一段是代理授权。记录客户端身份或授权类别、目标、策略版本、流量预算以及允许或拒绝。RFC 9298 特别提醒,任意客户端若能访问任意目标,流量会被归因到代理;本机、loopback、link-local、multicast、broadcast 等目标还可能穿透基于源 IP 的防线。

第二段是解析。记录名称到地址的当时结果,不把它称为应用发现。第三段是 socket:创建时间、本地出口、connected 与否、对端、请求流和关闭原因。

第四段是数据报。按方向记录首包、末包、数量与字节数;对超长、未知 Context ID、缓存耗尽、来源不符或系统错误造成的丢弃分类。证明数据移动不需要保存敏感载荷,计数和受控元数据已经足够。

第五段归内层协议所有。已认证的 QUIC 握手、能关联请求的 DNS 回答、业务确认、明确拒绝或超时,才说明目的应用发生了什么。重试也必须在这一层定义,因为同一个 UDP 包对查询可能无害,对非幂等操作却可能造成重复后果。

控制面最终可以显示多项状态:代理授权完成;DNS 已解析;socket 已打开;已见出站;已见入站;内层身份已确认;应用结果已接受。每项都有证人、时间和范围,不必用一个“健康”掩盖未知。

有限成功,比借来的确定性更强

运营系统喜欢把复杂状态压成一个数字。最早出现、最容易收集的 HTTP 成功,因而常被提升为端到端结论。随后,路由模型会把“快速接受”学成“交付良好”,告警会把 MTU 丢弃归咎于应用,合规报告会把五元组过滤写成身份验证。

这种错误不是因为 RFC 说得不够多,而是因为平台不愿保留它说出的边界。

代理可以为自己的授权、解析、socket 与转发作证。解析器可以为名称结果作证。操作系统可以为 socket 错误作证。内层应用可以为认证和业务结果作证。谁都不需要替下一个角色提前发言。

David Schinazi 的这份 RFC 把“成功”变成一条可执行的纪律:只陈述本层已经完成的事实。目的端尚未回应时,隧道仍可以真实地成功;真正不可接受的,是控制面把这句话改写成目的端已经回答。

来源