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

@ -1 +1 @@
Subproject commit 4dfe9fbf023777cbc6dbb278cd1e161fe464edb4
Subproject commit 7a52b2d2c469ece2b13ea49d017ce2d6989fea00

View File

@ -90,12 +90,14 @@ bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
}
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK && cl->getType() == ClientType::CLIENT_WEB) {
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK || cl->getType() == ClientType::CLIENT_TEASPEAK || cl->getType() == ClientType::CLIENT_WEB) {
sassert(cl->state == ConnectionState::DISCONNECTED);
}
auto client_id = cl->getClientId();
if(client_id == 0) return false; /* not registered */
if(client_id == 0) {
return false; /* not registered */
}
{
lock_guard lock(this->clients.lock);
@ -252,6 +254,10 @@ bool VirtualServer::assignDefaultChannel(const shared_ptr<ConnectedClient>& clie
skip_permissions:;
}
/* Clear these parameters. We don't need them any more after we initially payed attention. */
client->properties()[property::CLIENT_DEFAULT_CHANNEL] = "";
client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD] = "";
if(!channel) {
/* Client did not propose a channel or the proposed channel got rejected */
channel = this->channelTree->getDefaultChannel();

View File

@ -65,6 +65,22 @@ void VirtualServer::executeServerTick() {
size_t clientOnline{0};
size_t queryOnline{0};
for(const auto& conn : client_list){
switch(conn->connectionState()) {
case ConnectionState::CONNECTED:
case ConnectionState::INIT_HIGH:
break;
case ConnectionState::INIT_LOW:
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
continue;
case ConnectionState::UNKNWON:
default:
assert(false);
continue;
}
switch (conn->getType()) {
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_TEASPEAK:

View File

@ -237,7 +237,7 @@ namespace ts {
bool granted = false
);
bool verifyServerPassword(std::string, bool hashed = false);
[[nodiscard]] bool verifyServerPassword(std::string /* password */, bool /* hashed */);
void testBanStateChange(const std::shared_ptr<ConnectedClient>& invoker);

View File

@ -847,19 +847,21 @@ void ConnectedClient::sendServerInit() {
command["client_integrations"] = this->properties()[property::CLIENT_INTEGRATIONS].value();
if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_INSTANCE) {
if(serverInstance->getVoiceServerManager()->usedSlots() <= 32)
if(serverInstance->getVoiceServerManager()->usedSlots() <= 32) {
command["lt"] = LicenseType::LICENSE_NONE;
else if(serverInstance->getVoiceServerManager()->usedSlots() <= 512)
} else if(serverInstance->getVoiceServerManager()->usedSlots() <= 512) {
command["lt"] = LicenseType::LICENSE_NPL;
else
} else {
command["lt"] = LicenseType::LICENSE_HOSTING;
}
} else if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_SERVER){
if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0) <= 32)
if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0) <= 32) {
command["lt"] = LicenseType::LICENSE_NONE;
else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0) <= 512)
} else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0) <= 512) {
command["lt"] = LicenseType::LICENSE_NPL;
else
} else {
command["lt"] = LicenseType::LICENSE_HOSTING;
}
} else {
command["lt"] = ts::config::server::DefaultServerLicense;
}
@ -954,7 +956,9 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
}
std::shared_ptr<BanRecord> ConnectedClient::resolveActiveBan(const std::string& ip_address) {
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, 0))) return nullptr;
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, 0))) {
return nullptr;
}
//Check if manager banned
auto banManager = serverInstance->banManager();

View File

@ -368,7 +368,8 @@ namespace ts {
sockaddr_storage remote_address;
//General states
std::mutex state_lock;
/* TODO: Make this a shared lock and lock the client state via holding the lock when executing commands etc */
std::mutex state_lock{};
ConnectionState state{ConnectionState::UNKNWON};
bool allowedToTalk{false};

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,215 +94,153 @@ 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::handleCommandClientInit(Command& cmd) {
TIMING_START(timings);
{
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["_"];
if(config::voice::clientConnectLimit > 0 && client_join_attempts + 1 > config::voice::clientConnectLimit) {
return command_result{error::client_join_rate_limit_reached};
}
if(config::voice::connectLimit > 0 && general_join_attempts + 1 > config::voice::connectLimit) {
return command_result{error::server_join_rate_limit_reached};
}
client_join_attempts++;
general_join_attempts++;
}
TIMING_STEP(timings, "join atmp c");
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 ");
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",
"client_away",
"client_away_message",
"hwid",
"myTeamspeakId",
"acTime",
"userPubKey",
"authSign",
"pubSign",
"pubSignCert"
};
command_result SpeakingClient::applyClientInitParameters(Command &cmd) {
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;
}
}
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());
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"};
}
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())
/* 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};
}
} 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"};
}
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") {
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"};
auto value = cmd[key].string();
if(value.length() > ts::config::server::limits::afk_message_length) {
return command_result{error::parameter_invalid, "client_away_message"};
}
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};
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"};
}
if(!info.validate_input(cmd[key].as<string>()))
return command_result{error::parameter_invalid};
this->properties()[info] = cmd[key].as<std::string>();
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"};
}
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})};
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"};
}
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};
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"};
}
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)) {
return command_result{error::server_invalid_password};
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);
}
}
if(!config::server::clients::ignore_max_clone_permissions) {
size_t clones_uid = 0;
size_t clones_ip = 0;
size_t clones_hwid = 0;
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())
clones_ip++;
if(!_own_hwid.empty() && client->getHardwareId() == _own_hwid)
clones_hwid++;
});
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)"};
return ts::command_result{error::ok};
}
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)"};
command_result SpeakingClient::resolveClientInitBan() {
auto active_ban = this->resolveActiveBan(this->getPeerIp());
if(!active_ban) {
return ts::command_result{error::ok};
}
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());
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 " + (banEntry->serverId == 0 ? "globally" : "from this server") + ". Reason: \"" + banEntry->reason + "\". Ban expires ";
string fullReason = string() + "You are banned " + (active_ban->serverId == 0 ? "globally" : "from this server") + ". Reason: \"" + active_ban->reason + "\". Ban expires ";
string time;
if(banEntry->until.time_since_epoch().count() != 0) {
if(active_ban->until.time_since_epoch().count() != 0) {
time += "in ";
auto seconds = chrono::ceil<chrono::seconds>(banEntry->until - chrono::system_clock::now()).count();
auto seconds = chrono::ceil<chrono::seconds>(active_ban->until - chrono::system_clock::now()).count();
tm p{};
memset(&p, 0, sizeof(p));
@ -356,80 +292,253 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
return command_result{error::server_connect_banned, fullReason};
}
TIMING_STEP(timings, "ban resolve");
size_t count = 0;
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};
auto client_address = this->getPeerIp();
auto& client_join_attempts = this->server->join_attempts[client_address];
auto& general_join_attempts = this->server->join_attempts["_"];
if(config::voice::clientConnectLimit > 0 && client_join_attempts + 1 > config::voice::clientConnectLimit) {
return command_result{error::client_join_rate_limit_reached};
}
if(config::voice::connectLimit > 0 && general_join_attempts + 1 > config::voice::connectLimit) {
return command_result{error::server_join_rate_limit_reached};
}
client_join_attempts++;
general_join_attempts++;
}
/* 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!"};
}
{
auto result{this->applyClientInitParameters(cmd)};
if(result.has_error()) {
return result;
}
result.release_data();
}
TIMING_STEP(timings, "state load (db) ");
ClientPermissionCalculator permission_calculator{this, nullptr};
/* 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};
}
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};
}
}
TIMING_STEP(timings, "hwid check ");
}
/* 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_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();
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
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(client->getHardwareId() == own_hardware_id) {
clones_hardware_id++;
}
});
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) {
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 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");
/* 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;
}
result.release_data();
}
TIMING_STEP(timings, "active ban test ");
/* Check if the server might be full */
{
size_t online_client_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++;
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 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);
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);
bool allowReserved = permission::v2::permission_granted(1, permissions[permission::b_client_use_reserved_slot]);
if(reserved > maxClients){
if(!allowReserved) {
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};
}
} else if(maxClients - (allowReserved ? 0 : reserved) <= count) {
return command_result{error::server_maxclients_reached};
}
TIMING_STEP(timings, "max clients");
}
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);
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);
{
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(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

View File

@ -78,6 +78,10 @@ namespace ts::server {
virtual command_result handleCommandBroadcastVideoConfig(Command &);
virtual command_result handleCommandBroadcastVideoConfigure(Command &);
/* clientinit method helpers */
command_result applyClientInitParameters(Command&);
command_result resolveClientInitBan();
void triggerVoiceEnd();
void updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &time);
std::chrono::milliseconds speak_accuracy{1000};

View File

@ -2004,19 +2004,15 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto cldbid = cmd["cldbid"].as<ClientDbId>();
if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid))
return command_result{error::parameter_invalid, "Invalid manager db id"};
RESOLVE_CHANNEL_R(cmd["cid"], true);
auto channel = dynamic_pointer_cast<ServerChannel>(l_channel->entry);
if (!channel) return command_result{error::vs_critical};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), cldbid);
{
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
}
auto target_client_database_id = cmd["cldbid"].as<ClientDbId>();
ClientPermissionCalculator target_client_permission_calculator{this->server, target_client_database_id, ClientType::CLIENT_TEAMSPEAK, channel_id};
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, target_client_permission_calculator.calculate_permission(permission::i_client_needed_permission_modify_power), channel_id);
auto target_permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), target_client_database_id);
ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false};
if (!pparser.validate(this->ref(), channel->channelId()))
@ -2024,20 +2020,20 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
bool update_view{false};
for (const auto &ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId());
ppermission.apply_to_channel(target_permission_manager, permission::v2::PermissionUpdateType::delete_value, channel->channelId());
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CLIENT_CHANNEL,
permission::v2::PermissionUpdateType::delete_value,
cldbid, "",
target_client_database_id, "",
channel->channelId(), channel->name());
update_view |= ppermission.is_client_view_property();
}
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
serverInstance->databaseHelper()->saveClientPermissions(this->server, target_client_database_id, target_permission_manager);
auto onlineClients = this->server->findClientsByCldbId(cldbid);
auto onlineClients = this->server->findClientsByCldbId(target_client_database_id);
if (!onlineClients.empty()) {
for (const auto &elm : onlineClients) {
elm->task_update_needed_permissions.enqueue();
@ -2073,17 +2069,16 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto cldbid = cmd["cldbid"].as<ClientDbId>();
if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid))
return command_result{error::parameter_invalid, "Invalid client db id"};
RESOLVE_CHANNEL_R(cmd["cid"], true);
auto channel = dynamic_pointer_cast<ServerChannel>(l_channel->entry);
if (!channel) return command_result{error::vs_critical};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), cldbid);
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
auto target_client_database_id = cmd["cldbid"].as<ClientDbId>();
ClientPermissionCalculator target_client_permission_calculator{this->server, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id};
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, target_client_permission_calculator.calculate_permission(permission::i_client_needed_permission_modify_power), channel_id);
auto target_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), target_client_database_id);
ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true};
if (!pparser.validate(this->ref(), channel->channelId()))
@ -2091,21 +2086,21 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
bool update_view{false};
for (const auto &ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId());
ppermission.apply_to_channel(target_permissions, permission::v2::PermissionUpdateType::set_value, channel->channelId());
ppermission.log_update(serverInstance->action_logger()->permission_logger,
this->getServerId(),
this->ref(),
log::PermissionTarget::CLIENT_CHANNEL,
permission::v2::PermissionUpdateType::set_value,
cldbid, "",
target_client_database_id, "",
channel->channelId(), channel->name());
update_view |= ppermission.is_client_view_property();
}
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
serverInstance->databaseHelper()->saveClientPermissions(this->server, target_client_database_id, target_permissions);
auto onlineClients = this->server->findClientsByCldbId(cldbid);
auto onlineClients = this->server->findClientsByCldbId(target_client_database_id);
if (!onlineClients.empty())
for (const auto &elm : onlineClients) {
elm->task_update_needed_permissions.enqueue();

View File

@ -495,8 +495,13 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
unique_ptr<lock_guard<std::recursive_mutex>> nickname_lock;
std::deque<std::pair<const property::PropertyDescription*, std::string>> keys;
for(const auto& key : cmd[0].keys()) {
if(key == "return_code") continue;
if(key == "clid") continue;
if(key == "return_code") {
continue;
}
if(key == "clid") {
continue;
}
const auto &info = property::find<property::ClientProperties>(key);
if(info == property::CLIENT_UNDEFINED) {

View File

@ -705,6 +705,10 @@ command_result ConnectedClient::handleCommandGroupDel(Command &cmd, GroupTarget
result = group_manager->channel_groups()->delete_group(group->group_id());
break;
default:
assert(false);
return ts::command_result{error::vs_critical};
}
switch(result) {

View File

@ -73,50 +73,29 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
auto client = this->connection->getCurrentClient();
assert(client);
std::unique_lock state_lock{client->state_lock};
if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */
auto lastPingResponse = std::max(this->connection->ping_handler().last_ping_response(), this->connection->ping_handler().last_command_acknowledged());
if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) {
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
this->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
);
return ts::command_result{error::ok};
} else if(!config::voice::allow_session_reinitialize) {
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings",
this->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
);
return ts::command_result{error::ok};
}
logMessage(this->connection->virtual_server_id(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt",
this->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
);
state_lock.unlock();
{
std::unique_lock server_channel_lock(client->server->get_channel_tree_lock()); /* we cant get moved if this is locked! */
if(client->currentChannel) {
client->server->client_move(client->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock);
}
}
client->finalDisconnect();
state_lock.lock();
} else if(client->state >= ConnectionState::DISCONNECTING) {
state_lock.unlock();
std::shared_lock disconnect_finish{client->finalDisconnectLock}; /* await until the last disconnect has been processed */
state_lock.lock();
std::lock_guard state_lock{client->state_lock};
switch(client->state) {
case ConnectionState::INIT_LOW:
client->state = ConnectionState::INIT_HIGH;
} else if(client->state == ConnectionState::INIT_HIGH) {
break;
case ConnectionState::INIT_HIGH:
logTrace(client->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX_(client));
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
} else {
client->state = ConnectionState::INIT_HIGH;
case ConnectionState::CONNECTED:
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
/* That's really odd an should not happen */
return CommandHandleResult::PASS_THROUGH;
case ConnectionState::UNKNWON:
default:
assert(false);
return CommandHandleResult::PASS_THROUGH;
}
}
state_lock.unlock();
this->connection->reset();
this->connection->packet_decoder().register_initiv_packet();

View File

@ -3,10 +3,8 @@
#include <tomcrypt.h>
#include <arpa/inet.h>
#include <ThreadPool/Timer.h>
#include <misc/endianness.h>
#include <misc/memtracker.h>
#include <log/LogUtils.h>
#include <ThreadPool/ThreadHelper.h>
#include "VoiceClient.h"
#include "src/InstanceHandler.h"
@ -51,12 +49,9 @@ VoiceClient::~VoiceClient() {
delete this->connection;
this->connection = nullptr;
{
std::lock_guard fthread_lock{this->flush_thread_mutex};
if(this->flushing_thread) {
logCritical(this->getServerId(), "Deleting a VoiceClient which should still be hold within the flush thread!");
this->flushing_thread->detach();
}
if(this->flush_task) {
logCritical(this->getServerId(), "VoiceClient deleted with an active pending flush task!");
serverInstance->general_task_executor()->cancel_task(this->flush_task);
}
memtrack::freed<VoiceClient>(this);
@ -102,56 +97,99 @@ bool VoiceClient::disconnect(const std::string &reason) {
}
bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reason, const std::shared_ptr<ts::server::ConnectedClient>& invoker, bool notify_viewer) {
/*
* We don't have to lock the disconnect lock here, because we're not really unregistering the client.
* Its only for the clients own flavour and everything which the client receives after will be ignored :)
*/
task_id disconnect_id;
ConnectionState old_state{};
{
std::lock_guard state_lock{this->state_lock};
if(this->state == ConnectionState::DISCONNECTING || this->state == ConnectionState::DISCONNECTED)
return false; //Already disconnecting/disconnected
old_state = this->state;
this->state = ConnectionState::DISCONNECTING;
auto weak_client{this->weak_ref()};
serverInstance->general_task_executor()->schedule(disconnect_id, "voice client disconnect of " + this->getLoggingPrefix(), [
weak_client,
reason_id,
reason,
invoker,
notify_viewer
]{
auto client = dynamic_pointer_cast<VoiceClient>(weak_client.lock());
if(!client) {
/* client has already been deallocated */
return;
}
if(old_state == ConnectionState::CONNECTED) {
/* Client has been successflly initialized; Send normal disconnect. */
bool await_disconnect_ack{false};
{
std::lock_guard state_lock{client->state_lock};
switch (client->state) {
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
/* somebody else already disconnected the client */
return;
Command cmd("notifyclientleftview");
cmd["reasonmsg"] = reason;
cmd["reasonid"] = reason_id;
cmd["clid"] = this->getClientId();
cmd["cfid"] = this->currentChannel ? this->currentChannel->channelId() : 0; //Failed when cid = 0????
cmd["ctid"] = 0;
case ConnectionState::INIT_HIGH:
case ConnectionState::INIT_LOW:
/* disconnect the client and close the connection */
break;
case ConnectionState::CONNECTED:
await_disconnect_ack = true;
break;
case ConnectionState::UNKNWON:
default:
assert(false);
return;
}
client->state = ConnectionState::DISCONNECTING;
}
/* server should never be a nullptr */
assert(client->server);
ChannelId client_channel{client->getChannelId()};
{
std::lock_guard command_lock{client->command_lock};
std::unique_lock server_tree_lock{client->server->channel_tree_mutex};
if(client->currentChannel) {
if(notify_viewer) {
client->server->client_move(client->ref(), nullptr, invoker, reason, reason_id, false, server_tree_lock);
} else {
auto server_channel = dynamic_pointer_cast<ServerChannel>(client->currentChannel);
assert(server_channel);
server_channel->unregister_client(client);
client->currentChannel = nullptr;
}
}
}
{
ts::command_builder notify{"notifyclientleftview"};
notify.put_unchecked(0, "reasonmsg", reason);
notify.put_unchecked(0, "reasonid", reason_id);
notify.put_unchecked(0, "clid", client->getClientId());
notify.put_unchecked(0, "cfid", client_channel);
notify.put_unchecked(0, "ctid", "0");
if (invoker) {
cmd["invokerid"] = invoker->getClientId();
cmd["invokername"] = invoker->getDisplayName();
cmd["invokeruid"] = invoker->getUid();
notify.put_unchecked(0, "invokerid", invoker->getClientId());
notify.put_unchecked(0, "invokername", invoker->getDisplayName());
notify.put_unchecked(0, "invokeruid", invoker->getUid());
}
auto old_channel = this->currentChannel;
if(old_channel) {
serverInstance->action_logger()->client_channel_logger.log_client_leave(this->getServerId(), this->ref(), old_channel->channelId(), old_channel->name());
}
if(notify_viewer && this->server) {
unique_lock channel_lock(this->server->channel_tree_mutex);
this->server->client_move(this->ref(), nullptr, invoker, reason, reason_id, false, channel_lock);
if(await_disconnect_ack) {
{
std::lock_guard flush_lock{client->flush_mutex};
if(!client->disconnect_acknowledged.has_value()) {
client->disconnect_acknowledged = std::make_optional(false);
} else {
threads::MutexLock lock(this->command_lock);
auto server_channel = dynamic_pointer_cast<ServerChannel>(this->currentChannel);
if(server_channel)
server_channel->unregister_client(this->ref());
this->currentChannel = nullptr;
/*
* The disconnect acknowledged flag might already has been set to true in cases
* where we know that the client is aware of the disconnect.
* An example scenario would be when the client sends the `clientdisconnect` command.
*/
}
}
auto weak_self = this->weak_ref();
this->sendCommand0(cmd.build(), false, std::make_unique<std::function<void(bool)>>([weak_self](bool success) {
auto self = weak_self.lock();
client->sendCommand0(notify.build(), false, std::make_unique<std::function<void(bool)>>([weak_client](bool success){
auto self = dynamic_pointer_cast<VoiceClient>(weak_client.lock());
if(!self) {
return;
}
@ -163,85 +201,109 @@ bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reas
debugMessage(self->getServerId(), "{} Received disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self));
}
self->close_connection(chrono::system_clock::time_point{}); /* we received the ack, we do not need to flush anything */
std::lock_guard flush_lock{self->flush_mutex};
assert(self->disconnect_acknowledged.has_value());
*self->disconnect_acknowledged = true;
}));
} else {
//TODO: Extra case for INIT_HIGH?
this->close_connection(chrono::system_clock::now() + chrono::seconds{5});
client->sendCommand(notify, false);
}
}
/* close the connection after 5 seconds regardless if we've received a disconnect acknowledge */
client->close_connection(chrono::system_clock::now() + chrono::seconds{5});
});
return true;
}
bool VoiceClient::connection_flushed() {
/* Ensure that we've at least send everything we wanted to send */
if(!this->connection->wait_empty_write_and_prepare_queue(std::chrono::system_clock::time_point{})) {
/* we still want to write data */
return false;
}
{
std::lock_guard flush_lock{this->flush_mutex};
if(this->disconnect_acknowledged.has_value()) {
/* We've send a disconnect command to the client. If the client acknowledges this command we've nothing more to send. */
return *this->disconnect_acknowledged;
}
}
if(this->connection->packet_encoder().acknowledge_manager().awaiting_acknowledge() > 0) {
/* We're still waiting for some acknowledges from the client (for example the disconnect/kick packet) */
return false;
}
return true;
}
bool VoiceClient::close_connection(const system_clock::time_point &timeout) {
auto self_lock = dynamic_pointer_cast<VoiceClient>(this->ref());
assert(self_lock); //Should never happen!
std::lock_guard flush_lock{this->flush_mutex};
if(this->flush_executed) {
/* We've already scheduled a connection close. We only update the timeout. */
this->flush_timeout = std::min(timeout, this->flush_timeout);
return true;
}
this->flush_timeout = timeout;
auto weak_client{this->weak_ref()};
serverInstance->general_task_executor()->schedule_repeating(this->flush_task, "connection flush/close for " + this->getLoggingPrefix(), std::chrono::milliseconds{25}, [weak_client](const auto&){
auto client = dynamic_pointer_cast<VoiceClient>(weak_client.lock());
if(!client) {
/* client has already been deallocated */
return;
}
/* Dont execute any commands which might alter the current client state */
std::lock_guard command_lock{client->command_lock};
bool flush = timeout.time_since_epoch().count() > 0;
{
std::lock_guard state_lock{this->state_lock};
std::lock_guard state_lock{client->state_lock};
switch(client->state) {
case ConnectionState::DISCONNECTED:
/* somebody else already disconnected the client */
return;
if(this->state == ConnectionState::DISCONNECTED) {
return false;
} else if(this->state == ConnectionState::DISCONNECTING) {
/* here is nothing to pay attention for */
} else if(this->state == ConnectionState::DISCONNECTING_FLUSHING) {
if(!flush) {
this->state = ConnectionState::DISCONNECTED;
return true; /* the flush thread will execute the final disconnect */
case ConnectionState::CONNECTED:
case ConnectionState::INIT_HIGH:
case ConnectionState::INIT_LOW:
/* It's the first call to this task. Setting the clients state to disconnecting */
break;
case ConnectionState::DISCONNECTING:
/* We're just awaiting for the client to finish stuff */
break;
case ConnectionState::UNKNWON:
default:
assert(false);
return;
}
client->state = ConnectionState::DISCONNECTING;
}
auto timestamp_now = std::chrono::system_clock::now();
auto flushed = client->connection_flushed();
if(!flushed && client->flush_timeout >= timestamp_now) {
/* connection hasn't yet been flushed */
return;
}
if(flushed) {
debugMessage(client->getServerId(), "{} Connection successfully flushed.", client->getLoggingPrefix());
} else {
//TODO: May update the flush timeout if its less then the other one?
return true;
}
}
this->state = flush ? ConnectionState::DISCONNECTING_FLUSHING : ConnectionState::DISCONNECTED;
debugMessage(client->getServerId(), "{} Connection flush timed out. Force closing connection.", client->getLoggingPrefix());
}
debugMessage(this->getServerId(), "{} Closing voice client connection. (Flush: {})", CLIENT_STR_LOG_PREFIX, flush);
std::lock_guard fthread_lock{this->flush_thread_mutex};
//TODO: Move this out into a thread pool?
if(this->flushing_thread && this->flushing_thread->joinable()) {
logCritical(LOG_GENERAL, "VoiceClient::close_connection reached flushing thread with an active old handle. Ignoring request.");
return true;
}
auto flush_thread = std::make_shared<std::thread>([this, self_lock, timeout, flush]{
{
/* Await that all commands have been processed. It does not make sense to unregister the client while command handling. */
std::lock_guard cmd_lock{this->command_lock};
}
if(flush) {
debugMessage(this->getServerId(), "{} Awaiting write prepare, write and acknowledge queue flushed", CLIENT_STR_LOG_PREFIX);
while(this->state == DISCONNECTING_FLUSHING) {
if(system_clock::now() > timeout){
auto write_queue_flushed = this->connection->wait_empty_write_and_prepare_queue(timeout);
auto acknowledge_received = connection->packet_encoder().acknowledge_manager().awaiting_acknowledge() == 0;
if(write_queue_flushed && acknowledge_received)
break;
debugMessage(this->getServerId(), "{} Failed to flush pending messages. Acknowledges pending: {} Buffers pending: {}", CLIENT_STR_LOG_PREFIX, acknowledge_received, write_queue_flushed);
break;
}
if(!this->connection->wait_empty_write_and_prepare_queue(timeout))
continue;
if(connection->packet_encoder().acknowledge_manager().awaiting_acknowledge() > 0) {
usleep(5000);
continue;
}
debugMessage(this->getServerId(), "{} Write and acknowledge queue are flushed", CLIENT_STR_LOG_PREFIX);
break;
}
}
if(this->state > DISCONNECTING) /* it could happen that the client "reconnects" while flushing this shit */
this->finalDisconnect();
/* connection flushed or flush timed out. */
client->finalDisconnect();
});
this->flushing_thread = flush_thread;
threads::name(*flush_thread, "Flush thread VC");
return true;
}
@ -249,24 +311,33 @@ void VoiceClient::finalDisconnect() {
auto ownLock = dynamic_pointer_cast<VoiceClient>(this->ref());
assert(ownLock);
lock_guard disconnect_lock_final(this->finalDisconnectLock);
if(this->final_disconnected) {
logError(this->getServerId(), "Tried to final disconnect {}/{} twice", this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()), this->getDisplayName());
{
std::lock_guard state_lock{this->state_lock};
if(this->state != ConnectionState::DISCONNECTING) {
logCritical(this->getServerId(), "{} finalDisconnect called but state isn't disconnecting ({}).", this->getLoggingPrefix(), (uint32_t) this->state);
return;
}
this->final_disconnected = true;
this->state = ConnectionState::DISCONNECTED;
threads::MutexLock command_lock(this->command_lock); //We should not progress any commands while disconnecting
//Unload manager cache
this->processLeave();
this->state = ConnectionState::DISCONNECTED;
}
{
std::lock_guard fthread_lock{this->flush_thread_mutex};
if(this->flushing_thread) {
this->flushing_thread->detach(); //The thread itself should be already done or executing this method
std::lock_guard flush_lock{this->flush_mutex};
this->flush_executed = true;
if(this->flush_task) {
serverInstance->general_task_executor()->cancel_task(this->flush_task);
this->flush_task = 0;
}
this->flushing_thread = nullptr;
}
/* TODO: Remove? (legacy) */
{
lock_guard disconnect_lock_final(this->finalDisconnectLock);
}
std::lock_guard command_lock{this->command_lock};
this->processLeave();
if(this->voice_server) {
this->voice_server->unregisterConnection(ownLock);
}

View File

@ -63,6 +63,11 @@ namespace ts {
bool close_connection(const std::chrono::system_clock::time_point &timeout) override;
bool disconnect(const std::string&) override;
/*
* TODO: Use a helper class called InvokerDescription containing the invoker properties and not holding a whole connected client reference
* 2. May use some kind of class to easily set the disconnect reason?
*/
bool disconnect(ViewReasonId /* reason type */, const std::string& /* reason */, const std::shared_ptr<ts::server::ConnectedClient>& /* invoker */, bool /* notify viewer */);
void sendCommand(const ts::Command &command, bool low = false) override { return this->sendCommand0(command.build(), low, nullptr); }
@ -102,7 +107,9 @@ namespace ts {
private:
void finalDisconnect();
bool final_disconnected = false;
/* Used by close_connection to determine if we've successfully flushed the connection */
[[nodiscard]] bool connection_flushed();
rtc::NativeAudioSourceSupplier rtc_audio_supplier{};
rtc::NativeAudioSourceSupplier rtc_audio_whisper_supplier{};
@ -110,13 +117,14 @@ namespace ts {
uint16_t stop_seq_counter{0};
uint16_t whisper_head_counter{0};
//General TS3 manager commands
command_result handleCommandClientInit(Command&) override;
command_result handleCommandClientDisconnect(Command&);
//Locked by finalDisconnect, disconnect and close connection
std::mutex flush_thread_mutex{};
std::shared_ptr<std::thread> flushing_thread;
std::mutex flush_mutex{};
task_id flush_task{0};
bool flush_executed{false};
std::chrono::system_clock::time_point flush_timeout{};
std::optional<bool> disconnect_acknowledged{}; /* locked by flush_mutex */
std::unique_ptr<ServerCommandQueue> server_command_queue_{};
};

View File

@ -21,6 +21,27 @@ bool VoiceClientCommandHandler::handle_command(const std::string_view &command_s
return false;
}
std::lock_guard command_lock{client->command_lock};
{
std::lock_guard state_lock{client->state_lock};
switch(client->state) {
case ConnectionState::INIT_LOW:
case ConnectionState::INIT_HIGH:
case ConnectionState::CONNECTED:
break;
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
/* we're just dropping the command and all future commands */
return false;
case ConnectionState::UNKNWON:
default:
assert(false);
return false;
}
}
client->handlePacketCommand(command_string);
return true;
}
@ -88,8 +109,13 @@ command_result VoiceClient::handleCommandClientInit(Command &cmd) {
if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
auto client_identity = this->connection->crypt_setup_handler().identity_key();
auto client_key_offset = cmd["client_key_offset"].string();
if(client_key_offset.length() > 128 || client_key_offset.find_first_not_of("0123456789") != std::string::npos) {
return command_result{error::parameter_invalid, "client_key_offset"};
}
int security_level;
if(!calculate_security_level(security_level, &*client_identity, cmd["client_key_offset"].string())) {
if(!calculate_security_level(security_level, &*client_identity, client_key_offset)) {
logError(this->getServerId(), "[{}] Failed to calculate security level. Error code: {}", CLIENT_STR_LOG_PREFIX, security_level);
return command_result{error::vs_critical};
}
@ -109,18 +135,22 @@ command_result VoiceClient::handleCommandClientInit(Command &cmd) {
command_result VoiceClient::handleCommandClientDisconnect(Command& cmd) {
auto reason = cmd["reasonmsg"].size() > 0 ? cmd["reasonmsg"].as<string>() : "";
{
std::shared_lock own_lock{this->channel_tree_mutex};
this->notifyClientLeftView(this->ref(), nullptr, VREASON_SERVER_LEFT, reason, nullptr, false); //Before we're moving us out of the channel tree!
if(reason.empty()) {
debugMessage(this->getServerId(), "{} Received client disconnect with no custom reason.", CLIENT_STR_LOG_PREFIX);
} else {
debugMessage(this->getServerId(), "{} Received client disconnect with custom reason: {}", CLIENT_STR_LOG_PREFIX, reason);
}
if(this->state == CONNECTED) {
std::unique_lock channel_lock{this->server->channel_tree_mutex};
this->server->client_move(this->ref(), nullptr, nullptr, reason, VREASON_SERVER_LEFT, true, channel_lock);
}
logMessage(this->getServerId(), "{} Got remote disconnect with the reason '{}'", CLIENT_STR_LOG_PREFIX, reason);
this->disconnect(VREASON_SERVER_LEFT, reason, nullptr, true);
this->postCommandHandler.push_back([&]{
this->close_connection(std::chrono::system_clock::now() + std::chrono::seconds{1}); /* send acknowledge and close connection */
/*
* Instantly set the disconnect acknowledged flag since we actually received it from the client.
*/
std::lock_guard flush_lock{this->flush_mutex};
this->disconnect_acknowledged = std::make_optional(true);
});
return command_result{error::ok};
return ts::command_result{error::ok};
}

View File

@ -264,11 +264,21 @@ void VoiceClientConnection::callback_resend_failed(void *ptr_this, const shared_
debugMessage(connection->virtual_server_id_, "{} Failed to execute packet resend of packet {}. Dropping connection.", connection->log_prefix(), entry->packet_full_id);
auto client = connection->getCurrentClient();
assert(client); /* TIXME! */
if(client->state == ConnectionState::CONNECTED) {
client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true);
} else {
client->close_connection(system_clock::now() + seconds(1));
}
{
/*
* The connection timeout out.
* We don't expect any disconnect packages to be acknowledges so we don't need to
* await for such.
*/
std::lock_guard lock{client->flush_mutex};
client->disconnect_acknowledged = std::make_optional(true);
}
client->close_connection(std::chrono::system_clock::time_point{});
}
void VoiceClientConnection::callback_resend_statistics(void *ptr_this, size_t send_count) {

View File

@ -282,16 +282,70 @@ void POWHandler::handle_puzzle_solve(const std::shared_ptr<ts::server::POWHandle
}
shared_ptr<VoiceClient> POWHandler::register_verified_client(const std::shared_ptr <ts::server::POWHandler::Client> &client) {
shared_ptr<VoiceClient> voice_client;
std::shared_ptr<VoiceClient> voice_client;
{
lock_guard lock(this->server->connectionLock);
for(const auto& connection : this->server->activeConnections) {
if(memcmp(&connection->remote_address, &client->address, sizeof(client->address)) == 0){
if(memcmp(&connection->remote_address, &client->address, sizeof(client->address)) != 0) {
continue;
}
switch(connection->connectionState()) {
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
/* Connection already disconnecting/disconnected. Don't use this connection. */
continue;
case ConnectionState::INIT_LOW:
case ConnectionState::INIT_HIGH:
/* It seems like a clientinitiv command resend. Process it. */
break;
case ConnectionState::CONNECTED: {
auto timestamp_now = std::chrono::system_clock::now();
auto last_client_alive_signal = std::max(connection->connection->ping_handler().last_ping_response(), connection->connection->ping_handler().last_command_acknowledged());
if(timestamp_now - last_client_alive_signal < std::chrono::seconds(5)) {
logMessage(connection->connection->virtual_server_id(), "{} Client initialized session reconnect, but last alive signal is not older then 5 seconds ({}). Ignoring attempt.",
connection->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(timestamp_now - last_client_alive_signal).count()
);
/* FIXME: Somehow send an error? */
return nullptr;
} else if(!config::voice::allow_session_reinitialize) {
logMessage(connection->connection->virtual_server_id(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings",
connection->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(timestamp_now - last_client_alive_signal).count()
);
/* FIXME: Somehow send an error? */
return nullptr;
}
logMessage(connection->connection->virtual_server_id(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt and dropping old connection.",
connection->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(timestamp_now - last_client_alive_signal).count()
);
connection->close_connection(std::chrono::system_clock::time_point{});
{
std::lock_guard flush_lock{connection->flush_mutex};
connection->disconnect_acknowledged = std::make_optional(true);
}
continue;
}
case ConnectionState::UNKNWON:
default:
assert(false);
continue;
}
voice_client = connection;
break;
}
}
}
if(!voice_client) {
voice_client = std::make_shared<VoiceClient>(this->server->get_server()->getVoiceServer(), &client->address);
@ -303,14 +357,14 @@ shared_ptr<VoiceClient> POWHandler::register_verified_client(const std::shared_p
memcpy(&voice_client->connection->remote_address_info_, &client->address_info, sizeof(client->address_info));
{
lock_guard lock(this->server->connectionLock);
server->activeConnections.push_back(voice_client);
std::lock_guard lock{this->server->connectionLock};
this->server->activeConnections.push_back(voice_client);
}
debugMessage(this->get_server_id(), "Having new voice client. Remote address: {}", voice_client->getLoggingPeerIp() +":" + to_string(voice_client->getPeerPort()));
debugMessage(this->get_server_id(), "Having new voice client. Remote address: {}:{}", voice_client->getLoggingPeerIp(), voice_client->getPeerPort());
}
voice_client->getConnection()->crypt_setup_handler().set_client_protocol_time(client->client_version);
//voice_client->last_packet_handshake = system_clock::now();
return voice_client;
}

View File

@ -95,9 +95,13 @@ void PuzzleManager::generate_puzzle(std::mt19937& random_generator) {
puzzle->level = ts::config::voice::RsaPuzzleLevel;
while(true) {
#if 0
random_number(random_generator, &puzzle->x, 64);
random_number(random_generator, &puzzle->n, 64);
#else
mp_set(&puzzle->x, 1);
mp_set(&puzzle->n, 1);
#endif
if(!solve_puzzle(&*puzzle)) {
continue;
}

View File

@ -88,12 +88,12 @@ void VoiceIOManager::shutdownGlobally() {
/* keep a ref to all event loops so they dont despawn in their event thread */
unique_lock executor_lock{this->executorLock};
auto event_loops = this->event_loops;
auto wait_end = system_clock::now() + chrono::seconds{5};
while(true) {
if(this->event_loops.empty())
if(this->event_loops.empty()) {
break;
}
auto status = this->ioExecutorNotify.wait_until(executor_lock, wait_end);
if(status == std::cv_status::timeout) {
@ -106,7 +106,7 @@ void VoiceIOManager::shutdownGlobally() {
}
/* now delete all loops */
event_loops.clear();
this->event_loops.clear();
}
//TODO also reduce thread pool!
@ -299,18 +299,7 @@ void IOEventLoopEvents::despawn() {
void VoiceIOManager::dispatchBase(shared_ptr<IOEventLoop> self) {
debugMessage(LOG_INSTANCE, "Dispatching io base {}", (void*) self->base);
while(true) {
if(event_base_got_exit(self->base) || self->shutdown)
break;
event_base_loop(self->base, 0);
{
/* wait until reschedule */
unique_lock<mutex> execute_lock(this->executorLock);
if(event_base_get_num_events(self->base, EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED | EVENT_BASE_COUNT_VIRTUAL) == 0)
this->ioExecutorNotify.wait(execute_lock);
}
}
event_base_loop(self->base, EVLOOP_NO_EXIT_ON_EMPTY);
debugMessage(LOG_INSTANCE, "Dispatching io base {} finished", (void*) self->base);
{

View File

@ -194,24 +194,35 @@ std::shared_ptr<VoiceClient> VoiceServer::findClient(ts::ClientId client) {
std::shared_ptr<VoiceClient> VoiceServer::findClient(sockaddr_in *addr, bool) {
lock_guard lock(this->connectionLock);
for(const auto& elm : this->activeConnections) {
if(elm->isAddressV4())
if(elm->getAddressV4()->sin_addr.s_addr == addr->sin_addr.s_addr)
if(elm->getAddressV4()->sin_port == addr->sin_port)
/* Use a reverse iterator so we're getting the "last"/"newest" connection instance */
for(auto it{this->activeConnections.rbegin()}; it != this->activeConnections.rend(); it++) {
auto& elm = *it;
if(elm->isAddressV4()) {
if(elm->getAddressV4()->sin_addr.s_addr == addr->sin_addr.s_addr) {
if(elm->getAddressV4()->sin_port == addr->sin_port) {
return elm;
}
}
}
}
return nullptr;
}
std::shared_ptr<VoiceClient> VoiceServer::findClient(sockaddr_in6 *addr, bool) {
lock_guard lock(this->connectionLock);
for(const auto& elm : this->activeConnections) {
if(elm->isAddressV6())
if(memcmp(elm->getAddressV6()->sin6_addr.__in6_u.__u6_addr8, addr->sin6_addr.__in6_u.__u6_addr8, 16) == 0)
if(elm->getAddressV6()->sin6_port == addr->sin6_port)
/* Use a reverse iterator so we're getting the "last"/"newest" connection instance */
for(auto it{this->activeConnections.rbegin()}; it != this->activeConnections.rend(); it++) {
auto& elm = *it;
if(elm->isAddressV6()) {
if(memcmp(elm->getAddressV6()->sin6_addr.__in6_u.__u6_addr8, addr->sin6_addr.__in6_u.__u6_addr8, 16) == 0) {
if(elm->getAddressV6()->sin6_port == addr->sin6_port) {
return elm;
}
}
}
}
return nullptr;
}
@ -261,12 +272,15 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) {
message.msg_flags = 0;
bytes_read = recvmsg(fd, &message, 0);
if((message.msg_flags & MSG_TRUNC) > 0)
if((message.msg_flags & MSG_TRUNC) > 0) {
logError(ts_server->getServerId(), "Received truncated message from {}", net::to_string(remote_address));
}
if(bytes_read < 0) {
if(errno == EAGAIN)
if(errno == EAGAIN) {
break;
}
//Nothing more to read
logCritical(ts_server->getServerId(), "Could not receive datagram packet! Code: {} Reason: {}", errno, strerror(errno));
break;
@ -281,7 +295,7 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) {
continue;
}
shared_ptr<VoiceClient> client;
std::shared_ptr<VoiceClient> client;
{
if(*(uint64_t*) raw_read_buffer == TS3INIT.integral) {
//Handle ddos protection...
@ -427,6 +441,7 @@ void VoiceServer::handleMessageWrite(int fd, short events, void *_event_handle)
more_clients = (bool) client_queue_state;
}
client_wbuffer_state = WBufferPopResult::MORE_AVAILABLE;
while(system_clock::now() <= write_timeout) {
packet = nullptr;
client_wbuffer_state = client->connection->packet_encoder().pop_write_buffer(packet);

2
shared

@ -1 +1 @@
Subproject commit 88158742e873a9ecc076091a76b52af85ca32687
Subproject commit 0a73e4c10c4af3b6f982c0e2986f122e75bd20a8