Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg
+++ binaries/data/config/default.cfg
@@ -377,6 +377,11 @@
xpartamupp = "wfgbot22" ; Name of the server-side xmpp client that manage games
buddies = "," ; Comma separated list of playernames that the current user has marked as buddies
+[stun]
+enabled = true ; The STUN protocol allows hosting games without configuring the firewall and router.
+ ; If STUN is disabled, the game relies on direct connection, UPnP and port forwarding.
+server = "stun.ekiga.net" ; Address of STUN server
+
[mod]
enabledmods = "mod public"
Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js
===================================================================
--- binaries/data/mods/public/gui/gamesetup/gamesetup.js
+++ binaries/data/mods/public/gui/gamesetup/gamesetup.js
@@ -206,6 +206,11 @@
var g_ServerPort;
/**
+ * IP address of the STUN endpoint.
+ */
+var g_StunEndpoint;
+
+/**
* States whether the GUI is currently updated in response to network messages instead of user input
* and therefore shouldn't send further messages to the network.
*/
@@ -293,6 +298,11 @@
var g_LastViewedAIPlayer = -1;
/**
+ * Current username. Cannot contain whitespace.
+ */
+var g_Username = Engine.LobbyGetNick();
+
+/**
* Order in which the GUI elements will be shown.
* All valid options are required to appear here.
* The ones under "map" are shown in the map selection panel,
@@ -867,6 +877,7 @@
g_IsController = attribs.type != "client";
g_ServerName = attribs.serverName;
g_ServerPort = attribs.serverPort;
+ g_StunEndpoint = attribs.stunEndpoint;
if (!g_IsNetworked)
g_PlayerAssignments = {
@@ -2106,6 +2117,7 @@
let stanza = {
"name": g_ServerName,
"port": g_ServerPort,
+ "hostUsername": g_Username,
"mapName": g_GameAttributes.map,
"niceMapName": getMapDisplayName(g_GameAttributes.map),
"mapSize": g_GameAttributes.mapType == "random" ? g_GameAttributes.settings.Size : "Default",
@@ -2116,6 +2128,12 @@
"players": clients.list,
};
+ if (g_StunEndpoint !== undefined)
+ {
+ stanza.stunIP = g_StunEndpoint.ip;
+ stanza.stunPort = g_StunEndpoint.port;
+ }
+
// Only send the stanza if the relevant settings actually changed
if (g_LastGameStanza && Object.keys(stanza).every(prop => g_LastGameStanza[prop] == stanza[prop]))
return;
Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js
===================================================================
--- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js
+++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js
@@ -23,6 +23,11 @@
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;
@@ -33,7 +38,7 @@
{
if (Engine.HasXmppClient())
{
- if (startJoin(attribs.name, attribs.ip, getValidPort(attribs.port)))
+ if (startJoin(attribs.name, attribs.ip, getValidPort(attribs.port), attribs.hostJID))
switchSetupPage("pageConnecting");
}
else
@@ -47,6 +52,9 @@
Engine.GetGUIObjectByName("hostPlayerName").caption = attribs.name;
Engine.GetGUIObjectByName("hostServerName").caption =
sprintf(translate("%(name)s's game"), { "name": attribs.name });
+
+ Engine.GetGUIObjectByName("hostSTUNWrapper").hidden = false;
+ Engine.GetGUIObjectByName("useSTUN").checked = Engine.ConfigDB_GetValue("user", "stun.enabled") == "true";
}
switchSetupPage("pageHost");
@@ -230,7 +238,8 @@
Engine.SwitchGuiPage("page_gamesetup.xml", {
"type": g_GameType,
"serverName": g_ServerName,
- "serverPort": g_ServerPort
+ "serverPort": g_ServerPort,
+ "stunEndpoint": g_StunEndpoint
});
return; // don't process any more messages - leave them for the game GUI loop
}
@@ -271,6 +280,12 @@
Engine.GetGUIObjectByName("continueButton").hidden = newPage == "pageConnecting";
}
+function saveSTUNSetting(enabled)
+{
+ Engine.ConfigDB_CreateValue("user", "stun.enabled", enabled);
+ Engine.ConfigDB_WriteValueToFile("user", "stun.enabled", enabled, "config/user.cfg");
+}
+
function startHost(playername, servername, port)
{
startConnectionStatus("server");
@@ -293,6 +308,9 @@
return false;
}
+ if (Engine.HasXmppClient() && Engine.GetGUIObjectByName("useSTUN").checked)
+ g_StunEndpoint = Engine.FindStunEndpoint(port);
+
try
{
if (g_UserRating)
@@ -320,14 +338,11 @@
return true;
}
-function startJoin(playername, ip, port)
+function startJoin(playername, ip, port, hostJID)
{
try
{
- if (g_UserRating)
- Engine.StartNetworkJoin(playername + " (" + g_UserRating + ")", ip, port);
- else
- Engine.StartNetworkJoin(playername, ip, port);
+ Engine.StartNetworkJoin(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), ip, port, hostJID);
}
catch (e)
{
Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
===================================================================
--- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
+++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
@@ -94,7 +94,7 @@
+
+
+
+ saveSTUNSetting(String(this.checked));
+
+
+ Use STUN to work around firewalls
+
+
Index: binaries/data/mods/public/gui/lobby/lobby.js
===================================================================
--- binaries/data/mods/public/gui/lobby/lobby.js
+++ binaries/data/mods/public/gui/lobby/lobby.js
@@ -29,6 +29,17 @@
const g_Username = Engine.LobbyGetNick();
/**
+ * If STUN is enabled for both host and client, we are using it
+ * to discovered NAT-mapped host endpoint
+ */
+const g_StunEnabled = Engine.ConfigDB_GetValue("user", "stun.enabled") == "true";
+
+/**
+ * Lobby server address to construct host JID
+ */
+const g_LobbyServer = Engine.ConfigDB_GetValue("user", "lobby.server");
+
+/**
* Current games will be listed in these colors.
*/
const g_GameColors = {
@@ -988,7 +999,20 @@
if (!game)
return;
- if (game.ip.split('.').length != 4)
+ let ip;
+ let port;
+ if (g_StunEnabled && game.stunIP)
+ {
+ ip = game.stunIP;
+ port = game.stunPort;
+ }
+ else
+ {
+ ip = game.ip;
+ port = game.port;
+ }
+
+ if (ip.split('.').length != 4)
{
addChatMessage({
"from": "system",
@@ -1000,12 +1024,15 @@
return;
}
+ let hostJID = game.hostUsername + "@" + g_LobbyServer + "/0ad";
+
Engine.PushGuiPage("page_gamesetup_mp.xml", {
"multiplayerGameType": "join",
- "ip": game.ip,
- "port": game.port,
+ "ip": ip,
+ "port": port,
"name": g_Username,
- "rating": g_UserRating
+ "rating": g_UserRating,
+ "hostJID": hostJID
});
}
Index: build/premake/premake4.lua
===================================================================
--- build/premake/premake4.lua
+++ build/premake/premake4.lua
@@ -623,6 +623,7 @@
extern_libs = {
"spidermonkey",
"boost",
+ "enet",
"gloox",
"icu",
"iconv",
@@ -771,6 +772,7 @@
"sdl", -- key definitions
"opengl",
"boost",
+ "enet",
"tinygettext",
"icu",
"iconv",
Index: source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- source/gui/scripting/ScriptFunctions.cpp
+++ source/gui/scripting/ScriptFunctions.cpp
@@ -30,15 +30,18 @@
#include "gui/scripting/JSInterface_GUITypes.h"
#include "i18n/L10n.h"
#include "i18n/scripting/JSInterface_L10n.h"
+#include "lib/external_libraries/enet.h"
#include "lib/svn_revision.h"
#include "lib/sysdep/sysdep.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lobby/scripting/JSInterface_Lobby.h"
+#include "lobby/IXmppClient.h"
#include "maths/FixedVector3D.h"
#include "network/NetClient.h"
#include "network/NetMessage.h"
#include "network/NetServer.h"
+#include "network/StunClient.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/Errors.h"
@@ -237,6 +240,11 @@
return SavedGames::GetEngineInfo(*(pCxPrivate->pScriptInterface));
}
+JS::Value FindStunEndpoint(ScriptInterface::CxPrivate* pCxPrivate, int port)
+{
+ return StunClient::FindStunEndpoint(*(pCxPrivate->pScriptInterface), port);
+}
+
void StartNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_NetClient);
@@ -360,16 +368,45 @@
}
}
-void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort)
+void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, const std::string& hostJID)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
+ bool stunEnabled(false);
+ CFG_GET_VAL("stun.enabled", stunEnabled);
+ ENetHost* enetClient = nullptr;
+ if (stunEnabled)
+ {
+ // By default we are binding client to the same port as host (20595),
+ // if there are multiple 0ad instances running on the same machine
+ // (host + client or multiple STUN-enabled clients), this will fail
+ // (with enet_host_create returning null as a result),
+ // so we are also trying a couple of subsequent ports.
+ ENetAddress hostAddr{ENET_HOST_ANY, 20595};
+ for (int i = 0; i < 3 && enetClient == nullptr; ++i)
+ {
+ enetClient = enet_host_create(&hostAddr, 1, 1, 0, 0);
+ hostAddr.port++;
+ }
+
+ StunClient::StunEndpoint stunEndpoint = StunClient::FindStunEndpoint(enetClient);
+ g_XmppClient->SendStunEndpointToHost(stunEndpoint, hostJID);
+ // Note: we are sending endpoint and starting to connect right away
+ // we may actually need to wait for host's response (this needs
+ // to be checked)
+ SDL_Delay(1000);
+ }
+
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game, false);
g_NetClient->SetUserName(playerName);
- if (!g_NetClient->SetupConnection(serverAddress, serverPort))
+
+ if (stunEnabled)
+ StunClient::SendHolePunchingMessages(enetClient, serverAddress.c_str(), serverPort);
+
+ if (!g_NetClient->SetupConnection(serverAddress, serverPort, enetClient))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
@@ -1027,7 +1064,7 @@
scriptInterface.RegisterFunction("StartGame");
scriptInterface.RegisterFunction("EndGame");
scriptInterface.RegisterFunction("StartNetworkHost");
- scriptInterface.RegisterFunction("StartNetworkJoin");
+ scriptInterface.RegisterFunction("StartNetworkJoin");
scriptInterface.RegisterFunction("GetDefaultPort");
scriptInterface.RegisterFunction("DisconnectNetworkGame");
scriptInterface.RegisterFunction("GetPlayerGUID");
@@ -1040,6 +1077,7 @@
scriptInterface.RegisterFunction("SendNetworkReady");
scriptInterface.RegisterFunction("GetAIs");
scriptInterface.RegisterFunction("GetEngineInfo");
+ scriptInterface.RegisterFunction("FindStunEndpoint");
// Saved games
scriptInterface.RegisterFunction("StartSavedGame");
Index: source/lobby/IXmppClient.h
===================================================================
--- source/lobby/IXmppClient.h
+++ source/lobby/IXmppClient.h
@@ -21,6 +21,9 @@
#include "scriptinterface/ScriptTypes.h"
class ScriptInterface;
+namespace StunClient {
+class StunEndpoint;
+}
class IXmppClient
{
@@ -54,6 +57,8 @@
virtual void GuiPollMessage(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void SendMUCMessage(const std::string& message) = 0;
+
+ virtual void SendStunEndpointToHost(StunClient::StunEndpoint& stunEndpoint, const std::string& hostJID) = 0;
};
extern IXmppClient *g_XmppClient;
Index: source/lobby/XmppClient.h
===================================================================
--- source/lobby/XmppClient.h
+++ source/lobby/XmppClient.h
@@ -33,7 +33,7 @@
struct CertInfo;
}
-class XmppClient : public IXmppClient, public glooxwrapper::ConnectionListener, public glooxwrapper::MUCRoomHandler, public glooxwrapper::IqHandler, public glooxwrapper::RegistrationHandler, public glooxwrapper::MessageHandler
+class XmppClient : public IXmppClient, public glooxwrapper::ConnectionListener, public glooxwrapper::MUCRoomHandler, public glooxwrapper::IqHandler, public glooxwrapper::RegistrationHandler, public glooxwrapper::MessageHandler, public glooxwrapper::Jingle::SessionHandler
{
NONCOPYABLE(XmppClient);
@@ -42,6 +42,7 @@
glooxwrapper::Client* m_client;
glooxwrapper::MUCRoom* m_mucRoom;
glooxwrapper::Registration* m_registration;
+ glooxwrapper::SessionManager* m_sessionManager;
// Account infos
std::string m_username;
@@ -81,6 +82,8 @@
void GUIGetBoardList(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void GUIGetProfile(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
+ void SendStunEndpointToHost(StunClient::StunEndpoint& stunEndpoint, const std::string& hostJID);
+
//Script
ScriptInterface& GetScriptInterface();
@@ -119,6 +122,9 @@
/* Message Handler */
virtual void handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession * session);
+ /* Session Handler */
+ virtual void handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session *UNUSED(session), const glooxwrapper::Jingle::Session::Jingle *jingle);
+
// Helpers
void GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const;
void GetRoleString(const gloox::MUCRoomRole r, std::string& role) const;
@@ -126,6 +132,8 @@
std::string ConnectionErrorToString(gloox::ConnectionError err) const;
std::string RegistrationResultToString(gloox::RegistrationResult res) const;
+ void ProcessJingleData(const glooxwrapper::Jingle::Session::Jingle *jingle);
+
public:
/* Messages */
struct GUIMessage
Index: source/lobby/XmppClient.cpp
===================================================================
--- source/lobby/XmppClient.cpp
+++ source/lobby/XmppClient.cpp
@@ -19,9 +19,16 @@
#include "XmppClient.h"
#include "StanzaExtensions.h"
+#ifdef WIN32
+# include
+#endif
+
#include "glooxwrapper/glooxwrapper.h"
#include "i18n/L10n.h"
+#include "lib/external_libraries/enet.h"
#include "lib/utf8.h"
+#include "network/NetServer.h"
+#include "network/StunClient.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Pyrogenesis.h"
@@ -68,7 +75,7 @@
* @param regOpt If we are just registering or not.
*/
XmppClient::XmppClient(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize, bool regOpt)
- : m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_nick(sNick), m_initialLoadComplete(false)
+ : m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_nick(sNick), m_initialLoadComplete(false), m_sessionManager()
{
// Read lobby configuration from default.cfg
std::string sServer;
@@ -129,6 +136,11 @@
m_registration = new glooxwrapper::Registration(m_client);
m_registration->registerRegistrationHandler(this);
}
+
+ m_sessionManager = new glooxwrapper::SessionManager(m_client, this);
+ // Register plugins to allow gloox parse them in incoming sessions
+ m_sessionManager->registerPlugins();
+
}
/**
@@ -472,7 +484,7 @@
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
- const char* stats[] = { "name", "ip", "port", "state", "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition", "startTime" };
+ const char* stats[] = { "name", "ip", "port", "stunIP", "stunPort", "hostUsername", "state", "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition", "startTime" };
for(const glooxwrapper::Tag* const& t : m_GameList)
{
JS::RootedValue game(cx);
@@ -1084,3 +1096,31 @@
#undef DEBUG_CASE
#undef CASE
}
+
+
+void XmppClient::SendStunEndpointToHost(StunClient::StunEndpoint& stunEndpoint, const std::string& hostJIDStr)
+{
+ glooxwrapper::JID hostJID(hostJIDStr);
+ glooxwrapper::Jingle::Session session = m_sessionManager->createSession(hostJID);
+
+ char ipStr[256] = "(error)";
+ ENetAddress addr;
+ addr.host = ntohl(stunEndpoint.ip);
+ enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr));
+
+ session.sessionInitiate(ipStr, stunEndpoint.port);
+}
+
+void XmppClient::handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session *UNUSED(session), const glooxwrapper::Jingle::Session::Jingle *jingle)
+{
+ if (action != gloox::Jingle::SessionInitiate)
+ return;
+
+ ProcessJingleData(jingle);
+}
+
+void XmppClient::ProcessJingleData(const glooxwrapper::Jingle::Session::Jingle *jingle)
+{
+ glooxwrapper::Jingle::ICEUDP::Candidate candidate = jingle->getCandidate();
+ g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port);
+}
Index: source/lobby/glooxwrapper/glooxwrapper.h
===================================================================
--- source/lobby/glooxwrapper/glooxwrapper.h
+++ source/lobby/glooxwrapper/glooxwrapper.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Wildfire Games.
+/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -72,6 +72,10 @@
#include
#include
#include
+#include
+#include
+#include
+#include
#include
@@ -101,6 +105,7 @@
class ClientImpl;
class MUCRoomHandlerWrapper;
+ class SessionHandlerWrapper;
GLOOXWRAPPER_API void* glooxwrapper_alloc(size_t size);
GLOOXWRAPPER_API void glooxwrapper_free(void* p);
@@ -256,6 +261,11 @@
}
m_Head = m_Tail = NULL;
}
+
+ const T& front() const
+ {
+ return *begin();
+ }
};
typedef glooxwrapper::list TagList;
@@ -571,6 +581,98 @@
const Tag* findTag_clone(const string& expression) const; // like findTag but must be Tag::free()d
ConstTagList findTagList_clone(const string& expression) const; // like findTagList but each tag must be Tag::free()d
};
+
+ namespace Jingle
+ {
+
+ class GLOOXWRAPPER_API Plugin
+ {
+ protected:
+ const gloox::Jingle::Plugin* m_Wrapped;
+ bool m_Owned;
+
+ public:
+ Plugin(const gloox::Jingle::Plugin* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {}
+
+ virtual ~Plugin();
+
+ const Plugin findPlugin(int type) const;
+ const gloox::Jingle::Plugin* getWrapped() const { return m_Wrapped; }
+ };
+
+ typedef list PluginList;
+
+ class GLOOXWRAPPER_API Content : public Plugin
+ {
+ public:
+ Content(const string& name, const PluginList& plugins);
+ Content();
+ };
+
+ class GLOOXWRAPPER_API ICEUDP : public Plugin
+ {
+ public:
+ struct Candidate {
+ string ip;
+ int port;
+ };
+
+ typedef std::list CandidateList;
+
+ ICEUDP(CandidateList& candidates);
+ ICEUDP();
+
+ const CandidateList candidates() const;
+ };
+
+ class GLOOXWRAPPER_API Session
+ {
+ protected:
+ gloox::Jingle::Session* m_Wrapped;
+ bool m_Owned;
+
+ public:
+ class GLOOXWRAPPER_API Jingle
+ {
+ private:
+ const gloox::Jingle::Session::Jingle* m_Wrapped;
+ bool m_Owned;
+ public:
+ Jingle(const gloox::Jingle::Session::Jingle* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {}
+
+ const PluginList plugins() const;
+
+ ICEUDP::Candidate getCandidate() const;
+ };
+
+ Session(gloox::Jingle::Session* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {}
+
+ bool sessionInitiate(char* ipStr, uint16_t port);
+ };
+
+ class GLOOXWRAPPER_API SessionHandler
+ {
+ public:
+ virtual ~SessionHandler() {}
+ virtual void handleSessionAction(gloox::Jingle::Action action, Session *session, const Session::Jingle *jingle) = 0;
+ };
+
+ }
+
+ class GLOOXWRAPPER_API SessionManager
+ {
+ private:
+ gloox::Jingle::SessionManager* m_Wrapped;
+ SessionHandlerWrapper* m_HandlerWrapper;
+
+ public:
+ SessionManager(Client* parent, Jingle::SessionHandler* sh);
+ ~SessionManager();
+ void registerPlugins();
+ Jingle::Session createSession(const JID& callee);
+ };
+
+
}
#endif // INCLUDED_GLOOXWRAPPER_H
Index: source/lobby/glooxwrapper/glooxwrapper.cpp
===================================================================
--- source/lobby/glooxwrapper/glooxwrapper.cpp
+++ source/lobby/glooxwrapper/glooxwrapper.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -277,6 +277,29 @@
}
};
+class SessionHandlerWrapper : public gloox::Jingle::SessionHandler
+{
+public:
+ glooxwrapper::Jingle::SessionHandler* m_Wrapped;
+ bool m_Owned;
+
+ SessionHandlerWrapper(glooxwrapper::Jingle::SessionHandler* wrapped, bool owned)
+ : m_Wrapped(wrapped), m_Owned(owned) {}
+
+ virtual void handleSessionAction(gloox::Jingle::Action action, gloox::Jingle::Session* session, const gloox::Jingle::Session::Jingle* jingle)
+ {
+ m_Wrapped->handleSessionAction(action, new glooxwrapper::Jingle::Session(session, false), new glooxwrapper::Jingle::Session::Jingle(jingle, false));
+ }
+
+ virtual void handleSessionActionError(gloox::Jingle::Action UNUSED(action), gloox::Jingle::Session* UNUSED(session), const gloox::Error* UNUSED(error))
+ {
+ }
+
+ virtual void handleIncomingSession(gloox::Jingle::Session* UNUSED(session))
+ {
+ }
+};
+
class ClientImpl
{
public:
@@ -286,6 +309,39 @@
std::list > m_IqHandlers;
};
+static const std::string XMLNS = "xmlns";
+static const std::string XMLNS_JINGLE_0AD_GAME = "urn:xmpp:jingle:apps:0ad-game:1";
+
+class ZeroADGameData : public gloox::Jingle::Plugin
+{
+public:
+ ZeroADGameData() : Plugin(gloox::Jingle::PluginUser) {}
+
+ ZeroADGameData(const gloox::Tag* UNUSED(tag)) : Plugin(gloox::Jingle::PluginUser)
+ {
+ }
+
+ const std::string& filterString() const {
+ static const std::string filter = "content/description[@xmlns='" + XMLNS_JINGLE_0AD_GAME + "']";
+ return filter;
+ }
+
+ gloox::Tag* tag() const {
+ gloox::Tag* r = new gloox::Tag("description", XMLNS, XMLNS_JINGLE_0AD_GAME);
+ return r;
+ }
+
+ Plugin* newInstance(const gloox::Tag* tag) const
+ {
+ return new ZeroADGameData(tag);
+ }
+
+ Plugin* clone() const
+ {
+ return new ZeroADGameData(*this);
+ }
+};
+
} // namespace glooxwrapper
@@ -737,3 +793,163 @@
tagListWrapper.push_back(new glooxwrapper::Tag(const_cast(t), false));
return tagListWrapper;
}
+
+glooxwrapper::Jingle::Plugin::~Plugin()
+{
+ if (m_Owned)
+ delete m_Wrapped;
+}
+
+const glooxwrapper::Jingle::Plugin glooxwrapper::Jingle::Plugin::findPlugin(int type) const
+{
+ return glooxwrapper::Jingle::Plugin(m_Wrapped->findPlugin(type), false);
+}
+
+glooxwrapper::Jingle::Content::Content(const string& name, const PluginList& plugins)
+ : glooxwrapper::Jingle::Plugin(NULL, false)
+{
+ gloox::Jingle::PluginList glooxPluginList;
+ for (const glooxwrapper::Jingle::Plugin* const& plugin: plugins)
+ glooxPluginList.push_back(plugin->getWrapped());
+
+ m_Wrapped = new gloox::Jingle::Content(name.to_string(), glooxPluginList);
+ m_Owned = true;
+}
+
+glooxwrapper::Jingle::Content::Content()
+ : glooxwrapper::Jingle::Plugin(NULL, false)
+{
+ m_Wrapped = new gloox::Jingle::Content();
+ m_Owned = true;
+}
+
+const glooxwrapper::Jingle::PluginList glooxwrapper::Jingle::Session::Jingle::plugins() const
+{
+ glooxwrapper::Jingle::PluginList pluginListWrapper;
+ for (const gloox::Jingle::Plugin* const& plugin : m_Wrapped->plugins())
+ pluginListWrapper.push_back(new glooxwrapper::Jingle::Plugin(const_cast(plugin), false));
+ return pluginListWrapper;
+}
+
+glooxwrapper::Jingle::ICEUDP::Candidate glooxwrapper::Jingle::Session::Jingle::getCandidate() const
+{
+ const gloox::Jingle::Content *content = static_cast(m_Wrapped->plugins().front());
+ if (content == NULL)
+ {
+ printf("Failed to retrieve Jingle content\n");
+ return glooxwrapper::Jingle::ICEUDP::Candidate();
+ }
+
+ const ZeroADGameData *gameData = static_cast(content->findPlugin(gloox::Jingle::PluginUser));
+ if (gameData == NULL)
+ {
+ printf("Failed to retrieve Jingle game data\n");
+ return glooxwrapper::Jingle::ICEUDP::Candidate();
+ }
+
+ const gloox::Jingle::ICEUDP *iceUDP = static_cast(content->findPlugin(gloox::Jingle::PluginICEUDP));
+ if (iceUDP == NULL)
+ {
+ printf("Failed to retrieve Jingle ICE-UDP data\n");
+ return glooxwrapper::Jingle::ICEUDP::Candidate();
+ }
+
+ gloox::Jingle::ICEUDP::Candidate glooxCandidate = iceUDP->candidates().front();
+ return glooxwrapper::Jingle::ICEUDP::Candidate{glooxCandidate.ip, glooxCandidate.port};
+}
+
+bool glooxwrapper::Jingle::Session::sessionInitiate(char* ipStr, uint16_t port)
+{
+ ZeroADGameData *gameData = new ZeroADGameData();
+
+ gloox::Jingle::ICEUDP::CandidateList *candidateList = new gloox::Jingle::ICEUDP::CandidateList();
+
+ candidateList->push_back(gloox::Jingle::ICEUDP::Candidate
+ {
+ /*component_id*/ "1",
+ /*foundation*/ "1",
+ /*candidate_generation*/ "0",
+ /*candidate_id*/ "1",
+ ipStr,
+ /*network*/ "",
+ port,
+ /*priotiry*/ 0,
+ "udp",
+ /*base_ip*/ "",
+ /*base_port*/ 0,
+ /*type*/ gloox::Jingle::ICEUDP::ServerReflexive
+ });
+
+ gloox::Jingle::ICEUDP *iceUDP = new gloox::Jingle::ICEUDP(/*local_pwd*/"", /*local_ufrag*/"", *candidateList);
+
+ gloox::Jingle::PluginList *pluginList = new gloox::Jingle::PluginList();
+ pluginList->push_back(gameData);
+ pluginList->push_back(iceUDP);
+ gloox::Jingle::Content *content = new gloox::Jingle::Content(std::string("game-data"), *pluginList);
+
+ return m_Wrapped->sessionInitiate(content);
+}
+
+glooxwrapper::Jingle::ICEUDP::ICEUDP(glooxwrapper::Jingle::ICEUDP::CandidateList& candidates)
+ : glooxwrapper::Jingle::Plugin(NULL, false)
+{
+ gloox::Jingle::ICEUDP::CandidateList glooxCandidates;
+ for (const glooxwrapper::Jingle::ICEUDP::Candidate candidate : candidates)
+ glooxCandidates.push_back(gloox::Jingle::ICEUDP::Candidate
+ {
+ /*component_id*/ "1",
+ /*foundation*/ "1",
+ /*candidate_generation*/ "0",
+ /*candidate_id*/ "1",
+ candidate.ip.to_string(),
+ /*network*/ "",
+ candidate.port,
+ /*priotiry*/0, "udp",
+ /*base_ip*/ "",
+ /*base_port*/ 0,
+ /*type*/ gloox::Jingle::ICEUDP::ServerReflexive
+ });
+
+ m_Wrapped = new gloox::Jingle::ICEUDP(/*local_pwd*/"", /*local_ufrag*/"", glooxCandidates);
+ m_Owned = true;
+}
+
+glooxwrapper::Jingle::ICEUDP::ICEUDP()
+ : glooxwrapper::Jingle::Plugin(NULL, false)
+{
+ m_Wrapped = new gloox::Jingle::ICEUDP();
+ m_Owned = true;
+}
+
+const glooxwrapper::Jingle::ICEUDP::CandidateList glooxwrapper::Jingle::ICEUDP::candidates() const
+{
+ glooxwrapper::Jingle::ICEUDP::CandidateList candidateListWrapper;
+ for (const gloox::Jingle::ICEUDP::Candidate candidate : static_cast(m_Wrapped)->candidates())
+ candidateListWrapper.push_back(glooxwrapper::Jingle::ICEUDP::Candidate{candidate.ip, candidate.port});
+ return candidateListWrapper;
+}
+
+glooxwrapper::SessionManager::SessionManager(Client* parent, Jingle::SessionHandler* sh)
+{
+ m_HandlerWrapper = new SessionHandlerWrapper(sh, false);
+ m_Wrapped = new gloox::Jingle::SessionManager(parent->getWrapped(), m_HandlerWrapper);
+}
+
+glooxwrapper::SessionManager::~SessionManager()
+{
+ delete m_Wrapped;
+ delete m_HandlerWrapper;
+}
+
+void glooxwrapper::SessionManager::registerPlugins()
+{
+ m_Wrapped->registerPlugin(new gloox::Jingle::Content());
+ m_Wrapped->registerPlugin(new gloox::Jingle::ICEUDP());
+ m_Wrapped->registerPlugin(new ZeroADGameData());
+}
+
+glooxwrapper::Jingle::Session glooxwrapper::SessionManager::createSession(const JID& callee)
+{
+ gloox::Jingle::Session* glooxSession = m_Wrapped->createSession(callee.getWrapped(), m_HandlerWrapper);
+ return glooxwrapper::Jingle::Session(glooxSession, false);
+}
Index: source/network/NetClient.h
===================================================================
--- source/network/NetClient.h
+++ source/network/NetClient.h
@@ -33,6 +33,8 @@
class CNetServer;
class ScriptInterface;
+typedef struct _ENetHost ENetHost;
+
// NetClient session FSM states
enum
{
@@ -99,7 +101,7 @@
* @param server IP address or host name to connect to
* @return true on success, false on connection failure
*/
- bool SetupConnection(const CStr& server, const u16 port);
+ bool SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient = NULL);
/**
* Destroy the connection to the server.
Index: source/network/NetClient.cpp
===================================================================
--- source/network/NetClient.cpp
+++ source/network/NetClient.cpp
@@ -24,6 +24,7 @@
#include "NetSession.h"
#include "lib/byte_order.h"
+#include "lib/external_libraries/enet.h"
#include "lib/sysdep/sysdep.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
@@ -158,10 +159,10 @@
m_UserName = username;
}
-bool CNetClient::SetupConnection(const CStr& server, const u16 port)
+bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient)
{
CNetClientSession* session = new CNetClientSession(*this);
- bool ok = session->Connect(server, port, m_IsLocalClient);
+ bool ok = session->Connect(server, port, m_IsLocalClient, enetClient);
SetAndOwnSession(session);
return ok;
}
Index: source/network/NetServer.h
===================================================================
--- source/network/NetServer.h
+++ source/network/NetServer.h
@@ -134,6 +134,8 @@
*/
void SetTurnLength(u32 msecs);
+ void SendHolePunchingMessage(const CStr& ip, u16 port);
+
private:
CNetServerWorker* m_Worker;
};
@@ -271,6 +273,8 @@
*/
void CheckClientConnections();
+ void SendHolePunchingMessage(const CStr& ip, u16 port);
+
/**
* Internal script context for (de)serializing script messages,
* and for storing game attributes.
Index: source/network/NetServer.cpp
===================================================================
--- source/network/NetServer.cpp
+++ source/network/NetServer.cpp
@@ -26,6 +26,7 @@
#include "NetStats.h"
#include "lib/external_libraries/enet.h"
+#include "network/StunClient.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Profile.h"
@@ -1445,6 +1446,11 @@
}
}
+void CNetServerWorker::SendHolePunchingMessage(const CStr& ipStr, u16 port)
+{
+ StunClient::SendHolePunchingMessages(m_Host, ipStr.c_str(), port);
+}
+
@@ -1484,3 +1490,8 @@
CScopeLock lock(m_Worker->m_WorkerMutex);
m_Worker->m_TurnLengthQueue.push_back(msecs);
}
+
+void CNetServer::SendHolePunchingMessage(const CStr& ip, u16 port)
+{
+ m_Worker->SendHolePunchingMessage(ip, port);
+}
Index: source/network/NetSession.h
===================================================================
--- source/network/NetSession.h
+++ source/network/NetSession.h
@@ -39,6 +39,8 @@
class CNetStatsTable;
+typedef struct _ENetHost ENetHost;
+
/**
* @file
* Network client/server sessions.
@@ -70,7 +72,7 @@
CNetClientSession(CNetClient& client);
~CNetClientSession();
- bool Connect(const CStr& server, const u16 port, const bool isLocalClient);
+ bool Connect(const CStr& server, const u16 port, const bool isLocalClient, ENetHost* enetClient);
/**
* Process queued incoming messages.
Index: source/network/NetSession.cpp
===================================================================
--- source/network/NetSession.cpp
+++ source/network/NetSession.cpp
@@ -52,13 +52,18 @@
}
}
-bool CNetClientSession::Connect(const CStr& server, const u16 port, const bool isLocalClient)
+bool CNetClientSession::Connect(const CStr& server, const u16 port, const bool isLocalClient, ENetHost* enetClient)
{
ENSURE(!m_Host);
ENSURE(!m_Server);
// Create ENet host
- ENetHost* host = enet_host_create(NULL, 1, CHANNEL_COUNT, 0, 0);
+ ENetHost* host;
+ if (enetClient != NULL)
+ host = enetClient;
+ else
+ host = enet_host_create(NULL, 1, CHANNEL_COUNT, 0, 0);
+
if (!host)
return false;
Index: source/network/StunClient.h
===================================================================
--- /dev/null
+++ source/network/StunClient.h
@@ -0,0 +1,43 @@
+// SuperTuxKart - a fun racing game with go-kart
+// Copyright (C) 2013-2016 SuperTuxKart-Team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+#ifndef STUNCLIENT_H
+#define STUNCLIENT_H
+
+#include "scriptinterface/ScriptInterface.h"
+
+typedef struct _ENetHost ENetHost;
+
+namespace StunClient
+{
+
+struct StunEndpoint {
+ uint32_t ip;
+ uint16_t port;
+};
+
+void SendStunRequest(ENetHost* transactionHost, uint32_t targetIp, uint16_t targetPort);
+
+JS::Value FindStunEndpoint(ScriptInterface& scriptInterface, int port);
+
+StunEndpoint FindStunEndpoint(ENetHost* transactionHost);
+
+void SendHolePunchingMessages(ENetHost* enetClient, const char* serverAddress, u16 serverPort);
+
+}
+
+#endif // STUNCLIENT_H
Index: source/network/StunClient.cpp
===================================================================
--- /dev/null
+++ source/network/StunClient.cpp
@@ -0,0 +1,364 @@
+// SuperTuxKart - a fun racing game with go-kart
+// Copyright (C) 2013-2016 SuperTuxKart-Team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#include "precompiled.h"
+
+#include "StunClient.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#ifdef WIN32
+# include
+# include
+#else
+# include
+# include
+#endif
+
+#include
+
+#include "lib/external_libraries/enet.h"
+#include "scriptinterface/ScriptInterface.h"
+#include "ps/CLogger.h"
+#include "ps/ConfigDB.h"
+
+unsigned int m_StunServerIP;
+static const int m_StunServerPort = 3478;
+const uint32_t m_StunMagicCookie = 0x2112A442;
+uint8_t m_StunTransactionID[12];
+
+/**
+ * Discovered STUN endpoint
+ */
+uint32_t m_IP;
+uint16_t m_Port;
+
+void AddUInt16(std::vector& m_buffer, const uint16_t value)
+{
+ m_buffer.push_back((value >> 8) & 0xff);
+ m_buffer.push_back(value & 0xff);
+}
+
+void AddUInt32(std::vector& m_buffer, const uint32_t& value)
+{
+ m_buffer.push_back((value >> 24) & 0xff);
+ m_buffer.push_back((value >> 16) & 0xff);
+ m_buffer.push_back((value >> 8) & 0xff);
+ m_buffer.push_back( value & 0xff);
+}
+
+template
+T GetFromBuffer(std::vector m_buffer, int& m_current_offset)
+{
+ int a = n;
+ T result = 0;
+ m_current_offset += n;
+ int offset = m_current_offset -1;
+ while (a--)
+ {
+ result <<= 8;
+ result += m_buffer[offset - a];
+ }
+ return result;
+}
+
+/**
+ * Creates a STUN request and sends it to a STUN server.
+ * See https://tools.ietf.org/html/rfc5389#section-6
+ * for details on the message structure.
+ * The request is send through m_transaction_host, from which the answer
+ * will be retrieved by ParseStunResponse()
+ */
+void CreateStunRequest(ENetHost* transactionHost)
+{
+ std::string server_name;
+ CFG_GET_VAL("stun.server", server_name);
+ LOGMESSAGERENDER("GetPublicAddress: Using STUN server %s", server_name.c_str());
+
+ addrinfo hints;
+ addrinfo* res;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
+ hints.ai_socktype = SOCK_STREAM;
+
+ // Resolve the stun server name so we can send it a STUN request
+ int status = getaddrinfo(server_name.c_str(), nullptr, &hints, &res);
+ if (status != 0)
+ {
+ LOGERROR("GetPublicAddress: Error in getaddrinfo: %s", gai_strerror(status));
+ return;
+ }
+
+ // documentation says it points to "one or more addrinfo structures"
+ ENSURE(res != nullptr);
+ struct sockaddr_in* current_interface = (struct sockaddr_in*)(res->ai_addr);
+ m_StunServerIP = ntohl(current_interface->sin_addr.s_addr);
+
+ if (transactionHost == nullptr)
+ {
+ LOGERROR("Failed to create enet host");
+ return;
+ }
+
+ StunClient::SendStunRequest(transactionHost, m_StunServerIP, m_StunServerPort);
+
+ freeaddrinfo(res);
+}
+
+void StunClient::SendStunRequest(ENetHost* transactionHost, uint32_t targetIp, uint16_t targetPort)
+{
+ // Assemble the message for the stun server
+ std::vector m_buffer;
+
+ // bytes 0-1: the type of the message
+ // bytes 2-3: message length added to header (attributes)
+ uint16_t message_type = 0x0001; // binding request
+ uint16_t message_length = 0x0000;
+ AddUInt16(m_buffer, message_type);
+ AddUInt16(m_buffer, message_length);
+ AddUInt32(m_buffer, 0x2112A442);
+
+ // bytes 8-19: the transaction id
+ for (int i = 0; i < 12; i++)
+ {
+ uint8_t random_byte = rand() % 256;
+ m_buffer.push_back(random_byte);
+ m_StunTransactionID[i] = random_byte;
+ }
+ //m_buffer.push_back(0); -- this breaks STUN message
+
+ // sendRawPacket
+ struct sockaddr_in to;
+ int to_len = sizeof(to);
+ memset(&to, 0, to_len);
+
+ to.sin_family = AF_INET;
+ to.sin_port = htons(targetPort);
+ to.sin_addr.s_addr = htonl(targetIp);
+
+ LOGMESSAGERENDER("GetPublicAddress: Sending STUN request to: %d.%d.%d.%d:%d",
+ (targetIp >> 24) & 0xff,
+ (targetIp >> 16) & 0xff,
+ (targetIp >> 8) & 0xff,
+ (targetIp >> 0) & 0xff,
+ targetPort);
+
+ int send_result = sendto(transactionHost->socket, (char*)(m_buffer.data()), (int)m_buffer.size(), 0, (sockaddr*)&to, to_len);
+ LOGMESSAGERENDER("GetPublicAddress: sendto result: %d", send_result);
+}
+
+/**
+ * Gets the response from the STUN server, checks it for its validity and
+ * then parses the answer into address and port
+ * \return "" if the address could be parsed or an error message
+*/
+std::string ParseStunResponse(ENetHost* transactionHost)
+{
+ // TransportAddress sender;
+ const int LEN = 2048;
+ char buffer[LEN];
+
+ // receiveRawPacket
+ // int len = m_transaction_host->receiveRawPacket(buffer, LEN, &sender, 2000);
+ int max_tries = 2000;
+
+ memset(buffer, 0, LEN);
+
+ struct sockaddr_in addr;
+ socklen_t from_len = sizeof(addr);
+
+ int err;
+ int len = recvfrom(transactionHost->socket, buffer, LEN, 0, (struct sockaddr*)(&addr), &from_len);
+
+ int count = 0;
+ // wait to receive the message because enet sockets are non-blocking
+ while (len < 0 && (countsocket, buffer, LEN, 0, (struct sockaddr*)(&addr), &from_len);
+ }
+
+ if (len == -1)
+ err = errno;
+ LOGERROR("GetPublicAddress: recvfrom result: %d", len);
+
+ if (len == -1)
+ LOGERROR("GetPublicAddress: recvfrom error: %d", err);
+
+ if (len < 0)
+ return "No message received";
+
+ uint32_t sender_ip = ntohl((uint32_t)(addr.sin_addr.s_addr));
+ uint16_t sender_port = ntohs(addr.sin_port);
+
+ if (sender_ip != m_StunServerIP)
+ LOGMESSAGERENDER("GetPublicAddress: Received stun response from different address: %d:%d (%d.%d.%d.%d:%d) %s",
+ addr.sin_addr.s_addr, addr.sin_port,
+ (sender_ip >> 24) & 0xff,
+ (sender_ip >> 16) & 0xff,
+ (sender_ip >> 8) & 0xff,
+ (sender_ip >> 0) & 0xff,
+ sender_port, buffer);
+
+ if (len < 0)
+ return "STUN response contains no data at all";
+
+ // Convert to network string.
+ // NetworkString datas((uint8_t*)buffer, len);
+ std::vector m_buffer;
+ int m_current_offset;
+
+ m_buffer.resize(len);
+ memcpy(m_buffer.data(), (uint8_t*)buffer, len);
+
+ m_current_offset = 0;
+ // m_current_offset = 5; // ignore type and token -- this breaks STUN response processing
+
+ // check that the stun response is a response, contains the magic cookie
+ // and the transaction ID
+ if (GetFromBuffer(m_buffer, m_current_offset) != 0x0101)
+ return "STUN response has incorrect type";
+
+ int message_size = GetFromBuffer(m_buffer, m_current_offset);
+ if (GetFromBuffer(m_buffer, m_current_offset) != m_StunMagicCookie)
+ return "STUN response doesn't contain the magic cookie";
+
+ for (int i = 0; i < 12; ++i)
+ if (m_buffer[m_current_offset++] != m_StunTransactionID[i])
+ return "STUN response doesn't contain the transaction ID";
+
+ LOGERROR("GetPublicAddress: The STUN server responded with a valid answer");
+
+ // The stun message is valid, so we parse it now:
+ if (message_size == 0)
+ return "STUN response does not contain any information.";
+
+ if (message_size < 4)
+ return "STUN response is too short.";
+
+ // Those are the port and the address to be detected
+ while (true)
+ {
+ int type = GetFromBuffer(m_buffer, m_current_offset);
+ int size = GetFromBuffer(m_buffer, m_current_offset);
+ if (type == 0 || type == 1)
+ {
+ ENSURE(size == 8);
+ ++m_current_offset;
+
+ // Check address family
+ char address_family = m_buffer[m_current_offset++];
+ if (address_family != 0x01)
+ return "Unsupported address family, IPv4 is expected";
+
+ m_Port = GetFromBuffer(m_buffer, m_current_offset);
+ m_IP = GetFromBuffer(m_buffer, m_current_offset);
+
+ // finished parsing, we know our public transport address
+ LOGMESSAGERENDER("GetPublicAddress: The public address has been found: %d.%d.%d.%d:%d",
+ (m_IP >> 24) & 0xff,
+ (m_IP >> 16) & 0xff,
+ (m_IP >> 8) & 0xff,
+ (m_IP >> 0) & 0xff,
+ m_Port);
+ break;
+ }
+
+ m_current_offset += 4 + size;
+ ENSURE(m_current_offset >=0 && m_current_offset < (int)m_buffer.size());
+
+ message_size -= 4 + size;
+
+ if (message_size < 4)
+ return "STUN response is invalid.";
+ }
+
+ return "";
+}
+
+JS::Value StunClient::FindStunEndpoint(ScriptInterface& scriptInterface, int port)
+{
+ ENetAddress hostAddr;
+ hostAddr.host = ENET_HOST_ANY;
+ hostAddr.port = port;
+
+ ENetHost* transactionHost = enet_host_create(&hostAddr, 1, 1, 0, 0);
+
+ CreateStunRequest(transactionHost);
+ std::string parse_result = ParseStunResponse(transactionHost);
+ enet_host_destroy(transactionHost);
+
+ if (!parse_result.empty())
+ LOGERROR("Parse error: %s", parse_result.c_str());
+
+ // 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));
+
+ JSContext* cx = scriptInterface.GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::RootedValue stunEndpoint(cx);
+ scriptInterface.Eval("({})", &stunEndpoint);
+ scriptInterface.SetProperty(stunEndpoint, "ip", std::string(ipStr));
+ scriptInterface.SetProperty(stunEndpoint, "port", m_Port);
+ return stunEndpoint;
+}
+
+StunClient::StunEndpoint StunClient::FindStunEndpoint(ENetHost* transactionHost)
+{
+ CreateStunRequest(transactionHost);
+ std::string parse_result = ParseStunResponse(transactionHost);
+ if (!parse_result.empty())
+ LOGERROR("Parse error: %s", parse_result.c_str());
+
+ // 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));
+
+ StunEndpoint stunEndpoint;
+ stunEndpoint.ip = m_IP;
+ stunEndpoint.port = m_Port;
+ return stunEndpoint;
+}
+
+void StunClient::SendHolePunchingMessages(ENetHost* enetClient, const char* serverAddress, u16 serverPort)
+{
+ // Convert ip string to int64
+ ENetAddress addr;
+ addr.port = serverPort;
+ enet_address_set_host(&addr, serverAddress);
+
+ // Send a UDP message from enet host to ip:port
+ LOGMESSAGERENDER("Sending STUN request to %s:%d", serverAddress, serverPort);
+ for (int i = 0; i < 3; ++i)
+ {
+ StunClient::SendStunRequest(enetClient, htonl(addr.host), serverPort);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+ }
+}