16. 代理行为
16 代理行为
16.1 概述
SIP代理是将SIP请求路由到用户代理服务器并将SIP响应路由到用户代理客户端的元素.一个请求在到达UAS的途中可能会穿越多个代理.每个元素都将做出路由决定,在将请求转发到下一个元素之前修改请求.响应将以相反的顺序通过请求遍历的同一组代理进行路由.
代理是SIP元素的逻辑角色.当请求到达时,可以扮演代理角色的元素首先决定是否需要自己响应请求.例如,请求的格式可能不正确,或者元素在充当代理之前可能需要来自客户端的凭据.该元素可以用任何形式响应
正确的错误代码.当直接响应请求时,该元素扮演UAS的角色,并且必须按照第8.2节所述进行操作.
对于每个新请求,代理可以在有状态或无状态模式下运行.当无状态时,代理充当简单的转发元素.它将每个请求转发到下游的单个元素,该元素通过基于请求做出目标和路由决策来确定.它只是将收到的每个响应转发到上游.一旦消息被转发,无状态代理将丢弃有关该消息的信息.有状态代理会记住有关每个传入请求的信息(特别是事务状态),以及它在处理传入请求时发送的任何请求.它使用此信息影响与该请求关联的未来消息的处理.有状态代理可以选择"分叉"请求,将其路由到多个目的地.任何转发到多个位置的请求都必须有状态地处理.
在某些情况下,代理可能使用有状态传输(如TCP)转发请求,而不使用事务状态.例如,代理可以无状态地将请求从一个TCP连接转发到另一个事务,只要它在消息中放置了足够的信息,以便能够将响应转发到请求到达的同一个连接.在不同类型的传输之间转发的请求,其中代理的TU必须在确保其中一个传输上的可靠传递方面发挥积极作用,必须以事务状态转发请求.
有状态代理可以在处理请求期间的任何时候转换为无状态操作,只要它不做任何可能阻止其最初成为无状态的事情(例如,分叉或生成100响应).当执行这样的转换时,所有状态都被丢弃.代理不应启动CANCEL request.
当以无状态或有状态的方式处理请求时,所涉及的大部分处理是相同的.接下来的几个小节是从有状态代理的角度编写的.最后一节指出了无状态代理行为不同的地方.
16.2 有状态代理
有状态时,代理纯粹是SIP事务处理引擎.它的行为在这里根据第17节中定义的服务器和客户端事务进行建模.有状态代理有一个服务器事务,通过一个更高层的代理处理组件(见图3)与一个或多个客户端事务关联,该组件称为代理核心.传入请求由服务器处理
交易来自服务器事务的请求被传递到代理核心.代理核心通过选择一个或多个下一跳位置来确定将请求路由到何处.每个下一跳位置的传出请求由其自己的关联客户端事务处理.代理核心从客户端事务收集响应,并使用它们向服务器事务发送响应.
有状态代理为收到的每个新请求创建一个新的服务器事务.然后,根据第17节,该服务器事务将处理请求的任何重新传输.如第8.2.6节所述,代理核心必须作为UAS在该服务器事务上发送即时临时消息(如100 Trying).因此,有状态代理不应该对非INVITE请求生成100(尝试)响应.
这是代理行为的模型,而不是软件的模型.实现可以自由地采取任何复制该模型定义的外部行为的方法.
对于所有新请求,包括任何具有未知方法的请求,打算代理该请求的元素必须:
1. 验证请求(第16.3节)
2. 预处理路由信息(第16.4节)
3. 确定请求的目标(第16.5节)
+--------------------+
| | +---+
| | | C |
| | | T |
| | +---+
+---+ | Proxy | +---+ CT = Client Transaction
| S | | "Higher" Layer | | C |
| T | | | | T | ST = Server Transaction
+---+ | | +---+
| | +---+
| | | C |
| | | T |
| | +---+
+--------------------+
图3:有状态代理模型
4. 将请求转发给每个目标(第16.6节)
5. 处理所有响应(第16.7节)
16.3 请求验证
在元素可以代理请求之前,它必须验证消息的有效性.有效邮件必须通过以下检查:
1. 合理语法
2. URI方案
3. Max-Forwards
4. (可选)循环检测
5. Proxy-Require
6. Proxy-Authorization
如果这些检查中的任何一个失败,元素必须表现为用户代理服务器(参见第8.2节),并以错误代码响应.
请注意,检测合并请求不需要代理,并且不能将合并请求视为错误条件.接收请求的端点将按照第8.2.2.2节中的描述解析合并.
-
合理语法检查
请求的格式必须足够好,才能用服务器事务处理.这些请求验证步骤的剩余部分或请求转发部分中涉及的任何组件都必须格式良好.任何其他组件,无论是否格式正确,都应忽略,并在转发消息时保持不变.例如,元素不会因为日期头字段格式错误而拒绝请求.同样,在转发请求之前,代理不会删除格式错误的日期头字段.
该协议旨在扩展.未来的扩展可能会在任何时候定义新的方法和头字段.元素不能拒绝代理请求,因为它包含它不知道的方法或头字段.
-
URI方案检查
如果Request-URI具有代理无法理解其方案的URI,则代理应使用416(不支持的URI方案)响应拒绝请求.
-
最大远期支票
Max-Forwards头字段(第20.22节)用于限制SIP请求可以遍历的元素数.
如果请求不包含Max-Forwards头字段,则通过此检查.
如果请求包含字段值大于零的Max-Forwards头字段,则检查通过.
如果请求包含字段值为零(0)的Max-Forwards头字段,则元素不得转发请求.如果请求是针对OPTIONS的,元素可以作为最终接收者,并根据第11节进行响应.否则,元素必须返回483(跳数过多)响应.
-
可选环路检测检查
元素可以在转发请求之前检查转发循环.如果请求包含一个Via 头字段,其sent-by值等于代理在以前的请求中放置的值,则该请求之前已由该元素转发.请求已经循环或合法地螺旋式地通过元素.为了确定请求是否已循环,该元素可对该消息执行第16.6节第8步中所述的分支参数计算,并将其与该 Via 头字段中接收的参数进行比较.如果参数匹配,则请求已循环.如果它们不同,请求将螺旋上升,处理将继续.如果检测到回路,则元件可能返回482(检测到回路)响应.
-
代理需要检查
此协议的未来扩展可能会引入需要代理进行特殊处理的功能.端点将在使用这些功能的请求中包含一个Proxy Require header字段,告诉代理不要处理该请求,除非了解该功能.
如果请求包含一个代理请求头字段(第20.29节),其中包含一个或多个该元素无法理解的OPTIONS标记,则该元素必须返回420(错误扩展)响应.响应必须包含一个不受支持的(第20.40节)头字段,列出元素不理解的OPTIONS标记.
-
Proxy-Authorization检查
如果元素在转发请求之前需要凭据,则必须按照第22.3节所述检查请求.该部分还定义了检查失败时元素必须执行的操作.
16.4 路由信息预处理
代理必须检查请求的Request-URI.如果请求的Request-URI包含该代理先前放入Record-Route头字段中的值(参见第16.6节第4项),则代理必须用路由头字段中的最后一个值替换请求中的Request-URI,并从路由头字段中删除该值.然后,代理必须继续,就好像它收到了这个修改后的请求一样.
只有当向代理(可能是端点)发送请求的元素是严格的路由器时,才会发生这种情况.接收时的这种重写对于实现与这些元素的向后兼容性是必要的.它还允许遵循此规范的元素通过严格的路由代理保留Request-URI(请参见第12.2.1.1节).
此要求并不要求代理必须保持状态,以便检测它以前放置在Record-Route头字段中的URI.相反,代理只需要在这些URI中放置足够的信息,以便在以后出现时将它们识别为它提供的值.
如果Request-URI包含maddr参数,则代理必须检查其值是否在配置代理负责的地址或域集中.如果Request-URI有一个maddr参数,该参数的值由代理负责,并且该请求是使用Request-URI中指示的端口和传输(显式或默认)接收的,代理必须去除maddr和任何非默认端口或传输参数,并继续处理,就像请求中不存在这些值一样.
请求到达时可能会有一个与代理匹配的maddr,但其端口或传输与URI中指示的端口或传输不同.这样的请求需要使用指定的端口和传输转发到代理.
如果Route header字段中的第一个值指示此代理,则代理必须从请求中删除该值.
16.5 确定请求目标
接下来,代理计算请求的目标.目标集或者由请求的内容预先确定,或者从抽象位置服务获得.集合中的每个目标都表示为一个URI.
如果请求的Request-URI包含maddr参数,则必须将Request-URI作为唯一的目标URI放入目标集中,并且代理必须转至第16.6节.
如果Request-URI的域表示此元素不负责的域,则必须将Request-URI作为唯一目标放入目标集中,并且元素必须继续执行请求转发任务(第16.6节).
在许多情况下,代理可能会收到对其不负责的域的请求.处理传出呼叫的防火墙代理(HTTP代理处理传出请求的方式)就是可能发生这种情况的一个例子.
如果没有如上所述预先确定请求的目标集,这意味着该元素负责Request-URI中的域,并且该元素可以使用它想要的任何机制来确定将请求发送到哪里.这些机制中的任何一个都可以建模为访问抽象位置服务.这可能包括从SIPREGISTER器创建的位置服务获取信息,读取数据库,咨询存在服务器,利用其他协议,或者简单地对Request-URI执行算法替换.当访问由REGISTER器构造的位置服务时,在用作索引之前,必须首先按照第10.3节所述规范化Request-URI.这些机制的输出用于构造目标集.
如果Request-URI没有为代理提供足够的信息来确定目标集,它应该返回485(不明确)响应.此响应应包含Contact 头字段,其中包含要尝试的新地址的URI.例如,INVITE
啜饮:约翰[email protected]在其位置服务列出多个John Smith的代理上可能不明确.详见第21.4.23节.
在构建目标集时,可以使用请求中或关于元素的当前环境的任何信息.例如,可以根据头字段和正文的内容或存在,请求到达的时间,请求到达的接口,以前请求的失败,甚至元素的当前利用率水平来构造不同的集合.
由于潜在目标是通过这些服务定位的,因此它们的URI将添加到目标集中.目标只能在目标集中放置一次.如果目标URI已存在于集合中(基于URI类型的相等定义),则不得再次添加它.
如果原始请求的Request-URI未指示该代理负责的资源,则代理不得向目标集添加其他目标.
代理只能在转发期间更改请求的Request-URI,前提是它负责该URI.如果代理不负责该URI,它将不会在3xx或416响应上递归,如下所述.
如果原始请求的Request-URI指示该代理负责的资源,则该代理可以在开始请求转发后继续向集合中添加目标.它可以使用在处理过程中获得的任何信息来确定新的目标.例如,代理可以选择将重定向响应(3xx)中获得的联系人合并到目标集中.如果代理在构建目标集时使用动态信息源(例如,如果它咨询SIPREGISTER器),那么它应该在处理请求的过程中监视该源.新位置可用时应添加到目标集中.如上所述,任何给定的URI都不能多次添加到集合中.
只允许将URI添加到集合中一次可以减少不必要的网络流量,并且在合并来自重定向请求的联系人的情况下可以防止无限递归.
例如,一个普通的位置服务是"no-op",其中目标URI等于传入的Request-URI.请求被发送到特定的下一跳代理进行进一步处理.在请求期间
转发第16.6节第6项,下一跳的标识(表示为SIP或SIPS URI)作为最顶端的路由头字段值插入到请求中.
如果Request-URI指示此代理上不存在的资源,则该代理必须返回404(未找到)响应.
如果目标集在应用上述所有内容后仍为空,则代理必须返回错误响应,该响应应为480(暂时不可用)响应.
16.6 请求转发
一旦目标集非空,代理就可以开始转发请求.有状态代理可以按任何顺序处理集合.它可以连续处理多个目标,允许每个客户端事务在开始下一个事务之前完成.它可以与每个目标并行启动客户端事务.它还可以将集合任意分组,串行处理组,并行处理每个组中的目标.
一种常见的排序机制是使用从Contact header字段获得的目标的qvalue参数(参见第20.10节).目标从最高qvalue到最低qvalue进行处理.可以并行处理具有相等Q值的目标.
有状态代理必须具有在接收响应时维护目标集的机制,并将每个转发请求的响应与原始请求相关联.对于该模型,该机制是在转发第一个请求之前由代理层创建的"响应上下文".
对于每个目标,代理将按照以下步骤转发请求:
1. 将收到的请求复制一份
2. 更新Request-URI
3. 更新Max-Forwards头字段
4. (可选)添加Record-Route头字段值
5. (可选)添加其他头字段
6. 后处理路由信息
7. 确定下一跳地址,端口和传输
8. 添加一个Via 头字段值
9. 如有必要,添加Content-Length头字段
10. 转发新请求
11. 设置定时器C
以下详细介绍了每个步骤:
1. 复印请求
代理从接收到的请求的副本开始.副本最初必须包含收到的请求中的所有头字段.不得删除下述处理中未详细说明的字段.副本应保持头字段的顺序与收到的请求相同.代理不得使用公共字段名对字段值重新排序(见第7.3.1节).代理不得添加,修改或删除邮件正文.
实际实现不需要执行复制;主要要求是每个下一跳的处理从相同的请求开始.
2. 请求地址
副本开始行中的Request-URI必须替换为此目标的URI.如果URI包含Request-URI中不允许的任何参数,则必须删除这些参数.
这是代理角色的本质.这是代理将请求路由到其目标的机制.
在某些情况下,接收到的Request-URI被放入目标集中而不被修改.对于这一目标,上述替代措施实际上是不可行的.
3. Max-Forwards
如果副本包含Max-Forwards头字段,则代理必须将其值递减一(1).
如果副本不包含Max-Forwards头字段,则代理必须添加一个字段值为70的字段.
一些现有UAs不会在请求中提供Max-Forwards头字段.
4. 记录路线
如果此代理希望保留在此请求创建的对话中的未来请求路径上(假设请求创建了一个对话),则它必须在任何现有Record-Route头字段值之前将Record-Route头字段值插入副本中,即使路由头字段已经存在.
建立对话的请求可能包含预加载的路由头字段.
如果此请求已经是对话的一部分,则如果代理希望保留在对话中未来请求的路径上,则应插入Record-Route头字段值.在第12节所述的正常端点操作中,这些Record-Route头字段值不会对端点使用的路由集产生任何影响.
如果代理选择不将Record-Route头字段值插入到已经是对话一部分的请求中,则代理将保留在路径上.但是,当失败的端点重建对话时,它将从路径中删除.
代理可以在任何请求中插入Record-Route头字段值.如果请求未启动对话,端点将忽略该值.有关端点如何使用Record-Route头字段值来构造路由头字段的详细信息,请参见第12节.
请求路径中的每个代理选择是否独立添加Record-Route头字段值-请求中存在Record-Route头字段并不要求该代理添加值.
Record-Route头字段值中的URI必须是SIP或SIPS URI.此URI必须包含lr参数(见第19.1.1节).对于请求转发到的每个目的地,此URI可能不同.URI不应包含传输参数,除非代理知道(例如在专用网络中)后续请求路径中的下一个下游元素支持该传输.
这个代理提供的URI将被其他一些元素用来做出路由决定.通常,该代理无法知道该元素的功能,因此它必须将自身限制为SIP实现的必需元素:SIP URI和TCP或UDP传输.
当[4]的服务器定位过程应用于Record-Route头字段时,放置在Record-Route头字段中的URI必须解析为插入它的元素(或合适的替代),以便后续请求到达相同的SIP元素.如果Request-URI包含SIPS URI,或者最顶端的路由头字段值(在bullet 6的后期处理之后)包含SIPS URI,则放入Record-Route头字段的URI必须是SIPS URI.此外,如果请求未通过TLS接收,则代理必须插入Record-Route头字段.以类似的方式,通过TLS接收请求但在Request-URI或最顶端的路由头字段值(在bullet 6的后处理之后)中生成没有SIPS URI的请求的代理必须插入不是SIPS URI的Record-Route头字段.
在整个对话中,安全外围的代理必须保持在外围.
如果放置在Record-Route头字段中的URI在响应中传回时需要重写,则该URI必须足够清晰,以便在当时定位.(请求可能会螺旋式地通过该代理,导致添加多个Record-Route头字段值).第16.7节的第8项建议了一种机制,以使URI充分不同.
代理可以在Record-Route头字段值中包含参数.这些将在一些对请求的响应中得到响应,例如200(确定)个INVITE响应.这些参数可能有助于在消息中而不是代理中保持状态.
如果代理需要位于任何类型对话(例如跨防火墙的对话)的路径中,它应该使用它不理解的方法向每个请求添加Record-Route头字段值,因为该方法可能具有对话语义.
代理放置在Record-Route头字段中的URI仅在发生该URI的事务创建的任何对话的生存期内有效.例如,对话有状态代理可能在对话终止后拒绝接受Request-URI中具有该值的未来请求.当然,非对话状态代理不知道对话何时终止,但它们可能在值中编码足够的信息,以便将其与未来请求的对话标识符进行比较,并可能拒绝与该信息不匹配的请求.端点不得使用从提供URI的对话外部的Record-Route头字段中获取的URI.看见
有关端点使用Record-Route头字段的更多信息,请参见第12节.
某些服务可能需要Record-Route,其中代理需要观察对话中的所有消息.然而,它减慢了处理速度并损害了可伸缩性,因此代理仅应在特定服务需要时Record-Route.
Record-Route过程设计用于启动对话的任何SIP请求.INVITE是本规范中唯一的此类请求,但协议的扩展可能会定义其他请求.
5. 添加其他头字段
此时,代理可以向副本添加任何其他适当的头字段.
6. 后处理路由信息
代理可能有一个本地策略,该策略要求请求在发送到目标之前访问一组特定的代理.代理必须确保所有此类代理都是松散的路由器.通常,只有在代理位于同一管理域内时,才能确定这一点.这组代理由一组URI表示(每个URI都包含lr参数).此集合必须在任何现有值(如果存在)之前推入副本的Route header字段.如果缺少Route header字段,则必须添加该字段,其中包含URI列表.
如果代理有一个本地策略,要求请求访问一个特定代理,将路由值推送到Route header字段的替代方法是绕过下面第10项的转发逻辑,而只将请求发送到该特定代理的地址,端口和传输.如果请求具有路由头字段,则除非知道下一跳代理是松散路由器,否则不得使用此替代方案.否则,可以使用该方法,但上述路由插入机制因其健壮性,灵活性,通用性和操作一致性而更受欢迎.此外,如果Request-URI包含SIPS URI,则必须使用TLS与该代理进行通信.
如果副本包含路由头字段,则代理必须检查其第一个值中的URI.如果该URI不包含lr参数,则代理必须按如下方式修改副本:
- 代理必须将Request-URI作为最后一个值放入Route header字段.
- 然后,代理必须将第一个路由头字段值放入Request-URI中,并从路由头字段中删除该值.
将Request-URI追加到Route header字段是用于通过严格的路由元素传递该Request-URI中的信息的机制的一部分.将第一个路由头字段值"弹出"到Request-URI中,以严格路由元素期望接收消息的方式格式化消息(在Request-URI中有自己的URI,在第一个路由头字段值中有下一个要访问的位置).
7. 确定下一跳地址,端口和传输
代理可以具有将请求发送到特定IP地址,端口和传输的本地策略,而与路由和Request-URI的值无关.如果代理无法确定IP地址,端口和传输是否对应于松散路由器的服务器,则不得使用此类策略.但是,不推荐通过特定下一跳发送请求的这种机制;相反,应使用Route header字段实现上述目的.
在没有这种覆盖机制的情况下,代理应用[4]中列出的程序来确定向何处发送请求.如果代理已经按照上面步骤6中的描述重新格式化了发送到严格路由元素的请求,那么代理必须将这些过程应用于请求的Request-URI.否则,代理必须将过程应用于Route header字段中的第一个值(如果存在),否则应用于Request-URI.这些过程将生成一组有序的(地址,端口,传输)元组.与将哪个URI用作[4]过程的输入无关,如果Request-URI指定SIPS资源,则代理必须遵循[4]的过程,就像输入URI是SIPS URI一样.
如[4]所述,代理必须尝试将消息传递到该集合中的第一个元组,并按顺序遍历该集合,直到传递尝试成功.
对于尝试的每个元组,代理必须将消息格式化为适合该元组的格式,并使用新的客户端事务发送请求,如步骤8到10中所述.
由于每次尝试都使用一个新的客户端事务,因此它代表一个新的分支.因此,在步骤8中插入的Via header字段所提供的分支参数对于每次尝试都必须不同.
如果客户端事务报告发送请求失败或状态机超时,则代理将继续发送到该有序集中的下一个地址.如果已排序集已用尽,则无法将请求转发到目标集中的此元素.代理不需要在响应上下文中放置任何内容,但在其他情况下,其行为就好像目标集的该元素返回了408(请求超时)最终响应一样.
8. 添加一个Via 头字段值
代理必须在现有Via 头字段值之前将Via 头字段值插入副本中.该值的构造遵循第8.1.1.7节的相同指南.这意味着代理将计算其自身的分支参数,该参数对于该分支是全局唯一的,并包含必要的魔法cookie.请注意,这意味着对于通过代理的螺旋式或循环式请求的不同实例,分支参数将不同.
选择检测循环的代理在用于构造分支参数的值中有一个附加约束.选择检测循环的代理应创建一个分支参数,该参数可由实现分为两部分.第一部分必须满足上述第8.1.1.7节的约束条件.第二个用于执行回路检测并区分回路和螺旋.
循环检测是通过验证当请求返回到代理时,对请求处理有影响的字段没有更改来执行的.分支参数此部分中的值应反映所有这些字段(包括任何路由,Proxy-Require和Proxy-Authorization头字段).这是为了确保如果请求被路由回代理,并且其中一个字段发生更改,则将其视为螺旋而不是循环(请参见第16.3节).创建此值的常用方法是计算to标记,From标记,Call-ID头字段,接收到的请求的Request-URI(翻译前),最顶端的Via 头字段以及CSeq头字段中的序列号的加密哈希,以及可能存在的任何Proxy-Require和Proxy-Authorization头字段.这个
用于计算散列的算法取决于实现,但以十六进制表示的MD5(RFC 1321[35])是一个合理的选择.(令牌不允许使用Base64.)
如果代理希望检测循环,则它提供的"分支"参数必须取决于影响请求处理的所有信息,包括传入Request-URI和影响请求的接纳或路由的任何头字段.这是区分循环请求和路由参数在返回到此服务器之前已更改的请求所必需的.
请求方法不得包含在分支参数的计算中.特别是,CANCEL和ACK request(对于非2xx响应)必须与它们CANCEL或ACK的相应请求具有相同的分支值.branch参数用于在处理这些请求的服务器上关联这些请求(请参见第17.2.3和9.2节).
9. 如有必要,添加Content-Length头字段
如果将使用基于流的传输将请求发送到下一个跃点,并且副本不包含Content-Length头字段,则代理必须为请求正文插入一个具有正确值的头字段(参见第20.14节).
10. 转发请求
有状态代理必须为此请求创建一个新的客户端事务,如第17.1节所述,并指示事务使用步骤7中确定的地址,端口和传输发送请求.
11. 设置定时器C
为了处理INVITE请求从未生成最终响应的情况,TU使用一个称为定时器C的定时器.当INVITE请求被代理时,必须为每个客户端事务设置定时器C.计时器必须大于3分钟.第16.7节bullet 2讨论如何使用临时响应更新此计时器,第16.8节讨论触发时的处理.
16.7 响应处理
当元素收到响应时,它首先尝试定位与响应匹配的客户端事务(第17.1.3节).如果找不到响应,则元素必须将响应(即使是信息响应)作为无状态代理(如下所述)进行处理.如果找到匹配项,则将响应传递给客户端事务.
转发未找到客户端事务(或更一般地说,未发现已发送关联请求的任何知识)的响应可提高健壮性.特别是,它确保对INVITE请求的"延迟"2xx响应被正确转发.
当客户端事务将响应传递到代理层时,必须进行以下处理:
1. 找到合适的响应上下文
2. 更新临时响应的计时器C
3. 拆下最上面的通孔
4. 将响应添加到响应上下文中
5. 检查是否应立即转发此响应
6. 必要时,从响应上下文中选择最佳的最终响应
如果在与响应上下文关联的每个客户端事务终止后没有转发最终响应,则代理必须从迄今为止看到的响应中选择并转发"最佳"响应.
必须对转发的每个响应执行以下处理.可能会转发对每个请求的多个响应:至少每个临时响应和一个最终响应.
7. 如有必要,聚合授权头字段值
8. 可选地重写Record-Route头字段值
9. 转发回复
10. 生成任何必要的CANCEL request
上述每个步骤的详细说明如下:
1. 查找上下文
代理使用第16.6节中描述的密钥定位在转发原始请求之前创建的"响应上下文".其余的处理步骤在此上下文中进行.
2. 更新临时响应的计时器C
对于INVITE事务,如果响应是包含状态代码101到199的临时响应(即,除100以外的任何值),则代理必须为该客户端事务重置计时器C.计时器可以重置为不同的值,但该值必须大于3分钟.
3. 通过
代理从响应中删除最顶端的Via 头字段值.
如果响应中未保留任何Via 头字段值,则响应是针对此元素的,不得转发.本节中描述的其余处理不会在此消息上执行,而是遵循第8.1.3节中描述的UAC处理规则(传输层处理已经发生).
例如,当元素生成第10节所述的CANCEL request时,就会发生这种情况.
4. 将响应添加到上下文中
接收到的最终响应存储在响应上下文中,直到在与此上下文关联的服务器事务上生成最终响应为止.该响应可能是在该服务器事务上返回的最佳最终响应的候选.在形成最佳响应时,可能需要来自此响应的信息,即使未选择此响应.
如果代理选择通过将联系人添加到目标集在3xx响应中的任何联系人上递归,则必须在将响应添加到响应上下文之前将其从响应中删除.但是,如果原始请求的Request-URI是SIPS URI,则代理不应递归到非SIPS URI.如果
代理在3xx响应中的所有联系人上递归,代理不应将生成的非接触响应添加到响应上下文中.
在将响应添加到响应上下文之前删除联系人会阻止上游的下一个元素重试此代理已尝试的位置.
3xx响应可能包含SIP,SIP和非SIP URI的混合物.代理可以选择在SIP和SIPS URI上递归,并将剩余部分放入要返回的响应上下文中,可能在最终响应中.
如果代理收到416(不支持的URI方案)对请求的响应,该请求的Request-URI方案不是SIP,但原始接收请求中的方案是SIP或SIPS(即,代理在代理请求时将方案从SIP或SIPS更改为其他内容),则代理应向目标集添加新的URI.此URI应该是刚刚尝试的非SIP URI的SIP URI版本.在tel URL的情况下,这是通过将tel URL的电话订户部分放入SIP URI的用户部分,并将主机部分设置为发送先前请求的域来实现的.有关从tel URL形成SIP URI的更多详细信息,请参见第19.1.6节.
与3xx响应一样,如果代理通过尝试SIP或SIPS URI在416上"递归",则不应将416响应添加到响应上下文中.
5. 检查转发的响应
在服务器事务上发送最终响应之前,必须立即转发以下响应:
- 除100以外的任何临时响应(尝试)
- 任何2xx响应
如果收到6xx响应,则不会立即转发,但有状态代理应CANCEL第10节中所述的所有客户端挂起事务,并且不得在此上下文中创建任何新分支.
这与RFC 2543有所不同,RFC 2543要求代理立即转发6xx响应.对于INVITE事务,这种方法的问题是2xx响应可能到达另一个分支,在这种情况下,代理将
必须转发2xx.结果是UAC可能会收到一个6xx响应,然后是一个2xx响应,这是绝对不允许发生的.根据新规则,在收到6xx后,代理将发出CANCEL request,这通常会导致所有未完成的客户交易的487个响应,然后在该点上,6xx被转发到上游.
在服务器事务上发送最终响应后,必须立即转发以下响应:
- 对INVITE request的任何2xx响应
有状态代理不能立即转发任何其他响应.特别是,有状态代理不能转发任何100(尝试)响应.如步骤"将响应添加到上下文"中所述,已经收集了那些作为"最佳"响应转发的候选响应.
选择用于立即转发的任何响应必须按照步骤"聚合授权头字段值"通过"Record-Route"进行处理.
此步骤与下一步相结合,确保有状态代理将向非INVITE请求转发一个最终响应,并向INVITE请求转发一个非2xx响应或一个或多个2xx响应.
6. 选择最佳响应
如果上述规则没有立即转发最终响应,并且此响应上下文中的所有客户端事务都已终止,则有状态代理必须向响应上下文的服务器事务发送最终响应.
有状态代理必须在响应上下文中接收和存储的响应中选择"最佳"最终响应.
如果上下文中没有最终响应,则代理必须向服务器事务发送408(请求超时)响应.
否则,代理必须转发来自存储在响应上下文中的响应的响应.如果上下文中存在任何响应,则必须从6xx类响应中进行选择.如果不存在6xx类响应,则代理应从响应上下文中存储的最低响应类中进行选择.代理可以选择所选类中的任何响应.代理应该
优先选择提供影响此请求重新提交的信息的响应,如401,407,415,420和484(如果选择了4xx类).
接收503(服务不可用)响应的代理不应将其转发到上游,除非它可以确定它可能代理的任何后续请求也将生成503.换句话说,转发503意味着代理知道它不能服务于任何请求,而不仅仅是针对生成503的请求中的Request-URI的请求.如果收到的唯一响应是503,则代理应生成500响应并将其转发到上游.
转发的响应必须按照步骤"聚合授权头字段值"通过"Record-Route"进行处理.
例如,如果代理将请求转发到4个位置,并收到503,407,501和404响应,则它可以选择转发407(需要Proxy-Authenticate)响应.
对话的建立可能涉及1xx和2xx响应.当请求不包含To标记时,UAC使用响应中的To标记来区分对对话创建请求的多个响应.如果请求不包含标记,则代理不得将标记插入1xx或2xx响应的To头字段.代理不得修改1xx或2xx响应的To header字段中的标记.
由于代理可能不会将标记插入到对不包含标记的请求的1xx响应的To header字段中,因此它无法自行发出非100个临时响应.但是,它可以将请求分支到与代理共享相同元素的UAS.此UAS可以返回自己的临时响应,与请求的发起人进入早期对话.UAS不必是来自代理的谨慎流程.它可以是在与代理相同的代码空间中实现的虚拟UAS.
3-6xx响应逐跳发送.当发出3-6xx响应时,该元素有效地充当UAS,通常根据从下游元素收到的响应发出自己的响应.当简单地将3-6xx响应转发给不包含To标记的请求时,元素应该保留To标记.
代理不得在对包含To标记的请求的任何转发响应中修改To标记.
如果代理在转发的3-6xx响应中替换了to标记,则对上游元素没有影响,但保留原始标记可能有助于调试.
当代理聚合来自多个响应的信息时,从其中选择To标记是任意的,并且生成一个新的To标记可能会使调试更容易.例如,当组合401(未经授权)和407(需要Proxy-Authenticate)挑战,或组合来自未加密和未经验证的3xx响应的联系人值时,就会发生这种情况.
7. 聚合授权头字段值
如果选择的响应是401(未经授权)或407(需要Proxy-Authenticate),则代理必须从所有其他401(未经授权)和407(需要Proxy-Authenticate)收集任何WWW-Authenticate和Proxy-Authenticate头字段值在此响应上下文中接收到的响应,并在转发之前将它们添加到此响应中,而不进行修改.生成的401(未经授权)或407(需要Proxy-Authenticate)响应可能具有多个WWW Authenticate和Proxy Authenticate头字段值.
这是必要的,因为请求转发到的任何或所有目的地都可能具有请求的凭据.客户端需要接收所有这些挑战,并在重试请求时为每个挑战提供凭据.第26节提供了这种行为的动机.
8. 记录路线
如果所选响应包含此代理最初提供的Record-Route头字段值,则代理可以选择在转发响应之前重写该值.这允许代理为自己向下一个上游和下游元素提供不同的URI.代理可以出于任何原因选择使用此机制.例如,它对于多宿主主机非常有用.
如果代理通过TLS接收到请求,并通过非TLS连接将其发送出去,则代理必须将Record-Route头字段中的URI重写为SIPS URI.如果代理通过非TLS连接接收到请求,并通过TLS发送出去,则代理必须将Record-Route头字段中的URI重写为SIP URI.
代理提供的新URI必须满足对请求中Record-Route头字段中的URI的相同约束(参见第16.6节的步骤4),并进行以下修改:
URI不应该包含传输参数,除非代理知道后续请求路径中的下一个上游(相对于下游)元素支持该传输.
当代理决定修改响应中的Record-Route头字段时,它执行的操作之一是定位它插入的Record-Route值.如果请求是螺旋式的,并且代理在螺旋式的每次迭代中插入了一个Record-Route值,那么在响应中定位正确的值(必须是反向的正确迭代)是很困难的.上述规则建议希望重写Record-Route头字段值的代理在Record-Route头字段中插入足够不同的URI,以便选择正确的URI进行重写.实现这一点的推荐机制是代理将代理实例的唯一标识符附加到URI的用户部分.
当响应到达时,代理修改标识符与代理实例匹配的第一条Record-Route.修改会产生一个URI,该URI的用户部分没有附加这段数据.在下一次迭代中,相同的算法(使用参数查找最上面的Record-Route头字段值)将正确提取该代理插入的下一个Record-Route头字段值.
对于代理向其添加Record-Route头字段值的请求,并非每个响应都包含Record-Route头字段.如果响应确实包含Record-Route头字段,则它将包含代理添加的值.
9. 正向响应
在通过"Record-Route"执行步骤"聚合授权头字段值"中描述的处理之后,代理可以对所选响应执行任何特定于功能的操作.代理不得添加,修改或删除邮件正文.除非另有规定,否则代理不得删除除第16.7节第3项中讨论的Via 头字段值以外的任何头字段值.特别是,代理不得删除任何"received"参数
在处理与此响应关联的请求时,它可能已添加到下一个 Via header字段值.代理必须将响应传递给与响应上下文关联的服务器事务.这将导致响应被发送到当前最顶部的Via 头字段值中指示的位置.如果服务器事务不再可用于处理传输,则元素必须通过将响应发送到服务器传输来无状态转发响应.服务器事务可能指示发送响应失败或在其状态机中发出超时信号.这些错误将被记录下来,以便进行适当的诊断,但协议不需要代理采取补救措施.
代理必须维护响应上下文,直到其所有关联事务终止,即使在转发最终响应之后也是如此.
10. 生成CANCEL
如果转发的响应是最终响应,则代理必须为与此响应上下文关联的所有挂起的客户端事务生成CANCEL request.当代理收到6xx响应时,还应该为与此响应上下文关联的所有挂起的客户端事务生成CANCEL request.挂起的客户端事务是一个已收到临时响应但没有最终响应(它处于继续状态)且未生成相关CANCEL的事务.第9.1节描述了生成CANCEL request.
在转发最终响应时CANCEL挂起的客户端事务的要求并不保证端点不会收到对INVITE的多个200(OK)响应.在发送和处理CANCEL request之前,可以在多个分支上生成200(确定)个响应.此外,可以合理预期,未来的扩展可能会覆盖此要求以发出CANCEL request.
16.8 处理计时器C
如果触发计时器C,则代理必须使用其选择的任何值重置计时器,或终止客户端事务.如果客户端事务已收到临时响应,则代理必须生成与该事务匹配的CANCEL request.如果客户端事务尚未收到临时响应,则代理必须表现为事务收到408(请求超时)响应.
允许代理重置计时器允许代理在计时器启动时根据当前条件(如利用率)动态延长事务的生存期.
16.9 处理传输错误
如果传输层在尝试转发请求时向代理通知错误(参见第18.4节),则代理的行为必须与转发的请求收到503(服务不可用)响应的行为相同.
如果代理在转发响应时收到错误通知,它将丢弃响应.由于此通知,代理不应CANCEL与此响应上下文关联的任何未完成的客户端事务.
如果代理CANCEL其未完成的客户端事务,则单个恶意或行为不当的客户端可通过其Via 头字段导致所有事务失败.
16.10 CANCEL处理
有状态代理可以随时生成对其生成的任何其他请求的CANCEL(前提是收到第9.1节所述的对该请求的临时响应).代理收到匹配的CANCEL request时,必须CANCEL与响应上下文关联的任何挂起的客户端事务.
有状态代理可以根据INVITE的Expires头字段elapsing中指定的时间段,为挂起的INVITE客户端事务生成CANCEL request.然而,这通常是不必要的,因为所涉及的端点将负责发出事务结束的信号.
当CANCEL request由其自己的服务器事务在有状态代理中处理时,不会为其创建新的响应上下文.相反,代理层在其现有响应上下文中搜索处理与此CANCEL关联的请求的服务器事务.如果找到匹配的响应上下文,元素必须立即返回一个200(OK)响应到CANCEL请求.在这种情况下,该元素充当第8.2节中定义的用户代理服务器.此外,元素必须为第16.7节步骤10中描述的上下文中的所有挂起的客户端事务生成CANCEL request.
如果未找到响应上下文,则元素不知道要对其应用CANCEL的请求.它必须无状态地转发CANCEL request(它以前可能无状态地转发了关联的请求).
16.11 无状态代理
在无状态操作时,代理是一个简单的消息转发器.无状态行为时执行的许多处理与有状态行为时相同.这里详细介绍了这些区别.
无状态代理没有任何事务或用于描述有状态代理行为的响应上下文的概念.相反,无状态代理直接从传输层接收消息,包括请求和响应(参见第18节).因此,无状态代理不会自行重新传输消息.但是,它们确实转发接收到的所有重传(它们无法区分重传与原始消息).此外,当以无状态处理请求时,元素不得生成自己的100(Trying)或任何其他临时响应.
无状态代理必须验证第16.3节所述的请求
无状态代理必须遵循第16.4节至第16.5节中描述的请求处理步骤,但以下情况除外:
o 无状态代理必须从目标集中选择一个且仅选择一个目标.此选择只能依赖于消息中的字段和服务器的时间不变属性.特别是,每次处理重新传输的请求时,都必须将其转发到相同的目的地.此外,CANCEL和非路由ACK请求必须生成与其关联的INVITE相同的选择.
无状态代理必须遵循第16.6节中描述的请求处理步骤,但以下情况除外:
o 跨空间和时间的唯一分支ID要求也适用于无状态代理.但是,无状态代理不能简单地使用随机数生成器来计算分支ID的第一个组件,如第16.6.8节所述.这是因为请求的重新传输需要具有相同的值,并且无状态代理无法将重新传输与原始请求区分开来.因此,每次转发重新传输的请求时,使其唯一的分支参数的组件必须相同.因此,对于无状态代理,分支参数必须作为消息参数的组合函数进行计算,这些参数在重新传输时是不变的.
无状态代理可以使用它喜欢的任何技术来保证其分支ID在事务中的唯一性.但是,建议采用以下程序.代理检查接收到的请求的最顶端Via 头字段中的分支ID.如果它以magic cookie开头,则传出请求的分支ID的第一个组件将被计算为接收到的分支ID的哈希.否则,分支ID的第一个组件将被计算为最顶端的Via,To头字段中的标记,From头字段中的标记,Call-ID头字段,CSeq号的哈希(但不是方法),以及来自接收到的请求的Request-URI.这些字段中的一个字段在两个不同的事务中总是不同的.
o 第16.6节中规定的所有其他消息转换必须导致对重新传输的请求进行相同的转换.特别是,如果代理插入Record-Route值或将URI推入路由头字段,则必须在请求的重新传输中放置相同的值.至于Via 分支参数,这意味着转换必须基于请求的时不变配置或重传不变属性.
o 无状态代理确定在何处转发请求,如第16.6节第10项中有状态代理所述.请求直接发送到传输层,而不是通过客户端事务.
由于无状态代理必须将重新传输的请求转发到相同的目标,并向每个请求添加相同的分支参数,因此它只能使用来自消息本身的信息和用于这些计算的时不变配置数据.如果配置状态不是时不变的(例如,如果更新了路由表),则在与更改之前或之后的事务超时窗口相等的时间间隔内,可能受更改影响的任何请求都不能无状态转发.在该时间间隔内处理受影响请求的方法是实现决策.一个常见的解决方案是以事务状态转发它们.
无状态代理不能对CANCEL request执行特殊处理.它们与任何其他请求一样,按照上述规则进行处理.特别是,无状态代理应用与应用于任何其他请求相同的路由头字段处理来CANCEL request.
第16.7节中描述的响应处理不适用于无状态行为的代理.当响应到达无状态代理时,代理必须检查第一个(最上面的)Via 头字段值中的send by值.如果该地址与代理匹配(它等于此代理已插入到以前请求中的值),则代理必须从响应中删除该头字段值,并将结果转发到下一个Via 头字段值中指示的位置.代理不得添加,修改或删除邮件正文.除非另有规定,否则代理不得删除任何其他头字段值.如果地址与代理不匹配,则必须以静默方式丢弃消息.
16.12 代理路由处理概述
如果没有相反的本地策略,则代理对包含路由头字段的请求执行的处理可总结为以下步骤.
1. 代理将检查Request-URI.如果它指示此代理拥有的资源,则代理将用运行位置服务的结果替换它.否则,代理将不会更改Request-URI.
2. 代理将检查最上面的路由头字段值中的URI.如果指示此代理,则代理会将其从路由头字段中删除(已到达此路由节点).
3. 代理将把请求转发到最上面的路由头字段值中的URI所指示的资源,或者如果不存在路由头字段,则转发到Request-URI中的URI所指示的资源.代理通过将[4]中的过程应用于该URI来确定转发请求时要使用的地址,端口和传输.
如果在请求的路径上没有遇到严格的路由元素,那么Request-URI将始终指示请求的目标.
16.12.1 例子
16.12.1.1 基本梯形
这个场景是基本的SIP梯形,U1->P1->P2->U2,两个代理都Record-Route.这里是流程图.
U1发送:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
到P1.P1是一个出站代理.P1不负责domain.com,所以它在DNS中查找并发送到那里.它还添加了一个Record-Route头字段值:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route: `<sip:p1.example.com;lr>`
P2得到了这个.它负责domain.com,因此它运行位置服务并重写Request-URI.它还添加了一个Record-Route头字段值.没有Route header字段,因此它解析新的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>`
u2的被调用方还将其对话状态的远程目标URI设置为sip:[email protected]其路线设置为:
(`<sip:p2.domain.com;lr>`,`<sip:p1.example.com;lr>`)
这由P2正常转发至P1至U1.现在,U1将其对话状态的远程目标URI设置为sip:[email protected]其路线设置为:
(`<sip:p1.example.com;lr>`,`<sip:p2.domain.com;lr>`)
由于所有路由集元素都包含lr参数,因此U1构造以下BYE请求:
BYE sip:[email protected] SIP/2.0
Route: `<sip:p1.example.com;lr>`,`<sip:p2.domain.com;lr>`
与任何其他元素(包括代理)一样,它使用DNS解析最顶层路由头字段值中的URI以确定发送请求的位置.这是P1.P1注意到它不负责Request-URI中指示的资源,因此它不会更改它.它确实看到它是Route header字段中的第一个值,因此它删除该值,并将请求转发给P2:
BYE sip:[email protected] SIP/2.0
Route: `<sip:p2.domain.com;lr>`
P2还注意到它不负责Request-URI所指示的资源(它负责domain.com,而不是u2.domain.com),因此它不会更改它.它确实会在第一个路由头字段值中看到自己,因此它会删除它并根据Request-URI的DNS查找将以下内容转发到u2.domain.com:
BYE sip:[email protected] SIP/2.0
16.12.1.2 遍历严格路由代理
在这种情况下,将跨四个代理建立一个对话,每个代理都会添加Record-Route头字段值.第三个代理实现RFC2543中指定的严格路由过程,许多工作正在进行中.
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>`
Record-Route: `<sip:p2.example.com;lr>`
Record-Route: `<sip:p1.example.com;lr>`
哪个U2用200 OK来回应.稍后,U2基于第一个路由头字段值向P4发送以下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不负责Request-URI中指示的资源,因此它将不处理该资源.它注意到它是第一个路由头字段值中的元素,因此将其删除.然后,它准备根据sip:p3.middle.com的now first Route header字段值发送请求,但它注意到此URI不包含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:
BYE sip:p2.example.com;lr SIP/2.0
Route: `<sip:p1.example.com;lr>`
Route: `<sip:[email protected]>`
P2认为Request-URI是它放在Record-Route头字段中的一个值,因此在进一步处理之前,它将请求重写为:
BYE sip:[email protected] SIP/2.0
Route: `<sip:p1.example.com;lr>`
P2不负责u1.example.com,因此它根据路由头字段值的解析将请求发送给P1.
P1在最顶端的Route header字段值中注意到自己,因此将其删除,从而导致:
BYE sip:[email protected] SIP/2.0
由于P1不负责u1.example.com,并且没有路由头字段,P1将根据Request-URI将请求转发到u1.example.com.
16.12.1.3 重写Record-Route头字段值
在这个场景中,U1和U2位于不同的私有名称空间中,它们通过代理P1进入一个对话,代理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(正常)发送回P1:
SIP/2.0 200 OK
Contact: `<sip:[email protected]>`
Record-Route: `<sip:gateway.rightprivatespace.com;lr>`
P1重写其Record-Route标头参数,以提供U1将发现有用的值,并将以下内容发送给U1:
SIP/2.0 200 OK
Contact: `<sip:[email protected]>`
Record-Route: `<sip:gateway.leftprivatespace.com;lr>`
稍后,U1向P1发送以下BYE请求:
BYE sip:[email protected] SIP/2.0
Route: `<sip:gateway.leftprivatespace.com;lr>`
哪一个P1转发给U2作为:
BYE sip:[email protected] SIP/2.0