From 04dd5a8bfb0747bf28156f6b90814c473b4f15ba Mon Sep 17 00:00:00 2001 From: Benau Date: Fri, 25 Oct 2019 12:44:50 +0800 Subject: [PATCH] Add IPv6 ban table --- NETWORKING.md | 18 +++- src/network/protocols/server_lobby.cpp | 120 ++++++++++++++++++++++++- src/network/protocols/server_lobby.hpp | 3 + src/network/server_config.hpp | 11 ++- 4 files changed, 148 insertions(+), 4 deletions(-) diff --git a/NETWORKING.md b/NETWORKING.md index c83ea9734..79e2fe6fd 100644 --- a/NETWORKING.md +++ b/NETWORKING.md @@ -159,9 +159,12 @@ The current server configuration xml looks like this: - + + + + @@ -223,7 +226,7 @@ You have the best gaming experience when choosing server having all players less Currently STK uses sqlite (if building with sqlite3 on) for server management with the following functions at the moment: 1. Server statistics -2. IPv4 / online ID ban list +2. IP / online ID ban list 3. Player reports 4. IPv4 geolocation @@ -286,6 +289,17 @@ CREATE TABLE ip_ban last_trigger TIMESTAMP NULL DEFAULT NULL -- Latest time this banning entry was triggered ); +CREATE TABLE ipv6_ban +( + ipv6_cidr TEXT NOT NULL UNIQUE, -- IPv6 CIDR range for banning (for example 2001::/64), use /128 for a specific ip + starting_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Starting time of this banning entry to be effective + expired_days REAL NULL DEFAULT NULL, -- Days for this banning to be expired, use NULL for a permanent ban + reason TEXT NOT NULL DEFAULT '', -- Banned reason shown in user stk menu, can be empty + description TEXT NOT NULL DEFAULT '', -- Private description for server admin + trigger_count INTEGER UNSIGNED NOT NULL DEFAULT 0, -- Number of banning triggered by this ban entry + last_trigger TIMESTAMP NULL DEFAULT NULL -- Latest time this banning entry was triggered +); + CREATE TABLE online_id_ban ( online_id INTEGER UNSIGNED NOT NULL UNIQUE, -- Online id from STK addons database for banning diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 4ed84e4f5..64c5913e6 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -218,6 +218,7 @@ void ServerLobby::initDatabase() m_last_poll_db_time = StkTime::getMonoTimeMs(); m_db = NULL; m_ip_ban_table_exists = false; + m_ipv6_ban_table_exists = false; m_online_id_ban_table_exists = false; m_ip_geolocation_table_exists = false; if (!ServerConfig::m_sql_management) @@ -248,6 +249,7 @@ void ServerLobby::initDatabase() sqlite3_create_function(m_db, "insideIPv6CIDR", 2, SQLITE_UTF8, NULL, &insideIPv6CIDRSQL, 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, m_online_id_ban_table_exists); checkTableExists(ServerConfig::m_player_reports_table, @@ -830,6 +832,40 @@ void ServerLobby::pollDatabase() }, &peers, NULL); } + if (m_ipv6_ban_table_exists) + { + std::string query = + "SELECT ipv6_cidr, reason, description FROM "; + query += ServerConfig::m_ipv6_ban_table; + query += " WHERE datetime('now') > datetime(starting_time) AND " + "(expired_days is NULL OR datetime" + "(starting_time, '+'||expired_days||' days') > datetime('now'));"; + auto peers = STKHost::get()->getPeers(); + sqlite3_exec(m_db, query.c_str(), + [](void* ptr, int count, char** data, char** columns) + { + std::vector >* peers_ptr = + (std::vector >*)ptr; + for (std::shared_ptr& p : *peers_ptr) + { + // IPv6 ban list atm + if (p->isAIPeer() || p->getIPV6Address().empty()) + continue; + + char* ipv6_cidr = data[0]; + if (insideIPv6CIDR(ipv6_cidr, + p->getIPV6Address().c_str()) == 1) + { + Log::info("ServerLobby", + "Kick %s, reason: %s, description: %s", + p->getIPV6Address().c_str(), data[1], data[2]); + p->kick(); + } + } + return 0; + }, &peers, NULL); + } + if (m_online_id_ban_table_exists) { std::string query = "SELECT online_id, reason, description FROM "; @@ -3008,6 +3044,10 @@ void ServerLobby::connectionRequested(Event* event) if (peer->isDisconnected()) return; + testBannedForIPv6(peer.get()); + if (peer->isDisconnected()) + return; + if (online_id != 0) testBannedForOnlineId(peer.get(), online_id); // Will be disconnected if banned by online id @@ -4117,7 +4157,7 @@ void ServerLobby::testBannedForIP(STKPeer* peer) const if (!m_db || !m_ip_ban_table_exists) return; - // We only test for IPv4 atm + // Test for IPv4 if (!peer->getIPV6Address().empty()) return; @@ -4177,6 +4217,84 @@ void ServerLobby::testBannedForIP(STKPeer* peer) const #endif } // testBannedForIP +//----------------------------------------------------------------------------- +void ServerLobby::testBannedForIPv6(STKPeer* peer) const +{ +#ifdef ENABLE_SQLITE3 + if (!m_db || !m_ipv6_ban_table_exists) + return; + + // Test for IPv6 + if (peer->getIPV6Address().empty()) + return; + + int row_id = -1; + std::string ipv6_cidr; + std::string query = StringUtils::insertValues( + "SELECT rowid, ipv6_cidr, reason, description FROM %s " + "WHERE insideIPv6CIDR(ipv6_cidr, ?) = 1 " + "AND datetime('now') > datetime(starting_time) AND " + "(expired_days is NULL OR datetime" + "(starting_time, '+'||expired_days||' days') > datetime('now')) " + "LIMIT 1;", + ServerConfig::m_ipv6_ban_table.c_str()); + + sqlite3_stmt* stmt = NULL; + int ret = sqlite3_prepare_v2(m_db, query.c_str(), -1, &stmt, 0); + if (ret == SQLITE_OK) + { + if (sqlite3_bind_text(stmt, 1, peer->getIPV6Address().c_str(), + -1, SQLITE_TRANSIENT) != SQLITE_OK) + { + Log::error("ServerLobby", "Error binding ipv6 addr for query: %s", + sqlite3_errmsg(m_db)); + } + + ret = sqlite3_step(stmt); + if (ret == SQLITE_ROW) + { + row_id = sqlite3_column_int(stmt, 0); + ipv6_cidr = (char*)sqlite3_column_text(stmt, 1); + const char* reason = (char*)sqlite3_column_text(stmt, 2); + const char* desc = (char*)sqlite3_column_text(stmt, 3); + Log::info("ServerLobby", "%s banned by IP: %s " + "(rowid: %d, description: %s).", + peer->getRealAddress().c_str(), reason, row_id, desc); + kickPlayerWithReason(peer, reason); + } + 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; + } + if (row_id != -1) + { + query = StringUtils::insertValues( + "UPDATE %s SET trigger_count = trigger_count + 1, " + "last_trigger = datetime('now') " + "WHERE ipv6_cidr = ?;", ServerConfig::m_ipv6_ban_table.c_str()); + easySQLQuery(query, [ipv6_cidr](sqlite3_stmt* stmt) + { + if (sqlite3_bind_text(stmt, 1, ipv6_cidr.c_str(), + -1, SQLITE_TRANSIENT) != SQLITE_OK) + { + Log::error("easySQLQuery", "Failed to bind %s.", + ipv6_cidr.c_str()); + } + }); + } +#endif +} // testBannedForIPv6 + //----------------------------------------------------------------------------- void ServerLobby::testBannedForOnlineId(STKPeer* peer, uint32_t online_id) const diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index b815aac4e..f339d777b 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -80,6 +80,8 @@ private: bool m_ip_ban_table_exists; + bool m_ipv6_ban_table_exists; + bool m_online_id_ban_table_exists; bool m_ip_geolocation_table_exists; @@ -325,6 +327,7 @@ private: void clientSelectingAssetsWantsToBackLobby(Event* event); void kickPlayerWithReason(STKPeer* peer, const char* reason) const; void testBannedForIP(STKPeer* peer) const; + void testBannedForIPv6(STKPeer* peer) const; void testBannedForOnlineId(STKPeer* peer, uint32_t online_id) const; void writeDisconnectInfoTable(STKPeer* peer); void writePlayerReport(Event* event); diff --git a/src/network/server_config.hpp b/src/network/server_config.hpp index 8458a2146..069c00e0c 100644 --- a/src/network/server_config.hpp +++ b/src/network/server_config.hpp @@ -351,7 +351,16 @@ namespace ServerConfig SERVER_CFG_PREFIX StringServerConfigParam m_ip_ban_table SERVER_CFG_DEFAULT(StringServerConfigParam("ip_ban", "ip-ban-table", - "Ip ban list table name, you need to create the table first, see " + "IPv4 ban list table name, 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. " + "STK can auto kick active peer from ban list (update per minute) which" + "allows live kicking peer by inserting record to database.")); + + SERVER_CFG_PREFIX StringServerConfigParam m_ipv6_ban_table + SERVER_CFG_DEFAULT(StringServerConfigParam("ipv6_ban", + "ipv6-ban-table", + "IPv6 ban list table name, 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. " "STK can auto kick active peer from ban list (update per minute) which"