Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeout protection (WIP) #51

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/engine/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class IServer : public IInterface
virtual int GetClientVersion(int ClientID) const = 0;
virtual void RestrictRconOutput(int ClientID) = 0;

// DDrace
// DDRace

virtual void GetClientAddr(int ClientID, NETADDR* pAddr) = 0;
virtual const char* GetAnnouncementLine(char const* FileName) = 0;
Expand Down Expand Up @@ -82,6 +82,11 @@ class IServer : public IInterface

virtual void DemoRecorder_HandleAutoStart() = 0;
virtual bool DemoRecorder_IsRecording() = 0;

virtual const char *GetNetErrorString(int ClientID) = 0;
virtual void ResetNetErrorString(int ClientID) = 0;
virtual bool SetTimedOut(int ClientID, int OrigID) = 0;
virtual void SetTimeoutProtected(int ClientID) = 0;
};

class IGameServer : public IInterface
Expand Down
14 changes: 13 additions & 1 deletion src/engine/server/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2514,7 +2514,7 @@ int main(int argc, const char **argv) // ignore_convention
return Ret;
}

// DDrace
// DDRace

void CServer::GetClientAddr(int ClientID, NETADDR* pAddr)
{
Expand Down Expand Up @@ -2562,6 +2562,18 @@ const char* CServer::GetAnnouncementLine(char const* pFileName)
return v[m_AnnouncementLastLine];
}

bool CServer::SetTimedOut(int ClientID, int OrigID)
{
if (!m_NetServer.SetTimedOut(ClientID, OrigID))
{
return false;
}
DelClientCallback(OrigID, "Timeout Protection used", this);
m_aClients[ClientID].m_Authed = AUTHED_NO;
// m_aClients[ClientID].m_Flags = m_aClients[OrigID].m_Flags;
return true;
}

#if defined (CONF_SQL)

void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
Expand Down
7 changes: 6 additions & 1 deletion src/engine/server/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,16 @@ class CServer : public IServer

void RestrictRconOutput(int ClientID) { m_RconRestrict = ClientID; }

// DDrace
// DDRace

void GetClientAddr(int ClientID, NETADDR* pAddr);
const char* GetAnnouncementLine(char const* FileName);
unsigned m_AnnouncementLastLine;

const char *GetNetErrorString(int ClientID) { return m_NetServer.ErrorString(ClientID); };
void ResetNetErrorString(int ClientID) { m_NetServer.ResetErrorString(ClientID); };
bool SetTimedOut(int ClientID, int OrigID);
void SetTimeoutProtected(int ClientID) { m_NetServer.SetTimeoutProtected(ClientID); };
#if defined (CONF_SQL)
// console commands for sqlmasters
static void ConAddSqlServer(IConsole::IResult *pResult, void *pUserData);
Expand Down
4 changes: 3 additions & 1 deletion src/engine/shared/config_variables.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ MACRO_CONFIG_INT(DbgHitch, dbg_hitch, 0, 0, 0, CFGFLAG_SERVER, "Hitch warnings")
MACRO_CONFIG_STR(DbgStressServer, dbg_stress_server, 32, "localhost", CFGFLAG_CLIENT, "Server to stress")
MACRO_CONFIG_INT(DbgResizable, dbg_resizable, 0, 0, 0, CFGFLAG_CLIENT, "Enables window resizing")

// DDrace
// DDRace

#if defined(CONF_SQL)
MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables SQL DB instead of record file")
Expand All @@ -130,6 +130,8 @@ MACRO_CONFIG_INT(SvSqlQueriesDelay, sv_sql_queries_delay, 1, 0, 20, CFGFLAG_SERV
#endif

MACRO_CONFIG_STR(SvWelcome, sv_welcome, 64, "", CFGFLAG_SERVER, "Message that will be displayed to players who join the server")
MACRO_CONFIG_INT(ConnTimeout, conn_timeout, 100, 5, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT|CFGFLAG_SERVER, "Network timeout")
MACRO_CONFIG_INT(ConnTimeoutProtection, conn_timeout_protection, 1000, 5, 10000, CFGFLAG_SERVER, "Network timeout protection")
MACRO_CONFIG_INT(SvVoteMapTimeDelay, sv_vote_map_delay, 0, 0, 9999, CFGFLAG_SERVER, "The minimum time in seconds between map votes")
MACRO_CONFIG_INT(SvVoteDelay, sv_vote_delay, 3, 0, 9999, CFGFLAG_SERVER, "The time in seconds between any vote")
MACRO_CONFIG_INT(Events, events, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT|CFGFLAG_SERVER, "Enable triggering of events, like the happy eye emotes on some holidays.")
Expand Down
17 changes: 16 additions & 1 deletion src/engine/shared/network.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,6 @@ class CNetConnection
NETSTATS m_Stats;

//
void Reset();
void ResetStats();
void SetError(const char *pString);
void AckChunks(int Ack);
Expand All @@ -323,6 +322,7 @@ class CNetConnection
static TOKEN GenerateToken(const NETADDR *pPeerAddr);

public:
void Reset(bool Rejoin=false);
void Init(NETSOCKET Socket, bool BlockCloseMsg);
int Connect(NETADDR *pAddr);
void Disconnect(const char *pReason);
Expand Down Expand Up @@ -353,6 +353,14 @@ class CNetConnection
int64 ConnectTime() const { return m_LastUpdateTime; }

int AckSequence() const { return m_Ack; }

// DDRace

int SeqSequence() const { return m_Sequence; }
TStaticRingBuffer<CNetChunkResend, NET_CONN_BUFFERSIZE> *ResendBuffer() { return &m_Buffer; };
bool m_TimeoutProtected;
bool m_TimeoutSituation;
void SetTimedOut(const NETADDR *pAddr, int Sequence, int Ack, TOKEN Token, TStaticRingBuffer<CNetChunkResend, NET_CONN_BUFFERSIZE> *pResendBuffer);
};

class CConsoleNetConnection
Expand Down Expand Up @@ -465,6 +473,13 @@ class CNetServer

void SetMaxClients(int MaxClients);
void SetMaxClientsPerIP(int MaxClientsPerIP);

// DDRace

int ResetErrorString(int ClientID);
const char *ErrorString(int ClientID);
bool SetTimedOut(int ClientID, int OrigID);
void SetTimeoutProtected(int ClientID);
};

class CNetConsole
Expand Down
66 changes: 58 additions & 8 deletions src/engine/shared/network_conn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ void CNetConnection::ResetStats()
mem_zero(&m_Stats, sizeof(m_Stats));
}

void CNetConnection::Reset()
void CNetConnection::Reset(bool Rejoin)
{
m_Sequence = 0;
m_Ack = 0;
m_PeerAck = 0;
m_RemoteClosed = 0;

if(!Rejoin)
{
m_TimeoutProtected = false;
m_TimeoutSituation = false;
}

m_State = NET_CONNSTATE_OFFLINE;
m_LastSendTime = 0;
m_LastRecvTime = 0;
Expand Down Expand Up @@ -213,10 +219,13 @@ void CNetConnection::Disconnect(const char *pReason)

if(m_RemoteClosed == 0)
{
if(pReason)
SendControl(NET_CTRLMSG_CLOSE, pReason, str_length(pReason)+1);
else
SendControl(NET_CTRLMSG_CLOSE, 0, 0);
if(!m_TimeoutSituation)
{
if(pReason)
SendControl(NET_CTRLMSG_CLOSE, pReason, str_length(pReason)+1);
else
SendControl(NET_CTRLMSG_CLOSE, 0, 0);
}

if(pReason != m_ErrorString)
{
Expand Down Expand Up @@ -368,16 +377,25 @@ int CNetConnection::Update()
{
int64 Now = time_get();

if(State() == NET_CONNSTATE_ERROR && m_TimeoutSituation && (Now-m_LastRecvTime) > time_freq()*g_Config.m_ConnTimeoutProtection)
{
m_TimeoutSituation = false;
SetError("Timeout Protection over");
}

if(State() == NET_CONNSTATE_OFFLINE || State() == NET_CONNSTATE_ERROR)
return 0;

m_TimeoutSituation = false;

// check for timeout
if(State() != NET_CONNSTATE_OFFLINE &&
State() != NET_CONNSTATE_TOKEN &&
(Now-m_LastRecvTime) > time_freq()*10)
(Now-m_LastRecvTime) > time_freq()*g_Config.m_ConnTimeout)
{
m_State = NET_CONNSTATE_ERROR;
SetError("Timeout");
m_TimeoutSituation = true;
}
else if(State() == NET_CONNSTATE_TOKEN && (Now - m_LastRecvTime) > time_freq() * 5)
{
Expand All @@ -391,10 +409,13 @@ int CNetConnection::Update()
CNetChunkResend *pResend = m_Buffer.First();

// check if we have some really old stuff laying around and abort if not acked
if(Now-pResend->m_FirstSendTime > time_freq()*10)
if(Now-pResend->m_FirstSendTime > time_freq()*g_Config.m_ConnTimeout)
{
m_State = NET_CONNSTATE_ERROR;
SetError("Too weak connection (not acked for 10 seconds)");
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Too weak connection (not acked for %d seconds)", g_Config.m_ConnTimeout);
SetError(aBuf);
m_TimeoutSituation = true;
}
else
{
Expand Down Expand Up @@ -435,3 +456,32 @@ int CNetConnection::Update()

return 0;
}

void CNetConnection::SetTimedOut(const NETADDR *pAddr, int Sequence, int Ack, TOKEN Token, TStaticRingBuffer<CNetChunkResend, NET_CONN_BUFFERSIZE> *pResendBuffer)
{
int64 Now = time_get();

m_Sequence = Sequence;
m_Ack = Ack;
m_RemoteClosed = 0;

m_State = NET_CONNSTATE_ONLINE;
m_PeerAddr = *pAddr;
mem_zero(m_ErrorString, sizeof(m_ErrorString));
m_LastSendTime = Now;
m_LastRecvTime = Now;
m_LastUpdateTime = Now;
m_Token = Token;

// copy resend buffer
m_Buffer.Init();
while (pResendBuffer->First())
{
CNetChunkResend *First = pResendBuffer->First();

CNetChunkResend *pResend = m_Buffer.Allocate(sizeof(CNetChunkResend)+First->m_DataSize);
mem_copy(pResend, First, sizeof(CNetChunkResend)+First->m_DataSize);

pResendBuffer->PopFirst();
}
}
30 changes: 29 additions & 1 deletion src/engine/shared/network_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ int CNetServer::Update()
continue;

m_aSlots[i].m_Connection.Update();
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR &&
(!m_aSlots[i].m_Connection.m_TimeoutProtected ||
!m_aSlots[i].m_Connection.m_TimeoutSituation))
{
Drop(i, m_aSlots[i].m_Connection.ErrorString());
}
Expand Down Expand Up @@ -356,3 +358,29 @@ bool CNetServer::Connlimit(NETADDR Addr)
m_aSpamConns[Oldest].m_Conns = 1;
return false;
}

bool CNetServer::SetTimedOut(int ClientID, int OrigID)
{
if (m_aSlots[ClientID].m_Connection.State() != NET_CONNSTATE_ERROR)
return false;

m_aSlots[ClientID].m_Connection.SetTimedOut(ClientAddr(OrigID), m_aSlots[OrigID].m_Connection.SeqSequence(), m_aSlots[OrigID].m_Connection.AckSequence(), m_aSlots[OrigID].m_Connection.Token(), m_aSlots[OrigID].m_Connection.ResendBuffer());
m_aSlots[OrigID].m_Connection.Reset();
return true;
}

void CNetServer::SetTimeoutProtected(int ClientID)
{
m_aSlots[ClientID].m_Connection.m_TimeoutProtected = true;
}

int CNetServer::ResetErrorString(int ClientID)
{
m_aSlots[ClientID].m_Connection.ResetErrorString();
return 0;
}

const char *CNetServer::ErrorString(int ClientID)
{
return m_aSlots[ClientID].m_Connection.ErrorString();
}
34 changes: 33 additions & 1 deletion src/game/server/ddracechat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ void CGameContext::ConSettings(IConsole::IResult *pResult, void *pUserData)
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "settings",
"teams, collision, hooking, endlesshooking, me, ");
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "settings",
"hitting, oldlaser, votes, pause and scores");
"hitting, oldlaser, timeout, votes, pause and scores");
}
else
{
Expand Down Expand Up @@ -174,6 +174,11 @@ void CGameContext::ConSettings(IConsole::IResult *pResult, void *pUserData)
"Players can use /me commands the famous IRC Command" :
"Players can't use the /me command");
}
else if (str_comp(pArg, "timeout") == 0)
{
str_format(aBuf, sizeof(aBuf), "The Server Timeout is currently set to %d seconds", g_Config.m_ConnTimeout);
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "settings", aBuf);
}
else if (str_comp(pArg, "votes") == 0)
{
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "settings",
Expand Down Expand Up @@ -516,7 +521,34 @@ void CGameContext::ConPractice(IConsole::IResult *pResult, void *pUserData)
if(Teams.m_Core.Team(i) == Team)
pSelf->SendChatTarget(i, "Practice mode enabled for your team, happy practicing!");
}
}

void CGameContext::ConTimeout(IConsole::IResult *pResult, void *pUserData)
{
CGameContext *pSelf = (CGameContext *) pUserData;
if (!CheckClientID(pResult->m_ClientID))
return;

CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientID];
if (!pPlayer)
return;

const char* pTimeout = pResult->NumArguments() > 0 ? pResult->GetString(0) : pPlayer->m_TimeoutCode;

for(int i = 0; i < MAX_CLIENTS; i++)
{
if (i == pResult->m_ClientID) continue;
if (!pSelf->m_apPlayers[i]) continue;
if (str_comp(pSelf->m_apPlayers[i]->m_TimeoutCode, pTimeout)) continue;
if (pSelf->Server()->SetTimedOut(i, pResult->m_ClientID)) {
if (pSelf->m_apPlayers[i]->GetCharacter())
pSelf->SendTuningParams(i, pSelf->m_apPlayers[i]->GetCharacter()->m_TuneZone);
return;
}
}

pSelf->Server()->SetTimeoutProtected(pResult->m_ClientID);
str_copy(pPlayer->m_TimeoutCode, pResult->GetString(0), sizeof(pPlayer->m_TimeoutCode));
}

void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData)
Expand Down
1 change: 1 addition & 0 deletions src/game/server/ddracechat.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ CHAT_COMMAND("specvoted", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConToggleSpecVoted, t
CHAT_COMMAND("dnd", "", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConDND, this, "Toggle Do Not Disturb (no chat and server messages)")
CHAT_COMMAND("mapinfo", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConMapInfo, this, "Show info about the map with name r gives (current map by default)")
CHAT_COMMAND("practice", "?i['0'|'1']", CFGFLAG_CHAT|CFGFLAG_SERVER, ConPractice, this, "Enable cheats (currently only /rescue) for your current team's run, but you can't earn a rank")
CHAT_COMMAND("timeout", "?s[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTimeout, this, "Set timeout protection code s")
CHAT_COMMAND("save", "r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConSave, this, "Save team with code r to current server. To save to another server, use '/save s r' where s = server (case-sensitive: GER, RUS, etc) and r = code")
CHAT_COMMAND("load", "r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConLoad, this, "Load with code r")
CHAT_COMMAND("map", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConMap, this, "Vote a map by name")
Expand Down
1 change: 1 addition & 0 deletions src/game/server/gamecontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ class CGameContext : public IGameServer
static void ConDND(IConsole::IResult *pResult, void *pUserData);
static void ConMapInfo(IConsole::IResult* pResult, void* pUserData);
static void ConPractice(IConsole::IResult *pResult, void *pUserData);
static void ConTimeout(IConsole::IResult *pResult, void *pUserData);
static void ConSave(IConsole::IResult *pResult, void *pUserData);
static void ConLoad(IConsole::IResult *pResult, void *pUserData);
static void ConMap(IConsole::IResult *pResult, void *pUserData);
Expand Down
11 changes: 10 additions & 1 deletion src/game/server/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ void CPlayer::Reset()
m_IsReadyToPlay = false;
m_WeakHookSpawn = false;

// DDrace
// DDRace

m_LastCommandPos = 0;
m_LastPlaytime = time_get();
Expand All @@ -57,6 +57,7 @@ void CPlayer::Reset()
m_DefEmote = EMOTE_NORMAL;
m_Afk = false;
m_LastSetSpectatorMode = 0;
m_TimeoutCode[0] = '\0';

m_TuneZone = 0;
m_TuneZoneOld = m_TuneZone;
Expand Down Expand Up @@ -160,6 +161,14 @@ void CPlayer::Tick()
}
}

if(Server()->GetNetErrorString(m_ClientID)[0])
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "'%s' would have timed out, but can use timeout protection now", Server()->ClientName(m_ClientID));
GameServer()->SendChat(-1, CHAT_ALL, -1, aBuf);
Server()->ResetNetErrorString(m_ClientID);
}

if (!GameServer()->m_World.m_Paused)
{
int EarliestRespawnTick = m_PreviousDieTick + Server()->TickSpeed() * 3;
Expand Down
1 change: 1 addition & 0 deletions src/game/server/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class CPlayer

bool m_DND;
int64 m_FirstVoteTick;
char m_TimeoutCode[64];

void ProcessPause();
int Pause(int State, bool Force);
Expand Down