摘要

  • Replaces 里的 Call-ID、to-tag 和 from-tag 只负责从接收端视角锁定一个既有 INVITE 对话;匹配成功后,用户代理仍须独立验证发起者是否有权替换它。
  • 安全次序是先准入新 INVITE,再处置旧对话。新会话若因媒体、密钥、QoS 或资源条件失败,RFC 3891 要求旧对话保持不变。
  • REFER 的接受、Referred-By 的来源声明、Replaces 的 2xx、BYE/CANCEL 以及最终媒体和计费结果,属于不同证据层。将它们合并成一个“转接成功”,就会把协议没有授予的权力交给自动化平台。

一条绿色记录里的五个问题

先把一次有咨询过程的转接拆开。坐席 A 正在与客户 B 通话。A 先与专家 C 建立一条咨询对话,确认 C 可以接手;随后 A 向 B 发送 REFER,请 B 去联系 C。B 的终端接受 REFER,于是 A 的控制台收到成功响应。

到这里,控制台很容易亮绿灯,但真正的替换尚未发生。B 还要生成一条新的 INVITE,其中携带指向 A—C 咨询对话的 Replaces。C 收到后要先找到那条对话,再判断 B 是否有权取代 A,然后才判断新媒体、密钥与本地资源能否接受。任何一步失败,旧对话都不应被提前销毁。

因此,一次转接至少包含五个彼此独立的问题:

  1. B 是否批准并执行 REFER;
  2. Replaces 是否精确指向 C 持有的一条对话;
  3. C 是否授权 B 替换这条对话;
  4. C 是否能够接受 B 的新 INVITE;
  5. 新对话成功后,旧对话是否真的完成 BYE 或 CANCEL,并保持媒体、录音和计费连续。

RFC 3891 的价值,正在于它没有用一个字段替代这五个问题。它提供的是最小协调机制,而不是远端发起者对本地会话的统治权。

为什么必须是一条新的 INVITE

Replaces 不是在原对话上改名字,也不是要求中间代理直接删除旧记录。它只能出现在新的 INVITE 中;这条新 INVITE 自己拥有新的 Call-ID。

这样设计有三个直接好处。首先,INVITE 本来就承担新会话准入,媒体、密钥、资源和策略检查都能按正常路径执行。其次,显式的 Replaces 清楚表达“希望新对话替换哪条旧对话”,避免用户代理根据普通字段作隐含猜测。第三,不支持扩展的设备可以明确失败,而不会误改旧会话。

支持方通过 Supported: replaces 表示理解该扩展;需要不支持时明确报错的发起方可以使用 Require: replaces。IANA 的 SIP 参数注册表仍登记着该头字段和 option tag。但“理解语法”不等于“批准这次操作”。能力协商只回答软件会不会处理,不能替代授权与准入。

Replaces 与 REFER 也不是同义词。RFC 3891 说明二者经常一起用于转接,但可以彼此独立。终端可以通过受保护的其他渠道得到对话标识后直接发出带 Replaces 的 INVITE;REFER 也可以要求访问一个完全不涉及对话替换的资源。

三元组从谁的视角看

一个 Replaces 值包含 Call-ID、一个 to-tag 和一个 from-tag。接收新 INVITE 的 UAS 要把 to-tag 与自己的本地 tag 比较,把 from-tag 与自己的远端 tag 比较。

“本地”和“远端”比报文展示的 To/From 更重要。抓包界面往往按某条历史消息排列字段,而替换请求到达时,接收端的视角可能与抄写者想象的相反。若只复制标签名称,不重建接收端视角,结果可能是 481,也可能是实现内部找错状态。

RFC 3891 勘误记录对此给出了罕见的直观提醒:已验证的一条编辑性勘误修正了早期对话示例中颠倒的 tags;另一条同类问题处于“留待文档更新”。它们没有改动规范性逻辑,却说明示例不能替代视角推理。

三元组的作用域也很窄。RFC 3891 明确说,它不能匹配多个对话、整通电话、整个 transaction 或代理分叉链。一次初始 INVITE 可能产生多个 early dialogs;替换其中一个分支,并不会自动覆盖其他分支。代理若把替换 INVITE 重定向到完全不同的 Contact 集合,匹配应当失败。

业务平台常把“通话”定义得更大:客户腿、坐席腿、咨询腿、录音分段、排队记录和计费会话都可能挂在同一个 interaction ID 下。Replaces 只触及其中一个 SIP dialog。要把该结果上升为整笔业务状态,必须有明确的关联和完成规则。

匹配成功之后,授权才开始

接收端先检查格式。一个请求出现多个 Replaces,或在非 INVITE 方法中出现 Replaces,都必须返回 400。具有冲突控制语义的头字段同时出现,也必须拒绝。

RFC 3911 定义的 Join 是最好的对照。Join 同样用 Call-ID 和两个 tags 指向既有对话,却要求把新对话加入当前 conversation space;Replaces 则要求以新对话取代旧对话。对象标识相似,动作完全不同,因此同时出现时属于矛盾请求。

这说明一个字段模型必须分清:标识符回答“哪个对象”,操作名回答“想做什么”,认证回答“谁在请求”,授权回答“他能不能做”,准入回答“现在做不做得到”,执行证据回答“最后发生了什么”。

若三元组匹配多条对话,UA 必须按未匹配处理。没有匹配,或匹配到并非由 INVITE 创建的对话,返回 481;若匹配对象已经终止,则应返回 603,避免一条迟到的替换请求被当作普通新呼叫再次响铃。

481 也不是攻击判决。tag 方向错误、状态过期、实例路由错误、故障切换丢失对话、Contact 重定向和正常竞态都可能产生它。可靠审计应保留接收端当时看到的对话状态,而不是把每次失败都贴成欺诈。

当活动对话准确匹配后,RFC 3891 才要求 UA 验证新 INVITE 的发起者是否有权替换它。规范举出几种可能依据:与被替换用户等价的认证身份、由另一参与方触发且带有 Referred-By 的请求,或其他本地策略。它还明确允许新旧对话使用不同策略。

这里没有“匹配即授权”。共享凭证可以服务于同一身份的多终端、助理或呼叫中心角色,也可能把一个服务账号变成跨租户的危险万能钥匙。认证证明对凭证的控制,不证明某个自然人在此刻表达了意愿,更不证明其拥有组织、法律或商业上的全部处置权。

Referred-By 是来源证据,不是委任状

RFC 3892 把角色分得很清楚:referrer 提出引用,referee 执行引用,refer target 接收被触发的请求。referee 负责把 Referred-By 信息复制到新请求中,因此天然可以看见,也可能篡改它。

一个未受保护的 Referred-By URI 可以作为策略输入,但不能被无条件当作可信身份。若它会影响准入或展示给用户,规范建议要求有效的受保护 token;否则相关信息应被视为可疑。token 无效则应拒绝,裸信息展示给用户也必须标明其不可靠。

即使 token 验证成功,含义仍有限。它能够保护 referrer 身份、Refer-To、日期等 referral 事实,却不会自动证明主管有权调动某一客户、医生取得了跨系统同意、交易员具备账户授权,或录音义务能够跨司法辖区继承。那些问题必须由可问责的本地策略作答。

RFC 4538 的 Target-Dialog 进一步说明“知道标识符”可以是有条件的授权证据。在使用 SIPS 建立对话、标识符具有足够随机性、路径信任关系明确时,知道 Call-ID 和 tags 可以表明请求者接触过原路径或受到原路径参与方信任。若原路径未受保护,窃听者也可能知道这些值,授权强度自然下降。

所以,对话知识既不是毫无价值,也不是所有权。它是一份需要结合方法、身份、路径和本地规则解释的证据。

新会话不合格,旧会话必须留下

授权成功后,UAS 仍要尝试接受新的 INVITE,重新分配用户界面和其他资源,然后才关闭被替换对话。

RFC 3891 明确列举失败可能:所需 QoS 无法建立、密钥条件不满足、媒体不兼容。资源不足、策略限制或正常 INVITE 准入错误也可能阻止新对话。一旦新 INVITE 无法接受,UAS 必须返回适当错误,并保持旧对话不变。

这一步把授权与可行性分开。一个人可以有权提出转接,但目标终端仍可能无法承载。若系统在匹配时就删旧腿,泄露或过期标识符可以摧毁活跃通话;若在认证后就删,媒体不兼容仍会造成中断;只有等新会话实际被接受,再处理旧会话,才保留可靠的回退点。

已确认的旧对话在新 INVITE 返回 2xx 后由 UAS 发送 BYE。若是由该 UA 自己发起的 early dialog,则在接受新 INVITE 后发送 CANCEL。BYE 终止已建立对话,CANCEL 停止尚未完成的邀请;两者不能在日志里被简化为同一种“清腿”。

early-only 则保护呼叫拾取一类竞态。如果发起者只想替换仍在响铃的 early dialog,但目标已经确认,UAS 返回 486,不能趁竞态把已接听的会话挤掉。若 early dialog 不是由接收新 INVITE 的 UA 发起,接收端返回 481 并保持原状,因为单一对话机制无法重演另一侧的分叉逻辑。

新 INVITE 的 2xx 是有力证据,但仍不是全局终态。BYE 可能丢失,B2BUA 的另一条腿可能没有正确映射,fork sibling 可能继续存在,媒体可以在协商后失败,录音和计费也可能绑定到错误的业务对象。正确做法不是贬低 2xx,而是只赋予它精确含义,并继续观察后续动作。

REFER 的 200 只回答它自己的问题

RFC 3515 要求接收 well-formed REFER 的 UA 先取得用户批准;这个批准可以来自交互询问,也可以来自配置策略。批准后,UA 才按照 Refer-To URI 的正常方法去联系资源。

在 attended transfer 中,Refer-To 常包含转义后的 Replaces 参数。transferee 依据 REFER 生成新的 INVITE,transfer target 再独立执行匹配、授权和准入。REFER 发起者不能因为自己的请求已被接受,就越过目标端的本地决定。

REFER 的 transaction 状态与 referenced action 的结果通过 NOTIFY 分开报告。RFC 7647 将已接受 REFER 的响应由旧有 202 澄清为 200,并要求在需要标识既有对话时使用 Target-Dialog,同时一般避免在旧 dialog 上混入新的隐式订阅使用。这个更新并未把 REFER 200 变成转接完成证明。

RFC 5589 的转接 BCP 同样指出,成功的 REFER transaction 不会自动终止原 session;若双方要结束它,仍要随后发送 BYE。目标忙、无应答或替换被拒时,失败流程让 transferor 解除保持并恢复原会话。

平台若在 REFER 200 时关闭工单,实际做的是用第一道门的答复替代后面四道门。它把“愿意尝试”写成“已经交付”。

能经受争议的转接证据链

首先保留两套身份:新 INVITE 自己的 Call-ID,以及 Replaces 指向的旧 Call-ID/tag 三元组。记录接收端的 local/remote 视角、匹配时状态、Contact、route、fork branch 和 early-only。

其次记录谁作了什么决定:认证身份、Referred-By 及 token 校验结果、策略版本、租户或用户规则、批准来源和拒绝理由。只有一个 authorized=true,无法在事后说明权限从何而来。

再次保存新会话准入:响应码、SDP 指纹、编解码器和方向、密钥与 QoS 结果、目标实例和 ACK。若接受成功,则关联旧对话 BYE 或 CANCEL、响应、重传和最后一条信令及媒体。

REFER 应在同一时间线上,但不能塞进同一状态位。保存其 Call-ID、CSeq、Target-Dialog、Refer-To 指纹、批准、响应和各次 NOTIFY,并用实际 replacement INVITE 与旧腿证据核对最终通知。

媒体和业务事实继续独立:RTP/RTCP、录音连续性、用户提示、队列或案件归属、计费腿关闭和 entitlement。隐私可以要求受限存储或 keyed hashes,却不应成为删除决策链的理由。

应测试 tag 反向、歧义匹配、已终止对话、early-only 竞态、Require: replaces 不受支持、正确匹配后的授权拒绝、无效 Referred-By token、媒体或密钥不兼容、目标忙与无应答、2xx 后 BYE 丢失、fork siblings、retargeting、故障切换丢状态,以及无法还原上下游腿的 B2BUA。

最终记录不应只是更精致的绿色图标。它必须分别回答:谁提出、指向哪条对话、谁有权批准、新会话是否被接受、旧会话如何结束,以及用户实际获得了什么。

来源