1
0
forked from 0ad/0ad

Don't hardcode the "0ad" resource into lobby XMPP & hosting

XMPP JID has a concept of 'resources', which can be used to
differentiate multiple clients of the same account.

We currently hardcode this 'resource' to '0ad' in two places:
- The 0 A.D. client always uses '0ad'
- The network code expects a host resource to be '0ad' when connecting.

As noted in 0fd8aa2a77#31215, it is less effort to store the JI
D directly. This patch does that. It also makes 0 A.D. use a different
resource each time.
Note that resources ought not contain particular information, as the
XMPP server is free to
 clobber it. I keep '0ad-' here for debug purposes.

This allows:
- multiple 0 A.D. instances to log on the lobby at the same time (not
massively useful, but good for debugging sometimes)
- hosting a game with a custom resource, which will potentially make it
easier to have dedi
cated servers on one account.

Note that hosting multiple games on one account is currently not
supported and will have weird behaviour on the lobbybots side. They
should be upgraded independently of this.

Refs #3556

Differential Revision: https://code.wildfiregames.com/D3500
This was SVN commit r25407.
This commit is contained in:
wraitii 2021-05-09 12:51:32 +00:00
parent 88810524b3
commit e94faf7827
14 changed files with 76 additions and 44 deletions

View File

@ -90,6 +90,7 @@ class LobbyGameRegistrationController
let stanza = {
"name": this.serverName,
"hostUsername": Engine.LobbyGetNick(),
"hostJID": "", // Overwritten by C++, placeholder.
"mapName": g_GameSettings.map.map,
// TODO: if the map name was always up-to-date we wouldn't need the mapcache here.
"niceMapName": this.mapCache.getTranslatableMapName(g_GameSettings.map.type, g_GameSettings.map.map),

View File

@ -112,7 +112,7 @@ function confirmSetup()
let joinServer = Engine.GetGUIObjectByName("joinServer").caption;
let joinPort = Engine.GetGUIObjectByName("joinPort").caption;
if (startJoin(joinPlayerName, joinServer, getValidPort(joinPort), false, ""))
if (startJoin(joinPlayerName, joinServer, getValidPort(joinPort)))
switchSetupPage("pageConnecting");
}
else if (!Engine.GetGUIObjectByName("pageHost").hidden)
@ -365,7 +365,7 @@ function startHost(playername, servername, port, password)
try
{
Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, playername, useSTUN, password);
Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, useSTUN, password);
}
catch (e)
{
@ -388,13 +388,13 @@ function startHost(playername, servername, port, password)
}
/**
* Connects via STUN if the hostJID is given.
* Connect via direct IP (used by the 'simple' MP screen)
*/
function startJoin(playername, ip, port, useSTUN, hostJID)
function startJoin(playername, ip, port)
{
try
{
Engine.StartNetworkJoin(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), ip, port, useSTUN, hostJID);
Engine.StartNetworkJoin(playername, ip, port);
}
catch (e)
{
@ -409,18 +409,20 @@ function startJoin(playername, ip, port, useSTUN, hostJID)
startConnectionStatus("client");
// Future-proofing: there could be an XMPP client even if we join a game directly.
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
else
{
// Only save the player name and host address if they're valid and we're not in the lobby
Engine.ConfigDB_CreateAndWriteValueToFile("user", "playername.multiplayer", playername, "config/user.cfg");
Engine.ConfigDB_CreateAndWriteValueToFile("user", "multiplayerserver", ip, "config/user.cfg");
Engine.ConfigDB_CreateAndWriteValueToFile("user", "multiplayerjoining.port", port, "config/user.cfg");
}
// Only save the player name and host address if they're valid.
Engine.ConfigDB_CreateAndWriteValueToFile("user", "playername.multiplayer", playername, "config/user.cfg");
Engine.ConfigDB_CreateAndWriteValueToFile("user", "multiplayerserver", ip, "config/user.cfg");
Engine.ConfigDB_CreateAndWriteValueToFile("user", "multiplayerjoining.port", port, "config/user.cfg");
return true;
}
/**
* Connect via the lobby.
*/
function startJoinFromLobby(playername, hostJID, password)
{
if (!Engine.HasXmppClient())

View File

@ -73,7 +73,7 @@ class JoinButton
"name": g_Nickname,
"rating": this.getRejoinRating(stanza),
"hasPassword": !!stanza.hasPassword,
"hostJID": stanza.hostUsername + "@" + Engine.ConfigDB_GetValue("user", "lobby.server") + "/0ad"
"hostJID": stanza.hostJID
});
}

View File

@ -282,6 +282,7 @@ Game.prototype.StanzaKeys = [
"name",
"hasPassword",
"hostUsername",
"hostJID",
"state",
"nbp",
"maxnbp",

View File

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

View File

@ -44,7 +44,8 @@ public:
virtual void SendIqChangeStateGame(const std::string& nbp, const std::string& players) = 0;
virtual void SendIqLobbyAuth(const std::string& to, const std::string& token) = 0;
virtual void SetNick(const std::string& nick) = 0;
virtual std::string GetNick() = 0;
virtual std::string GetNick() const = 0;
virtual std::string GetJID() const = 0;
virtual void kick(const std::string& nick, const std::string& reason) = 0;
virtual void ban(const std::string& nick, const std::string& reason) = 0;
virtual void SetPresence(const std::string& presence) = 0;

View File

@ -32,6 +32,7 @@
#include "network/StunClient.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/GUID.h"
#include "ps/Pyrogenesis.h"
#include "scriptinterface/ScriptExtraHeaders.h" // StructuredClone
#include "scriptinterface/ScriptInterface.h"
@ -109,7 +110,8 @@ XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string
m_xpartamuppId = sXpartamupp + "@" + m_server + "/CC";
m_echelonId = sEchelon + "@" + m_server + "/CC";
glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad");
// Generate a unique, unpredictable resource to allow multiple 0 A.D. instances to connect to the lobby.
glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad-" + ps_generate_guid());
glooxwrapper::JID roomJid(m_room + "@conference." + m_server + "/" + sNick);
// If we are connecting, use the full jid and a password
@ -425,7 +427,7 @@ void XmppClient::SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Setup some base stanza attributes
GameListQuery* g = new GameListQuery();
std::unique_ptr<GameListQuery> g = std::make_unique<GameListQuery>();
g->m_Command = "register";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
@ -434,17 +436,27 @@ void XmppClient::SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::
scriptInterface.EnumeratePropertyNames(data, true, properties);
for (const std::string& p : properties)
{
std::wstring value;
scriptInterface.GetProperty(data, p.c_str(), value);
game->addAttribute(p, utf8_from_wstring(value));
std::string value;
if (!scriptInterface.GetProperty(data, p.c_str(), value))
{
LOGERROR("Could not parse attribute '%s' as string.", p);
return;
}
game->addAttribute(p, value);
}
// Overwrite some attributes to make it slightly less trivial to do bad things,
// and explicit some invariants.
// The JID must point to ourself.
game->addAttribute("hostJID", GetJID());
// Push the stanza onto the IQ
g->m_GameList.emplace_back(game);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
iq.addExtension(g);
iq.addExtension(g.release());
DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
@ -504,7 +516,7 @@ void XmppClient::SendIqLobbyAuth(const std::string& to, const std::string& token
LobbyAuth* auth = new LobbyAuth();
auth->m_Token = token;
glooxwrapper::JID clientJid(to + "@" + m_server + "/0ad");
glooxwrapper::JID clientJid(to);
glooxwrapper::IQ iq(gloox::IQ::Set, clientJid, m_client->getID());
iq.addExtension(auth);
DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]");
@ -595,7 +607,7 @@ JS::Value XmppClient::GUIGetGameList(const ScriptInterface& scriptInterface)
ScriptInterface::CreateArray(rq, &ret);
int j = 0;
const char* stats[] = { "name", "hostUsername", "state", "hasPassword",
const char* stats[] = { "name", "hostUsername", "hostJID", "state", "hasPassword",
"nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType",
"victoryConditions", "startTime", "mods" };
@ -850,7 +862,7 @@ bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
}
if (!m_connectionDataIqId.empty() && m_connectionDataIqId.compare(iq.id().to_string()) != 0) {
LOGWARNING("XmppClient: Received connection data with invalid id");
LOGMESSAGE("XmppClient: Received connection data with invalid id");
return true;
}
@ -943,7 +955,7 @@ bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
if (g_NetServer)
g_NetServer->OnLobbyAuth(iq.from().username(), lobbyAuth->m_Token.to_string());
else
LOGERROR("Received lobby authentication request, but not hosting currently!");
LOGMESSAGE("Received lobby authentication request, but not hosting currently!");
}
}
else if (iq.subtype() == gloox::IQ::Get)
@ -1164,11 +1176,16 @@ void XmppClient::SetNick(const std::string& nick)
/**
* Get current nickname.
*/
std::string XmppClient::GetNick()
std::string XmppClient::GetNick() const
{
return m_mucRoom->nick().to_string();
}
std::string XmppClient::GetJID() const
{
return m_client->getJID().to_string();
}
/**
* Kick a player from the current room.
*

View File

@ -91,7 +91,8 @@ public:
void SendIqChangeStateGame(const std::string& nbp, const std::string& players);
void SendIqLobbyAuth(const std::string& to, const std::string& token);
void SetNick(const std::string& nick);
std::string GetNick();
std::string GetNick() const;
std::string GetJID() const;
void kick(const std::string& nick, const std::string& reason);
void ban(const std::string& nick, const std::string& reason);
void SetPresence(const std::string& presence);

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
@ -354,6 +354,11 @@ const glooxwrapper::string glooxwrapper::Client::getID() const
return m_Wrapped->getID();
}
const glooxwrapper::string glooxwrapper::Client::getJID() const
{
return m_Wrapped->jid().full();
}
void glooxwrapper::Client::send(const IQ& iq)
{
m_Wrapped->send(iq.getWrapped());

View File

@ -414,6 +414,7 @@ namespace glooxwrapper
bool connect(bool block = true);
gloox::ConnectionError recv(int timeout = -1);
const string getID() const;
const string getJID() const;
void send(const IQ& iq);
void setTls(gloox::TLSPolicy tls);

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
@ -218,6 +218,7 @@ void RegisterScriptFunctions(const ScriptRequest& rq)
REGISTER_XMPP(SetPresence, "LobbySetPlayerPresence");
REGISTER_XMPP(SetNick, "LobbySetNick");
REGISTER_XMPP(GetNick, "LobbyGetNick");
REGISTER_XMPP(GetJID, "LobbyGetJID");
REGISTER_XMPP(kick, "LobbyKick");
REGISTER_XMPP(ban, "LobbyBan");
REGISTER_XMPP(GetPresence, "LobbyGetPlayerPresence");

View File

@ -176,9 +176,9 @@ void CNetClient::SetUserName(const CStrW& username)
m_UserName = username;
}
void CNetClient::SetHostingPlayerName(const CStr& hostingPlayerName)
void CNetClient::SetHostJID(const CStr& jid)
{
m_HostingPlayerName = hostingPlayerName;
m_HostJID = jid;
}
void CNetClient::SetGamePassword(const CStr& hashedPassword)
@ -632,8 +632,8 @@ bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
if (message->m_Flags & PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH)
{
if (g_XmppClient && !client->m_HostingPlayerName.empty())
g_XmppClient->SendIqLobbyAuth(client->m_HostingPlayerName, client->m_GUID);
if (g_XmppClient && !client->m_HostJID.empty())
g_XmppClient->SendIqLobbyAuth(client->m_HostJID, client->m_GUID);
else
{
client->PushGuiMessage(

View File

@ -92,10 +92,10 @@ public:
void SetUserName(const CStrW& username);
/**
* Set the name of the hosting player.
* Store the JID of the host.
* This is needed for the secure lobby authentication.
*/
void SetHostingPlayerName(const CStr& hostingPlayerName);
void SetHostJID(const CStr& jid);
void SetControllerSecret(const std::string& secret);
@ -295,7 +295,8 @@ private:
CGame *m_Game;
CStrW m_UserName;
CStr m_HostingPlayerName;
CStr m_HostJID;
CStr m_ServerAddress;
u16 m_ServerPort;
bool m_UseSTUN;

View File

@ -87,7 +87,8 @@ CStr HashPassword(const CStr& password)
return CStr(Hexify(encrypted, DIGESTSIZE)).UpperCase();
}
void StartNetworkHost(const ScriptRequest& rq, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& password)
void StartNetworkHost(const ScriptRequest& rq, const CStrW& playerName, const u16 serverPort, bool useSTUN, const CStr& password)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
@ -138,7 +139,8 @@ void StartNetworkHost(const ScriptRequest& rq, const CStrW& playerName, const u1
g_Game = new CGame(true);
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
g_NetClient->SetHostingPlayerName(hostLobbyName);
if (hasLobby)
g_NetClient->SetHostJID(g_XmppClient->GetJID());
g_NetClient->SetGamePassword(hashedPass);
g_NetClient->SetupServerData("127.0.0.1", serverPort, false);
g_NetClient->SetControllerSecret(secret);
@ -151,7 +153,7 @@ void StartNetworkHost(const ScriptRequest& rq, const CStrW& playerName, const u1
}
}
void StartNetworkJoin(const ScriptRequest& rq, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID)
void StartNetworkJoin(const ScriptRequest& rq, const CStrW& playerName, const CStr& serverAddress, u16 serverPort)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
@ -160,8 +162,7 @@ void StartNetworkJoin(const ScriptRequest& rq, const CStrW& playerName, const CS
g_Game = new CGame(true);
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@")));
g_NetClient->SetupServerData(serverAddress, serverPort, useSTUN);
g_NetClient->SetupServerData(serverAddress, serverPort, false);
if (!g_NetClient->SetupConnection(nullptr))
{
@ -187,7 +188,7 @@ void StartNetworkJoinLobby(const CStrW& playerName, const CStr& hostJID, const C
g_Game = new CGame(true);
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@")));
g_NetClient->SetHostJID(hostJID);
g_NetClient->SetGamePassword(hashedPass);
g_XmppClient->SendIqGetConnectionData(hostJID, hashedPass.c_str());
}