MSN PROTOCOL Oliver.Hao 2007.11.14
1> Login in(1).登陆Dispatch server。msn 在登陆的过程中首先会和一个叫做Dispatch Server的服务器沟通,Dispatch Server 会分配一个服务器让你登陆。
Dispatch Server 的域名为(messenger.hotmail.com), 打开的端口为1863。
我们首先要用tcp连上Dispatch Server(后面如果没有特指协议皆为TCP),然后发送命令。
msn-->Dispatch Server :VER 1 MSNP11 MSNP10 CVR0\r\n
VER 为MSN 的一个命令,1 为序列号,每条命令序列号都会加一,后面的为msn支持的msn协议版本。所有命令都要以\r\n为结束符。
Dispatch Server —〉msn :VER 1 MSNP11 MSNP10 CVR0\r\n
msn-->Dispatch Server :CVR 1 0x0409 winnt 5.1 i386 MSNMSGR 7.0.0816 MSMSGS aaa@hotmail.com\r\n
msn会向dispatch server发送自己使用的msn client版本信息。
Dispatch Server->msn :CVR 2 8.1.0178 8.1.0178 8.1.0178 \r\n
Dispatch server 会向msn返回最新版本的msn client的下载地址。
msn-->Dispatch Server :USR 2 TWN I im_camera1@hotmail.com\r\n
msn会向 dispatch server 发送自己的帐号信息
Dispatch Server->msn :XFR 2 NS 207.46.111.61:1863 0 65.54.239.140:1863\r\n
Dispatch server 会向msn发送下一步连接需要使用的服务器。例如现在我们下一步需要连接的服务器为207.46.111.61:1863。msn这端现在就可以关闭这个连接了。
(2).登陆Notification Servermsn在整个的生命活动中要始终和NS 连接,如果和它的连接关闭,则server认为该msn 已经下线。
msn-->NS :VER 0 MSNP11 MSNP10 CVR0\r\n
NS-->msn :VER 0 MSNP11 MSNP10 CVR0\r\n
msn-->NS :CVR 1 0x0409 winnt 5.1 i386 MSNMSGR 7.0.0816 MSMSGS aaa@hotmail.com\r\n
ns-->msn :CVR 2 8.1.0178 8.1.0178 8.1.0178 \r\n
msn-->NS :USR 2 TWN I im_camera1@hotmail.com\r\n
NS-->msn:USR 2 TWN S ct=1193988917,rver=4.5.2135.0,wp=FS_40SEC_0_COMPACT,lc=1033,id=507,ru=http:%2F%2Fmessenger.msn.com,tw=0,kpp=1,kv=4,ver=2.1.6000.1,rn=1lgjBfIL,tpf=b0735e3a873dfb5e75054465196398e0\r\n
这时候注意了,我们发送USR 命令后,NS 会向我们返回一串很长的字符串(ct=……),由于以后会用我们给他一个名字key_matter我们会将它送给一个验证服务器,验证服务器会向我们返回一张 ticket。我们再用这张ticket进行下一步的工作。获得ticket的过程会在下一节详述。
msn-->NS :USR 3 TWN S t=9ti7ayne!DDMRcqNGyPbj0ie3Hm2Q7giBxw4W7SwCoUMhYlF6kJibvambM3vo1Q10WL5Fxap26sxoID3SPAi1jz*zWhR1WSphA*mJQbyKSKLy1SkgI!sx9CXOZTqYDSkGC&p=9bqwmZtjNCWJPV8VW13RQ*qqFOf97X6OjxVzGcPEwn2WB7br0g142KwuiXZQlKZtdTz5ujB33eEor2YKjR5T7O*hu3g5cPYmQykadZYFVgCKaCsaambvakrnR6IOpuowSBnCF*j03m*Z!yF4gb3dB1LCpyqIIRCadSqyll05oRQUemNrubKAO*FBP2Ya08S5CA\r\n
msn 会将得到的ticket 加在 ”USR x TWN S”后面发送出去。
NS-->msn : USR 3 OK im_camera1@hotmail.com 1 0\r\n
如果登陆成功,则返回如上消息。
(3).获得ticketTicket 服务器的域名为nexus.passport.com,断口为443,msn应该和她建立SSL连接。
msn-->ticket server :GET /rdr/pprdr.asp HTTP/1.0\r\n\r\n
ticket server-->msn :HTTP/1.1 200 OK\r\n
Connection: close\r\n
Date: Wed, 14 Nov 2007 08:21:18 GMT\r\n
Server: Microsoft-IIS/6.0\r\n
PPServer: PPV: 30 H: BAYPPPRTS2B01 V: 0\r\n
P3P:CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"\r\n
X-Powered-By: ASP.NET\r\n
PassportURLs: DARealm=Passport.Net,DALogin=login.live.com/login2.srf,DAReg=\r\n
Content-Length: 0\r\n
Content-Type: text/html\r\n
Cache-control: private\r\n
这时候要找到DALogin后面的参数是下一步我们要连接的服务器地址及其资源地址, 在本例中下一步我们要使用ssl连接到login.live.com,资源地址为login2.srf.
msn-->login.live.com :GET /login2.srf HTTP/1.0\r\n
Authorization: Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=im%5fcamera2%40hotmail%2ecom,pwd=123456,ct=1195028476,rver=4.5.2135.0,wp=FS_40SEC_0_COMPACT,lc=1033,id=507,ru=http:%2F%2Fmessenger.msn.com,tw=0,kpp=1,kv=4,ver=2.1.6000.1,rn=1lgjBfIL,tpf=b0735e3a873dfb5e75054465196398e0\r\n
User-Agent: MSMSGS\r\n
Host: login.live.com\r\n
Connection: Keep-Alive\r\n
Cache-Control: no-cache\r\n\r\n
这一步主要应注意的是Authorization后面的字段。它前面部分是固定的: Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=后面部应为URL-encoded(accunt_name),pass= URL-encoded(password),auth=key_matter. URL-encoded为URL编码,key_matter为和NS沟通得到的字符串。
Login.live.com-->msn : HTTP/1.1 200 OK
Connection: close
Date: Wed, 14 Nov 2007 08:21:24 GMT
Server: Microsoft-IIS/6.0
PPServer: PPV: 30 H: BAYPPLOGN2A05 V: 0
P3P:CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"
X-Powered-By: ASP.NET
Content-Type: text/html; charset=iso-8859-1
Expires: Wed, 14 Nov 2007 08:20:24 GMT
Cache-Control: no-cache
Pragma: no-cache
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
Set-Cookie: PPAuth=AdG9ZVpevPos6RfuqGOwvzdbW2lfr1LO75W8*kRNJsSLCQ0GhqhDv89zUzoEFNHGttVJ7arRMj!b7GIxJdfO36aBaOqQjDxq3a9zfofwM3JhJgq*Bdl!KDSmhLd3Jp3WxeGKHHOymNC6YfAaqvAgfBAf!7mhl7SnO3qSJ*Ezeffn3!3TrGZQH34wuvf1gWRtB4Z2DJP5MC*D!ZJv8w$$; domain=login.live.com;secure= ;path=/;HTTPOnly= ;version=1
Set-Cookie: PPLState=1; domain=.live.com;path=/;version=1
Set-Cookie: MSPVis=507;domain=login.live.com;path=/
Authentication-Info: Passport1.4 da-status=success,from-PP='t=9WUn!F0VGD4Mw6ynZ*sCg7nk8mlLTRxLYMsIkGTSwZrzG5m93gEoq2rWPM2netkslefXDUtJukS1se4JZna6LmE8aZQxgPdKIWhrDIOLm4cwxhjVJmKqZGld5DVE37DkO6&p=9VAWok95zA*qMMaCfWlgVSwfaEqkc!cOqp3RJfP9w9VdDiPEHsBOxMPuEI9e*hkOQC40u9YdhK2FsGq7g6WyQisa8sjwmNB1NYKDpCEXcGAaN!xLBnvyUXigSCvN8Pf73y8OBg!OQq15O*IdiuzXwCerDNv7BUy1Wg3yo*kSkVhtsq!aPsHfh2sk5drCnbQIjF',ru=
Connection: Keep-Alive
Content-Length: 0
这时候如果一切正常,login.live.com就会返回此消息,注意Authentication-Info字段的内容。在该字段中在”’t=”至” ’ ”的字符串就为ticket。
至此整个login过程结束,以上没有列出出错消息,反正不一样八成就出错了,自己抓抓包应该很好解决的。
2〉construct sessionmsn 中诸如聊天视频语音之类的东西都是建立在session的概念上面的。交互双方都维持一条到Switchboard的tcp连接,所有发向对端的消息先发向Switchboard,然后由Switchboard转发给对端。
(1).发起连接msn-->NS : XFR 10 SB\r\n
NS-->msn : XFR 10 SB 207.46.26.139:1863 CKI 2109129998.11915253.1385978\r\n
要记下SB后面的IP:addr,本例中207.46.26.139即为Switchboard的IP,1863为Switchboard打开的port。CKI后面的值将在下一步使用。
msn-->Switchboard : USR 0 aaa@hotmail.com 2109129998.11915253.1385978\r\n
msn 建立和Switchboard的TCP连接,aaa@hotmail.com 为自己的帐号,并将上次的CKI后面的字符串加在后面。
Switchboard-->msn : USR 0 OK aaa@hotmail.com aaa@hotmail.com\r\n
msn-->Switchboard : CAL 1 bbb@hotmail.com\r\n
msn 将要加入该session的帐号作为CAL的参数传给Switchboard。
Switchboard-->msn : CAL 1 RINGING 2109129998\r\n
Switchboard-->msn : JOI bbb@hotmail.com\r\n
如果一切正常,就会有如上的值返回,session就建立成功了。
(2).接受连接NS-->msn : RNG 494758238 207.46.27.44:1863 CKI 20921690.4119876 bbb@hotmail.com bbb\r\n
如果有msn想和我建立连接,当他发送过CAL命令后,我们就会受到该条消息。和面跟着Switchboard的ip和port。CKI的值要记住,后面要用到,最后的参数为发起方的帐号和名字。
msn-->Switchboard : ANS 0 aaa@hotmail.com 20921690.4119876 494758238\r\n
我们会建立和Switchboard的TCP连接,然后发送此命令,后面跟着自己的帐号和CKI的值。
Switchboard-->msn : IRO 0 1 1 oliver.hao.open@hotmail.com Oliver\r\n
Switchboard-->msn : ANS 0 OK\r\n
如果一切正常,就会有如上的值返回,session就建立成功了。
3〉Talk(1).MSG结构所有聊天的内容都是由MSG命令发送的。Len为消息长度,不包含第一行。 Header都以\r\n结束,后面可以接二进制数据。
MSG XX XX len\r\n
XXXXXXXXX\r\n
XXXXXXXXX\r\n
..........................................................
(2).接受消息msn-->Switchboard : MSG aaa@hotmail.com aaa 130\r\n
MIME-Version: 1.0\r\n
Content-Type: text/plain; charset=UTF-8\r\n
X-MMS-IM-Format: FN=%E5%AE%8B%E4%BD%93; EF=; CO=0; CS=86; PF=0\r\n\r\n
sdfg
上面的例子中sdfg即为收到的消息。MIME-Version,Content-Type基本不会变。X-MMS-IM-Format为传输消息的字体,具体怎么对应就不知道了。
(3).发送消息Switchboard-->msn : MSG 10 U 139\r\n
MIME-Version: 1.0\r\n
Content-Type: text/plain; charset=UTF-8\r\n
User-Agent: KaKaKaKa/0.0.0.0
X-MMS-IM-Format: FN=; EF=; CO=0; CS=0; PF=0\r\n\r\n
xxxx
上面的例子中 xxxx即为收到的消息。
4〉Add/Del friend(1). Add friendmsn-->NS : ADC 10 FL N=aaa@hotmail.com F=aaa@hotmail.com\r\n
使用ADC来加好友。
NS-->msn : ADC 10 FL N=aaa@hotmail.com F=aaa@hotmail.com C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n
NS返回好友信息。
msn-->NS : ADC 11 AL N=aaa@hotmail.com\r\n
将好友加入allow列表。
(2). Del friend
msn-->NS : REM 9 FL xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n
最后面的参数是该好友的id。如使用ADC架好友时,传过来的C=后面的参数。
5〉video(1). p2p messagemsn p2p 的基本知识可以去msnpiki.msnfanatic.com上面看。其中最主要的就是关于p2p header 的处理,注意他所有的数据都是小端的。它分为9个部分分别为:
DWORD SessionID : DWORD Identifier :QWORD Data offset :QWORD Total data size :DWORD Message length :DWORD Flag :DWORD Acknowledged identifier :DWORD Acknowledged unique ID :QWORD Acknowledged data size;
基本上每个发向你的p2p消息都需要一个ACK,每个ACK消息的结构如下:
MSG 10 D 152\r\n
MIME-Version: 1.0\r\n
Content-Type: application/x-msnmsgrp2p\r\n
P2P-Dest: oliver.hao.open@hotmail.com\r\n\r\n
p2p_header
SessionId : 为你要回复的消息的sessionID字段.
Indentifier : 一个用来鉴别每条p2p消息的随机数,第一条p2p消息要随机生成一个,以后每条p2p消息加一。
Data offset : 0
Total data size : 应为要恢复消息的Total data size字段。
Message length : 0
Flag : 2
Acknowledged identifier : 要回复的消息的indentifier 字段。
Acknowledged unique ID : 要恢复消息的 Acknowledged identifier 字段。
Acknowledged data size : 要恢复消息的Total data size字段。
(2). View webcamera flow现在只是举一个简单的例子看一下video的流程,如果大家有兴趣可以自己抓包完成其它的协议。现在假设A想看B的webcamera的内容则有如下流程。
A-->B invite
B-->A 200 Ok, invite
A-->B decline, sip syn
B-->A sip syn
A-->B sip ack
B-->A sip ack, sip xml
A-->B sip xml
B-->A start view
开始传送数据:
B-->A recipientid=xxx&sessionid=xxxx\r\n\r\n
A-->B connected\r\n\r\n
A-->B connected\r\n\r\n
B-->A video_data
关于 invite, 200 Ok, decline 的结构可以去msnpiki.msnfanatic.com上面看。现在主要说一下关于sip syn 和sip ack 相关的信息。
Sip meaage :
MSG 9 D 170\r\n
MIME-Version: 1.0\r\n
Content-Type: application/x-msnmsgrp2p\r\n
P2P-Dest: oliver.hao.open@hotmail.com\r\n\r\n
p2p header + sip message
sip message 由sip头和sip消息体构成。
sip头的结构如下
| 1 2 3 4 5 6 7 8 9 10 |
xx xx xx xx xx xx | sip len |
message len = 后面消息的长度, 小端。不算foot(0000000x)。
所有syn消息都为字符串”syn”经过sip编码后的结果。注意要计算字符串最后面的0。
Sip 编码为把所有的字符扩展为两个,如0x01-->0x01,0x00。
所有的ack消息都为字符串“ack”经过sip编码后的结果。注意要计算字符串最后面的0。
所有的sip消息后面都加上了0x00,0x00,0x00,0x04.作为结尾,但不计算在sip length中。
对于消息 A-->B decline, sip syn
Sip头前6个字节应为:0x80, 0x06, 0x93, 0x7c, 0x08, 0x00.
对于消息 B-->A sip syn
Sip头前6个字节应为:0x80, 0x17, 0x2a, 0x01, 0x08, 0x00.
对于消息 A-->B sip ack
Sip ack头前6个字节应为:0x80, 0xfe, 0xdd, 0x05, 0x08, 0x00.
对于消息 B-->A sip ack, sip xml
Sip ack头前6个字节应为:0x80, 0xea, 0x00, 0x00, 0x08, 0x00.
Sip xml头前6个字节应为:0x80, 0x00, 0x09, 0x00, 0x08, 0x00.
对于消息 A-->B sip xml
Sip ack头前6个字节应为:0x80, 0x06, 0x93, 0x7c, 0x08, 0x00.
在消息sip xml,中消息为经过sip编码的xml文件。
在sip xml,这里面包含了所有关于自己的ip 及port 信息。其具体模板为
"<$who$>
2.0$rid$$urid$$session$02931$tcpport$\t\t\t\t\t\t\t\t $tcpport$\t\t\t\t\t\t\t\t $tcpport$$ip_list$778631863$ip_list$31859318603186131862111127.0.0.11$who$>\r\n\r\n"
$who$ : 应为 producer或者是 viewer。发送者为producer, 接收者为viewer。
$rid$ : 随机数,可用rand()%100+50。
$urid$ : rid+1
$session$ : 随机数,可用rand()%1000+5000。
$tcpport$ : 应为打开的tcp的端口。
$ip_list$:模板为"
ip_1ip_2",ip为本地及其gateway的ip(如果有NAT)。
当B收到A发送过来的sip xml,B会搜索 tcpipaddress 和port信息,然后尝试向其发送tcp连接,如果连接成功则开始协商数据传输。
假设前面B指向A的TCP连接已经建立成功。传输的第一个数据包为recipientid=xxx&sessionid=xxxx\r\n\r\n, 其值分别为前面生成xml时rid和session的值。
然后双方互发一个connected,B就会向A发送视频数据。
B向A发送视频数据的格式为:
Video header + 编码好的数据。
Video header的长度是24,则第一二字节为0x18,0x00;
第三四字节表示帧宽,如果帧宽为320,则其值为0x40,0x01;
第五六字节表示帧高,如果帧宽为240,则其值为0xf0,0x00;
第9,10,11,12字节表示帧长。
第13,14,15,16字节应为ML20。
最后四个字节应为时间戳,单位为微秒????
6〉NAT PASS 对于MSN来说,他过NAT的方式大概有3种,一种是利用UPNP—IGD,一种是利用STUN协议,最后一种是使用服务器中转。
对于STUN协议,不熟悉,所以略过。UPNP-IGD比较简单,所以也略过,重点讲一下如何利用服务器中转。
一般msn会首先尝试利用前两种方式连接MSN,如果失败就会尝试使用服务器中转。假设由A发送视频数据给B,则流程如下。
A-->m0reflector.spotlife.net:80 GET /createSession HTTP/1.1\r\n
User-Agent: Logitech Video\r\n
Host: m0reflector.spotlife.net\r\n
Cache-Control: no-cache\r\n\r\n
m0reflector.spotlife.net:80? A HTTP/1.1 200 OK\r\n
Content-Type: text/xml\r\n
Content-Length: 231\r\n
Date: Wed, 05 Dec 2007 05:23:02 GMT\r\n
Pragma: no-cache\r\n
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)\r\n\r\n
\r\n
\r\n
yGY4J5aSEg\r\n
47\r\n
\r\n
\r\n\r\n
A会记住
,以及的值。我们使用$sid表示的值。将$ createtunnelurl分解为
A-->$url:$port GET /createTunnel?sid=$sid&a=hash($kid,$sid) HTTP/1.1\r\n
User-Agent: Logitech Video\r\n
Host:$url:$port\r\n
Cache-Control: no-cache\r\n\r\n
$url:$port-->A HTTP/1.1 200 OK
Content-Type: text/xml
Content-Length: 449
Date: Wed, 05 Dec 2007 05:23:07 GMT
Pragma: no-cache
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
6189310638237098946
m0reflector13.spotlife.net
9000
102400000
6144
A应记住xml节点port, host和tid的值。现在记作 $port, $host, $tid.
A-->B msn tunnel info sip
该消息时将字符串ru=http:// $host:$port&ti=$tid,按照先ASIC—〉hex,然后再加上ReflData: , 最后将经过sip编码的sip消息传给B。如字符h最后编码为 h-〉0x68—〉0x36, 0x00, 0x38, 0x00.
A-->$host:$port PROD $tid TSP/1.0\r\n
$host:$port-->A TSP/1.0 200 OK\r\n
CONNECTED\r\n
当收到字符串CONNECTED,就表示双方的tunnel建立成功了,可以开始传输数据了。
A-->B video data