前言
前段时间产品需要实现IM功能,之前对IM的理解停留在 smack,xmpp,ejabberd 等概念。为了实现功能只能去研究xmpp,也在网上搜索了很多资料,但都无法把查询的信息串起来,正巧ejarberd服务器已经搭建完毕,于是就跑一下登录的流程,从抓包和日志去分析学习xmpp
环境
- 1,ejarberd服务器己搭建完毕,并正常工作。
- 2,引入了smack框架,Android项目地址:
项目github
登录流程详解
- 抓包如下:
- 详细描述(对上图中换47条包,逐条分析)
packet 1,2,3:tcp 三次握手
--------------------------------------------
packet 4: client to server:
<stream:stream
xmlns='jabber:client'
to='**.***.com'
xmlns:stream='http://etherx.jabber.org/streams'
version='1.0'
from='hanguojing0724@***.***.com'
xml:lang='en'>
标注及理解:建立tcp 连接后,client主动发第一条xmpp消息
client 发送一个流头(发起流头),打开到server 实
的流。
-----------------------------------------------------
packet 5: server to client ack (TCP)
-------------------------------------------------------
packet 6: server to client xmpp 消息:
<?xml version='1.0'?>
<stream:stream
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
id='716191580'
version='1.0'
from='**.***.com'
xml:lang='en'>
标注及理解:
server 发送一个流头(应答流头)回复 client(相对发起流头,多了一个id属性:流ID)
xmlns='jabber:client' : 内容命名空间,常见两种:jabber:client、jabber:server分别用于client-server,server-server通信,区别:jabber:client命名空间,xml流发送的节中,'to','from'属性是可选 的,而在jabber:server中,是必需的。
xmlns:stream='http://etherx.jabber.org/streams' 流命名空间,必须由:http://etherx.jabber.org/streams来限定,否则接收流实体以一个流错误来关闭流
id='716191580' 相对发起流头,多了id属性: id属性是流唯一标识符,称为流ID,必须是接收实体在发送应答流头时生成,并且在接收的应用内部,所生成的流ID必须是唯一的
version='1.0' :传达了对文本定义的流相关换协议支持信号,相关协议包括:tls协商,sasl协商,流特征,流错误 ;xmpp 1.0 封装了流相关协议以及三个定义的xml节点类型的基本语义(message,presence,iq)
xml:lang='en' // xml:lang属性,指定一个实体在该流上发送任何可读xml字符串数据的首选或缺省语言
-------------------------------------------------------
packet 7: server to client xmpp 消息:
<stream:features>
<register xmlns='http://jabber.org/features/iq-register'/>
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<mechanism>PLAIN</mechanism>
<mechanism>DIGEST-MD5</mechanism>
<mechanism>X-OAUTH2</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
</mechanisms>
</stream:features>
标注及理解:
接收方实体在发送应答流头之后,必须发送一个<features/> 子元素,给发起方实体以声明使流协商过程继续下去的任何条件,每个条件用<features/>元素的子元素的格式,由一个不同于流命名空间和内容命名空间的命名空间来限定。<features/>可以包含一个,多个子元素,或者为空。上面消息中包含 tls ,sasl 信息。
<starttls> 保护流的安全,使其免于被篡改和窃听,这个通道加密的方法使用传输层安全的TLS协议
如果接收方实体包含了<required/>子元素,双方必须tls强制协商。
在Tls协商之后,双方必须重启这个流。
数据格式:starttls协商时,实体不能在xml元素之间发送任何空格、符号。
如果初始化实体选择使用TLS,Starttls协商必须在SASL协商之前完成。
-------------------------------------------------------
packet 8,9: client to server tcp ack (Tcp)
-------------------------------------------------------
packet 10: client to server xmpp <starttls> 请求tls加密协商:
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>
------------------------------------------------------
packet 11: server to client xmpp 响应tls加密协商
<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
收到该条消息后,接下来开始tls加密协商(非xmpp协议)
----------------------------------------------------
packet 12,13,14,15: tls密钥协商:
----------------------------------------------------
packet 16: client to server xmpp (wireshark 抓包是加密的)
<stream:stream xmlns='jabber:client'
to='**.***.com'
xmlns:stream='http://etherx.jabber.org/streams'
version='1.0'
from='hanguojing0724@***.***.com'
xml:lang='en'>
标注及理解:
加密协商成功后,发起实体重新发起流头创建新加密流
-------------------------------------------------------
packet 17: server to client xmpp 消息:
<?xml version='1.0'?>
<stream:stream xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
id='2434506968'
from='**.***.com'
version='1.0' xml:lang='en'>
标注及理解:
接收实体响应流头,分配流id(唯一标识)
------------------------------------------------------
packet 18: servet to client xmpp 消息
<stream:features>
<register xmlns='http://jabber.org/features/iq-register'/>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<mechanism>PLAIN</mechanism>
<mechanism>DIGEST-MD5</mechanism>
<mechanism>X-OAUTH2</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
</mechanisms>
</stream:features>
标注及理解:
tls加密完成后,流特征中,不在包含starttls,但还需要sasl
----------------------------------------------------
packet 19:client to server ack (tcp)
------------------------------------------------
packet 20:client to server xmpp 消息:
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'>=</auth>
标注及理解:
开始sasl协商
发送初始化序列:从feature实体列表中选择一个方法并放在<auth/>元素的mechanism属性的值发送给接收实体,可选择包含一个初始化应答(“=”)以避免多一个来回
-------------------------------------------------------
packet 21: server to client ack (tcp):
---------------------------------------------------
packet 22: servet to client xmpp消息:<challenge>
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
bm9uY2U9IjIzNzY0NTY0NDgiLHFvcD0iYXV0aCIsY2hhcnNldD11dGYtOCxhbGdvcml0aG09bWQ1LXNlc3M=</challenge>
packet 23:client to server xmpp消息:<response>
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
dXNlcm5hbWU9Imhhbmd1b2ppbmcwNzI0IixyZWFsbT0iaW1zLjE4NmxpZmUuY29tIixub25jZT0iMjM3NjQ1NjQ0OCIsY25vbmNlPSI0R2tOdHVwbGowSmU3NHFpWjM1Mk5hb1owSElnOUk1TyIsbmM9MDAwMDAwMDEscW9wPWF1dGgsZGlnZXN0LXVyaT0ieG1wcC9pbXMuMTg2bGlmZS5jb20iLHJlc3BvbnNlPWI2ZWYxZDM2NDExZjY4YWNiOGM2OGE2MmRlMTY4NmVmLGNoYXJzZXQ9dXRmLTg=</response>
packet 24: server to client ack 消息:
pakcet 25: server to client xmpp 消息: <challenge>
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD04MDQ0MTdjYmQ4MGQ1ZGZlZWVlNDZlMWUwNDU0ODY2Yg==</challenge>
packet 26: client to servet xmpp消息:<response>
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'</response>
packet 27: server to client ack 消息:
packet 28: server to client xmpp消息:<sucess>
<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
// 到这之后,sasl成功
标注及理解:协商sasl
挑战和应答:
1,s2c: <challenge/>元素
2,c2s: <response/>元素 1,2可能多个回合
3,s2c: <failure/>元素标识失败;<success/>元素标识成功
---------------------------------------------------
packet 29: client to server xmpp: <stream:stream>
<stream:stream xmlns='jabber:client'
to='**.***.com'
xmlns:stream='http://etherx.jabber.org/streams'
version='1.0'
from='hanguojing0724@**.***.com'
id='2434506968' // 原始流的id
xml:lang='en'>
标注及理解:
sasl成功后,发送一个新的流头 但是不能发送</stream>标签,因为接收方实体和初始化实体必须确定原始的流被替换成SASL协商之后的流
-------------------------------------------------------
packet 30: server to client ack
-------------------------------------------------------
packet 31: server to client xmpp:<stream:stream>
<?xml version='1.0'?>
<stream:stream xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
id='3878138002' // 新流的id
from='***.***.com'
version='1.0' xml:lang='en'>
标注及理解:server回复响应流头,打开流
-----------------------------------------------------
packet 32: server to client xmpp:<stream:features>
<stream:features>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
<sm xmlns='urn:xmpp:sm:2'/>
<sm xmlns='urn:xmpp:sm:3'/>
<csi xmlns='urn:xmpp:csi:0'/>
</stream:features>
标注及理解:
server 发送流特性,包含资源绑定及会话创建
-----------------------------------------------------
packet 33: client to server ack
----------------------------------------------------
packet 34: client to server xmpp: <iq>
<iq id='BycQ5-53' type='set'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'></bind>
</iq>
标注及理解:
SASL成功后,server发送一个新的应答流给client,then server在流特性中包含一个<bind/>元素
server 不能包含<bind/>,直到client验证之后,通常是sasl协商成功之后。
client 发送一个类型为set并包含了bind命名空间限定的<bind/>元素来请求服务器生成资源部分
------------------------------------------------------
packet 35: server to client ack
-------------------------------------------------------
packet 36: server to client xmpp:<iq>
<iq id='BycQ5-53' type='result'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<jid>hanguojing0724@**.***.com/32889943041500975307369893
</jid>
</bind>
</iq>
标注及理解:
server为client生成了xmpp部分,返回一个类型为result的IQ节给客户端,必须包含一个<jid/>元素来指定服务器决定的已连接资源的全JID
------------------------------------------------------
packet 37:client to servet xmpp <iq>
<iq id='BycQ5-55' type='set'>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
</iq>
标注及理解:
client 请求创建一个session用于消息的收发
----------------------------------------------
packet 38: server to client ack
--------------------------------------------
packet 39: server to client xmpp : <iq>
<iq type='result' id='BycQ5-55'/>
标注及理解:
server 回 result类型IQ 标识session建立成功
-------------------------------------------------
packet 40: client to server xmpp:<enable>
<enable xmlns='urn:xmpp:sm:3' resume='true'/>
标注及理解:
client request enable Stream Management
--------------------------------------------------
packet 41: server to client xmpp:<enable>
<enabled xmlns='urn:xmpp:sm:3'
id='g2gCbQAAABozMjg4OTk0MzA0MTUwMDk3NTMwNzM2OTg5M2gDYgAABdxiAA7hymIACspL'
resume='true'
max='300' />
标注及理解:
resume='true' // 服务器允许恢复,设置为1
max='300' // 服务器指定的最大恢复时间
登录成功
--------------------------------------------------
packet 42: client to server xmpp:<iq> 查询花名册
<iq id='BycQ5-57' type='get'>
<query xmlns='jabber:iq:roster'>
</query>
</iq>
------------------------------------------------------
packet 43: server to client xmpp:<iq>
<iq
from='hanguojing0724@***.***.com'
to='hanguojing0724@***.***.com/32889943041500975307369893'
id='BycQ5-57'
type='result'>
<query xmlns='jabber:iq:roster'>
<item subscription='both' name='2222' jid='2222@***.***.com'>
<group>Friends</group>
</item>
<item subscription='both' name='hanguojing07241@***.***.com' jid='hanguojing07241@***.***.com'/>
</query>
</iq>
-------------------------------------------------------
packet 44: server to client xmpp <r>
// xmpp 0198 : 查询client是否收到了发送的stanzas
<r xmlns='urn:xmpp:sm:3'/>
------------------------------------------
packet 45:client to server ack 消息
------------------------------------------------
packet 46:client to server xmpp : <a>
client 回复收到,h值+1
<a xmlns='urn:xmpp:sm:3' h='1'/>
--------------------------------------------------
packet 47: server to client ack 消息