Index: source/lobby/IXmppClient.h =================================================================== --- source/lobby/IXmppClient.h +++ source/lobby/IXmppClient.h @@ -39,7 +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 SendIqGetConnectionData(const std::string& jid, const std::string& password, bool localIP) = 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: source/lobby/XmppClient.h =================================================================== --- source/lobby/XmppClient.h +++ source/lobby/XmppClient.h @@ -86,7 +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 SendIqGetConnectionData(const std::string& jid, const std::string& password, bool localIP); void SendIqUnregisterGame(); void SendIqChangeStateGame(const std::string& nbp, const std::string& players); void SendIqLobbyAuth(const std::string& to, const std::string& token); Index: source/lobby/XmppClient.cpp =================================================================== --- source/lobby/XmppClient.cpp +++ source/lobby/XmppClient.cpp @@ -370,12 +370,13 @@ /** * Request the Connection data (ip, port...) from the server. */ -void XmppClient::SendIqGetConnectionData(const std::string& jid, const std::string& password) +void XmppClient::SendIqGetConnectionData(const std::string& jid, const std::string& password, bool localIP) { glooxwrapper::JID targetJID(jid); ConnectionData* connectionData = new ConnectionData(); connectionData->m_Password = password; + connectionData->m_UseSTUN = localIP ? "0" : "1"; glooxwrapper::IQ iq(gloox::IQ::Get, targetJID, m_client->getID()); iq.addExtension(connectionData); m_connectionDataJid = iq.from().full(); @@ -1000,9 +1001,29 @@ 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" : ""; + if (cd->m_UseSTUN == "1") + { + connectionData->m_Ip = g_NetServer->GetPublicIp(); + connectionData->m_Port = std::to_string(g_NetServer->GetPublicPort()); + connectionData->m_UseSTUN = g_NetServer->GetUseSTUN() ? "true" : ""; + } + else + { + CStr ip; + if (StunClient::FindLocalIP(ip)) + { + connectionData->m_Ip = ip; + connectionData->m_Port = std::to_string(g_NetServer->GetPublicPort()); + connectionData->m_UseSTUN = ""; + } + else + { + LOGMESSAGE("Failed to get the local IP"); + connectionData->m_Error = "not_server"; + } + // Debug, remove before committing. + LOGWARNING("local IP %s", ip); + } response.addExtension(connectionData); Index: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -259,7 +259,8 @@ } StunClient::StunEndpoint stunEndpoint; - if (!StunClient::FindStunEndpointJoin(*enetClient, stunEndpoint)) + CStr ip; + if (!StunClient::FindStunEndpointJoin(*enetClient, stunEndpoint, ip)) { PushGuiMessage( "type", "netstatus", @@ -268,6 +269,16 @@ return false; } + // If the host is on the same network, we risk failing to connect + // on routers that don't support NAT hairpinning/NAT loopback. + // To work around that, send again a connection data request, but for internal IP this time. + if (ip == m_ServerAddress) + { + g_XmppClient->SendIqGetConnectionData(m_HostJID, m_Password, true); + // Return true anyways - we're on a success path here. + return true; + } + g_XmppClient->SendStunEndpointToHost(stunEndpoint, hostJID); SDL_Delay(1000); Index: source/network/StunClient.h =================================================================== --- source/network/StunClient.h +++ source/network/StunClient.h @@ -37,9 +37,15 @@ bool FindStunEndpointHost(CStr8& ip, u16& port); -bool FindStunEndpointJoin(ENetHost& transactionHost, StunClient::StunEndpoint& stunEndpoint); +bool FindStunEndpointJoin(ENetHost& transactionHost, StunClient::StunEndpoint& stunEndpoint, CStr8& ip); void SendHolePunchingMessages(ENetHost& enetClient, const std::string& serverAddress, u16 serverPort); + +/** + * Return the local IP. + * Technically not a STUN method, but convenient to define here. + */ +bool FindLocalIP(CStr8& ip); } #endif // STUNCLIENT_H Index: source/network/StunClient.cpp =================================================================== --- source/network/StunClient.cpp +++ source/network/StunClient.cpp @@ -392,7 +392,7 @@ return result == 0; } -bool StunClient::FindStunEndpointJoin(ENetHost& transactionHost, StunClient::StunEndpoint& stunEndpoint) +bool StunClient::FindStunEndpointJoin(ENetHost& transactionHost, StunClient::StunEndpoint& stunEndpoint, CStr8& ip) { if (!STUNRequestAndResponse(transactionHost)) return false; @@ -403,6 +403,7 @@ addr.host = ntohl(m_IP); enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr)); + ip = ipStr; stunEndpoint.ip = m_IP; stunEndpoint.port = m_Port; @@ -426,3 +427,47 @@ usleep(delay * 1000); } } + +bool StunClient::FindLocalIP(CStr8& ip) +{ + int sock = socket(PF_INET, SOCK_DGRAM, 0); + sockaddr_in loopback; + + if (sock == -1) + { + LOGERROR("Could not socket"); + return false; + } + + memset(&loopback, 0, sizeof(loopback)); + loopback.sin_family = AF_INET; + loopback.sin_addr.s_addr = 1337; // can be any IP address + loopback.sin_port = htons(9); // using debug port + + if (connect(sock, reinterpret_cast(&loopback), sizeof(loopback)) == -1) + { + close(sock); + LOGERROR("Could not connect"); + return false; + } + + socklen_t addrlen = sizeof(loopback); + if (getsockname(sock, reinterpret_cast(&loopback), &addrlen) == -1) + { + close(sock); + LOGERROR("Could not getsockname"); + return false; + } + + close(sock); + + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &loopback.sin_addr, buf, INET_ADDRSTRLEN) == 0) + { + LOGERROR("Could not inet_ntop"); + return false; + } + ip = buf; + + return true; +} Index: source/network/scripting/JSInterface_Network.cpp =================================================================== --- source/network/scripting/JSInterface_Network.cpp +++ source/network/scripting/JSInterface_Network.cpp @@ -190,7 +190,7 @@ g_NetClient->SetUserName(playerName); g_NetClient->SetHostJID(hostJID); g_NetClient->SetGamePassword(hashedPass); - g_XmppClient->SendIqGetConnectionData(hostJID, hashedPass.c_str()); + g_XmppClient->SendIqGetConnectionData(hostJID, hashedPass.c_str(), false); } void DisconnectNetworkGame()