Index: ps/trunk/binaries/data/mods/public/gui/common/network.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/network.js
+++ ps/trunk/binaries/data/mods/public/gui/common/network.js
@@ -76,6 +76,9 @@
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 });
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/GameRegisterStanza.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/GameRegisterStanza.js
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/GameRegisterStanza.js
@@ -10,8 +10,7 @@
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 @@
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 @@
"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
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js
@@ -14,20 +14,17 @@
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 @@
{
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
}
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
@@ -255,6 +318,7 @@
error("Unrecognised net message type: " + message.type);
break;
}
+ }
}
}
@@ -273,16 +337,24 @@
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 @@
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 @@
}
g_ServerName = servername;
- g_ServerPort = port;
+ g_ServerHasPassword = !!password;
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
@@ -370,9 +433,49 @@
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 "";
+}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
@@ -106,7 +106,21 @@
-
Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js
+++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js
@@ -67,38 +67,12 @@
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"
});
}
Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js
+++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js
@@ -270,10 +270,7 @@
*/
Game.prototype.StanzaKeys = [
"name",
- "ip",
- "port",
- "stunIP",
- "stunPort",
+ "hasPassword",
"hostUsername",
"state",
"nbp",
Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js
+++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js
@@ -97,13 +97,13 @@
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 @@
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();
Index: ps/trunk/build/premake/premake5.lua
===================================================================
--- ps/trunk/build/premake/premake5.lua
+++ ps/trunk/build/premake/premake5.lua
@@ -594,6 +594,7 @@
extern_libs = {
"spidermonkey",
"enet",
+ "sdl",
"boost", -- dragged in via server->simulation.h->random and NetSession.h->lockfree
"fmt",
}
Index: ps/trunk/source/lobby/IXmppClient.h
===================================================================
--- ps/trunk/source/lobby/IXmppClient.h
+++ ps/trunk/source/lobby/IXmppClient.h
@@ -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 @@
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;
Index: ps/trunk/source/lobby/StanzaExtensions.h
===================================================================
--- ps/trunk/source/lobby/StanzaExtensions.h
+++ ps/trunk/source/lobby/StanzaExtensions.h
@@ -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:
Index: ps/trunk/source/lobby/StanzaExtensions.cpp
===================================================================
--- ps/trunk/source/lobby/StanzaExtensions.cpp
+++ ps/trunk/source/lobby/StanzaExtensions.cpp
@@ -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 @@
{
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();
+}
Index: ps/trunk/source/lobby/XmppClient.h
===================================================================
--- ps/trunk/source/lobby/XmppClient.h
+++ ps/trunk/source/lobby/XmppClient.h
@@ -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 @@
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 @@
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);
Index: ps/trunk/source/lobby/XmppClient.cpp
===================================================================
--- ps/trunk/source/lobby/XmppClient.cpp
+++ ps/trunk/source/lobby/XmppClient.cpp
@@ -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 @@
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 @@
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
@@ -362,6 +368,23 @@
}
/**
+ * 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.
*
* @param data A JS array of game statistics
@@ -573,7 +596,7 @@
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 @@
const GameListQuery* gq = iq.findExtension(EXTGAMELISTQUERY);
const BoardListQuery* bq = iq.findExtension(EXTBOARDLISTQUERY);
const ProfileQuery* pq = iq.findExtension(EXTPROFILEQUERY);
+ const ConnectionData* cd = iq.findExtension(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 @@
LOGERROR("Received lobby authentication request, but not hosting currently!");
}
}
+ else if (iq.subtype() == gloox::IQ::Get)
+ {
+ const ConnectionData* cd = iq.findExtension(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
Index: ps/trunk/source/network/NetClient.h
===================================================================
--- ps/trunk/source/network/NetClient.h
+++ ps/trunk/source/network/NetClient.h
@@ -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
@@ -106,11 +106,24 @@
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(ENetHost* enetClient);
+
+ /**
+ * Connect to the remote networked server using lobby.
+ * Push netstatus messages on failure.
* @return true on success, false on connection failure
*/
- bool SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient);
+ bool TryToConnect(const CStr& hostJID);
/**
* Destroy the connection to the server.
@@ -232,6 +245,11 @@
* @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 @@
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;
Index: ps/trunk/source/network/NetClient.cpp
===================================================================
--- ps/trunk/source/network/NetClient.cpp
+++ ps/trunk/source/network/NetClient.cpp
@@ -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 @@
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 @@
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::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(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(NDR_STUN_PORT_FAILED));
+ return false;
+ }
+
+ StunClient::StunEndpoint stunEndpoint;
+ if (!StunClient::FindStunEndpointJoin(*enetClient, stunEndpoint))
+ {
+ PushGuiMessage(
+ "type", "netstatus",
+ "status", "disconnected",
+ "reason", static_cast(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(NDR_UNKNOWN));
+ return false;
+ }
+
+ return true;
+}
+
+
void CNetClient::SetAndOwnSession(CNetClientSession* session)
{
delete m_Session;
Index: ps/trunk/source/network/NetHost.h
===================================================================
--- ps/trunk/source/network/NetHost.h
+++ ps/trunk/source/network/NetHost.h
@@ -73,7 +73,10 @@
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
Index: ps/trunk/source/network/NetServer.h
===================================================================
--- ps/trunk/source/network/NetServer.h
+++ ps/trunk/source/network/NetServer.h
@@ -147,9 +147,25 @@
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;
};
/**
Index: ps/trunk/source/network/NetServer.cpp
===================================================================
--- ps/trunk/source/network/NetServer.cpp
+++ ps/trunk/source/network/NetServer.cpp
@@ -1573,7 +1573,7 @@
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 @@
delete m_Worker;
}
+bool CNetServer::GetUseSTUN() const
+{
+ return m_UseSTUN;
+}
+
bool CNetServer::UseLobbyAuth() const
{
return m_LobbyAuth;
@@ -1592,6 +1597,33 @@
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 lock(m_Worker->m_WorkerMutex);
Index: ps/trunk/source/network/StunClient.h
===================================================================
--- ps/trunk/source/network/StunClient.h
+++ ps/trunk/source/network/StunClient.h
@@ -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
typedef struct _ENetHost ENetHost;
+class ScriptInterface;
+class CStr8;
namespace StunClient
{
@@ -35,12 +35,13 @@
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
Index: ps/trunk/source/network/StunClient.cpp
===================================================================
--- ps/trunk/source/network/StunClient.cpp
+++ ps/trunk/source/network/StunClient.cpp
@@ -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
#include
@@ -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 @@
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(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));
-
- ScriptRequest rq(scriptInterface);
+ int result = enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr));
- 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)
Index: ps/trunk/source/network/scripting/JSInterface_Network.h
===================================================================
--- ps/trunk/source/network/scripting/JSInterface_Network.h
+++ ps/trunk/source/network/scripting/JSInterface_Network.h
@@ -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 @@
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 @@
void SendNetworkReady(ScriptInterface::CmptPrivate* pCmptPrivate, int message);
void SetTurnLength(ScriptInterface::CmptPrivate* pCmptPrivate, int length);
+ CStr HashPassword(const CStr& password);
void RegisterScriptFunctions(const ScriptInterface& scriptInterface);
}
Index: ps/trunk/source/network/scripting/JSInterface_Network.cpp
===================================================================
--- ps/trunk/source/network/scripting/JSInterface_Network.cpp
+++ ps/trunk/source/network/scripting/JSInterface_Network.cpp
@@ -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 @@
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 @@
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 @@
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::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 @@
scriptInterface.RegisterFunction("GetDefaultPort");
scriptInterface.RegisterFunction("HasNetServer");
scriptInterface.RegisterFunction("HasNetClient");
- scriptInterface.RegisterFunction("FindStunEndpoint");
- scriptInterface.RegisterFunction("StartNetworkHost");
+ scriptInterface.RegisterFunction("StartNetworkHost");
scriptInterface.RegisterFunction("StartNetworkJoin");
+ scriptInterface.RegisterFunction("StartNetworkJoinLobby");
scriptInterface.RegisterFunction("DisconnectNetworkGame");
scriptInterface.RegisterFunction("GetPlayerGUID");
scriptInterface.RegisterFunction("PollNetworkClient");
Index: ps/trunk/source/network/tests/test_Net.h
===================================================================
--- ps/trunk/source/network/tests/test_Net.h
+++ ps/trunk/source/network/tests/test_Net.h
@@ -74,8 +74,11 @@
void connect(CNetServer& server, const std::vector& 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 @@
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)
{
Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp
===================================================================
--- ps/trunk/source/ps/GameSetup/GameSetup.cpp
+++ ps/trunk/source/ps/GameSetup/GameSetup.cpp
@@ -1565,7 +1565,8 @@
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 @@
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
{