From 48bc69b028d997f31b678ddc2f485c66778b58ff Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Fri, 28 Apr 2023 23:47:58 +0200 Subject: [PATCH] Initial implementation of bulk ban checking Initial implementation of the bulk ban check system. This implementation takes a snapshot of the currently connected clients, sends it up to the master server, and the master server returns anything within this list that is marked 'banned'. The server would then kick the player from the server. This commit also removes the global banned list cache, as the bulk checking system offers a lot more freedom regarding banning specific players and have it sync across all available servers. --- r5dev/engine/host_state.cpp | 5 +- r5dev/engine/server/sv_main.cpp | 63 ++++++++++- r5dev/engine/server/sv_main.h | 1 + r5dev/networksystem/bansystem.cpp | 182 +++++++++--------------------- r5dev/networksystem/bansystem.h | 23 ++-- r5dev/networksystem/pylon.cpp | 51 ++++++++- r5dev/networksystem/pylon.h | 3 + 7 files changed, 178 insertions(+), 150 deletions(-) diff --git a/r5dev/engine/host_state.cpp b/r5dev/engine/host_state.cpp index 629bc452..e5969603 100644 --- a/r5dev/engine/host_state.cpp +++ b/r5dev/engine/host_state.cpp @@ -268,9 +268,10 @@ void CHostState::Think(void) const statsTimer.Start(); } - if (banListTimer.GetDurationInProgress().GetSeconds() > sv_banlistRefreshRate->GetDouble()) + if (sv_globalBanlist->GetBool() && + banListTimer.GetDurationInProgress().GetSeconds() > sv_banlistRefreshRate->GetDouble()) { - g_pBanSystem->BanListCheck(); + SV_CheckForBan(); banListTimer.Start(); } #ifdef DEDICATED diff --git a/r5dev/engine/server/sv_main.cpp b/r5dev/engine/server/sv_main.cpp index 05913471..ea7b72d7 100644 --- a/r5dev/engine/server/sv_main.cpp +++ b/r5dev/engine/server/sv_main.cpp @@ -8,6 +8,7 @@ #include "tier0/frametask.h" #include "tier1/cvar.h" #include "engine/server/sv_main.h" +#include "engine/client/client.h" #include "networksystem/pylon.h" #include "networksystem/bansystem.h" @@ -23,12 +24,68 @@ void SV_IsClientBanned(const string& svIPAddr, const uint64_t nNucleusID) { if (!ThreadInMainThread()) { - g_TaskScheduler->Dispatch([svError, nNucleusID] + g_TaskScheduler->Dispatch([svError, svIPAddr] { - g_pBanSystem->AddConnectionRefuse(svError, nNucleusID); // Add to the vector. + g_pBanSystem->KickPlayerById(svIPAddr.c_str(), svError.c_str()); }, 0); } - Warning(eDLL_T::SERVER, "Added '%s' to refused list ('%llu' is banned from the master server!)\n", svIPAddr.c_str(), nNucleusID); + + //Warning(eDLL_T::SERVER, "Added '%s' to refused list ('%llu' is banned from the master server!)\n", svIPAddr.c_str(), nNucleusID); + } +} + +void SV_ProcessBulkCheck(const BannedVec_t& bannedVec) +{ + BannedVec_t outBannedVec; + g_pMasterServer->GetBannedList(bannedVec, outBannedVec); + + if (!ThreadInMainThread()) + { + g_TaskScheduler->Dispatch([outBannedVec] + { + SV_CheckForBan(&outBannedVec); + }, 0); + } +} + +void SV_CheckForBan(const BannedVec_t* pBannedVec /*= nullptr*/) +{ + BannedVec_t bannedVec; + + for (int c = 0; c < MAX_PLAYERS; c++) // Loop through all possible client instances. + { + CClient* pClient = g_pClient->GetClient(c); + if (!pClient) + continue; + + CNetChan* pNetChan = pClient->GetNetChan(); + if (!pNetChan) + continue; + + if (!pClient->IsConnected()) + continue; + + const char* szIPAddr = pNetChan->GetAddress(); + const uint64_t nNucleusID = pClient->GetNucleusID(); + + if (!pBannedVec) + bannedVec.push_back(std::make_pair(szIPAddr, nNucleusID)); + else + { + for (auto& it : *pBannedVec) + { + if (it.second == pClient->GetNucleusID()) + { + Warning(eDLL_T::SERVER, "Removing client '%s' from slot '%i' ('%llu' is banned from this server!)\n", szIPAddr, c, nNucleusID); + pClient->Disconnect(Reputation_t::REP_MARK_BAD, "%s", it.first.c_str()); + } + } + } + } + + if (!pBannedVec && !bannedVec.empty()) + { + std::thread(&SV_ProcessBulkCheck, bannedVec).detach(); } } diff --git a/r5dev/engine/server/sv_main.h b/r5dev/engine/server/sv_main.h index 6f15d7fa..3d7bf8f3 100644 --- a/r5dev/engine/server/sv_main.h +++ b/r5dev/engine/server/sv_main.h @@ -28,6 +28,7 @@ void SV_InitGameDLL(); void SV_ShutdownGameDLL(); bool SV_ActivateServer(); void SV_IsClientBanned(const string& svIPAddr, const uint64_t nNucleusID); +void SV_CheckForBan(const BannedVec_t* pBannedVec = nullptr); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// diff --git a/r5dev/networksystem/bansystem.cpp b/r5dev/networksystem/bansystem.cpp index 4bc7ee8f..40d4c640 100644 --- a/r5dev/networksystem/bansystem.cpp +++ b/r5dev/networksystem/bansystem.cpp @@ -140,9 +140,7 @@ bool CBanSystem::DeleteEntry(const string& svIpAddress, const uint64_t nNucleusI if (it != m_vBanList.end()) { - DeleteConnectionRefuse(it->second); m_vBanList.erase(it); - return true; } } @@ -150,88 +148,6 @@ bool CBanSystem::DeleteEntry(const string& svIpAddress, const uint64_t nNucleusI return false; } -//----------------------------------------------------------------------------- -// Purpose: adds a connect refuse entry to the refused list -// Input : &svError - -// nNucleusID - -//----------------------------------------------------------------------------- -bool CBanSystem::AddConnectionRefuse(const string& svError, const uint64_t nNucleusID) -{ - if (IsRefuseListValid()) - { - auto it = std::find_if(m_vRefuseList.begin(), m_vRefuseList.end(), - [&](const pair& element) { return element.second == nNucleusID; }); - - if (it == m_vRefuseList.end()) - { - m_vRefuseList.push_back(std::make_pair(svError, nNucleusID)); - return true; - } - } - else - { - m_vRefuseList.push_back(std::make_pair(svError, nNucleusID)); - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: deletes an entry in the refused list -// Input : nNucleusID - -//----------------------------------------------------------------------------- -bool CBanSystem::DeleteConnectionRefuse(const uint64_t nNucleusID) -{ - if (IsRefuseListValid()) - { - auto it = std::find_if(m_vRefuseList.begin(), m_vRefuseList.end(), - [&](const pair& element) { return element.second == nNucleusID; }); - - if (it != m_vRefuseList.end()) - { - m_vRefuseList.erase(it); - return true; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Check refuse list and kill netchan connection. -//----------------------------------------------------------------------------- -void CBanSystem::BanListCheck(void) -{ - if (!IsRefuseListValid()) - return; - - for (size_t i = 0; i < m_vRefuseList.size(); i++) - { - for (int c = 0; c < MAX_PLAYERS; c++) // Loop through all possible client instances. - { - CClient* pClient = g_pClient->GetClient(c); - if (!pClient) - continue; - - CNetChan* pNetChan = pClient->GetNetChan(); - if (!pNetChan) - continue; - - if (!pClient->IsConnected()) - continue; - - if (pClient->GetNucleusID() != m_vRefuseList[i].second) - continue; - - string svIpAddress = pNetChan->GetAddress(); - - Warning(eDLL_T::SERVER, "Removing client '%s' from slot '%i' ('%llu' is banned from this server!)\n", svIpAddress.c_str(), c, pClient->GetNucleusID()); - pClient->Disconnect(Reputation_t::REP_MARK_BAD, "%s", m_vRefuseList[i].first.c_str()); - } - } -} - //----------------------------------------------------------------------------- // Purpose: checks if specified ip address or nucleus id is banned // Input : &svIpAddress - @@ -261,14 +177,6 @@ bool CBanSystem::IsBanned(const string& svIpAddress, const uint64_t nNucleusID) return false; } -//----------------------------------------------------------------------------- -// Purpose: checks if refused list is valid -//----------------------------------------------------------------------------- -bool CBanSystem::IsRefuseListValid(void) const -{ - return !m_vRefuseList.empty(); -} - //----------------------------------------------------------------------------- // Purpose: checks if banned list is valid //----------------------------------------------------------------------------- @@ -279,50 +187,54 @@ bool CBanSystem::IsBanListValid(void) const //----------------------------------------------------------------------------- // Purpose: kicks a player by given name -// Input : &svPlayerName - +// Input : *playerName - +// *reason - //----------------------------------------------------------------------------- -void CBanSystem::KickPlayerByName(const string& svPlayerName) +void CBanSystem::KickPlayerByName(const char* playerName, const char* reason) { - if (svPlayerName.empty()) + if (!VALID_CHARSTAR(playerName)) return; - AuthorPlayerByName(svPlayerName, false); + AuthorPlayerByName(playerName, false); } //----------------------------------------------------------------------------- // Purpose: kicks a player by given handle or id -// Input : &svHandle - +// Input : *playerHandle - +// *reason - //----------------------------------------------------------------------------- -void CBanSystem::KickPlayerById(const string& svHandle) +void CBanSystem::KickPlayerById(const char* playerHandle, const char* reason) { - if (svHandle.empty()) + if (!VALID_CHARSTAR(playerHandle)) return; - AuthorPlayerById(svHandle, false); + AuthorPlayerById(playerHandle, false); } //----------------------------------------------------------------------------- // Purpose: bans a player by given name -// Input : &svPlayerName - +// Input : *playerName - +// *reason - //----------------------------------------------------------------------------- -void CBanSystem::BanPlayerByName(const string& svPlayerName) +void CBanSystem::BanPlayerByName(const char* playerName, const char* reason) { - if (svPlayerName.empty()) + if (!VALID_CHARSTAR(playerName)) return; - AuthorPlayerByName(svPlayerName, true); + AuthorPlayerByName(playerName, true); } //----------------------------------------------------------------------------- // Purpose: bans a player by given handle or id -// Input : &svHandle - +// Input : *playerHandle - +// *reason - //----------------------------------------------------------------------------- -void CBanSystem::BanPlayerById(const string& svHandle) +void CBanSystem::BanPlayerById(const char* playerHandle, const char* reason) { - if (svHandle.empty()) + if (!VALID_CHARSTAR(playerHandle)) return; - AuthorPlayerById(svHandle, true); + AuthorPlayerById(playerHandle, true); } //----------------------------------------------------------------------------- @@ -364,15 +276,19 @@ void CBanSystem::UnbanPlayer(const string& svCriteria) //----------------------------------------------------------------------------- // Purpose: authors player by given name -// Input : &svPlayerName - -// bBan - (only kicks if false) +// Input : *playerName - +// shouldBan - (only kicks if false) +// *reason - //----------------------------------------------------------------------------- -void CBanSystem::AuthorPlayerByName(const string& svPlayerName, const bool bBan) +void CBanSystem::AuthorPlayerByName(const char* playerName, const bool shouldBan, const char* reason) { - Assert(!svPlayerName.empty()); + Assert(VALID_CHARSTAR(playerName)); bool bDisconnect = false; bool bSave = false; + if (!reason) + reason = shouldBan ? "Banned from server" : "Kicked from server"; + for (int i = 0; i < MAX_PLAYERS; i++) { CClient* pClient = g_pClient->GetClient(i); @@ -385,12 +301,12 @@ void CBanSystem::AuthorPlayerByName(const string& svPlayerName, const bool bBan) if (strlen(pNetChan->GetName()) > 0) { - if (svPlayerName.compare(pNetChan->GetName()) == NULL) // Our wanted name? + if (strcmp(playerName, pNetChan->GetName()) == NULL) // Our wanted name? { - if (bBan && AddEntry(pNetChan->GetAddress(), pClient->GetNucleusID()) && !bSave) + if (shouldBan && AddEntry(pNetChan->GetAddress(), pClient->GetNucleusID()) && !bSave) bSave = true; - pClient->Disconnect(REP_MARK_BAD, bBan ? "Banned from server" : "Kicked from server"); + pClient->Disconnect(REP_MARK_BAD, reason); bDisconnect = true; } } @@ -399,29 +315,33 @@ void CBanSystem::AuthorPlayerByName(const string& svPlayerName, const bool bBan) if (bSave) { Save(); - DevMsg(eDLL_T::SERVER, "Added '%s' to banned list\n", svPlayerName.c_str()); + DevMsg(eDLL_T::SERVER, "Added '%s' to banned list\n", playerName); } else if (bDisconnect) { - DevMsg(eDLL_T::SERVER, "Kicked '%s' from server\n", svPlayerName.c_str()); + DevMsg(eDLL_T::SERVER, "Kicked '%s' from server\n", playerName); } } //----------------------------------------------------------------------------- // Purpose: authors player by given nucleus id or ip address -// Input : &svHandle - -// bBan - (only kicks if false) +// Input : *playerHandle - +// shouldBan - (only kicks if false) +// *reason - //----------------------------------------------------------------------------- -void CBanSystem::AuthorPlayerById(const string& svHandle, const bool bBan) +void CBanSystem::AuthorPlayerById(const char* playerHandle, const bool shouldBan, const char* reason) { - Assert(!svHandle.empty()); + Assert(VALID_CHARSTAR(playerHandle)); try { - bool bOnlyDigits = StringIsDigit(svHandle); + bool bOnlyDigits = StringIsDigit(playerHandle); bool bDisconnect = false; bool bSave = false; + if (!reason) + reason = shouldBan ? "Banned from server" : "Kicked from server"; + for (int i = 0; i < MAX_PLAYERS; i++) { CClient* pClient = g_pClient->GetClient(i); @@ -434,7 +354,7 @@ void CBanSystem::AuthorPlayerById(const string& svHandle, const bool bBan) if (bOnlyDigits) { - uint64_t nTargetID = static_cast(std::stoll(svHandle)); + uint64_t nTargetID = static_cast(std::stoll(playerHandle)); if (nTargetID > static_cast(MAX_PLAYERS)) // Is it a possible nucleusID? { uint64_t nNucleusID = pClient->GetNucleusID(); @@ -448,21 +368,23 @@ void CBanSystem::AuthorPlayerById(const string& svHandle, const bool bBan) continue; } - if (bBan && AddEntry(pNetChan->GetAddress(), pClient->GetNucleusID()) && !bSave) + if (shouldBan && AddEntry(pNetChan->GetAddress(), pClient->GetNucleusID()) && !bSave) bSave = true; - pClient->Disconnect(REP_MARK_BAD, bBan ? "Banned from server" : "Kicked from server"); + + + pClient->Disconnect(REP_MARK_BAD, reason); bDisconnect = true; } else { - if (svHandle.compare(pNetChan->GetAddress()) != NULL) + if (strcmp(playerHandle, pNetChan->GetAddress()) != NULL) continue; - if (bBan && AddEntry(pNetChan->GetAddress(), pClient->GetNucleusID()) && !bSave) + if (shouldBan && AddEntry(pNetChan->GetAddress(), pClient->GetNucleusID()) && !bSave) bSave = true; - pClient->Disconnect(REP_MARK_BAD, bBan ? "Banned from server" : "Kicked from server"); + pClient->Disconnect(REP_MARK_BAD, reason); bDisconnect = true; } } @@ -470,11 +392,11 @@ void CBanSystem::AuthorPlayerById(const string& svHandle, const bool bBan) if (bSave) { Save(); - DevMsg(eDLL_T::SERVER, "Added '%s' to banned list\n", svHandle.c_str()); + DevMsg(eDLL_T::SERVER, "Added '%s' to banned list\n", playerHandle); } else if (bDisconnect) { - DevMsg(eDLL_T::SERVER, "Kicked '%s' from server\n", svHandle.c_str()); + DevMsg(eDLL_T::SERVER, "Kicked '%s' from server\n", playerHandle); } } catch (const std::exception& e) diff --git a/r5dev/networksystem/bansystem.h b/r5dev/networksystem/bansystem.h index 3874ac5b..c266bb9c 100644 --- a/r5dev/networksystem/bansystem.h +++ b/r5dev/networksystem/bansystem.h @@ -1,5 +1,7 @@ #pragma once +typedef vector> BannedVec_t; + class CBanSystem { public: @@ -9,29 +11,22 @@ public: bool AddEntry(const string& svIpAddress, const uint64_t nNucleusID); bool DeleteEntry(const string& svIpAddress, const uint64_t nNucleusID); - bool AddConnectionRefuse(const string& svError, const uint64_t nNucleusID); - bool DeleteConnectionRefuse(const uint64_t nNucleusID); - - void BanListCheck(void); - bool IsBanned(const string& svIpAddress, const uint64_t nNucleusID) const; - bool IsRefuseListValid(void) const; bool IsBanListValid(void) const; - void KickPlayerByName(const string& svPlayerName); - void KickPlayerById(const string& svHandle); + void KickPlayerByName(const char* playerName, const char* reason = nullptr); + void KickPlayerById(const char* playerHandle, const char* reason = nullptr); - void BanPlayerByName(const string& svPlayerName); - void BanPlayerById(const string& svHandle); + void BanPlayerByName(const char* playerName, const char* reason = nullptr); + void BanPlayerById(const char* playerHandle, const char* reason = nullptr); void UnbanPlayer(const string& svCriteria); private: - void AuthorPlayerByName(const string& svPlayerName, const bool bBan); - void AuthorPlayerById(const string& svHandle, const bool bBan); + void AuthorPlayerByName(const char* playerName, const bool bBan, const char* reason = nullptr); + void AuthorPlayerById(const char* playerHandle, const bool bBan, const char* reason = nullptr); - vector> m_vRefuseList; - vector> m_vBanList; + BannedVec_t m_vBanList; }; extern CBanSystem* g_pBanSystem; diff --git a/r5dev/networksystem/pylon.cpp b/r5dev/networksystem/pylon.cpp index 85a0000c..842e1643 100644 --- a/r5dev/networksystem/pylon.cpp +++ b/r5dev/networksystem/pylon.cpp @@ -209,12 +209,61 @@ bool CPylon::KeepAlive(const NetGameServer_t& netGameServer) } #endif // DEDICATED +//----------------------------------------------------------------------------- +// Purpose: Checks a list of clients for their banned status. +// Input : &inBannedVec - +// &outBannedVec - +// Output : True on success, false otherwise. +//----------------------------------------------------------------------------- +bool CPylon::GetBannedList(const BannedVec_t& inBannedVec, BannedVec_t& outBannedVec) const +{ + nlohmann::json arrayJson = nlohmann::json::array(); + + for (const auto& bannedPair : inBannedVec) + { + nlohmann::json player; + player["id"] = bannedPair.second; + player["ip"] = bannedPair.first; + arrayJson.push_back(player); + } + + nlohmann::json playerArray; + playerArray["players"] = arrayJson; + + string outMessage; + CURLINFO status; + + if (!SendRequest("/banlist/bulkCheck", playerArray, + arrayJson, outMessage, status, "banned bulk check error")) + { + return false; + } + + if (!arrayJson.contains("bannedPlayers")) + { + outMessage = Format("Invalid response with status: %d", int(status)); + return false; + } + + for (auto& obj : arrayJson["bannedPlayers"]) + { + outBannedVec.push_back( + std::make_pair( + obj.value("reason", "#DISCONNECT_BANNED"), + obj.value("id", uint64_t(0)) + ) + ); + } + + return true; +} + //----------------------------------------------------------------------------- // Purpose: Checks if client is banned on the comp server. // Input : &ipAddress - // nucleusId - // &outReason - <- contains banned reason if any. -// Output : Returns true if banned, false if not banned. +// Output : True if banned, false if not banned. //----------------------------------------------------------------------------- bool CPylon::CheckForBan(const string& ipAddress, const uint64_t nucleusId, string& outReason) const { diff --git a/r5dev/networksystem/pylon.h b/r5dev/networksystem/pylon.h index 39b3f279..c73eb909 100644 --- a/r5dev/networksystem/pylon.h +++ b/r5dev/networksystem/pylon.h @@ -1,5 +1,6 @@ #pragma once #include "thirdparty/curl/include/curl/curl.h" +#include "bansystem.h" #include "serverlisting.h" class CPylon @@ -8,6 +9,8 @@ public: vector GetServerList(string& outMessage) const; bool GetServerByToken(NetGameServer_t& slOutServer, string& outMessage, const string& svToken) const; bool PostServerHost(string& outMessage, string& svOutToken, const NetGameServer_t& netGameServer) const; + + bool GetBannedList(const BannedVec_t& inBannedVec, BannedVec_t& outBannedVec) const; bool CheckForBan(const string& ipAddress, const uint64_t nucleusId, string& outReason) const; void ExtractError(const nlohmann::json& resultBody, string& outMessage, CURLINFO status, const char* errorText = nullptr) const;