code cleanup
removed firewall_manager and experimentals
This commit is contained in:
@@ -119,10 +119,6 @@ bool SecureCipher::ComputeServerKeys(const uint8_t* client_pk)
|
||||
sodium_memzero(m_rx_stream_nonce, NONCE_SIZE);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,14 +101,6 @@ bool g_bCheckMultiHack = true;
|
||||
|
||||
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 LoadValidCRCList();
|
||||
bool LoadClientVersion();
|
||||
@@ -703,41 +695,6 @@ void config_init(const string& st_localeServiceName)
|
||||
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")
|
||||
{
|
||||
str_to_number(g_protectNormalPlayer, value_string);
|
||||
|
||||
@@ -105,9 +105,5 @@ extern int gPlayerMaxLevel;
|
||||
|
||||
extern bool g_BlockCharCreation;
|
||||
|
||||
extern bool g_bFirewallEnable;
|
||||
extern int g_iFirewallTcpSynLimit;
|
||||
extern int g_iFirewallTcpSynBurst;
|
||||
|
||||
#endif /* __INC_METIN_II_GAME_CONFIG_H__ */
|
||||
|
||||
|
||||
@@ -81,10 +81,6 @@ void DESC::Initialize()
|
||||
m_offtime = 0;
|
||||
|
||||
m_pkDisconnectEvent = NULL;
|
||||
|
||||
m_iFloodCheckPulse = 0;
|
||||
m_dwFloodPacketCount = 0;
|
||||
m_bIPCountTracked = false;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Set Phase to handshake and begin secure key exchange
|
||||
SetPhase(PHASE_HANDSHAKE);
|
||||
m_handshake_time = get_dword_time();
|
||||
SendKeyChallenge();
|
||||
@@ -242,7 +237,6 @@ int DESC::ProcessInput()
|
||||
else if (bytes_read == 0)
|
||||
return 0;
|
||||
|
||||
// Decrypt only the newly received bytes before committing to the buffer
|
||||
if (m_secureCipher.IsActivated()) {
|
||||
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)
|
||||
return;
|
||||
|
||||
// Log the packet for sequence tracking (only for real packet sends, not buffered flushes)
|
||||
if (!m_hasBufferedOutput && iSize >= (int)sizeof(uint16_t) * 2)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return m_dwClientTime;
|
||||
}
|
||||
|
||||
// Secure key exchange methods (libsodium/XChaCha20-Poly1305)
|
||||
void DESC::SendKeyChallenge()
|
||||
{
|
||||
// Initialize cipher and generate keypair
|
||||
if (!m_secureCipher.Initialize())
|
||||
{
|
||||
sys_err("Failed to initialize SecureCipher");
|
||||
@@ -509,10 +472,8 @@ void DESC::SendKeyChallenge()
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate challenge
|
||||
m_secureCipher.GenerateChallenge(m_challenge);
|
||||
|
||||
// Build and send challenge packet
|
||||
TPacketGCKeyChallenge packet;
|
||||
packet.header = GC::KEY_CHALLENGE;
|
||||
packet.length = sizeof(packet);
|
||||
@@ -529,14 +490,12 @@ void DESC::SendKeyChallenge()
|
||||
|
||||
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))
|
||||
{
|
||||
sys_err("Failed to compute server session keys for %s", GetHostName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify challenge response
|
||||
if (!m_secureCipher.VerifyChallengeResponse(m_challenge, challenge_response))
|
||||
{
|
||||
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()
|
||||
{
|
||||
// Generate session token
|
||||
uint8_t session_token[SecureCipher::SESSION_TOKEN_SIZE];
|
||||
randombytes_buf(session_token, sizeof(session_token));
|
||||
m_secureCipher.SetSessionToken(session_token);
|
||||
|
||||
// Build and send complete packet
|
||||
TPacketGCKeyComplete packet;
|
||||
packet.header = GC::KEY_COMPLETE;
|
||||
packet.length = sizeof(packet);
|
||||
|
||||
// Encrypt the session token
|
||||
if (!m_secureCipher.EncryptToken(session_token, sizeof(session_token),
|
||||
packet.encrypted_token, packet.nonce))
|
||||
{
|
||||
@@ -571,10 +527,7 @@ void DESC::SendKeyComplete()
|
||||
|
||||
Packet(&packet, sizeof(packet));
|
||||
|
||||
// Flush before activating encryption
|
||||
ProcessOutput();
|
||||
|
||||
// Activate encryption
|
||||
m_secureCipher.SetActivated(true);
|
||||
|
||||
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 sequence tracking ---
|
||||
|
||||
void DESC::LogRecvPacket(uint16_t header, uint16_t length)
|
||||
{
|
||||
auto& e = m_aRecentRecvPackets[m_dwRecvPacketSeq % PACKET_LOG_SIZE];
|
||||
|
||||
@@ -104,7 +104,6 @@ class DESC
|
||||
|
||||
DWORD GetClientTime();
|
||||
|
||||
// Secure key exchange (libsodium/XChaCha20-Poly1305)
|
||||
void SendKeyChallenge();
|
||||
bool HandleKeyResponse(const uint8_t* client_pk, const uint8_t* challenge_response);
|
||||
void SendKeyComplete();
|
||||
@@ -142,15 +141,9 @@ class DESC
|
||||
bool isChannelStatusRequested() const { return m_bChannelStatusRequested; }
|
||||
void SetChannelStatusRequested(bool bChannelStatusRequested) { m_bChannelStatusRequested = bChannelStatusRequested; }
|
||||
|
||||
// Handshake timeout check
|
||||
bool IsExpiredHandshake() const;
|
||||
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:
|
||||
void Initialize();
|
||||
|
||||
@@ -215,15 +208,7 @@ class DESC
|
||||
bool m_bDestroyed;
|
||||
bool m_bChannelStatusRequested;
|
||||
|
||||
// Handshake timeout protection
|
||||
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;
|
||||
uint8_t m_challenge[SecureCipher::CHALLENGE_SIZE];
|
||||
|
||||
@@ -242,7 +227,6 @@ class DESC
|
||||
void RawPacket(const void * c_pvData, int iSize);
|
||||
void ChatPacket(BYTE type, const char * format, ...);
|
||||
|
||||
// --- Packet sequence tracking (debug aid) ---
|
||||
public:
|
||||
struct PacketLogEntry
|
||||
{
|
||||
|
||||
@@ -88,27 +88,6 @@ LPDESC DESC_MANAGER::AcceptDesc(LPFDWATCH fdw, socket_t s)
|
||||
|
||||
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;
|
||||
|
||||
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_iSocketsConnected;
|
||||
|
||||
// Track per-IP count
|
||||
++m_map_ipConnCount[host];
|
||||
newd->SetIPCountTracked(true);
|
||||
|
||||
return (newd);
|
||||
}
|
||||
|
||||
@@ -181,17 +156,6 @@ void DESC_MANAGER::DestroyDesc(LPDESC d, bool bEraseFromSet)
|
||||
else
|
||||
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()
|
||||
d->Destroy();
|
||||
|
||||
@@ -419,7 +383,7 @@ DWORD DESC_MANAGER::CreateLoginKey(LPDESC d)
|
||||
|
||||
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())
|
||||
continue;
|
||||
@@ -445,7 +409,6 @@ void DESC_MANAGER::ProcessExpiredLoginKey()
|
||||
{
|
||||
it2 = it++;
|
||||
|
||||
// Clean up orphaned keys (descriptor gone but never expired)
|
||||
if (it2->second->m_dwExpireTime == 0 && it2->second->m_pkDesc == NULL)
|
||||
{
|
||||
M2_DELETE(it2->second);
|
||||
|
||||
@@ -67,8 +67,6 @@ class DESC_MANAGER : public singleton<DESC_MANAGER>
|
||||
CLIENT_DESC_SET m_set_pkClientDesc;
|
||||
DESC_SET m_set_pkDesc;
|
||||
|
||||
std::unordered_map<std::string, int> m_map_ipConnCount;
|
||||
|
||||
DESC_HANDLE_MAP m_map_handle;
|
||||
//DESC_ACCOUNTID_MAP m_AccountIDMap;
|
||||
DESC_LOGINNAME_MAP m_map_loginName;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -131,12 +131,6 @@ bool CInputProcessor::Process(LPDESC lpDesc, const void * c_pvOrig, int iBytes,
|
||||
|
||||
if (wHeader)
|
||||
{
|
||||
if (lpDesc->CheckPacketFlood())
|
||||
{
|
||||
lpDesc->SetPhase(PHASE_CLOSE);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_pPacketInfo->Start();
|
||||
|
||||
int iExtraPacketSize = Analyze(lpDesc, wHeader, c_pData);
|
||||
|
||||
@@ -128,7 +128,6 @@ class CInputMain : public CInputProcessor
|
||||
protected:
|
||||
int Analyze(LPDESC d, uint16_t wHeader, const char * c_pData) override;
|
||||
|
||||
// Handler dispatch
|
||||
using MainHandler = int (CInputMain::*)(LPDESC, const char*);
|
||||
struct HandlerEntry {
|
||||
MainHandler handler;
|
||||
@@ -137,14 +136,12 @@ class CInputMain : public CInputProcessor
|
||||
std::unordered_map<uint16_t, HandlerEntry> m_handlers;
|
||||
virtual void RegisterHandlers();
|
||||
|
||||
// Template adapters for common handler patterns (defined in input_main.cpp)
|
||||
template<void (CInputMain::*fn)(LPCHARACTER, const char*)>
|
||||
int SimpleHandler(LPDESC d, const char* p);
|
||||
|
||||
template<void (CInputMain::*fn)(LPCHARACTER, const void*)>
|
||||
int SimpleHandlerV(LPDESC d, const char* p);
|
||||
|
||||
// Custom adapters for non-standard handler signatures
|
||||
int HandlePong(LPDESC d, const char* p);
|
||||
int HandleChat(LPDESC d, const char* p);
|
||||
int HandleWhisper(LPDESC d, const char* p);
|
||||
@@ -262,19 +259,14 @@ protected:
|
||||
void RegisterHandlers();
|
||||
LPDESC FindByHandle() const;
|
||||
|
||||
// Template: void handler(const char*) — data-only
|
||||
template<void (CInputDB::*fn)(const char*)>
|
||||
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*)>
|
||||
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*)>
|
||||
int TypedHandler(LPDESC, const char* p) { (this->*fn)((T*)p); return 0; }
|
||||
|
||||
// Custom adapters for special signatures
|
||||
int HandleLoginSuccess(LPDESC, const char*);
|
||||
int HandleLoginNotExist(LPDESC, const char*);
|
||||
int HandleLoginWrongPasswd(LPDESC, const char*);
|
||||
|
||||
@@ -1729,7 +1729,6 @@ void CInputMain::Attack(LPCHARACTER ch, const uint16_t header, const char* data)
|
||||
if (NULL == ch)
|
||||
return;
|
||||
|
||||
// Updated for new packet format: [header:2][length:2][bType:1]
|
||||
struct type_identifier
|
||||
{
|
||||
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)
|
||||
@@ -3214,7 +3213,7 @@ CInputMain::CInputMain()
|
||||
RegisterHandlers();
|
||||
}
|
||||
|
||||
// Custom adapter methods — bridge varied handler signatures to unified MainHandler
|
||||
// Custom adapter methods
|
||||
|
||||
int CInputMain::HandlePong(LPDESC d, const char*)
|
||||
{
|
||||
@@ -3391,7 +3390,7 @@ void CInputMain::RegisterHandlers()
|
||||
reg(CG::CLIENT_VERSION, &CInputMain::HandleClientVersion);
|
||||
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::ITEM_USE, &CInputMain::SimpleHandler<&CInputMain::ItemUse>, 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::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_BUTTON, &CInputMain::SimpleHandlerV<&CInputMain::ScriptButton>);
|
||||
reg(CG::SCRIPT_SELECT_ITEM, &CInputMain::SimpleHandlerV<&CInputMain::ScriptSelectItem>);
|
||||
@@ -3429,8 +3428,6 @@ void CInputMain::RegisterHandlers()
|
||||
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)
|
||||
@@ -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()
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
#include "DragonLair.h"
|
||||
#include "skill_power.h"
|
||||
#include "DragonSoul.h"
|
||||
#include "firewall_manager.h"
|
||||
|
||||
// #ifndef OS_WINDOWS
|
||||
// #include <gtest/gtest.h>
|
||||
@@ -89,11 +88,6 @@ BYTE g_bLogLevel = 0;
|
||||
socket_t tcp_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;
|
||||
|
||||
int io_loop(LPFDWATCH fdw);
|
||||
@@ -347,7 +341,6 @@ int main(int argc, char **argv)
|
||||
CThreeWayWar threeway_war;
|
||||
CDragonLairManager dl_manager;
|
||||
DSManager dsManager;
|
||||
FirewallManager firewall_manager;
|
||||
|
||||
if (!start(argc, argv)) {
|
||||
CleanUpForEarlyExit();
|
||||
@@ -421,8 +414,6 @@ int main(int argc, char **argv)
|
||||
char_manager.Destroy();
|
||||
sys_log(0, "<shutdown> Destroying ITEM_MANAGER...");
|
||||
item_manager.Destroy();
|
||||
sys_log(0, "<shutdown> Destroying FirewallManager...");
|
||||
firewall_manager.Destroy();
|
||||
sys_log(0, "<shutdown> Destroying DESC_MANAGER...");
|
||||
desc_manager.Destroy();
|
||||
sys_log(0, "<shutdown> Destroying quest::CQuestManager...");
|
||||
@@ -440,49 +431,6 @@ int main(int argc, char **argv)
|
||||
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()
|
||||
{
|
||||
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, 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);
|
||||
if (!g_bAuthServer) {
|
||||
db_clientdesc->UpdateChannelStatus(0, true);
|
||||
@@ -669,9 +610,6 @@ void destroy()
|
||||
socket_close(tcp_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()...");
|
||||
fdwatch_delete(main_fdw);
|
||||
|
||||
|
||||
@@ -128,8 +128,6 @@ public:
|
||||
return v;
|
||||
}
|
||||
|
||||
// --- Read struct (for backward compatibility during migration) ---
|
||||
// Reads a struct directly via memcpy. Use sparingly — prefer typed reads.
|
||||
template<typename T>
|
||||
bool ReadStruct(T& out)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user