1155 lines
31 KiB
C++
1155 lines
31 KiB
C++
// NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif // HAVE_CONFIG_H
|
|
|
|
#ifndef NELNS_CONFIG
|
|
#define NELNS_CONFIG ""
|
|
#endif // NELNS_CONFIG
|
|
|
|
#ifndef NELNS_LOGS
|
|
#define NELNS_LOGS ""
|
|
#endif // NELNS_LOGS
|
|
|
|
|
|
//
|
|
// Includes
|
|
//
|
|
|
|
#include "nel/misc/types_nl.h"
|
|
|
|
#include <list>
|
|
#include <string>
|
|
|
|
#include "nel/misc/debug.h"
|
|
#include "nel/misc/command.h"
|
|
#include "nel/misc/variable.h"
|
|
#include "nel/misc/displayer.h"
|
|
|
|
#include "nel/net/callback_server.h"
|
|
#include "nel/net/service.h"
|
|
#include "nel/net/module_manager.h"
|
|
|
|
//
|
|
// Namespaces
|
|
//
|
|
|
|
using namespace std;
|
|
|
|
using namespace NLMISC;
|
|
using namespace NLNET;
|
|
|
|
|
|
NLMISC_COMMAND(test, "none", "none")
|
|
{
|
|
log.displayNL("Raw cmd line : '%s'", rawCommandString.c_str());
|
|
log.displayNL("Dumping %u parameters :", args.size());
|
|
for (uint i=0; i<args.size(); ++i)
|
|
{
|
|
log.displayNL(" %u : '%s'", i, args[i].c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// Structures
|
|
//
|
|
|
|
struct CServiceEntry
|
|
{
|
|
CServiceEntry (TSockId sock, const vector<CInetAddress> &a, const string &n, TServiceId s) : SockId(sock), Addr(a), Name(n), SId (s), WaitingUnregistration(false) { }
|
|
|
|
TSockId SockId; // the connection between the service and the naming service
|
|
vector<CInetAddress> Addr; // address to send to the service who wants to lookup this service
|
|
// it s possible to have more than one addr, anyway, the naming service
|
|
// will send good address depending of the sub net address of the service
|
|
string Name; // name of the service
|
|
TServiceId SId; // id of the service
|
|
|
|
bool WaitingUnregistration; // true if this service is in unregistration process (wait other service ACK)
|
|
TTime WaitingUnregistrationTime; // time of the beginning of the inregistration process
|
|
list<TServiceId> WaitingUnregistrationServices; // list of service that we wait the answer
|
|
};
|
|
|
|
|
|
|
|
// Helper that emulates layer5's send()
|
|
//void sendToService( uint16 sid, CMessage& msgout );
|
|
|
|
// Helper that emulate layer5's getServiceName()
|
|
string getServiceName( TServiceId sid );
|
|
|
|
// Helper that returns the first address of a service
|
|
CInetAddress getHostAddress( TServiceId sid );
|
|
|
|
// Asks a service to stop and tell every one
|
|
void doUnregisterService (TServiceId sid);
|
|
|
|
|
|
/**
|
|
* Manager for services instances
|
|
* (Moved from the TICKS to the NS)
|
|
* Implementable with layer 5, here implemented in NS (layer 3)
|
|
* \author Olivier Cado
|
|
* \author Nevrax France
|
|
* \date 2003
|
|
*/
|
|
class CServiceInstanceManager
|
|
{
|
|
public:
|
|
|
|
/// Constructor
|
|
CServiceInstanceManager();
|
|
|
|
/** Add the name of a service which must not be duplicated
|
|
* If uniqueOnShard is true, only one service is allowed.
|
|
* If uniqueOnShard is false, one service is allowed by physical machine.
|
|
*/
|
|
void addUniqueService( const std::string& serviceName, bool uniqueOnShard )
|
|
{
|
|
_UniqueServices.insert( std::make_pair( serviceName, uniqueOnShard ) );
|
|
}
|
|
|
|
/// Check if a service is allowed to start (if so, add it)
|
|
bool queryStartService( const std::string& serviceName, TServiceId serviceId, const std::vector<NLNET::CInetAddress> &addr, string& reason );
|
|
|
|
/// Release a service instance
|
|
void releaseService( NLNET::TServiceId serviceId );
|
|
|
|
/// Display information
|
|
void displayInfo( NLMISC::CLog *log = NLMISC::InfoLog ) const;
|
|
|
|
/// Make all controlled services quit
|
|
void killAllServices();
|
|
|
|
private:
|
|
|
|
/// List of restricted services
|
|
std::map< std::string, bool > _UniqueServices;
|
|
|
|
/// List of granted (online) services
|
|
std::set< TServiceId > _OnlineServices;
|
|
};
|
|
|
|
|
|
CServiceInstanceManager *SIMInstance = NULL;
|
|
|
|
|
|
/*
|
|
* Constructor
|
|
*/
|
|
CServiceInstanceManager::CServiceInstanceManager()
|
|
{
|
|
nlassert( ! SIMInstance );
|
|
SIMInstance = this;
|
|
|
|
// Note: addCallbackArray() done in CRangeMirrorManager::init()
|
|
}
|
|
|
|
|
|
/*
|
|
* Check if a service is allowed to start. Answer with a GSTS (Grant Start Service) message
|
|
*/
|
|
bool CServiceInstanceManager::queryStartService( const std::string& serviceName, TServiceId serviceId, const vector<CInetAddress> &addr, string& reason )
|
|
{
|
|
bool grantStarting = true;
|
|
std::map< std::string, bool >::iterator ius = _UniqueServices.find( serviceName );
|
|
if ( ius != _UniqueServices.end() )
|
|
{
|
|
// Service is restricted
|
|
set< TServiceId >::iterator ios;
|
|
bool uniqueOnShard = (*ius).second;
|
|
for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios )
|
|
{
|
|
string name = getServiceName( *ios );
|
|
if ( name == serviceName )
|
|
{
|
|
if ( uniqueOnShard )
|
|
{
|
|
// Only one service by shard is allowed => deny
|
|
grantStarting = false;
|
|
reason = toString( "Service %s already found as %hu, must be unique on shard", serviceName.c_str(), ios->get() );
|
|
nlinfo( reason.c_str() );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Only one service by physical machine is allowed
|
|
|
|
// Implementation for layer5
|
|
//TSockId hostid1, hostid2;
|
|
/*CCallbackNetBase *cnb1 = CUnifiedNetwork::getInstance()->getNetBase( serviceId, hostid1 );
|
|
CCallbackNetBase *cnb2 = CUnifiedNetwork::getInstance()->getNetBase( *ios, hostid2 );
|
|
if ( cnb1->hostAddress( hostid1 ).internalIPAddress() == cnb2->hostAddress( hostid2 ).internalIPAddress() )*/
|
|
|
|
// Implementation for NS
|
|
if ( addr[0].internalIPAddress() == getHostAddress( *ios ).internalIPAddress() )
|
|
{
|
|
grantStarting = false;
|
|
reason = toString( "Service %s already found as %hu on same machine", serviceName.c_str(), ios->get() );
|
|
nlinfo( reason.c_str() );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( grantStarting )
|
|
{
|
|
_OnlineServices.insert( serviceId );
|
|
}
|
|
return grantStarting;
|
|
}
|
|
|
|
|
|
/*
|
|
* Release a service instance
|
|
*/
|
|
void CServiceInstanceManager::releaseService( NLNET::TServiceId serviceId )
|
|
{
|
|
_OnlineServices.erase( serviceId ); // not a problem if not found
|
|
}
|
|
|
|
|
|
/*
|
|
* Display information
|
|
*/
|
|
void CServiceInstanceManager::displayInfo( NLMISC::CLog *log ) const
|
|
{
|
|
log->displayNL( "Restricted services:" );
|
|
std::map< std::string, bool >::const_iterator ius;
|
|
for ( ius=_UniqueServices.begin(); ius!=_UniqueServices.end(); ++ius )
|
|
{
|
|
log->displayNL( "%s -> only one per %s", (*ius).first.c_str(), (*ius).second?"shard":"machine" );
|
|
}
|
|
log->displayNL( "Online registered services:" );
|
|
std::set< TServiceId >::const_iterator ios;
|
|
for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios )
|
|
{
|
|
log->displayNL( "%s", CUnifiedNetwork::getInstance()->getServiceUnifiedName( *ios ).c_str() );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Make all controlled services quit
|
|
*/
|
|
void CServiceInstanceManager::killAllServices()
|
|
{
|
|
// Send to all known online services
|
|
std::set< TServiceId >::const_iterator ios;
|
|
for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios )
|
|
{
|
|
doUnregisterService( (TServiceId)(*ios) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Variables
|
|
//
|
|
|
|
list<CServiceEntry> RegisteredServices; /// List of all registred services
|
|
|
|
uint16 MinBasePort = 51000; /// Ports begin at 51000
|
|
uint16 MaxBasePort = 52000; /// (note: in this implementation there can be no more than 1000 services)
|
|
|
|
const TServiceId BaseSId(128); /// Allocated SIds begin at 128 (except for Agent Service)
|
|
|
|
const TTime UnregisterTimeout = 10000; /// After 10s we remove an unregister service if every server didn't ACK the message
|
|
|
|
CCallbackServer *CallbackServer = NULL;
|
|
|
|
//
|
|
// Functions
|
|
//
|
|
|
|
bool canAccess (const vector<CInetAddress> &addr, const CServiceEntry &entry, vector<CInetAddress> &accessibleAddr)
|
|
{
|
|
accessibleAddr.clear ();
|
|
|
|
if (entry.WaitingUnregistration)
|
|
return false;
|
|
|
|
for (uint i = 0; i < addr.size(); i++)
|
|
{
|
|
uint32 net = addr[i].internalNetAddress();
|
|
for (uint j = 0; j < entry.Addr.size(); j++)
|
|
{
|
|
if (net == entry.Addr[j].internalNetAddress())
|
|
{
|
|
accessibleAddr.push_back (entry.Addr[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (accessibleAddr.empty())
|
|
{
|
|
nldebug ("service %s-%hu is not accessible by '%s'", entry.Name.c_str(), entry.SId.get(), vectorCInetAddressToString (addr).c_str ());
|
|
}
|
|
else
|
|
{
|
|
nldebug ("service %s-%hu is accessible by '%s'", entry.Name.c_str(), entry.SId.get(), vectorCInetAddressToString (accessibleAddr).c_str ());
|
|
}
|
|
|
|
return !accessibleAddr.empty ();
|
|
}
|
|
|
|
void displayRegisteredServices (CLog *log = InfoLog)
|
|
{
|
|
log->displayNL ("Display the %d registered services :", RegisteredServices.size());
|
|
for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
TSockId id = (*it).SockId;
|
|
if (id == NULL)
|
|
{
|
|
log->displayNL ("> %s-%hu %s '%s' %s %d addr", (*it).Name.c_str(), it->SId.get(), "<NULL>", "<NULL>", (*it).WaitingUnregistration?"WaitUnreg":"", (*it).Addr.size());
|
|
for(uint i = 0; i < (*it).Addr.size(); i++)
|
|
log->displayNL (" '%s'", (*it).Addr[i].asString().c_str());
|
|
}
|
|
else
|
|
{
|
|
log->displayNL ("> %s-%hu %s '%s' %s %d addr", (*it).Name.c_str(), it->SId.get(), (*it).SockId->asString().c_str(), CallbackServer->hostAddress((*it).SockId).asString().c_str(), (*it).WaitingUnregistration?"WaitUnreg":"", (*it).Addr.size());
|
|
for(uint i = 0; i < (*it).Addr.size(); i++)
|
|
log->displayNL (" '%s'", (*it).Addr[i].asString().c_str());
|
|
}
|
|
}
|
|
log->displayNL ("End of the list");
|
|
}
|
|
|
|
|
|
list<CServiceEntry>::iterator effectivelyRemove (list<CServiceEntry>::iterator &it)
|
|
{
|
|
// remove the service from the registered service list
|
|
nlinfo ("Effectively remove the service %s-%hu", (*it).Name.c_str(), it->SId.get());
|
|
return RegisteredServices.erase (it);
|
|
}
|
|
|
|
/*
|
|
* Helper procedure for cbLookupAlternate and cbUnregister.
|
|
* Note: name is used for a LOGS.
|
|
*/
|
|
list<CServiceEntry>::iterator doRemove (list<CServiceEntry>::iterator it)
|
|
{
|
|
nldebug ("Unregister the service %s-%hu '%s'", (*it).Name.c_str(), it->SId.get(), (*it).Addr[0].asString().c_str());
|
|
|
|
// tell to everybody that this service is unregistered
|
|
|
|
CMessage msgout ("UNB");
|
|
msgout.serial ((*it).Name);
|
|
msgout.serial ((*it).SId);
|
|
|
|
vector<CInetAddress> accessibleAddress;
|
|
nlinfo ("Broadcast the Unregistration of %s-%hu to all registered services", (*it).Name.c_str(), it->SId.get());
|
|
for (list<CServiceEntry>::iterator it3 = RegisteredServices.begin(); it3 != RegisteredServices.end (); it3++)
|
|
{
|
|
if (canAccess((*it).Addr, (*it3), accessibleAddress))
|
|
{
|
|
CallbackServer->send (msgout, (*it3).SockId);
|
|
//CNetManager::send ("NS", msgout, (*it3).SockId);
|
|
nldebug ("Broadcast to %s-%hu", (*it3).Name.c_str(), it3->SId.get());
|
|
}
|
|
}
|
|
|
|
// new system, after the unregistation broadcast, we wait ACK from all services before really remove
|
|
// the service, before, we tag the service as 'wait before unregister'
|
|
// if everybody didn't answer before the time out, we remove it
|
|
|
|
(*it).SockId = NULL;
|
|
|
|
(*it).WaitingUnregistration = true;
|
|
(*it).WaitingUnregistrationTime = CTime::getLocalTime();
|
|
|
|
// we remove all services awaiting his ACK because this service is down so it'll never ACK
|
|
for (list<CServiceEntry>::iterator itr = RegisteredServices.begin(); itr != RegisteredServices.end (); itr++)
|
|
{
|
|
for (list<TServiceId>::iterator itw = (*itr).WaitingUnregistrationServices.begin(); itw != (*itr).WaitingUnregistrationServices.end ();)
|
|
{
|
|
if ((*itw) == (*it).SId)
|
|
{
|
|
itw = (*itr).WaitingUnregistrationServices.erase (itw);
|
|
}
|
|
else
|
|
{
|
|
itw++;
|
|
}
|
|
}
|
|
}
|
|
|
|
string res;
|
|
for (list<CServiceEntry>::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++)
|
|
{
|
|
if (!(*it2).WaitingUnregistration)
|
|
{
|
|
(*it).WaitingUnregistrationServices.push_back ((*it2).SId);
|
|
res += toString((*it2).SId.get()) + " ";
|
|
}
|
|
}
|
|
|
|
nlinfo ("Before removing the service %s-%hu, we wait the ACK of '%s'", (*it).Name.c_str(), (*it).SId.get(), res.c_str());
|
|
|
|
if ((*it).WaitingUnregistrationServices.empty())
|
|
{
|
|
return effectivelyRemove (it);
|
|
}
|
|
else
|
|
{
|
|
return ++it;
|
|
}
|
|
|
|
// Release from the service instance manager
|
|
SIMInstance->releaseService( (*it).SId );
|
|
}
|
|
|
|
void doUnregisterService (TServiceId sid)
|
|
{
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).SId == sid)
|
|
{
|
|
// found it, remove it
|
|
doRemove (it);
|
|
return;
|
|
}
|
|
}
|
|
nlwarning ("Service %hu not found", sid.get());
|
|
}
|
|
|
|
void doUnregisterService (TSockId from)
|
|
{
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end ();)
|
|
{
|
|
if ((*it).SockId == from)
|
|
{
|
|
// it's possible that one "from" have more than one registred service, so we have to find in all the list
|
|
// found it, remove it
|
|
it = doRemove (it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*void doUnregisterService (const CInetAddress &addr)
|
|
{
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).Addr == addr)
|
|
{
|
|
// found it, remove it
|
|
doRemove (it);
|
|
return;
|
|
}
|
|
}
|
|
nlwarning ("Service %s not found", addr.asString().c_str());
|
|
}*/
|
|
|
|
/*
|
|
* Helper function for cbRegister.
|
|
* If alloc_sid is true, sid is ignored
|
|
* Returns false in case of failure of sid allocation or bad sid provided
|
|
* Note: the reply is included in this function, because it must be done before things such as syncUniTime()
|
|
*/
|
|
bool doRegister (const string &name, const vector<CInetAddress> &addr, TServiceId sid, TSockId from, CCallbackNetBase &netbase, bool reconnection = false)
|
|
{
|
|
// Find if the service is not already registered
|
|
string reason;
|
|
uint8 ok = true;
|
|
bool needRegister = true;
|
|
/*for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).Addr.asIPString() == addr.asIPString() )
|
|
{
|
|
// we already have a service on this address, remplace it if it's the same name
|
|
if ((*it).Name == name)
|
|
{
|
|
// it's the same service, replace it
|
|
(*it).SockId = from;
|
|
sid = (*it).SId;
|
|
nlinfo ("Replace the service %s", name.c_str());
|
|
}
|
|
else
|
|
{
|
|
nlwarning ("Try to register %s to %s but the service %s already on this address. ignore it!", name.c_str(), addr.asIPString().c_str(), (*it).Name.c_str());
|
|
ok = false;
|
|
}
|
|
needRegister = false;
|
|
break;
|
|
}
|
|
}*/
|
|
|
|
if (needRegister)
|
|
{
|
|
if (sid.get() == 0)
|
|
{
|
|
// we have to find a sid
|
|
sid = BaseSId;
|
|
bool found = false;
|
|
while (!found)
|
|
{
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).SId == sid)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (it == RegisteredServices.end ())
|
|
{
|
|
// ok, we have an empty sid
|
|
found = true;
|
|
}
|
|
else
|
|
{
|
|
sid.set(sid.get()+1);
|
|
if (sid.get() == 0) // round the clock
|
|
{
|
|
nlwarning ("Service identifier allocation overflow");
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// we have to check that the user provided sid is available
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).SId == sid)
|
|
{
|
|
nlwarning ("Sid %d already used by another service", sid.get());
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
if (it != RegisteredServices.end ())
|
|
{
|
|
ok = true;
|
|
}
|
|
}
|
|
|
|
// if ok, register the service and send a broadcast to other people
|
|
if (ok)
|
|
{
|
|
// Check if the instance is allowed to start, according to the restriction in the config file
|
|
if ( SIMInstance->queryStartService( name, sid, addr, reason ) )
|
|
{
|
|
// add him in the registered list
|
|
RegisteredServices.push_back (CServiceEntry(from, addr, name, sid));
|
|
|
|
// tell to everybody but not him that this service is registered
|
|
if (!reconnection)
|
|
{
|
|
CMessage msgout ("RGB");
|
|
TServiceId::size_type s = 1;
|
|
msgout.serial (s);
|
|
msgout.serial (const_cast<string &>(name));
|
|
msgout.serial (sid);
|
|
// we need to send all addr to all services even if the service can't access because we use the address index
|
|
// to know which connection comes.
|
|
msgout.serialCont (const_cast<vector<CInetAddress> &>(addr));
|
|
nlinfo ("The service is %s-%d, broadcast the Registration to everybody", name.c_str(), sid.get());
|
|
|
|
vector<CInetAddress> accessibleAddress;
|
|
for (list<CServiceEntry>::iterator it3 = RegisteredServices.begin(); it3 != RegisteredServices.end (); it3++)
|
|
{
|
|
// send only services that can be accessed and not itself
|
|
if ((*it3).SId != sid && canAccess(addr, (*it3), accessibleAddress))
|
|
{
|
|
CallbackServer->send (msgout, (*it3).SockId);
|
|
//CNetManager::send ("NS", msgout, (*it3).SockId);
|
|
nldebug ("Broadcast to %s-%hu", (*it3).Name.c_str(), it3->SId.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
// set the sid only if it s ok
|
|
from->setAppId (sid.get());
|
|
}
|
|
else
|
|
{
|
|
// Reply "startup denied", and do not send registration to other services
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
// send the message to the service to say if it s ok or not
|
|
if (!reconnection)
|
|
{
|
|
// send the answer to the client
|
|
CMessage msgout ("RG");
|
|
msgout.serial (ok);
|
|
if (ok)
|
|
{
|
|
msgout.serial (sid);
|
|
|
|
// send him all services available (also itself)
|
|
TServiceId::size_type nb = 0;
|
|
|
|
vector<CInetAddress> accessibleAddress;
|
|
|
|
for (list<CServiceEntry>::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++)
|
|
{
|
|
// send only services that are available
|
|
if (canAccess(addr, (*it2), accessibleAddress))
|
|
nb++;
|
|
}
|
|
msgout.serial (nb);
|
|
|
|
for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
// send only services that are available
|
|
if (canAccess(addr, (*it), accessibleAddress))
|
|
{
|
|
msgout.serial ((*it).Name);
|
|
msgout.serial ((*it).SId);
|
|
msgout.serialCont ((*it).Addr);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
msgout.serial( reason );
|
|
}
|
|
|
|
netbase.send (msgout, from);
|
|
netbase.flush (from);
|
|
}
|
|
}
|
|
|
|
//displayRegisteredServices ();
|
|
|
|
return ok!=0;
|
|
}
|
|
|
|
void checkWaitingUnregistrationServices ()
|
|
{
|
|
for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end ();)
|
|
{
|
|
if ((*it).WaitingUnregistration && ((*it).WaitingUnregistrationServices.empty() || CTime::getLocalTime() > (*it).WaitingUnregistrationTime + UnregisterTimeout))
|
|
{
|
|
if ((*it).WaitingUnregistrationServices.empty())
|
|
{
|
|
nlinfo ("Removing the service %s-%hu because all services ACKd the removal", (*it).Name.c_str(), (*it).SId.get());
|
|
}
|
|
else
|
|
{
|
|
string res;
|
|
for (list<TServiceId>::iterator it2 = (*it).WaitingUnregistrationServices.begin(); it2 != (*it).WaitingUnregistrationServices.end (); it2++)
|
|
{
|
|
res += toString(it2->get()) + " ";
|
|
}
|
|
nlwarning ("Removing the service %s-%hu because time out occurs (service numbers %s didn't ACK)", (*it).Name.c_str(), (*it).SId.get(), res.c_str());
|
|
}
|
|
it = effectivelyRemove (it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for service unregistration ACK. Mean that a service was ACK the unregistration broadcast
|
|
*/
|
|
static void cbACKUnregistration (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
TServiceId sid;
|
|
msgin.serial (sid);
|
|
|
|
for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).SId == sid && (*it).WaitingUnregistration)
|
|
{
|
|
for (list<TServiceId>::iterator it2 = (*it).WaitingUnregistrationServices.begin(); it2 != (*it).WaitingUnregistrationServices.end (); it2++)
|
|
{
|
|
if (*it2 == TServiceId(uint16(from->appId())))
|
|
{
|
|
// remove the acked service
|
|
(*it).WaitingUnregistrationServices.erase (it2);
|
|
checkWaitingUnregistrationServices ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for service registration when the naming service goes down and up (don't need to broadcast)
|
|
*/
|
|
static void cbResendRegisteration (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
string name;
|
|
vector<CInetAddress> addr;
|
|
TServiceId sid;
|
|
msgin.serial (name);
|
|
msgin.serialCont (addr);
|
|
msgin.serial (sid);
|
|
|
|
doRegister (name, addr, sid, from, netbase, true);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Callback for service registration.
|
|
*
|
|
* Message expected : RG
|
|
* - Name of service to register (string)
|
|
* - Address of service (CInetAddress)
|
|
*
|
|
* Message emitted : RG
|
|
* - Allocated service identifier (TServiceId) or 0 if failed
|
|
*/
|
|
static void cbRegister (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
string name;
|
|
vector<CInetAddress> addr;
|
|
TServiceId sid;
|
|
msgin.serial (name);
|
|
msgin.serialCont (addr);
|
|
msgin.serial (sid);
|
|
|
|
doRegister (name, addr, sid, from, netbase);
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for service unregistration.
|
|
*
|
|
* Message expected : UNI
|
|
* - Service identifier (TServiceId)
|
|
*/
|
|
static void cbUnregisterSId (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
TServiceId sid;
|
|
msgin.serial( sid );
|
|
|
|
doUnregisterService (sid);
|
|
//displayRegisteredServices ();
|
|
}
|
|
|
|
|
|
/*
|
|
* Helper function for cbQueryPort
|
|
*
|
|
* \warning QueryPort + Registration is not atomic so more than one service could ask a port before register
|
|
*/
|
|
uint16 doAllocatePort (const CInetAddress &addr)
|
|
{
|
|
static uint16 nextAvailablePort = MinBasePort;
|
|
|
|
// check if nextavailableport is free
|
|
|
|
if (nextAvailablePort >= MaxBasePort) nextAvailablePort = MinBasePort;
|
|
|
|
bool ok;
|
|
do
|
|
{
|
|
ok = true;
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).Addr[0].port () == nextAvailablePort)
|
|
{
|
|
nextAvailablePort++;
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (!ok);
|
|
|
|
return nextAvailablePort++;
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for port allocation
|
|
* Note: if a service queries a port but does not register itself to the naming service, the
|
|
* port will remain allocated and unused.
|
|
*
|
|
* Message expected : QP
|
|
* - Name of service to register (string)
|
|
* - Address of service (CInetAddress) (its port can be 0)
|
|
*
|
|
* Message emitted : QP
|
|
* - Allocated port number (uint16)
|
|
*/
|
|
static void cbQueryPort (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
// Allocate port
|
|
uint16 port = doAllocatePort (netbase.hostAddress (from));
|
|
|
|
// Send port back
|
|
CMessage msgout ("QP");
|
|
msgout.serial (port);
|
|
netbase.send (msgout, from);
|
|
|
|
nlinfo ("The service got port %hu", port);
|
|
}
|
|
|
|
|
|
/*
|
|
* Unregisters a service if it has not been done before.
|
|
* Note: this callback is called whenever someone disconnects from the NS.
|
|
* May be there are too many calls if many clients perform many transactional lookups.
|
|
*/
|
|
static void cbDisconnect /*(const string &serviceName, TSockId from, void *arg)*/ ( TSockId from, void *arg )
|
|
{
|
|
doUnregisterService (from);
|
|
//displayRegisteredServices ();
|
|
}
|
|
|
|
/*
|
|
* a service is connected, send him all services infos
|
|
*/
|
|
static void cbConnect /*(const string &serviceName, TSockId from, void *arg)*/ ( TSockId from, void *arg )
|
|
{
|
|
// we have to wait the registred services message to send all services because it this points, we can't know which sub net
|
|
// the service can use
|
|
|
|
//displayRegisteredServices ();
|
|
|
|
// set the appid with a bad id (-1)
|
|
from->setAppId (~0);
|
|
}
|
|
|
|
/*// returns the list of accessible services with a list of address
|
|
static void cbRegisteredServices(CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
vector<CInetAddress> addr;
|
|
msgin.serialCont (addr);
|
|
|
|
nlinfo ("New service ask me the available services, sending him all services available");
|
|
// send to the new service the list of all services that this service can access (depending of his sub net)
|
|
|
|
CMessage msgout ("RGB");
|
|
|
|
uint8 nb = 0;
|
|
|
|
vector<CInetAddress> accessibleAddress;
|
|
|
|
for (list<CServiceEntry>::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++)
|
|
{
|
|
// send only services that are available
|
|
if (canAccess(addr, (*it2), accessibleAddress))
|
|
nb++;
|
|
}
|
|
|
|
msgout.serial (nb);
|
|
|
|
for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
// send only services that are available
|
|
if (canAccess(addr, (*it), accessibleAddress))
|
|
{
|
|
msgout.serial ((*it).Name);
|
|
msgout.serial ((*it).SId);
|
|
msgout.serialCont (accessibleAddress);
|
|
}
|
|
}
|
|
|
|
CNetManager::send ("NS", msgout, from);
|
|
}*/
|
|
|
|
|
|
/*
|
|
* Helper that emulates layer5 send()
|
|
*/
|
|
/*void sendToService( uint16 sid, CMessage& msgout )
|
|
{
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).SId == sid)
|
|
{
|
|
CallbackServer->send (msgout, (*it).SockId);
|
|
}
|
|
}
|
|
}*/
|
|
|
|
|
|
/*
|
|
* Helper that emulate layer5's getServiceName()
|
|
*/
|
|
string getServiceName( TServiceId sid )
|
|
{
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).SId == sid)
|
|
{
|
|
return (*it).Name;
|
|
}
|
|
}
|
|
return ""; // not found
|
|
}
|
|
|
|
|
|
/*
|
|
* Helper that returns the first address of a service
|
|
*/
|
|
CInetAddress getHostAddress( TServiceId sid )
|
|
{
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).SId == sid)
|
|
{
|
|
return (*it).Addr[0];
|
|
}
|
|
}
|
|
return CInetAddress();
|
|
}
|
|
|
|
|
|
//
|
|
// Callback array
|
|
//
|
|
|
|
TCallbackItem CallbackArray[] =
|
|
{
|
|
{ "RG", cbRegister },
|
|
{ "RRG", cbResendRegisteration },
|
|
{ "QP", cbQueryPort },
|
|
{ "UNI", cbUnregisterSId },
|
|
{ "ACK_UNI", cbACKUnregistration },
|
|
// { "RS", cbRegisteredServices },
|
|
};
|
|
|
|
|
|
//
|
|
// Service
|
|
//
|
|
|
|
class CNamingService : public NLNET::IService
|
|
{
|
|
public:
|
|
|
|
/**
|
|
* Init
|
|
*/
|
|
void init()
|
|
{
|
|
// if a baseport is available in the config file, get it
|
|
CConfigFile::CVar *var;
|
|
if ((var = ConfigFile.getVarPtr ("BasePort")) != NULL)
|
|
{
|
|
uint16 newBasePort = var->asInt ();
|
|
nlinfo ("Changing the MinBasePort number from %hu to %hu", MinBasePort, newBasePort);
|
|
sint32 delta = MaxBasePort - MinBasePort;
|
|
nlassert (delta > 0);
|
|
MinBasePort = newBasePort;
|
|
MaxBasePort = MinBasePort + uint16 (delta);
|
|
}
|
|
|
|
// Parameters for the service instance manager
|
|
try
|
|
{
|
|
CConfigFile::CVar& uniqueServices = ConfigFile.getVar("UniqueOnShardServices");
|
|
for ( uint i=0; i!=uniqueServices.size(); ++i )
|
|
{
|
|
_ServiceInstances.addUniqueService( uniqueServices.asString(i), true );
|
|
}
|
|
}
|
|
catch(Exception &)
|
|
{}
|
|
try
|
|
{
|
|
CConfigFile::CVar& uniqueServicesM = ConfigFile.getVar("UniqueByMachineServices");
|
|
for ( uint i=0; i!=uniqueServicesM.size(); ++i )
|
|
{
|
|
_ServiceInstances.addUniqueService( uniqueServicesM.asString(i), false );
|
|
}
|
|
}
|
|
catch(Exception &)
|
|
{}
|
|
|
|
/*
|
|
// we don't try to associate message from client
|
|
CNetManager::getNetBase ("NS")->ignoreAllUnknownId (true);
|
|
|
|
// add the callback in case of disconnection
|
|
CNetManager::setConnectionCallback ("NS", cbConnect, NULL);
|
|
|
|
// add the callback in case of disconnection
|
|
CNetManager::setDisconnectionCallback ("NS", cbDisconnect, NULL);
|
|
*/
|
|
// DEBUG
|
|
// DebugLog->addDisplayer( new CStdDisplayer() );
|
|
|
|
vector<CInetAddress> v = CInetAddress::localAddresses();
|
|
nlinfo ("%d detected local addresses:", v.size());
|
|
for (uint i = 0; i < v.size(); i++)
|
|
{
|
|
nlinfo (" %d - '%s'",i, v[i].asString().c_str());
|
|
}
|
|
|
|
uint16 nsport = 50000;
|
|
if ((var = ConfigFile.getVarPtr ("NSPort")) != NULL)
|
|
{
|
|
nsport = var->asInt ();
|
|
}
|
|
|
|
CallbackServer = new CCallbackServer;
|
|
CallbackServer->init(nsport);
|
|
CallbackServer->addCallbackArray(CallbackArray, sizeof(CallbackArray)/sizeof(CallbackArray[0]));
|
|
CallbackServer->setConnectionCallback(cbConnect, NULL);
|
|
CallbackServer->setDisconnectionCallback(cbDisconnect, NULL);
|
|
}
|
|
|
|
/**
|
|
* Update
|
|
*/
|
|
bool update ()
|
|
{
|
|
checkWaitingUnregistrationServices ();
|
|
|
|
CallbackServer->update ();
|
|
|
|
return true;
|
|
}
|
|
|
|
void release()
|
|
{
|
|
if (CallbackServer != NULL)
|
|
delete CallbackServer;
|
|
CallbackServer = NULL;
|
|
}
|
|
|
|
private:
|
|
|
|
/// Service instance manager singleton
|
|
CServiceInstanceManager _ServiceInstances;
|
|
};
|
|
|
|
|
|
static const char* getCompleteServiceName(const IService* theService)
|
|
{
|
|
static std::string s;
|
|
s= "naming_service";
|
|
|
|
if (theService->haveLongArg("nsname"))
|
|
{
|
|
s+= "_"+theService->getLongArg("nsname");
|
|
}
|
|
|
|
if (theService->haveLongArg("fullnsname"))
|
|
{
|
|
s= theService->getLongArg("fullnsname");
|
|
}
|
|
|
|
return s.c_str();
|
|
}
|
|
|
|
static const char* getShortServiceName(const IService* theService)
|
|
{
|
|
static std::string s;
|
|
s= "NS";
|
|
|
|
if (theService->haveLongArg("shortnsname"))
|
|
{
|
|
s= theService->getLongArg("shortnsname");
|
|
}
|
|
|
|
return s.c_str();
|
|
}
|
|
//
|
|
/// Naming Service
|
|
//
|
|
NLNET_SERVICE_MAIN( CNamingService, getShortServiceName(scn), getCompleteServiceName(scn), 0, EmptyCallbackArray, NELNS_CONFIG, NELNS_LOGS)
|
|
|
|
|
|
//
|
|
// Commands
|
|
//
|
|
|
|
|
|
NLMISC_COMMAND (nsServices, "displays the list of all registered services", "")
|
|
{
|
|
if(args.size() != 0) return false;
|
|
|
|
displayRegisteredServices (&log);
|
|
|
|
return true;
|
|
}
|
|
|
|
NLMISC_COMMAND (kill, "kill a service and send an unregister broadcast to other service", "<ServiceShortName>|<ServiceId>")
|
|
{
|
|
if(args.size() != 1) return false;
|
|
|
|
// try with number
|
|
|
|
TServiceId sid(atoi(args[0].c_str()));
|
|
|
|
if(sid.get() == 0)
|
|
{
|
|
// not a number, try a name
|
|
list<CServiceEntry>::iterator it;
|
|
for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
|
|
{
|
|
if ((*it).Name == args[0])
|
|
{
|
|
sid = (*it).SId;
|
|
break;
|
|
}
|
|
}
|
|
if (it == RegisteredServices.end())
|
|
{
|
|
log.displayNL ("Bad service name or id '%s'", args[0].c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
doUnregisterService (sid);
|
|
return true;
|
|
}
|
|
|
|
NLMISC_DYNVARIABLE(uint32, NbRegisteredServices, "display the number of service that are registered in naming service")
|
|
{
|
|
if (get) *pointer = RegisteredServices.size();
|
|
}
|
|
|
|
NLMISC_COMMAND( displayServiceInstances, "SIM: Display info on service instances", "" )
|
|
{
|
|
SIMInstance->displayInfo( &log );
|
|
return true;
|
|
}
|
|
|
|
NLMISC_COMMAND( killAllServices, "SIM: Make all the controlled services quit", "" )
|
|
{
|
|
SIMInstance->killAllServices();
|
|
return true;
|
|
}
|