// // 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. #include "network/network_config.hpp" #include "config/stk_config.hpp" #include "config/user_config.hpp" #include "input/device_manager.hpp" #include "modes/world.hpp" #include "network/network.hpp" #include "network/network_string.hpp" #include "network/rewind_manager.hpp" #include "network/server_config.hpp" #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" #include "states_screens/online/online_lan.hpp" #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" #include #ifdef WIN32 # include # include #ifndef __MINGW32__ # pragma comment(lib, "dnsapi.lib") #endif #else # include # include # include # include # include #endif #ifdef ANDROID #include #include "SDL_system.h" std::vector >* g_list = NULL; #define MAKE_ADD_DNS_SRV_RECORD_CALLBACK(x) JNIEXPORT void JNICALL Java_ ## x##_SuperTuxKartActivity_addDNSSrvRecords(JNIEnv* env, jclass cls, jstring name, jint weight) #define ANDROID_ADD_DNS_SRV_RECORD_CALLBACK(PKG_NAME) MAKE_ADD_DNS_SRV_RECORD_CALLBACK(PKG_NAME) extern "C" ANDROID_ADD_DNS_SRV_RECORD_CALLBACK(ANDROID_PACKAGE_CALLBACK_NAME) { if (!g_list || name == NULL) return; const uint16_t* utf16_text = (const uint16_t*)env->GetStringChars(name, NULL); if (utf16_text == NULL) return; const size_t str_len = env->GetStringLength(name); std::string tmp; utf8::unchecked::utf16to8( utf16_text, utf16_text + str_len, std::back_inserter(tmp)); g_list->emplace_back(tmp, weight); env->ReleaseStringChars(name, utf16_text); } #endif NetworkConfig *NetworkConfig::m_network_config[PT_COUNT]; bool NetworkConfig::m_system_ipv4 = false; bool NetworkConfig::m_system_ipv6 = false; /** Initialize detection of system IPv4 or IPv6 support. */ void NetworkConfig::initSystemIP() { // It calls WSAStartup in enet, for the rest new Network function we don't // need this because request manager runs curl_global_init which will do // WSAStartup too if (enet_initialize() != 0) { Log::error("NetworkConfig", "Could not initialize enet."); return; } ENetAddress eaddr = {}; setIPv6Socket(0); auto ipv4 = std::unique_ptr(new Network(1, 1, 0, 0, &eaddr)); setIPv6Socket(1); auto ipv6 = std::unique_ptr(new Network(1, 1, 0, 0, &eaddr)); setIPv6Socket(0); if (ipv4 && ipv4->getENetHost()) m_system_ipv4 = true; if (ipv6 && ipv6->getENetHost()) m_system_ipv6 = true; // If any 1 of them is missing set default network setting accordingly if (!m_system_ipv4) { Log::warn("NetworkConfig", "System doesn't support IPv4"); if (m_system_ipv6) { UserConfigParams::m_ipv6_lan = true; ServerConfig::m_ipv6_connection = true; } } else if (!m_system_ipv6) { Log::warn("NetworkConfig", "System doesn't support IPv6"); UserConfigParams::m_ipv6_lan = false; ServerConfig::m_ipv6_connection = false; } enet_deinitialize(); } // initSystemIP /** \class NetworkConfig * This class is the interface between STK and the online code, particularly * STKHost. It stores all online related properties (e.g. if this is a server * or a host, name of the server, maximum number of players, ip address, ...). * They can either be set from the GUI code, or via the command line (for a * stand-alone server). * When STKHost is created, it takes all necessary information from this * instance. */ // ============================================================================ /** Constructor. */ NetworkConfig::NetworkConfig() { m_ip_type = IP_NONE; m_network_type = NETWORK_NONE; m_auto_connect = false; m_is_server = false; m_is_public_server = false; m_done_adding_network_players = false; m_cur_user_id = 0; m_cur_user_token = ""; m_client_port = 0; m_joined_server_version = 0; m_network_ai_instance = false; m_state_frequency = 10; m_nat64_prefix_data.fill(-1); m_num_fixed_ai = 0; m_tux_hitbox_addon = false; } // NetworkConfig // ---------------------------------------------------------------------------- /** Separated from constructor because this needs to be run after user config * is load. */ void NetworkConfig::initClientPort() { m_client_port = UserConfigParams::m_random_client_port ? 0 : stk_config->m_client_port; } // initClientPort // ---------------------------------------------------------------------------- /** Set that this is not a networked game. */ void NetworkConfig::unsetNetworking() { clearServerCapabilities(); m_network_type = NETWORK_NONE; ServerConfig::m_private_server_password = ""; } // unsetNetworking // ---------------------------------------------------------------------------- void NetworkConfig::setUserDetails(std::shared_ptr r, const std::string& name) { assert(!m_cur_user_token.empty()); r->setApiURL(Online::API::USER_PATH, name); r->addParameter("userid", m_cur_user_id); r->addParameter("token", m_cur_user_token); } // setUserDetails // ---------------------------------------------------------------------------- void NetworkConfig::setServerDetails(std::shared_ptr r, const std::string& name) { assert(!m_cur_user_token.empty()); r->setApiURL(Online::API::SERVER_PATH, name); r->addParameter("userid", m_cur_user_id); r->addParameter("token", m_cur_user_token); } // setServerDetails // ---------------------------------------------------------------------------- std::vector NetworkConfig::getResetScreens(bool lobby) const { if (lobby) { if (isWAN()) { return { MainMenuScreen::getInstance(), OnlineScreen::getInstance(), OnlineProfileServers::getInstance(), NetworkingLobby::getInstance(), nullptr }; } else { return { MainMenuScreen::getInstance(), OnlineScreen::getInstance(), OnlineLanScreen::getInstance(), NetworkingLobby::getInstance(), nullptr }; } } else { if (isWAN()) { return { MainMenuScreen::getInstance(), OnlineScreen::getInstance(), OnlineProfileServers::getInstance(), nullptr }; } else { return { MainMenuScreen::getInstance(), OnlineScreen::getInstance(), OnlineLanScreen::getInstance(), nullptr }; } } } // getResetScreens // ---------------------------------------------------------------------------- /** Called before (re)starting network race, must be used before adding * split screen players. */ void NetworkConfig::clearActivePlayersForClient() const { if (!isClient()) return; StateManager::get()->resetActivePlayers(); if (input_manager) { input_manager->getDeviceManager()->setAssignMode(NO_ASSIGN); input_manager->getDeviceManager()->setSinglePlayer(NULL); input_manager->setMasterPlayerOnly(false); input_manager->getDeviceManager()->clearLatestUsedDevice(); } } // clearActivePlayersForClient // ---------------------------------------------------------------------------- /** True when client needs to round the bodies phyiscal info for current * ticks, server doesn't as it will be done implictly in save state. */ bool NetworkConfig::roundValuesNow() const { return isNetworking() && !isServer() && RewindManager::get() ->shouldSaveState(World::getWorld()->getTicksSinceStart()); } // roundValuesNow // ---------------------------------------------------------------------------- #ifdef ENABLE_IPV6 std::vector > g_ipv4_detection; std::vector > 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::queueIPDetection() { if (UserConfigParams::m_default_ip_type != IP_NONE || !m_system_ipv4 || !m_system_ipv6) return; #ifdef ENABLE_IPV6 auto& stunv4_map = UserConfigParams::m_stun_servers_v4; for (auto& s : getStunList(true/*ipv4*/)) { if (s.second == 0) stunv4_map.erase(s.first); else if (stunv4_map.find(s.first) == stunv4_map.end()) stunv4_map[s.first] = 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& stunv6_map = UserConfigParams::m_stun_servers; for (auto& s : getStunList(false/*ipv4*/)) { if (s.second == 0) stunv6_map.erase(s.first); else if (stunv6_map.find(s.first) == stunv6_map.end()) stunv6_map[s.first] = 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; std::unique_ptr ipv4_detect( new StunDetection(ipv4_it->first, true/*ipv4*/)); std::unique_ptr 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; if (!m_system_ipv4 || !m_system_ipv6) { has_ipv4 = m_system_ipv4; has_ipv6 = m_system_ipv6; goto end; } 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) { // 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 ipv4only.arpa (RFC 7050) m_nat64_prefix.clear(); m_nat64_prefix_data.fill(-1); SocketAddress nat64("ipv4only.arpa", 0/*port*/, AF_INET6); if (nat64.getFamily() == AF_INET6) { // Remove last 4 bytes which is IPv4 format struct sockaddr_in6* in6 = (struct sockaddr_in6*)nat64.getSockaddr(); uint8_t* byte = &(in6->sin6_addr.s6_addr[0]); byte[12] = 0; byte[13] = 0; byte[14] = 0; byte[15] = 0; m_nat64_prefix_data[0] = ((uint32_t)(byte[0]) << 8) | byte[1]; m_nat64_prefix_data[1] = ((uint32_t)(byte[2]) << 8) | byte[3]; m_nat64_prefix_data[2] = ((uint32_t)(byte[4]) << 8) | byte[5]; m_nat64_prefix_data[3] = ((uint32_t)(byte[6]) << 8) | byte[7]; m_nat64_prefix_data[4] = ((uint32_t)(byte[8]) << 8) | byte[9]; m_nat64_prefix_data[5] = ((uint32_t)(byte[10]) << 8) | byte[11]; m_nat64_prefix_data[6] = 0; m_nat64_prefix_data[7] = 0; m_nat64_prefix = getIPV6ReadableFromIn6(in6); } } } if (has_ipv4 && has_ipv6) { Log::info("NetworkConfig", "System is dual stack network."); m_nat64_prefix.clear(); m_nat64_prefix_data.fill(-1); m_ip_type = IP_DUAL_STACK; } else if (has_ipv4) { Log::info("NetworkConfig", "System is IPv4 only."); m_nat64_prefix.clear(); m_nat64_prefix_data.fill(-1); 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, " "using previously detected type: %d", (int)m_ip_type.load()); } 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()); } } #else m_ip_type = IP_V4; #endif } // getIPDetectionResult // ---------------------------------------------------------------------------- void NetworkConfig::fillStunList(std::vector >* 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->emplace_back( StringUtils::wideToUtf8(curr->Data.SRV.pNameTarget) + ":" + StringUtils::toString(curr->Data.SRV.wPort), curr->Data.SRV.wWeight); } } DnsRecordListFree(dns_record, DnsFreeRecordListDeep); } #elif defined(ANDROID) JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv(); if (env == NULL) { Log::error("NetworkConfig", "getDNSSrvRecords unable to SDL_AndroidGetJNIEnv."); return; } jobject native_activity = (jobject)SDL_AndroidGetActivity(); if (native_activity == NULL) { Log::error("NetworkConfig", "getDNSSrvRecords unable to SDL_AndroidGetActivity."); return; } jclass class_native_activity = env->GetObjectClass(native_activity); if (class_native_activity == NULL) { Log::error("NetworkConfig", "getDNSSrvRecords unable to find object class."); env->DeleteLocalRef(native_activity); return; } jmethodID method_id = env->GetMethodID(class_native_activity, "getDNSSrvRecords", "(Ljava/lang/String;)V"); if (method_id == NULL) { Log::error("NetworkConfig", "getDNSSrvRecords unable to find method id."); env->DeleteLocalRef(class_native_activity); env->DeleteLocalRef(native_activity); return; } std::vector 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."); env->DeleteLocalRef(class_native_activity); env->DeleteLocalRef(native_activity); return; } g_list = l; env->CallVoidMethod(native_activity, method_id, text); env->DeleteLocalRef(text); env->DeleteLocalRef(class_native_activity); env->DeleteLocalRef(native_activity); g_list = NULL; #elif !defined(__CYGWIN__) #define SRV_WEIGHT (RRFIXEDSZ+2) #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 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_uncompress(response, response + response_len, srv[i] + SRV_SERVER, server_name, 512) < 0) continue; uint16_t port = ns_get16(srv[i] + SRV_PORT); uint16_t weight = ns_get16(srv[i] + SRV_WEIGHT); l->emplace_back(std::string(server_name) + ":" + StringUtils::toString(port), weight); } } #endif } // fillStunList // ---------------------------------------------------------------------------- const std::vector >& NetworkConfig::getStunList(bool ipv4) { static std::vector > ipv4_list; static std::vector > 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