🔌 Soket Patlaması Nedir? Nasıl Tespit Edilir ve Çözülür?

🔌 Soket Patlaması Nedir? Nasıl Tespit Edilir ve Çözülür?

Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

DeltaSoft

Yönetici
MAREŞAL
Katılım
12 May 2026
Mesajlar
55
Tepkime puanı
34
🔌 Soket Patlaması Nedir? Nasıl Tespit Edilir ve Çözülür?
GameServer Kaynak Kodu Üzerinden Detaylı Analiz



📌 Soket Patlaması Nedir?

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



🔍 1. Bağlantı Yönetimi — Soketin Açılıp Kapanması

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
}

⚠️ Sorun: Limit sabit olarak 10 yazılmış, config dosyasından ayarlanamıyor. Aynı IP'den gelen 10+ bağlantı sessizce reddediliyor ama loglara yeterince yazılmıyor. Bir oyuncu VPN veya proxy arkasındaysa bu limit işe yaramaz.

✅ Çözüm: Limiti INI dosyasından okuyun, reddedilen bağlantıları IP bazlı loglayın.



🔍 2. Paket Okuma — OnRead() Fonksiyonu

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!

⚠️ Sorunlar:
  1. new uint8[m_remaining] her pakette heap'ten bellek alıyor. Yoğun trafikte binlerce allocation/deallocation → bellek parçalanması ve yavaşlama.
  2. Parçalı paket sayacı (m_readTries) sadece 4'e kadar koruyor. Kötü niyetli istemci bunu atlayabilir.
  3. Paket boyutu 0 gelirse kontrol yok → potansiyel crash.

✅ Çözüm: Sabit boyutlu stack buffer veya object pool kullanın. Minimum paket boyutu kontrolü ekleyin.



🔍 3. Paket Gönderme — Send() Fonksiyonu

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;
}

⚠️ Sorun: Yazma tamponu dolduğunda tüm bekleyen paketler siliniyor. Oyuncu bağlantısı kesilmeden önce kritik paketler (logout, save) kaybolabilir. Ayrıca şifreli paketler için new uint8[len] her seferinde yeniden allocate ediliyor.

✅ Çözüm: Buffer dolmadan önce uyarı logu atın. Kritik paketler için öncelik kuyruğu kullanın.



🔍 4. Oturum Yönetimi — Ghost Session Problemi

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
}

⚠️ Sorunlar:
  1. m_deleted ve m_bIsLoggingOut bayrakları atomic değil. Birden fazla thread aynı anda okuyup yazabilir → race condition.
  2. DB isteği gönderilip beklenmeden soket kapatılabilir → kayıt kaydedilemeyebilir.
  3. 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);
}

✅ Çözüm: m_deleted ve m_bIsLoggingOut için std::atomic<bool> kullanın. RemoveSessionNames() içinde active session map'ten de silin.



🔍 5. Thread Kilitleri — Deadlock ve Yavaşlama

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__);
            }
        }
    }
}

⚠️ Sorunlar:
  1. Kilit alınıp tüm session map kopyalanıyor. 1000 oyuncuda bu her döngüde büyük bir kopya işlemi.
  2. NPC thread'i sadece std::system_error yakalıyor, diğer exception türleri thread'i çökertir.
  3. 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!
}

✅ Çözüm: catch (std::exception& ex) kullanın. Session iterasyonunu daha küçük parçalara bölün.



🔍 6. Karakter Seçimi — Race Condition

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!

⚠️ Sorun: 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.

✅ Çözüm: Pointer alındıktan sonra kullanım süresince kilit tutun veya shared_ptr kullanın.



📊 Soket Patlamasının Belirtileri — Nasıl Tespit Edilir?

BelirtiMuhtemel NedenNerede Bakılır
Sunucu yavaşlıyor ama crash yokLock contention / session map şişmesiTimer_UpdateSessions log
Oyuncular aniden atılıyorWrite buffer dolması / timeoutgoDisconnect() logları
Aynı karakter iki kez giriyorGhost session / race conditionRemoveSessionNames() logları
RAM sürekli artıyorMemory leak / heap fragmentationnew/delete sayısı, buffer boyutları
Sunucu tamamen donuyorDeadlockThread dump, lock sırası
Belirli IP'den sonra crashMalformed paket / buffer overflowOnRead() hata logları



🛠️ Özet — Ne Yapılmalı?

  1. Atomic bayraklar: m_deleted ve m_bIsLoggingOutstd::atomic<bool> yapın
  2. Buffer pool: Her pakette new/delete yerine önceden ayrılmış havuz kullanın
  3. Exception handling: Tüm packet handler'lara try-catch (std::exception&) ekleyin
  4. Session temizliği: RemoveSessionNames() içinde active session map'ten de silin
  5. IP limiti: Sabit 10 yerine INI'den okunan değer kullanın
  6. Paket validasyonu: Minimum/maksimum boyut kontrolü, opcode whitelist
  7. Lock süresi: Kilit içinde paket göndermeyin, kopyalama yerine snapshot alın
  8. 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.
 

Konuyu Görüntüleyen Kullanıcılar (Toplam:2)

Geri
Üst