0ad/source/network/NetClient.h

355 lines
10 KiB
C
Raw Normal View History

/* Copyright (C) 2022 Wildfire Games.
* 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/>.
*/
#ifndef NETCLIENT_H
#define NETCLIENT_H
#include "network/FSM.h"
#include "network/NetFileTransfer.h"
#include "network/NetHost.h"
#include "scriptinterface/Object.h"
#include "ps/CStr.h"
#include <ctime>
#include <deque>
#include <thread>
class CGame;
class CNetClientSession;
class CNetClientTurnManager;
class ScriptInterface;
typedef struct _ENetHost ENetHost;
// NetClient session FSM states
enum
{
NCS_UNCONNECTED,
NCS_CONNECT,
NCS_HANDSHAKE,
NCS_AUTHENTICATE,
NCS_PREGAME,
NCS_LOADING,
NCS_JOIN_SYNCING,
NCS_INGAME
};
/**
* Network client.
* This code is run by every player (including the host, if they are not
* a dedicated server).
* It provides an interface between the GUI, the network (via CNetClientSession),
* and the game (via CGame and CNetClientTurnManager).
*/
class CNetClient : public CFsm
{
NONCOPYABLE(CNetClient);
friend class CNetFileReceiveTask_ClientRejoin;
public:
/**
* Construct a client associated with the given game object.
* The game must exist for the lifetime of this object.
*/
CNetClient(CGame* game);
virtual ~CNetClient();
/**
* We assume that adding a tracing function that's only called
* during GC is better for performance than using a
* PersistentRooted<T> where each value needs to be added to
* the root set.
*/
static void Trace(JSTracer *trc, void *data)
{
reinterpret_cast<CNetClient*>(data)->TraceMember(trc);
}
void TraceMember(JSTracer *trc);
/**
* Set the user's name that will be displayed to all players.
* This must not be called after the connection setup.
*/
void SetUserName(const CStrW& username);
/**
* Store the JID of the host.
* This is needed for the secure lobby authentication.
*/
void SetHostJID(const CStr& jid);
void SetControllerSecret(const std::string& secret);
bool IsController() const { return m_IsController; }
/**
* Set the game password.
* Must be called after SetUserName, as that is used to hash further.
*/
void SetGamePassword(const CStr& hashedPassword);
/**
* Returns the GUID of the local client.
* Used for distinguishing observers.
*/
CStr GetGUID() const { return m_GUID; }
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
/**
* Set connection data to the remote networked server.
* @param address IP address or host name to connect to
*/
void SetupServerData(CStr address, u16 port, bool stun);
/**
* Set up a connection to the remote networked server.
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
* Must call SetupServerData first.
* @return true on success, false on connection failure
*/
bool SetupConnection(ENetHost* enetClient);
/**
* Request connection information over the lobby.
*/
void SetupConnectionViaLobby();
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
/**
* Connect to the remote networked server using lobby.
* Push netstatus messages on failure.
* @param localNetwork - if true, assume we are trying to connect on the local network.
* @return true on success, false on connection failure
*/
bool TryToConnect(const CStr& hostJID, bool localNetwork);
/**
* Destroy the connection to the server.
* This client probably cannot be used again.
*/
void DestroyConnection();
/**
* Poll the connection for messages from the server and process them, and send
* any queued messages.
* This must be called frequently (i.e. once per frame).
*/
void Poll();
/**
* Locally triggers a GUI message if the connection to the server is being lost or has bad latency.
*/
void CheckServerConnection();
/**
* Retrieves the next queued GUI message, and removes it from the queue.
* The returned value is in the GetScriptInterface() JS context.
*
* This is the only mechanism for the networking code to send messages to
* the GUI - it is pull-based (instead of push) so the engine code does not
* need to know anything about the code structure of the GUI scripts.
*
* The structure of the messages is <code>{ "type": "...", ... }</code>.
* The exact types and associated data are not specified anywhere - the
* implementation and GUI scripts must make the same assumptions.
*
* @return next message, or the value 'undefined' if the queue is empty
*/
void GuiPoll(JS::MutableHandleValue);
/**
* Add a message to the queue, to be read by GuiPoll.
* The script value must be in the GetScriptInterface() JS context.
*/
template<typename... Args>
void PushGuiMessage(Args const&... args)
{
ScriptRequest rq(GetScriptInterface());
JS::RootedValue message(rq.cx);
Script::CreateObject(rq, &message, args...);
m_GuiMessageQueue.push_back(JS::Heap<JS::Value>(message));
}
/**
* Return a concatenation of all messages in the GUI queue,
* for test cases to easily verify the queue contents.
*/
std::string TestReadGuiMessages();
/**
* Get the script interface associated with this network client,
* which is equivalent to the one used by the CGame in the constructor.
*/
const ScriptInterface& GetScriptInterface();
/**
* Send a message to the server.
* @param message message to send
* @return true on success
*/
bool SendMessage(const CNetMessage* message);
/**
* Call when the network connection has been successfully initiated.
*/
void HandleConnect();
/**
* Call when the network connection has been lost.
*/
void HandleDisconnect(u32 reason);
/**
* Call when a message has been received from the network.
*/
bool HandleMessage(CNetMessage* message);
/**
* Call when the game has started and all data files have been loaded,
* to signal to the server that we are ready to begin the game.
*/
void LoadFinished();
void SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface);
void SendAssignPlayerMessage(const int playerID, const CStr& guid);
void SendChatMessage(const std::wstring& text);
void SendReadyMessage(const int status);
void SendClearAllReadyMessage();
void SendStartGameMessage(const CStr& initAttribs);
/**
* Call when the client has rejoined a running match and finished
* the loading screen.
*/
void SendRejoinedMessage();
/**
* Call to kick/ban a client
*/
void SendKickPlayerMessage(const CStrW& playerName, bool ban);
/**
* Call when the client has paused or unpaused the game.
*/
void SendPausedMessage(bool pause);
/**
* @return Whether the NetClient is shutting down.
*/
bool ShouldShutdown() 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
/**
* Called when fetching connection data from the host failed, to inform JS code.
*/
void HandleGetServerDataFailed(const CStr& error);
private:
void SendAuthenticateMessage();
// Net message / FSM transition handlers
static bool OnConnect(void* context, CFsmEvent* event);
static bool OnHandshake(void* context, CFsmEvent* event);
static bool OnHandshakeResponse(void* context, CFsmEvent* event);
static bool OnAuthenticateRequest(void* context, CFsmEvent* event);
static bool OnAuthenticate(void* context, CFsmEvent* event);
static bool OnChat(void* context, CFsmEvent* event);
static bool OnReady(void* context, CFsmEvent* event);
static bool OnGameSetup(void* context, CFsmEvent* event);
static bool OnPlayerAssignment(void* context, CFsmEvent* event);
static bool OnInGame(void* context, CFsmEvent* event);
static bool OnGameStart(void* context, CFsmEvent* event);
static bool OnJoinSyncStart(void* context, CFsmEvent* event);
static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event);
static bool OnRejoined(void* context, CFsmEvent* event);
static bool OnKicked(void* context, CFsmEvent* event);
static bool OnClientTimeout(void* context, CFsmEvent* event);
static bool OnClientPerformance(void* context, CFsmEvent* event);
static bool OnClientsLoading(void* context, CFsmEvent* event);
static bool OnClientPaused(void* context, CFsmEvent* event);
static bool OnLoadedGame(void* context, CFsmEvent* event);
/**
* Take ownership of a session object, and use it for all network communication.
*/
void SetAndOwnSession(CNetClientSession* session);
/**
* Push a message onto the GUI queue listing the current player assignments.
*/
void PostPlayerAssignmentsToScript();
CGame *m_Game;
CStrW m_UserName;
CStr m_HostJID;
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
CStr m_ServerAddress;
u16 m_ServerPort;
bool m_UseSTUN;
/**
* Password to join the game.
*/
CStr m_Password;
/// The 'secret' used to identify the controller of the game.
std::string m_ControllerSecret;
/// Note that this is just a "gui hint" with no actual impact on being controller.
bool m_IsController = false;
/// Current network session (or NULL if not connected)
CNetClientSession* m_Session;
std::thread m_PollingThread;
/// Turn manager associated with the current game (or NULL if we haven't started the game yet)
CNetClientTurnManager* m_ClientTurnManager;
/// Unique-per-game identifier of this client, used to identify the sender of simulation commands
u32 m_HostID;
/// True if the player is currently rejoining or has already rejoined the game.
bool m_Rejoin;
/// Latest copy of player assignments heard from the server
PlayerAssignmentMap m_PlayerAssignments;
/// Globally unique identifier to distinguish users beyond the lifetime of a single network session
CStr m_GUID;
/// Queue of messages for GuiPoll
std::deque<JS::Heap<JS::Value>> m_GuiMessageQueue;
/// Serialized game state received when joining an in-progress game
std::string m_JoinSyncBuffer;
/// Time when the server was last checked for timeouts and bad latency
std::time_t m_LastConnectionCheck;
};
/// Global network client for the standard game
extern CNetClient *g_NetClient;
#endif // NETCLIENT_H