Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nelns / welcome_service / welcome_service.cpp
blob847ecfa9f4c45577a9fd68210ec8a8d1cbe384e5
1 // NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif // HAVE_CONFIG_H
21 #ifndef NELNS_CONFIG
22 #define NELNS_CONFIG ""
23 #endif // NELNS_CONFIG
25 #ifndef NELNS_LOGS
26 #define NELNS_LOGS ""
27 #endif // NELNS_LOGS
29 #include "nel/misc/types_nl.h"
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <math.h>
35 #include <list>
37 #include "nel/misc/debug.h"
38 #include "nel/misc/config_file.h"
39 #include "nel/misc/displayer.h"
40 #include "nel/misc/command.h"
41 #include "nel/misc/variable.h"
42 #include "nel/misc/log.h"
43 #include "nel/misc/file.h"
44 #include "nel/misc/path.h"
46 #include "nel/net/service.h"
47 #include "nel/net/unified_network.h"
48 #include "nel/net/login_cookie.h"
50 #include "welcome_service.h"
52 using namespace std;
53 using namespace NLMISC;
54 using namespace NLNET;
55 using namespace WS;
58 CVariable<sint> PlayerLimit(
59 "ws","PlayerLimit", "Rough max number of players accepted on this shard (-1 for Unlimited)",
60 5000,
61 0, true );
63 // Forward declaration of callback cbShardOpen (see ShardOpen variable)
64 void cbShardOpen(IVariable &var);
66 // Forward declaration of callback cbShardOpenStateFile (see ShardOpenStateFile variable)
67 void cbShardOpenStateFile(IVariable &var);
69 // Forward declaration of callback cbUsePatchMode
70 void cbUsePatchMode(IVariable &var);
72 // Types of open state
73 enum TShardOpenState
75 ClosedForAll = 0,
76 OpenOnlyForAllowed = 1,
77 OpenForAll = 2
80 static bool AllowDispatchMsgToLS = false;
82 /**
83 * ShardOpen
84 * true if shard is open to public
85 * 0 means closed for all but :DEV:
86 * 1 means open only for groups in config file (see OpenGroups variable) and :DEV:
87 * 2 means open for all
89 CVariable<uint> ShardOpen("ws", "ShardOpen", "Indicates if shard is open to public (0 closed for all but :DEV:, 1 open only for groups in cfg, 2 open for all)", 2, 0, true, cbShardOpen);
91 /**
92 * ShardOpenStateFile
93 * true if shard is open to public
95 CVariable<string> ShardOpenStateFile("ws", "ShardOpenStateFile", "Name of the file that contains ShardOpen state", "", 0, true, cbShardOpenStateFile);
97 /**
98 * OpenGroups
100 CVariable<string> OpenGroups("ws", "OpenGroups", "list of groups allowed at ShardOpen Level 1", "", 0, true);
103 * OpenFrontEndThreshold
104 * The FS balance algorithm works like this:
105 * - select the least loaded frontend
106 * - if this frontend has more than the OpenFrontEndThreshold
107 * - try to open a new frontend
108 * - reselect least loaded frontend
110 CVariable<uint> OpenFrontEndThreshold("ws", "OpenFrontEndThreshold", "Limit number of players on all FS to decide to open a new FS", 800, 0, true );
114 * Use Patch mode
116 CVariable<bool> UsePatchMode("ws", "UsePatchMode", "Use Frontends as Patch servers (at FS startup)", true, 0, true, cbUsePatchMode );
119 * Use Patch mode
121 CVariable<bool> DontUseLS("ws", "DontUseLS", "Don't use the login service", false, 0, true);
124 // Shortcut to the module instance
125 //CWelcomeServiceMod *CWelcomeServiceMod::_Instance = NULL;
129 * Using expected services and current running service instances, this class
130 * reports a main "online status".
132 class COnlineServices
134 public:
136 /// Set expected instances. Ex: { "TICKS", "FS", "FS", "FS" }
137 void setExpectedInstances( CConfigFile::CVar& var )
139 // Reset "expected" counters (but don't clear the map, keep the running instances)
140 CInstances::iterator ici;
141 for ( ici=_Instances.begin(); ici!=_Instances.end(); ++ici )
143 (*ici).second.Expected = 0;
145 // Rebuild "expected" counters
146 for ( uint i=0; i!=var.size(); ++i )
148 ++_Instances[var.asString(i)].Expected;
152 /// Add a service instance
153 void addInstance( const std::string& serviceName )
155 ++_Instances[serviceName].Running;
158 /// Remove a service instance
159 void removeInstance( const std::string& serviceName )
161 CInstances::iterator ici = _Instances.find( serviceName );
162 if ( ici != _Instances.end() )
164 --(*ici).second.Running;
166 // Remove from the map only if not part of the expected list
167 if ( ((*ici).second.Expected == 0) && ((*ici).second.Running == 0) )
169 _Instances.erase( ici );
172 else
174 nlwarning( "Can't remove instance of %s", serviceName.c_str() );
178 /// Check if all expected instances are online
179 bool getOnlineStatus() const
181 CInstances::const_iterator ici;
182 for ( ici=_Instances.begin(); ici!=_Instances.end(); ++ici )
184 if ( ! ici->second.isOnlineAsExpected() )
185 return false;
187 return true;
190 /// Display contents
191 void display( NLMISC::CLog& log = *NLMISC::DebugLog )
193 CInstances::const_iterator ici;
194 for ( ici=_Instances.begin(); ici!=_Instances.end(); ++ici )
196 log.displayNL( "%s: %s (%u expected, %u running)",
197 (*ici).first.c_str(),
198 (*ici).second.Expected ? ((*ici).second.isOnlineAsExpected() ? "ONLINE" : "MISSING") : "OPTIONAL",
199 (*ici).second.Expected, (*ici).second.Running );
203 private:
205 struct TInstanceCounters
207 TInstanceCounters() : Expected(0), Running(0) {}
209 // If not expected, count as online as well
210 bool isOnlineAsExpected() const { return Running >= Expected; }
212 uint Expected;
213 uint Running;
216 typedef std::map< std::string, TInstanceCounters > CInstances;
218 CInstances _Instances;
221 /// Online services
222 COnlineServices OnlineServices;
225 /// Main online status
226 bool OnlineStatus;
228 /// Send changes of status to the LS
229 void reportOnlineStatus( bool newStatus )
231 if ( newStatus != OnlineStatus && AllowDispatchMsgToLS )
233 if (!DontUseLS)
235 CMessage msgout( "OL_ST" );
236 msgout.serial( newStatus );
237 CUnifiedNetwork::getInstance()->send( "LS", msgout );
240 if (CWelcomeServiceMod::isInitialized())
242 // send a status report to welcome service client
243 CWelcomeServiceMod::getInstance()->reportWSOpenState(newStatus);
246 OnlineStatus = newStatus;
252 /// Set the version of the shard. you have to increase it each time the client-server protocol changes.
253 /// You have to increment the client too (the server and client version must be the same to run correctly)
254 static const uint32 ServerVersion = 1;
256 /// Contains the correspondance between userid and the FES connection where the userid is connected.
257 map<uint32, TServiceId> UserIdSockAssociations;
259 // ubi hack
260 string FrontEndAddress;
264 enum TFESState
266 PatchOnly,
267 AcceptClientOnly
270 struct CFES
272 CFES (TServiceId sid) : SId(sid), NbPendingUsers(0), NbUser(0), State(PatchOnly) { }
274 TServiceId SId; // Connection to the front end
275 uint32 NbPendingUsers; // Number of not yet connected users (but rooted to this frontend)
276 uint32 NbUser; // Number of user currently connected on this front end
278 TFESState State; // State of frontend (patching/accepting clients)
279 std::string PatchAddress; // Address of frontend patching server
281 uint32 getUsersCountHeuristic() const
283 return NbUser + NbPendingUsers;
286 void setToAcceptClients()
288 if (State == AcceptClientOnly)
289 return;
291 // tell FS to accept client
292 State = AcceptClientOnly;
293 CMessage msgOpenFES("FS_ACCEPT");
294 CUnifiedNetwork::getInstance()->send(SId, msgOpenFES);
296 // report state to LS
297 bool dummy;
298 reportStateToLS(dummy, true);
301 void reportStateToLS(bool& reportPatching, bool alive = true)
303 // report to LS
305 bool patching = (State == PatchOnly);
306 if (alive && patching)
307 reportPatching = true;
309 if ( AllowDispatchMsgToLS )
311 if (!DontUseLS)
313 CMessage msgout("REPORT_FS_STATE");
314 msgout.serial(SId);
315 msgout.serial(alive);
316 msgout.serial(patching);
317 msgout.serial(PatchAddress);
318 CUnifiedNetwork::getInstance()->send("LS", msgout);
324 list<CFES> FESList;
327 * Find the best front end service for a new connecting user (return NULL if there is no suitable FES).
328 * Additionally, calculate totalNbUsers.
330 CFES *findBestFES ( uint& totalNbUsers )
332 totalNbUsers = 0;
334 CFES* best = NULL;
336 for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
338 CFES &fes = *it;
339 if (fes.State == AcceptClientOnly)
341 if (best == NULL || best->getUsersCountHeuristic() > fes.getUsersCountHeuristic())
342 best = &fes;
344 totalNbUsers += fes.NbUser;
349 return best;
353 * Select a frontend in patch mode to open
354 * Returns true if a new FES was open, false if no FES could be open
356 bool openNewFES()
358 for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
360 if ((*it).State == PatchOnly)
362 nlinfo("openNewFES: ask the FS %d to accept clients", it->SId.get());
364 // switch FES to AcceptClientOnly
365 (*it).setToAcceptClients();
366 return true;
370 return false;
375 void displayFES ()
377 nlinfo ("There's %d FES in the list:", FESList.size());
378 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
380 nlinfo(" > %u NbUser:%d NbPendingUser:%d", it->SId.get(), it->NbUser, it->NbPendingUsers);
382 nlinfo ("End of the list");
390 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
391 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
392 //////////////////// CONNECTION TO THE FRONT END SERVICE ///////////////////////////////////////////////////////
393 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
394 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
396 void cbFESShardChooseShard (CMessage &msgin, const std::string &serviceName, TServiceId sid)
398 // the WS answer a user authorize
399 string reason;
400 CLoginCookie cookie;
401 string addr;
404 // S09: receive "SCS" message from FES and send the "SCS" message to the LS
407 CMessage msgout ("SCS");
409 msgin.serial (reason);
410 msgout.serial (reason);
412 msgin.serial (cookie);
413 msgout.serial (cookie);
415 if (reason.empty())
417 msgin.serial (addr);
419 // if we set the FontEndAddress in the welcome_service.cfg we use this address
420 if (FrontEndAddress.empty())
422 msgout.serial (addr);
424 else
426 msgout.serial (FrontEndAddress);
429 uint32 nbPendingUser;
430 msgin.serial(nbPendingUser);
432 // update the pending user count for this shard
433 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
435 if (it->SId == sid)
437 it->NbPendingUsers = nbPendingUser;
438 break;
443 // OBSOLETE: LS doesn't read patching URLs
444 // build patch server list
445 std::string PatchURLS;
446 for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
448 if ((*it).State == PatchOnly && !(*it).PatchAddress.empty())
450 if (!PatchURLS.empty())
451 PatchURLS += '|';
452 PatchURLS += (*it).PatchAddress;
457 msgout.serial(PatchURLS);
461 if (PendingFeResponse.find(cookie) != PendingFeResponse.end())
463 nldebug( "ERLOG: SCS recvd from %s-%hu => sending %s to SU", serviceName.c_str(), sid.get(), cookie.toString().c_str());
465 // this response is not waited by LS
466 TPendingFEResponseInfo &pfri = PendingFeResponse.find(cookie)->second;
468 pfri.WSMod->frontendResponse(pfri.WaiterModule, pfri.UserId, reason, cookie, addr);
469 // cleanup pending record
470 PendingFeResponse.erase(cookie);
472 else
474 nldebug( "ERLOG: SCS recvd from %s-%hu, but pending %s not found", serviceName.c_str(), sid.get(), cookie.toString().c_str());
476 // return the result to the LS
477 if (!DontUseLS)
479 CUnifiedNetwork::getInstance()->send ("LS", msgout);
485 // This function is call when a FES accepted a new client or lost a connection to a client
486 void cbFESClientConnected (CMessage &msgin, const std::string &serviceName, TServiceId sid)
489 // S15: receive "CC" message from FES and send "CC" message to the "LS"
492 CMessage msgout ("CC");
494 uint32 userid;
495 msgin.serial (userid);
496 msgout.serial (userid);
498 uint8 con;
499 msgin.serial (con);
500 msgout.serial (con);
502 if (!DontUseLS)
504 CUnifiedNetwork::getInstance()->send ("LS", msgout);
507 // add or remove the user number really connected on this shard
508 uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
509 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
511 if (it->SId == sid)
513 if (con)
515 (*it).NbUser++;
517 // the client connected, it's no longer pending
518 if ((*it).NbPendingUsers > 0)
519 (*it).NbPendingUsers--;
521 else
523 if ( (*it).NbUser != 0 )
524 (*it).NbUser--;
527 totalNbOnlineUsers += (*it).NbUser;
528 totalNbPendingUsers += (*it).NbPendingUsers;
531 if (CWelcomeServiceMod::isInitialized())
532 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
534 if (con)
536 // we know that this user is on this FES
537 UserIdSockAssociations.insert (make_pair (userid, sid));
539 else
541 // remove the user
542 UserIdSockAssociations.erase (userid);
547 // This function is called when a FES rejected a client' cookie
548 void cbFESRemovedPendingCookie(CMessage &msgin, const std::string &serviceName, TServiceId sid)
550 CLoginCookie cookie;
551 msgin.serial(cookie);
552 nldebug( "ERLOG: RPC recvd from %s-%hu => %s removed", serviceName.c_str(), sid.get(), cookie.toString().c_str(), cookie.toString().c_str());
555 // client' cookie rejected, no longer pending
556 uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
557 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
559 if ((*it).SId == sid)
561 if ((*it).NbPendingUsers > 0)
562 --(*it).NbPendingUsers;
564 totalNbOnlineUsers += (*it).NbUser;
565 totalNbPendingUsers += (*it).NbPendingUsers;
568 if (CWelcomeServiceMod::isInitialized())
570 CWelcomeServiceMod::getInstance()->pendingUserLost(cookie);
571 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
575 // This function is called by FES to setup its PatchAddress
576 void cbFESPatchAddress(CMessage &msgin, const std::string &serviceName, TServiceId sid)
578 std::string address;
579 msgin.serial(address);
581 bool acceptClients;
582 msgin.serial(acceptClients);
584 nldebug("Received patch server address '%s' from service %s %d", address.c_str(), serviceName.c_str(), sid.get());
586 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
588 if ((*it).SId == sid)
590 nldebug("Affected patch server address '%s' to frontend %s %d", address.c_str(), serviceName.c_str(), sid.get());
592 if (!UsePatchMode.get() && !acceptClients)
594 // not in patch mode, force fs to accept clients
595 acceptClients = true;
596 (*it).setToAcceptClients();
599 (*it).PatchAddress = address;
600 (*it).State = (acceptClients ? AcceptClientOnly : PatchOnly);
601 if (acceptClients)
602 nldebug("Frontend %s %d reported to accept client, patching unavailable for that server", address.c_str(), serviceName.c_str(), sid.get());
603 else
604 nldebug("Frontend %s %d reported to be in patching mode", address.c_str(), serviceName.c_str(), sid.get());
606 bool dummy;
607 (*it).reportStateToLS(dummy);
608 break;
613 // This function is called by FES to setup the right number of players (if FES was already present before WS launching)
614 void cbFESNbPlayers(CMessage &msgin, const std::string &serviceName, TServiceId sid)
616 // *********** WARNING *******************
617 // This version of the callback is deprecated, the system
618 // now use cbFESNbPlayers2 that report the pending user count
619 // as well as the number of connected players.
620 // It is kept for backward compatibility only.
621 // ***************************************
623 uint32 nbPlayers;
624 msgin.serial(nbPlayers);
626 uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
627 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
629 if ((*it).SId == sid)
631 nldebug("Frontend '%d' reported %d online users", sid.get(), nbPlayers);
632 (*it).NbUser = nbPlayers;
633 if (nbPlayers != 0 && (*it).State == PatchOnly)
635 nlwarning("Frontend %d is in state PatchOnly, yet reports to have online %d players, state AcceptClientOnly is forced (FS_ACCEPT message sent)");
636 (*it).setToAcceptClients();
639 totalNbOnlineUsers += (*it).NbUser;
640 totalNbPendingUsers += (*it).NbPendingUsers;
643 if (CWelcomeServiceMod::isInitialized())
644 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
648 // This function is called by FES to setup the right number of players (if FES was already present before WS launching)
649 void cbFESNbPlayers2(CMessage &msgin, const std::string &serviceName, TServiceId sid)
651 uint32 nbPlayers;
652 uint32 nbPendingPlayers;
653 msgin.serial(nbPlayers);
654 msgin.serial(nbPendingPlayers);
656 uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
657 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
659 CFES &fes = *it;
660 if (fes.SId == sid)
662 nldebug("Frontend '%d' reported %d online users", sid.get(), nbPlayers);
663 fes.NbUser = nbPlayers;
664 fes.NbPendingUsers = nbPendingPlayers;
665 if (nbPlayers != 0 && fes.State == PatchOnly)
667 nlwarning("Frontend %d is in state PatchOnly, yet reports to have online %d players, state AcceptClientOnly is forced (FS_ACCEPT message sent)");
668 (*it).setToAcceptClients();
671 totalNbOnlineUsers += fes.NbUser;
672 totalNbPendingUsers += fes.NbPendingUsers;
675 if (CWelcomeServiceMod::isInitialized())
676 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
680 * Set Shard open state
682 void setShardOpenState(TShardOpenState state, bool writeInVar = true)
684 if (writeInVar)
685 ShardOpen = state;
687 if ( AllowDispatchMsgToLS )
689 if (!DontUseLS)
691 // send to LS current shard state
692 CMessage msgout ("SET_SHARD_OPEN");
693 uint8 shardOpenState = (uint8)state;
695 msgout.serial (shardOpenState);
696 CUnifiedNetwork::getInstance()->send ("LS", msgout);
703 * Set Shard Open State
704 * uint8 Open State (0 closed for all, 1 open for groups in cfg, 2 open for all)
706 void cbSetShardOpen(CMessage &msgin, const std::string &serviceName, TServiceId sid)
708 uint8 shardOpenState;
709 msgin.serial (shardOpenState);
711 if (shardOpenState > OpenForAll)
713 shardOpenState = OpenForAll;
716 setShardOpenState((TShardOpenState)shardOpenState);
719 // forward declaration to callback
720 void cbShardOpenStateFile(IVariable &var);
723 * Restore Shard Open state from config file or from file if found
725 void cbRestoreShardOpen(CMessage &msgin, const std::string &serviceName, TServiceId sid)
727 // first restore state from config file
728 CConfigFile::CVar* var = IService::getInstance()->ConfigFile.getVarPtr("ShardOpen");
729 if (var != NULL)
731 setShardOpenState((TShardOpenState)var->asInt());
734 // then restore state from state file, if it exists
735 cbShardOpenStateFile(ShardOpenStateFile);
742 // a new front end connecting to me, add it
743 void cbFESConnection (const std::string &serviceName, TServiceId sid, void *arg)
745 FESList.push_back (CFES ((TServiceId)sid));
746 nldebug("new FES connection: sid %u", sid.get());
747 displayFES ();
749 bool dummy;
750 FESList.back().reportStateToLS(dummy);
752 if (!UsePatchMode.get())
754 FESList.back().setToAcceptClients();
759 // a front end closes the connection, deconnect him
760 void cbFESDisconnection (const std::string &serviceName, TServiceId sid, void *arg)
762 nldebug("new FES disconnection: sid %u", sid.get());
764 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
766 if ((*it).SId == sid)
768 // send a message to the LS to say that all players from this FES are offline
769 map<uint32, TServiceId>::iterator itc = UserIdSockAssociations.begin();
770 map<uint32, TServiceId>::iterator nitc = itc;
771 while (itc != UserIdSockAssociations.end())
773 nitc++;
774 if ((*itc).second == sid)
776 // bye bye little player
777 uint32 userid = (*itc).first;
778 nlinfo ("Due to a frontend crash, removed the player %d", userid);
779 if (!DontUseLS)
781 CMessage msgout ("CC");
782 msgout.serial (userid);
783 uint8 con = 0;
784 msgout.serial (con);
785 CUnifiedNetwork::getInstance()->send ("LS", msgout);
787 UserIdSockAssociations.erase (itc);
789 itc = nitc;
792 bool dummy;
793 (*it).reportStateToLS(dummy, false);
795 // remove the FES
796 FESList.erase (it);
798 break;
802 // Update the welcome service client with the new count of connection
804 uint32 totalNbOnlineUsers =0, totalNbPendingUsers = 0;
805 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
807 const CFES &fes = *it;
808 totalNbOnlineUsers += fes.NbUser;
809 totalNbPendingUsers += fes.NbPendingUsers;
812 if (CWelcomeServiceMod::isInitialized())
813 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
815 displayFES ();
820 void cbServiceUp (const std::string &serviceName, TServiceId sid, void *arg)
822 OnlineServices.addInstance( serviceName );
823 bool online = OnlineServices.getOnlineStatus();
824 reportOnlineStatus( online );
826 // send shard id to service
827 sint32 shardId;
828 if (IService::getInstance()->haveArg('S'))
830 // use the command line param if set
831 shardId = atoi(IService::getInstance()->getArg('S').c_str());
833 else if (IService::getInstance()->ConfigFile.exists ("ShardId"))
835 // use the config file param if set
836 shardId = IService::getInstance()->ConfigFile.getVar ("ShardId").asInt();
838 else
840 shardId = -1;
843 if (shardId == -1)
845 nlerror ("ShardId variable must be valid (>0)");
848 CMessage msgout("R_SH_ID");
849 msgout.serial(shardId);
850 CUnifiedNetwork::getInstance()->send (sid, msgout);
855 void cbServiceDown (const std::string &serviceName, TServiceId sid, void *arg)
857 OnlineServices.removeInstance( serviceName );
858 bool online = OnlineServices.getOnlineStatus();
859 reportOnlineStatus( online );
863 // Callback Array for message from FES
864 TUnifiedCallbackItem FESCallbackArray[] =
866 { "SCS", cbFESShardChooseShard },
867 { "CC", cbFESClientConnected },
868 { "RPC", cbFESRemovedPendingCookie },
869 { "FEPA", cbFESPatchAddress },
870 { "NBPLAYERS", cbFESNbPlayers },
871 { "NBPLAYERS2", cbFESNbPlayers2 },
873 { "SET_SHARD_OPEN", cbSetShardOpen },
874 { "RESTORE_SHARD_OPEN", cbRestoreShardOpen },
878 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
879 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
880 //////////////////// CONNECTION TO THE LOGIN SERVICE ///////////////////////////////////////////////////////////
881 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
882 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
883 void cbLSChooseShard (CMessage &msgin, const std::string &serviceName, TServiceId sid)
885 // the LS warns me that a new client want to come in my shard
887 nldebug( "ERLOG: CS recvd from %s-%hu", serviceName.c_str(), sid.get());
890 // S07: receive the "CS" message from LS and send the "CS" message to the selected FES
893 CLoginCookie cookie;
894 msgin.serial (cookie);
895 string userName, userPriv, userExtended;
896 msgin.serial (userName);
900 msgin.serial (userPriv);
902 catch (Exception &)
904 nlwarning ("LS didn't give me the user privilege for user '%s', set to empty", userName.c_str());
909 msgin.serial (userExtended);
911 catch (Exception &)
913 nlwarning ("LS didn't give me the extended data for user '%s', set to empty", userName.c_str());
917 string ret = lsChooseShard(userName, cookie, userPriv, userExtended, WS::TUserRole::ur_player, 0xffffffff, ~0);
919 if (!ret.empty())
921 // send back an error message to LS
922 CMessage msgout ("SCS");
923 msgout.serial (ret);
924 msgout.serial (cookie);
925 CUnifiedNetwork::getInstance()->send(sid, msgout);
929 //void cbLSChooseShard (CMessage &msgin, const std::string &serviceName, uint16 sid)
930 std::string lsChooseShard (const std::string &userName,
931 const CLoginCookie &cookie,
932 const std::string &userPriv,
933 const std::string &userExtended,
934 WS::TUserRole userRole,
935 uint32 instanceId,
936 uint32 charSlot)
938 // the LS warns me that a new client want to come in my shard
941 // S07: receive the "CS" message from LS and send the "CS" message to the selected FES
945 uint totalNbUsers;
946 CFES *best = findBestFES( totalNbUsers );
947 if (best == NULL)
949 // answer the LS that we can't accept the user
950 CMessage msgout ("SCS");
951 string reason = "No front-end server available";
952 msgout.serial (reason);
953 msgout.serial (cookie);
954 CUnifiedNetwork::getInstance()->send(sid, msgout);
955 return;
959 uint totalNbUsers;
960 CFES* best = findBestFES( totalNbUsers );
962 // could not find a good FES or best FES has more players than balance limit
963 if (best == NULL || best->getUsersCountHeuristic() >= OpenFrontEndThreshold)
965 // open a new frontend
966 openNewFES();
968 // reselect best FES (will return newly open FES, or previous if no more FES available)
969 best = findBestFES(totalNbUsers);
971 // check there is a FES available
972 if (best == NULL)
974 // answer the LS that we can't accept the user
975 return "No front-end server available";
980 bool authorizeUser = false;
981 bool forceAuthorize = false;
983 if (userPriv == ":DEV:")
985 // devs have all privileges
986 authorizeUser = true;
987 forceAuthorize = true;
989 else if (ShardOpen != ClosedForAll)
991 const std::string& allowedGroups = OpenGroups;
992 bool userInOpenGroups = (!userPriv.empty() && !allowedGroups.empty() && allowedGroups.find(userPriv) != std::string::npos);
994 // open for all or user is privileged
995 authorizeUser = (ShardOpen == OpenForAll || userInOpenGroups);
996 // let authorized users to force access even if limit is reached
997 forceAuthorize = userInOpenGroups;
1000 bool shardLimitReached = ( (PlayerLimit.get() != -1) && (totalNbUsers >= (uint)PlayerLimit.get()) );
1002 if (!forceAuthorize && (!authorizeUser || shardLimitReached))
1004 // answer the LS that we can't accept the user
1005 CMessage msgout ("SCS");
1006 string reason;
1007 if (shardLimitReached)
1008 return "The shard is currently full, please try again in 5 minutes.";
1009 else
1010 return "The shard is closed.";
1014 CMessage msgout ("CS");
1015 msgout.serial (const_cast<CLoginCookie&>(cookie));
1016 msgout.serial (const_cast<string&>(userName), const_cast<string&>(userPriv), const_cast<string&>(userExtended));
1017 msgout.serial (instanceId);
1018 msgout.serial (charSlot);
1020 CUnifiedNetwork::getInstance()->send (best->SId, msgout);
1021 best->NbPendingUsers++;
1023 // Update counts
1024 uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
1025 for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
1027 totalNbOnlineUsers += (*it).NbUser;
1028 totalNbPendingUsers += (*it).NbPendingUsers;
1030 if (CWelcomeServiceMod::isInitialized())
1031 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
1033 return "";
1036 void cbFailed (CMessage &msgin, const std::string &serviceName, TServiceId sid)
1038 // I can't connect to the Login Service, just nlerror ();
1039 string reason;
1040 msgin.serial (reason);
1041 nlerror (reason.c_str());
1045 bool disconnectClient(uint32 userId)
1047 map<uint32, TServiceId>::iterator it = UserIdSockAssociations.find (userId);
1048 if (it == UserIdSockAssociations.end ())
1050 nlinfo ("Login service ask to disconnect user %d, he is not connected here, so ignoring", userId);
1051 return false;
1053 else
1055 CMessage msgout ("DC");
1056 msgout.serial (userId);
1057 CUnifiedNetwork::getInstance()->send (it->second, msgout);
1059 return true;
1063 void cbLSDisconnectClient (CMessage &msgin, const std::string &serviceName, TServiceId sid)
1065 // the LS tells me that i have to disconnect a client
1067 uint32 userid;
1068 msgin.serial (userid);
1070 disconnectClient(userid);
1074 // connection to the LS, send the identification message
1075 void cbLSConnection (const std::string &serviceName, TServiceId sid, void *arg)
1077 sint32 shardId;
1079 if (IService::getInstance()->haveArg('S'))
1081 // use the command line param if set
1082 shardId = atoi(IService::getInstance()->getArg('S').c_str());
1084 else if (IService::getInstance()->ConfigFile.exists ("ShardId"))
1086 // use the config file param if set
1087 shardId = IService::getInstance()->ConfigFile.getVar ("ShardId").asInt();
1089 else
1091 shardId = -1;
1094 if (shardId == -1)
1096 nlerror ("ShardId variable must be valid (>0)");
1099 CMessage msgout ("WS_IDENT");
1100 msgout.serial (shardId);
1101 CUnifiedNetwork::getInstance()->send (sid, msgout);
1103 nlinfo ("Connected to %s-%hu and sent identification with shardId '%d'", serviceName.c_str(), sid.get(), shardId);
1105 // send state to LS
1106 setShardOpenState((TShardOpenState)(ShardOpen.get()), false);
1109 if (!DontUseLS)
1111 CMessage msgrpn("REPORT_NO_PATCH");
1112 CUnifiedNetwork::getInstance()->send("LS", msgrpn);
1115 bool reportPatching = false;
1116 list<CFES>::iterator itfs;
1117 for (itfs=FESList.begin(); itfs!=FESList.end(); ++itfs)
1118 (*itfs).reportStateToLS(reportPatching);
1122 // Callback for detection of config file change about "ExpectedServices"
1123 void cbUpdateExpectedServices( CConfigFile::CVar& var )
1125 OnlineServices.setExpectedInstances( var );
1130 * ShardOpen update functions/callbacks etc.
1134 * updateShardOpenFromFile()
1135 * Update ShardOpen from a file.
1136 * Read a line of text in the file, converts it to int (atoi), then casts into bool for ShardOpen.
1138 void updateShardOpenFromFile(const std::string& filename)
1140 CIFile f;
1142 if (!f.open(filename))
1144 nlwarning("Failed to update ShardOpen from file '%s', couldn't open file", filename.c_str());
1145 return;
1150 char readBuffer[256];
1151 f.getline(readBuffer, 256);
1152 setShardOpenState((TShardOpenState)atoi(readBuffer));
1154 nlinfo("Updated ShardOpen state to '%u' from file '%s'", ShardOpen.get(), filename.c_str());
1156 catch (Exception& e)
1158 nlwarning("Failed to update ShardOpen from file '%s', exception raised while getline() '%s'", filename.c_str(), e.what());
1162 std::string ShardOpenStateFileName;
1165 * cbShardOpen()
1166 * Callback for ShardOpen
1168 void cbShardOpen(IVariable &var)
1170 setShardOpenState((TShardOpenState)(ShardOpen.get()), false);
1175 * cbShardOpenStateFile()
1176 * Callback for ShardOpenStateFile
1178 void cbShardOpenStateFile(IVariable &var)
1180 // remove previous file change callback
1181 if (!ShardOpenStateFileName.empty())
1183 CFile::removeFileChangeCallback(ShardOpenStateFileName);
1184 nlinfo("Removed callback for ShardOpenStateFileName file '%s'", ShardOpenStateFileName.c_str());
1187 ShardOpenStateFileName = var.toString();
1189 if (!ShardOpenStateFileName.empty())
1191 // set new callback for the file
1192 CFile::addFileChangeCallback(ShardOpenStateFileName, updateShardOpenFromFile);
1193 nlinfo("Set callback for ShardOpenStateFileName file '%s'", ShardOpenStateFileName.c_str());
1195 // and update state from file...
1196 updateShardOpenFromFile(ShardOpenStateFileName);
1201 * cbUsePatchMode()
1202 * Callback for UsePatchMode
1204 void cbUsePatchMode(IVariable &var)
1206 // if patch mode not set, set all fs in patching mode to accept clients now
1207 if (!UsePatchMode.get())
1209 nlinfo("UsePatchMode disabled, switch all patching servers to actual frontends");
1211 list<CFES>::iterator it;
1213 for (it=FESList.begin(); it!=FESList.end(); ++it)
1215 if ((*it).State == PatchOnly)
1217 (*it).setToAcceptClients();
1224 // Callback Array for message from LS
1225 TUnifiedCallbackItem LSCallbackArray[] =
1227 { "CS", cbLSChooseShard },
1228 { "DC", cbLSDisconnectClient },
1229 { "FAILED", cbFailed },
1232 class CWelcomeService : public IService
1235 public:
1237 /// Init the service, load the universal time.
1238 void init ()
1240 string FrontendServiceName = ConfigFile.getVar ("FrontendServiceName").asString();
1242 try { FrontEndAddress = ConfigFile.getVar ("FrontEndAddress").asString(); } catch(Exception &) { }
1244 nlinfo ("Waiting frontend services named '%s'", FrontendServiceName.c_str());
1246 CUnifiedNetwork::getInstance()->setServiceUpCallback(FrontendServiceName, cbFESConnection, NULL);
1247 CUnifiedNetwork::getInstance()->setServiceDownCallback(FrontendServiceName, cbFESDisconnection, NULL);
1248 CUnifiedNetwork::getInstance()->setServiceUpCallback("*", cbServiceUp, NULL);
1249 CUnifiedNetwork::getInstance()->setServiceDownCallback("*", cbServiceDown, NULL);
1251 // add a connection to the LS
1252 string LSAddr;
1253 if (haveArg('T'))
1255 // use the command line param if set
1256 LSAddr = getArg('T');
1258 else if (ConfigFile.exists ("LSHost"))
1260 // use the config file param if set
1261 LSAddr = ConfigFile.getVar("LSHost").asString();
1264 if (haveArg('S'))
1266 // use the command line param if set
1267 uint shardId = atoi(IService::getInstance()->getArg('S').c_str());
1269 nlinfo("Using shard id %u from command line '%s'", shardId, IService::getInstance()->getArg('S').c_str());
1270 anticipateShardId(shardId);
1272 else if (ConfigFile.exists ("ShardId"))
1274 // use the config file param if set
1275 uint shardId = IService::getInstance()->ConfigFile.getVar ("ShardId").asInt();
1277 nlinfo("Using shard id %u from config file '%s'", shardId, IService::getInstance()->ConfigFile.getVar ("ShardId").asString().c_str());
1278 anticipateShardId(shardId);
1281 // the config file must have a valid address where the login service is
1282 nlassert(!LSAddr.empty());
1284 // add default port if not set by the config file
1285 if (LSAddr.find (":") == string::npos)
1286 LSAddr += ":49999";
1288 AllowDispatchMsgToLS = true;
1290 if (ConfigFile.getVarPtr("DontUseLSService") == NULL
1291 || !ConfigFile.getVar("DontUseLSService").asBool())
1293 // We are using NeL Login Service
1294 CUnifiedNetwork::getInstance()->addCallbackArray(LSCallbackArray, sizeof(LSCallbackArray)/sizeof(LSCallbackArray[0]));
1295 if (!DontUseLS)
1297 CUnifiedNetwork::getInstance()->setServiceUpCallback("LS", cbLSConnection, NULL);
1298 CUnifiedNetwork::getInstance()->addService("LS", LSAddr);
1301 // List of expected service instances
1302 ConfigFile.setCallback( "ExpectedServices", cbUpdateExpectedServices );
1303 cbUpdateExpectedServices( ConfigFile.getVar( "ExpectedServices" ) );
1307 * read config variable ShardOpenStateFile to update
1310 cbShardOpenStateFile(ShardOpenStateFile);
1312 // // create a welcome service module (for SU comm)
1313 // IModuleManager::getInstance().createModule("WelcomeService", "ws", "");
1314 // // plug the module in the default gateway
1315 // NLMISC::CCommandRegistry::getInstance().execute("ws.plug wg", InfoLog());
1318 bool update ()
1320 // update the service status
1322 removeStatusTag("DEV_ONLY");
1323 removeStatusTag("RESTRICTED");
1324 removeStatusTag("Open");
1326 if (ShardOpen == 0)
1327 addStatusTag("DEV_ONLY");
1328 else if (ShardOpen == 1)
1329 addStatusTag("RESTRICTED");
1330 else if (ShardOpen == 2)
1331 addStatusTag("Open");
1333 return true;
1339 static const char* getCompleteServiceName(const IService* theService)
1341 static std::string s;
1342 s= "welcome_service";
1344 if (theService->haveLongArg("wsname"))
1346 s+= "_"+theService->getLongArg("wsname");
1349 if (theService->haveLongArg("fullwsname"))
1351 s= theService->getLongArg("fullwsname");
1354 return s.c_str();
1357 static const char* getShortServiceName(const IService* theService)
1359 static std::string s;
1360 s= "WS";
1362 if (theService->haveLongArg("shortwsname"))
1364 s= theService->getLongArg("shortwsname");
1367 return s.c_str();
1370 // Service instantiation
1371 NLNET_SERVICE_MAIN( CWelcomeService, getShortServiceName(scn), getCompleteServiceName(scn), 0, FESCallbackArray, NELNS_CONFIG, NELNS_LOGS);
1374 // welcome service module
1375 //class CWelcomeServiceMod :
1376 // public CEmptyModuleCommBehav<CEmptyModuleServiceBehav<CEmptySocketBehav<CModuleBase> > >,
1377 // public WS::CWelcomeServiceSkel
1379 // void onProcessModuleMessage(IModuleProxy *sender, const CMessage &message)
1380 // {
1381 // if (CWelcomeServiceSkel::onDispatchMessage(sender, message))
1382 // return;
1384 // nlwarning("Unknown message '%s' received by '%s'",
1385 // message.getName().c_str(),
1386 // getModuleName().c_str());
1387 // }
1390 // ////// CWelcomeServiceSkel implementation
1392 // // ask the welcome service to welcome a user
1393 // virtual void welcomeUser(NLNET::IModuleProxy *sender, uint32 userId, const std::string &userName, const CLoginCookie &cookie, const std::string &priviledge, const std::string &exPriviledge, WS::TUserRole mode, uint32 instanceId)
1394 // {
1395 // string ret = lsChooseShard(userName,
1396 // cookie,
1397 // priviledge,
1398 // exPriviledge,
1399 // mode,
1400 // instanceId);
1402 // if (!ret.empty())
1403 // {
1404 // // TODO : correct this
1405 // string fsAddr;
1406 // CWelcomeServiceClientProxy wsc(sender);
1407 // wsc.welcomeUserResult(this, userId, ret.empty(), fsAddr);
1408 // }
1409 // }
1411 // // ask the welcome service to disconnect a user
1412 // virtual void disconnectUser(NLNET::IModuleProxy *sender, uint32 userId)
1413 // {
1414 // nlstop;
1415 // }
1417 //};
1419 namespace WS
1422 void CWelcomeServiceMod::onModuleUp(IModuleProxy *proxy)
1424 if (proxy->getModuleClassName() == "RingSessionManager")
1426 if (_RingSessionManager != NULL)
1428 nlwarning("WelcomeServiceMod::onModuleUp : receiving module up for RingSessionManager '%s', but already have it as '%s', replacing it",
1429 proxy->getModuleName().c_str(),
1430 _RingSessionManager->getModuleName().c_str());
1432 // store this module as the ring session manager
1433 _RingSessionManager = proxy;
1435 // say hello to our new friend (transmit fixed session id if set in config file)
1436 nlinfo("Registering welcome service module into session manager '%s'", proxy->getModuleName().c_str());
1437 uint32 sessionId = 0;
1438 CConfigFile::CVar *varFixedSessionId = IService::getInstance()->ConfigFile.getVarPtr( "FixedSessionId" );
1439 if ( varFixedSessionId )
1440 sessionId = varFixedSessionId->asInt();
1441 CWelcomeServiceClientProxy wscp(proxy);
1442 wscp.registerWS(this, IService::getInstance()->getShardId(), sessionId, OnlineServices.getOnlineStatus());
1444 // Send counts
1445 uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
1446 for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
1448 totalNbOnlineUsers += (*it).NbUser;
1449 totalNbPendingUsers += (*it).NbPendingUsers;
1451 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
1453 else if (proxy->getModuleClassName() == "LoginService")
1455 _LoginService = proxy;
1459 void CWelcomeServiceMod::onModuleDown(IModuleProxy *proxy)
1461 if (_RingSessionManager == proxy)
1463 // remove this module as the ring session manager
1464 _RingSessionManager = NULL;
1466 else if (_LoginService == proxy)
1467 _LoginService = NULL;
1471 void CWelcomeServiceMod::welcomeUser(NLNET::IModuleProxy *sender, uint32 charId, const std::string &userName, const CLoginCookie &cookie, const std::string &priviledge, const std::string &exPriviledge, WS::TUserRole mode, uint32 instanceId)
1473 nldebug( "ERLOG: welcomeUser(%u,%s,%s,%s,%s,%u,%u)", charId, userName.c_str(), cookie.toString().c_str(), priviledge.c_str(), exPriviledge.c_str(), (uint)mode.getValue(), instanceId );
1474 string ret = lsChooseShard(userName,
1475 cookie,
1476 priviledge,
1477 exPriviledge,
1478 mode,
1479 instanceId,
1480 charId & 0xF);
1482 uint32 userId = charId >> 4;
1483 if (!ret.empty())
1485 nldebug( "ERLOG: lsChooseShard returned an error => welcomeUserResult");
1486 // TODO : correct this
1487 string fsAddr;
1488 CWelcomeServiceClientProxy wsc(sender);
1489 wsc.welcomeUserResult(this, userId, false, fsAddr, ret);
1491 else
1493 nldebug( "ERLOG: lsChooseShard OK => adding to pending");
1494 TPendingFEResponseInfo pfri;
1495 pfri.WSMod = this;
1496 pfri.UserId = userId;
1497 pfri.WaiterModule = sender;
1498 PendingFeResponse.insert(make_pair(cookie, pfri));
1502 void CWelcomeServiceMod::pendingUserLost(const NLNET::CLoginCookie &cookie)
1504 if (!_LoginService)
1505 return;
1507 CLoginServiceProxy ls(_LoginService);
1509 ls.pendingUserLost(this, cookie);
1513 // register the module
1514 NLNET_REGISTER_MODULE_FACTORY(CWelcomeServiceMod, "WelcomeService");
1516 } // namespace WS
1520 // Variables
1523 NLMISC_DYNVARIABLE(uint32, OnlineUsersNumber, "number of connected users on this shard")
1525 // we can only read the value
1526 if (get)
1528 uint32 nbusers = 0;
1529 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end (); it++)
1531 nbusers += (*it).NbUser;
1533 *pointer = nbusers;
1539 // Commands
1543 NLMISC_COMMAND (frontends, "displays the list of all registered front ends", "")
1545 if(args.size() != 0) return false;
1547 log.displayNL ("Display the %d registered front end :", FESList.size());
1548 for (list<CFES>::iterator it = FESList.begin(); it != FESList.end (); it++)
1550 // log.displayNL ("> FE %u: nb estimated users: %u nb users: %u, nb pending users : %u",
1551 log.displayNL ("> FE %u: nb users: %u, nb pending users : %u",
1552 it->SId.get(),
1553 it->NbUser,
1554 it->NbPendingUsers);
1556 log.displayNL ("End ot the list");
1558 return true;
1561 NLMISC_COMMAND (users, "displays the list of all registered users", "")
1563 if(args.size() != 0) return false;
1565 log.displayNL ("Display the %d registered users :", UserIdSockAssociations.size());
1566 for (map<uint32, TServiceId>::iterator it = UserIdSockAssociations.begin(); it != UserIdSockAssociations.end (); it++)
1568 log.displayNL ("> %u SId=%u", (*it).first, (*it).second.get());
1570 log.displayNL ("End ot the list");
1572 return true;
1575 NLMISC_COMMAND( displayOnlineServices, "Display the online service instances", "" )
1577 OnlineServices.display( log );
1578 return true;
1581 NLMISC_VARIABLE( bool, OnlineStatus, "Main online status of the shard" );