Add tools for advanced IPv4 and IPv6 detection
This commit is contained in:
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user