mangosd是MaNGOS-Zero项目中的游戏逻辑进程,玩家一旦与realmd的keyexchange过程完成后(详细内容见《
realmd认证登录服务器(一):认证登录基本流程》),便只与mangosd进行交互。而客户端与realmd的连接也会在客户端向mangosd发送enterworld之后断开。
本文将介绍客户端连接到mangosd后,mangosd认证客户端合法性并最终建立RC4流加密的过程。具体过程如下:
(1) 客户端与mangosd建立TCP连接后,mangosd会向客户端发送消息SMSG_AUTH_CHALLENGE
1: int WorldSocket::open (void *a)
2: {
3: ........
4:
5: // Send startup packet.
6: WorldPacket packet (SMSG_AUTH_CHALLENGE, 4);
7: packet << m_Seed;
8: if (SendPacket (packet) == -1)
9: return -1;
10:
11: ........
12: }
m_Seed是一个随机数,每次客户端连接上来的时生成一个新的随机数(随着WorldSocket的创建而初始化)。
(2)客户端收到SMSG_AUTH_CHALLENGE消息后,知道服务器要求其提供身份认证信息,于是开始构造CMSG_AUTH_SESSION消息。(以下代码并非客户端真实代码)
1: //client do auth
2: {
3: BigNumber clientSeed;
4: clientSeed.SetRand(4 * 8);
5: sha.Initialize();
6: sha.UpdateData("abu");
7: uint32 t = 0;
8: sha.UpdateData((uint8 *)&t, 4);
9: sha.UpdateBigNumbers(&clientSend, NULL);
10: sha.UpdateData((uint8 *)&serverSeed, 4);
11: sha.UpdateBigNumbers(&K, NULL);
12: sha.Finalize();
13:
14: uint32 unk2;
15: ByteBuffer pktbuf;
16: string account = "abu";
17: uint16 pktbuf_size = 4+4+4+account.length()+4+20;
18: EndianConvertReverse(pktbuf_size);
19: pktbuf << uint16(pktbuf_size);
20: pktbuf << uint32(CMSG_AUTH_SESSION);
21: pktbuf << uint32(5875); //build version
22: pktbuf << unk2;
23: pktbuf << account;
24: pktbuf.append(clientSeed.AsByteArray(4), 4);
25: pktbuf.append(sha.GetDigest(), 20);
26:
27: send((char const*)pktbuf.contents(), pktbuf.size());
28: }
其中最为关键的是构造20位的sha验证密文M:
M = sha(t, account, clientSeed, serverSeed, K);
t为0;account是明文的用户名;clientSeed是由客户端生成的随机数,用于本次连接游戏session;serverSeed是SMSG_AUTH_CHALLENGE消息发过来的服务器随机数;K是之前和realmd交互做keyexchange时生成的,由服务器和客户端分别进行计算,SRP6算法要求(保证)两边的计算结果一致,服务器端保存在realmd.account.sessionkey字段。
(3)服务器收到客户端发来的CMSG_AUTH_SESSION,首先对收到的数据包进行分析,客户端发来的数据包的包头如下:
1: struct ClientPktHeader
2: {
3: uint16 size; //packet_size except itself
4: uint32 cmd; //opCode
5: };
收到客户端发来的data,处理流程可以简化为如下代码:
int WorldSocket::handle_input (ACE_HANDLE)
{
……………
handle_input_missing_data()
{
handle_input_header();
handle_input_payload()
{
const int ret = ProcessIncoming (m_RecvWPct);
}
}
}
在ProcessIncoming()函数中使用switch case把客户端发过来的不同的opcode定位到不同的处理函数中,而登录认证过程需要定位到int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)函数。
在HandleAuthSession()函数中,服务器以客户端相同的方式计算sha密文,并和客户端传来的做比较,如果相同则认证通过,然后创建WorldSession实例,初始化m_Crypt成员,以便以后服务器和客户端之间交互的RC4对称加密使用。最后把新创建的WorldSession对象的m_Session添加到游戏世界中,添加完毕后,在游戏世界的主线程(Update线程)可以对该客户端做相应的处理。
(4)HandleAuthSession()处理的最后会使用下面的代码,进行判断:如果session可以作为normal_session的而不是queue_session则发送SMSG_AUTH_RESPONSE消息,至此所有发送的消息都将进行RC4的流加密。
1: void World::AddSession_ (WorldSession* s)
2: {
3: ........
4:
5: if (pLimit > 0 && Sessions >= pLimit && s->GetSecurity () == SEC_PLAYER )
6: {
7: AddQueuedSession(s);
8: UpdateMaxSessionCounters();
9: DETAIL_LOG("PlayerQueue: Account id %u is in Queue Position (%u).", s->GetAccountId (), ++QueueSize);
10: return;
11: }
12:
13: // Checked for 1.12.2
14: WorldPacket packet(SMSG_AUTH_RESPONSE, 1 + 4 + 1 + 4);
15: packet << uint8 (AUTH_OK);
16: packet << uint32 (0); // BillingTimeRemaining
17: packet << uint8 (0); // BillingPlanFlags
18: packet << uint32 (0); // BillingTimeRested
19: s->SendPacket (&packet);
20:
21: ........
22: }
总结:
(1)realmd和mangosd在登录认证过程中,相互之间基本不通信,通过MySQL来传递client认证所需的sessionkey。
(2)每次客户端和mangosd之间认证时,各自生成一个随机数Seed,保证在传输过程中隐藏sessionkey。