From f52050d9986bfaac18b6f843cecb9abc648363c1 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Sun, 29 Dec 2019 17:39:13 +0100 Subject: [PATCH] Add timeout protection --- src/engine/server.h | 7 ++- src/engine/server/server.cpp | 14 +++++- src/engine/server/server.h | 7 ++- src/engine/shared/config_variables.h | 4 +- src/engine/shared/network.h | 17 ++++++- src/engine/shared/network_conn.cpp | 66 ++++++++++++++++++++++++---- src/engine/shared/network_server.cpp | 30 ++++++++++++- src/game/server/ddracechat.cpp | 34 +++++++++++++- src/game/server/ddracechat.h | 1 + src/game/server/gamecontext.h | 1 + src/game/server/player.cpp | 11 ++++- src/game/server/player.h | 1 + 12 files changed, 177 insertions(+), 16 deletions(-) diff --git a/src/engine/server.h b/src/engine/server.h index 24990ca6..e782d340 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -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; @@ -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 diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 065f74c2..5cb41c01 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -2514,7 +2514,7 @@ int main(int argc, const char **argv) // ignore_convention return Ret; } -// DDrace +// DDRace void CServer::GetClientAddr(int ClientID, NETADDR* pAddr) { @@ -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) diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 9fd96401..5d370076 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -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); diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 8addae2f..5bda87af 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -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") @@ -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.") diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index b9b0ac25..a513845c 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -309,7 +309,6 @@ class CNetConnection NETSTATS m_Stats; // - void Reset(); void ResetStats(); void SetError(const char *pString); void AckChunks(int Ack); @@ -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); @@ -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 *ResendBuffer() { return &m_Buffer; }; + bool m_TimeoutProtected; + bool m_TimeoutSituation; + void SetTimedOut(const NETADDR *pAddr, int Sequence, int Ack, TOKEN Token, TStaticRingBuffer *pResendBuffer); }; class CConsoleNetConnection @@ -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 diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp index f1c2020b..c3e12d3a 100644 --- a/src/engine/shared/network_conn.cpp +++ b/src/engine/shared/network_conn.cpp @@ -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; @@ -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) { @@ -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) { @@ -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 { @@ -435,3 +456,32 @@ int CNetConnection::Update() return 0; } + +void CNetConnection::SetTimedOut(const NETADDR *pAddr, int Sequence, int Ack, TOKEN Token, TStaticRingBuffer *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(); + } +} diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index a4a34831..85eba34d 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -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()); } @@ -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(); +} diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index d49048d3..861a5fb2 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -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 { @@ -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", @@ -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) diff --git a/src/game/server/ddracechat.h b/src/game/server/ddracechat.h index 66ef16a1..d2aa09a5 100644 --- a/src/game/server/ddracechat.h +++ b/src/game/server/ddracechat.h @@ -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") diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 59def90b..ded83faa 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -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); diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index cf2d91d4..49fd4b3a 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -46,7 +46,7 @@ void CPlayer::Reset() m_IsReadyToPlay = false; m_WeakHookSpawn = false; - // DDrace + // DDRace m_LastCommandPos = 0; m_LastPlaytime = time_get(); @@ -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; @@ -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; diff --git a/src/game/server/player.h b/src/game/server/player.h index f5e6c1cd..66d87173 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -153,6 +153,7 @@ class CPlayer bool m_DND; int64 m_FirstVoteTick; + char m_TimeoutCode[64]; void ProcessPause(); int Pause(int State, bool Force);