posts - 311, comments - 0, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理
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。