2021-01-05 12:49:24 +01:00
/* Copyright (C) 2021 Wildfire Games.
2009-04-18 19:00:33 +02:00
* This file is part of 0 A . D .
*
* 0 A . D . is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . 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 General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
2009-04-11 19:00:39 +02:00
# include "precompiled.h"
2010-06-08 00:19:05 +02:00
# include "NetServer.h"
2010-06-30 23:41:04 +02:00
# include "NetClient.h"
# include "NetMessage.h"
2009-04-11 19:00:39 +02:00
# include "NetSession.h"
2017-01-24 03:04:50 +01:00
# include "NetServerTurnManager.h"
2010-07-03 15:15:01 +02:00
# include "NetStats.h"
2010-06-08 00:19:05 +02:00
2016-05-08 18:25:00 +02:00
# include "lib/external_libraries/enet.h"
2018-08-25 16:34:30 +02:00
# include "lib/types.h"
STUN + XMPP ICE implementation.
Allows lobby players to host games without having to configure their
router.
Differential Revision: https://code.wildfiregames.com/D364
Fixes #2305
Patch By: fcxSanya.
StunClient based on code by SuperTuxKart, relicensed with approval of
the according authors hilnius, hiker, Auria, deveee, Flakebi, leper,
konstin and KroArtem.
Added rfc5245 (ejabberd) support, a GUI option, refactoring and segfault
fixes by myself.
Tested By: user1, Sandarac, Sestroretsk1714, Vladislav, Grugnas,
javiergodas
Partially Reviewed By: leper, Philip, echotangoecho
This was SVN commit r19703.
2017-06-01 08:33:52 +02:00
# include "network/StunClient.h"
2010-06-08 00:19:05 +02:00
# include "ps/CLogger.h"
2014-11-18 00:29:49 +01:00
# include "ps/ConfigDB.h"
2017-10-25 00:05:24 +02:00
# include "ps/GUID.h"
2021-05-18 16:47:36 +02:00
# include "ps/Hashing.h"
2016-06-21 12:33:11 +02:00
# include "ps/Profile.h"
2021-01-10 09:39:54 +01:00
# include "ps/Threading.h"
2020-11-14 11:57:50 +01:00
# include "scriptinterface/ScriptContext.h"
2010-06-30 23:41:04 +02:00
# include "scriptinterface/ScriptInterface.h"
2021-05-14 12:18:03 +02:00
# include "scriptinterface/JSON.h"
2010-05-20 02:59:01 +02:00
# include "simulation2/Simulation2.h"
2017-01-24 03:04:50 +01:00
# include "simulation2/system/TurnManager.h"
2009-04-11 19:00:39 +02:00
2013-12-18 17:08:56 +01:00
# if CONFIG2_MINIUPNPC
2013-12-13 03:23:02 +01:00
# include <miniupnpc/miniwget.h>
# include <miniupnpc/miniupnpc.h>
# include <miniupnpc/upnpcommands.h>
# include <miniupnpc/upnperrors.h>
2013-12-18 17:08:56 +01:00
# endif
2013-12-13 03:23:02 +01:00
2018-08-25 16:34:30 +02:00
# include <string>
2016-03-13 17:52:00 +01:00
/**
* Number of peers to allocate for the enet host .
* Limited by ENET_PROTOCOL_MAXIMUM_PEER_ID ( 4096 ) .
*
2016-09-12 00:46:00 +02:00
* At most 8 players , 32 observers and 1 temporary connection to send the " server full " disconnect - reason .
2016-03-13 17:52:00 +01:00
*/
2016-09-12 00:46:00 +02:00
# define MAX_CLIENTS 41
2016-03-13 17:52:00 +01:00
2010-06-30 23:41:04 +02:00
# define DEFAULT_SERVER_NAME L"Unnamed Server"
2009-04-11 19:00:39 +02:00
2021-01-06 16:26:11 +01:00
constexpr int CHANNEL_COUNT = 1 ;
2021-01-26 21:20:48 +01:00
constexpr int FAILED_PASSWORD_TRIES_BEFORE_BAN = 3 ;
2011-05-29 22:57:28 +02:00
2010-10-31 23:00:28 +01:00
/**
* enet_host_service timeout ( msecs ) .
* Smaller numbers may hurt performance ; larger numbers will
* hurt latency responding to messages from game thread .
*/
static const int HOST_SERVICE_TIMEOUT = 50 ;
Increase MP Command delay to 4 turns, decrease MP turns to 200ms.
To hide network latency, MP turns send commands not for the next turn
but N turns after that (introduced in c684c211a2).
Further, MP turn length was increased to 500ms compared to 200ms SP
turns (introduced in 6a15b78c98).
Unfortunately, increasing MP turn length has negative consequences:
- makes pathfinding/unit motion much worse & unit behaviour worse in
general.
- makes the game more 'lag-spikey', since computations are done less
often, but thus then can take more time.
This diff essentially reverts 6a15b78c98, instead increasing
COMMAND_DELAY from 2 to 4 in MP. This:
- Reduces the 'inherent command lag' in MP from 1000ms to 800ms
- Increases the lag range at which MP will run smoothtly from 500ms to
600ms
- makes SP and MP turns behave identically again, removing the
hindrances described above.
As a side effect, single-player was not actually using COMMAND_DELAY,
this is now done (can be used to simulate MP command lag).
Refs #3752
Differential Revision: https://code.wildfiregames.com/D3275
This was SVN commit r25001.
2021-03-03 22:02:57 +01:00
/**
* Once ping goes above turn length * command delay ,
* the game will start ' freezing ' for other clients while we catch up .
* Since commands are sent client - > server - > client , divide by 2.
* ( duplicated in NetServer . cpp to avoid having to fetch the constants in a header file )
*/
constexpr u32 NETWORK_BAD_PING = DEFAULT_TURN_LENGTH * COMMAND_DELAY_MP / 2 ;
2010-06-30 23:41:04 +02:00
CNetServer * g_NetServer = NULL ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
static CStr DebugName ( CNetServerSession * session )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
if ( session = = NULL )
return " [unknown host] " ;
if ( session - > GetGUID ( ) . empty ( ) )
return " [unauthed host] " ;
return " [ " + session - > GetGUID ( ) . substr ( 0 , 8 ) + " ...] " ;
}
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
/**
* Async task for receiving the initial game state to be forwarded to another
* client that is rejoining an in - progress network game .
*/
class CNetFileReceiveTask_ServerRejoin : public CNetFileReceiveTask
{
NONCOPYABLE ( CNetFileReceiveTask_ServerRejoin ) ;
public :
CNetFileReceiveTask_ServerRejoin ( CNetServerWorker & server , u32 hostID )
: m_Server ( server ) , m_RejoinerHostID ( hostID )
{
}
virtual void OnComplete ( )
{
// We've received the game state from an existing player - now
// we need to send it onwards to the newly rejoining player
// Find the session corresponding to the rejoining host (if any)
CNetServerSession * session = NULL ;
2016-04-24 22:48:53 +02:00
for ( CNetServerSession * serverSession : m_Server . m_Sessions )
2011-10-27 18:46:48 +02:00
{
2016-04-24 22:48:53 +02:00
if ( serverSession - > GetHostID ( ) = = m_RejoinerHostID )
2011-10-27 18:46:48 +02:00
{
2016-04-24 22:48:53 +02:00
session = serverSession ;
2011-10-27 18:46:48 +02:00
break ;
}
}
if ( ! session )
{
2015-01-22 21:31:30 +01:00
LOGMESSAGE ( " Net server: rejoining client disconnected before we sent to it " ) ;
2011-10-27 18:46:48 +02:00
return ;
}
// Store the received state file, and tell the client to start downloading it from us
// TODO: this will get kind of confused if there's multiple clients downloading in parallel;
// they'll race and get whichever happens to be the latest received by the server,
// which should still work but isn't great
m_Server . m_JoinSyncFile = m_Buffer ;
2021-03-22 11:13:27 +01:00
// Send the init attributes alongside - these should be correct since the game should be started.
2011-10-27 18:46:48 +02:00
CJoinSyncStartMessage message ;
2021-05-14 12:18:03 +02:00
message . m_InitAttributes = Script : : StringifyJSON ( ScriptRequest ( m_Server . GetScriptInterface ( ) ) , & m_Server . m_InitAttributes ) ;
2011-10-27 18:46:48 +02:00
session - > SendMessage ( & message ) ;
}
private :
CNetServerWorker & m_Server ;
u32 m_RejoinerHostID ;
} ;
2010-10-31 23:00:28 +01:00
/*
* XXX : We use some non - threadsafe functions from the worker thread .
* See http : //trac.wildfiregames.com/ticket/654
*/
2018-03-12 01:23:40 +01:00
CNetServerWorker : : CNetServerWorker ( bool useLobbyAuth , int autostartPlayers ) :
2010-10-31 23:00:28 +01:00
m_AutostartPlayers ( autostartPlayers ) ,
2018-03-12 01:23:40 +01:00
m_LobbyAuth ( useLobbyAuth ) ,
2010-10-31 23:00:28 +01:00
m_Shutdown ( false ) ,
m_ScriptInterface ( NULL ) ,
2021-02-27 18:44:59 +01:00
m_NextHostID ( 1 ) , m_Host ( NULL ) , m_ControllerGUID ( ) , m_Stats ( NULL ) ,
2016-02-04 18:14:46 +01:00
m_LastConnectionCheck ( 0 )
2010-06-30 23:41:04 +02:00
{
m_State = SERVER_STATE_UNCONNECTED ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
m_ServerTurnManager = NULL ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
m_ServerName = DEFAULT_SERVER_NAME ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
CNetServerWorker : : ~ CNetServerWorker ( )
2009-04-11 19:00:39 +02:00
{
2010-10-31 23:00:28 +01:00
if ( m_State ! = SERVER_STATE_UNCONNECTED )
{
// Tell the thread to shut down
{
2019-06-06 21:30:48 +02:00
std : : lock_guard < std : : mutex > lock ( m_WorkerMutex ) ;
2010-10-31 23:00:28 +01:00
m_Shutdown = true ;
}
// Wait for it to shut down cleanly
2019-08-15 11:07:16 +02:00
m_WorkerThread . join ( ) ;
2010-10-31 23:00:28 +01:00
}
2019-08-16 19:38:58 +02:00
# if CONFIG2_MINIUPNPC
if ( m_UPnPThread . joinable ( ) )
m_UPnPThread . detach ( ) ;
# endif
2010-10-31 23:00:28 +01:00
// Clean up resources
2010-07-03 15:15:01 +02:00
delete m_Stats ;
2016-04-24 22:48:53 +02:00
for ( CNetServerSession * session : m_Sessions )
2010-07-02 23:28:48 +02:00
{
2016-04-24 22:48:53 +02:00
session - > DisconnectNow ( NDR_SERVER_SHUTDOWN ) ;
delete session ;
2010-07-02 23:28:48 +02:00
}
if ( m_Host )
enet_host_destroy ( m_Host ) ;
2010-07-06 21:54:17 +02:00
delete m_ServerTurnManager ;
2009-04-11 19:00:39 +02:00
}
2021-01-23 19:04:36 +01:00
void CNetServerWorker : : SetPassword ( const CStr & hashedPassword )
{
m_Password = hashedPassword ;
}
2021-02-27 18:44:59 +01:00
void CNetServerWorker : : SetControllerSecret ( const std : : string & secret )
{
m_ControllerSecret = secret ;
}
2021-05-18 16:47:36 +02:00
bool CNetServerWorker : : CheckPassword ( const std : : string & password , const std : : string & salt ) const
{
return HashCryptographically ( m_Password , salt ) = = password ;
}
2016-06-13 18:56:14 +02:00
bool CNetServerWorker : : SetupConnection ( const u16 port )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( m_State = = SERVER_STATE_UNCONNECTED ) ;
ENSURE ( ! m_Host ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Bind to default host
ENetAddress addr ;
addr . host = ENET_HOST_ANY ;
2016-06-13 18:56:14 +02:00
addr . port = port ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Create ENet server
2011-05-29 22:57:28 +02:00
m_Host = enet_host_create ( & addr , MAX_CLIENTS , CHANNEL_COUNT , 0 , 0 ) ;
2010-06-30 23:41:04 +02:00
if ( ! m_Host )
2009-04-11 19:00:39 +02:00
{
2015-01-22 21:31:30 +01:00
LOGERROR ( " Net server: enet_host_create failed " ) ;
2009-04-11 19:00:39 +02:00
return false ;
}
2010-10-31 23:00:28 +01:00
m_Stats = new CNetStatsTable ( ) ;
2010-07-04 21:59:05 +02:00
if ( CProfileViewer : : IsInitialised ( ) )
g_ProfileViewer . AddRootTable ( m_Stats ) ;
2010-07-03 15:15:01 +02:00
2010-06-30 23:41:04 +02:00
m_State = SERVER_STATE_PREGAME ;
2010-10-31 23:00:28 +01:00
// Launch the worker thread
2021-01-10 09:39:54 +01:00
m_WorkerThread = std : : thread ( Threading : : HandleExceptions < RunThread > : : Wrapper , this ) ;
2010-10-31 23:00:28 +01:00
2013-12-18 17:08:56 +01:00
# if CONFIG2_MINIUPNPC
2013-12-17 18:03:49 +01:00
// Launch the UPnP thread
2021-01-10 09:39:54 +01:00
m_UPnPThread = std : : thread ( Threading : : HandleExceptions < SetupUPnP > : : Wrapper ) ;
2013-12-18 17:08:56 +01:00
# endif
2013-12-17 18:03:49 +01:00
2013-12-17 15:21:49 +01:00
return true ;
}
2013-12-13 03:23:02 +01:00
2013-12-18 17:08:56 +01:00
# if CONFIG2_MINIUPNPC
2019-08-15 11:07:16 +02:00
void CNetServerWorker : : SetupUPnP ( )
2013-12-17 15:21:49 +01:00
{
2020-04-06 22:20:27 +02:00
debug_SetThreadName ( " UPnP " ) ;
2013-12-13 03:23:02 +01:00
// Values we want to set.
2013-12-15 02:02:26 +01:00
char psPort [ 6 ] ;
sprintf_s ( psPort , ARRAY_SIZE ( psPort ) , " %d " , PS_DEFAULT_PORT ) ;
const char * leaseDuration = " 0 " ; // Indefinite/permanent lease duration.
const char * description = " 0AD Multiplayer " ;
const char * protocall = " UDP " ;
2013-12-13 03:23:02 +01:00
char internalIPAddress [ 64 ] ;
char externalIPAddress [ 40 ] ;
2019-08-16 20:07:23 +02:00
2013-12-13 03:23:02 +01:00
// Variables to hold the values that actually get set.
char intClient [ 40 ] ;
char intPort [ 6 ] ;
char duration [ 16 ] ;
2019-08-16 20:07:23 +02:00
2013-12-13 03:23:02 +01:00
// Intermediate variables.
2019-08-16 20:07:23 +02:00
bool allocatedUrls = false ;
2013-12-13 03:23:02 +01:00
struct UPNPUrls urls ;
struct IGDdatas data ;
2014-08-06 16:11:04 +02:00
struct UPNPDev * devlist = NULL ;
2013-12-17 18:03:49 +01:00
2019-08-16 20:07:23 +02:00
// Make sure everything is properly freed.
std : : function < void ( ) > freeUPnP = [ & allocatedUrls , & urls , & devlist ] ( )
{
if ( allocatedUrls )
FreeUPNPUrls ( & urls ) ;
freeUPNPDevlist ( devlist ) ;
// IGDdatas does not need to be freed according to UPNP_GetIGDFromUrl
} ;
2013-12-17 18:03:49 +01:00
// Cached root descriptor URL.
2013-12-30 00:56:18 +01:00
std : : string rootDescURL ;
2014-11-18 00:29:49 +01:00
CFG_GET_VAL ( " network.upnprootdescurl " , rootDescURL ) ;
2013-12-30 00:56:18 +01:00
if ( ! rootDescURL . empty ( ) )
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: attempting to use cached root descriptor URL: %s " , rootDescURL . c_str ( ) ) ;
2013-12-17 18:03:49 +01:00
2014-08-06 16:11:04 +02:00
int ret = 0 ;
2013-12-17 15:21:49 +01:00
2014-08-06 16:11:04 +02:00
// Try a cached URL first
if ( ! rootDescURL . empty ( ) & & UPNP_GetIGDFromUrl ( rootDescURL . c_str ( ) , & urls , & data , internalIPAddress , sizeof ( internalIPAddress ) ) )
{
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: using cached IGD = %s " , urls . controlURL ) ;
2014-08-06 16:11:04 +02:00
ret = 1 ;
}
// No cached URL, or it did not respond. Try getting a valid UPnP device for 10 seconds.
2015-09-30 23:32:54 +02:00
# if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 14
else if ( ( devlist = upnpDiscover ( 10000 , 0 , 0 , 0 , 0 , 2 , 0 ) ) ! = NULL )
# else
2014-08-06 16:11:04 +02:00
else if ( ( devlist = upnpDiscover ( 10000 , 0 , 0 , 0 , 0 , 0 ) ) ! = NULL )
2015-09-30 23:32:54 +02:00
# endif
2014-08-06 16:11:04 +02:00
{
ret = UPNP_GetValidIGD ( devlist , & urls , & data , internalIPAddress , sizeof ( internalIPAddress ) ) ;
allocatedUrls = ret ! = 0 ; // urls is allocated on non-zero return values
}
else
2013-12-17 15:21:49 +01:00
{
2015-01-22 21:31:30 +01:00
LOGMESSAGE ( " Net server: upnpDiscover failed and no working cached URL. " ) ;
2019-08-16 20:07:23 +02:00
freeUPnP ( ) ;
2019-08-15 11:07:16 +02:00
return ;
2013-12-17 15:21:49 +01:00
}
2013-12-17 18:03:49 +01:00
2013-12-17 15:21:49 +01:00
switch ( ret )
{
2014-08-06 16:11:04 +02:00
case 0 :
2015-01-22 21:31:30 +01:00
LOGMESSAGE ( " Net server: No IGD found " ) ;
2014-08-06 16:11:04 +02:00
break ;
2013-12-17 15:21:49 +01:00
case 1 :
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: found valid IGD = %s " , urls . controlURL ) ;
2013-12-17 15:21:49 +01:00
break ;
case 2 :
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: found a valid, not connected IGD = %s, will try to continue anyway " , urls . controlURL ) ;
2013-12-17 15:21:49 +01:00
break ;
case 3 :
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: found a UPnP device unrecognized as IGD = %s, will try to continue anyway " , urls . controlURL ) ;
2013-12-17 15:21:49 +01:00
break ;
default :
debug_warn ( L " Unrecognized return value from UPNP_GetValidIGD " ) ;
}
2013-12-13 03:23:02 +01:00
2013-12-17 15:21:49 +01:00
// Try getting our external/internet facing IP. TODO: Display this on the game-setup page for conviniance.
ret = UPNP_GetExternalIPAddress ( urls . controlURL , data . first . servicetype , externalIPAddress ) ;
if ( ret ! = UPNPCOMMAND_SUCCESS )
2013-12-15 02:02:26 +01:00
{
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: GetExternalIPAddress failed with code %d (%s) " , ret , strupnperror ( ret ) ) ;
2019-08-16 20:07:23 +02:00
freeUPnP ( ) ;
2019-08-15 11:07:16 +02:00
return ;
2013-12-17 15:21:49 +01:00
}
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: ExternalIPAddress = %s " , externalIPAddress ) ;
2013-12-13 03:23:02 +01:00
2013-12-17 15:21:49 +01:00
// Try to setup port forwarding.
ret = UPNP_AddPortMapping ( urls . controlURL , data . first . servicetype , psPort , psPort ,
internalIPAddress , description , protocall , 0 , leaseDuration ) ;
if ( ret ! = UPNPCOMMAND_SUCCESS )
{
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: AddPortMapping(%s, %s, %s) failed with code %d (%s) " ,
2013-12-17 15:21:49 +01:00
psPort , psPort , internalIPAddress , ret , strupnperror ( ret ) ) ;
2019-08-16 20:07:23 +02:00
freeUPnP ( ) ;
2019-08-15 11:07:16 +02:00
return ;
2013-12-17 15:21:49 +01:00
}
2013-12-13 03:23:02 +01:00
2013-12-17 15:21:49 +01:00
// Check that the port was actually forwarded.
ret = UPNP_GetSpecificPortMappingEntry ( urls . controlURL ,
data . first . servicetype ,
psPort , protocall ,
2015-01-05 21:05:53 +01:00
# if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 10
2014-05-19 23:01:06 +02:00
NULL /*remoteHost*/ ,
# endif
2013-12-17 15:21:49 +01:00
intClient , intPort , NULL /*desc*/ ,
NULL /*enabled*/ , duration ) ;
2013-12-17 18:03:49 +01:00
2013-12-17 15:21:49 +01:00
if ( ret ! = UPNPCOMMAND_SUCCESS )
{
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: GetSpecificPortMappingEntry() failed with code %d (%s) " , ret , strupnperror ( ret ) ) ;
2019-08-16 20:07:23 +02:00
freeUPnP ( ) ;
2019-08-15 11:07:16 +02:00
return ;
2013-12-15 02:02:26 +01:00
}
2013-12-17 18:03:49 +01:00
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: External %s:%s %s is redirected to internal %s:%s (duration=%s) " ,
2013-12-17 15:21:49 +01:00
externalIPAddress , psPort , protocall , intClient , intPort , duration ) ;
2013-12-15 02:02:26 +01:00
2013-12-17 18:03:49 +01:00
// Cache root descriptor URL to try to avoid discovery next time.
2013-12-30 00:56:18 +01:00
g_ConfigDB . SetValueString ( CFG_USER , " network.upnprootdescurl " , urls . controlURL ) ;
2016-02-07 12:31:23 +01:00
g_ConfigDB . WriteValueToFile ( CFG_USER , " network.upnprootdescurl " , urls . controlURL ) ;
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: cached UPnP root descriptor URL as %s " , urls . controlURL ) ;
2013-12-13 03:23:02 +01:00
2019-08-16 20:07:23 +02:00
freeUPnP ( ) ;
2009-04-11 19:00:39 +02:00
}
2013-12-18 17:08:56 +01:00
# endif // CONFIG2_MINIUPNPC
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : SendMessage ( ENetPeer * peer , const CNetMessage * message )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( m_Host ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = static_cast < CNetServerSession * > ( peer - > data ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
return CNetHost : : SendMessage ( message , peer , DebugName ( session ) . c_str ( ) ) ;
2009-04-11 19:00:39 +02:00
}
2017-01-25 20:04:17 +01:00
bool CNetServerWorker : : Broadcast ( const CNetMessage * message , const std : : vector < NetServerSessionState > & targetStates )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( m_Host ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
bool ok = true ;
2009-04-11 19:00:39 +02:00
2016-04-24 22:48:53 +02:00
// TODO: this does lots of repeated message serialisation if we have lots
// of remote peers; could do it more efficiently if that's a real problem
2017-01-25 20:04:17 +01:00
2016-04-24 22:48:53 +02:00
for ( CNetServerSession * session : m_Sessions )
2020-11-26 23:28:50 +01:00
if ( std : : find ( targetStates . begin ( ) , targetStates . end ( ) , static_cast < NetServerSessionState > ( session - > GetCurrState ( ) ) ) ! = targetStates . end ( ) & &
2017-01-25 20:04:17 +01:00
! session - > SendMessage ( message ) )
ok = false ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
return ok ;
2009-04-11 19:00:39 +02:00
}
2019-08-15 11:07:16 +02:00
void CNetServerWorker : : RunThread ( CNetServerWorker * data )
2009-04-11 19:00:39 +02:00
{
2010-10-31 23:00:28 +01:00
debug_SetThreadName ( " NetServer " ) ;
2009-04-11 19:00:39 +02:00
2019-08-15 11:07:16 +02:00
data - > Run ( ) ;
2010-10-31 23:00:28 +01:00
}
void CNetServerWorker : : Run ( )
{
2020-11-14 11:57:50 +01:00
// The script context uses the profiler and therefore the thread must be registered before the context is created
2014-03-28 21:26:32 +01:00
g_Profiler2 . RegisterCurrentThread ( " Net server " ) ;
2015-12-21 14:58:32 +01:00
2020-11-14 11:57:50 +01:00
// We create a new ScriptContext for this network thread, with a single ScriptInterface.
2021-05-22 21:28:40 +02:00
std : : shared_ptr < ScriptContext > netServerContext = ScriptContext : : CreateContext ( ) ;
2020-11-14 11:57:50 +01:00
m_ScriptInterface = new ScriptInterface ( " Engine " , " Net server " , netServerContext ) ;
2021-03-22 11:13:27 +01:00
m_InitAttributes . init ( m_ScriptInterface - > GetGeneralJSContext ( ) , JS : : UndefinedValue ( ) ) ;
2015-01-24 15:46:52 +01:00
2010-10-31 23:00:28 +01:00
while ( true )
2010-06-30 23:41:04 +02:00
{
2010-10-31 23:00:28 +01:00
if ( ! RunStep ( ) )
break ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
// Implement autostart mode
if ( m_State = = SERVER_STATE_PREGAME & & ( int ) m_PlayerAssignments . size ( ) = = m_AutostartPlayers )
2021-05-14 12:18:03 +02:00
StartGame ( Script : : StringifyJSON ( ScriptRequest ( m_ScriptInterface ) , & m_InitAttributes ) ) ;
2010-07-06 21:54:17 +02:00
2010-10-31 23:00:28 +01:00
// Update profiler stats
m_Stats - > LatchHostState ( m_Host ) ;
}
2015-12-21 14:58:32 +01:00
2011-10-27 18:46:48 +02:00
// Clear roots before deleting their context
m_SavedCommands . clear ( ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
SAFE_DELETE ( m_ScriptInterface ) ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : RunStep ( )
{
// Check for messages from the game thread.
// (Do as little work as possible while the mutex is held open,
// to avoid performance problems and deadlocks.)
2015-12-21 14:58:32 +01:00
2020-11-14 11:57:50 +01:00
m_ScriptInterface - > GetContext ( ) - > MaybeIncrementalGC ( 0.5f ) ;
2015-12-21 14:58:32 +01:00
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
ScriptRequest rq ( m_ScriptInterface ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
std : : vector < bool > newStartGame ;
std : : vector < std : : string > newGameAttributes ;
2018-03-12 01:23:40 +01:00
std : : vector < std : : pair < CStr , CStr > > newLobbyAuths ;
2010-10-31 23:00:28 +01:00
std : : vector < u32 > newTurnLength ;
2010-06-30 23:41:04 +02:00
2010-10-31 23:00:28 +01:00
{
2019-06-06 21:30:48 +02:00
std : : lock_guard < std : : mutex > lock ( m_WorkerMutex ) ;
2010-07-06 21:54:17 +02:00
2010-10-31 23:00:28 +01:00
if ( m_Shutdown )
return false ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
newStartGame . swap ( m_StartGameQueue ) ;
2021-03-22 11:13:27 +01:00
newGameAttributes . swap ( m_InitAttributesQueue ) ;
2018-03-12 01:23:40 +01:00
newLobbyAuths . swap ( m_LobbyAuthQueue ) ;
2010-10-31 23:00:28 +01:00
newTurnLength . swap ( m_TurnLengthQueue ) ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
if ( ! newGameAttributes . empty ( ) )
2014-08-03 00:21:50 +02:00
{
2021-03-22 11:13:27 +01:00
if ( m_State ! = SERVER_STATE_UNCONNECTED & & m_State ! = SERVER_STATE_PREGAME )
LOGERROR ( " NetServer: Init Attributes cannot be changed after the server starts loading. " ) ;
else
{
JS : : RootedValue gameAttributesVal ( rq . cx ) ;
2021-05-14 12:18:03 +02:00
Script : : ParseJSON ( rq , newGameAttributes . back ( ) , & gameAttributesVal ) ;
2021-03-22 11:13:27 +01:00
m_InitAttributes = gameAttributesVal ;
}
2014-08-03 00:21:50 +02:00
}
2010-07-02 23:28:48 +02:00
2010-10-31 23:00:28 +01:00
if ( ! newTurnLength . empty ( ) )
SetTurnLength ( newTurnLength . back ( ) ) ;
2009-04-11 19:00:39 +02:00
2018-03-12 01:23:40 +01:00
while ( ! newLobbyAuths . empty ( ) )
{
const std : : pair < CStr , CStr > & auth = newLobbyAuths . back ( ) ;
ProcessLobbyAuth ( auth . first , auth . second ) ;
newLobbyAuths . pop_back ( ) ;
}
2011-10-27 18:46:48 +02:00
// Perform file transfers
2016-04-24 22:48:53 +02:00
for ( CNetServerSession * session : m_Sessions )
session - > GetFileTransferer ( ) . Poll ( ) ;
2011-10-27 18:46:48 +02:00
2016-02-04 18:14:46 +01:00
CheckClientConnections ( ) ;
2010-10-31 23:00:28 +01:00
// Process network events:
ENetEvent event ;
int status = enet_host_service ( m_Host , & event , HOST_SERVICE_TIMEOUT ) ;
if ( status < 0 )
{
2015-01-22 21:31:30 +01:00
LOGERROR ( " CNetServerWorker: enet_host_service failed (%d) " , status ) ;
2010-10-31 23:00:28 +01:00
// TODO: notify game that the server has shut down
return false ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
if ( status = = 0 )
{
// Reached timeout with no events - try again
return true ;
}
// Process the event:
switch ( event . type )
{
case ENET_EVENT_TYPE_CONNECT :
{
// Report the client address
char hostname [ 256 ] = " (error) " ;
enet_address_get_host_ip ( & event . peer - > address , hostname , ARRAY_SIZE ( hostname ) ) ;
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: Received connection from %s:%u " , hostname , ( unsigned int ) event . peer - > address . port ) ;
2010-10-31 23:00:28 +01:00
// Set up a session object for this peer
CNetServerSession * session = new CNetServerSession ( * this , event . peer ) ;
m_Sessions . push_back ( session ) ;
SetupSession ( session ) ;
2011-04-30 15:01:45 +02:00
ENSURE ( event . peer - > data = = NULL ) ;
2010-10-31 23:00:28 +01:00
event . peer - > data = session ;
HandleConnect ( session ) ;
break ;
}
case ENET_EVENT_TYPE_DISCONNECT :
{
// If there is an active session with this peer, then reset and delete it
CNetServerSession * session = static_cast < CNetServerSession * > ( event . peer - > data ) ;
if ( session )
2009-04-11 19:00:39 +02:00
{
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: Disconnected %s " , DebugName ( session ) . c_str ( ) ) ;
2010-10-31 23:00:28 +01:00
// Remove the session first, so we won't send player-update messages to it
// when updating the FSM
m_Sessions . erase ( remove ( m_Sessions . begin ( ) , m_Sessions . end ( ) , session ) , m_Sessions . end ( ) ) ;
session - > Update ( ( uint ) NMT_CONNECTION_LOST , NULL ) ;
delete session ;
event . peer - > data = NULL ;
}
2009-04-11 19:00:39 +02:00
2016-10-23 17:13:16 +02:00
if ( m_State = = SERVER_STATE_LOADING )
CheckGameLoadStatus ( NULL ) ;
2010-10-31 23:00:28 +01:00
break ;
}
case ENET_EVENT_TYPE_RECEIVE :
{
// If there is an active session with this peer, then process the message
CNetServerSession * session = static_cast < CNetServerSession * > ( event . peer - > data ) ;
if ( session )
{
// Create message from raw data
CNetMessage * msg = CNetMessageFactory : : CreateMessage ( event . packet - > data , event . packet - > dataLength , GetScriptInterface ( ) ) ;
if ( msg )
2010-06-30 23:41:04 +02:00
{
2015-01-22 21:36:24 +01:00
LOGMESSAGE ( " Net server: Received message %s of size %lu from %s " , msg - > ToString ( ) . c_str ( ) , ( unsigned long ) msg - > GetSerializedLength ( ) , DebugName ( session ) . c_str ( ) ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
HandleMessageReceive ( msg , session ) ;
2010-06-30 23:41:04 +02:00
2010-10-31 23:00:28 +01:00
delete msg ;
2010-06-30 23:41:04 +02:00
}
2010-10-31 23:00:28 +01:00
}
2010-06-30 23:41:04 +02:00
2010-10-31 23:00:28 +01:00
// Done using the packet
enet_packet_destroy ( event . packet ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
break ;
}
2010-12-11 13:35:50 +01:00
case ENET_EVENT_TYPE_NONE :
break ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
return true ;
2010-06-30 23:41:04 +02:00
}
2009-04-11 19:00:39 +02:00
2016-02-04 18:14:46 +01:00
void CNetServerWorker : : CheckClientConnections ( )
{
// Send messages at most once per second
std : : time_t now = std : : time ( nullptr ) ;
if ( now < = m_LastConnectionCheck )
return ;
m_LastConnectionCheck = now ;
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
{
u32 lastReceived = m_Sessions [ i ] - > GetLastReceivedTime ( ) ;
u32 meanRTT = m_Sessions [ i ] - > GetMeanRTT ( ) ;
CNetMessage * message = nullptr ;
// Report if we didn't hear from the client since few seconds
if ( lastReceived > NETWORK_WARNING_TIMEOUT )
{
CClientTimeoutMessage * msg = new CClientTimeoutMessage ( ) ;
msg - > m_GUID = m_Sessions [ i ] - > GetGUID ( ) ;
msg - > m_LastReceivedTime = lastReceived ;
message = msg ;
}
// Report if the client has bad ping
Increase MP Command delay to 4 turns, decrease MP turns to 200ms.
To hide network latency, MP turns send commands not for the next turn
but N turns after that (introduced in c684c211a2).
Further, MP turn length was increased to 500ms compared to 200ms SP
turns (introduced in 6a15b78c98).
Unfortunately, increasing MP turn length has negative consequences:
- makes pathfinding/unit motion much worse & unit behaviour worse in
general.
- makes the game more 'lag-spikey', since computations are done less
often, but thus then can take more time.
This diff essentially reverts 6a15b78c98, instead increasing
COMMAND_DELAY from 2 to 4 in MP. This:
- Reduces the 'inherent command lag' in MP from 1000ms to 800ms
- Increases the lag range at which MP will run smoothtly from 500ms to
600ms
- makes SP and MP turns behave identically again, removing the
hindrances described above.
As a side effect, single-player was not actually using COMMAND_DELAY,
this is now done (can be used to simulate MP command lag).
Refs #3752
Differential Revision: https://code.wildfiregames.com/D3275
This was SVN commit r25001.
2021-03-03 22:02:57 +01:00
else if ( meanRTT > NETWORK_BAD_PING )
2016-02-04 18:14:46 +01:00
{
CClientPerformanceMessage * msg = new CClientPerformanceMessage ( ) ;
CClientPerformanceMessage : : S_m_Clients client ;
client . m_GUID = m_Sessions [ i ] - > GetGUID ( ) ;
client . m_MeanRTT = meanRTT ;
msg - > m_Clients . push_back ( client ) ;
message = msg ;
}
// Send to all clients except the affected one
2018-06-01 19:35:00 +02:00
// (since that will show the locally triggered warning instead).
// Also send it to clients that finished the loading screen while
// the game is still waiting for other clients to finish the loading screen.
2016-02-04 18:14:46 +01:00
if ( message )
for ( size_t j = 0 ; j < m_Sessions . size ( ) ; + + j )
2016-05-01 12:33:51 +02:00
{
if ( i ! = j & & (
2018-06-01 19:35:00 +02:00
( m_Sessions [ j ] - > GetCurrState ( ) = = NSS_PREGAME & & m_State = = SERVER_STATE_PREGAME ) | |
2016-05-01 12:33:51 +02:00
m_Sessions [ j ] - > GetCurrState ( ) = = NSS_INGAME ) )
{
2016-02-04 18:14:46 +01:00
m_Sessions [ j ] - > SendMessage ( message ) ;
2016-05-01 12:33:51 +02:00
}
}
2016-02-04 18:14:46 +01:00
SAFE_DELETE ( message ) ;
}
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : HandleMessageReceive ( const CNetMessage * message , CNetServerSession * session )
2010-06-30 23:41:04 +02:00
{
2011-10-27 18:46:48 +02:00
// Handle non-FSM messages first
2019-09-17 16:18:46 +02:00
Status status = session - > GetFileTransferer ( ) . HandleMessageReceive ( * message ) ;
2011-10-27 18:46:48 +02:00
if ( status ! = INFO : : SKIPPED )
return ;
if ( message - > GetType ( ) = = NMT_FILE_TRANSFER_REQUEST )
{
CFileTransferRequestMessage * reqMessage = ( CFileTransferRequestMessage * ) message ;
// Rejoining client got our JoinSyncStart after we received the state from
// another client, and has now requested that we forward it to them
ENSURE ( ! m_JoinSyncFile . empty ( ) ) ;
session - > GetFileTransferer ( ) . StartResponse ( reqMessage - > m_RequestID , m_JoinSyncFile ) ;
return ;
}
2010-06-30 23:41:04 +02:00
// Update FSM
2016-04-24 22:48:53 +02:00
if ( ! session - > Update ( message - > GetType ( ) , ( void * ) message ) )
2015-01-22 21:31:30 +01:00
LOGERROR ( " Net server: Error running FSM update (type=%d state=%d) " , ( int ) message - > GetType ( ) , ( int ) session - > GetCurrState ( ) ) ;
2010-06-30 23:41:04 +02:00
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : SetupSession ( CNetServerSession * session )
2010-06-30 23:41:04 +02:00
{
void * context = session ;
// Set up transitions for session
2010-07-06 21:54:17 +02:00
session - > AddTransition ( NSS_UNCONNECTED , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
session - > AddTransition ( NSS_HANDSHAKE , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_HANDSHAKE , ( uint ) NMT_CLIENT_HANDSHAKE , NSS_AUTHENTICATE , ( void * ) & OnClientHandshake , context ) ;
2010-07-02 23:28:48 +02:00
2018-03-12 01:23:40 +01:00
session - > AddTransition ( NSS_LOBBY_AUTHENTICATE , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
session - > AddTransition ( NSS_LOBBY_AUTHENTICATE , ( uint ) NMT_AUTHENTICATE , NSS_PREGAME , ( void * ) & OnAuthenticate , context ) ;
2010-07-06 21:54:17 +02:00
session - > AddTransition ( NSS_AUTHENTICATE , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_AUTHENTICATE , ( uint ) NMT_AUTHENTICATE , NSS_PREGAME , ( void * ) & OnAuthenticate , context ) ;
2010-07-02 23:28:48 +02:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED , ( void * ) & OnDisconnect , context ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_CHAT , NSS_PREGAME , ( void * ) & OnChat , context ) ;
2014-04-26 20:34:34 +02:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_READY , NSS_PREGAME , ( void * ) & OnReady , context ) ;
2016-06-04 14:08:30 +02:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_CLEAR_ALL_READY , NSS_PREGAME , ( void * ) & OnClearAllReady , context ) ;
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_GAME_SETUP , NSS_PREGAME , ( void * ) & OnGameSetup , context ) ;
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_ASSIGN_PLAYER , NSS_PREGAME , ( void * ) & OnAssignPlayer , context ) ;
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_KICKED , NSS_PREGAME , ( void * ) & OnKickPlayer , context ) ;
2021-03-22 11:13:27 +01:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_GAME_START , NSS_PREGAME , ( void * ) & OnGameStart , context ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_LOADED_GAME , NSS_INGAME , ( void * ) & OnLoadedGame , context ) ;
2010-07-02 23:28:48 +02:00
2016-06-04 14:08:30 +02:00
session - > AddTransition ( NSS_JOIN_SYNCING , ( uint ) NMT_KICKED , NSS_JOIN_SYNCING , ( void * ) & OnKickPlayer , context ) ;
2014-06-20 05:22:40 +02:00
session - > AddTransition ( NSS_JOIN_SYNCING , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED , ( void * ) & OnDisconnect , context ) ;
2011-10-27 18:46:48 +02:00
session - > AddTransition ( NSS_JOIN_SYNCING , ( uint ) NMT_LOADED_GAME , NSS_INGAME , ( void * ) & OnJoinSyncingLoadedGame , context ) ;
2015-05-03 04:06:17 +02:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_REJOINED , NSS_INGAME , ( void * ) & OnRejoined , context ) ;
2016-06-04 14:08:30 +02:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_KICKED , NSS_INGAME , ( void * ) & OnKickPlayer , context ) ;
2016-05-20 00:10:38 +02:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_CLIENT_PAUSED , NSS_INGAME , ( void * ) & OnClientPaused , context ) ;
2010-07-02 23:28:48 +02:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED , ( void * ) & OnDisconnect , context ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_CHAT , NSS_INGAME , ( void * ) & OnChat , context ) ;
2019-01-03 01:15:31 +01:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_SIMULATION_COMMAND , NSS_INGAME , ( void * ) & OnSimulationCommand , context ) ;
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_SYNC_CHECK , NSS_INGAME , ( void * ) & OnSyncCheck , context ) ;
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_END_COMMAND_BATCH , NSS_INGAME , ( void * ) & OnEndCommandBatch , context ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Set first state
session - > SetFirstState ( NSS_HANDSHAKE ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : HandleConnect ( CNetServerSession * session )
2009-04-11 19:00:39 +02:00
{
2015-11-11 03:05:23 +01:00
if ( std : : find ( m_BannedIPs . begin ( ) , m_BannedIPs . end ( ) , session - > GetIPAddress ( ) ) ! = m_BannedIPs . end ( ) )
{
session - > Disconnect ( NDR_BANNED ) ;
return false ;
}
2010-06-30 23:41:04 +02:00
CSrvHandshakeMessage handshake ;
handshake . m_Magic = PS_PROTOCOL_MAGIC ;
handshake . m_ProtocolVersion = PS_PROTOCOL_VERSION ;
handshake . m_SoftwareVersion = PS_PROTOCOL_VERSION ;
return session - > SendMessage ( & handshake ) ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : OnUserJoin ( CNetServerSession * session )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
AddPlayer ( session - > GetGUID ( ) , session - > GetUserName ( ) ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CPlayerAssignmentMessage assignMessage ;
ConstructPlayerAssignmentMessage ( assignMessage ) ;
session - > SendMessage ( & assignMessage ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : OnUserLeave ( CNetServerSession * session )
2010-07-02 23:28:48 +02:00
{
2016-05-20 00:10:38 +02:00
std : : vector < CStr > : : iterator pausing = std : : find ( m_PausingPlayers . begin ( ) , m_PausingPlayers . end ( ) , session - > GetGUID ( ) ) ;
if ( pausing ! = m_PausingPlayers . end ( ) )
m_PausingPlayers . erase ( pausing ) ;
2010-07-02 23:28:48 +02:00
RemovePlayer ( session - > GetGUID ( ) ) ;
2010-11-03 01:21:52 +01:00
2014-06-20 05:22:40 +02:00
if ( m_ServerTurnManager & & session - > GetCurrState ( ) ! = NSS_JOIN_SYNCING )
2021-03-29 09:53:06 +02:00
m_ServerTurnManager - > UninitialiseClient ( session - > GetHostID ( ) ) ;
2010-11-03 01:21:52 +01:00
// TODO: ought to switch the player controlled by that client
// back to AI control, or something?
2010-07-02 23:28:48 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : AddPlayer ( const CStr & guid , const CStrW & name )
2009-04-11 19:00:39 +02:00
{
2014-03-29 16:09:42 +01:00
// Find all player IDs in active use; we mustn't give them to a second player (excluding the unassigned ID: -1)
2010-06-30 23:41:04 +02:00
std : : set < i32 > usedIDs ;
2020-12-31 15:27:02 +01:00
for ( const std : : pair < const CStr , PlayerAssignment > & p : m_PlayerAssignments )
2016-04-24 22:48:53 +02:00
if ( p . second . m_Enabled & & p . second . m_PlayerID ! = - 1 )
usedIDs . insert ( p . second . m_PlayerID ) ;
2011-10-27 18:46:48 +02:00
// If the player is rejoining after disconnecting, try to give them
// back their old player ID
i32 playerID = - 1 ;
// Try to match GUID first
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
{
if ( ! it - > second . m_Enabled & & it - > first = = guid & & usedIDs . find ( it - > second . m_PlayerID ) = = usedIDs . end ( ) )
{
playerID = it - > second . m_PlayerID ;
m_PlayerAssignments . erase ( it ) ; // delete the old mapping, since we've got a new one now
2014-05-19 18:02:42 +02:00
goto found ;
2011-10-27 18:46:48 +02:00
}
}
// Try to match username next
2014-05-19 18:02:42 +02:00
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
2011-10-27 18:46:48 +02:00
{
2014-05-19 18:02:42 +02:00
if ( ! it - > second . m_Enabled & & it - > second . m_Name = = name & & usedIDs . find ( it - > second . m_PlayerID ) = = usedIDs . end ( ) )
2011-10-27 18:46:48 +02:00
{
2014-05-19 18:02:42 +02:00
playerID = it - > second . m_PlayerID ;
m_PlayerAssignments . erase ( it ) ; // delete the old mapping, since we've got a new one now
goto found ;
2011-10-27 18:46:48 +02:00
}
}
2009-04-11 19:00:39 +02:00
2014-05-19 18:02:42 +02:00
// Otherwise leave the player ID as -1 (observer) and let gamesetup change it as needed.
2009-04-11 19:00:39 +02:00
2014-05-19 18:02:42 +02:00
found :
2010-06-30 23:41:04 +02:00
PlayerAssignment assignment ;
2011-10-27 18:46:48 +02:00
assignment . m_Enabled = true ;
2010-06-30 23:41:04 +02:00
assignment . m_Name = name ;
assignment . m_PlayerID = playerID ;
2014-04-26 20:34:34 +02:00
assignment . m_Status = 0 ;
2010-06-30 23:41:04 +02:00
m_PlayerAssignments [ guid ] = assignment ;
// Send the new assignments to all currently active players
// (which does not include the one that's just joining)
SendPlayerAssignments ( ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : RemovePlayer ( const CStr & guid )
2009-04-11 19:00:39 +02:00
{
2011-10-27 18:46:48 +02:00
m_PlayerAssignments [ guid ] . m_Enabled = false ;
2009-04-11 19:00:39 +02:00
2010-07-02 23:28:48 +02:00
SendPlayerAssignments ( ) ;
2009-04-11 19:00:39 +02:00
}
2014-04-26 20:34:34 +02:00
void CNetServerWorker : : ClearAllPlayerReady ( )
{
2017-02-10 17:09:10 +01:00
for ( std : : pair < const CStr , PlayerAssignment > & p : m_PlayerAssignments )
if ( p . second . m_Status ! = 2 )
p . second . m_Status = 0 ;
2014-04-26 20:34:34 +02:00
SendPlayerAssignments ( ) ;
}
2016-06-04 14:08:30 +02:00
void CNetServerWorker : : KickPlayer ( const CStrW & playerName , const bool ban )
2015-11-11 03:05:23 +01:00
{
// Find the user with that name
std : : vector < CNetServerSession * > : : iterator it = std : : find_if ( m_Sessions . begin ( ) , m_Sessions . end ( ) ,
[ & ] ( CNetServerSession * session ) { return session - > GetUserName ( ) = = playerName ; } ) ;
// and return if no one or the host has that name
2021-02-27 18:44:59 +01:00
if ( it = = m_Sessions . end ( ) | | ( * it ) - > GetGUID ( ) = = m_ControllerGUID )
2016-06-04 14:08:30 +02:00
return ;
2015-11-11 03:05:23 +01:00
if ( ban )
{
// Remember name
if ( std : : find ( m_BannedPlayers . begin ( ) , m_BannedPlayers . end ( ) , playerName ) = = m_BannedPlayers . end ( ) )
2018-10-25 13:58:26 +02:00
m_BannedPlayers . push_back ( m_LobbyAuth ? CStrW ( playerName . substr ( 0 , playerName . find ( L " ( " ) ) ) : playerName ) ;
2015-11-11 03:05:23 +01:00
// Remember IP address
2016-05-08 18:25:00 +02:00
u32 ipAddress = ( * it ) - > GetIPAddress ( ) ;
2016-05-08 13:46:19 +02:00
if ( std : : find ( m_BannedIPs . begin ( ) , m_BannedIPs . end ( ) , ipAddress ) = = m_BannedIPs . end ( ) )
2015-11-11 03:05:23 +01:00
m_BannedIPs . push_back ( ipAddress ) ;
}
// Disconnect that user
( * it ) - > Disconnect ( ban ? NDR_BANNED : NDR_KICKED ) ;
// Send message notifying other clients
CKickedMessage kickedMessage ;
kickedMessage . m_Name = playerName ;
kickedMessage . m_Ban = ban ;
2017-01-25 20:04:17 +01:00
Broadcast ( & kickedMessage , { NSS_PREGAME , NSS_JOIN_SYNCING , NSS_INGAME } ) ;
2015-11-11 03:05:23 +01:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : AssignPlayer ( int playerID , const CStr & guid )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
// Remove anyone who's already assigned to this player
2016-04-24 22:48:53 +02:00
for ( std : : pair < const CStr , PlayerAssignment > & p : m_PlayerAssignments )
2009-04-11 19:00:39 +02:00
{
2016-04-24 22:48:53 +02:00
if ( p . second . m_PlayerID = = playerID )
p . second . m_PlayerID = - 1 ;
2009-04-11 19:00:39 +02:00
}
2010-06-30 23:41:04 +02:00
// Update this host's assignment if it exists
if ( m_PlayerAssignments . find ( guid ) ! = m_PlayerAssignments . end ( ) )
m_PlayerAssignments [ guid ] . m_PlayerID = playerID ;
SendPlayerAssignments ( ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : ConstructPlayerAssignmentMessage ( CPlayerAssignmentMessage & message )
2009-04-11 19:00:39 +02:00
{
2021-01-05 12:49:24 +01:00
for ( const std : : pair < const CStr , PlayerAssignment > & p : m_PlayerAssignments )
2009-04-11 19:00:39 +02:00
{
2016-04-24 22:48:53 +02:00
if ( ! p . second . m_Enabled )
2011-10-27 18:46:48 +02:00
continue ;
2010-06-30 23:41:04 +02:00
CPlayerAssignmentMessage : : S_m_Hosts h ;
2016-04-24 22:48:53 +02:00
h . m_GUID = p . first ;
h . m_Name = p . second . m_Name ;
h . m_PlayerID = p . second . m_PlayerID ;
h . m_Status = p . second . m_Status ;
2010-06-30 23:41:04 +02:00
message . m_Hosts . push_back ( h ) ;
2009-04-11 19:00:39 +02:00
}
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : SendPlayerAssignments ( )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
CPlayerAssignmentMessage message ;
ConstructPlayerAssignmentMessage ( message ) ;
2017-01-25 20:04:17 +01:00
Broadcast ( & message , { NSS_PREGAME , NSS_JOIN_SYNCING , NSS_INGAME } ) ;
2009-04-11 19:00:39 +02:00
}
2017-11-25 07:49:58 +01:00
const ScriptInterface & CNetServerWorker : : GetScriptInterface ( )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
return * m_ScriptInterface ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : SetTurnLength ( u32 msecs )
2010-08-13 18:42:53 +02:00
{
if ( m_ServerTurnManager )
m_ServerTurnManager - > SetTurnLength ( msecs ) ;
}
2018-03-12 01:23:40 +01:00
void CNetServerWorker : : ProcessLobbyAuth ( const CStr & name , const CStr & token )
{
LOGMESSAGE ( " Net Server: Received lobby auth message from %s with %s " , name , token ) ;
// Find the user with that guid
std : : vector < CNetServerSession * > : : iterator it = std : : find_if ( m_Sessions . begin ( ) , m_Sessions . end ( ) ,
[ & ] ( CNetServerSession * session )
{ return session - > GetGUID ( ) = = token ; } ) ;
if ( it = = m_Sessions . end ( ) )
return ;
( * it ) - > SetUserName ( name . FromUTF8 ( ) ) ;
// Send an empty message to request the authentication message from the client
// after its identity has been confirmed via the lobby
CAuthenticateMessage emptyMessage ;
( * it ) - > SendMessage ( & emptyMessage ) ;
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnClientHandshake ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CLIENT_HANDSHAKE ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2010-07-06 21:54:17 +02:00
CCliHandshakeMessage * message = ( CCliHandshakeMessage * ) event - > GetParamRef ( ) ;
if ( message - > m_ProtocolVersion ! = PS_PROTOCOL_VERSION )
2010-06-30 23:41:04 +02:00
{
2010-07-06 21:54:17 +02:00
session - > Disconnect ( NDR_INCORRECT_PROTOCOL_VERSION ) ;
return false ;
2009-04-11 19:00:39 +02:00
}
2017-10-25 00:05:24 +02:00
CStr guid = ps_generate_guid ( ) ;
int count = 0 ;
// Ensure unique GUID
while ( std : : find_if (
server . m_Sessions . begin ( ) , server . m_Sessions . end ( ) ,
[ & guid ] ( const CNetServerSession * session )
{ return session - > GetGUID ( ) = = guid ; } ) ! = server . m_Sessions . end ( ) )
{
if ( + + count > 100 )
{
2019-09-26 13:36:03 +02:00
session - > Disconnect ( NDR_GUID_FAILED ) ;
2017-10-25 00:05:24 +02:00
return true ;
}
guid = ps_generate_guid ( ) ;
}
session - > SetGUID ( guid ) ;
2010-07-06 21:54:17 +02:00
CSrvHandshakeResponseMessage handshakeResponse ;
handshakeResponse . m_UseProtocolVersion = PS_PROTOCOL_VERSION ;
2017-10-25 00:05:24 +02:00
handshakeResponse . m_GUID = guid ;
2010-07-06 21:54:17 +02:00
handshakeResponse . m_Flags = 0 ;
2018-03-12 01:23:40 +01:00
if ( server . m_LobbyAuth )
{
handshakeResponse . m_Flags | = PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH ;
session - > SetNextState ( NSS_LOBBY_AUTHENTICATE ) ;
}
2010-07-06 21:54:17 +02:00
session - > SendMessage ( & handshakeResponse ) ;
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnAuthenticate ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_AUTHENTICATE ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2015-08-28 22:20:10 +02:00
// Prohibit joins while the game is loading
if ( server . m_State = = SERVER_STATE_LOADING )
{
LOGMESSAGE ( " Refused connection while the game is loading " ) ;
session - > Disconnect ( NDR_SERVER_LOADING ) ;
return true ;
}
2011-10-27 18:46:48 +02:00
2015-08-28 22:20:10 +02:00
CAuthenticateMessage * message = ( CAuthenticateMessage * ) event - > GetParamRef ( ) ;
2016-03-09 16:02:38 +01:00
CStrW username = SanitisePlayerName ( message - > m_Name ) ;
2018-03-12 01:23:40 +01:00
CStrW usernameWithoutRating ( username . substr ( 0 , username . find ( L " ( " ) ) ) ;
// Compare the lowercase names as specified by https://xmpp.org/extensions/xep-0029.html#sect-idm139493404168176
// "[...] comparisons will be made in case-normalized canonical form."
if ( server . m_LobbyAuth & & usernameWithoutRating . LowerCase ( ) ! = session - > GetUserName ( ) . LowerCase ( ) )
{
LOGERROR ( " Net server: lobby auth: %s tried joining as %s " ,
2018-03-13 15:55:55 +01:00
session - > GetUserName ( ) . ToUTF8 ( ) ,
usernameWithoutRating . ToUTF8 ( ) ) ;
2018-03-12 01:23:40 +01:00
session - > Disconnect ( NDR_LOBBY_AUTH_FAILED ) ;
return true ;
}
2016-03-09 16:02:38 +01:00
2021-01-23 19:04:36 +01:00
// Check the password before anything else.
2021-05-18 16:47:36 +02:00
// NB: m_Name must match the client's salt, @see CNetClient::SetGamePassword
if ( ! server . CheckPassword ( message - > m_Password , message - > m_Name . ToUTF8 ( ) ) )
2021-01-23 19:04:36 +01:00
{
// Noisy logerror because players are not supposed to be able to get the IP,
// so this might be someone targeting the host for some reason
// (or TODO a dedicated server and we do want to log anyways)
LOGERROR ( " Net server: user %s tried joining with the wrong password " ,
session - > GetUserName ( ) . ToUTF8 ( ) ) ;
session - > Disconnect ( NDR_SERVER_REFUSED ) ;
return true ;
}
2016-03-09 16:02:38 +01:00
// Either deduplicate or prohibit join if name is in use
bool duplicatePlayernames = false ;
2016-03-10 12:16:15 +01:00
CFG_GET_VAL ( " network.duplicateplayernames " , duplicatePlayernames ) ;
2019-01-28 13:09:42 +01:00
// If lobby authentication is enabled, the clients playername has already been registered.
// There also can't be any duplicated names.
if ( ! server . m_LobbyAuth & & duplicatePlayernames )
2016-03-09 16:02:38 +01:00
username = server . DeduplicatePlayerName ( username ) ;
2018-03-12 01:23:40 +01:00
else
{
std : : vector < CNetServerSession * > : : iterator it = std : : find_if (
2016-03-09 16:02:38 +01:00
server . m_Sessions . begin ( ) , server . m_Sessions . end ( ) ,
[ & username ] ( const CNetServerSession * session )
2018-03-12 01:23:40 +01:00
{ return session - > GetUserName ( ) = = username ; } ) ;
if ( it ! = server . m_Sessions . end ( ) & & ( * it ) ! = session )
{
session - > Disconnect ( NDR_PLAYERNAME_IN_USE ) ;
return true ;
}
2016-03-09 16:02:38 +01:00
}
2011-10-27 18:46:48 +02:00
2015-11-11 03:05:23 +01:00
// Disconnect banned usernames
2018-10-25 13:58:26 +02:00
if ( std : : find ( server . m_BannedPlayers . begin ( ) , server . m_BannedPlayers . end ( ) , server . m_LobbyAuth ? usernameWithoutRating : username ) ! = server . m_BannedPlayers . end ( ) )
2015-11-11 03:05:23 +01:00
{
session - > Disconnect ( NDR_BANNED ) ;
return true ;
}
2016-03-13 17:52:00 +01:00
int maxObservers = 0 ;
CFG_GET_VAL ( " network.observerlimit " , maxObservers ) ;
2015-08-28 22:20:10 +02:00
bool isRejoining = false ;
2016-03-13 17:52:00 +01:00
bool serverFull = false ;
if ( server . m_State = = SERVER_STATE_PREGAME )
2010-07-06 21:54:17 +02:00
{
2016-03-13 17:52:00 +01:00
// Don't check for maxObservers in the gamesetup, as we don't know yet who will be assigned
serverFull = server . m_Sessions . size ( ) > = MAX_CLIENTS ;
}
else
{
bool isObserver = true ;
int disconnectedPlayers = 0 ;
int connectedPlayers = 0 ;
2011-10-27 18:46:48 +02:00
// (TODO: if GUIDs were stable, we should use them instead)
2021-01-05 12:49:24 +01:00
for ( const std : : pair < const CStr , PlayerAssignment > & p : server . m_PlayerAssignments )
2016-03-13 17:52:00 +01:00
{
2016-04-24 22:48:53 +02:00
const PlayerAssignment & assignment = p . second ;
if ( ! assignment . m_Enabled & & assignment . m_Name = = username )
2016-03-13 17:52:00 +01:00
{
2016-04-24 22:48:53 +02:00
isObserver = assignment . m_PlayerID = = - 1 ;
2016-03-13 17:52:00 +01:00
isRejoining = true ;
}
2016-04-24 22:48:53 +02:00
if ( assignment . m_PlayerID = = - 1 )
2016-03-13 17:52:00 +01:00
continue ;
2016-04-24 22:48:53 +02:00
if ( assignment . m_Enabled )
2016-03-13 17:52:00 +01:00
+ + connectedPlayers ;
else
+ + disconnectedPlayers ;
}
2011-10-27 18:46:48 +02:00
2017-05-28 20:05:08 +02:00
// Optionally allow everyone or only buddies to join after the game has started
if ( ! isRejoining )
{
CStr observerLateJoin ;
CFG_GET_VAL ( " network.lateobservers " , observerLateJoin ) ;
if ( observerLateJoin = = " everyone " )
{
isRejoining = true ;
}
else if ( observerLateJoin = = " buddies " )
{
CStr buddies ;
CFG_GET_VAL ( " lobby.buddies " , buddies ) ;
std : : wstringstream buddiesStream ( wstring_from_utf8 ( buddies ) ) ;
CStrW buddy ;
while ( std : : getline ( buddiesStream , buddy , L ' , ' ) )
{
if ( buddy = = usernameWithoutRating )
{
isRejoining = true ;
break ;
}
}
}
}
2011-10-27 18:46:48 +02:00
if ( ! isRejoining )
{
2015-01-22 21:37:38 +01:00
LOGMESSAGE ( " Refused connection after game start from not-previously-known user \" %s \" " , utf8_from_wstring ( username ) ) ;
2011-10-27 18:46:48 +02:00
session - > Disconnect ( NDR_SERVER_ALREADY_IN_GAME ) ;
return true ;
}
2016-03-13 17:52:00 +01:00
// Ensure all players will be able to rejoin
serverFull = isObserver & & (
( int ) server . m_Sessions . size ( ) - connectedPlayers > maxObservers | |
( int ) server . m_Sessions . size ( ) + disconnectedPlayers > = MAX_CLIENTS ) ;
}
if ( serverFull )
{
session - > Disconnect ( NDR_SERVER_FULL ) ;
return true ;
2011-10-27 18:46:48 +02:00
}
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
u32 newHostID = server . m_NextHostID + + ;
2009-04-11 19:00:39 +02:00
2010-07-02 23:28:48 +02:00
session - > SetUserName ( username ) ;
2010-06-30 23:41:04 +02:00
session - > SetHostID ( newHostID ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CAuthenticateResultMessage authenticateResult ;
2011-10-29 16:53:13 +02:00
authenticateResult . m_Code = isRejoining ? ARC_OK_REJOINING : ARC_OK ;
2010-06-30 23:41:04 +02:00
authenticateResult . m_HostID = newHostID ;
authenticateResult . m_Message = L " Logged in " ;
2021-02-27 18:44:59 +01:00
authenticateResult . m_IsController = 0 ;
if ( message - > m_ControllerSecret = = server . m_ControllerSecret )
{
if ( server . m_ControllerGUID . empty ( ) )
{
server . m_ControllerGUID = session - > GetGUID ( ) ;
authenticateResult . m_IsController = 1 ;
}
// TODO: we could probably handle having several controllers, or swapping?
}
2010-06-30 23:41:04 +02:00
session - > SendMessage ( & authenticateResult ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
server . OnUserJoin ( session ) ;
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
if ( isRejoining )
{
2021-03-22 11:13:27 +01:00
ENSURE ( server . m_State ! = SERVER_STATE_UNCONNECTED & & server . m_State ! = SERVER_STATE_PREGAME ) ;
2011-10-27 18:46:48 +02:00
// Request a copy of the current game state from an existing player,
// so we can send it on to the new player
// Assume session 0 is most likely the local player, so they're
// the most efficient client to request a copy from
CNetServerSession * sourceSession = server . m_Sessions . at ( 0 ) ;
Prevent players from disconnecting during the loading screen by increasing the timeout tolerance to 60 seconds for that period, fixes #5163.
The NetClient runs in the main thread, so any part of the loading screen
consuming several seconds makes that client timeout.
This is a workaround because threading the NetClient would have prevent
these timeouts, refs #3700.
Coutnerintuitively, since enet timeout tolerance is proportional to the
latency, the better the connection of the player, the more likely it was
to drop on gamestart.
This problem became very frequent in Alpha 23, at least due to the Aura
bugfix 583b6ec625, AIInterface being particularly slow and that not
having been disabled yet in the loading screen resulting in additional
10 second freezes during the loading screen, even on empty maps, refs
#5200, 8e168f85e6.
Differential Revision: https://code.wildfiregames.com/D1513
Based on patch by: causative
This was SVN commit r21842.
2018-06-07 00:09:38 +02:00
2011-10-27 18:46:48 +02:00
sourceSession - > GetFileTransferer ( ) . StartTask (
2021-05-22 21:28:40 +02:00
std : : shared_ptr < CNetFileReceiveTask > ( new CNetFileReceiveTask_ServerRejoin ( server , newHostID ) )
2011-10-27 18:46:48 +02:00
) ;
session - > SetNextState ( NSS_JOIN_SYNCING ) ;
}
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2019-01-03 01:15:31 +01:00
bool CNetServerWorker : : OnSimulationCommand ( void * context , CFsmEvent * event )
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_SIMULATION_COMMAND ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
CSimulationMessage * message = ( CSimulationMessage * ) event - > GetParamRef ( ) ;
// Ignore messages sent by one player on behalf of another player
// unless cheating is enabled
bool cheatsEnabled = false ;
const ScriptInterface & scriptInterface = server . GetScriptInterface ( ) ;
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
ScriptRequest rq ( scriptInterface ) ;
2020-11-13 14:18:22 +01:00
JS : : RootedValue settings ( rq . cx ) ;
2021-05-13 19:23:52 +02:00
Script : : GetProperty ( rq , server . m_InitAttributes , " settings " , & settings ) ;
if ( Script : : HasProperty ( rq , settings , " CheatsEnabled " ) )
Script : : GetProperty ( rq , settings , " CheatsEnabled " , cheatsEnabled ) ;
2019-01-03 01:15:31 +01:00
PlayerAssignmentMap : : iterator it = server . m_PlayerAssignments . find ( session - > GetGUID ( ) ) ;
// When cheating is disabled, fail if the player the message claims to
// represent does not exist or does not match the sender's player name
if ( ! cheatsEnabled & & ( it = = server . m_PlayerAssignments . end ( ) | | it - > second . m_PlayerID ! = message - > m_Player ) )
return true ;
// Send it back to all clients that have finished
// the loading screen (and the synchronization when rejoining)
server . Broadcast ( message , { NSS_INGAME } ) ;
// Save all the received commands
if ( server . m_SavedCommands . size ( ) < message - > m_Turn + 1 )
server . m_SavedCommands . resize ( message - > m_Turn + 1 ) ;
server . m_SavedCommands [ message - > m_Turn ] . push_back ( * message ) ;
// TODO: we shouldn't send the message back to the client that first sent it
return true ;
}
2009-04-11 19:00:39 +02:00
2019-01-03 01:15:31 +01:00
bool CNetServerWorker : : OnSyncCheck ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2019-01-03 01:15:31 +01:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_SYNC_CHECK ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2019-01-03 01:15:31 +01:00
CSyncCheckMessage * message = ( CSyncCheckMessage * ) event - > GetParamRef ( ) ;
2015-11-02 04:20:44 +01:00
2019-01-03 01:15:31 +01:00
server . m_ServerTurnManager - > NotifyFinishedClientUpdate ( * session , message - > m_Turn , message - > m_Hash ) ;
return true ;
}
2009-04-11 19:00:39 +02:00
2019-01-03 01:15:31 +01:00
bool CNetServerWorker : : OnEndCommandBatch ( void * context , CFsmEvent * event )
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_END_COMMAND_BATCH ) ;
2011-10-27 18:46:48 +02:00
2019-01-03 01:15:31 +01:00
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
CEndCommandBatchMessage * message = ( CEndCommandBatchMessage * ) event - > GetParamRef ( ) ;
2010-06-30 23:41:04 +02:00
2019-01-03 01:15:31 +01:00
// The turn-length field is ignored
server . m_ServerTurnManager - > NotifyFinishedClientCommands ( * session , message - > m_Turn ) ;
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnChat ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CHAT ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CChatMessage * message = ( CChatMessage * ) event - > GetParamRef ( ) ;
2010-07-02 23:28:48 +02:00
2010-08-14 21:45:22 +02:00
message - > m_GUID = session - > GetGUID ( ) ;
2009-04-11 19:00:39 +02:00
2017-01-25 20:04:17 +01:00
server . Broadcast ( message , { NSS_PREGAME , NSS_INGAME } ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2014-04-26 20:34:34 +02:00
bool CNetServerWorker : : OnReady ( void * context , CFsmEvent * event )
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_READY ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
2017-04-10 18:52:52 +02:00
// Occurs if a client presses not-ready
// in the very last moment before the hosts starts the game
if ( server . m_State = = SERVER_STATE_LOADING )
return true ;
2014-04-26 20:34:34 +02:00
CReadyMessage * message = ( CReadyMessage * ) event - > GetParamRef ( ) ;
message - > m_GUID = session - > GetGUID ( ) ;
2017-01-25 20:04:17 +01:00
server . Broadcast ( message , { NSS_PREGAME } ) ;
2017-04-10 00:59:04 +02:00
server . m_PlayerAssignments [ message - > m_GUID ] . m_Status = message - > m_Status ;
2016-06-04 14:08:30 +02:00
return true ;
}
bool CNetServerWorker : : OnClearAllReady ( void * context , CFsmEvent * event )
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CLEAR_ALL_READY ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
2021-02-27 18:44:59 +01:00
if ( session - > GetGUID ( ) = = server . m_ControllerGUID )
2016-06-04 14:08:30 +02:00
server . ClearAllPlayerReady ( ) ;
return true ;
}
bool CNetServerWorker : : OnGameSetup ( void * context , CFsmEvent * event )
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_GAME_SETUP ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
2018-06-05 14:24:30 +02:00
// Changing the settings after gamestart is not implemented and would cause an Out-of-sync error.
// This happened when doubleclicking on the startgame button.
if ( server . m_State ! = SERVER_STATE_PREGAME )
return true ;
2021-03-22 11:13:27 +01:00
// Only the controller is allowed to send game setup updates.
// TODO: it would be good to allow other players to request changes to some settings,
// e.g. their civilisation.
// Possibly this should use another message, to enforce a single source of truth.
2021-02-27 18:44:59 +01:00
if ( session - > GetGUID ( ) = = server . m_ControllerGUID )
2016-06-04 14:08:30 +02:00
{
CGameSetupMessage * message = ( CGameSetupMessage * ) event - > GetParamRef ( ) ;
2021-03-22 11:13:27 +01:00
server . Broadcast ( message , { NSS_PREGAME } ) ;
2016-06-04 14:08:30 +02:00
}
return true ;
}
bool CNetServerWorker : : OnAssignPlayer ( void * context , CFsmEvent * event )
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_ASSIGN_PLAYER ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
2021-02-27 18:44:59 +01:00
if ( session - > GetGUID ( ) = = server . m_ControllerGUID )
2016-06-04 14:08:30 +02:00
{
CAssignPlayerMessage * message = ( CAssignPlayerMessage * ) event - > GetParamRef ( ) ;
server . AssignPlayer ( message - > m_PlayerID , message - > m_GUID ) ;
}
return true ;
}
2021-03-22 11:13:27 +01:00
bool CNetServerWorker : : OnGameStart ( void * context , CFsmEvent * event )
2016-06-04 14:08:30 +02:00
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_GAME_START ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
2021-03-22 11:13:27 +01:00
if ( session - > GetGUID ( ) ! = server . m_ControllerGUID )
return true ;
2014-04-26 20:34:34 +02:00
2021-03-22 11:13:27 +01:00
CGameStartMessage * message = ( CGameStartMessage * ) event - > GetParamRef ( ) ;
server . StartGame ( message - > m_InitAttributes ) ;
2014-04-26 20:34:34 +02:00
return true ;
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnLoadedGame ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_LOADED_GAME ) ;
2009-04-11 19:00:39 +02:00
2017-03-21 19:50:29 +01:00
CNetServerSession * loadedSession = ( CNetServerSession * ) context ;
CNetServerWorker & server = loadedSession - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2017-03-21 19:50:29 +01:00
// We're in the loading state, so wait until every client has loaded
// before starting the game
2011-10-27 18:46:48 +02:00
ENSURE ( server . m_State = = SERVER_STATE_LOADING ) ;
2017-03-21 19:50:29 +01:00
if ( server . CheckGameLoadStatus ( loadedSession ) )
return true ;
CClientsLoadingMessage message ;
// We always send all GUIDs of clients in the loading state
// so that we don't have to bother about switching GUI pages
for ( CNetServerSession * session : server . m_Sessions )
if ( session - > GetCurrState ( ) ! = NSS_INGAME & & loadedSession - > GetGUID ( ) ! = session - > GetGUID ( ) )
{
CClientsLoadingMessage : : S_m_Clients client ;
client . m_GUID = session - > GetGUID ( ) ;
message . m_Clients . push_back ( client ) ;
}
// Send to the client who has loaded the game but did not reach the NSS_INGAME state yet
loadedSession - > SendMessage ( & message ) ;
server . Broadcast ( & message , { NSS_INGAME } ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2011-10-27 18:46:48 +02:00
bool CNetServerWorker : : OnJoinSyncingLoadedGame ( void * context , CFsmEvent * event )
{
// A client rejoining an in-progress game has now finished loading the
// map and deserialized the initial state.
// The simulation may have progressed since then, so send any subsequent
// commands to them and set them as an active player so they can participate
// in all future turns.
2015-12-21 14:58:32 +01:00
//
2011-10-27 18:46:48 +02:00
// (TODO: if it takes a long time for them to receive and execute all these
// commands, the other players will get frozen for that time and may be unhappy;
// we could try repeating this process a few times until the client converges
// on the up-to-date state, before setting them as active.)
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_LOADED_GAME ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
CLoadedGameMessage * message = ( CLoadedGameMessage * ) event - > GetParamRef ( ) ;
u32 turn = message - > m_CurrentTurn ;
u32 readyTurn = server . m_ServerTurnManager - > GetReadyTurn ( ) ;
// Send them all commands received since their saved state,
// and turn-ended messages for any turns that have already been processed
for ( size_t i = turn + 1 ; i < std : : max ( readyTurn + 1 , ( u32 ) server . m_SavedCommands . size ( ) ) ; + + i )
{
if ( i < server . m_SavedCommands . size ( ) )
for ( size_t j = 0 ; j < server . m_SavedCommands [ i ] . size ( ) ; + + j )
session - > SendMessage ( & server . m_SavedCommands [ i ] [ j ] ) ;
if ( i < = readyTurn )
{
CEndCommandBatchMessage endMessage ;
endMessage . m_Turn = i ;
endMessage . m_TurnLength = server . m_ServerTurnManager - > GetSavedTurnLength ( i ) ;
session - > SendMessage ( & endMessage ) ;
}
}
// Tell the turn manager to expect commands from this new client
2021-03-29 09:53:06 +02:00
// Special case: the controller shouldn't be treated as an observer in any case.
bool isObserver = server . m_PlayerAssignments [ session - > GetGUID ( ) ] . m_PlayerID = = - 1 & & server . m_ControllerGUID ! = session - > GetGUID ( ) ;
server . m_ServerTurnManager - > InitialiseClient ( session - > GetHostID ( ) , readyTurn , isObserver ) ;
2011-10-27 18:46:48 +02:00
// Tell the client that everything has finished loading and it should start now
CLoadedGameMessage loaded ;
loaded . m_CurrentTurn = readyTurn ;
session - > SendMessage ( & loaded ) ;
return true ;
}
2015-05-03 04:06:17 +02:00
bool CNetServerWorker : : OnRejoined ( void * context , CFsmEvent * event )
{
// A client has finished rejoining and the loading screen disappeared.
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_REJOINED ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
2017-01-25 20:04:17 +01:00
// Inform everyone of the client having rejoined
2015-05-03 04:06:17 +02:00
CRejoinedMessage * message = ( CRejoinedMessage * ) event - > GetParamRef ( ) ;
message - > m_GUID = session - > GetGUID ( ) ;
2017-01-25 20:04:17 +01:00
server . Broadcast ( message , { NSS_INGAME } ) ;
2015-05-03 04:06:17 +02:00
2016-05-20 00:10:38 +02:00
// Send all pausing players to the rejoined client.
for ( const CStr & guid : server . m_PausingPlayers )
{
CClientPausedMessage pausedMessage ;
pausedMessage . m_GUID = guid ;
pausedMessage . m_Pause = true ;
session - > SendMessage ( & pausedMessage ) ;
}
2015-05-03 04:06:17 +02:00
return true ;
}
2016-06-04 14:08:30 +02:00
bool CNetServerWorker : : OnKickPlayer ( void * context , CFsmEvent * event )
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_KICKED ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
2021-02-27 18:44:59 +01:00
if ( session - > GetGUID ( ) = = server . m_ControllerGUID )
2016-06-04 14:08:30 +02:00
{
CKickedMessage * message = ( CKickedMessage * ) event - > GetParamRef ( ) ;
server . KickPlayer ( message - > m_Name , message - > m_Ban ) ;
}
return true ;
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnDisconnect ( void * context , CFsmEvent * event )
2010-07-02 23:28:48 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CONNECTION_LOST ) ;
2010-07-02 23:28:48 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2010-07-02 23:28:48 +02:00
2010-07-06 21:54:17 +02:00
server . OnUserLeave ( session ) ;
2010-07-02 23:28:48 +02:00
return true ;
}
2016-05-20 14:51:27 +02:00
bool CNetServerWorker : : OnClientPaused ( void * context , CFsmEvent * event )
2016-05-20 00:10:38 +02:00
{
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CLIENT_PAUSED ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
CClientPausedMessage * message = ( CClientPausedMessage * ) event - > GetParamRef ( ) ;
message - > m_GUID = session - > GetGUID ( ) ;
// Update the list of pausing players.
std : : vector < CStr > : : iterator player = std : : find ( server . m_PausingPlayers . begin ( ) , server . m_PausingPlayers . end ( ) , session - > GetGUID ( ) ) ;
if ( message - > m_Pause )
{
if ( player ! = server . m_PausingPlayers . end ( ) )
return true ;
server . m_PausingPlayers . push_back ( session - > GetGUID ( ) ) ;
}
else
{
if ( player = = server . m_PausingPlayers . end ( ) )
return true ;
server . m_PausingPlayers . erase ( player ) ;
}
// Send messages to clients that are in game, and are not the client who paused.
2020-11-26 23:28:50 +01:00
for ( CNetServerSession * netSession : server . m_Sessions )
if ( netSession - > GetCurrState ( ) = = NSS_INGAME & & message - > m_GUID ! = netSession - > GetGUID ( ) )
netSession - > SendMessage ( message ) ;
2016-05-20 00:10:38 +02:00
return true ;
}
2017-03-21 19:50:29 +01:00
bool CNetServerWorker : : CheckGameLoadStatus ( CNetServerSession * changedSession )
2009-04-11 19:00:39 +02:00
{
2016-04-24 22:48:53 +02:00
for ( const CNetServerSession * session : m_Sessions )
if ( session ! = changedSession & & session - > GetCurrState ( ) ! = NSS_INGAME )
2017-03-21 19:50:29 +01:00
return false ;
2010-06-30 23:41:04 +02:00
2017-01-25 20:04:17 +01:00
// Inform clients that everyone has loaded the map and that the game can start
2010-06-30 23:41:04 +02:00
CLoadedGameMessage loaded ;
2011-10-27 18:46:48 +02:00
loaded . m_CurrentTurn = 0 ;
2017-01-28 21:47:26 +01:00
// Notice the changedSession is still in the NSS_PREGAME state
Broadcast ( & loaded , { NSS_PREGAME , NSS_INGAME } ) ;
2010-06-30 23:41:04 +02:00
m_State = SERVER_STATE_INGAME ;
2017-03-21 19:50:29 +01:00
return true ;
2009-04-11 19:00:39 +02:00
}
2021-03-22 11:13:27 +01:00
void CNetServerWorker : : StartGame ( const CStr & initAttribs )
2009-04-11 19:00:39 +02:00
{
2018-07-21 13:58:35 +02:00
for ( std : : pair < const CStr , PlayerAssignment > & player : m_PlayerAssignments )
if ( player . second . m_Enabled & & player . second . m_PlayerID ! = - 1 & & player . second . m_Status = = 0 )
{
LOGERROR ( " Tried to start the game without player \" %s \" being ready! " , utf8_from_wstring ( player . second . m_Name ) . c_str ( ) ) ;
return ;
}
2010-06-30 23:41:04 +02:00
m_ServerTurnManager = new CNetServerTurnManager ( * this ) ;
2009-04-11 19:00:39 +02:00
Prevent players from disconnecting during the loading screen by increasing the timeout tolerance to 60 seconds for that period, fixes #5163.
The NetClient runs in the main thread, so any part of the loading screen
consuming several seconds makes that client timeout.
This is a workaround because threading the NetClient would have prevent
these timeouts, refs #3700.
Coutnerintuitively, since enet timeout tolerance is proportional to the
latency, the better the connection of the player, the more likely it was
to drop on gamestart.
This problem became very frequent in Alpha 23, at least due to the Aura
bugfix 583b6ec625, AIInterface being particularly slow and that not
having been disabled yet in the loading screen resulting in additional
10 second freezes during the loading screen, even on empty maps, refs
#5200, 8e168f85e6.
Differential Revision: https://code.wildfiregames.com/D1513
Based on patch by: causative
This was SVN commit r21842.
2018-06-07 00:09:38 +02:00
for ( CNetServerSession * session : m_Sessions )
2021-03-29 09:53:06 +02:00
{
// Special case: the controller shouldn't be treated as an observer in any case.
bool isObserver = m_PlayerAssignments [ session - > GetGUID ( ) ] . m_PlayerID = = - 1 & & m_ControllerGUID ! = session - > GetGUID ( ) ;
m_ServerTurnManager - > InitialiseClient ( session - > GetHostID ( ) , 0 , isObserver ) ;
}
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
m_State = SERVER_STATE_LOADING ;
2009-04-11 19:00:39 +02:00
2015-06-18 20:20:54 +02:00
// Remove players and observers that are not present when the game starts
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; )
if ( it - > second . m_Enabled )
+ + it ;
else
it = m_PlayerAssignments . erase ( it ) ;
2010-06-30 23:41:04 +02:00
SendPlayerAssignments ( ) ;
2009-04-11 19:00:39 +02:00
2021-03-22 11:13:27 +01:00
// Update init attributes. They should no longer change.
2021-05-14 12:18:03 +02:00
Script : : ParseJSON ( ScriptRequest ( m_ScriptInterface ) , initAttribs , & m_InitAttributes ) ;
2021-03-22 11:13:27 +01:00
2010-06-30 23:41:04 +02:00
CGameStartMessage gameStart ;
2021-03-22 11:13:27 +01:00
gameStart . m_InitAttributes = initAttribs ;
2017-01-25 20:04:17 +01:00
Broadcast ( & gameStart , { NSS_PREGAME } ) ;
2010-06-30 23:41:04 +02:00
}
2010-10-31 23:00:28 +01:00
CStrW CNetServerWorker : : SanitisePlayerName ( const CStrW & original )
2010-07-02 23:28:48 +02:00
{
const size_t MAX_LENGTH = 32 ;
CStrW name = original ;
name . Replace ( L " [ " , L " { " ) ; // remove GUI tags
name . Replace ( L " ] " , L " } " ) ; // remove for symmetry
// Restrict the length
if ( name . length ( ) > MAX_LENGTH )
name = name . Left ( MAX_LENGTH ) ;
// Don't allow surrounding whitespace
name . Trim ( PS_TRIM_BOTH ) ;
// Don't allow empty name
if ( name . empty ( ) )
name = L " Anonymous " ;
return name ;
}
2010-10-31 23:00:28 +01:00
CStrW CNetServerWorker : : DeduplicatePlayerName ( const CStrW & original )
2010-07-02 23:28:48 +02:00
{
CStrW name = original ;
2010-07-06 21:54:17 +02:00
// Try names "Foo", "Foo (2)", "Foo (3)", etc
2010-07-02 23:28:48 +02:00
size_t id = 2 ;
while ( true )
{
bool unique = true ;
2016-04-24 22:48:53 +02:00
for ( const CNetServerSession * session : m_Sessions )
2010-07-02 23:28:48 +02:00
{
2016-04-24 22:48:53 +02:00
if ( session - > GetUserName ( ) = = name )
2010-07-02 23:28:48 +02:00
{
unique = false ;
break ;
}
}
if ( unique )
return name ;
2011-02-17 21:08:20 +01:00
name = original + L " ( " + CStrW : : FromUInt ( id + + ) + L " ) " ;
2010-07-02 23:28:48 +02:00
}
}
2010-10-31 23:00:28 +01:00
STUN + XMPP ICE implementation.
Allows lobby players to host games without having to configure their
router.
Differential Revision: https://code.wildfiregames.com/D364
Fixes #2305
Patch By: fcxSanya.
StunClient based on code by SuperTuxKart, relicensed with approval of
the according authors hilnius, hiker, Auria, deveee, Flakebi, leper,
konstin and KroArtem.
Added rfc5245 (ejabberd) support, a GUI option, refactoring and segfault
fixes by myself.
Tested By: user1, Sandarac, Sestroretsk1714, Vladislav, Grugnas,
javiergodas
Partially Reviewed By: leper, Philip, echotangoecho
This was SVN commit r19703.
2017-06-01 08:33:52 +02:00
void CNetServerWorker : : SendHolePunchingMessage ( const CStr & ipStr , u16 port )
{
2019-08-17 02:12:19 +02:00
if ( m_Host )
StunClient : : SendHolePunchingMessages ( * m_Host , ipStr , port ) ;
STUN + XMPP ICE implementation.
Allows lobby players to host games without having to configure their
router.
Differential Revision: https://code.wildfiregames.com/D364
Fixes #2305
Patch By: fcxSanya.
StunClient based on code by SuperTuxKart, relicensed with approval of
the according authors hilnius, hiker, Auria, deveee, Flakebi, leper,
konstin and KroArtem.
Added rfc5245 (ejabberd) support, a GUI option, refactoring and segfault
fixes by myself.
Tested By: user1, Sandarac, Sestroretsk1714, Vladislav, Grugnas,
javiergodas
Partially Reviewed By: leper, Philip, echotangoecho
This was SVN commit r19703.
2017-06-01 08:33:52 +02:00
}
2010-10-31 23:00:28 +01:00
2018-03-12 01:23:40 +01:00
CNetServer : : CNetServer ( bool useLobbyAuth , int autostartPlayers ) :
2018-08-25 16:34:30 +02:00
m_Worker ( new CNetServerWorker ( useLobbyAuth , autostartPlayers ) ) ,
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
m_LobbyAuth ( useLobbyAuth ) , m_UseSTUN ( false ) , m_PublicIp ( " " ) , m_PublicPort ( 20595 ) , m_Password ( )
2010-10-31 23:00:28 +01:00
{
}
CNetServer : : ~ CNetServer ( )
{
delete m_Worker ;
}
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
bool CNetServer : : GetUseSTUN ( ) const
{
return m_UseSTUN ;
}
2018-08-25 16:34:30 +02:00
bool CNetServer : : UseLobbyAuth ( ) const
{
return m_LobbyAuth ;
}
2016-06-13 18:56:14 +02:00
bool CNetServer : : SetupConnection ( const u16 port )
2010-10-31 23:00:28 +01:00
{
2016-06-13 18:56:14 +02:00
return m_Worker - > SetupConnection ( port ) ;
2010-10-31 23:00:28 +01:00
}
2021-05-16 17:34:38 +02:00
CStr CNetServer : : GetPublicIp ( ) const
{
return m_PublicIp ;
}
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
u16 CNetServer : : GetPublicPort ( ) const
{
return m_PublicPort ;
}
2021-05-16 17:34:38 +02:00
u16 CNetServer : : GetLocalPort ( ) const
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
{
2021-05-16 17:34:38 +02:00
std : : lock_guard < std : : mutex > lock ( m_Worker - > m_WorkerMutex ) ;
if ( ! m_Worker - > m_Host )
return 0 ;
return m_Worker - > m_Host - > address . port ;
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
}
2021-05-17 17:14:10 +02:00
void CNetServer : : SetConnectionData ( const CStr & ip , const u16 port )
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
{
m_PublicIp = ip ;
m_PublicPort = port ;
2021-05-17 17:14:10 +02:00
m_UseSTUN = false ;
}
bool CNetServer : : SetConnectionDataViaSTUN ( )
{
m_UseSTUN = true ;
std : : lock_guard < std : : mutex > lock ( m_Worker - > m_WorkerMutex ) ;
if ( ! m_Worker - > m_Host )
return false ;
return StunClient : : FindPublicIP ( * m_Worker - > m_Host , m_PublicIp , m_PublicPort ) ;
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
}
2021-05-18 16:47:36 +02:00
bool CNetServer : : CheckPasswordAndIncrement ( const std : : string & username , const std : : string & password , const std : : string & salt )
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
{
2021-01-26 21:20:48 +01:00
std : : unordered_map < std : : string , int > : : iterator it = m_FailedAttempts . find ( username ) ;
2021-05-18 16:47:36 +02:00
if ( m_Worker - > CheckPassword ( password , salt ) )
2021-01-26 21:20:48 +01:00
{
if ( it ! = m_FailedAttempts . end ( ) )
it - > second = 0 ;
return true ;
}
if ( it = = m_FailedAttempts . end ( ) )
m_FailedAttempts . emplace ( username , 1 ) ;
else
it - > second + + ;
return false ;
}
bool CNetServer : : IsBanned ( const std : : string & username ) const
{
std : : unordered_map < std : : string , int > : : const_iterator it = m_FailedAttempts . find ( username ) ;
return it ! = m_FailedAttempts . end ( ) & & it - > second > = FAILED_PASSWORD_TRIES_BEFORE_BAN ;
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
}
void CNetServer : : SetPassword ( const CStr & password )
{
m_Password = password ;
2021-01-23 19:04:36 +01:00
std : : lock_guard < std : : mutex > lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > SetPassword ( password ) ;
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 19:31:39 +01:00
}
2021-02-27 18:44:59 +01:00
void CNetServer : : SetControllerSecret ( const std : : string & secret )
{
std : : lock_guard < std : : mutex > lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > SetControllerSecret ( secret ) ;
}
2010-10-31 23:00:28 +01:00
void CNetServer : : StartGame ( )
{
2019-06-06 21:30:48 +02:00
std : : lock_guard < std : : mutex > lock ( m_Worker - > m_WorkerMutex ) ;
2010-10-31 23:00:28 +01:00
m_Worker - > m_StartGameQueue . push_back ( true ) ;
}
2021-05-14 12:18:03 +02:00
void CNetServer : : UpdateInitAttributes ( JS : : MutableHandleValue attrs , const ScriptRequest & rq )
2010-10-31 23:00:28 +01:00
{
// Pass the attributes as JSON, since that's the easiest safe
// cross-thread way of passing script data
2021-05-14 12:18:03 +02:00
std : : string attrsJSON = Script : : StringifyJSON ( rq , attrs , false ) ;
2010-10-31 23:00:28 +01:00
2019-06-06 21:30:48 +02:00
std : : lock_guard < std : : mutex > lock ( m_Worker - > m_WorkerMutex ) ;
2021-03-22 11:13:27 +01:00
m_Worker - > m_InitAttributesQueue . push_back ( attrsJSON ) ;
2010-10-31 23:00:28 +01:00
}
2018-03-12 01:23:40 +01:00
void CNetServer : : OnLobbyAuth ( const CStr & name , const CStr & token )
{
2019-06-06 21:30:48 +02:00
std : : lock_guard < std : : mutex > lock ( m_Worker - > m_WorkerMutex ) ;
2018-03-12 01:23:40 +01:00
m_Worker - > m_LobbyAuthQueue . push_back ( std : : make_pair ( name , token ) ) ;
}
2010-10-31 23:00:28 +01:00
void CNetServer : : SetTurnLength ( u32 msecs )
{
2019-06-06 21:30:48 +02:00
std : : lock_guard < std : : mutex > lock ( m_Worker - > m_WorkerMutex ) ;
2010-10-31 23:00:28 +01:00
m_Worker - > m_TurnLengthQueue . push_back ( msecs ) ;
}
STUN + XMPP ICE implementation.
Allows lobby players to host games without having to configure their
router.
Differential Revision: https://code.wildfiregames.com/D364
Fixes #2305
Patch By: fcxSanya.
StunClient based on code by SuperTuxKart, relicensed with approval of
the according authors hilnius, hiker, Auria, deveee, Flakebi, leper,
konstin and KroArtem.
Added rfc5245 (ejabberd) support, a GUI option, refactoring and segfault
fixes by myself.
Tested By: user1, Sandarac, Sestroretsk1714, Vladislav, Grugnas,
javiergodas
Partially Reviewed By: leper, Philip, echotangoecho
This was SVN commit r19703.
2017-06-01 08:33:52 +02:00
void CNetServer : : SendHolePunchingMessage ( const CStr & ip , u16 port )
{
m_Worker - > SendHolePunchingMessage ( ip , port ) ;
}