Add tools for advanced IPv4 and IPv6 detection

This commit is contained in:
Benau
2020-01-19 10:13:17 +08:00
parent 7b3559ba69
commit fcdec55bc1
5 changed files with 233 additions and 23 deletions

View File

@@ -160,6 +160,10 @@ public:
m_elements = std::map<T,U>(v);
return m_elements;
}
size_t size() const
{
return m_elements.size();
}
U& operator[] (const T key)
{
return m_elements[key];

View File

@@ -21,8 +21,11 @@
#include "config/user_config.hpp"
#include "input/device_manager.hpp"
#include "modes/world.hpp"
#include "network/network.hpp"
#include "network/rewind_manager.hpp"
#include "network/server_config.hpp"
#include "network/stk_host.hpp"
#include "network/stk_ipv6.hpp"
#include "network/transport_address.hpp"
#include "online/xml_request.hpp"
#include "states_screens/main_menu_screen.hpp"
@@ -31,6 +34,14 @@
#include "states_screens/online/online_profile_servers.hpp"
#include "states_screens/online/online_screen.hpp"
#include "states_screens/state_manager.hpp"
#include "utils/time.hpp"
#ifdef WIN32
# include <winsock2.h>
# include <ws2tcpip.h>
#else
# include <netdb.h>
#endif
NetworkConfig *NetworkConfig::m_network_config = NULL;
@@ -48,6 +59,7 @@ NetworkConfig *NetworkConfig::m_network_config = NULL;
*/
NetworkConfig::NetworkConfig()
{
m_ip_type = IP_NONE;
m_network_type = NETWORK_NONE;
m_auto_connect = false;
m_is_server = false;
@@ -171,3 +183,167 @@ bool NetworkConfig::roundValuesNow() const
return isNetworking() && !isServer() && RewindManager::get()
->shouldSaveState(World::getWorld()->getTicksSinceStart());
} // roundValuesNow
// ----------------------------------------------------------------------------
/** Use stun servers to detect current ip type.
*/
void NetworkConfig::detectIPType()
{
ENetAddress addr;
addr.host = STKHost::HOST_ANY;
addr.port = STKHost::PORT_ANY;
// 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);
setIPV6(0);
auto ipv4 = std::unique_ptr<Network>(new Network(1, 1, 0, 0, &addr));
setIPV6(1);
auto ipv6 = std::unique_ptr<Network>(new Network(1, 1, 0, 0, &addr));
setIPV6(0);
auto ipv4_it = UserConfigParams::m_stun_servers_v4.begin();
int adv = rand() % UserConfigParams::m_stun_servers_v4.size();
std::advance(ipv4_it, adv);
auto ipv6_it = UserConfigParams::m_stun_servers.begin();
adv = rand() % UserConfigParams::m_stun_servers.size();
std::advance(ipv6_it, adv);
std::vector<std::string> addrv4_and_port =
StringUtils::split(ipv4_it->first, ':');
if (addrv4_and_port.size() != 2)
{
Log::error("NetworkConfig", "Wrong server address and port");
return;
}
struct addrinfo hints;
struct addrinfo* res = NULL;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
int status = getaddrinfo_compat(addrv4_and_port[0].c_str(),
addrv4_and_port[1].c_str(), &hints, &res);
bool sent_ipv4 = false;
if (status == 0 && res != NULL)
{
if (res->ai_family == AF_INET)
{
sendto(ipv4->getENetHost()->socket, s.getData(), s.size(), 0,
res->ai_addr, sizeof(sockaddr_in));
sent_ipv4 = true;
}
freeaddrinfo(res);
}
std::vector<std::string> addrv6_and_port =
StringUtils::split(ipv6_it->first, ':');
if (addrv6_and_port.size() != 2)
{
Log::error("NetworkConfig", "Wrong server address and port");
return;
}
res = NULL;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
status = getaddrinfo_compat(addrv6_and_port[0].c_str(),
addrv6_and_port[1].c_str(), &hints, &res);
bool sent_ipv6 = false;
if (status == 0 && res != NULL)
{
if (res->ai_family == AF_INET6)
{
sendto(ipv6->getENetHost()->socket, s.getData(), s.size(), 0,
res->ai_addr, sizeof(sockaddr_in6));
sent_ipv6 = true;
}
freeaddrinfo(res);
}
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)
{
// 1.5 second timeout
has_ipv4 = enet_socketset_select(
ipv4->getENetHost()->socket, &socket_set, NULL, 1500) > 0;
}
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)
{
has_ipv6 = true;
// For non dual stack IPv6 we try to get a NAT64 prefix to connect
// to IPv4 only servers
if (!has_ipv4)
{
// Detect NAT64 prefix by using the IPv4 only stun:
// All IPv4 only stun servers are *.supertuxkart.net which only
// have A record, so the below code which forces to get an AF_INET6
// will return their NAT64 addresses
res = NULL;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
status = getaddrinfo_compat(addrv4_and_port[0].c_str(),
addrv4_and_port[1].c_str(), &hints, &res);
if (status == 0 && res != NULL)
{
if (res->ai_family == AF_INET6)
{
struct sockaddr_in6 nat64 = {};
// Copy first 12 bytes
struct sockaddr_in6* out =
(struct sockaddr_in6*)res->ai_addr;
memcpy(nat64.sin6_addr.s6_addr, out->sin6_addr.s6_addr,
12);
m_nat64_prefix = getIPV6ReadableFromIn6(&nat64);
}
freeaddrinfo(res);
}
}
}
if (has_ipv4 && has_ipv6)
{
Log::info("NetworkConfig", "System is dual stack network.");
m_ip_type = IP_DUAL_STACK;
}
else if (has_ipv4)
{
Log::info("NetworkConfig", "System is IPv4 only.");
m_ip_type = IP_V4;
}
else if (has_ipv6)
{
Log::info("NetworkConfig", "System is IPv6 only.");
if (m_nat64_prefix.empty())
m_ip_type = IP_V6;
}
else
{
Log::error("NetworkConfig", "Cannot detect network type using stun.");
}
if (has_ipv6)
{
if (!has_ipv4 && m_nat64_prefix.empty())
{
Log::warn("NetworkConfig", "NAT64 prefix not found, "
"you may not be able to join any IPv4 only servers.");
}
if (!m_nat64_prefix.empty())
{
m_ip_type = IP_V6_NAT64;
Log::info("NetworkConfig",
"NAT64 prefix is %s.", m_nat64_prefix.c_str());
}
}
} // detectIPType

View File

@@ -27,6 +27,7 @@
#include "utils/no_copy.hpp"
#include "irrString.h"
#include <atomic>
#include <memory>
#include <set>
#include <tuple>
@@ -47,6 +48,11 @@ class PlayerProfile;
class NetworkConfig : public NoCopy
{
public:
enum IPType : uint8_t
{
IP_NONE, IP_V4, IP_V6, IP_DUAL_STACK
};
private:
/** The singleton instance. */
static NetworkConfig *m_network_config;
@@ -56,6 +62,8 @@ private:
NETWORK_NONE, NETWORK_WAN, NETWORK_LAN
};
std::atomic<IPType> m_ip_type;
/** Keeps the type of network connection: none (yet), LAN or WAN. */
NetworkType m_network_type;
@@ -98,6 +106,10 @@ private:
* available in same version. */
std::set<std::string> m_server_capabilities;
/** For IPv6 only network we try to detect the NAT64 prefix so we can
* use it to connect to ipv4 only servers. STK assumes that for all ipv4
* addresses they use the same prefix for each initIPTest. */
std::string m_nat64_prefix;
public:
/** Singleton get, which creates this object if necessary. */
static NetworkConfig *get()
@@ -245,7 +257,12 @@ public:
// ------------------------------------------------------------------------
const std::set<std::string>& getServerCapabilities() const
{ return m_server_capabilities; }
// ------------------------------------------------------------------------
void detectIPType();
// ------------------------------------------------------------------------
IPType getIPType() const { return m_ip_type.load(); }
// ------------------------------------------------------------------------
const std::string& getNAT64Prefix() const { return m_nat64_prefix; }
}; // class NetworkConfig
#endif // HEADER_NETWORK_CONFIG

View File

@@ -368,6 +368,37 @@ void STKHost::shutdown()
destroy();
} // shutdown
//-----------------------------------------------------------------------------
/** Get the stun network string required for binding request
* \param stun_tansaction_id 16 bytes array for filling to validate later.
*/
BareNetworkString STKHost::getStunRequest(uint8_t* stun_tansaction_id)
{
// Assemble the message for the stun server
BareNetworkString s(20);
constexpr uint32_t magic_cookie = 0x2112A442;
// 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;
s.addUInt16(message_type).addUInt16(message_length)
.addUInt32(magic_cookie);
stun_tansaction_id[0] = 0x21;
stun_tansaction_id[1] = 0x12;
stun_tansaction_id[2] = 0xA4;
stun_tansaction_id[3] = 0x42;
// bytes 8-19: the transaction id
for (int i = 0; i < 12; i++)
{
uint8_t random_byte = rand() % 256;
s.addUInt8(random_byte);
stun_tansaction_id[i + 4] = random_byte;
}
return s;
} // getStunRequest
//-----------------------------------------------------------------------------
std::string STKHost::getIPFromStun(int socket, const std::string& stun_address,
bool ipv4)
@@ -437,29 +468,9 @@ std::string STKHost::getIPFromStun(int socket, const std::string& stun_address,
m_stun_address.setPort(ntohs(ipv4_addr->sin_port));
}
// Assemble the message for the stun server
BareNetworkString s(20);
constexpr uint32_t magic_cookie = 0x2112A442;
// 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;
s.addUInt16(message_type).addUInt16(message_length)
.addUInt32(magic_cookie);
uint8_t stun_tansaction_id[16];
stun_tansaction_id[0] = 0x21;
stun_tansaction_id[1] = 0x12;
stun_tansaction_id[2] = 0xA4;
stun_tansaction_id[3] = 0x42;
// bytes 8-19: the transaction id
for (int i = 0; i < 12; i++)
{
uint8_t random_byte = rand() % 256;
s.addUInt8(random_byte);
stun_tansaction_id[i + 4] = random_byte;
}
constexpr uint32_t magic_cookie = 0x2112A442;
BareNetworkString s = getStunRequest(stun_tansaction_id);
sendto(socket, s.getData(), s.size(), 0, stun_addr, isIPV6() ?
sizeof(sockaddr_in6) : sizeof(sockaddr_in));

View File

@@ -393,6 +393,8 @@ public:
// ------------------------------------------------------------------------
std::vector<std::shared_ptr<NetworkPlayerProfile> >
getPlayersForNewGame() const;
// ------------------------------------------------------------------------
static BareNetworkString getStunRequest(uint8_t* stun_tansaction_id);
}; // class STKHost
#endif // STK_HOST_HPP