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
}