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.
This commit is contained in:
Kawe Mazidjatari 2023-04-28 23:47:58 +02:00
parent fcc4b410f3
commit 48bc69b028
7 changed files with 178 additions and 150 deletions

View File

@ -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

View File

@ -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();
}
}

View File

@ -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);
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

View File

@ -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<const string, const uint64_t>& 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<const string, const uint64_t>& 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<uint64_t>(std::stoll(svHandle));
uint64_t nTargetID = static_cast<uint64_t>(std::stoll(playerHandle));
if (nTargetID > static_cast<uint64_t>(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)

View File

@ -1,5 +1,7 @@
#pragma once
typedef vector<std::pair<string, uint64_t>> 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<std::pair<string, uint64_t>> m_vRefuseList;
vector<std::pair<string, uint64_t>> m_vBanList;
BannedVec_t m_vBanList;
};
extern CBanSystem* g_pBanSystem;

View File

@ -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
{

View File

@ -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<NetGameServer_t> 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;