Use TCP stun for more reliable IP type detection

This commit is contained in:
Benau 2020-09-03 13:24:49 +08:00
parent 6e17d51802
commit 618eb87df0
7 changed files with 239 additions and 90 deletions

View File

@ -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 }
}
));

View File

@ -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();

View File

@ -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();

View File

@ -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,

View File

@ -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(); }
// ------------------------------------------------------------------------

View File

@ -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;

View 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