16. 代理行为 (Proxy Behavior)
本章详细描述了SIP代理服务器的行为规范,包括有状态代理和无状态代理的处理流程。
16.1 概述 (Overview)
代理的角色
SIP代理是路由SIP请求到用户代理服务器和SIP响应到用户代理客户端的元素。请求在到达UAS的途中可能经过多个代理,每个代理都会做出路由决策,在转发请求前修改请求。响应将按照请求经过的代理集以相反顺序路由。
代理的逻辑角色
作为代理是SIP元素的逻辑角色。当请求到达时,可以扮演代理角色的元素首先决定是否需要自己响应请求。例如:
- 请求可能格式不正确
- 元素可能需要客户端的凭证才能充当代理
元素可以用任何适当的错误代码响应。当直接响应请求时,元素扮演UAS的角色,必须按照第8.2节的描述行为。
有状态 vs 无状态
代理可以对每个新请求以有状态或无状态模式操作:
无状态代理 (Stateless Proxy):
- 充当简单的转发元素
- 将每个请求转发到下游的单个元素
- 简单地转发收到的每个响应到上游
- 一旦消息被转发就丢弃消息信息
有状态代理 (Stateful Proxy):
- 记住每个传入请求的信息(特别是事务状态)
- 记住它作为处理传入请求结果发送的任何请求
- 使用此信息影响与该请求相关的未来消息的处理
- 可以选择"分叉"请求,将其路由到多个目的地
重要约束: 任何转发到多个位置的请求必须以有状态方式处理。
传输考虑
在某些情况下,代理可以使用有状态传输(如TCP)转发请求而不必是事务有状态的。例如:
- 代理可以从一个TCP连接到另一个TCP连接无状态地转发请求
- 前提是它在消息中放置足够的信息,能够将响应转发回请求到达的同一连接
当请求在不同类型的传输之间转发时,如果代理的TU必须在确保其中一个传输上的可靠交付方面发挥积极作用,则必须以事务有状态方式转发。
16.2 有状态代理 (Stateful Proxy)
事务处理引擎
当有状态时,代理纯粹是一个SIP事务处理引擎。其行为根据第17节定义的服务器和客户端事务建模。
代理核心架构
有状态代理具有一个服务器事务,该事务通过更高层代理处理组件(称为代理核心)与一个或多个客户端事务相关联:
+--------------------+
| | +---+
| | | C | CT = Client Transaction
| | | T |
| | +---+
+---+ | Proxy | +---+
| S | | "Higher" Layer | | C |
| T | | | | T | ST = Server Transaction
+---+ | | +---+
| | +---+
| | | C |
| | | T |
| | +---+
+--------------------+
有状态代理模型
处理流程
- 传入请求由服务器事务处理
- 来自服务器事务的请求传递给代理核心
- 代理核心确定请求路由位置,选择一个或多个下一跳位置
- 每个下一跳位置的传出请求由其自己关联的客户端事务处理
- 代理核心从客户端事务收集响应,并使用它们向服务器事务发送响应
事务创建
有状态代理为每个收到的新请求创建一个新的服务器事务。该请求的任何重传将由该服务器事务根据第17节处理。
必需的处理步骤
对于所有新请求(包括任何未知方法的请求),打算代理请求的元素必须:
- 验证请求 (Section 16.3)
- 预处理路由信息 (Section 16.4)
- 确定请求的目标 (Section 16.5)
- 将请求转发到每个目标 (Section 16.6)
- 处理所有响应 (Section 16.7)
16.3 请求验证 (Request Validation)
在元素可以代理请求之前,它必须验证消息的有效性。有效消息必须通过以下检查:
1. 合理的语法检查 (Reasonable Syntax)
请求必须格式良好到足以用服务器事务处理。参与这些验证步骤或请求转发部分其余部分的任何组件都必须格式良好。
关键点:
- 其他组件(格式良好或不良好)应该被忽略,并在转发消息时保持不变
- 元素不会因为格式不正确的Date头字段而拒绝请求
- 代理不会在转发请求前删除格式不正确的Date头字段
- 元素不得拒绝代理包含它不知道的方法或头字段的请求
2. URI方案检查 (URI Scheme)
如果Request-URI的URI方案是代理不理解的,代理应该用416 (Unsupported URI Scheme) 响应拒绝请求。
3. Max-Forwards检查
Max-Forwards头字段用于限制SIP请求可以遍历的元素数量。
检查规则:
- 如果请求不包含Max-Forwards头字段,此检查通过
- 如果请求包含Max-Forwards头字段,字段值大于零,检查通过
- 如果请求包含Max-Forwards头字段,字段值为零(0),元素不得转发请求
- 如果请求是OPTIONS,元素可以充当最终接收者并按照第11节响应
- 否则,元素必须返回483 (Too Many Hops) 响应
4. 可选的循环检测检查 (Loop Detection)
元素可以在转发请求之前检查转发循环。
检测机制:
- 如果请求包含Via头字段,其sent-by值等于代理先前放入请求中的值,则请求之前被此元素转发过
- 请求可能是循环或合法地螺旋通过元素
- 为确定是否循环,元素可以对此消息执行第16.6节步骤8中描述的分支参数计算,并将其与该Via头字段中收到的参数进行比较
- 如果参数匹配,请求已循环;如果不同,请求在螺旋,处理继续
- 如果检测到循环,元素可以返回482 (Loop Detected) 响应
5. Proxy-Require检查
未来对此协议的扩展可能会引入需要代理特殊处理的功能。
处理规则:
- 如果请求包含Proxy-Require头字段,其中有一个或多个此元素不理解的option-tags,元素必须返回420 (Bad Extension) 响应
- 响应必须包含Unsupported头字段,列出元素不理解的那些option-tags
6. Proxy-Authorization检查
如果元素在转发请求之前需要凭证,必须按照第22.3节的描述检查请求。该节还定义了如果检查失败元素必须做什么。
16.4 路由信息预处理 (Route Information Preprocessing)
Record-Route处理
代理必须检查请求的Request-URI。如果Request-URI包含此代理先前放入Record-Route头字段的值,代理必须:
- 用Route头字段的最后一个值替换请求中的Request-URI
- 从Route头字段删除该值
- 然后继续,就好像它收到了这个修改后的请求
目的: 这种接收时的重写对于实现与严格路由器元素的向后兼容性是必要的。
maddr参数处理
如果Request-URI包含maddr参数,代理必须检查其值是否在代理配置为负责的地址或域集合中。
处理规则:
- 如果Request-URI有一个maddr参数,其值是代理负责的
- 并且请求是使用Request-URI中指示的端口和传输接收的(显式或默认)
- 代理必须剥离maddr和任何非默认端口或传输参数,并继续处理
Route头字段处理
如果Route头字段的第一个值指示此代理,代理必须从请求中删除该值。
16.5 确定请求目标 (Determining Request Targets)
代理计算请求的目标。目标集要么由请求的内容预先确定,要么从抽象位置服务获取。集合中的每个目标都表示为URI。
处理规则
1. maddr参数存在:
- 如果Request-URI包含maddr参数,Request-URI必须作为唯一目标URI放入目标集
- 代理必须继续到第16.6节
2. 不负责的域:
- 如果Request-URI的域指示此元素不负责的域,Request-URI必须作为唯一目标放入目标集
- 元素必须继续到请求转发任务(第16.6节)
3. 使用位置服务:
- 如果请求的目标集尚未如上所述预先确定,这意味着元素对Request-URI中的域负责
- 元素可以使用它希望的任何机制来确定发送请求的位置
- 任何这些机制都可以建模为访问抽象位置服务
位置服务机制
位置服务可能包括:
- 从SIP注册器创建的位置服务获取信息
- 读取数据库
- 咨询在线状态服务器
- 利用其他协议
- 对Request-URI执行算法替换
错误响应
485 (Ambiguous):
- 如果Request-URI没有提供足够信息让代理确定目标集,应该返回485响应
- 此响应应该包含Contact头字段,其中包含要尝试的新地址的URI
404 (Not Found):
- 如果Request-URI指示此代理上不存在的资源,代理必须返回404响应
480 (Temporarily Unavailable):
- 如果应用上述所有规则后目标集仍然为空,代理必须返回错误响应,应该是480响应
16.6 请求转发 (Request Forwarding)
一旦目标集非空,代理可以开始转发请求。
转发策略
有状态代理可以以任何顺序处理目标集:
- 可以串行处理多个目标,允许每个客户端事务在开始下一个之前完成
- 可以并行启动与目标集中每个目标的客户端事务
- 可以任意将目标集划分为组,串行处理组,并行处理每组中的目标
常见排序机制: 使用从Contact头字段获取的目标的qvalue参数。从最高qvalue到最低处理目标。具有相等qvalue的目标可以并行处理。
转发步骤(11步)
对于每个目标,代理遵循以下步骤转发请求:
1. 复制请求 (Copy Request)
代理从收到的请求开始复制。副本最初必须包含收到请求中的所有头字段。
2. 更新Request-URI
副本起始行中的Request-URI必须替换为此目标的URI。如果URI包含Request-URI中不允许的任何参数,必须删除它们。
3. 更新Max-Forwards
- 如果副本包含Max-Forwards头字段,代理必须将其值递减一(1)
- 如果副本不包含Max-Forwards头字段,代理必须添加一个字段值,应该是70
4. 可选地添加Record-Route头字段值
如果此代理希望保留在此请求创建的对话的未来请求路径上(假设请求创建对话),它必须在任何现有Record-Route头字段值之前向副本插入Record-Route头字段值。
关键要求:
- Record-Route中放置的URI必须是SIP或SIPS URI
- 此URI必须包含lr参数
- 此URI对于请求转发到的每个目的地可以不同
- URI不应该包含transport参数(除非代理知道下游元素支持该传输)
SIPS处理:
- 如果Request-URI包含SIPS URI,或最顶部Route头字段值包含SIPS URI,放入Record-Route头字段的URI必须是SIPS URI
- 如果请求不是通过TLS接收的,代理必须插入Record-Route头字段
- 如果代理通过TLS接收请求,但生成的请求在Request-URI或最顶部Route头字段值中没有SIPS URI,代理必须插入不是SIPS URI的Record-Route头字段
5. 添加额外的头字段 (Add Additional Header Fields)
代理可以在此时向副本添加任何其他适当的头字段。
6. 后处理路由信息 (Postprocess Routing Information)
强制代理路由:
- 代理可能有本地策略,要求请求在传递到目的地之前访问特定的代理集
- 代理必须确保所有这些代理都是宽松路由器
- 这组代理由一组URI表示(每个都包含lr参数)
- 此集合必须被推入副本的Route头字段,在任何现有值之前(如果存在)
严格路由处理:
- 如果副本包含Route头字段,代理必须检查其第一个值中的URI
- 如果该URI不包含lr参数,代理必须修改副本:
- 代理必须将Request-URI放入Route头字段作为最后一个值
- 代理必须然后将第一个Route头字段值放入Request-URI并从Route头字段删除该值
7. 确定下一跳地址、端口和传输
代理应用RFC 3263中列出的程序来确定发送请求的位置:
- 如果代理已重新格式化请求以发送到严格路由元素(如步骤6所述),代理必须将这些程序应用于请求的Request-URI
- 否则,代理必须将程序应用于Route头字段的第一个值(如果存在),否则应用于Request-URI
- 程序将产生一组有序的(地址, 端口, 传输)元组
8. 添加Via头字段值
代理必须在现有Via头字段值之前向副本插入Via头字段值。
分支参数 (Branch Parameter):
- 代理将计算自己的分支参数,该参数对于该分支将是全局唯一的
- 必须包含所需的魔术cookie
- 这意味着对于通过代理螺旋或循环的请求的不同实例,分支参数将不同
循环检测的额外约束:
- 选择检测循环的代理在用于构造分支参数的值中有额外约束
- 选择检测循环的代理应该创建可由实现分为两部分的分支参数
- 第一部分必须满足第8.1.1.7节的约束
- 第二部分用于执行循环检测并区分循环和螺旋
分支计算建议: 创建此值的常见方法是计算以下内容的加密哈希:
- To标签
- From标签
- Call-ID头字段
- 收到的请求的Request-URI(翻译前)
- 最顶部Via头
- CSeq头字段的序列号
- 可能存在的任何Proxy-Require和Proxy-Authorization头字段
重要: 请求方法不得包含在分支参数的计算中。特别是,CANCEL和ACK请求(对于非2xx响应)必须具有与它们取消或确认的相应请求相同的分支值。
9. 如有必要添加Content-Length头字段
如果请求将使用基于流的传输发送到下一跳,并且副本不包含Content-Length头字段,代理必须插入一个,其值正确对应请求体。
10. 转发请求 (Forward Request)
有状态代理必须为此请求创建一个新的客户端事务(如第17.1节所述),并指示事务使用步骤7中确定的地址、端口和传输发送请求。
11. 设置计时器C (Set Timer C)
为了处理INVITE请求从不生成最终响应的情况,TU使用称为计时器C的计时器。
要求:
- 当代理INVITE请求时,必须为每个客户端事务设置计时器C
- 计时器必须大于3分钟
16.7 响应处理 (Response Processing)
当元素收到响应时,它首先尝试定位与响应匹配的客户端事务。如果找不到,元素必须将响应作为无状态代理处理。如果找到匹配项,响应将传递给客户端事务。
处理步骤(10步)
当客户端事务将响应传递给代理层时,必须进行以下处理:
1. 查找适当的响应上下文
代理使用第16.6节中描述的密钥定位它在转发原始请求之前创建的"响应上下文"。
2. 为临时响应更新计时器C
对于INVITE事务,如果响应是状态代码101到199(即除100以外的任何代码)的临时响应,代理必须为该客户端事务重置计时器C。计时器可以重置为不同的值,但此值必须大于3分钟。
3. 移除最顶部的Via
代理从响应中移除最顶部的Via头字段值。
重要检查:
- 如果响应中没有剩余Via头字段值,响应是针对此元素的,不得转发
- 此部分中描述的其余处理不对此消息执行
- 而是遵循第8.1.3节中描述的UAC处理规则
4. 将响应添加到上下文
收到的最终响应存储在响应上下文中,直到在与此上下文关联的服务器事务上生成最终响应。
3xx响应的递归处理:
- 如果代理选择通过将3xx响应中的任何联系人添加到目标集来递归处理它们,它必须在将响应添加到响应上下文之前从响应中删除它们
- 如果原始请求的Request-URI是SIPS URI,代理不应该递归到非SIPS URI
416响应处理:
- 如果代理收到对Request-URI方案不是SIP但原始收到请求中的方案是SIP或SIPS的请求的416响应,代理应该向目标集添加新URI
- 此URI应该是刚尝试的非SIP URI的SIP URI版本
5. 检查响应是否立即转发
在服务器事务上发送最终响应之前,必须立即转发以下响应:
- 除100 (Trying)以外的任何临时响应
- 任何2xx响应
6xx响应特殊处理:
- 如果收到6xx响应,它不会立即转发
- 有状态代理应该取消所有客户端挂起事务(如第10节所述)
- 它不得在此上下文中创建任何新分支
在服务器事务上发送最终响应后,必须立即转发以下响应:
- 对INVITE请求的任何2xx响应
有状态代理不得立即转发任何其他响应。特别是,有状态代理不得转发任何100 (Trying)响应。
6. 选择最佳响应
如果没有通过上述规则立即转发最终响应,并且此响应上下文中的所有客户端事务都已终止,有状态代理必须向响应上下文的服务器事务发送最终响应。
选择规则:
- 如果上下文中没有最终响应,代理必须向服务器事务发送408 (Request Timeout) 响应
- 否则,代理必须转发存储在响应上下文中的响应
- 它必须从6xx类响应中选择(如果上下文中存在任何响应)
- 如果没有6xx类响应,代理应该从存储在响应上下文中的最低响应类中选择
- 代理可以选择该类中的任何响应
优先响应: 代理应该优先考虑提供影响此请求重新提交的信息的响应,例如:
- 401 (Unauthorized)
- 407 (Proxy Authentication Required)
- 415 (Unsupported Media Type)
- 420 (Bad Extension)
- 484 (Address Incomplete)
503响应特殊处理:
- 收到503 (Service Unavailable)响应的代理不应该将其转发到上游
- 除非它可以确定它可能代理的任何后续请求也将生成503
- 如果收到的唯一响应是503,代理应该生成500响应并将其转发到上游
To标签处理:
- 代理不得将标签插入1xx或2xx响应的To头字段(如果请求不包含标签)
- 代理不得修改1xx或2xx响应的To头字段中的标签
- 代理不得修改包含To标签的请求的任何转发响应中的To标签
7. 聚合授权头字段值
如果选择的响应是401 (Unauthorized)或407 (Proxy Authentication Required),代理必须:
- 从到目前为止在此响应上下文中收到的所有其他401和407响应收集任何WWW-Authenticate和Proxy-Authenticate头字段值
- 在转发之前将它们添加到此响应而不修改
目的: 这是必要的,因为请求转发到的任何或所有目的地都可能请求了凭证。客户端需要接收所有这些挑战,并在重试请求时为每个挑战提供凭证。
8. Record-Route重写
如果选择的响应包含最初由此代理提供的Record-Route头字段值,代理可以选择在转发响应之前重写该值。
目的: 这允许代理向下一个上游和下游元素提供不同的URI。代理可能出于任何原因选择使用此机制。例如,它对多宿主主机很有用。
TLS处理:
- 如果代理通过TLS接收请求,并通过非TLS连接发送,代理必须将Record-Route头字段中的URI重写为SIPS URI
- 如果代理通过非TLS连接接收请求,并通过TLS发送,代理必须将Record-Route头字段中的URI重写为SIP URI
9. 转发响应 (Forward Response)
在执行步骤"聚合授权头字段值"到"Record-Route"中描述的处理后:
- 代理可以对选择的响应执行任何功能特定的操作
- 代理不得添加、修改或删除消息体
- 除非另有规定,代理不得删除除Via头字段值以外的任何头字段值
- 代理必须将响应传递给与响应上下文关联的服务器事务
重要: 代理必须维护响应上下文,直到其所有关联事务都已终止,即使在转发最终响应之后也是如此。
10. 生成CANCEL请求
如果转发的响应是最终响应,代理必须为与此响应上下文关联的所有挂起客户端事务生成CANCEL请求。
挂起客户端事务定义: 已收到临时响应但没有最终响应(处于proceeding状态)且没有为其生成关联CANCEL的事务。
6xx响应: 当收到6xx响应时,代理也应该为与此响应上下文关联的所有挂起客户端事务生成CANCEL请求。
16.8 处理计时器C (Processing Timer C)
如果计时器C触发,代理必须重置计时器C,或者终止客户端事务或不执行任何操作。
建议行为:
- 如果代理选择重置计时器,它应该选择大于3分钟的值
- 如果代理已收到与此客户端事务关联的临时响应,计时器应该重置为更大的值
- 如果客户端事务已进入"已完成"状态,计时器不得重置
16.9 处理传输错误 (Handling Transport Errors)
如果客户端事务报告传输失败,元素必须采取与503 (Service Unavailable)或408 (Request Timeout)最终响应收到时相同的操作。
16.10 CANCEL处理 (CANCEL Processing)
有状态代理可以在任何时候为它生成的任何其他请求生成CANCEL(前提是对该请求收到临时响应,如第9.1节所述)。
匹配CANCEL处理: 当代理收到匹配的CANCEL请求时,它必须取消与响应上下文关联的任何挂起客户端事务。
CANCEL的事务处理:
- 虽然CANCEL请求在有状态代理中由其自己的服务器事务处理,但不会为其创建新的响应上下文
- 代理层搜索其现有响应上下文,以查找处理与此CANCEL关联的请求的服务器事务
- 如果找到匹配的响应上下文,元素必须立即向CANCEL请求返回200 (OK) 响应
- 此外,元素必须为上下文中所有挂起的客户端事务生成CANCEL请求
无匹配上下文:
- 如果未找到响应上下文,元素没有应用CANCEL的请求的任何知识
- 它必须无状态地转发CANCEL请求
16.11 无状态代理 (Stateless Proxy)
当无状态行动时,代理是一个简单的消息转发器。无状态行动时执行的大部分处理与有状态行为时相同。区别详述如下。
特点
无状态代理没有任何事务概念或用于描述有状态代理行为的响应上下文概念。相反,无状态代理直接从传输层获取消息(请求和响应)。
结果:
- 无状态代理不会自己重传消息
- 但是,它们会转发收到的所有重传(它们无法区分重传和原始消息)
- 当无状态处理请求时,元素不得生成自己的100 (Trying)或任何其他临时响应
请求处理
无状态代理必须验证请求(如第16.3节所述)。
无状态代理必须遵循第16.4到16.5节中描述的请求处理步骤,但有以下例外:
目标选择约束:
- 无状态代理必须从目标集中选择一个且仅一个目标
- 此选择必须仅依赖于消息中的字段和服务器的时间不变属性
- 特别是,重传的请求每次处理时必须转发到相同的目的地
- 此外,CANCEL和非路由ACK请求必须生成与其关联INVITE相同的选择
转发处理
无状态代理必须遵循第16.6节中描述的请求处理步骤,但有以下例外:
分支ID要求:
- 跨空间和时间的唯一分支ID要求也适用于无状态代理
- 但是,无状态代理不能简单地使用随机数生成器来计算分支ID的第一个组件
- 这是因为请求的重传需要具有相同的值,而无状态代理无法区分重传和原始请求
- 因此,使分支参数唯一的组件在每次转发重传请求时必须相同
- 分支参数必须计算为重传时不变的消息参数的组合函数
推荐程序:
- 代理检查收到请求的最顶部Via头字段中的分支ID
- 如果它以魔术cookie开头,传出请求的分支ID的第一个组件计算为收到的分支ID的哈希
- 否则,分支ID的第一个组件计算为以下内容的哈希:
- 最顶部Via
- To头字段中的标签
- From头字段中的标签
- Call-ID头字段
- CSeq号(但不是方法)
- 从收到请求的Request-URI
其他转换约束:
- 第16.6节中指定的所有其他消息转换必须导致重传请求的相同转换
- 如果代理插入Record-Route值或将URI推入Route头字段,它必须在请求的重传中放置相同的值
- 转换必须基于时间不变配置或重传不变的请求属性
转发目的地:
- 无状态代理确定转发请求的位置(如第16.6节项目10中为有状态代理所述)
- 请求直接发送到传输层,而不是通过客户端事务
CANCEL处理
无状态代理不得对CANCEL请求执行特殊处理。它们按照上述规则作为任何其他请求处理。
特别是,无状态代理对CANCEL请求应用与对任何其他请求相同的Route头字段处理。
响应处理
第16.7节中描述的响应处理不适用于无状态行为的代理。
当响应到达无状态代理时,代理必须:
- 检查第一个(最顶部)Via头字段值中的sent-by值
- 如果该地址与代理匹配(它等于此代理插入到先前请求中的值),代理必须从响应中删除该头字段值,并将结果转发到下一个Via头字段值中指示的位置
- 代理不得添加、修改或删除消息体
- 除非另有规定,代理不得删除任何其他头字段值
- 如果地址与代理不匹配,消息必须被静默丢弃
16.12 代理路由处理摘要 (Summary of Proxy Route Processing)
在没有与之相反的本地策略的情况下,代理对包含Route头字段的请求执行的处理可以总结为以下步骤:
处理步骤
1. 检查Request-URI:
- 如果它指示此代理拥有的资源,代理将用运行位置服务的结果替换它
- 否则,代理将不更改Request-URI
2. 检查最顶部Route头字段值中的URI:
- 如果它指示此代理,代理从Route头字段删除它(此路由节点已到达)
3. 转发请求:
- 代理将请求转发到最顶部Route头字段值中的URI指示的资源
- 或者如果没有Route头字段,则转发到Request-URI中的资源
- 代理通过对该URI应用RFC 3263中的程序来确定转发请求时使用的地址、端口和传输
关键点: 如果在请求路径上没有遇到严格路由元素,Request-URI将始终指示请求的目标。
16.12.1 示例 (Examples)
16.12.1.1 基本SIP梯形 (Basic SIP Trapezoid)
此场景是基本SIP梯形,U1 -> P1 -> P2 -> U2,两个代理都记录路由。
U1发送:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
P1(出站代理)查找domain.com的DNS并发送:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route: <sip:p1.example.com;lr>
P2负责domain.com,运行位置服务并重写Request-URI:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route: <sip:p2.domain.com;lr>
Record-Route: <sip:p1.example.com;lr>
u2.domain.com的被叫方响应200 OK:
SIP/2.0 200 OK
Contact: sip:[email protected]
Record-Route: <sip:p2.domain.com;lr>
Record-Route: <sip:p1.example.com;lr>
U1构造BYE请求:
BYE sip:[email protected] SIP/2.0
Route: <sip:p1.example.com;lr>,<sip:p2.domain.com;lr>
流程: U1 -> P1 -> P2 -> U2
16.12.1.2 穿越严格路由代理
此场景中,对话跨四个代理建立,每个代理都添加Record-Route头字段值。第三个代理实现RFC 2543中指定的严格路由程序。
U1->P1->P2->P3->P4->U2
到达U2的INVITE包含:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route: <sip:p4.domain.com;lr>
Record-Route: <sip:p3.middle.com> (无lr参数)
Record-Route: <sip:p2.example.com;lr>
Record-Route: <sip:p1.example.com;lr>
U2发送BYE:
BYE sip:[email protected] SIP/2.0
Route: <sip:p4.domain.com;lr>
Route: <sip:p3.middle.com>
Route: <sip:p2.example.com;lr>
Route: <sip:p1.example.com;lr>
P4注意到p3.middle.com没有lr参数,重新格式化为:
BYE sip:p3.middle.com SIP/2.0
Route: <sip:p2.example.com;lr>
Route: <sip:p1.example.com;lr>
Route: <sip:[email protected]>
P3(严格路由器)转发到P2...
最终请求到达U1。
16.12.1.3 重写Record-Route头字段值
此场景中,U1和U2在不同的私有命名空间中,它们通过代理P1(充当命名空间之间的网关)进入对话。
U1->P1->U2
U1发送:
INVITE sip:[email protected] SIP/2.0
Contact: <sip:[email protected]>
P1使用位置服务并发送到U2:
INVITE sip:[email protected] SIP/2.0
Contact: <sip:[email protected]>
Record-Route: <sip:gateway.rightprivatespace.com;lr>
U2发送200 OK回P1:
SIP/2.0 200 OK
Contact: <sip:[email protected]>
Record-Route: <sip:gateway.rightprivatespace.com;lr>
P1重写其Record-Route参数并发送到U1:
SIP/2.0 200 OK
Contact: <sip:[email protected]>
Record-Route: <sip:gateway.leftprivatespace.com;lr>
稍后,U1发送BYE:
BYE sip:[email protected] SIP/2.0
Route: <sip:gateway.leftprivatespace.com;lr>
P1转发到U2:
BYE sip:[email protected] SIP/2.0
📊 关键要点总结
有状态代理 vs 无状态代理
| 特性 | 有状态代理 | 无状态代理 |
|---|---|---|
| 事务状态 | 维护 | 不维护 |
| 响应上下文 | 创建 | 不创建 |
| 重传 | 由事务层处理 | 转发所有收到的重传 |
| 分叉 | 可以 | 不可以(只能选择一个目标) |
| 100响应 | 可以生成 | 不能生成 |
| 最佳响应选择 | 是 | 否 |
| CANCEL处理 | 特殊处理 | 作为普通请求处理 |
请求处理流程
1. 请求验证
├─ 语法检查
├─ URI方案检查
├─ Max-Forwards检查
├─ 循环检测
├─ Proxy-Require检查
└─ Proxy-Authorization检查
2. 路由信息预处理
├─ Record-Route处理
├─ maddr参数处理
└─ Route头字段处理
3. 确定请求目标
├─ maddr参数
├─ 不负责的域
└─ 位置服务查询
4. 请求转发(11步)
├─ 复制请求
├─ 更新Request-URI
├─ 更新Max-Forwards
├─ 添加Record-Route
├─ 添加额外头字段
├─ 后处理路由信息
├─ 确定下一跳
├─ 添加Via
├─ 添加Content-Length
├─ 转发请求
└─ 设置计时器C
5. 响应处理(10步)
├─ 查找上下文
├─ 更新计时器C
├─ 移除Via
├─ 添加到上下文
├─ 检查立即转发
├─ 选择最佳响应
├─ 聚合授权头
├─ Record-Route重写
├─ 转发响应
└─ 生成CANCEL
Record-Route机制
目的: 确保对话中的后续请求经过相同的代理路径
工作原理:
1. 代理在转发INVITE时添加Record-Route头字段
2. 响应携带所有Record-Route值返回
3. UAC和UAS从Record-Route构建路由集
4. 后续请求使用路由集中的Route头字段
关键要求:
- URI必须包含lr参数(宽松路由)
- SIPS URI需要特殊处理
- 可以在响应中重写以适应不同命名空间
重要错误码
- 416 - Unsupported URI Scheme
- 420 - Bad Extension
- 482 - Loop Detected
- 483 - Too Many Hops
- 485 - Ambiguous
- 408 - Request Timeout
- 480 - Temporarily Unavailable
- 481 - Call/Transaction Does Not Exist
- 491 - Request Pending
本章小结:
第16章是RFC 3261中最长、最详细的章节之一,全面描述了SIP代理服务器的行为规范。它涵盖了有状态代理和无状态代理的完整处理流程,包括请求验证、路由信息预处理、目标确定、请求转发(11步)、响应处理(10步)、CANCEL处理等核心机制。理解代理行为对于构建可扩展的SIP网络基础设施至关重要,因为代理是SIP架构中的关键路由和策略执行点。