- Katılım
- 12 May 2026
- Mesajlar
- 55
- Tepkime puanı
- 34
GameServer Kaynak Kodu Üzerinden Detaylı Analiz
Soket patlaması, GameServer'ın oyuncularla kurduğu TCP bağlantılarının kontrolsüz biçimde birikmesi, temizlenememesi ya da aynı anda çok fazla hatalı bağlantı gelmesi sonucu sunucunun çökmesi veya donmasıdır. Teknik olarak şu anlara gelir:
- Bağlantı açılıyor ama düzgün kapatılmıyor → soket sızıntısı
- Aynı IP'den çok fazla bağlantı geliyor → DoS / flood
- Paket tamponu dolup taşıyor → buffer overflow
- Thread'ler birbirini kilitliyor → deadlock
- Oturum temizlenmeden yeniden kullanılıyor → ghost session
Sunucu her oyuncuya bir soket ID atar. Oyuncu bağlandığında
AssignSocket() çalışır:
C++:
// shared/KOSocketMgr.h
if (nCount >= 10)
{
printf("Connection IP %s, Server Access Blocked, Active Connections(%d)\n", strIP.c_str(), nCount);
return nullptr; // Bağlantı reddedildi
}
Her gelen veri bu fonksiyondan geçer:
C++:
// shared/KOSocket.cpp
if (m_remaining > GetReadBuffer().GetAllocatedSize()) {
// Paket çok büyük → bağlantıyı kes
goto error_handler;
}
if (m_readTries > 4) {
// 4'ten fazla parçalı paket → muhtemelen saldırı
goto error_handler;
}
uint8* in_stream = new uint8[m_remaining]; // ← Her pakette heap allocation!
new uint8[m_remaining]her pakette heap'ten bellek alıyor. Yoğun trafikte binlerce allocation/deallocation → bellek parçalanması ve yavaşlama.- Parçalı paket sayacı (
m_readTries) sadece 4'e kadar koruyor. Kötü niyetli istemci bunu atlayabilir. - Paket boyutu 0 gelirse kontrol yok → potansiyel crash.
C++:
// shared/KOSocket.cpp
if (GetWriteBuffer().GetSpace() < size_t(len + 6) && !IsOffCharacter())
{
BurstEnd();
GetWriteBuffer().Remove(GetWriteBuffer().GetSize()); // ← Tüm buffer siliniyor!
delete[] out_stream;
return false;
}
new uint8[len] her seferinde yeniden allocate ediliyor.Oyuncu çıkış yaptığında
LogOut() çalışır:
C++:
// GameServer/User.cpp
void CUser::LogOut()
{
if (m_strUserID.empty()) {
return g_pMain->RemoveSessionNames(this); // ← Erken çıkış!
}
m_deleted = true;
m_bIsLoggingOut = true;
Packet newpkt(WIZ_LOGOUT);
g_pMain->AddDatabaseRequest(newpkt, this); // ← DB isteği gönderildi ama bitmesi beklenmedi
}
m_deletedvem_bIsLoggingOutbayrakları atomic değil. Birden fazla thread aynı anda okuyup yazabilir → race condition.- DB isteği gönderilip beklenmeden soket kapatılabilir → kayıt kaydedilemeyebilir.
RemoveSessionNames()her zaman çağrılmıyor → isim haritasında hayalet kayıt kalıyor.
C++:
// GameServer/GameServerDlg.cpp — RemoveSessionNames
void CGameServerDlg::RemoveSessionNames(CUser *pSession)
{
// Sadece isim haritalarından siliyor
// Active session map'ten SİLMİYOR ← Ghost session buradan doğuyor
m_characterNameMap.erase(upperName);
}
m_deleted ve m_bIsLoggingOut için std::atomic<bool> kullanın. RemoveSessionNames() içinde active session map'ten de silin.
C++:
// GameServer/ServerStartStopHandler.cpp
uint32 CGameServerDlg::Timer_UpdateSessions(void * lpParam)
{
while (g_bRunning)
{
g_pMain->m_socketMgr.GetLock().lock();
SessionMap sessMap = g_pMain->m_socketMgr.GetActiveSessionMap();
g_pMain->m_socketMgr.GetLock().unlock();
foreach(itr, sessMap) {
// Timeout kontrolü — kilit dışında ama sessMap kopyası
if (nDifference >= timeout) {
pUser->goDisconnect("time out", __FUNCTION__);
}
}
}
}
- Kilit alınıp tüm session map kopyalanıyor. 1000 oyuncuda bu her döngüde büyük bir kopya işlemi.
- NPC thread'i sadece
std::system_erroryakalıyor, diğer exception türleri thread'i çökertir. - Farklı yerlerde farklı kilit sırası kullanılıyor → deadlock riski.
C++:
// GameServer/NpcThread.cpp
catch (std::system_error& ex) {
printf("NPC ERROR - _NpcThread:1 - %s\n", ex.what());
continue; // ← std::exception veya diğerleri yakalanmıyor!
}
catch (std::exception& ex) kullanın. Session iterasyonunu daha küçük parçalara bölün.
C++:
// GameServer/CharacterSelectionHandler.cpp
CUser* pUser = g_pMain->GetUserPtr(strUserID, NameType::TYPE_CHARACTER);
if (pUser && (pUser->GetSocketID() != GetSocketID() ...))
return pUser->goDisconnect(...);
// ← Bu iki satır arasında başka bir thread pUser'ı silebilir!
GetUserPtr() çağrısı ile kullanımı arasında başka bir thread o kullanıcıyı silebilir. Bu "use-after-free" hatasına yol açar ve sunucuyu çökertir.| Belirti | Muhtemel Neden | Nerede Bakılır |
|---|---|---|
| Sunucu yavaşlıyor ama crash yok | Lock contention / session map şişmesi | Timer_UpdateSessions log |
| Oyuncular aniden atılıyor | Write buffer dolması / timeout | goDisconnect() logları |
| Aynı karakter iki kez giriyor | Ghost session / race condition | RemoveSessionNames() logları |
| RAM sürekli artıyor | Memory leak / heap fragmentation | new/delete sayısı, buffer boyutları |
| Sunucu tamamen donuyor | Deadlock | Thread dump, lock sırası |
| Belirli IP'den sonra crash | Malformed paket / buffer overflow | OnRead() hata logları |
- Atomic bayraklar:
m_deletedvem_bIsLoggingOut→std::atomic<bool>yapın - Buffer pool: Her pakette
new/deleteyerine önceden ayrılmış havuz kullanın - Exception handling: Tüm packet handler'lara
try-catch (std::exception&)ekleyin - Session temizliği:
RemoveSessionNames()içinde active session map'ten de silin - IP limiti: Sabit 10 yerine INI'den okunan değer kullanın
- Paket validasyonu: Minimum/maksimum boyut kontrolü, opcode whitelist
- Lock süresi: Kilit içinde paket göndermeyin, kopyalama yerine snapshot alın
- Loglama: Her
goDisconnect()çağrısına sebep + IP + zaman damgası ekleyin
Bu analiz ISTIRAP GameServer kaynak kodu incelenerek hazırlanmıştır.
Soket yönetimi konusunda sorularınız için bu konuya yazabilirsiniz.
Soket yönetimi konusunda sorularınız için bu konuya yazabilirsiniz.
