Skip to content

Commit

Permalink
Introduce new, vanilla-compatible server info protocol
Browse files Browse the repository at this point in the history
This means that we have a reliable and fast way to query for extended info,
while also not wasting network bandwidth.

The protocol is designed to be extensible, there's four bytes space for
encoding more request types (currently zeroed), and there's one string in each
response packet and one string for each player available (currently the empty
string).

The protocol itself has no problems with more than 64 players, although the
current client implementation will drop the player info after the 64th player,
because it uses a static array for storage.

Also fixes #130, the player list is just sorted each time new player info
arrives.

(cherry picked from commit 1d81d568502cfc548e01912595a25da166335b37)
  • Loading branch information
heinrich5991 authored and Kaffeine committed Sep 2, 2021
1 parent 6322571 commit 09ed64a
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 90 deletions.
251 changes: 180 additions & 71 deletions src/engine/server/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
}
}

void CServer::SendServerInfoConnless(const NETADDR *pAddr, int Token, bool Extended)
void CServer::SendServerInfoConnless(const NETADDR *pAddr, int Token, int Type)
{
const int MaxRequests = g_Config.m_SvServerInfoPerSecond;
int64 Now = Tick();
Expand All @@ -1624,14 +1624,16 @@ void CServer::SendServerInfoConnless(const NETADDR *pAddr, int Token, bool Exten
str_format(aBuf, sizeof(aBuf), "Too many info requests from %s: %d > %d (Now = %lld, mSIFR = %lld)",
aAddrStr, m_ServerInfoNumRequests, MaxRequests, Now, m_ServerInfoFirstRequest);
Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "inforequests", aBuf);
} else {
SendServerInfo(pAddr, Token, Extended, true);
return;
}

bool SendClients = m_ServerInfoNumRequests <= MaxRequests && !m_ServerInfoHighLoad;
SendServerInfo(pAddr, Token, Type, SendClients);
}

void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, bool SendClients, int Offset)
void CServer::SendServerInfo(const NETADDR *pAddr, int Token, int Type, bool SendClients)
{
CNetChunk Packet;
// One chance to improve the protocol!
CPacker p;
char aBuf[256];

Expand All @@ -1653,9 +1655,19 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, boo

p.Reset();

p.AddRaw(Extended?SERVERBROWSE_INFO64:SERVERBROWSE_INFO, sizeof(Extended?SERVERBROWSE_INFO64:SERVERBROWSE_INFO));
str_format(aBuf, sizeof(aBuf), "%d", Token);
p.AddString(aBuf, 6);
#define ADD_RAW(p, x) (p).AddRaw(x, sizeof(x))
#define ADD_INT(p, x) do { str_format(aBuf, sizeof(aBuf), "%d", x); (p).AddString(aBuf, 0); } while(0)

switch(Type)
{
case SERVERINFO_EXTENDED: ADD_RAW(p, SERVERBROWSE_INFO_EXTENDED); break;
case SERVERINFO_64_LEGACY: ADD_RAW(p, SERVERBROWSE_INFO_64_LEGACY); break;
case SERVERINFO_VANILLA: ADD_RAW(p, SERVERBROWSE_INFO); break;
case SERVERINFO_INGAME: ADD_RAW(p, SERVERBROWSE_INFO); break;
default: dbg_assert(false, "unknown serverinfo type");
}

ADD_INT(p, Token);

p.AddString(GameServer()->Version(), 32);

Expand Down Expand Up @@ -1736,114 +1748,197 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, bool Extended, boo
}
}

if (Extended)
if(Type != SERVERINFO_VANILLA)
{
p.AddString(aBuf, 256);
p.AddString(aBuf, 256);
}
else
{
if (ClientCount < VANILLA_MAX_CLIENTS)
if(m_NetServer.MaxClients() <= VANILLA_MAX_CLIENTS)
{
p.AddString(aBuf, 64);
}
else
{
char bBuf[64];
str_format(bBuf, sizeof(bBuf), "%s - %d/%d online", aBuf, ClientCount, m_NetServer.MaxClients());
p.AddString(bBuf, 64);
str_format(aBuf, sizeof(aBuf), "%s [%d/%d]", aBuf, ClientCount, m_NetServer.MaxClients());
p.AddString(aBuf, 64);
}
}

p.AddString(pMapName, 32);

if(Type == SERVERINFO_EXTENDED)
{
ADD_INT(p, m_CurrentMapCrc);
ADD_INT(p, m_CurrentMapSize);
}

// gametype
p.AddString(GameServer()->GameType(), 16);

// flags
int i = 0;
if(g_Config.m_Password[0]) // password set
i |= SERVER_FLAG_PASSWORD;
str_format(aBuf, sizeof(aBuf), "%d", i);
p.AddString(aBuf, 2);
ADD_INT(p, g_Config.m_Password[0] ? SERVER_FLAG_PASSWORD : 0);

int MaxClients = m_NetServer.MaxClients();
if (!Extended)
if(Type == SERVERINFO_VANILLA || Type == SERVERINFO_INGAME)
{
if (ClientCount >= VANILLA_MAX_CLIENTS)
if(ClientCount >= VANILLA_MAX_CLIENTS)
{
if (ClientCount < MaxClients)
if(ClientCount < MaxClients)
ClientCount = VANILLA_MAX_CLIENTS - 1;
else
ClientCount = VANILLA_MAX_CLIENTS;
}
if (MaxClients > VANILLA_MAX_CLIENTS) MaxClients = VANILLA_MAX_CLIENTS;
if(MaxClients > VANILLA_MAX_CLIENTS)
MaxClients = VANILLA_MAX_CLIENTS;
if(PlayerCount > ClientCount)
PlayerCount = ClientCount;
}

if (PlayerCount > ClientCount)
PlayerCount = ClientCount;
ADD_INT(p, PlayerCount); // num players
ADD_INT(p, MaxClients-g_Config.m_SvSpectatorSlots); // max players
ADD_INT(p, ClientCount); // num clients
ADD_INT(p, MaxClients); // max clients

str_format(aBuf, sizeof(aBuf), "%d", PlayerCount); p.AddString(aBuf, 3); // num players
str_format(aBuf, sizeof(aBuf), "%d", MaxClients-g_Config.m_SvSpectatorSlots); p.AddString(aBuf, 3); // max players
str_format(aBuf, sizeof(aBuf), "%d", ClientCount); p.AddString(aBuf, 3); // num clients
str_format(aBuf, sizeof(aBuf), "%d", MaxClients); p.AddString(aBuf, 3); // max clients
if(Type == SERVERINFO_EXTENDED)
p.AddString("", 0); // extra info, reserved

if (Extended)
p.AddInt(Offset);
const void *pPrefix = p.Data();
int PrefixSize = p.Size();

int ClientsPerPacket = Extended ? 24 : VANILLA_MAX_CLIENTS;
int Skip = Offset;
int Take = ClientsPerPacket;
CPacker pp;
CNetChunk Packet;
int PacketsSent = 0;
int PlayersSent = 0;
Packet.m_ClientID = -1;
Packet.m_Address = *pAddr;
Packet.m_Flags = NETSENDFLAG_CONNLESS;

if (SendClients) {
for(i = 0; i < MAX_CLIENTS; i++)
#define SEND(size) \
do \
{ \
Packet.m_pData = pp.Data(); \
Packet.m_DataSize = size; \
m_NetServer.Send(&Packet); \
PacketsSent++; \
} while(0)

#define RESET() \
do \
{ \
pp.Reset(); \
pp.AddRaw(pPrefix, PrefixSize); \
} while(0)

RESET();

if(Type == SERVERINFO_64_LEGACY)
pp.AddInt(PlayersSent); // offset

if(!SendClients)
{
SEND(pp.Size());
return;
}

if(Type == SERVERINFO_EXTENDED)
{
pPrefix = SERVERBROWSE_INFO_EXTENDED_MORE;
PrefixSize = sizeof(SERVERBROWSE_INFO_EXTENDED_MORE);
}

int Remaining;
switch(Type)
{
case SERVERINFO_EXTENDED: Remaining = -1; break;
case SERVERINFO_64_LEGACY: Remaining = 24; break;
case SERVERINFO_VANILLA: Remaining = VANILLA_MAX_CLIENTS; break;
case SERVERINFO_INGAME: Remaining = VANILLA_MAX_CLIENTS; break;
default: dbg_assert(0, "caught earlier, unreachable"); return;
}

// Use the following strategy for sending:
// For vanilla, send the first 16 players.
// For legacy 64p, send 24 players per packet.
// For extended, send as much players as possible.

for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_aClients[i].m_State != CClient::STATE_EMPTY)
{
if(m_aClients[i].m_State != CClient::STATE_EMPTY)
{
if(GameServer()->IsClientBot(i))
continue;
if(GameServer()->IsClientBot(i))
continue;

if(g_Config.m_SvHideInfo)
{
if(ClientCount == 0)
break;
if(g_Config.m_SvHideInfo)
{
if(ClientCount == 0)
break;

--ClientCount;
}
--ClientCount;
}

if (Skip-- > 0)
continue;
if (--Take < 0)
if(Remaining == 0)
{
if(Type == SERVERINFO_VANILLA || Type == SERVERINFO_INGAME)
break;

p.AddString(ClientName(i), MAX_NAME_LENGTH); // client name
p.AddString(ClientClan(i), MAX_CLAN_LENGTH); // client clan
// Otherwise we're SERVERINFO_64_LEGACY.
SEND(pp.Size());
RESET();
pp.AddInt(PlayersSent); // offset
Remaining = 24;
}
if(Remaining > 0)
{
Remaining--;
}

int PreviousSize = pp.Size();

pp.AddString(ClientName(i), MAX_NAME_LENGTH); // client name
pp.AddString(ClientClan(i), MAX_CLAN_LENGTH); // client clan

str_format(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Country); p.AddString(aBuf, 6); // client country
str_format(aBuf, sizeof(aBuf), "%d", RoundStatistics()->PlayerScore(i)); p.AddString(aBuf, 6); // client score
str_format(aBuf, sizeof(aBuf), "%d", GameServer()->IsClientPlayer(i)?1:0); p.AddString(aBuf, 2); // is player?
ADD_INT(pp, m_aClients[i].m_Country); // client country
ADD_INT(pp, RoundStatistics()->PlayerScore(i)); // client score
ADD_INT(pp, GameServer()->IsClientPlayer(i) ? 1 : 0); // is player?
if(Type == SERVERINFO_EXTENDED)
pp.AddString("", 0); // extra info, reserved

if(Type == SERVERINFO_EXTENDED)
{
if(pp.Size() >= NET_MAX_PAYLOAD)
{
// Retry current player.
i--;
SEND(PreviousSize);
RESET();
ADD_INT(pp, Token);
ADD_INT(pp, PacketsSent);
pp.AddString("", 0); // extra info, reserved
continue;
}
}
PlayersSent++;
}
}

Packet.m_ClientID = -1;
Packet.m_Address = *pAddr;
Packet.m_Flags = NETSENDFLAG_CONNLESS;
Packet.m_DataSize = p.Size();
Packet.m_pData = p.Data();
m_NetServer.Send(&Packet);

if (Extended && Take < 0)
SendServerInfo(pAddr, Token, Extended, SendClients, Offset + ClientsPerPacket);
SEND(pp.Size());
#undef SEND
#undef RESET
#undef ADD_RAW
#undef ADD_INT
}

void CServer::UpdateServerInfo()
{
for(int i = 0; i < MAX_CLIENTS; ++i)
{
if(m_aClients[i].m_State != CClient::STATE_EMPTY)
SendServerInfo(m_NetServer.ClientAddr(i), -1);
{
SendServerInfo(m_NetServer.ClientAddr(i), -1, SERVERINFO_INGAME, false);
}
}
}


void CServer::PumpNetwork()
{
CNetChunk Packet;
Expand All @@ -1858,15 +1953,29 @@ void CServer::PumpNetwork()
// stateless
if(!m_Register.RegisterProcessPacket(&Packet))
{
if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 &&
int ExtraToken = 0;
int Type = -1;
if(Packet.m_DataSize >= (int)sizeof(SERVERBROWSE_GETINFO)+1 &&
mem_comp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0)
{
SendServerInfoConnless(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]);
if(Packet.m_Flags&NETSENDFLAG_EXTENDED)
{
Type = SERVERINFO_EXTENDED;
ExtraToken = (Packet.m_aExtraData[0] << 8) | Packet.m_aExtraData[1];
}
else
Type = SERVERINFO_VANILLA;
}
else if(Packet.m_DataSize >= (int)sizeof(SERVERBROWSE_GETINFO_64_LEGACY)+1 &&
mem_comp(Packet.m_pData, SERVERBROWSE_GETINFO_64_LEGACY, sizeof(SERVERBROWSE_GETINFO_64_LEGACY)) == 0)
{
Type = SERVERINFO_64_LEGACY;
}
else if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO64)+1 &&
mem_comp(Packet.m_pData, SERVERBROWSE_GETINFO64, sizeof(SERVERBROWSE_GETINFO64)) == 0)
if(Type != -1)
{
SendServerInfoConnless(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO64)], true);
int Token = ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)];
Token |= ExtraToken << 8;
SendServerInfoConnless(&Packet.m_Address, Token, Type);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/engine/server/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,8 @@ class CServer : public IServer

void ProcessClientPacket(CNetChunk *pPacket);

void SendServerInfoConnless(const NETADDR *pAddr, int Token, bool Extended = false);
void SendServerInfo(const NETADDR *pAddr, int Token, bool Extended=false, bool SendClients = true, int Offset=0);
void SendServerInfo(const NETADDR *pAddr, int Token, int Type, bool SendClients);
void SendServerInfoConnless(const NETADDR *pAddr, int Token, int Type);
void UpdateServerInfo();

void PumpNetwork();
Expand Down
Loading

0 comments on commit 09ed64a

Please sign in to comment.