Heavily improving the clients join and leave performance

This commit is contained in:
WolverinDEV
2021-04-15 03:28:08 +02:00
parent f41cfb8c30
commit 6e2e005ed7
21 changed files with 857 additions and 555 deletions
+392 -285
View File
@@ -17,15 +17,13 @@
#include "./voice/VoiceClient.h"
#include "../rtc/imports.h"
#include "../groups/GroupManager.h"
#include "../PermissionCalculator.h"
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::protocol;
//#define PKT_LOG_VOICE
//#define PKT_LOG_WHISPER
SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr<VirtualServer> &b) : ConnectedClient{a, b}, whisper_handler_{this} {
speak_begin = std::chrono::system_clock::now();
speak_last_packet = std::chrono::system_clock::now();
@@ -96,11 +94,211 @@ HWID_REGEX(unix, "^[a-z0-9]{32}$");
HWID_REGEX(android, "^[a-z0-9]{16}$");
HWID_REGEX(ios, "^[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}$");
command_result SpeakingClient::applyClientInitParameters(Command &cmd) {
for(const auto& key : cmd[0].keys()) {
if(key == "return_code") {
/* That's ok */
continue;
} else if(key == "client_nickname") {
auto name = cmd[key].string();
if (count_characters(name) < 3) {
return command_result{error::parameter_invalid, "client_nickname"};
}
if (count_characters(name) > 30) {
return command_result{error::parameter_invalid, "client_nickname"};
}
/* The unique of the name will be checked when registering the client at the target server */
this->properties()[property::CLIENT_NICKNAME] = name;
} else if(key == "client_nickname_phonetic") {
auto name = cmd["client_nickname_phonetic"].string();
if (count_characters(name) > 30) {
return command_result{error::parameter_invalid, "client_nickname_phonetic"};
}
this->properties()[property::CLIENT_NICKNAME_PHONETIC] = name;
} else if(key == "client_version" || key == "client_platform") {
auto value = cmd[key].string();
if(value.length() > 64) {
return command_result{error::client_hacked};
}
for(auto& character : value) {
if(!isascii(character)) {
logWarning(this->getServerId(), "{} Tried to join within an invalid supplied '{}' ({})", CLIENT_STR_LOG_PREFIX, key,cmd[key].string());
return command_result{error::client_hacked};
}
}
if(key == "client_version") {
this->properties()[property::CLIENT_VERSION] = value;
} else {
this->properties()[property::CLIENT_PLATFORM] = value;
}
} else if(key == "client_version_sign") {
/* We currently don't check this parameter nor we need it later. Don't store it. */
} else if(key == "hwid") {
auto value = cmd[key].string();
if(value.length() > 255) {
return command_result{error::parameter_invalid, "hwid"};
}
this->properties()[property::CLIENT_HARDWARE_ID] = value;
} else if(key == "client_input_muted") {
this->properties()[property::CLIENT_INPUT_MUTED] = cmd[key].as<bool>();
} else if(key == "client_input_hardware") {
this->properties()[property::CLIENT_INPUT_HARDWARE] = cmd[key].as<bool>();
} else if(key == "client_output_hardware") {
this->properties()[property::CLIENT_OUTPUT_HARDWARE] = cmd[key].as<bool>();
} else if(key == "client_output_muted") {
this->properties()[property::CLIENT_OUTPUT_MUTED] = cmd[key].as<bool>();
} else if(key == "client_default_channel") {
auto value = cmd[key].string();
if(value.length() > 255) {
return command_result{error::parameter_invalid, "client_default_channel"};
}
this->properties()[property::CLIENT_DEFAULT_CHANNEL] = cmd[key].string();
} else if(key == "client_default_channel_password") {
auto value = cmd[key].string();
if(value.length() > 255) {
return command_result{error::parameter_invalid, "client_default_channel_password"};
}
this->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD] = cmd[key].string();
} else if(key == "client_away") {
this->properties()[property::CLIENT_AWAY] = cmd[key].as<bool>();
} else if(key == "client_away_message") {
auto value = cmd[key].string();
if(value.length() > ts::config::server::limits::afk_message_length) {
return command_result{error::parameter_invalid, "client_away_message"};
}
this->properties()[property::CLIENT_AWAY_MESSAGE] = cmd[key].string();
} else if(key == "client_badges") {
auto value = cmd[key].string();
if(value.length() > 255) {
return command_result{error::parameter_invalid, "client_badges"};
}
this->properties()[property::CLIENT_BADGES] = value;
} else if(key == "client_meta_data") {
auto value = cmd[key].string();
if(value.length() > 65536) {
return command_result{error::parameter_invalid, "client_meta_data"};
}
this->properties()[property::CLIENT_META_DATA] = value;
} else if(key == "client_key_offset") {
/* We don't really care about this value */
} else if(key == "client_server_password") {
/* We don't need to store the password. */
} else if(key == "client_default_token") {
auto value = cmd[key].string();
if(value.length() > 255) {
return command_result{error::parameter_invalid, "client_default_token"};
}
this->properties()[property::CLIENT_DEFAULT_TOKEN] = cmd[key].string();
} else if(key == "client_myteamspeak_id" || key == "myTeamspeakId") {
auto value = cmd[key].string();
if(value.length() > 255) {
return command_result{error::parameter_invalid, "client_myteamspeak_id"};
}
this->properties()[property::CLIENT_MYTEAMSPEAK_ID] = cmd[key].string();
} else if(key == "acTime" || key == "userPubKey" || key == "authSign" || key == "pubSign" || key == "pubSignCert") {
/* Used for the MyTeamSpeak services. We don't store them. */
} else if(key == "client_integrations") {
auto value = cmd[key].string();
if(value.length() > 255) {
return command_result{error::parameter_invalid, "client_integrations"};
}
this->properties()[property::CLIENT_INTEGRATIONS] = cmd[key].string();
} else if(key == "client_active_integrations_info") {
auto value = cmd[key].string();
if(value.length() > 255) {
return command_result{error::parameter_invalid, "client_active_integrations_info"};
}
this->properties()[property::CLIENT_ACTIVE_INTEGRATIONS_INFO] = cmd[key].string();
} else if(key == "client_browser_engine") {
/* Currently not really used but passed by the web client */
} else {
debugMessage(this->getServerId(), "{} Received unknown clientinit parameter {}. Ignoring it.", this->getLoggingPrefix(), key);
}
}
return ts::command_result{error::ok};
}
command_result SpeakingClient::resolveClientInitBan() {
auto active_ban = this->resolveActiveBan(this->getPeerIp());
if(!active_ban) {
return ts::command_result{error::ok};
}
logMessage(this->getServerId(), "{} Disconnecting while init because of ban record. Record id {} at server {}",
CLIENT_STR_LOG_PREFIX,
active_ban->banId,
active_ban->serverId);
serverInstance->banManager()->trigger_ban(active_ban, this->getServerId(), this->getUid(), this->getHardwareId(), this->getDisplayName(), this->getPeerIp());
string fullReason = string() + "You are banned " + (active_ban->serverId == 0 ? "globally" : "from this server") + ". Reason: \"" + active_ban->reason + "\". Ban expires ";
string time;
if(active_ban->until.time_since_epoch().count() != 0) {
time += "in ";
auto seconds = chrono::ceil<chrono::seconds>(active_ban->until - chrono::system_clock::now()).count();
tm p{};
memset(&p, 0, sizeof(p));
while(seconds >= 365 * 24 * 60 * 60){
p.tm_year++;
seconds -= 365 * 24 * 60 * 60;
}
while(seconds >= 24 * 60 * 60){
p.tm_yday++;
seconds -= 24 * 60 * 60;
}
while(seconds >= 60 * 60){
p.tm_hour++;
seconds -= 60 * 60;
}
while(seconds >= 60){
p.tm_min++;
seconds -= 60;
}
p.tm_sec = (int) seconds;
if(p.tm_year > 0) {
time += to_string(p.tm_year) + " years, ";
}
if(p.tm_yday > 0) {
time += to_string(p.tm_yday) + " days, ";
}
if(p.tm_hour > 0) {
time += to_string(p.tm_hour) + " hours, ";
}
if(p.tm_min > 0) {
time += to_string(p.tm_min) + " minutes, ";
}
if(p.tm_sec > 0) {
time += to_string(p.tm_sec) + " seconds, ";
}
if(time.empty()) time = "now, ";
time = time.substr(0, time.length() - 2);
} else time = "never";
fullReason += time + "!";
return command_result{error::server_connect_banned, fullReason};
}
command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
TIMING_START(timings);
/* Firstly check if the client tries to join flood */
{
lock_guard lock(this->server->join_attempts_lock);
lock_guard lock{this->server->join_attempts_lock};
auto client_address = this->getPeerIp();
auto& client_join_attempts = this->server->join_attempts[client_address];
auto& general_join_attempts = this->server->join_attempts["_"];
@@ -116,320 +314,231 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
client_join_attempts++;
general_join_attempts++;
}
TIMING_STEP(timings, "join atmp c");
/* Check if we've enabled the modal quit modal. If so just deny the join in general */
{
auto host_message_mode = this->server->properties()[property::VIRTUALSERVER_HOSTMESSAGE_MODE].as_or<int>(0);
auto quit_message = this->server->properties()[property::VIRTUALSERVER_HOSTMESSAGE].value();
if(host_message_mode == 3) {
return ts::command_result{error::server_modal_quit, quit_message};
}
}
TIMING_STEP(timings, "join limit check");
if(!DatabaseHelper::assignDatabaseId(this->server->getSql(), this->server->getServerId(), this->ref())) {
return command_result{error::vs_critical, "Could not assign database id!"};
}
TIMING_STEP(timings, "db assign ");
{
auto result{this->applyClientInitParameters(cmd)};
if(result.has_error()) {
return result;
}
result.release_data();
}
const static vector<string> available_parameters = {
"client_nickname",
"client_version",
"client_platform",
"client_input_muted",
"client_input_hardware",
"client_output_hardware",
"client_output_muted",
"client_default_channel",
"client_default_channel_password",
"client_server_password",
"client_meta_data",
"client_version_sign",
"client_key_offset",
"client_nickname_phonetic",
"client_default_token",
"client_badges=badges",
"client_badges",
"client_myteamspeak_id",
"client_integrations",
"client_active_integrations_info",
"client_browser_engine",
TIMING_STEP(timings, "state load (db) ");
"client_away",
"client_away_message",
ClientPermissionCalculator permission_calculator{this, nullptr};
"hwid",
"myTeamspeakId",
"acTime",
"userPubKey",
"authSign",
"pubSign",
"pubSignCert"
};
/* Check if the target client ip address has been denied */
if(geoloc::provider && !permission_calculator.permission_granted(permission::b_client_ignore_vpn, 1)) {
auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true);
if(provider) {
auto message = strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side});
return command_result{error::server_connect_banned, message};
}
for(const auto& key : cmd[0].keys()) {
if(key == "return_code") continue;
bool parm_allowed = false;
for(const auto& _allowed_key : available_parameters) {
if(_allowed_key == key) {
parm_allowed = true;
break;
TIMING_STEP(timings, "VPN block test ");
}
/*
* Check if the supplied client hardware id is correct (only applies for TeamSpeak 3 clients)
* This has been created due to Bluescrems request but I think we might want to drop this.
*/
if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
if(permission_calculator.permission_granted(permission::b_client_enforce_valid_hwid, 1)) {
auto hardware_id = this->properties()[property::CLIENT_HARDWARE_ID].value();
if(
!std::regex_match(hardware_id, regex_hwid_windows) &&
!std::regex_match(hardware_id, regex_hwid_unix) &&
!std::regex_match(hardware_id, regex_hwid_android) &&
!std::regex_match(hardware_id, regex_hwid_ios)
) {
return command_result{error::parameter_invalid, config::messages::kick_invalid_hardware_id};
}
}
if(!parm_allowed) {
debugMessage(this->getServerId(), "{} Tried to insert a not allowed parameter within clientinit (Key: {}, Value: {})", CLIENT_STR_LOG_PREFIX, key,cmd[key].string());
continue;
}
if(key == "myTeamspeakId") {
this->properties()[property::CLIENT_MYTEAMSPEAK_ID] = cmd[key].string();
continue;
} else if(key == "acTime") continue;
else if(key == "userPubKey") continue;
else if(key == "authSign") continue;
else if(key == "pubSign") continue;
else if(key == "pubSignCert") continue;
else if(key == "client_version" || key == "client_platform") {
for(auto& character : cmd[key].string())
if(!isascii(character)) {
logWarning(this->getServerId(), "{} Tried to join within an invalid supplied '{}' ({})", CLIENT_STR_LOG_PREFIX, key,cmd[key].string());
return command_result{error::client_hacked};
}
} else if(key == "client_talk_request_msg") {
if(cmd["client_talk_request_msg"].string().length() > ts::config::server::limits::talk_power_request_message_length)
return command_result{error::parameter_invalid_size, "client_talk_request_msg"};
} else if(key == "client_away_message") {
if(cmd["client_away_message"].string().length() > ts::config::server::limits::afk_message_length)
return command_result{error::parameter_invalid_size, "client_away_message"};
} else if(key == "client_nickname_phonetic") {
auto name = cmd["client_nickname_phonetic"].string();
if (count_characters(name) > 30) return command_result{error::parameter_invalid, "client_nickname_phonetic"};
} else if(key == "client_nickname") {
auto name = cmd["client_nickname"].string();
if (count_characters(name) < 3) return command_result{error::parameter_invalid, "client_nickname"};
if (count_characters(name) > 30) return command_result{error::parameter_invalid, "client_nickname"};
}
const auto &info = property::find<property::ClientProperties>(key);
if(info.is_undefined()) {
logError(this->getServerId(), "{} Tried to pass a unknown value {}. Please report this, if you're sure that this key should be known!", CLIENT_STR_LOG_PREFIX, key);
continue;
//return {findError("parameter_invalid"), "Unknown property " + key};
}
if(!info.validate_input(cmd[key].as<string>()))
return command_result{error::parameter_invalid};
this->properties()[info] = cmd[key].as<std::string>();
}
debugMessage(this->getServerId(), "{} Got client init. (HWID: {})", CLIENT_STR_LOG_PREFIX, this->getHardwareId());
TIMING_STEP(timings, "props apply");
auto permissions_list = this->calculate_permissions({
permission::b_virtualserver_join_ignore_password,
permission::b_client_ignore_bans,
permission::b_client_ignore_vpn,
permission::i_client_max_clones_uid,
permission::i_client_max_clones_ip,
permission::i_client_max_clones_hwid,
permission::b_client_enforce_valid_hwid,
permission::b_client_use_reserved_slot
}, 0);
auto permissions = map<permission::PermissionType, permission::v2::PermissionFlaggedValue>(permissions_list.begin(), permissions_list.end());
TIMING_STEP(timings, "perm calc 1");
if(geoloc::provider_vpn && !permission::v2::permission_granted(1, permissions[permission::b_client_ignore_vpn])) {
auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true);
if(provider)
return command_result{error::server_connect_banned, strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side})};
TIMING_STEP(timings, "hwid check ");
}
if(this->getType() == ClientType::CLIENT_TEAMSPEAK && permission::v2::permission_granted(1, permissions[permission::b_client_enforce_valid_hwid])) {
auto hwid = this->properties()[property::CLIENT_HARDWARE_ID].value();
if(
!std::regex_match(hwid, regex_hwid_windows) &&
!std::regex_match(hwid, regex_hwid_unix) &&
!std::regex_match(hwid, regex_hwid_android) &&
!std::regex_match(hwid, regex_hwid_ios)
) {
return command_result{error::parameter_invalid, config::messages::kick_invalid_hardware_id};
}
}
TIMING_STEP(timings, "valid hw ip");
if(!permission::v2::permission_granted(1, permissions[permission::b_virtualserver_join_ignore_password])) {
if(!this->server->verifyServerPassword(cmd["client_server_password"].string(), true)) {
/* Check if the client has supplied a valid password or permissions to ignore it */
if(!this->server->verifyServerPassword(cmd["client_server_password"].string(), true)) {
if(!permission_calculator.permission_granted(permission::b_virtualserver_join_ignore_password, 1)) {
return command_result{error::server_invalid_password};
}
}
TIMING_STEP(timings, "server password ");
/* Maximal client connected clones */
if(!config::server::clients::ignore_max_clone_permissions) {
size_t clones_uid = 0;
size_t clones_ip = 0;
size_t clones_hwid = 0;
size_t clones_hardware_id{0};
size_t clones_unique_id{0};
size_t clones_ip{0};
auto own_hardware_id = this->getHardwareId();
auto own_unique_id = this->getUid();
auto own_ip = this->getPeerIp();
auto _own_hwid = this->getHardwareId();
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
if(client->getExternalType() != CLIENT_TEAMSPEAK) return;
if(client->getUid() == this->getUid())
clones_uid++;
if(client->getPeerIp() == this->getPeerIp())
if(&*client == this) {
return;
}
switch(client->getType()) {
case ClientType::CLIENT_TEASPEAK:
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_WEB:
break;
case ClientType::CLIENT_MUSIC:
case ClientType::CLIENT_INTERNAL:
case ClientType::CLIENT_QUERY:
return;
case ClientType::MAX:
default:
assert(false);
return;
}
if(client->getUid() == this->getUid()) {
clones_unique_id++;
}
if(client->getPeerIp() == this->getPeerIp()) {
clones_ip++;
if(!_own_hwid.empty() && client->getHardwareId() == _own_hwid)
clones_hwid++;
}
if(client->getHardwareId() == own_hardware_id) {
clones_hardware_id++;
}
});
if(clones_uid > 0 && permissions[permission::i_client_max_clones_uid].has_value && !permission::v2::permission_granted(clones_uid, permissions[permission::i_client_max_clones_uid])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} uid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_uid, permissions[permission::i_client_max_clones_uid].value);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (uid)"};
if(clones_unique_id) {
auto max_clones = permission_calculator.calculate_permission(permission::i_client_max_clones_uid);
if(max_clones.has_value && !permission::v2::permission_granted(clones_unique_id, max_clones)) {
return command_result{error:: client_too_many_clones_connected, "too many clones connected (unique id)"};
}
}
if(clones_ip > 0 && permissions[permission::i_client_max_clones_ip].has_value && !permission::v2::permission_granted(clones_ip, permissions[permission::i_client_max_clones_ip])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} ip clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_ip, permissions[permission::i_client_max_clones_ip].value);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (ip)"};
if(clones_ip) {
auto max_clones = permission_calculator.calculate_permission(permission::i_client_max_clones_ip);
if(max_clones.has_value && !permission::v2::permission_granted(clones_ip, max_clones)) {
return command_result{error:: client_too_many_clones_connected, "too many clones connected (ip)"};
}
}
if(clones_hwid > 0 && permissions[permission::i_client_max_clones_hwid].has_value && !permission::v2::permission_granted(clones_hwid, permissions[permission::i_client_max_clones_hwid])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} hwid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_hwid, permissions[permission::i_client_max_clones_hwid].value);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (hwid)"};
}
TIMING_STEP(timings, "max clones ");
}
auto banEntry = this->resolveActiveBan(this->getPeerIp());
if(banEntry) {
logMessage(this->getServerId(), "{} Disconnecting while init because of ban record. Record id {} at server {}",
CLIENT_STR_LOG_PREFIX,
banEntry->banId,
banEntry->serverId);
serverInstance->banManager()->trigger_ban(banEntry, this->getServerId(), this->getUid(), this->getHardwareId(), this->getDisplayName(), this->getPeerIp());
string fullReason = string() + "You are banned " + (banEntry->serverId == 0 ? "globally" : "from this server") + ". Reason: \"" + banEntry->reason + "\". Ban expires ";
string time;
if(banEntry->until.time_since_epoch().count() != 0) {
time += "in ";
auto seconds = chrono::ceil<chrono::seconds>(banEntry->until - chrono::system_clock::now()).count();
tm p{};
memset(&p, 0, sizeof(p));
while(seconds >= 365 * 24 * 60 * 60){
p.tm_year++;
seconds -= 365 * 24 * 60 * 60;
}
while(seconds >= 24 * 60 * 60){
p.tm_yday++;
seconds -= 24 * 60 * 60;
}
while(seconds >= 60 * 60){
p.tm_hour++;
seconds -= 60 * 60;
}
while(seconds >= 60){
p.tm_min++;
seconds -= 60;
}
p.tm_sec = (int) seconds;
if(p.tm_year > 0) {
time += to_string(p.tm_year) + " years, ";
}
if(p.tm_yday > 0) {
time += to_string(p.tm_yday) + " days, ";
}
if(p.tm_hour > 0) {
time += to_string(p.tm_hour) + " hours, ";
}
if(p.tm_min > 0) {
time += to_string(p.tm_min) + " minutes, ";
}
if(p.tm_sec > 0) {
time += to_string(p.tm_sec) + " seconds, ";
}
if(time.empty()) time = "now, ";
time = time.substr(0, time.length() - 2);
} else time = "never";
fullReason += time + "!";
return command_result{error::server_connect_banned, fullReason};
}
TIMING_STEP(timings, "ban resolve");
size_t count = 0;
for(const auto &cl : this->server->getClients()) {
if((cl->getType() == CLIENT_TEAMSPEAK || cl->getType() == CLIENT_WEB || cl->getType() == CLIENT_TEASPEAK || cl->getType() == CLIENT_MUSIC)) {
if(cl->connectionState() <= ConnectionState::CONNECTED && cl->connectionState() >= ConnectionState::INIT_HIGH) {
count++;
/* If we have no hardware id don't count any clones */
if(clones_hardware_id && !own_hardware_id.empty()) {
auto max_clones = permission_calculator.calculate_permission(permission::i_client_max_clones_hwid);
if(max_clones.has_value && !permission::v2::permission_granted(clones_hardware_id, max_clones)) {
return command_result{error:: client_too_many_clones_connected, "too many clones connected (hardware id)"};
}
}
}
TIMING_STEP(timings, "max client clone");
auto maxClients = this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0);
auto reserved = this->server->properties()[property::VIRTUALSERVER_RESERVED_SLOTS].as_or<size_t>(0);
bool allowReserved = permission::v2::permission_granted(1, permissions[permission::b_client_use_reserved_slot]);
if(reserved > maxClients){
if(!allowReserved) {
return command_result{error::server_maxclients_reached};
/* Resolve active bans and deny the connect */
if(!permission_calculator.permission_granted(permission::b_client_ignore_bans, 1)) {
auto result{this->resolveClientInitBan()};
if(result.has_error()) {
return result;
}
} else if(maxClients - (allowReserved ? 0 : reserved) <= count) {
return command_result{error::server_maxclients_reached};
result.release_data();
}
TIMING_STEP(timings, "max clients");
TIMING_STEP(timings, "active ban test ");
auto old_last_connected = this->properties()[property::CLIENT_LASTCONNECTED].as_or<int64_t>(0);
serverInstance->databaseHelper()->updateClientIpAddress(this->getServerId(), this->getClientDatabaseId(), this->getLoggingPeerIp());
this->properties()[property::CLIENT_LASTCONNECTED] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
this->properties()[property::CLIENT_TOTALCONNECTIONS].increment_by<uint64_t>(1);
/* Check if the server might be full */
{
size_t online_client_count{0};
for(const auto &cl : this->server->getClients()) {
switch(cl->getType()) {
case ClientType::CLIENT_TEASPEAK:
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_WEB:
switch(cl->connectionState()) {
case ConnectionState::CONNECTED:
case ConnectionState::INIT_HIGH:
online_client_count++;
break;
case ConnectionState::INIT_LOW:
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
break;
case ConnectionState::UNKNWON:
default:
assert(false);
continue;
}
break;
case ClientType::CLIENT_MUSIC:
case ClientType::CLIENT_INTERNAL:
case ClientType::CLIENT_QUERY:
break;
case ClientType::MAX:
default:
assert(false);
break;
}
}
auto server_client_limit = this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0);
auto server_reserved_slots = this->server->properties()[property::VIRTUALSERVER_RESERVED_SLOTS].as_or<size_t>(0);
auto normal_slots = server_reserved_slots >= server_client_limit ? 0 : server_client_limit - server_reserved_slots;
if(normal_slots <= online_client_count) {
if(server_client_limit <= online_client_count || !permission_calculator.permission_granted(permission::b_client_use_reserved_slot, 1)) {
return command_result{error::server_maxclients_reached};
}
}
}
TIMING_STEP(timings, "server slot test");
/* Update the last connected and total connected statistics and send a permission list update if the server has been updated */
{
auto old_last_connected = this->properties()[property::CLIENT_LASTCONNECTED].as_or<int64_t>(0);
this->properties()[property::CLIENT_LASTCONNECTED] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
this->properties()[property::CLIENT_TOTALCONNECTIONS].increment_by<uint64_t>(1);
auto time_point = system_clock::time_point() + seconds(old_last_connected);
if(time_point < build::version()->timestamp) {
logMessage(this->getServerId(), "{} Client may cached a old permission list (Server is newer than the client's last join)", CLIENT_STR_LOG_PREFIX);
TIMING_STEP(timings, "pre dummy p");
Command _dummy("dummy_permissionslist");
this->handleCommandPermissionList(_dummy);
TIMING_STEP(timings, "pst dummy p");
}
}
this->postCommandHandler.emplace_back([&](){
auto self = dynamic_pointer_cast<SpeakingClient>(this->ref());
std::thread([self](){
if(self->state != ConnectionState::INIT_HIGH) return;
try {
self->processJoin();
} catch (std::exception& ex) {
logError(self->getServerId(), "Failed to proceed client join for {}. Got exception with message {}", CLIENT_STR_LOG_PREFIX_(self), ex.what());
self->close_connection(chrono::system_clock::now() + chrono::seconds{5});
}
}).detach();
});
/* Update the clients IP address within the database */
serverInstance->databaseHelper()->updateClientIpAddress(this->getServerId(), this->getClientDatabaseId(), this->getLoggingPeerIp());
TIMING_STEP(timings, "con stats update");
this->processJoin();
TIMING_STEP(timings, "join client ");
debugMessage(this->getServerId(), "{} Client init timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
return command_result{error::ok};
}
/* must be triggered while helding an execute lock */
void SpeakingClient::processJoin() {
TIMING_START(timings);
this->resetIdleTime();
/* don't process any commands */
std::lock_guard command_lock_{this->command_lock};
auto ref_server = this->server;
assert(ref_server);
if(this->state != ConnectionState::INIT_HIGH) {
logError(this->getServerId(), "{} Invalid processJoin() connection state!", CLIENT_STR_LOG_PREFIX);
return;
}
TIMING_STEP(timings, "setup ");
ref_server->registerClient(this->ref());
this->server->registerClient(this->ref());
{
if(this->rtc_client_id) {
/* in case of client reconnect */
@@ -465,17 +574,6 @@ void SpeakingClient::processJoin() {
debugMessage(this->getServerId(), "{} Could not resolve IP info for {}", this->getLoggingPrefix(), this->getLoggingPeerIp());
}
}
//this->updateChannelClientProperties(false); /* will be already updated via assignChannel */
if(ref_server->properties()[property::VIRTUALSERVER_HOSTMESSAGE_MODE].as_or<int>(0) == 3 && !ref_server->properties()[property::VIRTUALSERVER_HOSTMESSAGE].value().empty()) {
auto weak = this->_this;
threads::Thread([weak](){
threads::self::sleep_for(milliseconds(2000));
auto client = weak.lock();
if(!client || !client->server) return;
client->disconnect(client->server->properties()[property::VIRTUALSERVER_HOSTMESSAGE].value());
}).detach();
}
TIMING_STEP(timings, "ip 2 loc as"); //IP to location assignment
this->sendServerInit();
@@ -483,7 +581,7 @@ void SpeakingClient::processJoin() {
TIMING_STEP(timings, "notify sini");
if(auto token{this->properties()[property::CLIENT_DEFAULT_TOKEN].value()}; !token.empty()){
auto token_info = ref_server->getTokenManager().load_token(token, true);
auto token_info = this->server->getTokenManager().load_token(token, true);
if(token_info) {
if(token_info->is_expired()) {
debugMessage(this->getServerId(), "{} Client tried to use an expired token {}", this->getLoginName(), token);
@@ -491,17 +589,20 @@ void SpeakingClient::processJoin() {
debugMessage(this->getServerId(), "{} Client tried to use an token which reached the use limit {}", this->getLoginName(), token);
} else {
debugMessage(this->getServerId(), "{} Client used token {}", this->getLoginName(), token);
ref_server->getTokenManager().log_token_use(token_info->id);
this->server->getTokenManager().log_token_use(token_info->id);
this->useToken(token_info->id);
}
} else {
debugMessage(this->getServerId(), "{} Client tried to use an unknown token {}", token);
}
/* Clear out the value. We don't need the default token any more */
this->properties()[property::CLIENT_DEFAULT_TOKEN] = "";
}
TIMING_STEP(timings, "token use ");
if(!ref_server->assignDefaultChannel(this->ref(), false)) {
if(!this->server->assignDefaultChannel(this->ref(), false)) {
auto result = command_result{error::vs_critical, "Could not assign default channel!"};
this->notifyError(result);
result.release_data();
@@ -583,12 +684,18 @@ void SpeakingClient::processLeave() {
auto ownLock = this->ref();
auto server = this->getServer();
auto channel = this->currentChannel;
if(server){
if(server) {
if(this->rtc_client_id) {
server->rtc_server().destroy_client(this->rtc_client_id);
this->rtc_client_id = 0;
}
logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} left.", this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()));
{
unique_lock server_channel_lock(this->server->channel_tree_mutex);
server->unregisterClient(ownLock, "disconnected", server_channel_lock); /* already moves client to void if needed */
std::unique_lock server_channel_lock(server->channel_tree_mutex);
server->unregisterClient(ownLock, "disconnected", server_channel_lock);
}
server->music_manager_->cleanup_client_bots(this->getClientDatabaseId());
//ref_server = nullptr; Removed caused nullptr exceptions