摘要
- SIP Outbound 让客户端先建立的通信路径承载后来到达的请求。登记中的地址、具体设备的身份与当前可用的路径,因此不能再当成同一件事。
- 同一账户下,一部设备可以登记多条路径;代理却不能把这些路径同时当作多个独立接收者。某条路径失效,与设备已经答复,是不同的结果。
- 保活、重新注册、寻找备用路径和确认请求是否还该继续,各有自己的边界。多一条连接不是多一次推翻答复的机会。
重启以后,编号反而要重复
许多系统把“不要重复”当成编号设计的常识。SIP Outbound 却要求设备在重新启动后,继续使用同一套注册编号。这里需要的恰恰是一次有意安排的相撞。
2009 年 10 月发布的 RFC 5626 把两个标识分开。instance-id 指向这一个用户代理实例,需要跨越断电、重启和网络移动保持稳定;reg-id 则区分它同时登记的不同通信路径。设备恢复后,以相同的账户、实例标识和注册编号重新登记,服务器便能替换旧记录,而不是在失效记录旁边再添加一个“接收者”。
编号重复并非去重失误。它传达的是:旧路由所代表的那一份工作,现在由新路径接替。若每次重连都被理解成新设备,登记表可能越来越长,实际可联系的人却一个也没有增加。
这个细节揭示了问题的中心。电话系统需要保存的,不只是“哪里还有一个地址”,还包括“这些地址和连接,究竟通往几个接收者”。
一部电话,在表里占了两行
多条路径可以让设备在一条连接失效、另一条仍然工作时继续收到新请求。但它们必须被识别为同一实例的替代路径,而不是两个应当分别呼叫的终端。
RFC 5626 对此有一条很具体的限制:代理不能在同一时刻,把同一账户和实例标识对应的多个 Contact 都放入目标集合。账户下可以有不同设备;这个限制并不是禁止所有并行呼叫。它限制的是,把通往同一设备的不同登记路径同时当作多个接收者。
绑定的比较方式也随之改变。服务器用账户、实例标识与 reg-id 的组合来区分记录;Contact 地址仍须保留,因为构造目标集合还要使用它。变化不是“地址不要了”,而是地址不再独自承担判断设备和路径是否相同的全部工作。
因此,两行记录可能是两部电话,也可能是一部电话的两条路。只统计登记行数,无法准确统计设备、用户,更无法证明系统有多少真正独立的故障出口。
430 说的是路坏了
规范用一段假想过程说明差别:电话已经通过两个边缘代理完成注册,其中一个代理崩溃并重启。电话还没有察觉原 flow 失效,新的呼叫请求就到了。
请求先被送往重启后的代理。它找不到原来的 flow,于是返回 430 Flow Failed。负责该账户的代理可以换用同一实例、另一 reg-id 的登记,让请求经仍然可用的路径到达电话。电话稍后才通过保活发现故障,并重新建立失效的那一份注册。
这不是某家运营商的事故报道,也不是恢复时延的测量。它说明,服务端对路径的认识,可以先于终端的故障检测;系统不必先把整个设备判成离线,才能处理其中一条路的问题。
430 的范围因此很重要。它不是“这个人不存在”,也不是“用户拒绝接听”。规范把它安排在边缘代理与负责转发的代理之间,终端不应收到它;如果真的收到,则按通常规则把它当作 400 处理。被篡改的令牌又是另一个问题,对应的建议响应是 403,而不是拿路径故障掩盖完整性问题。
电话答过了,就不能只为换路再问一次
备用路径并不赋予代理无限重试权。RFC 5626 规定,若某个分支已经返回最终响应,除 408 和 430 两种情况外,代理不能把同一个请求再转发给同一账户、同一实例的另一个目标。原因很朴素:目标实例已经提供了答复。
这条限制保护的是答复的含义。路径没有把请求送到,与设备已经对请求作出处理,不应进入同一个“失败后再试”的分支。否则,可靠性机制就可能变成一次又一次重复询问接收者的机制。
它也没有禁止未来的新呼叫,或规定所有网络协议怎样重试。它约束的是这一请求、这一实例与这些备用路径之间的关系。可替换的是坏掉的路,不是一个已经得到的答复。
接通服务器,不等于服务器能反过来接通你
SIP 原本就有注册机制。RFC 3261 在 2002 年描述了账户地址与一个或多个 Contact 地址之间的绑定。代理收到发给这个账户的请求,查询位置服务,再选择接近目标用户的地址。注册服务器和代理是逻辑角色,可以放在同一台设备上,也可以分开部署。
但在防火墙和网络地址转换设备之后,客户端能向外发起连接,不代表外部服务器能建立一条新的反向连接。还有一种限制来自身份凭证:个人电脑或电话可以作为 TLS 客户端连接有服务器证书的代理,却未必有稳定的域名,足以让自己成为可被外界认证和连接的服务器。
这里不是说 SIP 从来不能沿原连接返回任何东西。对于可靠传输,旧规范已经要求在原连接仍然打开时,用它发送该事务的响应。后来发起的呼叫请求,却不是先前 REGISTER 的响应。能够收到“注册成功”,并不能自动解释下一次来电要怎样穿过边界。
Outbound 的办法,是让客户端已经建立的路径承担这个任务。TCP 的 flow 对应连接;UDP 的 flow 则是一对地址、端口及传输协议构成的双向关联,不能一概称为 TCP 连接。共同点是:服务器使用已知能通向该客户端的关联,而不是仅凭 Contact 中的字面地址,再试着从外面敲门。
留下经过的路,仍然不够
注册请求可能先经过边缘代理,再到达注册服务器。后者保存了用户的 Contact,并不一定知道返回请求还必须经过哪些中间节点。
2002 年 12 月的 RFC 3327 为此定义了 Path。中间代理可以在注册过程中留下路由信息;注册服务器把有序路径与绑定一起保存,所属域的代理日后把它放入新请求的 Route 字段。
Path 不是给 REGISTER 强行建立一段通话。REGISTER 本身不创建 dialog,不能靠其中的 Record-Route 生成会话路由。Path 处理的是之后如何把请求送回已注册终端;真正建立 dialog 时,还要安排该会话后续请求的路径。
Outbound 再往前走一步。紧邻客户端的第一跳代理,要在 Path 中加入能够识别具体 flow 的令牌,并带上相应的 ob 参数。请求回到边缘时,代理不仅知道“经过我”,还知道“从我这里走哪一条关联”。令牌必须能够对应到该 flow,并防止被攻击者修改;规范给出的 HMAC 做法只是示例,不是唯一许可的算法。
这样的路标不能变成永久通行证。某条连接关闭后,即使相同端口或本地句柄后来被再次使用,也不能因此把新连接视为旧接收者的当然延续。
回答了保活,也可能证明旧路径失效
路径健康与注册状态有两个方向的分离。其一,重新注册遇到可恢复的错误,例如要求稍后再试的 503,未必需要拆掉一条仍在正常回答保活的连接。其二,收到了保活响应,也不一定说明原来的 flow 仍然有效。
对于 UDP,Outbound 使用发往下一 SIP 跳的 STUN 检查。如果响应中的映射地址改变,客户端必须把它当作 flow 失效。响应的到达证明有东西还能回答;地址变化却说明,原来登记的那种关联已经变了。
TCP 等面向连接的传输使用 CRLF 保活。其时间规则、服务器给出的 Flow-Timer,以及注册的有效期,解决的是不同问题。Flow-Timer 不是承诺在某个时间段内每次来电都能送达。检查越频繁,故障发现通常越快,也要付出电量、连接状态与服务器处理的代价。
2011 年 4 月的 RFC 6223 把保活协商进一步独立出来,用 Via 中的 keep 参数表达相邻节点发送和接收保活的意愿。文件明确说明,它并不定义连接复用机制。保持路径不被闲置超时,并不单独决定什么请求有权沿它返回。
还有几种名字,不能混成一种能力
2010 年的 RFC 5923 讨论另一种连接复用:两个相邻节点都能主动建立连接,通过经过认证的 TLS 别名,让反向请求复用已有通道。它的适用场景与只能由客户端向外建立路径的 Outbound 不同。把连接留着、验证对方身份、许可反向复用,仍然不是一句话可以替代的三件事。
同在 2009 年发布的 RFC 5627 则处理 GRUU:如何向外界提供一个指向具体用户代理实例的可路由 URI。转接电话时,需要找回正在参与通话的那部终端,而不是让账户地址另行选中语音信箱或另一部设备。GRUU 负责这样的目标识别;Outbound 负责能把请求送回实例的路径,两者并不互相取代。
2014 年的 RFC 7118 给出了浏览器里的 SIP 场景。其实现指南允许在请求并获得 Outbound 支持的情况下,以随机的 .invalid 名称构造 Contact:浏览器中的脚本不知道本地传输地址,返回请求仍可经过 WebSocket 代理与已有 flow。这个有条件的例子,不是说所有无效地址都能正常收话,更不意味着语音媒体路径已经被解决。
如今 IANA 的 SIP 参数表 能确认这些响应码与参数的名称和规范出处,不能替任何部署证明它们被正确实现。历史留下的关键不是又多了几个字段,而是系统终于能够区分:多一个名字、多一条路、多一个接收者,究竟是哪一种变化。
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
