传输层负责请求和应答在网络上的实际传输。这包括了在面向连接的通讯方式下的请求和应答所使用的连接管理。
传输层负责管理像TCP/SCTP之类通讯协议的长连接,或者在这些协议上的TLS连接,并且包括管理打开这些连接的使用者的管理。这包括了客户端或者服务端传输层打开的连接,这样在客户端服务端通讯函数可以共享这些连接。这些连接采用一组由远端的地址、端口、通讯协议的组合来索引。当传输层打开了一个连接,这个连接的索引就设置成为目的IP、端口、还有打开这个连接的通讯层的实例。当传输层受理了一个连接,那么这个连接的索引就被设置成为连接方的源IP地址、port、还有传输层的实例。注意,由于源端口通常是临时创建的,并且通过附件[4]的步骤不能知道它是临时创建的还是配置的,所以传输层被动接受的连接通常是不被重复使用的。这就是说,如果两个proxy在一个“peering”(点对点)的关系中,使用一个面向连接的通讯协议通常有两个连接要使用,每个都是自己作为主动方连接的。
我们建议在实现中,当发送(或者接收)完最后一个消息之后,依旧维持这个连接一段时间(这段时间可以是自定义的)。这段时间应当是至少等于本节点的事务从创建到结束的最长时间。这是为了让事务能够在他们所创建的同一个连接上完成(比如,在这个连接上完成请求,应答的处理,在INVITE的情况下的给非2xx的ACK应答等等)。这通常意味着至少64×T1秒(参见17.1.1.1中关于T1的定义)。不过,如果当本程序的TU使用的是一个比较大的定时器C(参见16.6节11步)的时候,也可以选取一个比较大的值。
所有的SIP元素都必须实现基于UDP和TCP的通讯。SIP元素还可以实现其他的协议。
要求UA支持TCP是对RFC2543的一个重要改进。这是由于需要处理更大的消息,就像接下来讲到的那样,必须使用到TCP协议。因此,即使是SIP元素不要发送大的消息,但是由于它可能收到大消息并且处理这些消息,所以,要求支持TCP。
18.1 客户Clients
18.1.1 发送请求
传输层的客户端负责发送请求和接收应答。传输层的用户把请求交给传输层的实例进行处理,包括IP地址端口、传输层实例,还可能有多点广播的TTL。
如果请求的大小和MTU相差200个字节以内,或者它是大于1300字节的,并且路径MTU的大小是未知的,那么请求必须遵循RFC2914[43]的阻塞控制传输协议,比如使用TCP。如果这导致了Via最上边指定的通讯协议的改变,那么Via最上边的值就必须也随之改变。这使得在UDP传输上的消息被分割,并且也提供了大消息的传输阻塞控制。不过,在实现上,必须能够支持达到最大包大小的消息的处理。对于UDP来说,包含了IP和UDP头的大小是65535个字节。
在消息的大小和MTU之间的200个字节的”buffer”,提供了一个机制使得在SIP的应答中,可以超过请求的大小。比如在INVITE请求的应答中,增加了Record-Route头域值。有了这个额外的buffer,应答可以大概比请求大170个字节,而且在Ipv4上不用进行分块传输(假设没有IPSec,大概IP/UDP会使用30个字节)。当MTU是未知的时候,选取1300是基于假设Ethernet的MTU是1500字节的基础上。
如果SIP元素是因为消息大小的限制,所以基于TCP发送一个请求,并且消息如果不是因为大小的限制,会使用UDP来发送,并且如果建立连接产生一个ICMP 协议不支持的错误,或者导致TCP reset,那么这个元素就应当用UDP重试这个请求。这只是为了向后兼容RFC 2543针对不支持TCP的实现。在本规范以后的改动中,这部分内容会有修订。
如果客户端向多个地址发送请求,那么必须增加“maddr”参数到Via头域值上,并且这个参数值指定多个目的地址,对于Ipv4来说,应当增加“ttl”参数=1,IPV6的多点传送在本规范中没有定义,会在后续的标准中描述。
这些规则定义了SIP的多点传送。首要的目的是为了提供“寻找最接近的单点”服务,这个服务将请求转发到一组类似的服务器,并且只需要处理其中任意一个服务器的应答。这个功能主要用于注册服务。实际上,基于17.1.3的事务处理规则,客户端事务会接收第一个应答,并且因为其他应答包含同样的Via的branch参数,而视这些应答为重发应答。
在请求发送之前,客户端传输层必须在Via头域中增加一个”sent-by”参数。这个字段包含了一个IP地址或者主机名、端口。我们推荐使用FQDN方法描述这个主机名。这个字段在某些特定情况下,用于发送应答。如果端口不存在,缺省的值依赖于通讯协议。对于UDP、TCP和SCTP来说是5060,TLS是5061。
对于可靠传输协议,应答通常简单的通过连接发送,并且这个连接是收到对应请求的连接。因此,客户端传输层必须准备在发出请求的同一个连接上接收应答。在出现错误的情况下,服务端可能会尝试新建立一个连接来发送应答。为了能够处理这种情况,传输层必须准备接收一个从源IP建立的新连接,这个连接的IP是请求发起的源IP,port是在“sent-by”字段中指定的port。这也同样要求准备接收从任意地址和端口来的新连接上接收应答,这个端口是由服务器根据附件[4]的5节所讲述的步骤来选取的。
对于非可靠的传输协议,客户端传输层必须准备从发送请求的那个原始IP地址上接收应答。(因为应答会送到原始地址去),并且端口号是在“sent-by”字段的端口号。进一步说,和可靠传输一样,在某些情况下,应答会发往不同的地方。客户端必须能够准备从其他地址和端口上接收应答,这个端口是由服务器根据附件[4]的5节所讲述的步骤来选取的。
对于多点传送的情况来说,客户端传输层必须准备从相同的多点传输组上接收应答,这个组的地址和端口和发出请求的组相同(就是说,它必须是发送请求的那个多点传输组的一个成员)。
如果请求发送的目的IP地址、端口和transport都和现有的一个连接相同,那么建议使用这个连接来发送请求,同时也允许新建立一个连接来发送。
如果请求通过多点发送,那么它发送的一组地址,端口和TTL都是由传输层的用户提供。如果请求是通过不可靠通讯协议发送,那么发送的IP地址和端口也是由传输层的用户提供。
18.1.2 接收应答
当接收到应答的时候,客户端传输层检查最上的Via头域值。如果“sent-by”参数不符合客户端传输层在请求中插入的值,那么这个应答必须悄悄丢弃。
如果已经有客户端事存在,客户端传输层使用17.1.3的步骤来匹配现存的事务和这个接收到的应答。如果匹配到了,应答必须交给事务层进行处理。否则,应答必须交给核心去处理(无论是有状态的proxy,还是无状态的proxy,还是UA的核心)。处理这些“stray”(迷路)的应答取决于核心(如果是proxy就会转发,如果是UA就会忽略,等等)。
18.2 服务端
18.2.1 接收请求
一个服务器应当能够接收从任何IP地址、端口和协议的组合上过来的请求。这个组合是通过对(发布)的与服务器通信的SIP或者SIPS URI(附件[4])进行DNS查找得到的。在这里(发布)包含了在REGISTER请求或者转发应答的Contact头域中设置的URI,或者在请求或者应答的”Record-Route”头域中设置的URI。这个URI可以通过放在网页或者名片上被(发布)。同样的,我们也建议服务器在公网上监听缺省的SIP端口(TCP/UDP是5060,5061是在TCP上的TLS)。如果是在局域网上,或者私网上,或者一个物理服务器上运行好几个服务实例,那就很自然的可以设置成不同的。对于服务器监听UDP的任何端口和界面,都必须在TCP上也进行同样的监听。这是因为可能消息还需要通过TCP进行传输,比如消息过大的情况。但是,反过来就不需要了。如果一个服务器在TCP监听了,那么它不一定需要在UDP上也进行相应的监听。当然服务器也可以因为某些原因在特定地址和端口上监听UDP。当服务端事务从任意一个传输层上接收到请求的时候,它必须检查最上的Via头域的“sent-by”参数。如果“sent-by”参数的主机部分包含了一个主机名,或者它包含的IP地址和包的源地址不同,服务器必须增加一个“received”参数到这个Via头域值中。这个参数必须包含收到的包的原地址。由于服务端必须把应答发送给收到请求的那个源IP地址,所以这个可以用来帮助服务端传输层发送应答。
一个服务端传输层收到的请求可能是这样的(部分):
INVITE sip:bob@Biloxi.com SIP/2.0
Via: SIP/2.0/UDP bobspc.biloxi.com:5060
请求是从源IP:192.0.2.4收到的。在请求转交到上层之前,传输层增加了一个”received”参数,这样请求的部分就是:
INVITE sip:bob@Biloxi.com SIP/2.0
Via: SIP/2.0/UDP bobspc.biloxi.com:5060;received=192.0.2.4
接着,服务端传输层尝试和服务端事务做匹配。这个使用的是17.2.3节定义的规则。如果匹配上一个服务端事务,那么请求就交给那个事务去处理。如果没有匹配到事务,请求就交给核心去处理,可能会创建一个新的服务端事务来处理。注意当UAS核心给INVITE请求发送一个2xx应答的时候,服务端事务已经销毁了。这就是说,当ACK收到的时候,不会有匹配的服务端事务,并且基于这个规则,ACK回交给UAS核心来处理。
18.2.2 发送应答
服务端事务使用最上边的Via头域值来决定把应答发送到哪里。它必须遵从如下步骤来发送:
o 如果“sent-protocol”是一个可靠的传输协议比如TCP或者SCTP,或者在其上的TLS,应答必须用现存的到原始请求(创建这个事务的请求)的连接进行发送(如果连接还存在的情况下)。这个要求服务端传输层保留服务端事务和传输层连接的相关性。如果连接不存在了,服务端应当创建一个新的连接,如果存在“received”参数,就用对应的在“received”参数中指定的IP地址。如果存在“sent-by”参数,那么就用”sent-by”指定的port,如果不存在,那么就用缺省的port。如果对应的连接已经失效,那么服务器应当采用附件[4]的步骤来决定使用那个IP地址和端口来建立连接并且发送应答。
o 否则,如果Via头域包含一个“maddr”参数,就必须把应答转发到maddr所指明的地址,并且使用“sent-by”所指定的端口,如果没有“sent-by”参数,那么就使用5060缺省参数。如果地址是一个多点地址,应答应当使用”ttl”参数所指定的TTL,或者如果没有指定“ttl”参数,则使用TTL=1的参数。
o 否则(对于非可靠传输),如果Via的最上头域包含一个“received”参数,那么应答必须发送到“received”参数所指定的地址,并且使用“sent-by”所指定的端口,如果没有“sent-by”参数,那么就使用5060缺省参数。如果这步失败了,比如,如果得到一个ICMP“端口不能到达”的错误,那么就应当根据附件[4]的第5节的步骤来决定应当把应答发送到哪里。
o 否则,如果没有“receiver”标记,那么应答应当使用附件[4]的第5节指定的步骤,送到“sent-by”参数指定的地址。
18.3 分块
在面向消息的通讯协议中(比如UDP),如果消息有一个Content-Length头域,那么消息体就有可能包含很多字节。并且收到的包中除了这个消息体的Content-Length字节意外,还有传输层附加的通讯包字节,那么这部分额外的字节应当被丢弃。如果通讯包在没有收到完整的Content-Length字节时消息体就终止了,这就意味着出错了。如果这个消息是一个应答,那么这个消息必须被丢弃。如果消息是一个请求,那么本程序应当给出一个400(Bad Request)应答。如果消息没有包含一个Content-Length头域,消息体的结束点就是消息体的结束点。
在面向流的通讯协议中(比如TCP),Content-Length头域标志这包体的大小。在面向流的通讯协议中,必须使用Content-Length字段。
18.4 错误处理
错误的处理取决于出现错误的消息是请求还是应答。
如果传输层的用户要求在一个非可靠传输协议上发送一个消息,并且结果是一个ICMP错误,那么错误处理的方法依赖于ICMP错误类型。当传输层遇到主机、网络、端口或者协议无法到达的错误,或者参数错误的时候,应当通知传输层的用户发送失败。Source quench和TTL exceeded ICMP错误应当被忽略。
如果传输层用户要求在一个可靠传输协议上发送一个请求,并且结果是一个连接错误,传输层应当通知传输层用户这个发送错误