1
0
forked from 0ad/0ad

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.
This commit is contained in:
Angen 2021-01-20 18:31:39 +00:00
parent a30c4a69f7
commit 1a8de6d2b8
24 changed files with 672 additions and 164 deletions

View File

@ -76,6 +76,9 @@ function getDisconnectReason(id, wasConnected)
case 10: return translate("Error: Server failed to allocate a unique client identifier.");
case 11: return translate("Error: Client commands were ready for an unexpected game turn.");
case 12: return translate("Error: Client simulated an unexpected game turn.");
case 13: return translate("Password is invalid.");
case 14: return translate("Could not find an unused port for the enet STUN client.");
case 15: return translate("Could not find the STUN endpoint.");
default:
warn("Unknown disconnect-reason ID received: " + id);
return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id });

View File

@ -10,8 +10,7 @@ class GameRegisterStanza
this.mapCache = mapCache;
this.serverName = initData.serverName;
this.serverPort = initData.serverPort;
this.stunEndpoint = initData.stunEndpoint;
this.hasPassword = initData.hasPassword;
this.mods = JSON.stringify(Engine.GetEngineInfo().mods);
this.timer = undefined;
@ -83,7 +82,6 @@ class GameRegisterStanza
let stanza = {
"name": this.serverName,
"port": this.serverPort,
"hostUsername": Engine.LobbyGetNick(),
"mapName": g_GameAttributes.map,
"niceMapName": this.mapCache.getTranslatableMapName(g_GameAttributes.mapType, g_GameAttributes.map),
@ -93,9 +91,8 @@ class GameRegisterStanza
"nbp": clients.connectedPlayers,
"maxnbp": g_GameAttributes.settings.PlayerData.length,
"players": clients.list,
"stunIP": this.stunEndpoint ? this.stunEndpoint.ip : "",
"stunPort": this.stunEndpoint ? this.stunEndpoint.port : "",
"mods": this.mods
"mods": this.mods,
"hasPassword": this.hasPassword || ""
};
// Only send the stanza if one of these properties changed

View File

@ -14,20 +14,17 @@ var g_GameType;
var g_ServerName = "";
/**
* Cached to pass it to the game setup of the controller to report the game to the lobby.
* Identifier if server is using password.
*/
var g_ServerPort;
var g_ServerHasPassword = false;
var g_ServerId;
var g_IsRejoining = false;
var g_GameAttributes; // used when rejoining
var g_PlayerAssignments; // used when rejoining
var g_UserRating;
/**
* Object containing the IP address and port of the STUN server.
*/
var g_StunEndpoint;
function init(attribs)
{
g_UserRating = attribs.rating;
@ -36,19 +33,27 @@ function init(attribs)
{
case "join":
{
if (Engine.HasXmppClient())
if (!Engine.HasXmppClient())
{
if (startJoin(attribs.name, attribs.ip, getValidPort(attribs.port), attribs.useSTUN, attribs.hostJID))
switchSetupPage("pageConnecting");
}
else
switchSetupPage("pageJoin");
break;
}
if (attribs.hasPassword)
{
g_ServerName = attribs.name;
g_ServerId = attribs.hostJID;
switchSetupPage("pagePassword");
}
else if (startJoinFromLobby(attribs.name, attribs.hostJID, ""))
switchSetupPage("pageConnecting");
break;
}
case "host":
{
Engine.GetGUIObjectByName("hostSTUNWrapper").hidden = !Engine.HasXmppClient();
if (Engine.HasXmppClient())
let hasXmppClient = Engine.HasXmppClient();
Engine.GetGUIObjectByName("hostSTUNWrapper").hidden = !hasXmppClient;
Engine.GetGUIObjectByName("hostPasswordWrapper").hidden = !hasXmppClient;
if (hasXmppClient)
{
Engine.GetGUIObjectByName("hostPlayerName").caption = attribs.name;
Engine.GetGUIObjectByName("hostServerName").caption =
@ -92,6 +97,14 @@ function cancelSetup()
error("cancelSetup: Unrecognised multiplayer game type: " + g_GameType);
}
function confirmPassword()
{
if (Engine.GetGUIObjectByName("pagePassword").hidden)
return;
if (startJoinFromLobby(g_ServerName, g_ServerId, Engine.GetGUIObjectByName("clientPassword").caption))
switchSetupPage("pageConnecting");
}
function confirmSetup()
{
if (!Engine.GetGUIObjectByName("pageJoin").hidden)
@ -105,16 +118,14 @@ function confirmSetup()
}
else if (!Engine.GetGUIObjectByName("pageHost").hidden)
{
let hostPlayerName = Engine.GetGUIObjectByName("hostPlayerName").caption;
let hostServerName = Engine.GetGUIObjectByName("hostServerName").caption;
let hostPort = Engine.GetGUIObjectByName("hostPort").caption;
if (!hostServerName)
{
Engine.GetGUIObjectByName("hostFeedback").caption = translate("Please enter a valid server name.");
return;
}
let hostPort = Engine.GetGUIObjectByName("hostPort").caption;
if (getValidPort(hostPort) != +hostPort)
{
Engine.GetGUIObjectByName("hostFeedback").caption = sprintf(
@ -125,7 +136,9 @@ function confirmSetup()
return;
}
if (startHost(hostPlayerName, hostServerName, getValidPort(hostPort)))
let hostPlayerName = Engine.GetGUIObjectByName("hostPlayerName").caption;
let hostPassword = Engine.GetGUIObjectByName("hostPassword").caption;
if (startHost(hostPlayerName, hostServerName, getValidPort(hostPort), hostPassword))
switchSetupPage("pageConnecting");
}
}
@ -146,6 +159,28 @@ function onTick()
pollAndHandleNetworkClient();
}
function getConnectionFailReason(reason)
{
switch (reason)
{
case "not_server": return translate("Server is not running.");
case "invalid_password": return translate("Password is invalid.");
default:
warn("Unknown connection failure reason: " + reason);
return sprintf(translate("\\[Invalid value %(reason)s]"), { "reason": id });
}
}
function reportConnectionFail(reason)
{
messageBox(
400, 200,
(translate("Failed to connect to the server.")
) + "\n\n" + getConnectionFailReason(reason),
translate("Connection failed")
);
}
function pollAndHandleNetworkClient()
{
while (true)
@ -155,13 +190,27 @@ function pollAndHandleNetworkClient()
break;
log(sprintf(translate("Net message: %(message)s"), { "message": uneval(message) }));
// If we're rejoining an active game, we don't want to actually display
// the game setup screen, so perform similar processing to gamesetup.js
// in this screen
if (g_IsRejoining)
{
switch (message.type)
{
case "serverdata":
switch (message.status)
{
case "failed":
cancelSetup();
reportConnectionFail(message.reason, false);
return;
default:
error("Unrecognised netstatus type: " + message.status);
break;
}
break;
case "netstatus":
switch (message.status)
{
@ -211,11 +260,26 @@ function pollAndHandleNetworkClient()
default:
error("Unrecognised net message type: " + message.type);
}
}
else
// Not rejoining - just trying to connect to server
// Not rejoining - just trying to connect to server.
{
switch (message.type)
{
case "serverdata":
switch (message.status)
{
case "failed":
cancelSetup();
reportConnectionFail(message.reason, false);
return;
default:
error("Unrecognised netstatus type: " + message.status);
break;
}
break;
case "netstatus":
switch (message.status)
{
@ -232,8 +296,7 @@ function pollAndHandleNetworkClient()
}
Engine.SwitchGuiPage("page_gamesetup.xml", {
"serverName": g_ServerName,
"serverPort": g_ServerPort,
"stunEndpoint": g_StunEndpoint
"hasPassword": g_ServerHasPassword
});
return; // don't process any more messages - leave them for the game GUI loop
@ -256,6 +319,7 @@ function pollAndHandleNetworkClient()
break;
}
}
}
}
function switchSetupPage(newPage)
@ -273,16 +337,24 @@ function switchSetupPage(newPage)
pageSize.bottom = halfHeight;
multiplayerPages.size = pageSize;
}
else if (newPage == "pagePassword")
{
let pageSize = multiplayerPages.size;
let halfHeight = 60;
pageSize.top = -halfHeight;
pageSize.bottom = halfHeight;
multiplayerPages.size = pageSize;
}
Engine.GetGUIObjectByName(newPage).hidden = false;
Engine.GetGUIObjectByName("hostPlayerNameWrapper").hidden = Engine.HasXmppClient();
Engine.GetGUIObjectByName("hostServerNameWrapper").hidden = !Engine.HasXmppClient();
Engine.GetGUIObjectByName("continueButton").hidden = newPage == "pageConnecting";
Engine.GetGUIObjectByName("continueButton").hidden = newPage == "pageConnecting" || newPage == "pagePassword";
}
function startHost(playername, servername, port)
function startHost(playername, servername, port, password)
{
startConnectionStatus("server");
@ -301,20 +373,11 @@ function startHost(playername, servername, port)
return false;
}
if (Engine.HasXmppClient() && Engine.GetGUIObjectByName("useSTUN").checked)
{
g_StunEndpoint = Engine.FindStunEndpoint(port);
if (!g_StunEndpoint)
{
cancelSetup();
hostFeedback.caption = translate("Failed to host via STUN.");
return false;
}
}
let useSTUN = Engine.HasXmppClient() && Engine.GetGUIObjectByName("useSTUN").checked;
try
{
Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, playername);
Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, playername, useSTUN, password);
}
catch (e)
{
@ -328,7 +391,7 @@ function startHost(playername, servername, port)
}
g_ServerName = servername;
g_ServerPort = port;
g_ServerHasPassword = !!password;
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
@ -370,9 +433,49 @@ function startJoin(playername, ip, port, useSTUN, hostJID)
return true;
}
function startJoinFromLobby(playername, hostJID, password)
{
if (!Engine.HasXmppClient())
{
cancelSetup();
messageBox(
400, 200,
sprintf("You cannot join a lobby game without logging in to the lobby."),
translate("Error")
);
return false;
}
try
{
Engine.StartNetworkJoinLobby(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), hostJID, password);
}
catch (e)
{
cancelSetup();
messageBox(
400, 200,
sprintf(translate("Cannot join game: %(message)s."), { "message": e.message }),
translate("Error")
);
return false;
}
startConnectionStatus("client");
Engine.LobbySetPlayerPresence("playing");
return true;
}
function getDefaultGameName()
{
return sprintf(translate("%(playername)s's game"), {
"playername": multiplayerName()
});
}
function getDefaultPassword()
{
return "";
}

View File

@ -106,7 +106,21 @@
</object>
</object>
<object name="hostSTUNWrapper" size="120 106 100% 146">
<!-- Host password is only used on games started through the lobby. -->
<object name="hostPasswordWrapper" size="0 116 100% 126" hidden="true">
<object type="text" size="20 0 50% 22" style="ModernRightLabelText">
<translatableAttribute id="caption">Server Password:</translatableAttribute>
<translatableAttribute id="tooltip">Leave blank to not require it.</translatableAttribute>
</object>
<object name="hostPassword" type="input" size="50%+10 0 100%-20 22" style="ModernInput" mask="true" mask_char="*" max_length="256">
<action on="Load">
this.caption = getDefaultPassword();
</action>
</object>
</object>
<object name="hostSTUNWrapper" size="120 136 100% 180">
<object name="useSTUN" size="0 10 32 100%" type="checkbox" style="ModernTickBox">
<action on="Press">Engine.ConfigDB_CreateAndWriteValueToFile("user", "lobby.stun.enabled", String(this.checked), "config/user.cfg");</action>
</object>
@ -129,9 +143,26 @@
</object>
<object name="pageConnecting" hidden="true">
<object name="connectionStatus" type="text" style="ModernLabelText" size="0 100 100% 120"/>
<object name="connectionStatus" type="text" style="ModernLabelText" size="0 50%-10 100% 50%+10"/>
</object>
<object name="pagePassword" size="0 32 100% 100%" hidden="true">
<object name="clientPasswordWrapper" size="0 0 100% 40">
<object type="text" size="20 0 50% 22" style="ModernRightLabelText">
<translatableAttribute id="caption">Password:</translatableAttribute>
</object>
<object name="clientPassword" type="input" size="50%+10 0 100%-20 22" style="ModernInput" mask="true" mask_char="*" max_length="256">
<action on="Load">
this.caption = getDefaultPassword();
</action>
</object>
</object>
<object name="confirmPasswordButton" hotkey="confirm" type="button" size="50%+5 100%-45 100%-18 100%-17" style="ModernButtonRed">
<translatableAttribute id="caption">Confirm</translatableAttribute>
<action on="Press">confirmPassword();</action>
</object>
</object>
</object>
</objects>

View File

@ -67,38 +67,12 @@ class JoinButton
if (!game)
return;
let ip;
let port;
let stanza = game.stanza;
if (stanza.stunIP)
{
ip = stanza.stunIP;
port = stanza.stunPort;
}
else
{
ip = stanza.ip;
port = stanza.port;
}
if (ip.split('.').length != 4)
{
messageBox(
400, 250,
sprintf(
translate("This game's address '%(ip)s' does not appear to be valid."),
{ "ip": escapeText(stanza.ip) }),
translate("Error"));
return;
}
Engine.PushGuiPage("page_gamesetup_mp.xml", {
"multiplayerGameType": "join",
"ip": ip,
"port": port,
"name": g_Nickname,
"rating": this.getRejoinRating(stanza),
"useSTUN": !!stanza.stunIP,
"hasPassword": !!stanza.hasPassword,
"hostJID": stanza.hostUsername + "@" + Engine.ConfigDB_GetValue("user", "lobby.server") + "/0ad"
});
}

View File

@ -270,10 +270,7 @@ class Game
*/
Game.prototype.StanzaKeys = [
"name",
"ip",
"port",
"stunIP",
"stunPort",
"hasPassword",
"hostUsername",
"state",
"nbp",

View File

@ -97,13 +97,13 @@ class GameList
let newGames = {};
for (let stanza of gameListData)
{
let game = this.games[stanza.ip] || undefined;
let game = this.games[stanza.hostUsername] || undefined;
let exists = !!game;
if (!exists)
game = new Game(this.mapCache);
game.update(stanza, selectedColumn);
newGames[stanza.ip] = game;
newGames[stanza.hostUsername] = game;
}
this.games = newGames;
Engine.ProfileStop();
@ -157,8 +157,7 @@ class GameList
this.list_maxnbp[i] = displayData.playerCount;
this.list_gameRating[i] = game.gameRating;
this.list[i] = "";
if (selectedGame && game.stanza.ip == selectedGame.stanza.ip && game.stanza.port == selectedGame.stanza.port)
if (selectedGame && game.hostUsername == selectedGame.hostUsername && game.stanza.gameName == selectedGame.stanza.gameName)
selectedGameIndex = i;
});
Engine.ProfileStop();

View File

@ -594,6 +594,7 @@ function setup_all_libs ()
extern_libs = {
"spidermonkey",
"enet",
"sdl",
"boost", -- dragged in via server->simulation.h->random and NetSession.h->lockfree
"fmt",
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -39,6 +39,7 @@ public:
virtual void SendIqGetProfile(const std::string& player) = 0;
virtual void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0;
virtual void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0;
virtual void SendIqGetConnectionData(const std::string& jid, const std::string& password) = 0;
virtual void SendIqUnregisterGame() = 0;
virtual void SendIqChangeStateGame(const std::string& nbp, const std::string& players) = 0;
virtual void SendIqLobbyAuth(const std::string& to, const std::string& token) = 0;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2018 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -283,3 +283,71 @@ glooxwrapper::StanzaExtension* LobbyAuth::clone() const
{
return new LobbyAuth();
}
/******************************************************
* ConnectionData, a custom IQ Stanza, used to send and
* receive a ip and port of the server.
*/
ConnectionData::ConnectionData(const glooxwrapper::Tag* tag)
: StanzaExtension(EXTCONNECTIONDATA)
{
if (!tag || tag->name() != "connectiondata" || tag->xmlns() != XMLNS_CONNECTIONDATA)
return;
const glooxwrapper::Tag* c = tag->findTag_clone("connectiondata/ip");
if (c)
m_Ip = c->cdata();
const glooxwrapper::Tag* p= tag->findTag_clone("connectiondata/port");
if (p)
m_Port = p->cdata();
const glooxwrapper::Tag* s = tag->findTag_clone("connectiondata/useSTUN");
if (s)
m_UseSTUN = s->cdata();
const glooxwrapper::Tag* pw = tag->findTag_clone("connectiondata/password");
if (pw)
m_Password = pw->cdata();
const glooxwrapper::Tag* e = tag->findTag_clone("connectiondata/error");
if (e)
m_Error= e->cdata();
glooxwrapper::Tag::free(c);
glooxwrapper::Tag::free(p);
glooxwrapper::Tag::free(s);
glooxwrapper::Tag::free(pw);
glooxwrapper::Tag::free(e);
}
/**
* Required by gloox, used to find the LobbyAuth element in a received IQ.
*/
const glooxwrapper::string& ConnectionData::filterString() const
{
static const glooxwrapper::string filter = "/iq/connectiondata[@xmlns='" XMLNS_CONNECTIONDATA "']";
return filter;
}
/**
* Required by gloox, used to serialize the auth object into XML for sending.
*/
glooxwrapper::Tag* ConnectionData::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("connectiondata");
t->setXmlns(XMLNS_CONNECTIONDATA);
if (!m_Ip.empty())
t->addChild(glooxwrapper::Tag::allocate("ip", m_Ip));
if (!m_Port.empty())
t->addChild(glooxwrapper::Tag::allocate("port", m_Port));
if (!m_UseSTUN.empty())
t->addChild(glooxwrapper::Tag::allocate("useSTUN", m_UseSTUN));
if (!m_Password.empty())
t->addChild(glooxwrapper::Tag::allocate("password", m_Password));
if (!m_Error.empty())
t->addChild(glooxwrapper::Tag::allocate("error", m_Error));
return t;
}
glooxwrapper::StanzaExtension* ConnectionData::clone() const
{
return new ConnectionData();
}

View File

@ -41,6 +41,30 @@
#define EXTLOBBYAUTH 1407
#define XMLNS_LOBBYAUTH "jabber:iq:lobbyauth"
#define EXTCONNECTIONDATA 1408
#define XMLNS_CONNECTIONDATA "jabber:iq:connectiondata"
class ConnectionData : public glooxwrapper::StanzaExtension
{
public:
ConnectionData(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new ConnectionData(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
glooxwrapper::string m_Ip;
glooxwrapper::string m_Port;
glooxwrapper::string m_UseSTUN;
glooxwrapper::string m_Password;
glooxwrapper::string m_Error;
};
class GameReport : public glooxwrapper::StanzaExtension
{
public:

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -28,6 +28,7 @@
#include "lib/external_libraries/enet.h"
#include "lib/utf8.h"
#include "network/NetServer.h"
#include "network/NetClient.h"
#include "network/StunClient.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
@ -94,7 +95,9 @@ XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string
m_isConnected(false),
m_sessionManager(nullptr),
m_certStatus(gloox::CertStatus::CertOk),
m_PlayerMapUpdate(false)
m_PlayerMapUpdate(false),
m_connectionDataJid(),
m_connectionDataIqId()
{
if (m_ScriptInterface)
JS_AddExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), XmppClient::Trace, this);
@ -148,6 +151,9 @@ XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string
m_client->registerStanzaExtension(new LobbyAuth());
m_client->registerIqHandler(this, EXTLOBBYAUTH);
m_client->registerStanzaExtension(new ConnectionData());
m_client->registerIqHandler(this, EXTCONNECTIONDATA);
m_client->registerMessageHandler(this);
// Uncomment to see the raw stanzas
@ -361,6 +367,23 @@ void XmppClient::SendIqGetProfile(const std::string& player)
m_client->send(iq);
}
/**
* Request the Connection data (ip, port...) from the server.
*/
void XmppClient::SendIqGetConnectionData(const std::string& jid, const std::string& password)
{
glooxwrapper::JID targetJID(jid);
ConnectionData* connectionData = new ConnectionData();
connectionData->m_Password = password;
glooxwrapper::IQ iq(gloox::IQ::Get, targetJID, m_client->getID());
iq.addExtension(connectionData);
m_connectionDataJid = jid;
m_connectionDataIqId = iq.id().to_string();
DbgXMPP("SendIqGetConnectionData [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send game report containing numerous game properties to the server.
*
@ -573,7 +596,7 @@ void XmppClient::GUIGetGameList(const ScriptInterface& scriptInterface, JS::Muta
ScriptInterface::CreateArray(rq, ret);
int j = 0;
const char* stats[] = { "name", "ip", "port", "stunIP", "stunPort", "hostUsername", "state",
const char* stats[] = { "name", "hostUsername", "state", "hasPassword",
"nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType",
"victoryConditions", "startTime", "mods" };
@ -811,6 +834,27 @@ bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
const GameListQuery* gq = iq.findExtension<GameListQuery>(EXTGAMELISTQUERY);
const BoardListQuery* bq = iq.findExtension<BoardListQuery>(EXTBOARDLISTQUERY);
const ProfileQuery* pq = iq.findExtension<ProfileQuery>(EXTPROFILEQUERY);
const ConnectionData* cd = iq.findExtension<ConnectionData>(EXTCONNECTIONDATA);
if (cd)
{
if (g_NetServer || !g_NetClient)
return true;
if (!m_connectionDataJid.empty() && m_connectionDataJid.compare(iq.from().full()) != 0)
return true;
if (!m_connectionDataIqId.empty() && m_connectionDataIqId.compare(iq.id().to_string()) != 0)
return true;
if (!cd->m_Error.empty())
{
g_NetClient->HandleGetServerDataFailed(cd->m_Error.c_str());
return true;
}
g_NetClient->SetupServerData(cd->m_Ip.to_string(), stoi(cd->m_Port.to_string()), !cd->m_UseSTUN.empty());
g_NetClient->TryToConnect(iq.from().full());
}
if (gq)
{
for (const glooxwrapper::Tag* const& t : m_GameList)
@ -877,6 +921,47 @@ bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
LOGERROR("Received lobby authentication request, but not hosting currently!");
}
}
else if (iq.subtype() == gloox::IQ::Get)
{
const ConnectionData* cd = iq.findExtension<ConnectionData>(EXTCONNECTIONDATA);
if (cd)
{
LOGMESSAGE("XmppClient: Recieved request for connection data from %s", iq.from().username());
if (!g_NetServer)
{
glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
ConnectionData* connectionData = new ConnectionData();
connectionData->m_Error = "not_server";
response.addExtension(connectionData);
m_client->send(response);
return true;
}
if (!g_NetServer->CheckPassword(CStr(cd->m_Password.c_str())))
{
glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
ConnectionData* connectionData = new ConnectionData();
connectionData->m_Error = "invalid_password";
response.addExtension(connectionData);
m_client->send(response);
return true;
}
glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
ConnectionData* connectionData = new ConnectionData();
connectionData->m_Ip = g_NetServer->GetPublicIp();;
connectionData->m_Port = std::to_string(g_NetServer->GetPublicPort());
connectionData->m_UseSTUN = g_NetServer->GetUseSTUN() ? "true" : "";
response.addExtension(connectionData);
m_client->send(response);
}
}
else if (iq.subtype() == gloox::IQ::Error)
CreateGUIMessage("system", "error", std::time(nullptr), "text", iq.error_error());
else

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -55,6 +55,10 @@ private:
std::string m_xpartamuppId;
std::string m_echelonId;
// Security
std::string m_connectionDataJid;
std::string m_connectionDataIqId;
// State
gloox::CertStatus m_certStatus;
bool m_initialLoadComplete;
@ -82,6 +86,7 @@ public:
void SendIqGetProfile(const std::string& player);
void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data);
void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data);
void SendIqGetConnectionData(const std::string& jid, const std::string& password);
void SendIqUnregisterGame();
void SendIqChangeStateGame(const std::string& nbp, const std::string& players);
void SendIqLobbyAuth(const std::string& to, const std::string& token);

View File

@ -25,6 +25,7 @@
#include "lib/byte_order.h"
#include "lib/external_libraries/enet.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/sysdep/sysdep.h"
#include "lobby/IXmppClient.h"
#include "ps/CConsole.h"
@ -37,6 +38,7 @@
#include "ps/Threading.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "network/StunClient.h"
CNetClient *g_NetClient = NULL;
@ -76,6 +78,8 @@ CNetClient::CNetClient(CGame* game, bool isLocalClient) :
m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetGeneralJSContext()),
m_IsLocalClient(isLocalClient),
m_LastConnectionCheck(0),
m_ServerAddress(),
m_ServerPort(0),
m_Rejoin(false)
{
m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
@ -171,15 +175,102 @@ void CNetClient::SetHostingPlayerName(const CStr& hostingPlayerName)
m_HostingPlayerName = hostingPlayerName;
}
bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient)
bool CNetClient::SetupConnection(ENetHost* enetClient)
{
CNetClientSession* session = new CNetClientSession(*this);
bool ok = session->Connect(server, port, m_IsLocalClient, enetClient);
bool ok = session->Connect(m_ServerAddress, m_ServerPort, m_IsLocalClient, enetClient);
SetAndOwnSession(session);
m_PollingThread = std::thread(Threading::HandleExceptions<CNetClientSession::RunNetLoop>::Wrapper, m_Session);
return ok;
}
void CNetClient::SetupServerData(CStr address, u16 port, bool stun)
{
ENSURE(!m_Session);
m_ServerAddress = address;
m_ServerPort = port;
m_UseSTUN = stun;
}
void CNetClient::HandleGetServerDataFailed(const CStr& error)
{
if (m_Session)
return;
PushGuiMessage(
"type", "serverdata",
"status", "failed",
"reason", error
);
}
bool CNetClient::TryToConnect(const CStr& hostJID)
{
if (m_Session)
return false;
if (m_ServerAddress.empty())
{
PushGuiMessage(
"type", "netstatus",
"status", "disconnected",
"reason", static_cast<i32>(NDR_SERVER_REFUSED));
return false;
}
ENetHost* enetClient = nullptr;
if (g_XmppClient && m_UseSTUN)
{
// Find an unused port
for (int i = 0; i < 5 && !enetClient; ++i)
{
// Ports below 1024 are privileged on unix
u16 port = 1024 + rand() % (UINT16_MAX - 1024);
ENetAddress hostAddr{ ENET_HOST_ANY, port };
enetClient = enet_host_create(&hostAddr, 1, 1, 0, 0);
++hostAddr.port;
}
if (!enetClient)
{
PushGuiMessage(
"type", "netstatus",
"status", "disconnected",
"reason", static_cast<i32>(NDR_STUN_PORT_FAILED));
return false;
}
StunClient::StunEndpoint stunEndpoint;
if (!StunClient::FindStunEndpointJoin(*enetClient, stunEndpoint))
{
PushGuiMessage(
"type", "netstatus",
"status", "disconnected",
"reason", static_cast<i32>(NDR_STUN_ENDPOINT_FAILED));
return false;
}
g_XmppClient->SendStunEndpointToHost(stunEndpoint, hostJID);
SDL_Delay(1000);
StunClient::SendHolePunchingMessages(*enetClient, m_ServerAddress, m_ServerPort);
}
if (!g_NetClient->SetupConnection(enetClient))
{
PushGuiMessage(
"type", "netstatus",
"status", "disconnected",
"reason", static_cast<i32>(NDR_UNKNOWN));
return false;
}
return true;
}
void CNetClient::SetAndOwnSession(CNetClientSession* session)
{
delete m_Session;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -105,12 +105,25 @@ public:
*/
CStr GetGUID() const { return m_GUID; }
/**
* 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.
* @param server IP address or host name to connect to
* Must call SetupServerData first.
* @return true on success, false on connection failure
*/
bool SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient);
bool SetupConnection(ENetHost* enetClient);
/**
* Connect to the remote networked server using lobby.
* Push netstatus messages on failure.
* @return true on success, false on connection failure
*/
bool TryToConnect(const CStr& hostJID);
/**
* Destroy the connection to the server.
@ -232,6 +245,11 @@ public:
* @return Whether the NetClient is shutting down.
*/
bool ShouldShutdown() const;
/**
* Called when fetching connection data from the host failed, to inform JS code.
*/
void HandleGetServerDataFailed(const CStr& error);
private:
void SendAuthenticateMessage();
@ -271,6 +289,9 @@ private:
CGame *m_Game;
CStrW m_UserName;
CStr m_HostingPlayerName;
CStr m_ServerAddress;
u16 m_ServerPort;
bool m_UseSTUN;
/// Current network session (or NULL if not connected)
CNetClientSession* m_Session;

View File

@ -73,7 +73,10 @@ enum NetDisconnectReason
NDR_LOBBY_AUTH_FAILED,
NDR_GUID_FAILED,
NDR_INCORRECT_READY_TURN_COMMANDS,
NDR_INCORRECT_READY_TURN_SIMULATED
NDR_INCORRECT_READY_TURN_SIMULATED,
NDR_SERVER_REFUSED,
NDR_STUN_PORT_FAILED,
NDR_STUN_ENDPOINT_FAILED
};
class CNetHost

View File

@ -1573,7 +1573,7 @@ void CNetServerWorker::SendHolePunchingMessage(const CStr& ipStr, u16 port)
CNetServer::CNetServer(bool useLobbyAuth, int autostartPlayers) :
m_Worker(new CNetServerWorker(useLobbyAuth, autostartPlayers)),
m_LobbyAuth(useLobbyAuth)
m_LobbyAuth(useLobbyAuth), m_UseSTUN(false), m_PublicIp(""), m_PublicPort(20595), m_Password()
{
}
@ -1582,6 +1582,11 @@ CNetServer::~CNetServer()
delete m_Worker;
}
bool CNetServer::GetUseSTUN() const
{
return m_UseSTUN;
}
bool CNetServer::UseLobbyAuth() const
{
return m_LobbyAuth;
@ -1592,6 +1597,33 @@ bool CNetServer::SetupConnection(const u16 port)
return m_Worker->SetupConnection(port);
}
u16 CNetServer::GetPublicPort() const
{
return m_PublicPort;
}
CStr CNetServer::GetPublicIp() const
{
return m_PublicIp;
}
void CNetServer::SetConnectionData(const CStr& ip, const u16 port, bool useSTUN)
{
m_PublicIp = ip;
m_PublicPort = port;
m_UseSTUN = useSTUN;
}
bool CNetServer::CheckPassword(const CStr& password) const
{
return m_Password == password;
}
void CNetServer::SetPassword(const CStr& password)
{
m_Password = password;
}
void CNetServer::StartGame()
{
std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);

View File

@ -147,9 +147,25 @@ public:
void SendHolePunchingMessage(const CStr& ip, u16 port);
void SetConnectionData(const CStr& ip, u16 port, bool useSTUN);
bool GetUseSTUN() const;
CStr GetPublicIp() const;
u16 GetPublicPort() const;
bool CheckPassword(const CStr& password) const;
void SetPassword(const CStr& password);
private:
CNetServerWorker* m_Worker;
const bool m_LobbyAuth;
bool m_UseSTUN;
u16 m_PublicPort;
CStr m_PublicIp;
CStr m_Password;
};
/**

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* Copyright (C) 2013-2016 SuperTuxKart-Team.
* This file is part of 0 A.D.
*
@ -19,6 +19,7 @@
#include "precompiled.h"
#include "StunClient.h"
#include "scriptinterface/ScriptInterface.h"
#include <chrono>
#include <cstdio>
@ -46,6 +47,7 @@
#include "scriptinterface/ScriptInterface.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStr.h"
unsigned int m_StunServerIP;
int m_StunServerPort;
@ -369,32 +371,32 @@ bool STUNRequestAndResponse(ENetHost& transactionHost)
ParseStunResponse(buffer);
}
JS::Value StunClient::FindStunEndpointHost(const ScriptInterface& scriptInterface, int port)
bool StunClient::GetPublicIp(CStr8& ip, u16 port)
{
return FindStunEndpointHost(ip, port);
}
bool StunClient::FindStunEndpointHost(CStr8& ip, u16& port)
{
ENetAddress hostAddr{ENET_HOST_ANY, static_cast<u16>(port)};
ENetHost* transactionHost = enet_host_create(&hostAddr, 1, 1, 0, 0);
if (!transactionHost)
{
LOGERROR("FindStunEndpointHost: Failed to create enet host");
return JS::UndefinedValue();
}
return false;
bool success = STUNRequestAndResponse(*transactionHost);
enet_host_destroy(transactionHost);
if (!success)
return JS::UndefinedValue();
return false;
// Convert m_IP to string
char ipStr[256] = "(error)";
ENetAddress addr;
addr.host = ntohl(m_IP);
enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr));
int result = enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr));
ScriptRequest rq(scriptInterface);
JS::RootedValue stunEndpoint(rq.cx);
ScriptInterface::CreateObject(rq, &stunEndpoint, "ip", ipStr, "port", m_Port);
return stunEndpoint;
ip = ipStr;
port = m_Port;
return result == 0;
}
bool StunClient::FindStunEndpointJoin(ENetHost& transactionHost, StunClient::StunEndpoint& stunEndpoint)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* Copyright (C) 2013-2016 SuperTuxKart-Team.
* This file is part of 0 A.D.
*
@ -19,11 +19,11 @@
#ifndef STUNCLIENT_H
#define STUNCLIENT_H
#include "scriptinterface/ScriptInterface.h"
#include <string>
typedef struct _ENetHost ENetHost;
class ScriptInterface;
class CStr8;
namespace StunClient
{
@ -35,12 +35,13 @@ struct StunEndpoint {
void SendStunRequest(ENetHost& transactionHost, u32 targetIp, u16 targetPort);
JS::Value FindStunEndpointHost(const ScriptInterface& scriptInterface, int port);
bool FindStunEndpointHost(CStr8& ip, u16& port);
bool FindStunEndpointJoin(ENetHost& transactionHost, StunClient::StunEndpoint& stunEndpoint);
void SendHolePunchingMessages(ENetHost& enetClient, const std::string& serverAddress, u16 serverPort);
bool GetPublicIp(CStr8& ip, u16 port);
}
#endif // STUNCLIENT_H

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -29,8 +29,11 @@
#include "network/StunClient.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Util.h"
#include "scriptinterface/ScriptInterface.h"
#include "third_party/encryption/pkcs5_pbkdf2.h"
u16 JSI_Network::GetDefaultPort(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate))
{
return PS_DEFAULT_PORT;
@ -46,19 +49,76 @@ bool JSI_Network::HasNetClient(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate
return !!g_NetClient;
}
JS::Value JSI_Network::FindStunEndpoint(ScriptInterface::CmptPrivate* pCmptPrivate, int port)
CStr JSI_Network::HashPassword(const CStr& password)
{
return StunClient::FindStunEndpointHost(*(pCmptPrivate->pScriptInterface), port);
if (password.empty())
return password;
ENSURE(sodium_init() >= 0);
const int DIGESTSIZE = crypto_hash_sha256_BYTES;
constexpr int ITERATIONS = 1737;
cassert(DIGESTSIZE == 32);
static const unsigned char salt_base[DIGESTSIZE] = {
244, 243, 249, 244, 32, 33, 19, 35, 16, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 32, 33, 244, 224, 127, 129, 130, 140, 153, 88, 123, 234, 123 };
// initialize the salt buffer
unsigned char salt_buffer[DIGESTSIZE] = { 0 };
crypto_hash_sha256_state state;
crypto_hash_sha256_init(&state);
crypto_hash_sha256_update(&state, salt_base, sizeof(salt_base));
crypto_hash_sha256_final(&state, salt_buffer);
// PBKDF2 to create the buffer
unsigned char encrypted[DIGESTSIZE];
pbkdf2(encrypted, (unsigned char*)password.c_str(), password.length(), salt_buffer, DIGESTSIZE, ITERATIONS);
return CStr(Hexify(encrypted, DIGESTSIZE)).UpperCase();
}
void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName)
void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& password)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
// Always use lobby authentication for lobby matches to prevent impersonation and smurfing, in particular through mods that implemented an UI for arbitrary or other players nicknames.
g_NetServer = new CNetServer(!!g_XmppClient);
bool hasLobby = !!g_XmppClient;
g_NetServer = new CNetServer(hasLobby);
// In lobby, we send our public ip and port on request to the players, who want to connect.
// In both cases we need to ping stun server to get our public ip. If we want to host via stun,
// we need port as well.
if (hasLobby)
{
CStr ip;
if (!useSTUN)
{
if (!StunClient::GetPublicIp(ip, serverPort))
{
ScriptRequest rq(pCmptPrivate->pScriptInterface);
ScriptException::Raise(rq, "Failed to get public ip.");
SAFE_DELETE(g_NetServer);
return;
}
g_NetServer->SetConnectionData(ip, serverPort, false);
}
else
{
u16 port = serverPort;
// This is using port variable to store return value, do not pass serverPort itself.
if (!StunClient::FindStunEndpointHost(ip, port))
{
ScriptRequest rq(pCmptPrivate->pScriptInterface);
ScriptException::Raise(rq, "Failed to host via STUN.");
SAFE_DELETE(g_NetServer);
return;
}
g_NetServer->SetConnectionData(ip, port, true);
}
}
if (!g_NetServer->SetupConnection(serverPort))
{
ScriptRequest rq(pCmptPrivate->pScriptInterface);
@ -67,12 +127,16 @@ void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, c
return;
}
// We will get hashed password from clients, so hash it once for server
g_NetServer->SetPassword(HashPassword(password));
g_Game = new CGame(true);
g_NetClient = new CNetClient(g_Game, true);
g_NetClient->SetUserName(playerName);
g_NetClient->SetHostingPlayerName(hostLobbyName);
g_NetClient->SetupServerData("127.0.0.1", serverPort, false);
if (!g_NetClient->SetupConnection("127.0.0.1", serverPort, nullptr))
if (!g_NetClient->SetupConnection(nullptr))
{
ScriptRequest rq(pCmptPrivate->pScriptInterface);
ScriptException::Raise(rq, "Failed to connect to server");
@ -87,48 +151,13 @@ void JSI_Network::StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, c
ENSURE(!g_NetServer);
ENSURE(!g_Game);
ENetHost* enetClient = nullptr;
if (g_XmppClient && useSTUN)
{
// Find an unused port
for (int i = 0; i < 5 && !enetClient; ++i)
{
// Ports below 1024 are privileged on unix
u16 port = 1024 + rand() % (UINT16_MAX - 1024);
ENetAddress hostAddr{ENET_HOST_ANY, port};
enetClient = enet_host_create(&hostAddr, 1, 1, 0, 0);
++hostAddr.port;
}
if (!enetClient)
{
ScriptRequest rq(pCmptPrivate->pScriptInterface);
ScriptException::Raise(rq, "Could not find an unused port for the enet STUN client");
return;
}
StunClient::StunEndpoint stunEndpoint;
if (!StunClient::FindStunEndpointJoin(*enetClient, stunEndpoint))
{
ScriptRequest rq(pCmptPrivate->pScriptInterface);
ScriptException::Raise(rq, "Could not find the STUN endpoint");
return;
}
g_XmppClient->SendStunEndpointToHost(stunEndpoint, hostJID);
SDL_Delay(1000);
}
g_Game = new CGame(true);
g_NetClient = new CNetClient(g_Game, false);
g_NetClient->SetUserName(playerName);
g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@")));
g_NetClient->SetupServerData(serverAddress, serverPort, useSTUN);
if (g_XmppClient && useSTUN)
StunClient::SendHolePunchingMessages(*enetClient, serverAddress, serverPort);
if (!g_NetClient->SetupConnection(serverAddress, serverPort, enetClient))
if (!g_NetClient->SetupConnection(nullptr))
{
ScriptRequest rq(pCmptPrivate->pScriptInterface);
ScriptException::Raise(rq, "Failed to connect to server");
@ -137,6 +166,20 @@ void JSI_Network::StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, c
}
}
void JSI_Network::StartNetworkJoinLobby(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& hostJID, const CStr& password)
{
ENSURE(!!g_XmppClient);
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_Game = new CGame(true);
g_NetClient = new CNetClient(g_Game, false);
g_NetClient->SetUserName(playerName);
g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@")));
g_XmppClient->SendIqGetConnectionData(hostJID, HashPassword(password).c_str());
}
void JSI_Network::DisconnectNetworkGame(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate))
{
// TODO: we ought to do async reliable disconnections
@ -231,9 +274,9 @@ void JSI_Network::RegisterScriptFunctions(const ScriptInterface& scriptInterface
scriptInterface.RegisterFunction<u16, &GetDefaultPort>("GetDefaultPort");
scriptInterface.RegisterFunction<bool, &HasNetServer>("HasNetServer");
scriptInterface.RegisterFunction<bool, &HasNetClient>("HasNetClient");
scriptInterface.RegisterFunction<JS::Value, int, &FindStunEndpoint>("FindStunEndpoint");
scriptInterface.RegisterFunction<void, CStrW, u16, CStr, &StartNetworkHost>("StartNetworkHost");
scriptInterface.RegisterFunction<void, CStrW, u16, CStr, bool, CStr, &StartNetworkHost>("StartNetworkHost");
scriptInterface.RegisterFunction<void, CStrW, CStr, u16, bool, CStr, &StartNetworkJoin>("StartNetworkJoin");
scriptInterface.RegisterFunction<void, CStrW, CStr, CStr, &StartNetworkJoinLobby>("StartNetworkJoinLobby");
scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame");
scriptInterface.RegisterFunction<CStr, &GetPlayerGUID>("GetPlayerGUID");
scriptInterface.RegisterFunction<JS::Value, &PollNetworkClient>("PollNetworkClient");

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2018 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -29,9 +29,14 @@ namespace JSI_Network
bool HasNetClient(ScriptInterface::CmptPrivate* pCmptPrivate);
void StartNetworkGame(ScriptInterface::CmptPrivate* pCmptPrivate);
void SetNetworkGameAttributes(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue attribs1);
void StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName);
void StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& password);
void StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID);
JS::Value FindStunEndpoint(ScriptInterface::CmptPrivate* pCmptPrivate, int port);
/**
* Requires XmppClient to send iq request to the server to get server's ip and port based on passed password.
* This is needed to not force server to share it's public ip with all potential clients in the lobby.
* XmppClient will also handle logic after receiving the answer.
*/
void StartNetworkJoinLobby(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& hostJID, const CStr& password);
void DisconnectNetworkGame(ScriptInterface::CmptPrivate* pCmptPrivate);
JS::Value PollNetworkClient(ScriptInterface::CmptPrivate* pCmptPrivate);
CStr GetPlayerGUID(ScriptInterface::CmptPrivate* pCmptPrivate);
@ -42,6 +47,7 @@ namespace JSI_Network
void SendNetworkReady(ScriptInterface::CmptPrivate* pCmptPrivate, int message);
void SetTurnLength(ScriptInterface::CmptPrivate* pCmptPrivate, int length);
CStr HashPassword(const CStr& password);
void RegisterScriptFunctions(const ScriptInterface& scriptInterface);
}

View File

@ -74,8 +74,11 @@ public:
void connect(CNetServer& server, const std::vector<CNetClient*>& clients)
{
TS_ASSERT(server.SetupConnection(PS_DEFAULT_PORT));
for (size_t j = 0; j < clients.size(); ++j)
TS_ASSERT(clients[j]->SetupConnection("127.0.0.1", PS_DEFAULT_PORT, nullptr));
for (CNetClient* client: clients)
{
client->SetupServerData("127.0.0.1", PS_DEFAULT_PORT, false);
TS_ASSERT(client->SetupConnection(nullptr));
}
for (size_t i = 0; ; ++i)
{
@ -304,7 +307,8 @@ public:
client2B.SetUserName(L"bob");
clients.push_back(&client2B);
TS_ASSERT(client2B.SetupConnection("127.0.0.1", PS_DEFAULT_PORT, nullptr));
client2B.SetupServerData("127.0.0.1", PS_DEFAULT_PORT, false);
TS_ASSERT(client2B.SetupConnection(nullptr));
for (size_t i = 0; ; ++i)
{

View File

@ -1565,7 +1565,8 @@ bool Autostart(const CmdLineArgs& args)
g_NetClient = new CNetClient(g_Game, true);
g_NetClient->SetUserName(userName);
g_NetClient->SetupConnection("127.0.0.1", PS_DEFAULT_PORT, nullptr);
g_NetClient->SetupServerData("127.0.0.1", PS_DEFAULT_PORT, false);
g_NetClient->SetupConnection(nullptr);
}
else if (args.Has("autostart-client"))
{
@ -1578,8 +1579,8 @@ bool Autostart(const CmdLineArgs& args)
if (ip.empty())
ip = "127.0.0.1";
bool ok = g_NetClient->SetupConnection(ip, PS_DEFAULT_PORT, nullptr);
ENSURE(ok);
g_NetClient->SetupServerData(ip, PS_DEFAULT_PORT, false);
ENSURE(g_NetClient->SetupConnection(nullptr));
}
else
{