code cleanup

removed firewall_manager and experimentals
This commit is contained in:
rtw1x1
2026-02-12 23:57:32 +00:00
parent 4f9b84d3e9
commit c8da3dc6c5
14 changed files with 6 additions and 502 deletions

View File

@@ -119,10 +119,6 @@ bool SecureCipher::ComputeServerKeys(const uint8_t* client_pk)
sodium_memzero(m_rx_stream_nonce, NONCE_SIZE); sodium_memzero(m_rx_stream_nonce, NONCE_SIZE);
m_rx_stream_nonce[0] = 0x02; m_rx_stream_nonce[0] = 0x02;
sys_log(0, "[CIPHER] Server keys computed (tx_key: %02x%02x%02x%02x, rx_key: %02x%02x%02x%02x)",
m_tx_key[0], m_tx_key[1], m_tx_key[2], m_tx_key[3],
m_rx_key[0], m_rx_key[1], m_rx_key[2], m_rx_key[3]);
return true; return true;
} }

View File

@@ -101,14 +101,6 @@ bool g_bCheckMultiHack = true;
int g_iSpamBlockMaxLevel = 10; int g_iSpamBlockMaxLevel = 10;
int g_iFloodMaxPacketsPerSec = 300;
int g_iFloodMaxConnectionsPerIP = 10;
int g_iFloodMaxGlobalConnections = 8192;
bool g_bFirewallEnable = false;
int g_iFirewallTcpSynLimit = 500;
int g_iFirewallTcpSynBurst = 1000;
void LoadStateUserCount(); void LoadStateUserCount();
void LoadValidCRCList(); void LoadValidCRCList();
bool LoadClientVersion(); bool LoadClientVersion();
@@ -703,41 +695,6 @@ void config_init(const string& st_localeServiceName)
str_to_number(g_iSpamBlockMaxLevel, value_string); str_to_number(g_iSpamBlockMaxLevel, value_string);
} }
TOKEN("flood_max_packets_per_sec")
{
str_to_number(g_iFloodMaxPacketsPerSec, value_string);
g_iFloodMaxPacketsPerSec = MAX(50, g_iFloodMaxPacketsPerSec);
}
TOKEN("flood_max_connections_per_ip")
{
str_to_number(g_iFloodMaxConnectionsPerIP, value_string);
g_iFloodMaxConnectionsPerIP = MAX(1, g_iFloodMaxConnectionsPerIP);
}
TOKEN("flood_max_global_connections")
{
str_to_number(g_iFloodMaxGlobalConnections, value_string);
g_iFloodMaxGlobalConnections = MAX(64, g_iFloodMaxGlobalConnections);
}
TOKEN("firewall_enable")
{
str_to_number(g_bFirewallEnable, value_string);
}
TOKEN("firewall_tcp_syn_limit")
{
str_to_number(g_iFirewallTcpSynLimit, value_string);
g_iFirewallTcpSynLimit = MAX(10, g_iFirewallTcpSynLimit);
}
TOKEN("firewall_tcp_syn_burst")
{
str_to_number(g_iFirewallTcpSynBurst, value_string);
g_iFirewallTcpSynBurst = MAX(10, g_iFirewallTcpSynBurst);
}
TOKEN("protect_normal_player") TOKEN("protect_normal_player")
{ {
str_to_number(g_protectNormalPlayer, value_string); str_to_number(g_protectNormalPlayer, value_string);

View File

@@ -105,9 +105,5 @@ extern int gPlayerMaxLevel;
extern bool g_BlockCharCreation; extern bool g_BlockCharCreation;
extern bool g_bFirewallEnable;
extern int g_iFirewallTcpSynLimit;
extern int g_iFirewallTcpSynBurst;
#endif /* __INC_METIN_II_GAME_CONFIG_H__ */ #endif /* __INC_METIN_II_GAME_CONFIG_H__ */

View File

@@ -81,10 +81,6 @@ void DESC::Initialize()
m_offtime = 0; m_offtime = 0;
m_pkDisconnectEvent = NULL; m_pkDisconnectEvent = NULL;
m_iFloodCheckPulse = 0;
m_dwFloodPacketCount = 0;
m_bIPCountTracked = false;
} }
void DESC::Destroy() void DESC::Destroy()
@@ -216,7 +212,6 @@ bool DESC::Setup(LPFDWATCH _fdw, socket_t _fd, const struct sockaddr_in & c_rSoc
m_pkPingEvent = event_create(ping_event, info, ping_event_second_cycle); m_pkPingEvent = event_create(ping_event, info, ping_event_second_cycle);
// Set Phase to handshake and begin secure key exchange
SetPhase(PHASE_HANDSHAKE); SetPhase(PHASE_HANDSHAKE);
m_handshake_time = get_dword_time(); m_handshake_time = get_dword_time();
SendKeyChallenge(); SendKeyChallenge();
@@ -242,7 +237,6 @@ int DESC::ProcessInput()
else if (bytes_read == 0) else if (bytes_read == 0)
return 0; return 0;
// Decrypt only the newly received bytes before committing to the buffer
if (m_secureCipher.IsActivated()) { if (m_secureCipher.IsActivated()) {
m_secureCipher.DecryptInPlace(m_inputBuffer.WritePtr(), bytes_read); m_secureCipher.DecryptInPlace(m_inputBuffer.WritePtr(), bytes_read);
} }
@@ -328,7 +322,6 @@ void DESC::Packet(const void * c_pvData, int iSize)
if (m_iPhase == PHASE_CLOSE) if (m_iPhase == PHASE_CLOSE)
return; return;
// Log the packet for sequence tracking (only for real packet sends, not buffered flushes)
if (!m_hasBufferedOutput && iSize >= (int)sizeof(uint16_t) * 2) if (!m_hasBufferedOutput && iSize >= (int)sizeof(uint16_t) * 2)
{ {
const uint16_t wHeader = *static_cast<const uint16_t*>(c_pvData); const uint16_t wHeader = *static_cast<const uint16_t*>(c_pvData);
@@ -465,43 +458,13 @@ bool DESC::IsExpiredHandshake() const
return (m_handshake_time + (5 * 1000)) < get_dword_time(); return (m_handshake_time + (5 * 1000)) < get_dword_time();
} }
bool DESC::CheckPacketFlood()
{
extern int g_iFloodMaxPacketsPerSec;
// Use thecore_pulse() (cached per game-loop iteration) instead of
// get_dword_time() (gettimeofday syscall) to avoid per-packet syscall overhead.
int pulse = thecore_pulse();
int pps = static_cast<int>(thecore_pulse_per_second());
if (pulse - m_iFloodCheckPulse >= pps)
{
m_iFloodCheckPulse = pulse;
m_dwFloodPacketCount = 1;
return false;
}
++m_dwFloodPacketCount;
if (m_dwFloodPacketCount > (uint32_t)g_iFloodMaxPacketsPerSec)
{
sys_log(0, "FLOOD: %s exceeded %d packets/sec (count: %u), disconnecting",
GetHostName(), g_iFloodMaxPacketsPerSec, m_dwFloodPacketCount);
return true;
}
return false;
}
DWORD DESC::GetClientTime() DWORD DESC::GetClientTime()
{ {
return m_dwClientTime; return m_dwClientTime;
} }
// Secure key exchange methods (libsodium/XChaCha20-Poly1305)
void DESC::SendKeyChallenge() void DESC::SendKeyChallenge()
{ {
// Initialize cipher and generate keypair
if (!m_secureCipher.Initialize()) if (!m_secureCipher.Initialize())
{ {
sys_err("Failed to initialize SecureCipher"); sys_err("Failed to initialize SecureCipher");
@@ -509,10 +472,8 @@ void DESC::SendKeyChallenge()
return; return;
} }
// Generate challenge
m_secureCipher.GenerateChallenge(m_challenge); m_secureCipher.GenerateChallenge(m_challenge);
// Build and send challenge packet
TPacketGCKeyChallenge packet; TPacketGCKeyChallenge packet;
packet.header = GC::KEY_CHALLENGE; packet.header = GC::KEY_CHALLENGE;
packet.length = sizeof(packet); packet.length = sizeof(packet);
@@ -529,14 +490,12 @@ void DESC::SendKeyChallenge()
bool DESC::HandleKeyResponse(const uint8_t* client_pk, const uint8_t* challenge_response) bool DESC::HandleKeyResponse(const uint8_t* client_pk, const uint8_t* challenge_response)
{ {
// Compute session keys from client's public key
if (!m_secureCipher.ComputeServerKeys(client_pk)) if (!m_secureCipher.ComputeServerKeys(client_pk))
{ {
sys_err("Failed to compute server session keys for %s", GetHostName()); sys_err("Failed to compute server session keys for %s", GetHostName());
return false; return false;
} }
// Verify challenge response
if (!m_secureCipher.VerifyChallengeResponse(m_challenge, challenge_response)) if (!m_secureCipher.VerifyChallengeResponse(m_challenge, challenge_response))
{ {
sys_err("Challenge response verification failed for %s", GetHostName()); sys_err("Challenge response verification failed for %s", GetHostName());
@@ -550,17 +509,14 @@ bool DESC::HandleKeyResponse(const uint8_t* client_pk, const uint8_t* challenge_
void DESC::SendKeyComplete() void DESC::SendKeyComplete()
{ {
// Generate session token
uint8_t session_token[SecureCipher::SESSION_TOKEN_SIZE]; uint8_t session_token[SecureCipher::SESSION_TOKEN_SIZE];
randombytes_buf(session_token, sizeof(session_token)); randombytes_buf(session_token, sizeof(session_token));
m_secureCipher.SetSessionToken(session_token); m_secureCipher.SetSessionToken(session_token);
// Build and send complete packet
TPacketGCKeyComplete packet; TPacketGCKeyComplete packet;
packet.header = GC::KEY_COMPLETE; packet.header = GC::KEY_COMPLETE;
packet.length = sizeof(packet); packet.length = sizeof(packet);
// Encrypt the session token
if (!m_secureCipher.EncryptToken(session_token, sizeof(session_token), if (!m_secureCipher.EncryptToken(session_token, sizeof(session_token),
packet.encrypted_token, packet.nonce)) packet.encrypted_token, packet.nonce))
{ {
@@ -571,10 +527,7 @@ void DESC::SendKeyComplete()
Packet(&packet, sizeof(packet)); Packet(&packet, sizeof(packet));
// Flush before activating encryption
ProcessOutput(); ProcessOutput();
// Activate encryption
m_secureCipher.SetActivated(true); m_secureCipher.SetActivated(true);
sys_log(0, "[HANDSHAKE] Cipher ACTIVATED for %s (tx_nonce: %llu, rx_nonce: %llu)", sys_log(0, "[HANDSHAKE] Cipher ACTIVATED for %s (tx_nonce: %llu, rx_nonce: %llu)",
@@ -852,8 +805,6 @@ void DESC::ChatPacket(BYTE type, const char * format, ...)
Packet(buf.read_peek(), buf.size()); Packet(buf.read_peek(), buf.size());
} }
// --- Packet sequence tracking ---
void DESC::LogRecvPacket(uint16_t header, uint16_t length) void DESC::LogRecvPacket(uint16_t header, uint16_t length)
{ {
auto& e = m_aRecentRecvPackets[m_dwRecvPacketSeq % PACKET_LOG_SIZE]; auto& e = m_aRecentRecvPackets[m_dwRecvPacketSeq % PACKET_LOG_SIZE];

View File

@@ -104,7 +104,6 @@ class DESC
DWORD GetClientTime(); DWORD GetClientTime();
// Secure key exchange (libsodium/XChaCha20-Poly1305)
void SendKeyChallenge(); void SendKeyChallenge();
bool HandleKeyResponse(const uint8_t* client_pk, const uint8_t* challenge_response); bool HandleKeyResponse(const uint8_t* client_pk, const uint8_t* challenge_response);
void SendKeyComplete(); void SendKeyComplete();
@@ -142,15 +141,9 @@ class DESC
bool isChannelStatusRequested() const { return m_bChannelStatusRequested; } bool isChannelStatusRequested() const { return m_bChannelStatusRequested; }
void SetChannelStatusRequested(bool bChannelStatusRequested) { m_bChannelStatusRequested = bChannelStatusRequested; } void SetChannelStatusRequested(bool bChannelStatusRequested) { m_bChannelStatusRequested = bChannelStatusRequested; }
// Handshake timeout check
bool IsExpiredHandshake() const; bool IsExpiredHandshake() const;
void SetHandshakeTime(uint32_t handshake_time) { m_handshake_time = handshake_time; } void SetHandshakeTime(uint32_t handshake_time) { m_handshake_time = handshake_time; }
// Flood protection
bool CheckPacketFlood();
void SetIPCountTracked(bool b) { m_bIPCountTracked = b; }
bool IsIPCountTracked() const { return m_bIPCountTracked; }
protected: protected:
void Initialize(); void Initialize();
@@ -215,15 +208,7 @@ class DESC
bool m_bDestroyed; bool m_bDestroyed;
bool m_bChannelStatusRequested; bool m_bChannelStatusRequested;
// Handshake timeout protection
uint32_t m_handshake_time; uint32_t m_handshake_time;
// Flood protection
int m_iFloodCheckPulse;
uint32_t m_dwFloodPacketCount;
bool m_bIPCountTracked;
// Secure cipher (libsodium/XChaCha20-Poly1305)
SecureCipher m_secureCipher; SecureCipher m_secureCipher;
uint8_t m_challenge[SecureCipher::CHALLENGE_SIZE]; uint8_t m_challenge[SecureCipher::CHALLENGE_SIZE];
@@ -242,7 +227,6 @@ class DESC
void RawPacket(const void * c_pvData, int iSize); void RawPacket(const void * c_pvData, int iSize);
void ChatPacket(BYTE type, const char * format, ...); void ChatPacket(BYTE type, const char * format, ...);
// --- Packet sequence tracking (debug aid) ---
public: public:
struct PacketLogEntry struct PacketLogEntry
{ {

View File

@@ -88,27 +88,6 @@ LPDESC DESC_MANAGER::AcceptDesc(LPFDWATCH fdw, socket_t s)
strlcpy(host, inet_ntoa(peer.sin_addr), sizeof(host)); strlcpy(host, inet_ntoa(peer.sin_addr), sizeof(host));
// Global connection limit
extern int g_iFloodMaxGlobalConnections;
if (m_iSocketsConnected >= g_iFloodMaxGlobalConnections)
{
sys_log(0, "FLOOD: rejecting connection from %s (global limit %d/%d reached)",
host, m_iSocketsConnected, g_iFloodMaxGlobalConnections);
socket_close(desc);
return NULL;
}
// Per-IP connection limit
extern int g_iFloodMaxConnectionsPerIP;
auto itIP = m_map_ipConnCount.find(host);
if (itIP != m_map_ipConnCount.end() && itIP->second >= g_iFloodMaxConnectionsPerIP)
{
sys_log(0, "FLOOD: rejecting connection from %s (%d/%d connections)",
host, itIP->second, g_iFloodMaxConnectionsPerIP);
socket_close(desc);
return NULL;
}
newd = M2_NEW DESC; newd = M2_NEW DESC;
if (!newd->Setup(fdw, desc, peer, ++m_iHandleCount)) if (!newd->Setup(fdw, desc, peer, ++m_iHandleCount))
@@ -123,10 +102,6 @@ LPDESC DESC_MANAGER::AcceptDesc(LPFDWATCH fdw, socket_t s)
m_set_pkDesc.insert(newd); m_set_pkDesc.insert(newd);
++m_iSocketsConnected; ++m_iSocketsConnected;
// Track per-IP count
++m_map_ipConnCount[host];
newd->SetIPCountTracked(true);
return (newd); return (newd);
} }
@@ -181,17 +156,6 @@ void DESC_MANAGER::DestroyDesc(LPDESC d, bool bEraseFromSet)
else else
m_set_pkClientDesc.erase((LPCLIENT_DESC) d); m_set_pkClientDesc.erase((LPCLIENT_DESC) d);
// Decrement per-IP connection count (before Destroy invalidates state)
if (d->IsIPCountTracked())
{
auto it = m_map_ipConnCount.find(d->GetHostName());
if (it != m_map_ipConnCount.end())
{
if (--it->second <= 0)
m_map_ipConnCount.erase(it);
}
}
// Explicit call to the virtual function Destroy() // Explicit call to the virtual function Destroy()
d->Destroy(); d->Destroy();
@@ -419,7 +383,7 @@ DWORD DESC_MANAGER::CreateLoginKey(LPDESC d)
do do
{ {
dwKey = randombytes_uniform(INT_MAX) + 1; // CSPRNG: [1, INT_MAX] dwKey = randombytes_uniform(INT_MAX) + 1;
if (m_map_pkLoginKey.find(dwKey) != m_map_pkLoginKey.end()) if (m_map_pkLoginKey.find(dwKey) != m_map_pkLoginKey.end())
continue; continue;
@@ -445,7 +409,6 @@ void DESC_MANAGER::ProcessExpiredLoginKey()
{ {
it2 = it++; it2 = it++;
// Clean up orphaned keys (descriptor gone but never expired)
if (it2->second->m_dwExpireTime == 0 && it2->second->m_pkDesc == NULL) if (it2->second->m_dwExpireTime == 0 && it2->second->m_pkDesc == NULL)
{ {
M2_DELETE(it2->second); M2_DELETE(it2->second);

View File

@@ -67,8 +67,6 @@ class DESC_MANAGER : public singleton<DESC_MANAGER>
CLIENT_DESC_SET m_set_pkClientDesc; CLIENT_DESC_SET m_set_pkClientDesc;
DESC_SET m_set_pkDesc; DESC_SET m_set_pkDesc;
std::unordered_map<std::string, int> m_map_ipConnCount;
DESC_HANDLE_MAP m_map_handle; DESC_HANDLE_MAP m_map_handle;
//DESC_ACCOUNTID_MAP m_AccountIDMap; //DESC_ACCOUNTID_MAP m_AccountIDMap;
DESC_LOGINNAME_MAP m_map_loginName; DESC_LOGINNAME_MAP m_map_loginName;

View File

@@ -1,227 +0,0 @@
#include "stdafx.h"
#include "config.h"
#include "firewall_manager.h"
#ifndef OS_WINDOWS
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <signal.h>
#include <sys/wait.h>
#endif
extern bool g_bFirewallEnable;
extern int g_iFirewallTcpSynLimit;
extern int g_iFirewallTcpSynBurst;
FirewallManager::FirewallManager() = default;
FirewallManager::~FirewallManager() = default;
#ifdef OS_WINDOWS
// Windows: no-op stubs
bool FirewallManager::Initialize(WORD, WORD) { return false; }
void FirewallManager::Destroy() {}
#else
bool FirewallManager::RunCommand(const char* fmt, ...)
{
char cmd[512];
va_list args;
va_start(args, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, args);
va_end(args);
// Temporarily set SIGCHLD to SIG_DFL before calling system().
// The game server sets SIGCHLD to SIG_IGN (auto-reap children), which
// causes waitpid() inside system() to return -1/ECHILD on FreeBSD
// because the child gets reaped before status can be collected.
struct sigaction saved, dflt;
memset(&dflt, 0, sizeof(dflt));
dflt.sa_handler = SIG_DFL;
sigemptyset(&dflt.sa_mask);
sigaction(SIGCHLD, &dflt, &saved);
int status = system(cmd);
sigaction(SIGCHLD, &saved, NULL);
if (status == -1)
{
sys_err("FirewallManager: system() failed for: %s", cmd);
return false;
}
return (WIFEXITED(status) && WEXITSTATUS(status) == 0);
}
#ifdef OS_FREEBSD
// ---------------------------------------------------------------
// FreeBSD: ipfw
// Uses deterministic rule numbers based on port to avoid conflicts
// between multiple game processes on the same machine.
// Rule base = 50000 + (gamePort % 1000) * 10
// ---------------------------------------------------------------
void FirewallManager::CleanupRules()
{
if (m_ruleBase == 0)
return;
for (int i = 0; i < 10; ++i)
RunCommand("/sbin/ipfw -q delete %d 2>/dev/null", m_ruleBase + i);
m_ruleCount = 0;
}
bool FirewallManager::Initialize(WORD gamePort, WORD p2pPort)
{
if (!g_bFirewallEnable)
return false;
m_ruleBase = 50000 + (gamePort % 1000) * 10;
// Clean up stale rules from previous crash
CleanupRules();
// Test if ipfw is available (use absolute path — popen() may have minimal PATH)
if (!RunCommand("/sbin/ipfw -q list >/dev/null 2>&1"))
{
sys_err("FirewallManager: ipfw not available (module not loaded? try: kldload ipfw)");
m_ruleBase = 0;
return false;
}
int r = m_ruleBase;
// Drop inbound UDP to game and P2P ports
RunCommand("/sbin/ipfw -q add %d deny udp from any to me dst-port %d in", r++, gamePort);
RunCommand("/sbin/ipfw -q add %d deny udp from any to me dst-port %d in", r++, p2pPort);
// Drop ICMP port-unreachable (type 3)
RunCommand("/sbin/ipfw -q add %d deny icmp from any to me icmptypes 3 in", r++);
// TCP SYN rate limiting on game port (per-source concurrent connection limit)
RunCommand("/sbin/ipfw -q add %d allow tcp from any to me dst-port %d setup limit src-addr %d",
r++, gamePort, g_iFirewallTcpSynLimit);
RunCommand("/sbin/ipfw -q add %d deny tcp from any to me dst-port %d setup",
r++, gamePort);
// TCP SYN rate limiting on P2P port
RunCommand("/sbin/ipfw -q add %d allow tcp from any to me dst-port %d setup limit src-addr %d",
r++, p2pPort, g_iFirewallTcpSynLimit);
RunCommand("/sbin/ipfw -q add %d deny tcp from any to me dst-port %d setup",
r++, p2pPort);
m_ruleCount = r - m_ruleBase;
m_initialized = true;
sys_log(0, "FirewallManager: ipfw rules %d-%d installed (UDP DROP ports %d/%d, TCP SYN limit %d/src)",
m_ruleBase, r - 1, gamePort, p2pPort, g_iFirewallTcpSynLimit);
return true;
}
#else
// ---------------------------------------------------------------
// Linux: iptables
// Uses a dedicated chain per process (e.g., M2_GUARD_11011)
// ---------------------------------------------------------------
void FirewallManager::CleanupRules()
{
if (m_chainName.empty())
return;
// Unhook from INPUT (ignore failure — may not exist)
RunCommand("iptables -D INPUT -j %s 2>/dev/null", m_chainName.c_str());
// Flush and delete the chain (ignore failure)
RunCommand("iptables -F %s 2>/dev/null", m_chainName.c_str());
RunCommand("iptables -X %s 2>/dev/null", m_chainName.c_str());
}
bool FirewallManager::Initialize(WORD gamePort, WORD p2pPort)
{
if (!g_bFirewallEnable)
return false;
// Chain name includes port to avoid conflicts with multi-channel servers
char buf[64];
snprintf(buf, sizeof(buf), "M2_GUARD_%d", gamePort);
m_chainName = buf;
// Always clean up stale rules first (handles crash recovery)
CleanupRules();
// Create fresh chain
if (!RunCommand("iptables -N %s", m_chainName.c_str()))
{
sys_err("FirewallManager: failed to create chain %s (not root? iptables not installed?)", m_chainName.c_str());
m_chainName.clear();
return false;
}
// Allow established/related UDP (e.g. DNS replies from outbound queries)
RunCommand("iptables -A %s -p udp -m state --state ESTABLISHED,RELATED -j ACCEPT",
m_chainName.c_str());
// DROP all unsolicited inbound UDP
RunCommand("iptables -A %s -p udp -j DROP", m_chainName.c_str());
// Rate-limit ICMP to prevent reflection attacks
RunCommand("iptables -A %s -p icmp --icmp-type port-unreachable -j DROP",
m_chainName.c_str());
RunCommand("iptables -A %s -p icmp -m limit --limit 10/s --limit-burst 20 -j ACCEPT",
m_chainName.c_str());
RunCommand("iptables -A %s -p icmp -j DROP", m_chainName.c_str());
// TCP SYN flood protection on game port
RunCommand("iptables -A %s -p tcp --dport %d --syn -m limit --limit %d/s --limit-burst %d -j ACCEPT",
m_chainName.c_str(), gamePort, g_iFirewallTcpSynLimit, g_iFirewallTcpSynBurst);
RunCommand("iptables -A %s -p tcp --dport %d --syn -j DROP",
m_chainName.c_str(), gamePort);
// TCP SYN flood protection on P2P port
RunCommand("iptables -A %s -p tcp --dport %d --syn -m limit --limit %d/s --limit-burst %d -j ACCEPT",
m_chainName.c_str(), p2pPort, g_iFirewallTcpSynLimit, g_iFirewallTcpSynBurst);
RunCommand("iptables -A %s -p tcp --dport %d --syn -j DROP",
m_chainName.c_str(), p2pPort);
// Hook chain into INPUT
if (!RunCommand("iptables -A INPUT -j %s", m_chainName.c_str()))
{
sys_err("FirewallManager: failed to hook chain into INPUT");
CleanupRules();
m_chainName.clear();
return false;
}
m_initialized = true;
sys_log(0, "FirewallManager: chain %s installed (UDP DROP, TCP SYN limit %d/s burst %d)",
m_chainName.c_str(), g_iFirewallTcpSynLimit, g_iFirewallTcpSynBurst);
return true;
}
#endif // OS_FREEBSD
void FirewallManager::Destroy()
{
if (!m_initialized)
return;
CleanupRules();
m_initialized = false;
#ifdef OS_FREEBSD
sys_log(0, "FirewallManager: ipfw rules %d-%d removed", m_ruleBase, m_ruleBase + m_ruleCount - 1);
m_ruleBase = 0;
m_ruleCount = 0;
#else
sys_log(0, "FirewallManager: chain %s removed", m_chainName.c_str());
m_chainName.clear();
#endif
}
#endif // !OS_WINDOWS

View File

@@ -1,33 +0,0 @@
#ifndef __INC_FIREWALL_MANAGER_H__
#define __INC_FIREWALL_MANAGER_H__
// Kernel-level firewall management.
// Linux: iptables chains. FreeBSD: ipfw rules.
// Installs DROP rules for unsolicited UDP and rate-limits TCP SYN floods.
// Windows: no-op stubs.
class FirewallManager : public singleton<FirewallManager>
{
public:
FirewallManager();
~FirewallManager();
// Install firewall rules — call after config_init()
bool Initialize(WORD gamePort, WORD p2pPort);
// Remove firewall rules — call during shutdown
void Destroy();
private:
#ifndef OS_WINDOWS
bool RunCommand(const char* fmt, ...);
void CleanupRules();
#endif
std::string m_chainName; // Linux: iptables chain name
int m_ruleBase = 0; // FreeBSD: ipfw rule number base
int m_ruleCount = 0; // FreeBSD: number of rules installed
bool m_initialized = false;
};
#endif

View File

@@ -131,12 +131,6 @@ bool CInputProcessor::Process(LPDESC lpDesc, const void * c_pvOrig, int iBytes,
if (wHeader) if (wHeader)
{ {
if (lpDesc->CheckPacketFlood())
{
lpDesc->SetPhase(PHASE_CLOSE);
return true;
}
m_pPacketInfo->Start(); m_pPacketInfo->Start();
int iExtraPacketSize = Analyze(lpDesc, wHeader, c_pData); int iExtraPacketSize = Analyze(lpDesc, wHeader, c_pData);

View File

@@ -128,7 +128,6 @@ class CInputMain : public CInputProcessor
protected: protected:
int Analyze(LPDESC d, uint16_t wHeader, const char * c_pData) override; int Analyze(LPDESC d, uint16_t wHeader, const char * c_pData) override;
// Handler dispatch
using MainHandler = int (CInputMain::*)(LPDESC, const char*); using MainHandler = int (CInputMain::*)(LPDESC, const char*);
struct HandlerEntry { struct HandlerEntry {
MainHandler handler; MainHandler handler;
@@ -137,14 +136,12 @@ class CInputMain : public CInputProcessor
std::unordered_map<uint16_t, HandlerEntry> m_handlers; std::unordered_map<uint16_t, HandlerEntry> m_handlers;
virtual void RegisterHandlers(); virtual void RegisterHandlers();
// Template adapters for common handler patterns (defined in input_main.cpp)
template<void (CInputMain::*fn)(LPCHARACTER, const char*)> template<void (CInputMain::*fn)(LPCHARACTER, const char*)>
int SimpleHandler(LPDESC d, const char* p); int SimpleHandler(LPDESC d, const char* p);
template<void (CInputMain::*fn)(LPCHARACTER, const void*)> template<void (CInputMain::*fn)(LPCHARACTER, const void*)>
int SimpleHandlerV(LPDESC d, const char* p); int SimpleHandlerV(LPDESC d, const char* p);
// Custom adapters for non-standard handler signatures
int HandlePong(LPDESC d, const char* p); int HandlePong(LPDESC d, const char* p);
int HandleChat(LPDESC d, const char* p); int HandleChat(LPDESC d, const char* p);
int HandleWhisper(LPDESC d, const char* p); int HandleWhisper(LPDESC d, const char* p);
@@ -262,19 +259,14 @@ protected:
void RegisterHandlers(); void RegisterHandlers();
LPDESC FindByHandle() const; LPDESC FindByHandle() const;
// Template: void handler(const char*) — data-only
template<void (CInputDB::*fn)(const char*)> template<void (CInputDB::*fn)(const char*)>
int DataHandler(LPDESC, const char* p) { (this->*fn)(p); return 0; } int DataHandler(LPDESC, const char* p) { (this->*fn)(p); return 0; }
// Template: void handler(LPDESC, const char*) — desc from FindByHandle
template<void (CInputDB::*fn)(LPDESC, const char*)> template<void (CInputDB::*fn)(LPDESC, const char*)>
int DescHandler(LPDESC, const char* p) { (this->*fn)(FindByHandle(), p); return 0; } int DescHandler(LPDESC, const char* p) { (this->*fn)(FindByHandle(), p); return 0; }
// Template: void handler(T*) — cast c_pData to typed pointer
template<class T, void (CInputDB::*fn)(T*)> template<class T, void (CInputDB::*fn)(T*)>
int TypedHandler(LPDESC, const char* p) { (this->*fn)((T*)p); return 0; } int TypedHandler(LPDESC, const char* p) { (this->*fn)((T*)p); return 0; }
// Custom adapters for special signatures
int HandleLoginSuccess(LPDESC, const char*); int HandleLoginSuccess(LPDESC, const char*);
int HandleLoginNotExist(LPDESC, const char*); int HandleLoginNotExist(LPDESC, const char*);
int HandleLoginWrongPasswd(LPDESC, const char*); int HandleLoginWrongPasswd(LPDESC, const char*);

View File

@@ -1729,7 +1729,6 @@ void CInputMain::Attack(LPCHARACTER ch, const uint16_t header, const char* data)
if (NULL == ch) if (NULL == ch)
return; return;
// Updated for new packet format: [header:2][length:2][bType:1]
struct type_identifier struct type_identifier
{ {
uint16_t header; uint16_t header;
@@ -2673,7 +2672,7 @@ int CInputMain::Guild(LPCHARACTER ch, const char * data, size_t uiBytes)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Guild sub-handlers — extracted from Guild() switch cases // Guild sub-handlers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
int CInputMain::GuildSub_DepositMoney(LPCHARACTER ch, const char* data, size_t uiBytes) int CInputMain::GuildSub_DepositMoney(LPCHARACTER ch, const char* data, size_t uiBytes)
@@ -3214,7 +3213,7 @@ CInputMain::CInputMain()
RegisterHandlers(); RegisterHandlers();
} }
// Custom adapter methods — bridge varied handler signatures to unified MainHandler // Custom adapter methods
int CInputMain::HandlePong(LPDESC d, const char*) int CInputMain::HandlePong(LPDESC d, const char*)
{ {
@@ -3391,7 +3390,7 @@ void CInputMain::RegisterHandlers()
reg(CG::CLIENT_VERSION, &CInputMain::HandleClientVersion); reg(CG::CLIENT_VERSION, &CInputMain::HandleClientVersion);
reg(CG::DRAGON_SOUL_REFINE,&CInputMain::HandleDragonSoulRefine); reg(CG::DRAGON_SOUL_REFINE,&CInputMain::HandleDragonSoulRefine);
// Simple void(LPCHARACTER, const char*) — via template adapter // SimpleHandler<fn>(LPCHARACTER, const char*)
reg(CG::CHARACTER_POSITION,&CInputMain::SimpleHandler<&CInputMain::Position>); reg(CG::CHARACTER_POSITION,&CInputMain::SimpleHandler<&CInputMain::Position>);
reg(CG::ITEM_USE, &CInputMain::SimpleHandler<&CInputMain::ItemUse>, true); reg(CG::ITEM_USE, &CInputMain::SimpleHandler<&CInputMain::ItemUse>, true);
reg(CG::ITEM_DROP, &CInputMain::SimpleHandler<&CInputMain::ItemDrop>, true); reg(CG::ITEM_DROP, &CInputMain::SimpleHandler<&CInputMain::ItemDrop>, true);
@@ -3421,7 +3420,7 @@ void CInputMain::RegisterHandlers()
reg(CG::HACK, &CInputMain::SimpleHandler<&CInputMain::Hack>); reg(CG::HACK, &CInputMain::SimpleHandler<&CInputMain::Hack>);
reg(CG::REFINE, &CInputMain::SimpleHandler<&CInputMain::Refine>); reg(CG::REFINE, &CInputMain::SimpleHandler<&CInputMain::Refine>);
// void(LPCHARACTER, const void*) — via template adapter // SimpleHandlerV<fn>(LPCHARACTER, const void*)
reg(CG::SCRIPT_ANSWER, &CInputMain::SimpleHandlerV<&CInputMain::ScriptAnswer>); reg(CG::SCRIPT_ANSWER, &CInputMain::SimpleHandlerV<&CInputMain::ScriptAnswer>);
reg(CG::SCRIPT_BUTTON, &CInputMain::SimpleHandlerV<&CInputMain::ScriptButton>); reg(CG::SCRIPT_BUTTON, &CInputMain::SimpleHandlerV<&CInputMain::ScriptButton>);
reg(CG::SCRIPT_SELECT_ITEM, &CInputMain::SimpleHandlerV<&CInputMain::ScriptSelectItem>); reg(CG::SCRIPT_SELECT_ITEM, &CInputMain::SimpleHandlerV<&CInputMain::ScriptSelectItem>);
@@ -3429,8 +3428,6 @@ void CInputMain::RegisterHandlers()
reg(CG::QUEST_CONFIRM, &CInputMain::SimpleHandlerV<&CInputMain::QuestConfirm>); reg(CG::QUEST_CONFIRM, &CInputMain::SimpleHandlerV<&CInputMain::QuestConfirm>);
} }
// ---------------------------------------------------------------------------
// Table-driven Analyze — replaces switch statement
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
int CInputMain::Analyze(LPDESC d, uint16_t wHeader, const char * c_pData) int CInputMain::Analyze(LPDESC d, uint16_t wHeader, const char * c_pData)
@@ -3458,7 +3455,7 @@ int CInputMain::Analyze(LPDESC d, uint16_t wHeader, const char * c_pData)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// CInputDead — only allows PONG, CHAT, WHISPER, HACK // CInputDead
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
CInputDead::CInputDead() CInputDead::CInputDead()

View File

@@ -54,7 +54,6 @@
#include "DragonLair.h" #include "DragonLair.h"
#include "skill_power.h" #include "skill_power.h"
#include "DragonSoul.h" #include "DragonSoul.h"
#include "firewall_manager.h"
// #ifndef OS_WINDOWS // #ifndef OS_WINDOWS
// #include <gtest/gtest.h> // #include <gtest/gtest.h>
@@ -89,11 +88,6 @@ BYTE g_bLogLevel = 0;
socket_t tcp_socket = 0; socket_t tcp_socket = 0;
socket_t p2p_socket = 0; socket_t p2p_socket = 0;
// UDP sink sockets — bound but never read, prevents kernel ICMP port-unreachable generation
// during UDP floods (kernel silently drops once the tiny receive buffer fills)
static socket_t udp_sink_game = INVALID_SOCKET;
static socket_t udp_sink_p2p = INVALID_SOCKET;
LPFDWATCH main_fdw = NULL; LPFDWATCH main_fdw = NULL;
int io_loop(LPFDWATCH fdw); int io_loop(LPFDWATCH fdw);
@@ -347,7 +341,6 @@ int main(int argc, char **argv)
CThreeWayWar threeway_war; CThreeWayWar threeway_war;
CDragonLairManager dl_manager; CDragonLairManager dl_manager;
DSManager dsManager; DSManager dsManager;
FirewallManager firewall_manager;
if (!start(argc, argv)) { if (!start(argc, argv)) {
CleanUpForEarlyExit(); CleanUpForEarlyExit();
@@ -421,8 +414,6 @@ int main(int argc, char **argv)
char_manager.Destroy(); char_manager.Destroy();
sys_log(0, "<shutdown> Destroying ITEM_MANAGER..."); sys_log(0, "<shutdown> Destroying ITEM_MANAGER...");
item_manager.Destroy(); item_manager.Destroy();
sys_log(0, "<shutdown> Destroying FirewallManager...");
firewall_manager.Destroy();
sys_log(0, "<shutdown> Destroying DESC_MANAGER..."); sys_log(0, "<shutdown> Destroying DESC_MANAGER...");
desc_manager.Destroy(); desc_manager.Destroy();
sys_log(0, "<shutdown> Destroying quest::CQuestManager..."); sys_log(0, "<shutdown> Destroying quest::CQuestManager...");
@@ -440,49 +431,6 @@ int main(int argc, char **argv)
return 1; return 1;
} }
// Create a UDP sink socket: bind to ip:port with minimal receive buffer, never read.
// Prevents ICMP port-unreachable replies during UDP floods — the kernel silently drops
// packets once the tiny buffer fills. Returns INVALID_SOCKET on failure (non-fatal).
static socket_t create_udp_sink(const char* ip, WORD port)
{
#ifdef OS_WINDOWS
(void)ip; (void)port;
return INVALID_SOCKET;
#else
socket_t s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0)
{
sys_err("create_udp_sink: socket() failed for port %d: %s", port, strerror(errno));
return INVALID_SOCKET;
}
// Allow rebind if previous instance didn't clean up
int reuse = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
// Minimal receive buffer — kernel rounds up to its minimum (typically 256 bytes on Linux).
// Once full, incoming UDP is silently dropped with zero CPU overhead.
int rcvbuf = 1;
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
sys_err("create_udp_sink: bind() failed for port %d: %s", port, strerror(errno));
close(s);
return INVALID_SOCKET;
}
sys_log(0, "UDP sink bound on %s:%d (fd %d) — ICMP suppression active", ip, port, s);
return s;
#endif
}
void usage() void usage()
{ {
printf("Option list\n" printf("Option list\n"
@@ -613,13 +561,6 @@ int start(int argc, char **argv)
fdwatch_add_fd(main_fdw, tcp_socket, NULL, FDW_READ, false); fdwatch_add_fd(main_fdw, tcp_socket, NULL, FDW_READ, false);
fdwatch_add_fd(main_fdw, p2p_socket, NULL, FDW_READ, false); fdwatch_add_fd(main_fdw, p2p_socket, NULL, FDW_READ, false);
// UDP sink sockets — suppress ICMP port-unreachable during UDP floods
udp_sink_game = create_udp_sink(g_szPublicIP, mother_port);
udp_sink_p2p = create_udp_sink(g_szPublicIP, p2p_port);
// Kernel-level firewall — DROP unsolicited UDP + rate-limit TCP SYN at netfilter layer
FirewallManager::instance().Initialize(mother_port, p2p_port);
db_clientdesc = DESC_MANAGER::instance().CreateConnectionDesc(main_fdw, db_addr, db_port, PHASE_DBCLIENT, true); db_clientdesc = DESC_MANAGER::instance().CreateConnectionDesc(main_fdw, db_addr, db_port, PHASE_DBCLIENT, true);
if (!g_bAuthServer) { if (!g_bAuthServer) {
db_clientdesc->UpdateChannelStatus(0, true); db_clientdesc->UpdateChannelStatus(0, true);
@@ -669,9 +610,6 @@ void destroy()
socket_close(tcp_socket); socket_close(tcp_socket);
socket_close(p2p_socket); socket_close(p2p_socket);
if (udp_sink_game != INVALID_SOCKET) { socket_close(udp_sink_game); udp_sink_game = INVALID_SOCKET; }
if (udp_sink_p2p != INVALID_SOCKET) { socket_close(udp_sink_p2p); udp_sink_p2p = INVALID_SOCKET; }
sys_log(0, "<shutdown> fdwatch_delete()..."); sys_log(0, "<shutdown> fdwatch_delete()...");
fdwatch_delete(main_fdw); fdwatch_delete(main_fdw);

View File

@@ -128,8 +128,6 @@ public:
return v; return v;
} }
// --- Read struct (for backward compatibility during migration) ---
// Reads a struct directly via memcpy. Use sparingly — prefer typed reads.
template<typename T> template<typename T>
bool ReadStruct(T& out) bool ReadStruct(T& out)
{ {