Use TCP stun for more reliable IP type detection
This commit is contained in:
parent
6e17d51802
commit
618eb87df0
@ -785,7 +785,7 @@ namespace UserConfigParams
|
||||
// These stk domains have only a record to each ipv6 stun below,
|
||||
// so we can use this to know ipv4 address of nat64 gateway (if any)
|
||||
PARAM_PREFIX StringToUIntUserConfigParam m_stun_servers_v4
|
||||
PARAM_DEFAULT(StringToUIntUserConfigParam("stun-servers-ipv4",
|
||||
PARAM_DEFAULT(StringToUIntUserConfigParam("ipv4-stun-servers",
|
||||
"The stun servers that will be used to know the public address "
|
||||
"(ipv4 only) with port", {{ "stun-server", "address", "ping" }},
|
||||
{
|
||||
@ -796,16 +796,13 @@ namespace UserConfigParams
|
||||
));
|
||||
|
||||
PARAM_PREFIX StringToUIntUserConfigParam m_stun_servers
|
||||
PARAM_DEFAULT(StringToUIntUserConfigParam("stun-servers-ipv6",
|
||||
PARAM_DEFAULT(StringToUIntUserConfigParam("ipv6-stun-servers",
|
||||
"The stun servers that will be used to know the public address "
|
||||
"(including ipv6) with port", {{ "stun-server", "address", "ping" }},
|
||||
{
|
||||
{ "stunv6.linuxreviews.org:3478", 0u },
|
||||
{ "stun.l.google.com:19302", 0u },
|
||||
{ "stun1.l.google.com:19302", 0u },
|
||||
{ "stun2.l.google.com:19302", 0u },
|
||||
{ "stun3.l.google.com:19302", 0u },
|
||||
{ "stun4.l.google.com:19302", 0u }
|
||||
{ "stun.linuxreviews.org:3478", 0u },
|
||||
{ "stun.supertuxkart.net:3478", 0u },
|
||||
{ "stun.stunprotocol.org:3478", 0u }
|
||||
}
|
||||
));
|
||||
|
||||
|
@ -1400,8 +1400,13 @@ int handleCmdLine(bool has_server_config, bool has_parent_process)
|
||||
Online::RequestManager::m_disable_polling = true;
|
||||
// For server we assume it is an IPv4 one, because if it fails
|
||||
// to detect the server won't start at all
|
||||
NetworkConfig::get()->setIPType(NetworkConfig::IP_V4);
|
||||
NetworkConfig::get()->detectIPType();
|
||||
if (UserConfigParams::m_default_ip_type == NetworkConfig::IP_NONE)
|
||||
{
|
||||
NetworkConfig::get()->setIPType(NetworkConfig::IP_V4);
|
||||
NetworkConfig::get()->queueIPDetection();
|
||||
}
|
||||
// Longer timeout for server creation
|
||||
NetworkConfig::get()->getIPDetectionResult(4000);
|
||||
NetworkConfig::get()->setIsWAN();
|
||||
NetworkConfig::get()->setIsPublicServer();
|
||||
ServerConfig::loadServerLobbyFromConfig();
|
||||
|
@ -87,8 +87,13 @@ void ChildLoop::run()
|
||||
NetworkConfig::get()->setIsLAN();
|
||||
else
|
||||
{
|
||||
NetworkConfig::get()->setIPType(NetworkConfig::IP_V4);
|
||||
NetworkConfig::get()->detectIPType();
|
||||
if (UserConfigParams::m_default_ip_type == NetworkConfig::IP_NONE)
|
||||
{
|
||||
NetworkConfig::get()->setIPType(NetworkConfig::IP_V4);
|
||||
NetworkConfig::get()->queueIPDetection();
|
||||
}
|
||||
// Longer timeout for server creation
|
||||
NetworkConfig::get()->getIPDetectionResult(4000);
|
||||
NetworkConfig::getByType(PT_MAIN)->setIPType(
|
||||
NetworkConfig::get()->getIPType());
|
||||
NetworkConfig::get()->setIsWAN();
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "network/socket_address.hpp"
|
||||
#include "network/stk_host.hpp"
|
||||
#include "network/stk_ipv6.hpp"
|
||||
#include "network/stun_detection.hpp"
|
||||
#include "online/xml_request.hpp"
|
||||
#include "states_screens/main_menu_screen.hpp"
|
||||
#include "states_screens/online/networking_lobby.hpp"
|
||||
@ -39,6 +40,8 @@
|
||||
#include "utils/time.hpp"
|
||||
#include "utils/utf8/unchecked.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#ifdef WIN32
|
||||
# include <windns.h>
|
||||
# include <ws2tcpip.h>
|
||||
@ -276,45 +279,56 @@ bool NetworkConfig::roundValuesNow() const
|
||||
->shouldSaveState(World::getWorld()->getTicksSinceStart());
|
||||
} // roundValuesNow
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
#ifdef ENABLE_IPV6
|
||||
std::vector<std::unique_ptr<StunDetection> > g_ipv4_detection;
|
||||
std::vector<std::unique_ptr<StunDetection> > g_ipv6_detection;
|
||||
#endif
|
||||
|
||||
void NetworkConfig::clearDetectIPThread(bool quit_stk)
|
||||
{
|
||||
#ifdef ENABLE_IPV6
|
||||
if (!quit_stk)
|
||||
{
|
||||
auto it = g_ipv4_detection.begin();
|
||||
while (it != g_ipv4_detection.end())
|
||||
{
|
||||
if ((*it)->isConnecting())
|
||||
{
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
it = g_ipv4_detection.erase(it);
|
||||
}
|
||||
it = g_ipv6_detection.begin();
|
||||
while (it != g_ipv6_detection.end())
|
||||
{
|
||||
if ((*it)->isConnecting())
|
||||
{
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
it = g_ipv6_detection.erase(it);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_ipv4_detection.clear();
|
||||
g_ipv6_detection.clear();
|
||||
}
|
||||
#endif
|
||||
} // clearDetectIPThread
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Use stun servers to detect current ip type.
|
||||
*/
|
||||
void NetworkConfig::detectIPType()
|
||||
void NetworkConfig::queueIPDetection()
|
||||
{
|
||||
if (UserConfigParams::m_default_ip_type != IP_NONE)
|
||||
{
|
||||
int ip_type = UserConfigParams::m_default_ip_type;
|
||||
m_nat64_prefix.clear();
|
||||
m_nat64_prefix_data.fill(-1);
|
||||
m_ip_type.store((IPType)ip_type);
|
||||
if (UserConfigParams::m_default_ip_type != IP_NONE ||
|
||||
!m_system_ipv4 || !m_system_ipv6)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IPV6
|
||||
if (!m_system_ipv4 || !m_system_ipv6)
|
||||
{
|
||||
// Don't test connection if only IPv4 or IPv6 system support
|
||||
if (m_system_ipv4)
|
||||
m_ip_type.store(IP_V4);
|
||||
else if (m_system_ipv6)
|
||||
m_ip_type.store(IP_V6);
|
||||
return;
|
||||
}
|
||||
|
||||
ENetAddress eaddr = {};
|
||||
// We don't need to result of stun, just to check if the socket can be
|
||||
// used in ipv4 or ipv6
|
||||
uint8_t stun_tansaction_id[16] = {};
|
||||
BareNetworkString s = STKHost::getStunRequest(stun_tansaction_id);
|
||||
setIPv6Socket(0);
|
||||
auto ipv4 = std::unique_ptr<Network>(new Network(1, 1, 0, 0, &eaddr));
|
||||
setIPv6Socket(1);
|
||||
auto ipv6 = std::unique_ptr<Network>(new Network(1, 1, 0, 0, &eaddr));
|
||||
setIPv6Socket(0);
|
||||
|
||||
// This should only happen if system doesn't support all socket types
|
||||
if (!ipv4->getENetHost() || !ipv6->getENetHost())
|
||||
return;
|
||||
|
||||
auto& stunv4_map = UserConfigParams::m_stun_servers_v4;
|
||||
for (auto& s : getStunList(true/*ipv4*/))
|
||||
{
|
||||
@ -344,46 +358,59 @@ void NetworkConfig::detectIPType()
|
||||
std::advance(ipv6_it, adv);
|
||||
|
||||
SocketAddress::g_ignore_error_message = true;
|
||||
SocketAddress stun_v4(ipv4_it->first, 0/*port specified in addr*/,
|
||||
AF_INET);
|
||||
bool sent_ipv4 = false;
|
||||
if (!stun_v4.isUnset() && stun_v4.getFamily() == AF_INET)
|
||||
{
|
||||
sendto(ipv4->getENetHost()->socket, s.getData(), s.size(), 0,
|
||||
stun_v4.getSockaddr(), stun_v4.getSocklen());
|
||||
sent_ipv4 = true;
|
||||
}
|
||||
|
||||
SocketAddress stun_v6(ipv6_it->first, 0/*port specified in addr*/,
|
||||
AF_INET6);
|
||||
bool sent_ipv6 = false;
|
||||
if (!stun_v6.isUnset() && stun_v6.getFamily() == AF_INET6)
|
||||
{
|
||||
sendto(ipv6->getENetHost()->socket, s.getData(), s.size(), 0,
|
||||
stun_v6.getSockaddr(), stun_v6.getSocklen());
|
||||
sent_ipv6 = true;
|
||||
}
|
||||
std::unique_ptr<StunDetection> ipv4_detect(
|
||||
new StunDetection(ipv4_it->first, true/*ipv4*/));
|
||||
std::unique_ptr<StunDetection> ipv6_detect(
|
||||
new StunDetection(ipv6_it->first, false/*ipv4*/));
|
||||
SocketAddress::g_ignore_error_message = false;
|
||||
Log::debug("NetworkConfig", "Using TCP stun IPv4: %s, IPv6: %s",
|
||||
ipv4_it->first.c_str(), ipv6_it->first.c_str());
|
||||
g_ipv4_detection.emplace_back(std::move(ipv4_detect));
|
||||
g_ipv6_detection.emplace_back(std::move(ipv6_detect));
|
||||
#endif
|
||||
} // queueIPDetection
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Use stun servers to detect current ip type.
|
||||
*/
|
||||
void NetworkConfig::getIPDetectionResult(uint64_t timeout)
|
||||
{
|
||||
if (UserConfigParams::m_default_ip_type != IP_NONE)
|
||||
{
|
||||
int ip_type = UserConfigParams::m_default_ip_type;
|
||||
m_nat64_prefix.clear();
|
||||
m_nat64_prefix_data.fill(-1);
|
||||
m_ip_type.store((IPType)ip_type);
|
||||
return;
|
||||
}
|
||||
#ifdef ENABLE_IPV6
|
||||
bool has_ipv4 = false;
|
||||
bool has_ipv6 = false;
|
||||
|
||||
ENetSocketSet socket_set;
|
||||
ENET_SOCKETSET_EMPTY(socket_set);
|
||||
ENET_SOCKETSET_ADD(socket_set, ipv4->getENetHost()->socket);
|
||||
if (sent_ipv4)
|
||||
if (!m_system_ipv4 || !m_system_ipv6)
|
||||
{
|
||||
// 1.5 second timeout
|
||||
has_ipv4 = enet_socketset_select(
|
||||
ipv4->getENetHost()->socket, &socket_set, NULL, 1500) > 0;
|
||||
has_ipv4 = m_system_ipv4;
|
||||
has_ipv6 = m_system_ipv6;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ENET_SOCKETSET_EMPTY(socket_set);
|
||||
ENET_SOCKETSET_ADD(socket_set, ipv6->getENetHost()->socket);
|
||||
if (sent_ipv6 && enet_socketset_select(
|
||||
ipv6->getENetHost()->socket, &socket_set, NULL, 1500) > 0)
|
||||
if (g_ipv4_detection.empty() || g_ipv6_detection.empty())
|
||||
goto end;
|
||||
|
||||
timeout += StkTime::getMonoTimeMs();
|
||||
do
|
||||
{
|
||||
has_ipv4 = g_ipv4_detection.back()->connectionSucceeded();
|
||||
has_ipv6 = g_ipv6_detection.back()->connectionSucceeded();
|
||||
// Exit early if socket closed
|
||||
if (g_ipv4_detection.back()->socketClosed() &&
|
||||
g_ipv6_detection.back()->socketClosed())
|
||||
break;
|
||||
} while (timeout > StkTime::getMonoTimeMs());
|
||||
clearDetectIPThread(false/*quit_stk*/);
|
||||
|
||||
end:
|
||||
if (has_ipv6)
|
||||
{
|
||||
has_ipv6 = true;
|
||||
// For non dual stack IPv6 we try to get a NAT64 prefix to connect
|
||||
// to IPv4 only servers
|
||||
if (!has_ipv4)
|
||||
@ -457,7 +484,7 @@ void NetworkConfig::detectIPType()
|
||||
#else
|
||||
m_ip_type = IP_V4;
|
||||
#endif
|
||||
} // detectIPType
|
||||
} // getIPDetectionResult
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void NetworkConfig::fillStunList(std::vector<std::pair<std::string, int> >* l,
|
||||
|
@ -149,11 +149,17 @@ public:
|
||||
return m_network_config[type];
|
||||
} // get
|
||||
// ------------------------------------------------------------------------
|
||||
static void clearDetectIPThread(bool quit_stk);
|
||||
// ------------------------------------------------------------------------
|
||||
static void queueIPDetection();
|
||||
// ------------------------------------------------------------------------
|
||||
static void destroy()
|
||||
{
|
||||
ProcessType type = STKProcess::getType();
|
||||
delete m_network_config[type]; // It's ok to delete NULL
|
||||
m_network_config[type] = NULL;
|
||||
if (type == PT_MAIN)
|
||||
clearDetectIPThread(true/*quit_stk*/);
|
||||
} // destroy
|
||||
// ------------------------------------------------------------------------
|
||||
static void clear()
|
||||
@ -292,7 +298,7 @@ public:
|
||||
const std::set<std::string>& getServerCapabilities() const
|
||||
{ return m_server_capabilities; }
|
||||
// ------------------------------------------------------------------------
|
||||
void detectIPType();
|
||||
void getIPDetectionResult(uint64_t timeout);
|
||||
// ------------------------------------------------------------------------
|
||||
IPType getIPType() const { return m_ip_type.load(); }
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -91,27 +91,24 @@ std::shared_ptr<ServerList> ServersManager::getWANRefreshRequest() const
|
||||
std::weak_ptr<ServerList> m_server_list;
|
||||
// Run the ip detect in separate thread, so it can be done parallel
|
||||
// with the wan server request (which takes few seconds too)
|
||||
std::thread m_ip_detect_thread;
|
||||
uint64_t m_creation_time;
|
||||
public:
|
||||
WANRefreshRequest(std::shared_ptr<ServerList> server_list)
|
||||
: Online::XMLRequest(/*priority*/100)
|
||||
{
|
||||
m_ip_detect_thread = std::thread(std::bind(
|
||||
&NetworkConfig::detectIPType, NetworkConfig::get()));
|
||||
NetworkConfig::queueIPDetection();
|
||||
m_creation_time = StkTime::getMonoTimeMs();
|
||||
m_server_list = server_list;
|
||||
}
|
||||
~WANRefreshRequest()
|
||||
{
|
||||
if (m_ip_detect_thread.joinable())
|
||||
m_ip_detect_thread.join();
|
||||
}
|
||||
// --------------------------------------------------------------------
|
||||
virtual void afterOperation() OVERRIDE
|
||||
{
|
||||
Online::XMLRequest::afterOperation();
|
||||
if (m_ip_detect_thread.joinable())
|
||||
m_ip_detect_thread.join();
|
||||
|
||||
// Wait at most 2 seconds for ip detection
|
||||
uint64_t timeout = StkTime::getMonoTimeMs() - m_creation_time;
|
||||
if (timeout > 2000)
|
||||
timeout = 0;
|
||||
NetworkConfig::get()->getIPDetectionResult(timeout);
|
||||
auto server_list = m_server_list.lock();
|
||||
if (!server_list)
|
||||
return;
|
||||
|
112
src/network/stun_detection.hpp
Normal file
112
src/network/stun_detection.hpp
Normal file
@ -0,0 +1,112 @@
|
||||
//
|
||||
// SuperTuxKart - a fun racing game with go-kart
|
||||
// Copyright (C) 2015 Joerg Henrichs
|
||||
//
|
||||
// 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 3
|
||||
// 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.
|
||||
|
||||
#ifdef ENABLE_IPV6
|
||||
|
||||
#ifndef HEADER_STUN_DETECTION
|
||||
#define HEADER_STUN_DETECTION
|
||||
|
||||
#include "network/socket_address.hpp"
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
#ifdef WIN32
|
||||
# include <winsock.h>
|
||||
#else
|
||||
# include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
class StunDetection
|
||||
{
|
||||
private:
|
||||
/** Socket connection thread */
|
||||
std::thread m_thread;
|
||||
|
||||
/** Socket variable */
|
||||
#ifdef WIN32
|
||||
SOCKET m_socket;
|
||||
#else
|
||||
int m_socket;
|
||||
#endif
|
||||
|
||||
/** True if socket connected */
|
||||
std::atomic_bool m_connected;
|
||||
|
||||
/** True if socket closed and the thread can be joined */
|
||||
std::atomic_bool m_socket_closed;
|
||||
public:
|
||||
// ------------------------------------------------------------------------
|
||||
StunDetection(const std::string& addr, bool ipv4)
|
||||
{
|
||||
m_connected = false;
|
||||
m_socket_closed = true;
|
||||
SocketAddress sa(addr.c_str(), 0/*port specified in addr*/,
|
||||
ipv4 ? AF_INET : AF_INET6);
|
||||
if (sa.isUnset() || (ipv4 && sa.getFamily() != AF_INET) ||
|
||||
(!ipv4 && sa.getFamily() != AF_INET6))
|
||||
return;
|
||||
m_socket = socket(ipv4? AF_INET : AF_INET6, SOCK_STREAM, 0);
|
||||
#ifdef WIN32
|
||||
if (m_socket == INVALID_SOCKET)
|
||||
return;
|
||||
#else
|
||||
if (m_socket == -1)
|
||||
return;
|
||||
#endif
|
||||
m_socket_closed = false;
|
||||
m_thread = std::thread([addr, ipv4, sa, this]()
|
||||
{
|
||||
uint64_t t = StkTime::getMonoTimeMs();
|
||||
if (connect(m_socket, sa.getSockaddr(), sa.getSocklen()) == -1)
|
||||
m_connected.store(false);
|
||||
else
|
||||
m_connected.store(true);
|
||||
shutdown(m_socket, 2);
|
||||
#ifdef WIN32
|
||||
closesocket(m_socket);
|
||||
#else
|
||||
close(m_socket);
|
||||
#endif
|
||||
m_socket_closed.store(true);
|
||||
Log::debug("StunDetection", "Took %dms for %s.",
|
||||
(int)(StkTime::getMonoTimeMs() - t),
|
||||
(addr + (ipv4 ? " (IPv4)" : " (IPv6)")).c_str());
|
||||
});
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
~StunDetection()
|
||||
{
|
||||
if (m_thread.joinable())
|
||||
{
|
||||
if (m_socket_closed)
|
||||
m_thread.join();
|
||||
else
|
||||
m_thread.detach();
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
bool isConnecting()
|
||||
{ return m_thread.joinable() && m_socket_closed == false; }
|
||||
// ------------------------------------------------------------------------
|
||||
bool connectionSucceeded() { return m_connected == true; }
|
||||
// ------------------------------------------------------------------------
|
||||
bool socketClosed() { return m_socket_closed == true; }
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user