diff --git a/NETWORKING.md b/NETWORKING.md index a51b7b75b..06ecd6466 100644 --- a/NETWORKING.md +++ b/NETWORKING.md @@ -183,6 +183,9 @@ The current server configuration xml looks like this: + + + @@ -341,6 +344,15 @@ CREATE TABLE ip_mapping longitude REAL NOT NULL, -- Longitude of this IP range country_code TEXT NOT NULL -- 2-letter country code ) WITHOUT ROWID; + +CREATE TABLE ipv6_mapping +( + ip_start INTEGER UNSIGNED NOT NULL PRIMARY KEY UNIQUE, -- IP decimal (upper 64bit) start + ip_end INTEGER UNSIGNED NOT NULL UNIQUE, -- IP decimal (upper 64bit) end + latitude REAL NOT NULL, -- Latitude of this IP range + longitude REAL NOT NULL, -- Longitude of this IP range + country_code TEXT NOT NULL -- 2-letter country code +) ``` For initialization of `ip_mapping` table, check [this script](tools/generate-ip-mappings.py). diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index e60b068ce..4576a4380 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -63,6 +63,26 @@ #ifdef ENABLE_SQLITE3 + +// ---------------------------------------------------------------------------- +static void upperIPv6SQL(sqlite3_context* context, int argc, + sqlite3_value** argv) +{ + if (argc != 1) + { + sqlite3_result_int64(context, 0); + return; + } + + char* ipv6 = (char*)sqlite3_value_text(argv[0]); + if (ipv6 == NULL) + { + sqlite3_result_int64(context, 0); + return; + } + sqlite3_result_int64(context, upperIPv6(ipv6)); +} + // ---------------------------------------------------------------------------- void insideIPv6CIDRSQL(sqlite3_context* context, int argc, sqlite3_value** argv) @@ -97,6 +117,8 @@ sqlite3_extension_init(sqlite3* db, char** pzErrMsg, SQLITE_EXTENSION_INIT2(pApi) sqlite3_create_function(db, "insideIPv6CIDR", 2, SQLITE_UTF8, NULL, insideIPv6CIDRSQL, NULL, NULL); + sqlite3_create_function(db, "upperIPv6", 1, SQLITE_UTF8, 0, upperIPv6SQL, + 0, 0); return 0; } // sqlite3_extension_init */ @@ -256,6 +278,7 @@ void ServerLobby::initDatabase() m_ipv6_ban_table_exists = false; m_online_id_ban_table_exists = false; m_ip_geolocation_table_exists = false; + m_ipv6_geolocation_table_exists = false; if (!ServerConfig::m_sql_management) return; const std::string& path = ServerConfig::getConfigDirectory() + "/" + @@ -285,6 +308,8 @@ void ServerLobby::initDatabase() }, NULL); sqlite3_create_function(m_db, "insideIPv6CIDR", 2, SQLITE_UTF8, NULL, &insideIPv6CIDRSQL, NULL, NULL); + sqlite3_create_function(m_db, "upperIPv6", 1, SQLITE_UTF8, NULL, + &upperIPv6SQL, NULL, NULL); checkTableExists(ServerConfig::m_ip_ban_table, m_ip_ban_table_exists); checkTableExists(ServerConfig::m_ipv6_ban_table, m_ipv6_ban_table_exists); checkTableExists(ServerConfig::m_online_id_ban_table, @@ -293,6 +318,8 @@ void ServerLobby::initDatabase() m_player_reports_table_exists); checkTableExists(ServerConfig::m_ip_geolocation_table, m_ip_geolocation_table_exists); + checkTableExists(ServerConfig::m_ipv6_geolocation_table, + m_ipv6_geolocation_table_exists); #endif } // initDatabase @@ -1144,6 +1171,47 @@ std::string ServerLobby::ip2Country(const TransportAddress& addr) const return cc_code; } // ip2Country +//----------------------------------------------------------------------------- +std::string ServerLobby::ipv62Country(const std::string& ipv6) const +{ + if (!m_db || !m_ipv6_geolocation_table_exists) + return ""; + + std::string cc_code; + std::string query = StringUtils::insertValues( + "SELECT country_code FROM %s " + "WHERE `ip_start` <= upperIPv6(\"%s\") AND `ip_end` >= upperIPv6(\"%s\") " + "ORDER BY `ip_start` DESC LIMIT 1;", + ServerConfig::m_ipv6_geolocation_table.c_str(), ipv6.c_str(), + ipv6.c_str()); + + sqlite3_stmt* stmt = NULL; + int ret = sqlite3_prepare_v2(m_db, query.c_str(), -1, &stmt, 0); + if (ret == SQLITE_OK) + { + ret = sqlite3_step(stmt); + if (ret == SQLITE_ROW) + { + const char* country_code = (char*)sqlite3_column_text(stmt, 0); + cc_code = country_code; + } + ret = sqlite3_finalize(stmt); + if (ret != SQLITE_OK) + { + Log::error("ServerLobby", + "Error finalize database for query %s: %s", + query.c_str(), sqlite3_errmsg(m_db)); + } + } + else + { + Log::error("ServerLobby", "Error preparing database for query %s: %s", + query.c_str(), sqlite3_errmsg(m_db)); + return ""; + } + return cc_code; +} // ipv62Country + #endif //----------------------------------------------------------------------------- @@ -3317,8 +3385,10 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, } #ifdef ENABLE_SQLITE3 - if (country_code.empty()) + if (country_code.empty() && peer->getIPV6Address().empty()) country_code = ip2Country(peer->getAddress()); + if (country_code.empty() && !peer->getIPV6Address().empty()) + country_code = ipv62Country(peer->getIPV6Address()); #endif auto red_blue = STKHost::get()->getAllPlayersTeamInfo(); diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index face5ff2d..356d026d3 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -91,6 +91,8 @@ private: bool m_ip_geolocation_table_exists; + bool m_ipv6_geolocation_table_exists; + uint64_t m_last_poll_db_time; void pollDatabase(); @@ -101,6 +103,8 @@ private: void checkTableExists(const std::string& table, bool& result); std::string ip2Country(const TransportAddress& addr) const; + + std::string ipv62Country(const std::string& ipv6) const; #endif void initDatabase(); diff --git a/src/network/server_config.hpp b/src/network/server_config.hpp index 487f1fa20..83c335cbe 100644 --- a/src/network/server_config.hpp +++ b/src/network/server_config.hpp @@ -412,6 +412,16 @@ namespace ServerConfig "empty to disable. " "This table can be shared for all servers if you use the same name.")); + SERVER_CFG_PREFIX StringServerConfigParam m_ipv6_geolocation_table + SERVER_CFG_DEFAULT(StringServerConfigParam("ipv6_mapping", + "ipv6-geolocation-table", + "IPv6 geolocation table, you only need this table if you want to " + "geolocate IP from non-stk-addons connection, as all validated " + "players connecting from stk-addons will provide the location info, " + "you need to create the table first, see NETWORKING.md for details, " + "empty to disable. " + "This table can be shared for all servers if you use the same name.")); + SERVER_CFG_PREFIX BoolServerConfigParam m_ai_handling SERVER_CFG_DEFAULT(BoolServerConfigParam(false, "ai-handling", "If true this server will auto add / remove AI connected with " diff --git a/src/network/stk_ipv6.cpp b/src/network/stk_ipv6.cpp index 791bb9ca5..4768631d2 100644 --- a/src/network/stk_ipv6.cpp +++ b/src/network/stk_ipv6.cpp @@ -138,6 +138,23 @@ void andIPv6(struct in6_addr* ipv6, const struct in6_addr* mask) ipv6->s6_addr[i] &= mask->s6_addr[i]; } // andIPv6 +// ---------------------------------------------------------------------------- +extern "C" int64_t upperIPv6(const char* ipv6) +{ + struct in6_addr v6_in; + if (inet_pton(AF_INET6, ipv6, &v6_in) != 1) + return 0; + uint64_t result = 0; + unsigned shift = 56; + for (unsigned i = 0; i < 8; i++) + { + uint64_t val = v6_in.s6_addr[i]; + result += val << shift; + shift -= 8; + } + return result; +} + // ---------------------------------------------------------------------------- extern "C" int insideIPv6CIDR(const char* ipv6_cidr, const char* ipv6_in) { diff --git a/src/network/stk_ipv6.hpp b/src/network/stk_ipv6.hpp index 80b9e6a09..69623d377 100644 --- a/src/network/stk_ipv6.hpp +++ b/src/network/stk_ipv6.hpp @@ -29,6 +29,7 @@ void getMappedFromIPV6(const struct sockaddr_in6* in6, ENetAddress* ea); void addMappedAddress(const ENetAddress* ea, const struct sockaddr_in6* in6); int getaddrinfo_compat(const char* hostname, const char* servname, const struct addrinfo* hints, struct addrinfo** res); +int64_t upperIPv6(const char* ipv6); int insideIPv6CIDR(const char* ipv6_cidr, const char* ipv6_in); #ifdef __cplusplus }