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.ResolverResult;
import org.minidns.record.SRV;
import org.minidns.record.TXT;
public class SuperTuxKartActivity extends NativeActivity
@ -244,4 +245,24 @@ public class SuperTuxKartActivity extends NativeActivity
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"
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
time for skidmarks to fade out. Maximum number will over
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);
}
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"))
{
fonts_list->get("normal-ttf", &m_normal_ttf);

View File

@ -207,6 +207,10 @@ public:
std::string m_password_reset_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. */
std::vector<std::string> m_normal_ttf;
std::vector<std::string> m_digit_ttf;

View File

@ -138,7 +138,7 @@ public:
irr::core::stringc toString() const;
operator std::map<T, U>() const
operator std::map<T, U>&() const
{
return m_elements;
}
@ -150,7 +150,19 @@ public:
{
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);
return m_elements;
@ -771,29 +783,13 @@ namespace UserConfigParams
PARAM_DEFAULT(StringToUIntUserConfigParam("stun-servers-ipv4",
"The stun servers that will be used to know the public address "
"(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_DEFAULT(StringToUIntUserConfigParam("stun-servers-ipv6",
"The stun servers that will be used to know the public address "
"(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_DEFAULT(GroupUserConfigParam("Network", "Network Settings"));

View File

@ -35,13 +35,27 @@
#include "states_screens/online/online_profile_servers.hpp"
#include "states_screens/online/online_screen.hpp"
#include "states_screens/state_manager.hpp"
#include "utils/string_utils.hpp"
#include "utils/time.hpp"
#include "utils/utf8/unchecked.h"
#ifdef WIN32
# include <winsock2.h>
# include <windns.h>
# include <ws2tcpip.h>
#ifndef __MINGW32__
# pragma comment(lib, "dnsapi.lib")
#endif
#else
# include <arpa/nameser.h>
# include <arpa/nameser_compat.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
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));
setIPv6Socket(0);
auto ipv4_it = UserConfigParams::m_stun_servers_v4.begin();
int adv = StkTime::getMonoTimeMs() % UserConfigParams::m_stun_servers_v4.size();
auto& stunv4_map = UserConfigParams::m_stun_servers_v4;
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);
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);
SocketAddress::g_ignore_error_message = true;
@ -343,3 +374,206 @@ void NetworkConfig::detectIPType()
m_ip_type = IP_V4;
#endif
} // 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. */
std::string m_nat64_prefix;
std::array<uint32_t, 8> m_nat64_prefix_data;
// ------------------------------------------------------------------------
static void fillStunList(std::vector<std::string>& l,
const std::string& dns);
public:
/** Singleton get, which creates this object if necessary. */
static NetworkConfig *get()
@ -294,6 +298,8 @@ public:
void setNumFixedAI(unsigned num) { m_num_fixed_ai = num; }
// ------------------------------------------------------------------------
unsigned getNumFixedAI() const { return m_num_fixed_ai; }
// ------------------------------------------------------------------------
static const std::vector<std::string>& getStunList(bool ipv4);
}; // class NetworkConfig
#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)
{
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 :
UserConfigParams::m_stun_servers;
std::vector<std::pair<std::string, uint32_t> > untried_server;
for (auto& p : stun_map)
untried_server.push_back(p);
assert(untried_server.size() > 2);
// Randomly use stun servers of the low ping from top-half of the list
std::sort(untried_server.begin(), untried_server.end(),
[] (const std::pair<std::string, uint32_t>& a,
@ -651,8 +664,16 @@ void STKHost::setPublicAddress(short family)
});
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(untried_server.begin() + (untried_server.size() / 2),
untried_server.end(), g);
if (untried_server.size() > 2)
{
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())
{
@ -698,7 +719,13 @@ void STKHost::setPublicAddress(short family)
untried_server.clear();
}
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();
}
}
} // setPublicAddress