Compare commits

...

2 Commits

Author SHA1 Message Date
github-actions[bot]
880804d6fa chore(DB): import pending files
Referenced commit(s): 4cd1ed218147905532d3c43c524db716420367fc
2025-02-21 06:18:37 +00:00
Takenbacon
4cd1ed2181
refactor(Core/Server): Improvements to antidos opcode handling (#21502) 2025-02-21 07:17:34 +01:00
9 changed files with 414 additions and 419 deletions

View File

@ -0,0 +1,112 @@
-- DB update 2025_02_19_01 -> 2025_02_21_00
DROP TABLE IF EXISTS `antidos_opcode_policies`;
CREATE TABLE `antidos_opcode_policies` (
`Opcode` smallint unsigned NOT NULL,
`Policy` tinyint unsigned NOT NULL,
`MaxAllowedCount` smallint unsigned NOT NULL,
PRIMARY KEY (`Opcode`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `antidos_opcode_policies` (`Opcode`, `Policy`, `MaxAllowedCount`) VALUES
(393, 1, 200),
(404, 1, 200),
(398, 1, 200),
(102, 1, 200),
(1217, 1, 200),
(643, 1, 200),
(642, 1, 200),
(98, 1, 200),
(1192, 1, 200),
(1218, 1, 200),
(238, 1, 200),
(564, 1, 50),
(565, 1, 50),
(107, 1, 50),
(1065, 1, 50),
(999, 1, 50),
(1131, 1, 50),
(1153, 1, 50),
(177, 1, 50),
(450, 1, 50),
(483, 1, 25),
(1282, 1, 20),
(1016, 1, 20),
(1162, 1, 20),
(1133, 1, 20),
(448, 1, 10),
(452, 1, 10),
(638, 1, 10),
(454, 1, 10),
(1272, 1, 10),
(1139, 1, 10),
(1241, 1, 10),
(56, 1, 10),
(106, 1, 10),
(105, 1, 10),
(711, 1, 10),
(810, 1, 10),
(458, 1, 10),
(120, 1, 10),
(654, 1, 10),
(655, 1, 10),
(445, 1, 10),
(1179, 1, 10),
(1143, 1, 10),
(1144, 1, 10),
(1145, 1, 10),
(1142, 1, 10),
(1193, 1, 10),
(1204, 1, 10),
(839, 1, 10),
(467, 1, 10),
(996, 1, 10),
(600, 4, 5),
(612, 4, 5),
(601, 4, 5),
(54, 1, 3),
(55, 1, 3),
(517, 1, 3),
(519, 1, 3),
(535, 1, 3),
(1264, 1, 3),
(1069, 1, 3),
(1070, 1, 3),
(1071, 1, 3),
(1072, 1, 3),
(1073, 1, 3),
(1210, 1, 3),
(1074, 1, 3),
(1075, 1, 3),
(1077, 1, 3),
(847, 1, 3),
(849, 1, 3),
(850, 1, 3),
(851, 1, 3),
(853, 1, 3),
(852, 1, 3),
(854, 1, 3),
(122, 1, 3),
(130, 1, 3),
(132, 1, 3),
(133, 1, 3),
(141, 1, 3),
(143, 1, 3),
(144, 1, 3),
(145, 1, 3),
(561, 1, 3),
(562, 1, 3),
(563, 1, 3),
(764, 1, 3),
(1004, 1, 3),
(1005, 1, 3),
(1002, 1, 3),
(1003, 1, 3),
(1035, 1, 3),
(497, 1, 3),
(705, 1, 3),
(682, 1, 3),
(809, 1, 3),
(1259, 1, 3),
(910, 1, 3),
(802, 1, 3),
(1203, 1, 150);

View File

@ -1118,17 +1118,6 @@ PreventAFKLogout = 0
###################################################################################################
# PACKET SPOOF PROTECTION SETTINGS
#
# These settings determine which action to take when harmful packet spoofing is detected.
#
# PacketSpoof.Policy
# Description: Determines the course of action when packet spoofing is detected.
# Values: 0 - Log only
# 1 - Log + kick
# 2 - Log + kick + ban
PacketSpoof.Policy = 1
#
# PacketSpoof.BanMode
# Description: If PacketSpoof.Policy equals 2, this will determine the ban mode.

View File

@ -0,0 +1,76 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DatabaseEnv.h"
#include "Log.h"
#include "QueryResult.h"
#include "Timer.h"
#include "WorldGlobals.h"
WorldGlobals* WorldGlobals::instance()
{
static WorldGlobals instance;
return &instance;
}
void WorldGlobals::LoadAntiDosOpcodePolicies()
{
uint32 oldMSTime = getMSTime();
_antiDosOpcodePolicies = {};
QueryResult result = WorldDatabase.Query("SELECT Opcode, Policy, MaxAllowedCount FROM antidos_opcode_policies");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 AntiDos Opcode Policies. DB table `antidos_opcode_policies` is empty!");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint16 opcode = fields[0].Get<uint16>();
if (opcode >= NUM_OPCODE_HANDLERS)
{
LOG_ERROR("server.loading", "Unkown opcode {} in table `antidos_opcode_policies`, skipping.", opcode);
continue;
}
std::unique_ptr<AntiDosOpcodePolicy> policy = std::make_unique<AntiDosOpcodePolicy>();
policy->Policy = fields[1].Get<uint8>();
policy->MaxAllowedCount = fields[2].Get<uint16>();
_antiDosOpcodePolicies[opcode] = std::move(policy);
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} AntiDos Opcode Policies in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
AntiDosOpcodePolicy const* WorldGlobals::GetAntiDosPolicyForOpcode(uint16 opcode)
{
if (opcode >= NUM_OPCODE_HANDLERS)
return nullptr;
return _antiDosOpcodePolicies[opcode].get();
}

View File

@ -0,0 +1,44 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __WORLDGLOBALS_H
#define __WORLDGLOBALS_H
#include "Common.h"
#include "Opcodes.h"
struct AntiDosOpcodePolicy
{
uint8 Policy;
uint16 MaxAllowedCount;
};
class WorldGlobals
{
public:
static WorldGlobals* instance();
void LoadAntiDosOpcodePolicies();
AntiDosOpcodePolicy const* GetAntiDosPolicyForOpcode(uint16 opcode);
private:
std::array<std::unique_ptr<AntiDosOpcodePolicy>, NUM_OPCODE_HANDLERS> _antiDosOpcodePolicies;
};
#define sWorldGlobals WorldGlobals::instance()
#endif

View File

@ -49,6 +49,7 @@
#include "Vehicle.h"
#include "WardenWin.h"
#include "World.h"
#include "WorldGlobals.h"
#include "WorldPacket.h"
#include "WorldSocket.h"
#include "WorldState.h"
@ -330,138 +331,138 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
METRIC_DETAILED_TIMER("worldsession_update_opcode_time", METRIC_TAG("opcode", opHandle->Name));
LOG_DEBUG("network", "message id {} ({}) under READ", opcode, opHandle->Name);
try
WorldSession::DosProtection::Policy const evaluationPolicy = AntiDOS.EvaluateOpcode(*packet, currentTime);
switch (evaluationPolicy)
{
switch (opHandle->Status)
{
case STATUS_LOGGEDIN:
if (!_player)
{
// skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets
//! If player didn't log out a while ago, it means packets are being sent while the server does not recognize
//! the client to be in world yet. We will re-add the packets to the bottom of the queue and process them later.
if (!m_playerRecentlyLogout)
{
requeuePackets.push_back(packet);
deletePacket = false;
case WorldSession::DosProtection::Policy::Kick:
case WorldSession::DosProtection::Policy::Ban:
processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE;
break;
case WorldSession::DosProtection::Policy::BlockingThrottle:
requeuePackets.push_back(packet);
deletePacket = false;
processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE;
break;
default:
break;
}
LOG_DEBUG("network", "Delaying processing of message with status STATUS_LOGGEDIN: No players in the world for account id {}", GetAccountId());
}
}
else if (_player->IsInWorld())
if (evaluationPolicy == WorldSession::DosProtection::Policy::Process
|| evaluationPolicy == WorldSession::DosProtection::Policy::Log)
{
try
{
switch (opHandle->Status)
{
if (AntiDOS.EvaluateOpcode(*packet, currentTime))
case STATUS_LOGGEDIN:
if (!_player)
{
// skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets
//! If player didn't log out a while ago, it means packets are being sent while the server does not recognize
//! the client to be in world yet. We will re-add the packets to the bottom of the queue and process them later.
if (!m_playerRecentlyLogout)
{
requeuePackets.push_back(packet);
deletePacket = false;
LOG_DEBUG("network", "Delaying processing of message with status STATUS_LOGGEDIN: No players in the world for account id {}", GetAccountId());
}
}
else if (_player->IsInWorld())
{
if (!sScriptMgr->CanPacketReceive(this, *packet))
{
break;
}
opHandle->Call(this, *packet);
LogUnprocessedTail(packet);
}
else
processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop
}
// lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
break;
case STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT:
if (!_player && !m_playerRecentlyLogout) // There's a short delay between _player = null and m_playerRecentlyLogout = true during logout
{
LogUnexpectedOpcode(packet, "STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT",
"the player has not logged in yet and not recently logout");
}
else if (AntiDOS.EvaluateOpcode(*packet, currentTime))
{
// not expected _player or must checked in packet hanlder
if (!sScriptMgr->CanPacketReceive(this, *packet))
break;
opHandle->Call(this, *packet);
LogUnprocessedTail(packet);
}
else
processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop
break;
case STATUS_TRANSFER:
if (_player && !_player->IsInWorld() && AntiDOS.EvaluateOpcode(*packet, currentTime))
{
if (!sScriptMgr->CanPacketReceive(this, *packet))
{
break;
}
opHandle->Call(this, *packet);
LogUnprocessedTail(packet);
}
else
processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop
break;
case STATUS_AUTHED:
if (m_inQueue) // prevent cheating
// lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
break;
// some auth opcodes can be recieved before STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT opcodes
// however when we recieve CMSG_CHAR_ENUM we are surely no longer during the logout process.
if (packet->GetOpcode() == CMSG_CHAR_ENUM)
m_playerRecentlyLogout = false;
if (AntiDOS.EvaluateOpcode(*packet, currentTime))
{
if (!sScriptMgr->CanPacketReceive(this, *packet))
case STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT:
if (!_player && !m_playerRecentlyLogout) // There's a short delay between _player = null and m_playerRecentlyLogout = true during logout
{
break;
LogUnexpectedOpcode(packet, "STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT",
"the player has not logged in yet and not recently logout");
}
else
{
// not expected _player or must checked in packet hanlder
if (!sScriptMgr->CanPacketReceive(this, *packet))
break;
opHandle->Call(this, *packet);
LogUnprocessedTail(packet);
}
break;
case STATUS_TRANSFER:
if (_player && !_player->IsInWorld())
{
if (!sScriptMgr->CanPacketReceive(this, *packet))
break;
opHandle->Call(this, *packet);
LogUnprocessedTail(packet);
}
break;
case STATUS_AUTHED:
if (m_inQueue) // prevent cheating
break;
// some auth opcodes can be recieved before STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT opcodes
// however when we recieve CMSG_CHAR_ENUM we are surely no longer during the logout process.
if (packet->GetOpcode() == CMSG_CHAR_ENUM)
m_playerRecentlyLogout = false;
if (!sScriptMgr->CanPacketReceive(this, *packet))
break;
opHandle->Call(this, *packet);
LogUnprocessedTail(packet);
break;
case STATUS_NEVER:
LOG_ERROR("network.opcode", "Received not allowed opcode {} from {}",
GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), GetPlayerInfo());
break;
case STATUS_UNHANDLED:
LOG_DEBUG("network.opcode", "Received not handled opcode {} from {}",
GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), GetPlayerInfo());
break;
}
else
processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop
break;
case STATUS_NEVER:
LOG_ERROR("network.opcode", "Received not allowed opcode {} from {}",
GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), GetPlayerInfo());
break;
case STATUS_UNHANDLED:
LOG_DEBUG("network.opcode", "Received not handled opcode {} from {}",
GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), GetPlayerInfo());
break;
}
}
catch (WorldPackets::InvalidHyperlinkException const& ihe)
{
LOG_ERROR("network", "{} sent {} with an invalid link:\n{}", GetPlayerInfo(),
GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), ihe.GetInvalidValue());
catch (WorldPackets::InvalidHyperlinkException const& ihe)
{
LOG_ERROR("network", "{} sent {} with an invalid link:\n{}", GetPlayerInfo(),
GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), ihe.GetInvalidValue());
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
{
KickPlayer("WorldSession::Update Invalid chat link");
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
{
KickPlayer("WorldSession::Update Invalid chat link");
}
}
}
catch (WorldPackets::IllegalHyperlinkException const& ihe)
{
LOG_ERROR("network", "{} sent {} which illegally contained a hyperlink:\n{}", GetPlayerInfo(),
GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), ihe.GetInvalidValue());
catch (WorldPackets::IllegalHyperlinkException const& ihe)
{
LOG_ERROR("network", "{} sent {} which illegally contained a hyperlink:\n{}", GetPlayerInfo(),
GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), ihe.GetInvalidValue());
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
{
KickPlayer("WorldSession::Update Illegal chat link");
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
{
KickPlayer("WorldSession::Update Illegal chat link");
}
}
}
catch (WorldPackets::PacketArrayMaxCapacityException const& pamce)
{
LOG_ERROR("network", "PacketArrayMaxCapacityException: {} while parsing {} from {}.",
pamce.what(), GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), GetPlayerInfo());
}
catch (ByteBufferException const&)
{
LOG_ERROR("network", "WorldSession::Update ByteBufferException occured while parsing a packet (opcode: {}) from client {}, accountid={}. Skipped packet.", packet->GetOpcode(), GetRemoteAddress(), GetAccountId());
if (sLog->ShouldLog("network", LogLevel::LOG_LEVEL_DEBUG))
catch (WorldPackets::PacketArrayMaxCapacityException const& pamce)
{
LOG_DEBUG("network", "Dumping error causing packet:");
packet->hexlike();
LOG_ERROR("network", "PacketArrayMaxCapacityException: {} while parsing {} from {}.",
pamce.what(), GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())), GetPlayerInfo());
}
catch (ByteBufferException const&)
{
LOG_ERROR("network", "WorldSession::Update ByteBufferException occured while parsing a packet (opcode: {}) from client {}, accountid={}. Skipped packet.", packet->GetOpcode(), GetRemoteAddress(), GetAccountId());
if (sLog->ShouldLog("network", LogLevel::LOG_LEVEL_DEBUG))
{
LOG_DEBUG("network", "Dumping error causing packet:");
packet->hexlike();
}
}
}
@ -1324,14 +1325,17 @@ Warden* WorldSession::GetWarden()
return &(*_warden);
}
bool WorldSession::DosProtection::EvaluateOpcode(WorldPacket& p, time_t time) const
WorldSession::DosProtection::Policy WorldSession::DosProtection::EvaluateOpcode(WorldPacket const& p, time_t const time) const
{
uint32 maxPacketCounterAllowed = GetMaxPacketCounterAllowed(p.GetOpcode());
AntiDosOpcodePolicy const* policy = sWorldGlobals->GetAntiDosPolicyForOpcode(p.GetOpcode());
if (!policy)
return WorldSession::DosProtection::Policy::Process; // Return true if there is no policy for the opcode
// Return true if there no limit for the opcode
uint32 const maxPacketCounterAllowed = policy->MaxAllowedCount;
if (!maxPacketCounterAllowed)
return true;
return WorldSession::DosProtection::Policy::Process; // Return true if there no limit for the opcode
// packetCounter is opcodes handled in the same world second, so MaxAllowedCount is per second
PacketCounter& packetCounter = _PacketThrottlingMap[p.GetOpcode()];
if (packetCounter.lastReceiveTime != time)
{
@ -1341,298 +1345,57 @@ bool WorldSession::DosProtection::EvaluateOpcode(WorldPacket& p, time_t time) co
// Check if player is flooding some packets
if (++packetCounter.amountCounter <= maxPacketCounterAllowed)
return true;
return WorldSession::DosProtection::Policy::Process;
LOG_WARN("network", "AntiDOS: Account {}, IP: {}, Ping: {}, Character: {}, flooding packet (opc: {} (0x{:X}), count: {})",
Session->GetAccountId(), Session->GetRemoteAddress(), Session->GetLatency(), Session->GetPlayerName(),
opcodeTable[static_cast<OpcodeClient>(p.GetOpcode())]->Name, p.GetOpcode(), packetCounter.amountCounter);
switch (_policy)
if (WorldSession::DosProtection::Policy(policy->Policy) != WorldSession::DosProtection::Policy::BlockingThrottle)
{
case POLICY_LOG:
return true;
case POLICY_KICK:
{
LOG_INFO("network", "AntiDOS: Player {} kicked!", Session->GetPlayerName());
Session->KickPlayer();
return false;
}
case POLICY_BAN:
{
uint32 bm = sWorld->getIntConfig(CONFIG_PACKET_SPOOF_BANMODE);
uint32 duration = sWorld->getIntConfig(CONFIG_PACKET_SPOOF_BANDURATION); // in seconds
std::string nameOrIp = "";
switch (bm)
{
case 0: // Ban account
(void)AccountMgr::GetName(Session->GetAccountId(), nameOrIp);
sBan->BanAccount(nameOrIp, std::to_string(duration), "DOS (Packet Flooding/Spoofing", "Server: AutoDOS");
break;
case 1: // Ban ip
nameOrIp = Session->GetRemoteAddress();
sBan->BanIP(nameOrIp, std::to_string(duration), "DOS (Packet Flooding/Spoofing", "Server: AutoDOS");
break;
}
LOG_WARN("network", "AntiDOS: Account {}, IP: {}, Ping: {}, Character: {}, flooding packet (opc: {} (0x{:X}), count: {})",
Session->GetAccountId(), Session->GetRemoteAddress(), Session->GetLatency(), Session->GetPlayerName(),
opcodeTable[static_cast<OpcodeClient>(p.GetOpcode())]->Name, p.GetOpcode(), packetCounter.amountCounter);
}
LOG_INFO("network", "AntiDOS: Player automatically banned for {} seconds.", duration);
return false;
switch (WorldSession::DosProtection::Policy(policy->Policy))
{
case WorldSession::DosProtection::Policy::Kick:
{
LOG_INFO("network", "AntiDOS: Player {} kicked!", Session->GetPlayerName());
Session->KickPlayer();
break;
}
case WorldSession::DosProtection::Policy::Ban:
{
uint32 bm = sWorld->getIntConfig(CONFIG_PACKET_SPOOF_BANMODE);
uint32 duration = sWorld->getIntConfig(CONFIG_PACKET_SPOOF_BANDURATION); // in seconds
std::string nameOrIp = "";
switch (bm)
{
case 0: // Ban account
(void)AccountMgr::GetName(Session->GetAccountId(), nameOrIp);
sBan->BanAccount(nameOrIp, std::to_string(duration), "DOS (Packet Flooding/Spoofing", "Server: AutoDOS");
break;
case 1: // Ban ip
nameOrIp = Session->GetRemoteAddress();
sBan->BanIP(nameOrIp, std::to_string(duration), "DOS (Packet Flooding/Spoofing", "Server: AutoDOS");
break;
}
LOG_INFO("network", "AntiDOS: Player automatically banned for {} seconds.", duration);
break;
}
case WorldSession::DosProtection::Policy::DropPacket:
{
LOG_INFO("network", "AntiDOS: Opcode packet {} from player {} will be dropped.", p.GetOpcode(), Session->GetPlayerName());
break;
}
default: // invalid policy
return true;
}
}
uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) const
{
uint32 maxPacketCounterAllowed;
switch (opcode)
{
// CPU usage sending 2000 packets/second on a 3.70 GHz 4 cores on Win x64
// [% CPU mysqld] [%CPU worldserver RelWithDebInfo]
case CMSG_PLAYER_LOGIN: // 0 0.5
case CMSG_NAME_QUERY: // 0 1
case CMSG_PET_NAME_QUERY: // 0 1
case CMSG_NPC_TEXT_QUERY: // 0 1
case CMSG_ATTACKSTOP: // 0 1
case CMSG_QUERY_QUESTS_COMPLETED: // 0 1
case CMSG_QUERY_TIME: // 0 1
case CMSG_CORPSE_MAP_POSITION_QUERY: // 0 1
case CMSG_MOVE_TIME_SKIPPED: // 0 1
case MSG_QUERY_NEXT_MAIL_TIME: // 0 1
case CMSG_SET_SHEATHED: // 0 1
case MSG_RAID_TARGET_UPDATE: // 0 1
case CMSG_PLAYER_LOGOUT: // 0 1
case CMSG_LOGOUT_REQUEST: // 0 1
case CMSG_PET_RENAME: // 0 1
case CMSG_QUESTGIVER_CANCEL: // 0 1
case CMSG_QUESTGIVER_REQUEST_REWARD: // 0 1
case CMSG_COMPLETE_CINEMATIC: // 0 1
case CMSG_BANKER_ACTIVATE: // 0 1
case CMSG_BUY_BANK_SLOT: // 0 1
case CMSG_OPT_OUT_OF_LOOT: // 0 1
case CMSG_DUEL_ACCEPTED: // 0 1
case CMSG_DUEL_CANCELLED: // 0 1
case CMSG_CALENDAR_COMPLAIN: // 0 1
case CMSG_QUEST_QUERY: // 0 1.5
case CMSG_ITEM_QUERY_SINGLE: // 0 1.5
case CMSG_ITEM_NAME_QUERY: // 0 1.5
case CMSG_GAMEOBJECT_QUERY: // 0 1.5
case CMSG_CREATURE_QUERY: // 0 1.5
case CMSG_QUESTGIVER_STATUS_QUERY: // 0 1.5
case CMSG_GUILD_QUERY: // 0 1.5
case CMSG_ARENA_TEAM_QUERY: // 0 1.5
case CMSG_TAXINODE_STATUS_QUERY: // 0 1.5
case CMSG_TAXIQUERYAVAILABLENODES: // 0 1.5
case CMSG_QUESTGIVER_QUERY_QUEST: // 0 1.5
case CMSG_PAGE_TEXT_QUERY: // 0 1.5
case MSG_QUERY_GUILD_BANK_TEXT: // 0 1.5
case MSG_CORPSE_QUERY: // 0 1.5
case MSG_MOVE_SET_FACING: // 0 1.5
case CMSG_REQUEST_PARTY_MEMBER_STATS: // 0 1.5
case CMSG_QUESTGIVER_COMPLETE_QUEST: // 0 1.5
case CMSG_SET_ACTION_BUTTON: // 0 1.5
case CMSG_RESET_INSTANCES: // 0 1.5
case CMSG_HEARTH_AND_RESURRECT: // 0 1.5
case CMSG_TOGGLE_PVP: // 0 1.5
case CMSG_PET_ABANDON: // 0 1.5
case CMSG_ACTIVATETAXIEXPRESS: // 0 1.5
case CMSG_ACTIVATETAXI: // 0 1.5
case CMSG_SELF_RES: // 0 1.5
case CMSG_UNLEARN_SKILL: // 0 1.5
case CMSG_EQUIPMENT_SET_SAVE: // 0 1.5
case CMSG_DELETEEQUIPMENT_SET: // 0 1.5
case CMSG_DISMISS_CRITTER: // 0 1.5
case CMSG_REPOP_REQUEST: // 0 1.5
case CMSG_GROUP_INVITE: // 0 1.5
case CMSG_GROUP_DECLINE: // 0 1.5
case CMSG_GROUP_ACCEPT: // 0 1.5
case CMSG_GROUP_UNINVITE_GUID: // 0 1.5
case CMSG_GROUP_UNINVITE: // 0 1.5
case CMSG_GROUP_DISBAND: // 0 1.5
case CMSG_BATTLEMASTER_JOIN_ARENA: // 0 1.5
case CMSG_LEAVE_BATTLEFIELD: // 0 1.5
case MSG_GUILD_BANK_LOG_QUERY: // 0 2
case CMSG_LOGOUT_CANCEL: // 0 2
case CMSG_REALM_SPLIT: // 0 2
case CMSG_ALTER_APPEARANCE: // 0 2
case CMSG_QUEST_CONFIRM_ACCEPT: // 0 2
case MSG_GUILD_EVENT_LOG_QUERY: // 0 2.5
case CMSG_READY_FOR_ACCOUNT_DATA_TIMES: // 0 2.5
case CMSG_QUESTGIVER_STATUS_MULTIPLE_QUERY: // 0 2.5
case CMSG_BEGIN_TRADE: // 0 2.5
case CMSG_INITIATE_TRADE: // 0 3
case CMSG_MESSAGECHAT: // 0 3.5
case CMSG_INSPECT: // 0 3.5
case CMSG_AREA_SPIRIT_HEALER_QUERY: // not profiled
case CMSG_STANDSTATECHANGE: // not profiled
case MSG_RANDOM_ROLL: // not profiled
case CMSG_TIME_SYNC_RESP: // not profiled
case CMSG_TRAINER_BUY_SPELL: // not profiled
case CMSG_FORCE_SWIM_SPEED_CHANGE_ACK: // not profiled
case CMSG_FORCE_SWIM_BACK_SPEED_CHANGE_ACK: // not profiled
case CMSG_FORCE_RUN_SPEED_CHANGE_ACK: // not profiled
case CMSG_FORCE_RUN_BACK_SPEED_CHANGE_ACK: // not profiled
case CMSG_FORCE_FLIGHT_SPEED_CHANGE_ACK: // not profiled
case CMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE_ACK: // not profiled
case CMSG_FORCE_WALK_SPEED_CHANGE_ACK: // not profiled
case CMSG_FORCE_TURN_RATE_CHANGE_ACK: // not profiled
case CMSG_FORCE_PITCH_RATE_CHANGE_ACK: // not profiled
{
// "0" is a magic number meaning there's no limit for the opcode.
// All the opcodes above must cause little CPU usage and no sync/async database queries at all
maxPacketCounterAllowed = 0;
break;
}
case CMSG_QUESTGIVER_ACCEPT_QUEST: // 0 4
case CMSG_QUESTLOG_REMOVE_QUEST: // 0 4
case CMSG_QUESTGIVER_CHOOSE_REWARD: // 0 4
case CMSG_CONTACT_LIST: // 0 5
case CMSG_LEARN_PREVIEW_TALENTS: // 0 6
case CMSG_AUTOBANK_ITEM: // 0 6
case CMSG_AUTOSTORE_BANK_ITEM: // 0 6
case CMSG_WHO: // 0 7
case CMSG_PLAYER_VEHICLE_ENTER: // 0 8
case CMSG_LEARN_PREVIEW_TALENTS_PET: // not profiled
case MSG_MOVE_HEARTBEAT:
{
maxPacketCounterAllowed = 200;
break;
}
case CMSG_GUILD_SET_PUBLIC_NOTE: // 1 2 1 async db query
case CMSG_GUILD_SET_OFFICER_NOTE: // 1 2 1 async db query
case CMSG_SET_CONTACT_NOTES: // 1 2.5 1 async db query
case CMSG_CALENDAR_GET_CALENDAR: // 0 1.5 medium upload bandwidth usage
case CMSG_GUILD_BANK_QUERY_TAB: // 0 3.5 medium upload bandwidth usage
case CMSG_QUERY_INSPECT_ACHIEVEMENTS: // 0 13 high upload bandwidth usage
case CMSG_GAMEOBJ_REPORT_USE: // not profiled
case CMSG_GAMEOBJ_USE: // not profiled
case MSG_PETITION_DECLINE: // not profiled
{
maxPacketCounterAllowed = 50;
break;
}
case CMSG_QUEST_POI_QUERY: // 0 25 very high upload bandwidth usage
{
maxPacketCounterAllowed = MAX_QUEST_LOG_SIZE;
break;
}
case CMSG_GM_REPORT_LAG: // 1 3 1 async db query
case CMSG_SPELLCLICK: // not profiled
case CMSG_REMOVE_GLYPH: // not profiled
case CMSG_DISMISS_CONTROLLED_VEHICLE: // not profiled
{
maxPacketCounterAllowed = 20;
break;
}
case CMSG_PETITION_SIGN: // 9 4 2 sync 1 async db queries
case CMSG_TURN_IN_PETITION: // 8 5.5 2 sync db query
case CMSG_GROUP_CHANGE_SUB_GROUP: // 6 5 1 sync 1 async db queries
case CMSG_PETITION_QUERY: // 4 3.5 1 sync db query
case CMSG_CHAR_RACE_CHANGE: // 5 4 1 sync db query
case CMSG_CHAR_CUSTOMIZE: // 5 5 1 sync db query
case CMSG_CHAR_FACTION_CHANGE: // 5 5 1 sync db query
case CMSG_CHAR_DELETE: // 4 4 1 sync db query
case CMSG_DEL_FRIEND: // 7 5 1 async db query
case CMSG_ADD_FRIEND: // 6 4 1 async db query
case CMSG_CHAR_RENAME: // 5 3 1 async db query
case CMSG_GMSURVEY_SUBMIT: // 2 3 1 async db query
case CMSG_BUG: // 1 1 1 async db query
case CMSG_GROUP_SET_LEADER: // 1 2 1 async db query
case CMSG_GROUP_RAID_CONVERT: // 1 5 1 async db query
case CMSG_GROUP_ASSISTANT_LEADER: // 1 2 1 async db query
case CMSG_PETITION_BUY: // not profiled 1 sync 1 async db queries
case CMSG_CHANGE_SEATS_ON_CONTROLLED_VEHICLE: // not profiled
case CMSG_REQUEST_VEHICLE_PREV_SEAT: // not profiled
case CMSG_REQUEST_VEHICLE_NEXT_SEAT: // not profiled
case CMSG_REQUEST_VEHICLE_SWITCH_SEAT: // not profiled
case CMSG_REQUEST_VEHICLE_EXIT: // not profiled
case CMSG_CONTROLLER_EJECT_PASSENGER: // not profiled
case CMSG_ITEM_REFUND: // not profiled
case CMSG_SOCKET_GEMS: // not profiled
case CMSG_WRAP_ITEM: // not profiled
case CMSG_REPORT_PVP_AFK: // not profiled
case CMSG_AUCTION_LIST_ITEMS: // not profiled
case CMSG_AUCTION_LIST_BIDDER_ITEMS: // not profiled
case CMSG_AUCTION_LIST_OWNER_ITEMS: // not profiled
{
maxPacketCounterAllowed = 10;
break;
}
case CMSG_CHAR_CREATE: // 7 5 3 async db queries
case CMSG_CHAR_ENUM: // 22 3 2 async db queries
case CMSG_GMTICKET_CREATE: // 1 25 1 async db query
case CMSG_GMTICKET_UPDATETEXT: // 0 15 1 async db query
case CMSG_GMTICKET_DELETETICKET: // 1 25 1 async db query
case CMSG_GMRESPONSE_RESOLVE: // 1 25 1 async db query
case CMSG_CALENDAR_ADD_EVENT: // 21 10 2 async db query
case CMSG_CALENDAR_UPDATE_EVENT: // not profiled
case CMSG_CALENDAR_REMOVE_EVENT: // not profiled
case CMSG_CALENDAR_COPY_EVENT: // not profiled
case CMSG_CALENDAR_EVENT_INVITE: // not profiled
case CMSG_CALENDAR_EVENT_SIGNUP: // not profiled
case CMSG_CALENDAR_EVENT_RSVP: // not profiled
case CMSG_CALENDAR_EVENT_REMOVE_INVITE: // not profiled
case CMSG_CALENDAR_EVENT_MODERATOR_STATUS: // not profiled
case CMSG_ARENA_TEAM_INVITE: // not profiled
case CMSG_ARENA_TEAM_ACCEPT: // not profiled
case CMSG_ARENA_TEAM_DECLINE: // not profiled
case CMSG_ARENA_TEAM_LEAVE: // not profiled
case CMSG_ARENA_TEAM_DISBAND: // not profiled
case CMSG_ARENA_TEAM_REMOVE: // not profiled
case CMSG_ARENA_TEAM_LEADER: // not profiled
case CMSG_LOOT_METHOD: // not profiled
case CMSG_GUILD_INVITE: // not profiled
case CMSG_GUILD_ACCEPT: // not profiled
case CMSG_GUILD_DECLINE: // not profiled
case CMSG_GUILD_LEAVE: // not profiled
case CMSG_GUILD_DISBAND: // not profiled
case CMSG_GUILD_LEADER: // not profiled
case CMSG_GUILD_MOTD: // not profiled
case CMSG_GUILD_RANK: // not profiled
case CMSG_GUILD_ADD_RANK: // not profiled
case CMSG_GUILD_DEL_RANK: // not profiled
case CMSG_GUILD_INFO_TEXT: // not profiled
case CMSG_GUILD_BANK_DEPOSIT_MONEY: // not profiled
case CMSG_GUILD_BANK_WITHDRAW_MONEY: // not profiled
case CMSG_GUILD_BANK_BUY_TAB: // not profiled
case CMSG_GUILD_BANK_UPDATE_TAB: // not profiled
case CMSG_SET_GUILD_BANK_TEXT: // not profiled
case MSG_SAVE_GUILD_EMBLEM: // not profiled
case MSG_PETITION_RENAME: // not profiled
case MSG_TALENT_WIPE_CONFIRM: // not profiled
case MSG_SET_DUNGEON_DIFFICULTY: // not profiled
case MSG_SET_RAID_DIFFICULTY: // not profiled
case MSG_PARTY_ASSIGNMENT: // not profiled
case MSG_RAID_READY_CHECK: // not profiled
{
maxPacketCounterAllowed = 3;
break;
}
case CMSG_ITEM_REFUND_INFO: // not profiled
{
maxPacketCounterAllowed = PLAYER_SLOTS_COUNT;
break;
}
default:
{
maxPacketCounterAllowed = 100;
break;
}
break;
}
return maxPacketCounterAllowed;
return WorldSession::DosProtection::Policy(policy->Policy);
}
WorldSession::DosProtection::DosProtection(WorldSession* s) :
Session(s), _policy((Policy)sWorld->getIntConfig(CONFIG_PACKET_SPOOF_POLICY)) { }
Session(s) { }
void WorldSession::ResetTimeSync()
{

View File

@ -322,7 +322,7 @@ protected:
struct PacketCounter
{
time_t lastReceiveTime;
uint32 amountCounter;
uint16 amountCounter;
};
/// Player session in the World
@ -1104,22 +1104,21 @@ protected:
{
friend class World;
public:
DosProtection(WorldSession* s);
bool EvaluateOpcode(WorldPacket& p, time_t time) const;
protected:
enum Policy
enum class Policy
{
POLICY_LOG,
POLICY_KICK,
POLICY_BAN
Process,
Kick,
Ban,
Log,
BlockingThrottle,
DropPacket
};
uint32 GetMaxPacketCounterAllowed(uint16 opcode) const;
DosProtection(WorldSession* s);
Policy EvaluateOpcode(WorldPacket const& p, time_t const time) const;
protected:
WorldSession* Session;
private:
Policy _policy;
typedef std::unordered_map<uint16, PacketCounter> PacketThrottlingMap;
// mark this member as "mutable" so it can be modified even in const functions
mutable PacketThrottlingMap _PacketThrottlingMap;

View File

@ -366,7 +366,6 @@ enum WorldIntConfigs
CONFIG_WINTERGRASP_BATTLETIME,
CONFIG_WINTERGRASP_NOBATTLETIME,
CONFIG_WINTERGRASP_RESTART_AFTER_CRASH,
CONFIG_PACKET_SPOOF_POLICY,
CONFIG_PACKET_SPOOF_BANMODE,
CONFIG_PACKET_SPOOF_BANDURATION,
CONFIG_WARDEN_CLIENT_RESPONSE_DELAY,

View File

@ -90,6 +90,7 @@
#include "WaypointMovementGenerator.h"
#include "WeatherMgr.h"
#include "WhoListCacheMgr.h"
#include "WorldGlobals.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "WorldSessionMgr.h"
@ -1196,7 +1197,6 @@ void World::LoadConfigSettings(bool reload)
_bool_configs[CONFIG_SET_ALL_CREATURES_WITH_WAYPOINT_MOVEMENT_ACTIVE] = sConfigMgr->GetOption<bool>("SetAllCreaturesWithWaypointMovementActive", false);
// packet spoof punishment
_int_configs[CONFIG_PACKET_SPOOF_POLICY] = sConfigMgr->GetOption<int32>("PacketSpoof.Policy", (uint32)WorldSession::DosProtection::POLICY_KICK);
_int_configs[CONFIG_PACKET_SPOOF_BANMODE] = sConfigMgr->GetOption<int32>("PacketSpoof.BanMode", (uint32)0);
if (_int_configs[CONFIG_PACKET_SPOOF_BANMODE] > 1)
_int_configs[CONFIG_PACKET_SPOOF_BANMODE] = (uint32)0;
@ -1952,6 +1952,9 @@ void World::SetInitialWorldSettings()
LOG_INFO("server.loading", "Load Channels...");
ChannelMgr::LoadChannels();
LOG_INFO("server.loading", "Loading AntiDos opcode policies");
sWorldGlobals->LoadAntiDosOpcodePolicies();
sScriptMgr->OnBeforeWorldInitialized();
if (sWorld->getBoolConfig(CONFIG_PRELOAD_ALL_NON_INSTANCED_MAP_GRIDS))

View File

@ -47,6 +47,7 @@ EndScriptData */
#include "Tokenize.h"
#include "WardenCheckMgr.h"
#include "WaypointMgr.h"
#include "WorldGlobals.h"
using namespace Acore::ChatCommands;
@ -73,6 +74,7 @@ public:
};
static ChatCommandTable reloadCommandTable =
{
{ "antidos_opcode_policies", HandleReloadAntiDosOpcodePoliciesCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "auctions", HandleReloadAuctionsCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "dungeon_access_template", HandleReloadDungeonAccessCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "dungeon_access_requirements", HandleReloadDungeonAccessCommand, SEC_ADMINISTRATOR, Console::Yes },
@ -1199,6 +1201,14 @@ public:
return true;
}
static bool HandleReloadAntiDosOpcodePoliciesCommand(ChatHandler* handler)
{
LOG_INFO("server.loading", "Reloading AntiDos opcode policies...");
sWorldGlobals->LoadAntiDosOpcodePolicies();
handler->SendGlobalGMSysMessage("AntiDos opcode policies reloaded.");
return true;
}
static bool HandleReloadAuctionsCommand(ChatHandler* handler)
{
///- Reload dynamic data tables from the database