XMPP之流磋商过程
版权所有,转载请注明出处:http://guangboo.org/2013/03/08/xmpp-stream-negotiate-precess
由于接收端是作为其所服务域的守护者,它会对连接来的客户端提出一些条件。至少,在接收端在接收请求端发送来的XML节点前,需要对请求方进行身份验证。然而,接收方也可能要考虑一些其他比身份验证更有强制协商性的条件,如采用TLS加密通讯。当然接收方会通知请求方,提出自己的条件,这个通知是采用“流特性”的方式进行的---这是请求方在接收方接收其发出的XML节点前,必须要完成的一组协议交互过程,当然有些协议交互过程是自愿的,但有些可能会优化XML流的处理过程(如在应用层建立压缩机制)。
有了这些连接条件,也就意味着协商的必要性。由于TCP, TLS和SASL等都有先后顺序,那也就表示协商也要分阶段处理。有两个因素决定了这个处理顺序:(1)是否提供一个流特性,要看实体是否需要,即可能只有某些实体才依赖这个特性;也可能依赖于某些其他特性的协商,就是说在某些其他特性协商后才能提供该流特性(如资源绑定只有在SASL验证之后才能提供);(2)流特性可以是强制协商的,也可以是自愿协商的;最后,出于安全原因,流的各部分需要在完成对某些特性的交互的定义后,丢弃一些在协商过程中得到的能力(如规范中定义的SASL验证机制,当通过SASL验证完毕后,TLS连接即被丢弃和而SASL在安全层建立时的SASL)。这一般要在当前TCP连接中,通过重新发送初始化流来完成。
流特性格式
如果请求方发生的初始化流中包含version属性,并且属性值只是是1.0,那么接收方在发送完反馈的初始化流之后,必须发送<features/>子节点,以通知请求方完成协商过程所需要的条件。条件是以<features/>的子节点的形式发送的,每个子节点表示一个条件。<features/>可以包含一个子节点,多个子节点,也可以不包含任何节点,子节点的顺序不做要求。
如果一个特定的流特性是可以强制协商的,那么它的定义就需要完成以下一种操作:
- 特性本身就是被要求为强制协商的(如XMPP客户端的资源绑定);或者
- 在一次交互过程中,为接收方指定一种方式来标记特性是强制协商的(如,STARTTLS,它是通过在该流特性中添加一个空的<required/>节点来实现的,但这不是所有特性都可以使用这样的方式);对于新的强制协商特性来说,还是推荐像STARTTLS这样,在特性节点中添加<required/>节点来实现。
由于没有通用的格式或方式来标记特性是否为强制性的,接收方也无法做出正确的判断,从而导致无法完成流协商。但是,尽管存在这样的问题,但是XMPP协议工作组认为这样的情况是很罕见的,因此没有必要寻找通用的解决方案。
出于安全原因,某些流特性在完成流协商后,有必要要求请求方重新发送初始化流(如TLS,及 在安全层建立时的SASL特性)。如果一个特性是这样的,那么它的定义就需要体现出“协商完后,需要重启”的特征。
如果<features/>节点中只是还包含一个强制协商的特性,那么就表示流协商并没有完成,就需要请求方必须进一步对特性进行协商。如:
R: <stream:features> <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'> <required/> </starttls> </stream:features>
<features/>节点可能包含多个强制协商的特性,就意味着请求方可以在一次协商阶段中,从这些特性中从中做出选择。例如,未来可能有一项技类似于TLS的技术,那么接收方就可能需要在同一协商阶段支持TLS和这项技术。但这只适用于给定的一个协商阶段,而不适用于强制协商的不同阶段(如,接收方不需要STARTTLS和SASL都是强制协商的,或SASL和资源绑定是强制协商的,因为TLS是在SASL之前就需要协商的,SASL在资源绑定之前就需要协商的)。
如一个<features/>节点既包含强制协商特性也包含自愿协商特性,可以判断协商没有完成,但是请求方可能需要先完成自愿协商的特性,然后在试着协商强制特性。如:
R: <stream:features> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> <compression xmlns='http://jabber.org/features/compress'> <method>zlib</method> <method>lzw</method> </compression> </stream:features>
如一个<features/>节点只包含自愿协商的特性,那么就意味着协商已经完成,请求方就可以开始发生XML节(message, presence, iq等)了,但是如果请求反需要改特性的话,也可以进一步协商这些特性。如:
R: <stream:features> <compression xmlns='http://jabber.org/features/compress'> <method>zlib</method> <method>lzw</method> </compression> </stream:features>
如果<features/>节点时空的,那么就表示协商完成,请求方可以发生XML节(message, presence, iq等)了。如:
R: <stream:features/>
重启
特性协商成功后有必要重启流,双方都必须考虑替换之前的流,但一定不能发送关闭流</stream>,也一定不能终止当前TCP连接;相反的,双方一定要重用当前连接,当然连接可能出于新的状态下(如,由于TLS协商的缘故,连接已被加密)。请求方必须发送新的初始化流,接收方接到新的初始化流后,在发送反馈流之前,必须生成新的流ID(一定不能重用之前的老ID)。
重发特性
流重启之后,接收方必须发送一个更新后的流特性列表,如果没有特性需要进一步协商,或者可能只有特性的组合的话,更新后的特性列表可能就是空的。
协商完成
接收方通过给请求方发送空的<features/>节点,或只包含自愿协商的特性的<features/>节点的方式,来决定协商的完成。之后,请求方可能发送一个空的<features/>节点,但一定不能发送其他附加的特性(如果接收方有新的特性需要发送的话,最好限制在强制协商或紧急的安全特性上,那么可以通过发送关闭流,并带一个<reset/>的错误节点,然后在请求方重新连接时处理新特性。最好通过交叉的方式关闭现有的流,避免所有的初始实体都一起重连)。流协商完成后,请求方就可以发送XML节(message, presence, iq等)了。
需要注意的是,由于历史原因,资源绑定不适用于上述规则,因为它是客户端使用XML节来实现强制协商的。
请求方在协商完成之前,一定不能尝试向其他实体或连接的服务器发送XML节(message, presence, iq等)。即使请求方发送了,接收方也一定不能接收,而一定要关闭流,并带<not-authorized/>流错误。该规则只适用于XML节(即<message/>, <presence/>和<iq/>等内容命名空间限定的节点),而不适用于流协商时使用的XML节点(如,用于完成TLS和SASL协商的节点)。