Allow getting stun server list using SRV records

This commit is contained in:
Benau 2020-03-15 10:05:13 +08:00
parent 7598dc7b37
commit 364936e37b
8 changed files with 327 additions and 28 deletions

View File

@ -23,6 +23,7 @@ import java.util.Set;
import org.minidns.hla.DnssecResolverApi; import org.minidns.hla.DnssecResolverApi;
import org.minidns.hla.ResolverResult; import org.minidns.hla.ResolverResult;
import org.minidns.record.SRV;
import org.minidns.record.TXT; import org.minidns.record.TXT;
public class SuperTuxKartActivity extends NativeActivity public class SuperTuxKartActivity extends NativeActivity
@ -244,4 +245,24 @@ public class SuperTuxKartActivity extends NativeActivity
return new String[0]; return new String[0];
} }
} }
// ------------------------------------------------------------------------
public String[] getDNSSrvRecords(String domain)
{
try
{
ResolverResult<SRV> srvs =
DnssecResolverApi.INSTANCE.resolve(domain, SRV.class);
Set<SRV> ans = srvs.getAnswers();
String[] result = new String[ans.size()];
int i = 0;
for (SRV s : ans)
result[i++] = s.target.toString() + ":" + s.port;
return result;
}
catch (Exception e)
{
e.printStackTrace();
return new String[0];
}
}
} }

View File

@ -180,6 +180,11 @@
password-reset="https://online.supertuxkart.net/password-reset.php" password-reset="https://online.supertuxkart.net/password-reset.php"
assets-download="https://github.com/supertuxkart/stk-assets-mobile/releases/download/"/> assets-download="https://github.com/supertuxkart/stk-assets-mobile/releases/download/"/>
<!-- STK will use dns query of the SRV records with these domains to get the list of
stun servers to use for IPv4 and IPv6. -->
<stun ipv4="_stunv4._udp.supertuxkart.net"
ipv6="_stunv6._udp.supertuxkart.net"/>
<!-- Skidmark data: maximum number of skid marks, and <!-- Skidmark data: maximum number of skid marks, and
time for skidmarks to fade out. Maximum number will over time for skidmarks to fade out. Maximum number will over
current number of karts, so the more karts, the less current number of karts, so the more karts, the less

View File

@ -496,6 +496,12 @@ void STKConfig::getAllData(const XMLNode * root)
urls->get("assets-download", &m_assets_download_url); urls->get("assets-download", &m_assets_download_url);
} }
if (const XMLNode *urls = root->getNode("stun"))
{
urls->get("ipv4", &m_stun_ipv4);
urls->get("ipv6", &m_stun_ipv6);
}
if (const XMLNode *fonts_list = root->getNode("fonts-list")) if (const XMLNode *fonts_list = root->getNode("fonts-list"))
{ {
fonts_list->get("normal-ttf", &m_normal_ttf); fonts_list->get("normal-ttf", &m_normal_ttf);

View File

@ -207,6 +207,10 @@ public:
std::string m_password_reset_url; std::string m_password_reset_url;
std::string m_assets_download_url; std::string m_assets_download_url;
/* SRV records for stun server lists created */
std::string m_stun_ipv4;
std::string m_stun_ipv6;
/** Lists of TTF files used in STK. */ /** Lists of TTF files used in STK. */
std::vector<std::string> m_normal_ttf; std::vector<std::string> m_normal_ttf;
std::vector<std::string> m_digit_ttf; std::vector<std::string> m_digit_ttf;

View File

@ -138,7 +138,7 @@ public:
irr::core::stringc toString() const; irr::core::stringc toString() const;
operator std::map<T, U>() const operator std::map<T, U>&() const
{ {
return m_elements; return m_elements;
} }
@ -150,7 +150,19 @@ public:
{ {
return m_elements.end(); return m_elements.end();
} }
std::map<T, U>& operator=(const std::map<T,U>& v) typename std::map<T, U>::iterator find(const T& key)
{
return m_elements.find(key);
}
size_t erase(const T& key)
{
return m_elements.erase(key);
}
bool empty() const
{
return m_elements.empty();
}
std::map<T, U>& operator=(const std::map<T, U>& v)
{ {
m_elements = std::map<T, U>(v); m_elements = std::map<T, U>(v);
return m_elements; return m_elements;
@ -771,29 +783,13 @@ namespace UserConfigParams
PARAM_DEFAULT(StringToUIntUserConfigParam("stun-servers-ipv4", PARAM_DEFAULT(StringToUIntUserConfigParam("stun-servers-ipv4",
"The stun servers that will be used to know the public address " "The stun servers that will be used to know the public address "
"(ipv4 only) with port", {{ "stun-server", "address", "ping" }}, "(ipv4 only) with port", {{ "stun-server", "address", "ping" }},
{ { }));
{ "stunv4.1.supertuxkart.net:3478", 0u },
{ "stunv4.2.supertuxkart.net:19302", 0u },
{ "stunv4.3.supertuxkart.net:19302", 0u },
{ "stunv4.4.supertuxkart.net:19302", 0u },
{ "stunv4.5.supertuxkart.net:19302", 0u },
{ "stunv4.6.supertuxkart.net:19302", 0u }
}
));
PARAM_PREFIX StringToUIntUserConfigParam m_stun_servers PARAM_PREFIX StringToUIntUserConfigParam m_stun_servers
PARAM_DEFAULT(StringToUIntUserConfigParam("stun-servers-ipv6", PARAM_DEFAULT(StringToUIntUserConfigParam("stun-servers-ipv6",
"The stun servers that will be used to know the public address " "The stun servers that will be used to know the public address "
"(including ipv6) with port", {{ "stun-server", "address", "ping" }}, "(including ipv6) with port", {{ "stun-server", "address", "ping" }},
{ { }));
{ "stun.stunprotocol.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 }
}
));
PARAM_PREFIX GroupUserConfigParam m_network_group PARAM_PREFIX GroupUserConfigParam m_network_group
PARAM_DEFAULT(GroupUserConfigParam("Network", "Network Settings")); PARAM_DEFAULT(GroupUserConfigParam("Network", "Network Settings"));

View File

@ -35,13 +35,27 @@
#include "states_screens/online/online_profile_servers.hpp" #include "states_screens/online/online_profile_servers.hpp"
#include "states_screens/online/online_screen.hpp" #include "states_screens/online/online_screen.hpp"
#include "states_screens/state_manager.hpp" #include "states_screens/state_manager.hpp"
#include "utils/string_utils.hpp"
#include "utils/time.hpp" #include "utils/time.hpp"
#include "utils/utf8/unchecked.h"
#ifdef WIN32 #ifdef WIN32
# include <winsock2.h> # include <windns.h>
# include <ws2tcpip.h> # include <ws2tcpip.h>
#ifndef __MINGW32__
# pragma comment(lib, "dnsapi.lib")
#endif
#else #else
# include <arpa/nameser.h>
# include <arpa/nameser_compat.h>
# include <netdb.h> # include <netdb.h>
# include <netinet/in.h>
# include <resolv.h>
#endif
#ifdef ANDROID
#include "../../../lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h"
#include "graphics/irr_driver.hpp"
#endif #endif
NetworkConfig *NetworkConfig::m_network_config[PT_COUNT]; NetworkConfig *NetworkConfig::m_network_config[PT_COUNT];
@ -221,11 +235,28 @@ void NetworkConfig::detectIPType()
auto ipv6 = std::unique_ptr<Network>(new Network(1, 1, 0, 0, &eaddr)); auto ipv6 = std::unique_ptr<Network>(new Network(1, 1, 0, 0, &eaddr));
setIPv6Socket(0); setIPv6Socket(0);
auto ipv4_it = UserConfigParams::m_stun_servers_v4.begin(); auto& stunv4_map = UserConfigParams::m_stun_servers_v4;
int adv = StkTime::getMonoTimeMs() % UserConfigParams::m_stun_servers_v4.size(); for (auto& s : getStunList(true/*ipv4*/))
{
if (stunv4_map.find(s) == stunv4_map.end())
stunv4_map[s] = 0;
}
if (stunv4_map.empty())
return;
auto ipv4_it = stunv4_map.begin();
int adv = StkTime::getMonoTimeMs() % stunv4_map.size();
std::advance(ipv4_it, adv); std::advance(ipv4_it, adv);
auto ipv6_it = UserConfigParams::m_stun_servers.begin();
adv = StkTime::getMonoTimeMs() % UserConfigParams::m_stun_servers.size(); auto& stunv6_map = UserConfigParams::m_stun_servers;
for (auto& s : getStunList(false/*ipv4*/))
{
if (stunv6_map.find(s) == stunv6_map.end())
stunv6_map[s] = 0;
}
if (stunv6_map.empty())
return;
auto ipv6_it = stunv6_map.begin();
adv = StkTime::getMonoTimeMs() % stunv6_map.size();
std::advance(ipv6_it, adv); std::advance(ipv6_it, adv);
SocketAddress::g_ignore_error_message = true; SocketAddress::g_ignore_error_message = true;
@ -343,3 +374,206 @@ void NetworkConfig::detectIPType()
m_ip_type = IP_V4; m_ip_type = IP_V4;
#endif #endif
} // detectIPType } // detectIPType
// ----------------------------------------------------------------------------
void NetworkConfig::fillStunList(std::vector<std::string>& l,
const std::string& dns)
{
#if defined(WIN32)
PDNS_RECORD dns_record = NULL;
DnsQuery(StringUtils::utf8ToWide(dns).c_str(), DNS_TYPE_SRV,
DNS_QUERY_STANDARD, NULL, &dns_record, NULL);
if (dns_record)
{
for (PDNS_RECORD curr = dns_record; curr; curr = curr->pNext)
{
if (curr->wType == DNS_TYPE_SRV)
{
l.push_back(
StringUtils::wideToUtf8(curr->Data.SRV.pNameTarget) +
":" + StringUtils::toString(curr->Data.SRV.wPort));
}
}
DnsRecordListFree(dns_record, DnsFreeRecordListDeep);
}
#elif defined(ANDROID)
CIrrDeviceAndroid* dev =
dynamic_cast<CIrrDeviceAndroid*>(irr_driver->getDevice());
if (!dev)
return;
android_app* android = dev->getAndroid();
if (!android)
return;
bool was_detached = false;
JNIEnv* env = NULL;
jint status = android->activity->vm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (status == JNI_EDETACHED)
{
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
args.name = "NativeThread";
args.group = NULL;
status = android->activity->vm->AttachCurrentThread(&env, &args);
was_detached = true;
}
if (status != JNI_OK)
{
Log::error("NetworkConfig",
"Cannot attach current thread in getDNSSrvRecords.");
return;
}
jobject native_activity = android->activity->clazz;
jclass class_native_activity = env->GetObjectClass(native_activity);
if (class_native_activity == NULL)
{
Log::error("NetworkConfig",
"getDNSSrvRecords unable to find object class.");
if (was_detached)
{
android->activity->vm->DetachCurrentThread();
}
return;
}
jmethodID method_id = env->GetMethodID(class_native_activity,
"getDNSSrvRecords", "(Ljava/lang/String;)[Ljava/lang/String;");
if (method_id == NULL)
{
Log::error("NetworkConfig",
"getDNSSrvRecords unable to find method id.");
if (was_detached)
{
android->activity->vm->DetachCurrentThread();
}
return;
}
std::vector<uint16_t> jstr_data;
utf8::unchecked::utf8to16(
dns.c_str(), dns.c_str() + dns.size(), std::back_inserter(jstr_data));
jstring text =
env->NewString((const jchar*)jstr_data.data(), jstr_data.size());
if (text == NULL)
{
Log::error("NetworkConfig",
"Failed to create text for domain name.");
if (was_detached)
{
android->activity->vm->DetachCurrentThread();
}
return;
}
jobjectArray arr =
(jobjectArray)env->CallObjectMethod(native_activity, method_id, text);
if (arr == NULL)
{
Log::error("NetworkConfig", "No array is created.");
if (was_detached)
{
android->activity->vm->DetachCurrentThread();
}
return;
}
int len = env->GetArrayLength(arr);
for (int i = 0; i < len; i++)
{
jstring jstr = (jstring)(env->GetObjectArrayElement(arr, i));
if (!jstr)
continue;
const uint16_t* utf16_text =
(const uint16_t*)env->GetStringChars(jstr, NULL);
if (utf16_text == NULL)
continue;
const size_t str_len = env->GetStringLength(jstr);
std::string tmp;
utf8::unchecked::utf16to8(
utf16_text, utf16_text + str_len, std::back_inserter(tmp));
l.push_back(tmp);
env->ReleaseStringChars(jstr, utf16_text);
}
if (was_detached)
{
android->activity->vm->DetachCurrentThread();
}
#else
#define SRV_PORT (RRFIXEDSZ+4)
#define SRV_SERVER (RRFIXEDSZ+6)
#define SRV_FIXEDSZ (RRFIXEDSZ+6)
unsigned char response[512] = {};
int response_len = res_query(dns.c_str(), C_IN, T_SRV, response, 512);
if (response_len > 0)
{
HEADER* header = (HEADER*)response;
unsigned char* start = response + NS_HFIXEDSZ;
if ((header->tc) || (response_len < NS_HFIXEDSZ))
return;
if (header->rcode >= 1 && header->rcode <= 5)
return;
int ancount = ntohs(header->ancount);
int qdcount = ntohs(header->qdcount);
if (ancount == 0)
return;
if (ancount > NS_PACKETSZ)
return;
for (int count = qdcount; count > 0; count--)
{
int str_len = dn_skipname(start, response + response_len);
start += str_len + NS_QFIXEDSZ;
}
std::vector<unsigned char*> srv;
for (int count = ancount; count > 0; count--)
{
int str_len = dn_skipname(start, response + response_len);
start += str_len;
srv.push_back(start);
start += SRV_FIXEDSZ;
start += dn_skipname(start, response + response_len);
}
for (unsigned i = 0; i < srv.size(); i++)
{
char server_name[512] = {};
if (ns_name_ntop(srv[i] + SRV_SERVER, server_name, 512) < 0)
continue;
uint16_t port = ns_get16(srv[i] + SRV_PORT);
l.push_back(std::string(server_name) + ":" +
StringUtils::toString(port));
}
}
#endif
} // fillStunList
// ----------------------------------------------------------------------------
const std::vector<std::string>& NetworkConfig::getStunList(bool ipv4)
{
static std::vector<std::string> ipv4_list;
static std::vector<std::string> ipv6_list;
if (ipv4)
{
if (ipv4_list.empty())
NetworkConfig::fillStunList(ipv4_list, stk_config->m_stun_ipv4);
return ipv4_list;
}
else
{
if (ipv6_list.empty())
NetworkConfig::fillStunList(ipv6_list, stk_config->m_stun_ipv6);
return ipv6_list;
}
} // getStunList

View File

@ -120,6 +120,10 @@ private:
* addresses they use the same prefix for each initIPTest. */ * addresses they use the same prefix for each initIPTest. */
std::string m_nat64_prefix; std::string m_nat64_prefix;
std::array<uint32_t, 8> m_nat64_prefix_data; std::array<uint32_t, 8> m_nat64_prefix_data;
// ------------------------------------------------------------------------
static void fillStunList(std::vector<std::string>& l,
const std::string& dns);
public: public:
/** Singleton get, which creates this object if necessary. */ /** Singleton get, which creates this object if necessary. */
static NetworkConfig *get() static NetworkConfig *get()
@ -294,6 +298,8 @@ public:
void setNumFixedAI(unsigned num) { m_num_fixed_ai = num; } void setNumFixedAI(unsigned num) { m_num_fixed_ai = num; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
unsigned getNumFixedAI() const { return m_num_fixed_ai; } unsigned getNumFixedAI() const { return m_num_fixed_ai; }
// ------------------------------------------------------------------------
static const std::vector<std::string>& getStunList(bool ipv4);
}; // class NetworkConfig }; // class NetworkConfig
#endif // HEADER_NETWORK_CONFIG #endif // HEADER_NETWORK_CONFIG

View File

@ -635,13 +635,26 @@ void STKHost::getIPFromStun(int socket, const std::string& stun_address,
*/ */
void STKHost::setPublicAddress(short family) void STKHost::setPublicAddress(short family)
{ {
auto& stunv4_map = UserConfigParams::m_stun_servers_v4;
for (auto& s : NetworkConfig::getStunList(true/*ipv4*/))
{
if (stunv4_map.find(s) == stunv4_map.end())
stunv4_map[s] = 0;
}
auto& stunv6_map = UserConfigParams::m_stun_servers;
for (auto& s : NetworkConfig::getStunList(false/*ipv4*/))
{
if (stunv6_map.find(s) == stunv6_map.end())
stunv6_map[s] = 0;
}
auto& stun_map = family == AF_INET ? UserConfigParams::m_stun_servers_v4 : auto& stun_map = family == AF_INET ? UserConfigParams::m_stun_servers_v4 :
UserConfigParams::m_stun_servers; UserConfigParams::m_stun_servers;
std::vector<std::pair<std::string, uint32_t> > untried_server; std::vector<std::pair<std::string, uint32_t> > untried_server;
for (auto& p : stun_map) for (auto& p : stun_map)
untried_server.push_back(p); untried_server.push_back(p);
assert(untried_server.size() > 2);
// Randomly use stun servers of the low ping from top-half of the list // Randomly use stun servers of the low ping from top-half of the list
std::sort(untried_server.begin(), untried_server.end(), std::sort(untried_server.begin(), untried_server.end(),
[] (const std::pair<std::string, uint32_t>& a, [] (const std::pair<std::string, uint32_t>& a,
@ -651,8 +664,16 @@ void STKHost::setPublicAddress(short family)
}); });
std::random_device rd; std::random_device rd;
std::mt19937 g(rd()); std::mt19937 g(rd());
std::shuffle(untried_server.begin() + (untried_server.size() / 2), if (untried_server.size() > 2)
untried_server.end(), g); {
std::shuffle(untried_server.begin() + (untried_server.size() / 2),
untried_server.end(), g);
}
else
{
Log::warn("STKHost", "Failed to get enough stun servers using SRV"
" record.");
}
while (!untried_server.empty() && !ProtocolManager::lock()->isExiting()) while (!untried_server.empty() && !ProtocolManager::lock()->isExiting())
{ {
@ -698,7 +719,13 @@ void STKHost::setPublicAddress(short family)
untried_server.clear(); untried_server.clear();
} }
else else
{
// Erase from user config in stun, if it's provide by SRV records
// from STK then it will be re-added next time, and STK team will
// remove it if it stops working
stun_map.erase(untried_server.back().first);
untried_server.pop_back(); untried_server.pop_back();
}
} }
} // setPublicAddress } // setPublicAddress