摘要

  • RFC 3581 允许 SIP 客户端要求服务器把响应发往实际观察到的请求源地址与端口。成功收到回包,只证明当前事务借助当时仍存在的 NAT 映射走通。
  • 身份、注册、流、映射、事务、对话和用户结果具有不同责任人和失效时间。把 received+rport 提升为“持续可达”,会把这些原本独立的事实压扁成无法审计的结论。

设想一块运营大屏:INVITE 发出,代理返回响应,时延正常,于是订户状态由灰变绿。几分钟后,面向同一地址记录发起的新请求却没有抵达。若数据模型只有“最后一次成功”,团队只能把两次现象解释成矛盾;若保留 RFC 3581 所要求的边界,两件事可以同时为真。

第一次成功依赖刚发出的 UDP 报文。它在 NAT 上创建或刷新了一个映射,服务器又沿该映射回送了本事务的响应。第二次请求可能来自另一台集群节点、另一个源端口,也可能发生在映射失效以后。它需要的不只是一个曾被观察到的端口,而是仍然有效的流、正确的边缘代理、注册选择与对话路由。

RFC 3581 于 2003 年 8 月发布,属于 Standards Track。它最重要的遗产并非让某个字段“支持 NAT”,而是明确展示:线路观察可以用于一次狭窄决策,却不能自动取得更长生命周期的权威。

Via 里原本混合了两个世界

基础 SIP 通过 UDP 返回响应时,目的地址与端口来自不同地方。IP 地址取自服务器在数据包上看到的源地址,端口却取自最上层 Via 的 sent-by。这种混合规则有其规模优势,但 NAT 可能同时改写地址和源端口,于是地址描述公网一侧,端口仍描述私网一侧。

客户端可在顶层 Via 加入无值的 rport。无值并非缺陷,而是权限边界:客户端没有声称自己知道公网端口,只是请求接收方记录所见事实。服务器收到请求后,把实际源端口写入 rport,把实际源地址写入 received;即使地址与 sent-by 相同,也要写入后者。

在不可靠单播传输、没有 maddr 且两个参数齐全时,服务器把响应发往这组观察值,并从接收请求时相同的服务器地址与端口发出。对称 NAT 可能检查双向元组,因而服务器自己的出口身份也是证据的一部分。

这不是“相信头字段”。准确说法是:用本次请求的线路观察约束本次响应。若将记录简化为一个端口号,机制的时效、方向和观察者都会丢失。

观察值没有身份权

某个代理看到 192.0.2.1:9988,可以据此发送响应;它不能据此证明该元组永久属于某个用户、设备或 SIP Address-of-Record。NAT 可重分配端口,映射可因静默、重启或策略变化而消失;对称 NAT 还可能只接受来自原远端节点的流量。

证据还要分三层。空 rport 只证明消息请求这种行为;响应里的数值只证明服务器写入了观察结果;客户端实际收到响应,才证明该响应走完路径。第三层仍然不证明路径能维持多久,也不证明另一服务器可沿同一方向进入。

因此,事务收据至少应绑定 branch、Call-ID、CSeq、修改前后的顶层 Via、线路源元组、代理实例、入接口、出接口、响应状态与客户端确认。reachable=true 既没有时间范围,也没有主体,实际上无法复核。

集群名字不能替代具体套接字

RFC 3581 要求服务器从收到请求的同一地址和端口发出响应。多网卡、多监听端口与负载均衡环境都会使这条规则变得关键。业务域名可能代表一个集群,但 NAT 看见的是某台机器的某个套接字。

有状态代理可在事务期间保存该信息。无状态代理也不能凭空忘掉它;规范建议把目标地址和端口编码进代理自己插入的 Via,待响应返回后再恢复转发决定。“无状态”只是状态随消息携带,不等于不需要来源链。

如果遥测只记录逻辑服务名,故障切换会显得等价,线路却可能把新节点视为完全不同的对端。集群健康、代理健康和某次事务的路径健康,必须是三项指标。

成功响应不是映射租约

规范直接写明:NAT 映射必须持续到事务结束。普通非 INVITE 事务通常较短;INVITE 可在不确定时间内等待最终响应。RFC 3581 因而建议,即使已经收到临时响应,客户端仍按一定间隔重传 INVITE,以刷新映射。

这项建议本身就是限制声明。观察到的端口不携带有效期,临时响应也不会签发租约。若 NAT 重启、映射表回收、策略调整或代理切换,SIP 身份字段可以完全不变,而返回路径已经死亡。

文档引用了 RFC 3489 的映射寿命探测,同时警告探测并不可靠。后来 RFC 5389 取代 RFC 3489,RFC 8489 又取代 RFC 5389。不能把 2003 年的超时估计当成今日测量事实。稳健做法是保存实际报文、最后成功时间、重传间隔和不确定性,而非把旧观察外推到未来。

学到公网端口,不等于取得未来路由

received 与 rport 还会让客户端知道服务器眼中的公网元组。把它写入 Contact 或 Record-Route 看似省事,RFC 3581 却把这种用法归入 UNSAF,并逐项说明脆弱性。

映射要靠远高于常规注册频率的刷新保持。若是对称 NAT,只有原接收服务器发出的请求可能被接受;集群内其他节点即使读到同一注册记录也会失败。若 REGISTER 先到代理再到注册器,后续请求还必须经过那个代理,RFC 3327 的 Path 才提供这种路径声明。

一个 rport=9988 无法表达允许的远端、到期时间、边缘代理和集群亲和性。把数值当成公开地址簿条目,相当于把所有前提藏进运气。

RFC 3581 给出的退出方向很清晰:长期方案应让客户端声明“未来请求走我建立的连接或流”,应处理服务器集群,也不应以过度刷新换取可达性。后来的 RFC 5626 SIP Outbound 把注册绑定到客户端发起的流,支持多流、保活和失效检测;RFC 6314 把它列为 NAT 穿越实践建议。

但后续机制也不能互相冒充。RFC 6223 说明保活协商不等于连接复用;RFC 5923 处理面向连接传输的反向请求;RFC 5627 的 GRUU 提供稳定的 UA 可路由标识。保活成功不是注册被选中,流令牌不是授权,稳定 URI 也不是媒体或用户接听结果。

安全保护不会扩大语义

观察到的地址和端口可能敏感,RFC 3581 因而讨论 SIP over TLS。TLS 可保护信令免受篡改;在 TCP/TLS 上,rport 主要提供观察信息,并非 UDP 回包能够抵达的根本原因。

中间人删除 rport,可能让 NAT 后的客户端收不到响应。完整性保护能阻止这种改写,却不能证明谁控制公网元组,也不能让映射永久有效。IANA 参数登记只证明语法得到协调,不证明任何产品实现、运行状态或路径结果。

所以证据链必须分权:身份由认证收据支持,事务元组由线路观察支持,回包由具体代理与套接字支持,未来请求由 Path 或流信息支持,Contact 选择由注册器状态支持,对话由 Route set 支持,振铃、接听、媒体与应用结果另有收据。

证据边界

本文不指向任何运营商、PBX、SIP 提供商、终端、代理、注册器、NAT 产品、集群、用户、通话、事故或媒体结果。Standards Track 身份不是部署证据。

RFC 3261 提供 SIP 事务与 Via 基础;RFC 3327 提供 Path;RFC 3424 提供 UNSAF 审查框架。RFC 3489 仅是历史上下文,RFC 5389 与 RFC 8489 说明 STUN 后续演进。RFC 5626、RFC 6314、RFC 5923、RFC 6223 与 RFC 5627 分别约束 Outbound、NAT 实践、连接复用、保活和稳定 UA 路由。IANA 登记不能替代运行证明。

Heng Lu 关于 Running-Code Primacy 与 Minimum Initial Specification 的文章是公开披露的编辑视角:前者要求用运行路径验证声明,后者要求共享规则只覆盖必要互操作,而把后续选择留给可问责的本地决策。它们不是 RFC 作者意图或部署情况的证据。

结论应保持在机制允许的范围内:一次响应可以证明一个报文在某时找到了某端口。保留这份证明,但不要把它包装成明天的路由。

来源