// ConnectionServer.cpp // copyright 2001 Verant Interactive //----------------------------------------------------------------------- #include "FirstConnectionServer.h" #include "ConnectionServer.h" #include "Archive/ByteStream.h" #include "CentralConnection.h" #include "ChatServerConnection.h" #include "ClientConnection.h" #include "ConfigConnectionServer.h" #include "ConnectionServerMetricsData.h" #include "CustomerServiceConnection.h" #include "GameConnection.h" #include "PseudoClientConnection.h" #include "SessionApiClient.h" #include "UdpLibrary.h" #include "UnicodeUtils.h" #include "serverKeyShare/KeyShare.h" #include "serverMetrics/MetricsManager.h" #include "serverNetworkMessages/CentralConnectionServerMessages.h" #include "serverNetworkMessages/CharacterListMessage.h" #include "serverNetworkMessages/ExcommunicateGameServerMessage.h" #include "serverNetworkMessages/GameConnectionServerMessages.h" #include "serverNetworkMessages/GameServerForLoginMessage.h" #include "serverNetworkMessages/LoginKeyPush.h" #include "serverNetworkMessages/NewClient.h" #include "serverNetworkMessages/ProfilerOperationMessage.h" #include "serverNetworkMessages/RandomName.h" #include "serverNetworkMessages/SetConnectionServerPublic.h" #include "serverNetworkMessages/UpdateConnectionServerStatus.h" #include "serverNetworkMessages/UpdatePlayerCountMessage.h" #include "serverNetworkMessages/ValidateAccountReplyMessage.h" #include "serverNetworkMessages/ValidateCharacterForLoginReplyMessage.h" #include "serverNetworkMessages/VerifyAndLockName.h" #include "serverUtility/AdminAccountManager.h" #include "sharedDebug/Profiler.h" #include "sharedFoundation/Clock.h" #include "sharedFoundation/ConfigFile.h" #include "sharedFoundation/Os.h" #include "sharedFoundation/Timer.h" #include "sharedGame/PlatformFeatureBits.h" #include "sharedLog/Log.h" #include "sharedLog/LogManager.h" #include "sharedLog/SetupSharedLog.h" #include "sharedNetwork/NetworkSetupData.h" #include "sharedNetwork/Service.h" #include "sharedNetwork/UdpSock.h" #include "sharedNetworkMessages/ClientCentralMessages.h" #include "sharedNetworkMessages/GenericValueTypeMessage.h" #include "sharedUtility/DataTableManager.h" #include #include "sharedFoundation/CrcConstexpr.hpp" // ====================================================================== namespace ConnectionServerNamespace { ConnectionServer * s_connectionServer = 0; NetworkSetupData * s_clientServiceSetup = 0; const std::string SCENE_NAME_TUTORIAL = "tutorial"; const std::string SCENE_NAME_FALCON_PREFIX = "space_npe_falcon"; }; using namespace ConnectionServerNamespace; // ====================================================================== ConnectionServer & ConnectionServer::instance() { return *s_connectionServer; } //----------------------------------------------------------------------- ConnectionServer::ConnectionServer() : MessageDispatch::Receiver(), chatService(0), customerService(0), clientServicePrivate(0), clientServicePublic(0), gameService(0), loginServerKeys(0), done(false), m_id(0), m_metricsData(0), centralConnection(0), chatServers(), customerServiceServers(), clientMap(), connectedMap(), gameServerMap(), freeTrials(), networkBarrier(0), pingSocket(new UdpSock), m_recoverTime(0), m_sessionApiClient(0), m_pingTrafficNumBytes(0), m_recoveringClientList() { if (s_clientServiceSetup == 0) s_clientServiceSetup = new NetworkSetupData; s_clientServiceSetup->maxOutstandingPackets = ConfigConnectionServer::getClientMaxOutstandingPackets(); s_clientServiceSetup->maxRawPacketSize = ConfigConnectionServer::getClientMaxRawPacketSize(); s_clientServiceSetup->maxConnections = ConfigConnectionServer::getClientMaxConnections(); s_clientServiceSetup->fragmentSize = ConfigConnectionServer::getClientFragmentSize(); s_clientServiceSetup->maxDataHoldTime = ConfigConnectionServer::getClientMaxDataHoldTime(); s_clientServiceSetup->hashTableSize = ConfigConnectionServer::getClientHashTableSize(); s_clientServiceSetup->port = ConfigConnectionServer::getClientServicePortPublic(); s_clientServiceSetup->maxConnectionsPerIP = ConfigConnectionServer::getMaxConnectionsPerIP(); s_clientServiceSetup->compress = ConfigConnectionServer::getCompressClientNetworkTraffic(); s_clientServiceSetup->useTcp = false; loginServerKeys = new KeyServer(20); Address a("", ConfigConnectionServer::getPingPort()); IGNORE_RETURN(pingSocket->bind(a)); if (ConfigConnectionServer::getValidateStationKey()) { installSessionValidation(); } } //----------------------------------------------------------------------- ConnectionServer::~ConnectionServer() { delete pingSocket; pingSocket = 0; delete loginServerKeys; loginServerKeys = 0; centralConnection = 0; chatServers.clear(); customerServiceServers.clear(); connectedMap.clear(); clientServicePublic = 0; clientServicePrivate = 0; chatService = 0; customerService = 0; gameService = 0; gameServerMap.clear(); freeTrials.clear(); delete s_clientServiceSetup; } //----------------------------------------------------------------------- const CustomerServiceConnection * ConnectionServer::getCustomerServiceConnection() { if (!instance().customerServiceServers.empty()) { return (*(instance().customerServiceServers.begin())); } return nullptr; } //----------------------------------------------------------------------- //TODO: This assumes that all characters that aren't on a game server // are waiting for this gameserver. This assumption is bad: they could // be waiting on a different gameserver, or they could be in the process // of being validated void ConnectionServer::addGameConnection(unsigned long gameServerId, GameConnection* gc) { static ConnectionServer & cs = instance(); cs.gameServerMap[gameServerId] = gc;//@todo check for dupe // find characters pending for THIS gameserver SuidMap::iterator i; for (i = cs.connectedMap.begin(); i != cs.connectedMap.end(); ++i) { ClientConnection * c = (*i).second; if (!c->getHasBeenSentToGameServer()) IGNORE_RETURN(c->sendToGameServer()); } } //----------------------------------------------------------------------- bool ConnectionServer::decryptToken(const KeyShare::Token & token, uint32 & stationUserId, bool & secure, std::string & accountName) { static ConnectionServer & cs = instance(); //Also the sizeof(int) is likewise magic from the session api uint32 len = sizeof(uint32) + sizeof(bool) + MAX_ACCOUNT_NAME_LENGTH + 1; unsigned char * keyBuffer = new unsigned char[len]; unsigned char * keyBufferPointer = keyBuffer; memset(keyBuffer, 0, len); bool retval = cs.loginServerKeys->decipherToken(token, keyBuffer, len); if (!retval) return retval; char *tmpBuffer = new char[MAX_ACCOUNT_NAME_LENGTH + 1]; memset(tmpBuffer, 0, MAX_ACCOUNT_NAME_LENGTH + 1); memcpy(&stationUserId, keyBufferPointer, sizeof(uint32)); keyBufferPointer += sizeof(uint32); memcpy(&secure, keyBufferPointer, sizeof(bool)); keyBufferPointer += sizeof(bool); memcpy(tmpBuffer, keyBufferPointer, MAX_ACCOUNT_NAME_LENGTH); accountName = tmpBuffer; delete[] tmpBuffer; delete[] keyBuffer; return retval; } bool ConnectionServer::decryptToken(const KeyShare::Token & token, char* sessionKey, StationId & stationId) { static ConnectionServer & cs = instance(); uint32 len = apiSessionIdWidth + sizeof(StationId); unsigned char * keyBuffer = new unsigned char[len + 1]; unsigned char * keyBufferPointer = keyBuffer; memset(keyBuffer, 0, sizeof(*keyBuffer)); bool retval = cs.loginServerKeys->decipherToken(token, keyBuffer, len); if (!retval) return retval; memcpy(sessionKey, keyBufferPointer, len); keyBufferPointer += apiSessionIdWidth; memcpy(&stationId, keyBufferPointer, sizeof(StationId)); delete[] keyBuffer; return retval; } //----------------------------------------------------------------------- const Service * ConnectionServer::getChatService() { static ConnectionServer & cs = instance(); return cs.chatService; } //----------------------------------------------------------------------- const Service * ConnectionServer::getCustomerService() { static ConnectionServer & cs = instance(); return cs.customerService; } //----------------------------------------------------------------------- Service * ConnectionServer::getClientServicePrivate() { static ConnectionServer & cs = instance(); return cs.clientServicePrivate; } //----------------------------------------------------------------------- Service * ConnectionServer::getClientServicePublic() { static ConnectionServer & cs = instance(); return cs.clientServicePublic; } //----------------------------------------------------------------------- KeyShare::Token ConnectionServer::makeToken(const unsigned char * newData, const uint32 dataLen) { static ConnectionServer & cs = instance(); return cs.loginServerKeys->makeToken(newData, dataLen); } //----------------------------------------------------------------------- void ConnectionServer::pushKey(const KeyShare::Key & newKey) { static ConnectionServer & cs = instance(); cs.loginServerKeys->pushKey(newKey); } //----------------------------------------------------------------------- void ConnectionServer::addNewClient(ClientConnection* cconn, const NetworkId &oid, GameConnection* gconn, const std::string &, bool sendToStarport) { static ConnectionServer & cs = instance(); ClientMap::iterator i = cs.clientMap.find(oid); if (i != cs.clientMap.end()) { if (cconn->getClient()) { WARNING_STRICT_FATAL(true, ("Client already connected, attempting to drop old one\n")); dropClient(cconn, "Duplicate Login"); return; } else { // stale connection in map cs.removeFromClientMap(oid); } } // Create a new entry in the client map cs.addToClientMap(oid, cconn); // Get the client that was just created Client * newClient = cs.getClient(oid); NOT_NULL(newClient); // select a chat server connection for the client // if non exists, then the client will be notified when // one starts if (!cs.chatServers.empty()) { //ChatServerConnection * c = (*chatServers.begin()); // find chat server with least load size_t max = 0xFFFFFFFF; ChatServerConnection * candidate = 0; std::set::const_iterator iter; for (iter = cs.chatServers.begin(); iter != cs.chatServers.end(); ++iter) { if ((*iter)->getClients().size() <= max) { candidate = (*iter); max = (*iter)->getClients().size(); } } if (candidate) newClient->setChatConnection(candidate); } // select a cs server connection for the client // if non exists, then the client will be notified when // one starts if (!cs.customerServiceServers.empty()) { //ChatServerConnection * c = (*chatServers.begin()); // find chat server with least load size_t max = 0xFFFFFFFF; CustomerServiceConnection * candidate = 0; std::set::const_iterator iter; for (iter = cs.customerServiceServers.begin(); iter != cs.customerServiceServers.end(); ++iter) { if ((*iter)->getClients().size() <= max) { candidate = (*iter); max = (*iter)->getClients().size(); } } if (candidate) newClient->setCustomerServiceConnection(candidate); } //send the game server a message about this client. static const std::string loginTrace("TRACE_LOGIN"); LOG(loginTrace, ("NewClient(%d, %s, %s)", cconn->getSUID(), oid.getValueString().c_str(), cconn->getAccountName().c_str())); NewClient m(oid, cconn->getAccountName(), cconn->getRemoteAddress(), cconn->getIsSecure(), false, cconn->getSUID(), nullptr, cconn->getGameFeatures(), cconn->getSubscriptionFeatures(), cconn->getEntitlementTotalTime(), cconn->getEntitlementEntitledTime(), cconn->getEntitlementTotalTimeSinceLastLogin(), cconn->getEntitlementEntitledTimeSinceLastLogin(), cconn->getBuddyPoints(), cconn->getConsumedRewardEvents(), cconn->getClaimedRewardItems(), cconn->isUsingAdminLogin(), cconn->getCanSkipTutorial(), sendToStarport); gconn->send(m, true); //@todo move this to ClientConnection.cpp } //----------------------------------------------------------------------- void ConnectionServer::dropClient(ClientConnection * conn, const std::string& description) { DEBUG_FATAL(!conn, ("Cannot call dropClient with nullptr connection")); if (!conn) //lint !e774 // boolean within 'if' always evaluates to False //suppresed because this is only relevant in DEBUG builds return; static ConnectionServer & cs = instance(); //Client dropped. Tell game server if they've logged in. LOG("ClientDisconnect", ("Dropping client for SUID %d\n", conn->getSUID())); LOG("CustomerService", ("Login:%s Dropped Reason: %s. Character: %s (%s). Play time: %s. Active play time: %s", ClientConnection::describeAccount(conn).c_str(), description.c_str(), conn->getCharacterName().c_str(), conn->getCharacterId().getValueString().c_str(), conn->getPlayTimeDuration().c_str(), conn->getActivePlayTimeDuration().c_str())); Client *client = conn->getClient(); if (client) { DropClient msg(client->getNetworkId()); GameConnection* gconn = client->getGameConnection(); //Don't worry about sending a message to a non-existant game server if (gconn) gconn->send(msg, true); else DEBUG_REPORT_LOG(true, ("Could not find game server to drop this client\n")); // Remove the entry from the client map cs.removeFromClientMap(client->getNetworkId()); } else { //If they aren't connected to the game yet, they're probably on the pending list. //@todo ensure we don't need to send a cleanup message to central // removePendingCharacter(conn->getSUID()); } // Remove entry from the connected map cs.removeFromConnectedMap(conn->getSUID()); conn->disconnect(); } //----------------------------------------------------------------------- // void ConnectionServer::addPendingCharacter(uint32 suid, ClientConnection* conn) // { // if (pendingMap.find(suid) == pendingMap.end()) // pendingMap[suid] = conn; // else // { // WARNING_STRICT_FATAL(true, ("Attepting to add duplicate pending chatacter")); // pendingMap[suid] = conn; // } // } //----------------------------------------------------------------------- void ConnectionServer::addConnectedClient(uint32 suid, ClientConnection* conn) { static ConnectionServer & cs = instance(); cs.addToConnectedMap(suid, conn); } //----------------------------------------------------------------------- ClientConnection* ConnectionServer::getClientConnection(const uint32 suid) { static ConnectionServer & cs = instance(); ClientConnection * result = 0; SuidMap::const_iterator i = cs.connectedMap.find(suid); if (i != cs.connectedMap.end()) { result = (*i).second; } return result; } // ---------------------------------------------------------------------- const Service * ConnectionServer::getGameService() { static ConnectionServer & cs = instance(); return cs.gameService; } // ---------------------------------------------------------------------- GameConnection* ConnectionServer::getGameConnection(const std::string &sceneName) { static ConnectionServer & cs = instance(); GameServerMap::iterator i = cs.gameServerMap.begin(); for (; i != cs.gameServerMap.end(); ++i) { if (sceneName == (*i).second->getSceneName()) { return (*i).second; } } return nullptr; } // ---------------------------------------------------------------------- void ConnectionServer::handleConnectionServerIdMessage(const ConnectionServerId & m) { // Connection established with central server. Set everything up. m_id = m.getId(); const Service * const servicePrivate = getClientServicePrivate(); const Service * const servicePublic = getClientServicePublic(); FATAL(servicePrivate == nullptr && servicePublic == nullptr, ("No client service is active!")); const Service * const g = getGameService(); FATAL(g == nullptr, ("No game service is active!")); const Service * const c = getChatService(); const Service * const cs = getCustomerService(); const uint16 pingPort = getPingPort(); if ((servicePrivate != nullptr || servicePublic != nullptr) && g != nullptr) //lint !e774 // always evaluates to false // suppresed because the FATAL macro triggers this lint warning { uint16 chatPort = 0; uint16 csPort = 0; if (c) { chatPort = c->getBindPort(); } if (cs) { csPort = cs->getBindPort(); } uint16 publicPort = 0; if (servicePublic) { publicPort = servicePublic->getBindPort(); } uint16 privatePort = 0; if (servicePrivate) { privatePort = servicePrivate->getBindPort(); } std::string clientServicePublicBindAddress = ConfigConnectionServer::getPublicBindAddress(); if (clientServicePublicBindAddress.empty()) { clientServicePublicBindAddress = NetworkHandler::getHostName(); } NOT_NULL(gameService); NOT_NULL(chatService); NOT_NULL(customerService); const NewCentralConnectionServer ncs(gameService->getBindAddress(), clientServicePublicBindAddress, chatService->getBindAddress(), customerService->getBindAddress(), privatePort, publicPort, g->getBindPort(), chatPort, csPort, pingPort, ConfigConnectionServer::getConnectionServerNumber()); sendToCentralProcess(ncs); } else { FATAL(true, ("Error in connection server startup")); } } // ---------------------------------------------------------------------- void ConnectionServer::receiveMessage(const MessageDispatch::Emitter & source, const MessageDispatch::MessageBase & message) { // it's reasonably safe to cast, message type verified // determine message type const uint32 messageType = message.getType(); switch(messageType) { case constcrc("GameConnectionOpened") : { DEBUG_REPORT_LOG(true, ("Game Connection opened\n")); break; } case constcrc("GameConnectionClosed") : { //@todo handle case where game server drops and we have users connected to it. //Drop all connected clients. const GameConnection & downConnection = static_cast(source); DEBUG_REPORT_LOG(true, ("Game Server connection went down. Dropping clients.\n")); //remove Game Conection from list PseudoClientConnection::gameConnectionClosed(&downConnection); GameServerMap::iterator j = gameServerMap.find(downConnection.getGameServerId()); if (j != gameServerMap.end()) { gameServerMap.erase(j); } break; } case constcrc("CentralConnectionOpened") : { DEBUG_REPORT_LOG(true, ("Opened connection with central\n")); centralConnection = const_cast(static_cast(&source));//lint !e826 // info: Suspiscious pointer-to-pointer conversion (area too small) if (ConfigConnectionServer::getStartPublicServer()) { s_clientServiceSetup->port = ConfigConnectionServer::getClientServicePortPublic(); s_clientServiceSetup->maxConnectionsPerIP = ConfigConnectionServer::getMaxConnectionsPerIP(); clientServicePublic = new Service(ConnectionAllocator(), *s_clientServiceSetup); } s_clientServiceSetup->port = ConfigConnectionServer::getClientServicePortPrivate(); s_clientServiceSetup->maxConnectionsPerIP = 0; clientServicePrivate = new Service(ConnectionAllocator(), *s_clientServiceSetup); connectToMessage("ClientConnectionOpened"); connectToMessage("ClientConnectionClosed"); break; } case constcrc("CentralConnectionClosed") : { centralConnection = const_cast(static_cast(&source));//lint !e826 // info: Suspiscious pointer-to-pointer conversion (area too small) setDone("CentralConnectionClosed: %s", centralConnection ? centralConnection->getDisconnectReason().c_str() : ""); centralConnection = 0; DEBUG_REPORT_LOG(true, ("CentralDied. So we will too\n")); //@todo Drop all pending clients. break; } case constcrc("ClientConnectionOpened") : { DEBUG_REPORT_LOG(true, ("Opened connection with client\n")); break; } case constcrc("ClientConnectionClosed") : { DEBUG_REPORT_LOG(true, ("Client is Dropping connection\n")); ClientConnection * cconn = const_cast(static_cast(&source));//lint !e826 // info: Suspiscious pointer-to-pointer conversion (area too small) //tell CentralServer if (centralConnection) { GenericValueTypeMessage const msg("ClientConnectionClosed", cconn->getSUID()); centralConnection->send(msg, true); } //Client dropped. Tell game server if they've logged in. Client *client = cconn->getClient(); if (client) { DropClient msg(client->getNetworkId()); GameConnection* gconn = client->getGameConnection(); //Don't worry about sending a message to a non-existant game server if (gconn) gconn->send(msg, true); // Remove the entry from the client map removeFromClientMap(client->getNetworkId()); } else { // TODO: wtf is this? @Darth, fix it! //If they aren't connected to the game yet, they're probably on the pending list. //removePendingCharacter(cconn->getSUID()); } removeFromConnectedMap(cconn->getSUID()); break; } case constcrc("ConnectionServerId") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); ConnectionServerId m(ri); handleConnectionServerIdMessage(m); break; } case constcrc("ConnectionKeyPush") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); ConnectionKeyPush pk(ri); loginServerKeys->pushKey(pk.getKey()); break; } case constcrc("LoginKeyPush") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); const LoginKeyPush k(ri); loginServerKeys->pushKey(k.getKey()); break; } case constcrc("CharacterListMessage") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); const CharacterListMessage msg(ri); WARNING_STRICT_FATAL(true, ("CharacterListMessage is deprecated on the ConnectionServer -- fix whoever is sending it.\n")); break; } case constcrc("ConnectionCreateCharacterSuccess") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); const ConnectionCreateCharacterSuccess msg(ri); LOG("TraceCharacterCreation", ("Received ConnectionCreateCharacterSuccess for %d", msg.getStationId())); ClientConnection* const client = getClientConnection(msg.getStationId()); if (client) { const ClientCreateCharacterSuccess m(msg.getNetworkId()); client->send(m, true); } else { LOG("CustomerService", ("CharacterTransfer: Trying to deliver ConnectionCreateCharacterSuccess to PsuedoClientConnection(%d)", msg.getStationId())); PseudoClientConnection::tryToDeliverMessageTo(static_cast(msg.getStationId()), static_cast(message).getByteStream()); } break; } case constcrc("ConnectionCreateCharacterFailed") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); const ConnectionCreateCharacterFailed msg(ri); ClientConnection* client = getClientConnection(msg.getStationId()); if (client) { client->setHasRequestedCharacterCreate(false); ClientCreateCharacterFailed m(msg.getName(), msg.getErrorMessage()); //lint !e1013 !e1055 !e746 (Symbol 'getErrorMessage' not a member of class 'const ConnectionCreateCharacterFailed') // supressed because it IS a member of that class. //lint !e1055 //lint !e746 client->send(m, true); } else { PseudoClientConnection::tryToDeliverMessageTo(static_cast(msg.getStationId()), static_cast(message).getByteStream()); } break; } case constcrc("NewCharacterCreated") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); GenericValueTypeMessage const msg(ri); ClientConnection* client = getClientConnection(msg.getValue()); if (client) { // don't allow this client to request another character create; // this will forced the client to disconnect and reconnect at which time // a check will be done (taking the newly created character into account) // to see if the client is allowed to create another character on this account client->setHasCreatedCharacter(true); } break; } case constcrc("RandomNameResponse") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); RandomNameResponse connMsg(ri); ClientConnection* cconn = getClientConnection(connMsg.getStationId()); ClientRandomNameResponse cnr(connMsg.getCreatureTemplate(), connMsg.getName(), connMsg.getErrorMessage());//lint !e1013 (Symbol 'getErrorMessage' not a member of class 'RandomNameResponse') // supressed because it IS a member of that class. if (cconn) cconn->send(cnr, true); break; } case constcrc("VerifyAndLockNameResponse") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); VerifyAndLockNameResponse connMsg(ri); ClientConnection* cconn = getClientConnection(connMsg.getStationId()); ClientVerifyAndLockNameResponse cvalnr(connMsg.getCharacterName(), connMsg.getErrorMessage()); if (cconn) cconn->send(cvalnr, true); break; } case constcrc("ChatServerConnectionOpened") : { ChatServerConnection * c = const_cast(static_cast(&source));//lint !e826 suspiscious pointer-to-pointer conversion // suppressed, you bet it is IGNORE_RETURN(chatServers.insert(c)); break; } case constcrc("ChatServerConnectionClosed") : { ChatServerConnection * c = const_cast(static_cast(&source));//lint !e826 suspiscious pointer-to-pointer conversion // suppressed, you bet it is std::set::iterator f = chatServers.find(c); if (f != chatServers.end()) { // migrate players that were on this chat server // to another chat server // now remove the server from the set chatServers.erase(f); if (!chatServers.empty()) { std::set::iterator ic = chatServers.begin(); const std::set & clients = c->getClients(); std::set::const_iterator i; for (i = clients.begin(); i != clients.end(); ++i) { ChatServerConnection * newConn = (*ic); Client * cl = (*i); cl->setChatConnection(newConn); ++ic; if (ic == chatServers.end()) ic = chatServers.begin(); } } else { const std::set & clients = c->getClients(); std::set::const_iterator i; for (i = clients.begin(); i != clients.end(); ++i) { (*i)->setChatConnection(nullptr); } } } break; } case constcrc("CustomerServiceConnectionOpened") : { CustomerServiceConnection * c = const_cast(static_cast(&source));//lint !e826 suspiscious pointer-to-pointer conversion // suppressed, you bet it is IGNORE_RETURN(customerServiceServers.insert(c)); break; } case constcrc("CustomerServiceConnectionClosed") : { CustomerServiceConnection * c = const_cast(static_cast(&source));//lint !e826 suspiscious pointer-to-pointer conversion // suppressed, you bet it is std::set::iterator f = customerServiceServers.find(c); if (f != customerServiceServers.end()) { // migrate players that were on this chat server // to another chat server // now remove the server from the set customerServiceServers.erase(f); if (!customerServiceServers.empty()) { std::set::iterator ic = customerServiceServers.begin(); const std::set & clients = c->getClients(); std::set::const_iterator i; for (i = clients.begin(); i != clients.end(); ++i) { CustomerServiceConnection * newConn = (*ic); Client * cl = (*i); cl->setCustomerServiceConnection(newConn); ++ic; if (ic == customerServiceServers.end()) ic = customerServiceServers.begin(); } } else { const std::set & clients = c->getClients(); std::set::const_iterator i; for (i = clients.begin(); i != clients.end(); ++i) { (*i)->setCustomerServiceConnection(nullptr); } } } break; } case constcrc("GameServerForLoginMessage") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); const GameServerForLoginMessage msg(ri); ClientConnection* client = getClientConnection(msg.getStationId()); // see if the client is for the same character as the login message. // this is required to prevent admin login via the CS Tool from // disconnecting other characters logged in from the same account // (but not the same character) as the character we're administratively // logging in. bool clientIsForSameCharacterId = false; if (client) { clientIsForSameCharacterId = client->getCharacterId() == msg.getCharacterId(); } bool handledByPseudoClient = false; // if the character id in the message doesn't match the character id for // the existing client, it may be for a pseudoclient. Check, and if so, // handle there. if (!clientIsForSameCharacterId) { PseudoClientConnection * pcc = PseudoClientConnection::getPseudoClientConnection(msg.getCharacterId()); // hand off to the PCC only if we do have a pseudoclient, and either we don't have a client, or // the pseudoclient has a tool id, telling us that it's for cs tool login. if (pcc && ((!client) || (pcc->getTransferCharacterData().getCSToolId() > 0))) { handledByPseudoClient = true; bool result; result = PseudoClientConnection::tryToDeliverMessageTo(msg.getStationId(), static_cast(message).getByteStream()); UNREF(result); DEBUG_REPORT_LOG(!result, ("Received GameServerForLoginMessage for %lu, who was not connected.\n", msg.getStationId())); } } if (client && (!handledByPseudoClient)) { static const std::string loginTrace("TRACE_LOGIN"); LOG(loginTrace, ("GameServerForLoginMessage(%d, %s)", client->getSUID(), client->getCharacterId().getValueString().c_str())); client->handleGameServerForLoginMessage(msg.getServer()); } break; } case constcrc("ValidateCharacterForLoginReplyMessage") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); const ValidateCharacterForLoginReplyMessage msg(ri); ClientConnection* cconn = getClientConnection(msg.getSuid()); if (!cconn) DEBUG_REPORT_LOG(true, ("Received ValidateCharacterForLoginReplyMessage for account %lu, which is no longer connected.\n", msg.getSuid())); else { cconn->onCharacterValidated(msg.getApproved(), msg.getCharacterId(), Unicode::wideToNarrow(msg.getCharacterName()), msg.getContainerId(), msg.getScene(), msg.getCoordinates()); } break; } case constcrc("ValidateAccountReplyMessage") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); const ValidateAccountReplyMessage msg(ri); ClientConnection* cconn = getClientConnection(msg.getStationId()); if (!cconn) DEBUG_REPORT_LOG(true, ("Received ValidateAccountReplyMessage for account %lu, which is no longer connected.\n", msg.getStationId())); else { cconn->onIdValidated(msg.getCanLogin(), msg.getCanCreateRegular(), msg.getCanCreateJedi(), msg.getCanSkipTutorial(), msg.getConsumedRewardEvents(), msg.getClaimedRewardItems()); } break; } case constcrc("SetConnectionServerPublic") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); SetConnectionServerPublic p(ri);//lint !e40 !e522 !e10 // Undeclared identifier 'SetConnectionServerPublic' // suppressed because it IS declared bool statusChanged = false; DEBUG_REPORT_LOG(true, ("Conn Server: attempting to chang status\n")); if (p.getIsPublic())//lint !e40 !e1013 !e10 // Undeclared identifier 'p' // suppressed because it IS declared { if (!clientServicePublic) { statusChanged = true; } } else { delete clientServicePublic; clientServicePublic = 0; statusChanged = true; } if (statusChanged && centralConnection) { const Service * publicService = getClientServicePublic(); const Service * privateService = getClientServicePrivate(); if (publicService || privateService) { uint16 publicServicePort = 0; uint16 privateServicePort = 0; if (publicService) { publicServicePort = publicService->getBindPort(); } if (privateService) { privateServicePort = privateService->getBindPort(); } const UpdateConnectionServerStatus ucs(publicServicePort, privateServicePort); centralConnection->send(ucs, true); } } break; }//lint !e529 // Symbol 'ri' not subsequently referenced // suppressed because it IS referenced. I think lint is very confused for some reason. case constcrc("ProfilerOperationMessage") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); ProfilerOperationMessage msg(ri); unsigned int processId = msg.getProcessId(); if (!processId) Profiler::handleOperation(msg.getOperation().c_str()); break; } case constcrc("ExcommunicateGameServerMessage") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); ExcommunicateGameServerMessage msg(ri); LOG("GameGameConnect", ("Told to drop connection to %lu by Central", msg.getServerId())); GameConnection *conn = getGameConnection(msg.getServerId()); if (conn) conn->disconnect(); break; } case constcrc("CntrlSrvDropDupeConns") : { Archive::ReadIterator ri = static_cast(message).getByteStream().begin(); GenericValueTypeMessage > const msg(ri); ClientConnection* client = getClientConnection(msg.getValue().first); if (client && !client->isUsingAdminLogin() && !client->getIsSecure()) { std::string s = "New Connection on galaxy "; s += msg.getValue().second; dropClient(client, s); } break; } } } //----------------------------------------------------------------------- void ConnectionServer::install() { s_connectionServer = new ConnectionServer; char tmp[128] = { "\0" }; IGNORE_RETURN(snprintf(tmp, sizeof(tmp), "ConnectionServer:%d", Os::getProcessId())); SetupSharedLog::install(tmp); s_connectionServer->setupConnections(); s_connectionServer->m_metricsData = new ConnectionServerMetricsData; MetricsManager::install(s_connectionServer->m_metricsData, true, "ConnectionServer", "", ConfigConnectionServer::getConnectionServerNumber()); DataTableManager::install(); AdminAccountManager::install(ConfigConnectionServer::getAdminAccountDataTable()); } //----------------------------------------------------------------------- void ConnectionServer::remove() { MetricsManager::remove(); delete s_connectionServer->m_metricsData; s_connectionServer->m_metricsData = 0; // explicitly delete all connections that were setup from setupConnections rather than letting // Connection::remove do it. There are connections that require a valid s_connectionServer which is deleted // and set to nullptr. s_connectionServer->unsetupConnections(); SetupSharedLog::remove(); delete s_connectionServer; s_connectionServer = 0; } //----------------------------------------------------------------------- void ConnectionServer::run(void) { static const bool shouldSleep = ConfigConnectionServer::getShouldSleep(); static ConnectionServer & cserver = instance(); DEBUG_FATAL(!cserver.m_metricsData, ("Connection server not installed properly")); unsigned long startTime = Clock::timeMs(); Clock::setFrameRateLimit(50.0f); LOG("ServerStartup", ("ConnectionServer starting on %s", NetworkHandler::getHostName().c_str())); while (!cserver.done) { PROFILER_AUTO_BLOCK_DEFINE("main loop"); //bool barrierReached = true; do { { PROFILER_AUTO_BLOCK_DEFINE("Os::update"); if (!Os::update()) cserver.setDone("Os condition (Parent pid change)"); } { PROFILER_AUTO_BLOCK_DEFINE("NetworkHandler::update1"); NetworkHandler::update(); } { GenericValueTypeMessage > const syncStampMessage("SetSyncStamp", std::make_pair(static_cast(UdpMisc::LocalSyncStampShort()), static_cast(UdpMisc::LocalSyncStampLong()))); GameServerMap::iterator end = cserver.gameServerMap.end(); for (GameServerMap::iterator iter = cserver.gameServerMap.begin(); iter != end; ++iter) iter->second->send(syncStampMessage, true); } { PROFILER_AUTO_BLOCK_DEFINE("NetworkHandler::dispatch"); NetworkHandler::dispatch(); } { PROFILER_AUTO_BLOCK_DEFINE("update"); cserver.update(); } { PROFILER_AUTO_BLOCK_DEFINE("NetworkHandler::update2"); NetworkHandler::update(); } { PROFILER_AUTO_BLOCK_DEFINE("MetricsManager::update"); unsigned long curTime = Clock::timeMs(); MetricsManager::update(static_cast(curTime - startTime)); startTime = curTime; } if (shouldSleep) { PROFILER_AUTO_BLOCK_DEFINE("Os::sleep"); Os::sleep(1); } } while (!cserver.done); NetworkHandler::clearBytesThisFrame(); cserver.updateRecoveringClientList(static_cast(Clock::frameTime()*1000.0f)); } } // ---------------------------------------------------------------------- /** * Invoked every frame to do whatever updates are needed */ void ConnectionServer::update() { if (centralConnection) { static Timer t(5.0f); if (t.updateZero(Clock::frameTime())) { // Update the population on the central server updatePopulationOnCentralServer(); if (getNumberOfClients() >= ConfigConnectionServer::getMaxClients() - 1) { //@todo need an Alert here WARNING(true, ("We've reached maximum client capacity on a connection server with %d clients", getNumberOfClients())); } } } if (m_sessionApiClient) { m_sessionApiClient->update(); } static const int ping_throttle_max = 1024; static char buffer[4]; for (int throttle = 0; pingSocket->canRecv() && throttle < ping_throttle_max; ++throttle) { Address addr; const uint32 count = pingSocket->recvFrom(addr, buffer, 4); m_pingTrafficNumBytes += static_cast(count); if (m_pingTrafficNumBytes < 0) m_pingTrafficNumBytes = 0; IGNORE_RETURN(pingSocket->sendTo(addr, buffer, count)); } } // ---------------------------------------------------------------------- /** * The recovering client list holds players who are trying to recover from a server * crash. If another game server takes authority within a few frames, the player * is OK and we don't do anything. If no game server takes authority for them, * we drop them and force them to log in again. */ void ConnectionServer::updateRecoveringClientList(uint elapsedTime) { if (!m_recoveringClientList.empty()) { m_recoverTime += elapsedTime; static std::set listPlayersDropped; for (RecoveringClientListType::iterator i = m_recoveringClientList.begin(); i != m_recoveringClientList.end();) { if (m_recoverTime > i->first) { Client *client = getClient(i->second); if (client) { if (client->getGameConnection()) DEBUG_REPORT_LOG(true, ("Player %s recovered from a server crash and will not be dropped.\n", i->second.getValueString().c_str())); else { LOG("Network", ("Dropping player %s because game server crashed and no other server took authority.\n", i->second.getValueString().c_str())); dropClient(client->getClientConnection(), "Game Server Crash"); IGNORE_RETURN(listPlayersDropped.insert(i->second)); } } i = m_recoveringClientList.erase(i); } else ++i; } if (!listPlayersDropped.empty()) { GenericValueTypeMessage > const m("PlayerDroppedFromGameServerCrash", listPlayersDropped); // let one of the game server know that we dropped the player(s) due to a game server crash GameConnection * const anyGameConnection = getAnyGameConnection(); if (anyGameConnection) anyGameConnection->send(m, true); // let CentralServer know that we dropped the player(s) due to a game server crash sendToCentralProcess(m); listPlayersDropped.clear(); } if (m_recoveringClientList.empty()) m_recoverTime = 0; // prevent rollover and other problems I don't want to worry about } } // ---------------------------------------------------------------------- void ConnectionServer::addRecoveringClient(const NetworkId& networkId) { instance().m_recoveringClientList.push_back(std::pair(static_cast(instance().m_recoverTime) + ConfigConnectionServer::getCrashRecoveryTimeout(), networkId));//lint !e737 !e713 !e1703 !e1025 // going nuts over unsigned/signed conversion and type mismatch on template } // ---------------------------------------------------------------------- void ConnectionServer::unsetupConnections() { // remove all ClientConnection objects so when Connection::remove() is called we don't crash since // ConnectionServer no longer exists while (!instance().connectedMap.empty()) { SuidMap::iterator i = instance().connectedMap.begin(); if (i != instance().connectedMap.end()) { ClientConnection * c = (*i).second; uint32 suid = (*i).first; IGNORE_RETURN(instance().connectedMap.erase(suid)); if (c) { ConnectionServer::dropClient(c, "ConnectionServer shutting down."); delete c; } } } if (customerService) { delete customerService; customerService = 0; } if (chatService) { delete chatService; chatService = 0; } if (centralConnection) { delete centralConnection; centralConnection = 0; } if (gameService) { delete gameService; gameService = 0; } } //----------------------------------------------------------------------- void ConnectionServer::setupConnections() { // set up message connections connectToMessage("ConnectionGameServerConnect"); connectToMessage("SetConnectionServerPublic"); // set up port to listen for clients, central, and game servers connectToMessage("CentralConnectionOpened"); connectToMessage("CentralConnectionClosed"); NetworkSetupData setup; setup.port = ConfigConnectionServer::getGameServicePort(); setup.bindInterface = ConfigConnectionServer::getGameServiceBindInterface(); setup.maxConnections = 1000; gameService = new Service(ConnectionAllocator(), setup); connectToMessage("GameConnectionOpened"); connectToMessage("GameConnectionClosed"); // connect to central server centralConnection = new CentralConnection(ConfigConnectionServer::getCentralServerAddress(), ConfigConnectionServer::getCentralServerPort()); setup.port = 0; setup.bindInterface = ConfigConnectionServer::getChatServiceBindInterface(); chatService = new Service(ConnectionAllocator(), setup); connectToMessage("ChatServerConnectionOpened"); connectToMessage("ChatServerConnectionClosed"); setup.bindInterface = ConfigConnectionServer::getCustomerServiceBindInterface(); customerService = new Service(ConnectionAllocator(), setup); connectToMessage("CustomerServiceConnectionOpened"); connectToMessage("CustomerServiceConnectionClosed"); connectToMessage("ConnectionServerId"); connectToMessage("ConnectionKeyPush"); connectToMessage("LoginKeyPush"); connectToMessage("CharacterListMessage"); //Create Characters Messages connectToMessage("ClientCreateCharacter"); connectToMessage("ConnectionCreateCharacterSuccess"); connectToMessage("ConnectionCreateCharacterFailed"); connectToMessage("NewCharacterCreated"); connectToMessage("GameServerForLoginMessage"); // name query messages connectToMessage("ClientRandomNameRequest"); // from client connectToMessage("RandomNameResponse"); // from game server connectToMessage("ClientVerifyAndLockNameRequest"); // from client connectToMessage("VerifyAndLockNameResponse"); // from game server connectToMessage("ValidateCharacterForLoginReplyMessage"); connectToMessage("ValidateAccountReplyMessage"); connectToMessage("ProfilerOperationMessage"); connectToMessage("CentralConnectionClosed"); connectToMessage("CentralConnectionOpened"); connectToMessage("ChunkCompleteMessage"); connectToMessage("FrameEndMessage"); connectToMessage("GameConnectionClosed"); connectToMessage("GameGameServerConnect"); connectToMessage("GameServerReadyMessage"); connectToMessage("GameServerUniverseLoadedMessage"); connectToMessage("PersistedPlayerMessage"); connectToMessage("PreloadListMessage"); connectToMessage("PreloadRequestCompleteMessage"); connectToMessage("ProfilerOperationMessage"); connectToMessage("RequestGameServerForLoginMessage"); connectToMessage("RequestSceneTransfer"); connectToMessage("ShutdownMessage"); connectToMessage("TaskConnectionOpened"); connectToMessage("UnloadedPlayerMessage"); connectToMessage("WatcherConnectionClosed"); connectToMessage("WatcherConnectionOpened"); connectToMessage("ExcommunicateGameServerMessage"); connectToMessage("CntrlSrvDropDupeConns"); } //---------------------------------------------------------------------- uint16 ConnectionServer::getPingPort() { static ConnectionServer & cs = instance(); return cs.pingSocket->getBindAddress().getHostPort(); } //----------------------------------------------------------------------- Client* ConnectionServer::getClient(const NetworkId & oid) { static ConnectionServer & cs = instance(); ClientMap::const_iterator iter = cs.clientMap.find(oid); if (iter != cs.clientMap.end()) return (*iter).second; return 0; } //----------------------------------------------------------------------- const ConnectionServer::ClientMap & ConnectionServer::getClientMap() { return instance().clientMap; } //----------------------------------------------------------------------- GameConnection* ConnectionServer::getGameConnection(uint32 gameServerId) { static ConnectionServer & cs = instance(); const GameServerMap::const_iterator i = cs.gameServerMap.find(gameServerId); if (i != cs.gameServerMap.end()) return (*i).second; return nullptr; } //----------------------------------------------------------------------- GameConnection* ConnectionServer::getAnyGameConnection() { static ConnectionServer & cs = instance(); if (!cs.gameServerMap.empty()) return cs.gameServerMap.begin()->second; return nullptr; } //----------------------------------------------------------------------- int ConnectionServer::getPingTrafficNumBytes() { static ConnectionServer & cs = instance(); return cs.m_pingTrafficNumBytes; } //----------------------------------------------------------------------- int ConnectionServer::getNumberOfClients() { static ConnectionServer & cs = instance(); return static_cast(cs.connectedMap.size()); } //----------------------------------------------------------------------- int ConnectionServer::getNumberOfFreeTrials() { static ConnectionServer & cs = instance(); return static_cast(cs.freeTrials.size()); } //----------------------------------------------------------------------- int ConnectionServer::getNumberOfGameServers() { static ConnectionServer & cs = instance(); return static_cast(cs.gameServerMap.size()); } //----------------------------------------------------------------------- void ConnectionServer::removeConnectedCharacter(uint32 suid) { static ConnectionServer & cs = instance(); cs.removeFromConnectedMap(suid); } //----------------------------------------------------------------------- void ConnectionServer::sendToCentralProcess(const GameNetworkMessage & msg) { static ConnectionServer & cs = instance(); if (cs.centralConnection) cs.centralConnection->send(msg, true); else WARNING(true, ("Connection tried to send a message Central, but there is no Central Connection")); } //----------------------------------------------------------------------- CentralConnection * ConnectionServer::getCentralConnection() { static ConnectionServer & cs = instance(); return cs.centralConnection; } //----------------------------------------------------------------------- void ConnectionServer::installSessionValidation() { int i = 0; std::vector sessionServers; int const numberOfSessionServers = ConfigConnectionServer::getNumberOfSessionServers(); for (i = 0; i < numberOfSessionServers; ++i) { char const * const p = ConfigConnectionServer::getSessionServer(i); if (p) { REPORT_LOG(true, ("Using session server %s\n", p)); sessionServers.push_back(p); } } // if there were none specified, use defaults FATAL(i == 0, ("No session servers specified for session API")); m_sessionApiClient = new SessionApiClient(&sessionServers[0], i); } // ---------------------------------------------------------------------- void ConnectionServer::addToClientMap(const NetworkId &oid, ClientConnection* cconn) { Client * newClient = new Client(cconn, oid); clientMap[oid] = newClient; //associate the ClientConnection object. cconn->setClient(newClient); } // ---------------------------------------------------------------------- void ConnectionServer::removeFromClientMap(const NetworkId &oid) { ClientMap::iterator i = clientMap.find(oid); if (i != clientMap.end()) { clientMap.erase(i); } } //----------------------------------------------------------------------- void ConnectionServer::addToConnectedMap(uint32 suid, ClientConnection* cconn) { SuidMap::iterator i = connectedMap.find(suid); if (i == connectedMap.end()) { connectedMap[suid] = cconn; } else { WARNING_STRICT_FATAL(true, ("Attepting to add duplicate connected chatacter")); connectedMap[suid] = cconn; } if (((cconn->getSubscriptionFeatures() & ClientSubscriptionFeature::FreeTrial) != 0) && ((cconn->getSubscriptionFeatures() & ClientSubscriptionFeature::Base) == 0)) { freeTrials.insert(suid); } // Update the population on the CentralServer immediately // since we are trying to avoid people "rushing" the server updatePopulationOnCentralServer(); } // ---------------------------------------------------------------------- void ConnectionServer::removeFromConnectedMap(uint32 suid) { SuidMap::iterator i = connectedMap.find(suid); if (i != connectedMap.end()) { connectedMap.erase(i); } FreeTrialsSet::iterator j = freeTrials.find(suid); if (j != freeTrials.end()) { freeTrials.erase(j); } // We could update the CentralServer population but people // leaving the server are not as important as people connecting // and so we will wait for update() to handle things } // ---------------------------------------------------------------------- void ConnectionServer::updatePopulationOnCentralServer() { if (centralConnection) { // Total number of clients and how many of those are free trials const int numPlayers = getNumberOfClients(); const int numFreeTrial = getNumberOfFreeTrials(); // We are concerned about too many people piling up at the beginning // of the tutorial, so count how many players could be a problem int numPlayersEmptyScene = 0; int numPlayersTutorialScene = 0; int numPlayersFalconScene = 0; // Walk through the clients and evaluate what scene they are in SuidMap::const_iterator i; for (i = connectedMap.begin(); i != connectedMap.end(); ++i) { const ClientConnection * const conn = (*i).second; const Client * const client = conn->getClient(); if (client && client->getGameConnection()) { const std::string& scene = client->getGameConnection()->getSceneName(); if (scene.empty()) { numPlayersEmptyScene += 1; } else if (scene == SCENE_NAME_TUTORIAL) { numPlayersTutorialScene += 1; } else if (scene.substr(0, SCENE_NAME_FALCON_PREFIX.length()) == SCENE_NAME_FALCON_PREFIX) { numPlayersFalconScene += 1; } } else { numPlayersEmptyScene += 1; } } const UpdatePlayerCountMessage msg(false, numPlayers, numFreeTrial, numPlayersEmptyScene, numPlayersTutorialScene, numPlayersFalconScene); centralConnection->send(msg, true); } } // ---------------------------------------------------------------------- SessionApiClient* ConnectionServer::getSessionApiClient() { // this is causing crashes when ConnectionServer is shutdown and something calls this function // because instance() returns 0. if (s_connectionServer) { return instance().m_sessionApiClient; } else { return 0; } } // ---------------------------------------------------------------------- void ConnectionServer::setDone(char const *reasonfmt, ...) { if (!done) { char reason[1024]; va_list ap; va_start(ap, reasonfmt); IGNORE_RETURN(_vsnprintf(reason, sizeof(reason), reasonfmt, ap));//lint !e530 Symbol 'ap' not initialized reason[sizeof(reason) - 1] = '\0'; LOG( "ServerShutdown", ( "ConnectionServer (pid %d) shutdown, reason: %s", static_cast(Os::getProcessId()), reason)); REPORT_LOG( true, ( "ConnectionServer (pid %d) shutdown, reason: %s\n", static_cast(Os::getProcessId()), reason)); done = true; va_end(ap); } } // ======================================================================