A lot of query reworking
This commit is contained in:
parent
9a5aa1d42b
commit
902b1f3511
@ -1 +1 @@
|
|||||||
Subproject commit 2e7730459d6941a97f7bc2b9b50b678ab195a41d
|
Subproject commit 8e7874872ebb2c19dda702bc68372da5eea77e99
|
@ -57,7 +57,6 @@ set(SERVER_SOURCE_FILES
|
|||||||
src/server/VoiceServer.cpp
|
src/server/VoiceServer.cpp
|
||||||
src/server/POWHandler.cpp
|
src/server/POWHandler.cpp
|
||||||
src/client/voice/VoiceClientConnection.cpp
|
src/client/voice/VoiceClientConnection.cpp
|
||||||
#src/client/ConnectedClientCommandHandler.cpp
|
|
||||||
src/client/command_handler/channel.cpp
|
src/client/command_handler/channel.cpp
|
||||||
src/client/command_handler/client.cpp
|
src/client/command_handler/client.cpp
|
||||||
src/client/command_handler/server.cpp
|
src/client/command_handler/server.cpp
|
||||||
@ -71,8 +70,6 @@ set(SERVER_SOURCE_FILES
|
|||||||
src/Group.cpp
|
src/Group.cpp
|
||||||
src/manager/BanManager.cpp
|
src/manager/BanManager.cpp
|
||||||
src/client/InternalClient.cpp
|
src/client/InternalClient.cpp
|
||||||
#src/weblist/WeblistClient.cpp
|
|
||||||
#src/weblist/WebList.cpp
|
|
||||||
|
|
||||||
src/client/DataClient.cpp
|
src/client/DataClient.cpp
|
||||||
src/server/QueryServer.cpp
|
src/server/QueryServer.cpp
|
||||||
@ -150,7 +147,8 @@ set(SERVER_SOURCE_FILES
|
|||||||
|
|
||||||
src/client/voice/PacketDecoder.cpp
|
src/client/voice/PacketDecoder.cpp
|
||||||
src/client/voice/PacketEncoder.cpp
|
src/client/voice/PacketEncoder.cpp
|
||||||
src/client/voice/ServerCommandExecutor.cpp
|
src/client/shared/ServerCommandExecutor.cpp
|
||||||
|
src/client/shared/RawCommand.cpp
|
||||||
src/client/voice/PingHandler.cpp
|
src/client/voice/PingHandler.cpp
|
||||||
src/client/voice/CryptSetupHandler.cpp
|
src/client/voice/CryptSetupHandler.cpp
|
||||||
src/client/shared/WhisperHandler.cpp
|
src/client/shared/WhisperHandler.cpp
|
||||||
|
@ -135,7 +135,7 @@ int main(int argc, char** argv) {
|
|||||||
group.target = line == "2" ? TARGET_QUERY : TARGET_SERVER;
|
group.target = line == "2" ? TARGET_QUERY : TARGET_SERVER;
|
||||||
read_line(file, line);
|
read_line(file, line);
|
||||||
auto data = "perms " + line;
|
auto data = "perms " + line;
|
||||||
ts::Command group_parms = ts::Command::parse(pipes::buffer_view(data.data(), data.length()));
|
ts::Command group_parms = ts::Command::parse(data);
|
||||||
|
|
||||||
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
|
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
|
||||||
for (int index = 0; index < group_parms.bulkCount(); index++) {
|
for (int index = 0; index < group_parms.bulkCount(); index++) {
|
||||||
@ -184,7 +184,7 @@ int main(int argc, char** argv) {
|
|||||||
group.target = TARGET_CHANNEL;
|
group.target = TARGET_CHANNEL;
|
||||||
read_line(file, line);
|
read_line(file, line);
|
||||||
auto data = "perms " + line;
|
auto data = "perms " + line;
|
||||||
ts::Command group_parms = ts::Command::parse(pipes::buffer_view(data.data(), data.length()));
|
ts::Command group_parms = ts::Command::parse(data);
|
||||||
|
|
||||||
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
|
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
|
||||||
for (int index = 0; index < group_parms.bulkCount(); index++) {
|
for (int index = 0; index < group_parms.bulkCount(); index++) {
|
||||||
|
@ -92,6 +92,7 @@ bool config::voice::allow_session_reinitialize;
|
|||||||
|
|
||||||
std::string config::query::motd;
|
std::string config::query::motd;
|
||||||
std::string config::query::newlineCharacter;
|
std::string config::query::newlineCharacter;
|
||||||
|
size_t config::query::max_line_buffer;
|
||||||
int config::query::sslMode;
|
int config::query::sslMode;
|
||||||
std::string config::query::ssl::certFile;
|
std::string config::query::ssl::certFile;
|
||||||
std::string config::query::ssl::keyFile;
|
std::string config::query::ssl::keyFile;
|
||||||
@ -121,8 +122,7 @@ std::string config::messages::timeout::packet_resend_failed;
|
|||||||
std::string config::messages::timeout::connection_reinitialized;
|
std::string config::messages::timeout::connection_reinitialized;
|
||||||
|
|
||||||
size_t config::threads::ticking;
|
size_t config::threads::ticking;
|
||||||
size_t config::threads::voice::execute_limit;
|
size_t config::threads::command_execute;
|
||||||
size_t config::threads::voice::execute_per_server;
|
|
||||||
size_t config::threads::voice::events_per_server;
|
size_t config::threads::voice::events_per_server;
|
||||||
size_t config::threads::voice::io_min;
|
size_t config::threads::voice::io_min;
|
||||||
size_t config::threads::voice::io_per_server;
|
size_t config::threads::voice::io_per_server;
|
||||||
@ -1121,13 +1121,18 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
|||||||
{
|
{
|
||||||
BIND_GROUP(query);
|
BIND_GROUP(query);
|
||||||
{
|
{
|
||||||
CREATE_BINDING("nl_char", 0);
|
CREATE_BINDING("nl_char", FLAG_RELOADABLE);
|
||||||
BIND_STRING(config::query::newlineCharacter, "\n");
|
BIND_STRING(config::query::newlineCharacter, "\n");
|
||||||
ADD_DESCRIPTION("Change the query newline character");
|
ADD_DESCRIPTION("Change the query newline character");
|
||||||
ADD_NOTE("NOTE: TS3 - Compatible bots may require \"\n\r\"");
|
ADD_NOTE("NOTE: TS3 - Compatible bots may require \"\n\r\"");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CREATE_BINDING("motd", 0);
|
CREATE_BINDING("max_line_buffer", FLAG_RELOADABLE);
|
||||||
|
BIND_INTEGRAL(config::query::max_line_buffer, 1024 * 1024, 1024 * 8, 1024 * 1024 * 512);
|
||||||
|
ADD_DESCRIPTION("Max number of characters one query command could contain.");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CREATE_BINDING("motd", FLAG_RELOADABLE);
|
||||||
BIND_STRING(config::query::motd, "TeaSpeak\nWelcome on the TeaSpeak ServerQuery interface.\n");
|
BIND_STRING(config::query::motd, "TeaSpeak\nWelcome on the TeaSpeak ServerQuery interface.\n");
|
||||||
ADD_DESCRIPTION("The query welcome message");
|
ADD_DESCRIPTION("The query welcome message");
|
||||||
|
|
||||||
@ -1143,7 +1148,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
|||||||
ADD_DESCRIPTION("Available modes:");
|
ADD_DESCRIPTION("Available modes:");
|
||||||
ADD_DESCRIPTION(" 0: Disabled");
|
ADD_DESCRIPTION(" 0: Disabled");
|
||||||
ADD_DESCRIPTION(" 1: Enabled (Enforced encryption)");
|
ADD_DESCRIPTION(" 1: Enabled (Enforced encryption)");
|
||||||
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isnt available)");
|
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isn't available)");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
BIND_GROUP(ssl);
|
BIND_GROUP(ssl);
|
||||||
@ -1825,6 +1830,12 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
|||||||
ADD_DESCRIPTION("Thread pool size for the ticking task of a VirtualServer");
|
ADD_DESCRIPTION("Thread pool size for the ticking task of a VirtualServer");
|
||||||
ADD_SENSITIVE();
|
ADD_SENSITIVE();
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
CREATE_BINDING("command_execute", 0);
|
||||||
|
BIND_INTEGRAL(config::threads::command_execute, 4, 1, 128);
|
||||||
|
ADD_DESCRIPTION("Command executors");
|
||||||
|
ADD_SENSITIVE();
|
||||||
|
}
|
||||||
{
|
{
|
||||||
BIND_GROUP(voice)
|
BIND_GROUP(voice)
|
||||||
{
|
{
|
||||||
@ -1833,18 +1844,6 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
|||||||
ADD_DESCRIPTION("Kernel events per server");
|
ADD_DESCRIPTION("Kernel events per server");
|
||||||
ADD_SENSITIVE();
|
ADD_SENSITIVE();
|
||||||
}
|
}
|
||||||
{
|
|
||||||
CREATE_BINDING("execute_per_server", 0);
|
|
||||||
BIND_INTEGRAL(config::threads::voice::execute_per_server, 2, 1, 128);
|
|
||||||
ADD_DESCRIPTION("Threads per server for command executing");
|
|
||||||
ADD_SENSITIVE();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
CREATE_BINDING("execute_limit", 0);
|
|
||||||
BIND_INTEGRAL(config::threads::voice::execute_limit, 5, 1, 1024);
|
|
||||||
ADD_DESCRIPTION("Max number of threads for command handling threads within the instance");
|
|
||||||
ADD_SENSITIVE();
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
CREATE_BINDING("io_min", 0);
|
CREATE_BINDING("io_min", 0);
|
||||||
BIND_INTEGRAL(config::threads::voice::io_min, 2, 1, 1024);
|
BIND_INTEGRAL(config::threads::voice::io_min, 2, 1, 1024);
|
||||||
|
@ -157,6 +157,7 @@ namespace ts::config {
|
|||||||
namespace query {
|
namespace query {
|
||||||
extern std::string motd;
|
extern std::string motd;
|
||||||
extern std::string newlineCharacter;
|
extern std::string newlineCharacter;
|
||||||
|
extern size_t max_line_buffer;
|
||||||
|
|
||||||
extern int sslMode;
|
extern int sslMode;
|
||||||
namespace ssl {
|
namespace ssl {
|
||||||
@ -230,11 +231,9 @@ namespace ts::config {
|
|||||||
|
|
||||||
namespace threads {
|
namespace threads {
|
||||||
extern size_t ticking;
|
extern size_t ticking;
|
||||||
|
extern size_t command_execute;
|
||||||
|
|
||||||
namespace voice {
|
namespace voice {
|
||||||
extern size_t execute_per_server;
|
|
||||||
extern size_t execute_limit;
|
|
||||||
|
|
||||||
extern size_t events_per_server;
|
extern size_t events_per_server;
|
||||||
extern size_t io_min;
|
extern size_t io_min;
|
||||||
extern size_t io_per_server;
|
extern size_t io_per_server;
|
||||||
|
@ -25,8 +25,8 @@
|
|||||||
#define _POSIX_SOURCE
|
#define _POSIX_SOURCE
|
||||||
#endif
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <files/FileServer.h>
|
|
||||||
#include "./manager/ActionLogger.h"
|
#include "./manager/ActionLogger.h"
|
||||||
|
#include "./client/shared/ServerCommandExecutor.h"
|
||||||
|
|
||||||
#undef _POSIX_SOURCE
|
#undef _POSIX_SOURCE
|
||||||
|
|
||||||
@ -251,11 +251,15 @@ inline vector<string> split_hosts(const std::string& message, char delimiter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool InstanceHandler::startInstance() {
|
bool InstanceHandler::startInstance() {
|
||||||
if (this->active)
|
if (this->active) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
active = true;
|
active = true;
|
||||||
string errorMessage;
|
string errorMessage;
|
||||||
|
|
||||||
|
this->server_command_executor_ = std::make_shared<ServerCommandExecutor>(ts::config::threads::command_execute);
|
||||||
|
|
||||||
this->web_list->enabled = ts::config::server::enable_teamspeak_weblist;
|
this->web_list->enabled = ts::config::server::enable_teamspeak_weblist;
|
||||||
|
|
||||||
this->permission_mapper = make_shared<permission::PermissionNameMapper>();
|
this->permission_mapper = make_shared<permission::PermissionNameMapper>();
|
||||||
@ -412,6 +416,7 @@ void InstanceHandler::stopInstance() {
|
|||||||
this->activeCon.notify_all();
|
this->activeCon.notify_all();
|
||||||
}
|
}
|
||||||
this->web_list->enabled = false;
|
this->web_list->enabled = false;
|
||||||
|
this->server_command_executor_->shutdown();
|
||||||
|
|
||||||
threads::MutexLock lock_tick(this->lock_tick);
|
threads::MutexLock lock_tick(this->lock_tick);
|
||||||
this->scheduler()->cancelTask(INSTANCE_TICK_NAME);
|
this->scheduler()->cancelTask(INSTANCE_TICK_NAME);
|
||||||
@ -447,6 +452,7 @@ void InstanceHandler::stopInstance() {
|
|||||||
this->web_event_loop = nullptr;
|
this->web_event_loop = nullptr;
|
||||||
|
|
||||||
this->license_service_->shutdown();
|
this->license_service_->shutdown();
|
||||||
|
this->server_command_executor_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceHandler::tickInstance() {
|
void InstanceHandler::tickInstance() {
|
||||||
|
@ -31,6 +31,8 @@ namespace ts {
|
|||||||
class ActionLogger;
|
class ActionLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ServerCommandExecutor;
|
||||||
|
|
||||||
class InstanceHandler {
|
class InstanceHandler {
|
||||||
public:
|
public:
|
||||||
explicit InstanceHandler(SqlDataManager*);
|
explicit InstanceHandler(SqlDataManager*);
|
||||||
@ -85,6 +87,8 @@ namespace ts {
|
|||||||
std::shared_ptr<permission::PermissionNameMapper> getPermissionMapper() { return this->permission_mapper; }
|
std::shared_ptr<permission::PermissionNameMapper> getPermissionMapper() { return this->permission_mapper; }
|
||||||
std::shared_ptr<ts::event::EventExecutor> getConversationIo() { return this->conversation_io; }
|
std::shared_ptr<ts::event::EventExecutor> getConversationIo() { return this->conversation_io; }
|
||||||
|
|
||||||
|
[[nodiscard]] inline auto server_command_executor() { return this->server_command_executor_; }
|
||||||
|
|
||||||
permission::v2::PermissionFlaggedValue calculate_permission(
|
permission::v2::PermissionFlaggedValue calculate_permission(
|
||||||
permission::PermissionType,
|
permission::PermissionType,
|
||||||
ClientDbId,
|
ClientDbId,
|
||||||
@ -129,6 +133,8 @@ namespace ts {
|
|||||||
|
|
||||||
ts::Properties* _properties = nullptr;
|
ts::Properties* _properties = nullptr;
|
||||||
|
|
||||||
|
std::shared_ptr<ServerCommandExecutor> server_command_executor_{};
|
||||||
|
|
||||||
std::shared_ptr<ts::event::EventExecutor> conversation_io = nullptr;
|
std::shared_ptr<ts::event::EventExecutor> conversation_io = nullptr;
|
||||||
std::shared_ptr<webio::LoopManager> web_event_loop = nullptr;
|
std::shared_ptr<webio::LoopManager> web_event_loop = nullptr;
|
||||||
std::shared_ptr<weblist::WebListManager> web_list = nullptr;
|
std::shared_ptr<weblist::WebListManager> web_list = nullptr;
|
||||||
|
@ -81,7 +81,7 @@ bool ts::syssignal::setup() {
|
|||||||
//We cant listen for this signal if stdin ist a atty
|
//We cant listen for this signal if stdin ist a atty
|
||||||
SIG(SIGINT, &ts::syssignal::handleStopSignal);
|
SIG(SIGINT, &ts::syssignal::handleStopSignal);
|
||||||
}
|
}
|
||||||
SIG(SIGABRT, &ts::syssignal::handleAbortSignal);
|
//SIG(SIGABRT, &ts::syssignal::handleAbortSignal);
|
||||||
std::set_terminate(ts::syssignal::handleTerminate);
|
std::set_terminate(ts::syssignal::handleTerminate);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -377,14 +377,16 @@ void VirtualServer::client_move(
|
|||||||
std::unique_lock<std::shared_mutex> &server_channel_write_lock) {
|
std::unique_lock<std::shared_mutex> &server_channel_write_lock) {
|
||||||
|
|
||||||
TIMING_START(timings);
|
TIMING_START(timings);
|
||||||
if(server_channel_write_lock.owns_lock())
|
if(server_channel_write_lock.owns_lock()) {
|
||||||
server_channel_write_lock.unlock();
|
server_channel_write_lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
lock_guard client_command_lock(target->command_lock);
|
lock_guard client_command_lock(target->command_lock);
|
||||||
server_channel_write_lock.lock();
|
server_channel_write_lock.lock();
|
||||||
TIMING_STEP(timings, "chan tree l");
|
TIMING_STEP(timings, "chan tree l");
|
||||||
if(target->currentChannel == target_channel)
|
if(target->currentChannel == target_channel) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* first step: resolve the target channel / or fix missing */
|
/* first step: resolve the target channel / or fix missing */
|
||||||
auto s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
|
auto s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
|
||||||
|
@ -126,7 +126,7 @@ void VirtualServer::executeServerTick() {
|
|||||||
if(cl->floodPoints > flood_decrease)
|
if(cl->floodPoints > flood_decrease)
|
||||||
cl->floodPoints -= flood_decrease;
|
cl->floodPoints -= flood_decrease;
|
||||||
else cl->floodPoints = 0;
|
else cl->floodPoints = 0;
|
||||||
cl->tick(tick_client_end);
|
cl->tick_server(tick_client_end);
|
||||||
auto voice = dynamic_pointer_cast<SpeakingClient>(cl);
|
auto voice = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||||
if(flag_update_spoken && voice)
|
if(flag_update_spoken && voice)
|
||||||
this->spoken_time += voice->takeSpokenTime();
|
this->spoken_time += voice->takeSpokenTime();
|
||||||
|
@ -577,7 +577,7 @@ void VirtualServer::stop(const std::string& reason, bool disconnect_query) {
|
|||||||
|
|
||||||
if(disconnect_query) {
|
if(disconnect_query) {
|
||||||
auto qc = dynamic_pointer_cast<QueryClient>(cl);
|
auto qc = dynamic_pointer_cast<QueryClient>(cl);
|
||||||
qc->disconnect_from_virtual_server();
|
qc->disconnect_from_virtual_server("server disconnect");
|
||||||
}
|
}
|
||||||
} else if(cl->getType() == CLIENT_MUSIC) {
|
} else if(cl->getType() == CLIENT_MUSIC) {
|
||||||
cl->disconnect("");
|
cl->disconnect("");
|
||||||
|
@ -15,7 +15,6 @@ using namespace ts::server;
|
|||||||
VirtualServerManager::VirtualServerManager(InstanceHandler* handle) : handle(handle) {
|
VirtualServerManager::VirtualServerManager(InstanceHandler* handle) : handle(handle) {
|
||||||
this->puzzles = new udp::PuzzleManager{};
|
this->puzzles = new udp::PuzzleManager{};
|
||||||
this->handshakeTickers = new threads::Scheduler(1, "handshake ticker");
|
this->handshakeTickers = new threads::Scheduler(1, "handshake ticker");
|
||||||
this->execute_loop = new event::EventExecutor("executor #");
|
|
||||||
//this->join_loop = new event::EventExecutor("joiner #");
|
//this->join_loop = new event::EventExecutor("joiner #");
|
||||||
this->_ioManager = new io::VoiceIOManager();
|
this->_ioManager = new io::VoiceIOManager();
|
||||||
|
|
||||||
@ -40,11 +39,6 @@ VirtualServerManager::~VirtualServerManager() {
|
|||||||
delete this->puzzles;
|
delete this->puzzles;
|
||||||
this->puzzles = nullptr;
|
this->puzzles = nullptr;
|
||||||
|
|
||||||
if(this->execute_loop)
|
|
||||||
this->execute_loop->shutdown();
|
|
||||||
delete this->execute_loop;
|
|
||||||
this->execute_loop = nullptr;
|
|
||||||
|
|
||||||
if(this->join_loop)
|
if(this->join_loop)
|
||||||
this->join_loop->shutdown();
|
this->join_loop->shutdown();
|
||||||
delete this->join_loop;
|
delete this->join_loop;
|
||||||
@ -62,8 +56,6 @@ VirtualServerManager::~VirtualServerManager() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool VirtualServerManager::initialize(bool autostart) {
|
bool VirtualServerManager::initialize(bool autostart) {
|
||||||
this->execute_loop->initialize(1);
|
|
||||||
|
|
||||||
this->state = State::STARTING;
|
this->state = State::STARTING;
|
||||||
logMessage(LOG_INSTANCE, "Generating server puzzles...");
|
logMessage(LOG_INSTANCE, "Generating server puzzles...");
|
||||||
auto start = system_clock::now();
|
auto start = system_clock::now();
|
||||||
@ -162,7 +154,6 @@ bool VirtualServerManager::initialize(bool autostart) {
|
|||||||
(float) server_count / (time / 1024 == 0 ? 1 : time / 1024)
|
(float) server_count / (time / 1024 == 0 ? 1 : time / 1024)
|
||||||
);
|
);
|
||||||
this->handle->databaseHelper()->clearStartupCache(0);
|
this->handle->databaseHelper()->clearStartupCache(0);
|
||||||
this->adjust_executor_threads();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
this->acknowledge.executor = std::thread([&]{
|
this->acknowledge.executor = std::thread([&]{
|
||||||
@ -370,7 +361,6 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
|
|||||||
threads::MutexLock l(this->instanceLock);
|
threads::MutexLock l(this->instanceLock);
|
||||||
this->instances.push_back(server);
|
this->instances.push_back(server);
|
||||||
}
|
}
|
||||||
this->adjust_executor_threads();
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +378,6 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
|
|||||||
return s == server;
|
return s == server;
|
||||||
}), this->instances.end());
|
}), this->instances.end());
|
||||||
}
|
}
|
||||||
this->adjust_executor_threads();
|
|
||||||
|
|
||||||
if(server->getState() != ServerState::OFFLINE)
|
if(server->getState() != ServerState::OFFLINE)
|
||||||
server->stop("server deleted", true);
|
server->stop("server deleted", true);
|
||||||
@ -397,7 +386,7 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
|
|||||||
cl->close_connection(chrono::system_clock::now());
|
cl->close_connection(chrono::system_clock::now());
|
||||||
} else if(cl->getType() == CLIENT_QUERY){
|
} else if(cl->getType() == CLIENT_QUERY){
|
||||||
auto qc = dynamic_pointer_cast<QueryClient>(cl);
|
auto qc = dynamic_pointer_cast<QueryClient>(cl);
|
||||||
qc->disconnect_from_virtual_server();
|
qc->disconnect_from_virtual_server("server delete");
|
||||||
} else if(cl->getType() == CLIENT_MUSIC) {
|
} else if(cl->getType() == CLIENT_MUSIC) {
|
||||||
cl->disconnect("");
|
cl->disconnect("");
|
||||||
cl->currentChannel = nullptr;
|
cl->currentChannel = nullptr;
|
||||||
@ -473,8 +462,6 @@ void VirtualServerManager::shutdownAll(const std::string& msg) {
|
|||||||
for(const auto &server : this->serverInstances()){
|
for(const auto &server : this->serverInstances()){
|
||||||
if(server->running()) server->stop(msg, true);
|
if(server->running()) server->stop(msg, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->execute_loop->shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VirtualServerManager::tickHandshakeClients() {
|
void VirtualServerManager::tickHandshakeClients() {
|
||||||
|
@ -71,16 +71,7 @@ namespace ts::server {
|
|||||||
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
|
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
|
||||||
|
|
||||||
event::EventExecutor* get_join_loop() { return this->join_loop; }
|
event::EventExecutor* get_join_loop() { return this->join_loop; }
|
||||||
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
|
|
||||||
|
|
||||||
inline void adjust_executor_threads() {
|
|
||||||
std::unique_lock instance_lock(this->instanceLock);
|
|
||||||
auto instance_count = this->instances.size();
|
|
||||||
instance_lock.unlock();
|
|
||||||
|
|
||||||
auto threads = std::min(config::threads::voice::execute_per_server * instance_count, config::threads::voice::execute_limit);
|
|
||||||
this->execute_loop->threads(threads);
|
|
||||||
}
|
|
||||||
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
|
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
|
||||||
|
|
||||||
/* This must be recursive */
|
/* This must be recursive */
|
||||||
@ -94,7 +85,6 @@ namespace ts::server {
|
|||||||
std::deque<std::shared_ptr<VirtualServer>> instances;
|
std::deque<std::shared_ptr<VirtualServer>> instances;
|
||||||
udp::PuzzleManager* puzzles{nullptr};
|
udp::PuzzleManager* puzzles{nullptr};
|
||||||
|
|
||||||
event::EventExecutor* execute_loop = nullptr;
|
|
||||||
event::EventExecutor* join_loop = nullptr;
|
event::EventExecutor* join_loop = nullptr;
|
||||||
threads::Scheduler* handshakeTickers = nullptr;
|
threads::Scheduler* handshakeTickers = nullptr;
|
||||||
io::VoiceIOManager* _ioManager = nullptr;
|
io::VoiceIOManager* _ioManager = nullptr;
|
||||||
|
@ -38,7 +38,9 @@ ConnectedClient::~ConnectedClient() {
|
|||||||
|
|
||||||
bool ConnectedClient::loadDataForCurrentServer() {
|
bool ConnectedClient::loadDataForCurrentServer() {
|
||||||
auto result = DataClient::loadDataForCurrentServer();
|
auto result = DataClient::loadDataForCurrentServer();
|
||||||
if(!result) return false;
|
if(!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp();
|
this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp();
|
||||||
return true;
|
return true;
|
||||||
@ -720,7 +722,7 @@ void ConnectedClient::sendChannelDescription(const std::shared_ptr<BasicChannel>
|
|||||||
this->sendCommand(cmd, true);
|
this->sendCommand(cmd, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectedClient::tick(const std::chrono::system_clock::time_point &time) {
|
void ConnectedClient::tick_server(const std::chrono::system_clock::time_point &time) {
|
||||||
ALARM_TIMER(A1, "ConnectedClient::tick", milliseconds(2));
|
ALARM_TIMER(A1, "ConnectedClient::tick", milliseconds(2));
|
||||||
if(this->state == ConnectionState::CONNECTED) {
|
if(this->state == ConnectionState::CONNECTED) {
|
||||||
if(this->requireNeededPermissionResend)
|
if(this->requireNeededPermissionResend)
|
||||||
|
@ -240,7 +240,16 @@ namespace ts {
|
|||||||
|
|
||||||
virtual bool notifyConversationMessageDelete(const ChannelId /* conversation id */, const std::chrono::system_clock::time_point& /* begin timestamp */, const std::chrono::system_clock::time_point& /* begin end */, ClientDbId /* client id */, size_t /* messages */);
|
virtual bool notifyConversationMessageDelete(const ChannelId /* conversation id */, const std::chrono::system_clock::time_point& /* begin timestamp */, const std::chrono::system_clock::time_point& /* begin end */, ClientDbId /* client id */, size_t /* messages */);
|
||||||
|
|
||||||
/* this method should be callable from everywhere; the method is non blocking! */
|
/**
|
||||||
|
* Close the network connection.
|
||||||
|
*
|
||||||
|
* Note:
|
||||||
|
* This method could be called from any thread with any locks in hold.
|
||||||
|
* It's not blocking.
|
||||||
|
*
|
||||||
|
* @param timeout The timestamp when to drop the client if not all data has been send.
|
||||||
|
* @returns `true` if the connection has been closed and `false` if the connection is already closed.
|
||||||
|
*/
|
||||||
virtual bool close_connection(const std::chrono::system_clock::time_point &timeout) = 0;
|
virtual bool close_connection(const std::chrono::system_clock::time_point &timeout) = 0;
|
||||||
|
|
||||||
/* this method should be callable from everywhere; the method is non blocking! */
|
/* this method should be callable from everywhere; the method is non blocking! */
|
||||||
@ -382,7 +391,7 @@ namespace ts {
|
|||||||
|
|
||||||
bool loadDataForCurrentServer() override;
|
bool loadDataForCurrentServer() override;
|
||||||
|
|
||||||
virtual void tick(const std::chrono::system_clock::time_point &time);
|
virtual void tick_server(const std::chrono::system_clock::time_point &time);
|
||||||
//Locked by everything who has something todo with command handling
|
//Locked by everything who has something todo with command handling
|
||||||
threads::Mutex command_lock; /* Note: This mutex must be recursive! */
|
threads::Mutex command_lock; /* Note: This mutex must be recursive! */
|
||||||
std::vector<std::function<void()>> postCommandHandler;
|
std::vector<std::function<void()>> postCommandHandler;
|
||||||
|
@ -34,8 +34,8 @@ bool InternalClient::close_connection(const std::chrono::system_clock::time_poin
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InternalClient::tick(const std::chrono::system_clock::time_point &time) {
|
void InternalClient::tick_server(const std::chrono::system_clock::time_point &time) {
|
||||||
ConnectedClient::tick(time);
|
ConnectedClient::tick_server(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InternalClient::disconnect(const std::string &reason) {
|
bool InternalClient::disconnect(const std::string &reason) {
|
||||||
|
@ -19,6 +19,6 @@ namespace ts::server {
|
|||||||
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
|
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
|
||||||
bool disconnect(const std::string &reason) override;
|
bool disconnect(const std::string &reason) override;
|
||||||
protected:
|
protected:
|
||||||
void tick(const std::chrono::system_clock::time_point &time) override;
|
void tick_server(const std::chrono::system_clock::time_point &time) override;
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -574,8 +574,8 @@ void SpeakingClient::updateSpeak(bool only_update, const std::chrono::system_clo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpeakingClient::tick(const std::chrono::system_clock::time_point &time) {
|
void SpeakingClient::tick_server(const std::chrono::system_clock::time_point &time) {
|
||||||
ConnectedClient::tick(time);
|
ConnectedClient::tick_server(time);
|
||||||
ALARM_TIMER(A1, "SpeakingClient::tick", milliseconds(2));
|
ALARM_TIMER(A1, "SpeakingClient::tick", milliseconds(2));
|
||||||
this->updateSpeak(true, time);
|
this->updateSpeak(true, time);
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ namespace ts::server {
|
|||||||
|
|
||||||
[[nodiscard]] inline whisper::WhisperHandler& whisper_handler() { return this->whisper_handler_; }
|
[[nodiscard]] inline whisper::WhisperHandler& whisper_handler() { return this->whisper_handler_; }
|
||||||
protected:
|
protected:
|
||||||
void tick(const std::chrono::system_clock::time_point &time) override;
|
void tick_server(const std::chrono::system_clock::time_point &time) override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void updateChannelClientProperties(bool channel_lock, bool notify) override;
|
void updateChannelClientProperties(bool channel_lock, bool notify) override;
|
||||||
|
@ -526,7 +526,7 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) {
|
|||||||
if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
|
if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
|
||||||
ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
|
ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||||
if (!pparser.validate(this->ref(), 0)) {
|
if (!pparser.validate(this->ref(), 0)) {
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
}
|
}
|
||||||
@ -581,7 +581,7 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
|
|||||||
if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
|
if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
|
||||||
ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
|
ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||||
if (!pparser.validate(this->ref(), 0))
|
if (!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -2142,7 +2142,7 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) {
|
|||||||
|
|
||||||
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true);
|
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true);
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||||
if (!pparser.validate(this->ref(), channel->channelId())) {
|
if (!pparser.validate(this->ref(), channel->channelId())) {
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
}
|
}
|
||||||
@ -2219,7 +2219,7 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) {
|
|||||||
|
|
||||||
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true);
|
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true);
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||||
if (!pparser.validate(this->ref(), channel->channelId()))
|
if (!pparser.validate(this->ref(), channel->channelId()))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -2332,7 +2332,7 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
|
|||||||
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
|
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||||
if (!pparser.validate(this->ref(), channel->channelId()))
|
if (!pparser.validate(this->ref(), channel->channelId()))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -2400,7 +2400,7 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
|
|||||||
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
|
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);
|
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||||
if (!pparser.validate(this->ref(), channel->channelId()))
|
if (!pparser.validate(this->ref(), channel->channelId()))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
|
@ -953,7 +953,7 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
|
|||||||
|
|
||||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), 0))
|
if(!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -997,7 +997,7 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
|
|||||||
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
|
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
|
||||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), 0))
|
if(!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <openssl/sha.h>
|
#include <openssl/sha.h>
|
||||||
#include "../../build.h"
|
|
||||||
#include "../ConnectedClient.h"
|
#include "../ConnectedClient.h"
|
||||||
#include "../InternalClient.h"
|
#include "../InternalClient.h"
|
||||||
#include "../../server/VoiceServer.h"
|
#include "../../server/VoiceServer.h"
|
||||||
@ -24,13 +23,10 @@
|
|||||||
|
|
||||||
#include <files/FileServer.h>
|
#include <files/FileServer.h>
|
||||||
|
|
||||||
#include <log/LogUtils.h>
|
|
||||||
#include <misc/sassert.h>
|
#include <misc/sassert.h>
|
||||||
#include <misc/base64.h>
|
#include <misc/base64.h>
|
||||||
#include <misc/hex.h>
|
#include <misc/hex.h>
|
||||||
#include <misc/rnd.h>
|
#include <misc/rnd.h>
|
||||||
#include <misc/strobf.h>
|
|
||||||
#include <bbcode/bbcodes.h>
|
|
||||||
|
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -525,16 +521,18 @@ command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!info_response->wait_for(kFileAPITimeout))
|
if(!info_response->wait_for(kFileAPITimeout)) {
|
||||||
return command_result{error::file_api_timeout};
|
return command_result{error::file_api_timeout};
|
||||||
|
}
|
||||||
|
|
||||||
if(!info_response->succeeded()) {
|
if(!info_response->succeeded()) {
|
||||||
debugMessage(this->getServerId(), "{} Failed to execute file info query: {} / {}", CLIENT_STR_LOG_PREFIX, (int) info_response->error().error_type, info_response->error().error_message);
|
debugMessage(this->getServerId(), "{} Failed to execute file info query: {} / {}", CLIENT_STR_LOG_PREFIX, (int) info_response->error().error_type, info_response->error().error_message);
|
||||||
switch(info_response->error().error_type) {
|
switch(info_response->error().error_type) {
|
||||||
case ErrorType::UNKNOWN: {
|
case ErrorType::UNKNOWN: {
|
||||||
auto error_detail = std::to_string((int) info_response->error().error_type);
|
auto error_detail = std::to_string((int) info_response->error().error_type);
|
||||||
if(!info_response->error().error_message.empty())
|
if(!info_response->error().error_message.empty()) {
|
||||||
error_detail += "/" + info_response->error().error_message;
|
error_detail += "/" + info_response->error().error_message;
|
||||||
|
}
|
||||||
return command_result{error::vs_critical, error_detail};
|
return command_result{error::vs_critical, error_detail};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -547,8 +545,9 @@ command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) {
|
|||||||
ts::command_builder notify_file_info{this->notify_response_command("notifyfileinfo")};
|
ts::command_builder notify_file_info{this->notify_response_command("notifyfileinfo")};
|
||||||
size_t notify_index{0};
|
size_t notify_index{0};
|
||||||
for(const auto& file : file_status.file_info) {
|
for(const auto& file : file_status.file_info) {
|
||||||
while(response.response(bulk_index).error_code() != error::ok)
|
while(response.response(bulk_index).error_code() != error::ok) {
|
||||||
bulk_index++;
|
bulk_index++;
|
||||||
|
}
|
||||||
|
|
||||||
using Status = file::filesystem::FileInfoResponse::StatusType;
|
using Status = file::filesystem::FileInfoResponse::StatusType;
|
||||||
switch (file.status) {
|
switch (file.status) {
|
||||||
@ -601,16 +600,19 @@ command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) {
|
|||||||
notify_index = 0;
|
notify_index = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(notify_index > 0)
|
if(notify_index > 0) {
|
||||||
this->sendCommand(notify_file_info);
|
this->sendCommand(notify_file_info);
|
||||||
|
}
|
||||||
|
|
||||||
if(as_list && this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
|
if(as_list && this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||||
ts::command_builder notify{this->notify_response_command("notifyfileinfofinished")};
|
ts::command_builder notify{this->notify_response_command("notifyfileinfofinished")};
|
||||||
notify.put_unchecked(0, "return_code", cmd["return_code"].string());
|
notify.put_unchecked(0, "return_code", cmd["return_code"].string());
|
||||||
this->sendCommand(notify);
|
this->sendCommand(notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
while(response.length() > bulk_index && response.response(bulk_index).type() == command_result_type::error && response.response(bulk_index).error_code() != error::ok)
|
while(response.length() > bulk_index && response.response(bulk_index).type() == command_result_type::error && response.response(bulk_index).error_code() != error::ok) {
|
||||||
bulk_index++;
|
bulk_index++;
|
||||||
|
}
|
||||||
|
|
||||||
assert(bulk_index == cmd.bulkCount());
|
assert(bulk_index == cmd.bulkCount());
|
||||||
return command_result{std::move(response)};
|
return command_result{std::move(response)};
|
||||||
@ -978,11 +980,14 @@ command_result ConnectedClient::handleCommandFTList(ts::Command &cmd) {
|
|||||||
ACTION_REQUIRES_PERMISSION(permission::b_ft_transfer_list, 1, 0);
|
ACTION_REQUIRES_PERMISSION(permission::b_ft_transfer_list, 1, 0);
|
||||||
|
|
||||||
auto virtual_file_server = file::server()->find_virtual_server(this->getServerId());
|
auto virtual_file_server = file::server()->find_virtual_server(this->getServerId());
|
||||||
if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered};
|
if(!virtual_file_server) {
|
||||||
|
return command_result{error::file_virtual_server_not_registered};
|
||||||
|
}
|
||||||
|
|
||||||
auto list_response = file::server()->file_transfer().list_transfer(); //FIXME: Only for the appropriate servers!
|
auto list_response = file::server()->file_transfer().list_transfer(); //FIXME: Only for the appropriate servers!
|
||||||
if(!list_response->wait_for(kFileAPITimeout))
|
if(!list_response->wait_for(kFileAPITimeout)) {
|
||||||
return command_result{error::file_api_timeout};
|
return command_result{error::file_api_timeout};
|
||||||
|
}
|
||||||
|
|
||||||
if(!list_response->succeeded()) {
|
if(!list_response->succeeded()) {
|
||||||
using ErrorType = file::transfer::TransferListError;
|
using ErrorType = file::transfer::TransferListError;
|
||||||
@ -998,8 +1003,9 @@ command_result ConnectedClient::handleCommandFTList(ts::Command &cmd) {
|
|||||||
|
|
||||||
|
|
||||||
const auto& transfers = list_response->response();
|
const auto& transfers = list_response->response();
|
||||||
if(transfers.empty())
|
if(transfers.empty()) {
|
||||||
return command_result{error::database_empty_result};
|
return command_result{error::database_empty_result};
|
||||||
|
}
|
||||||
|
|
||||||
ts::command_builder notify{this->notify_response_command("notifyftlist")};
|
ts::command_builder notify{this->notify_response_command("notifyftlist")};
|
||||||
size_t bulk_index{0};
|
size_t bulk_index{0};
|
||||||
@ -1036,11 +1042,14 @@ command_result ConnectedClient::handleCommandFTStop(ts::Command &cmd) {
|
|||||||
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
||||||
|
|
||||||
auto virtual_file_server = file::server()->find_virtual_server(this->getServerId());
|
auto virtual_file_server = file::server()->find_virtual_server(this->getServerId());
|
||||||
if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered};
|
if(!virtual_file_server) {
|
||||||
|
return command_result{error::file_virtual_server_not_registered};
|
||||||
|
}
|
||||||
|
|
||||||
auto stop_response = file::server()->file_transfer().stop_transfer(virtual_file_server, cmd["serverftfid"], false);
|
auto stop_response = file::server()->file_transfer().stop_transfer(virtual_file_server, cmd["serverftfid"], false);
|
||||||
if(!stop_response->wait_for(kFileAPITimeout))
|
if(!stop_response->wait_for(kFileAPITimeout)) {
|
||||||
return command_result{error::file_api_timeout};
|
return command_result{error::file_api_timeout};
|
||||||
|
}
|
||||||
|
|
||||||
if(!stop_response->succeeded()) {
|
if(!stop_response->succeeded()) {
|
||||||
using ErrorType = file::transfer::TransferActionError::Type;
|
using ErrorType = file::transfer::TransferActionError::Type;
|
||||||
@ -1051,8 +1060,9 @@ command_result ConnectedClient::handleCommandFTStop(ts::Command &cmd) {
|
|||||||
|
|
||||||
case ErrorType::UNKNOWN: {
|
case ErrorType::UNKNOWN: {
|
||||||
auto error_detail = std::to_string((int) stop_response->error().error_type);
|
auto error_detail = std::to_string((int) stop_response->error().error_type);
|
||||||
if(!stop_response->error().error_message.empty())
|
if(!stop_response->error().error_message.empty()) {
|
||||||
error_detail += "/" + stop_response->error().error_message;
|
error_detail += "/" + stop_response->error().error_message;
|
||||||
|
}
|
||||||
return command_result{error::vs_critical, error_detail};
|
return command_result{error::vs_critical, error_detail};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -559,7 +559,7 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) {
|
|||||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
||||||
return command_result{perr};
|
return command_result{perr};
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), 0))
|
if(!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -589,7 +589,7 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
|
|||||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
||||||
return command_result{perr};
|
return command_result{perr};
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), 0))
|
if(!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -721,7 +721,7 @@ command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command &
|
|||||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
|
||||||
return command_result{perr};
|
return command_result{perr};
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
|
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -755,7 +755,7 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &
|
|||||||
return command_result{perr};
|
return command_result{perr};
|
||||||
|
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
|
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
|
@ -919,7 +919,7 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), 0))
|
if(!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -985,7 +985,7 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), 0))
|
if(!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -1059,7 +1059,7 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command&
|
|||||||
if(groups.empty())
|
if(groups.empty())
|
||||||
return command_result{error::ok};
|
return command_result{error::ok};
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), 0))
|
if(!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
@ -1138,7 +1138,7 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command&
|
|||||||
|
|
||||||
if(groups.empty()) return command_result{error::ok};
|
if(groups.empty()) return command_result{error::ok};
|
||||||
|
|
||||||
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
ts::command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
|
||||||
if(!pparser.validate(this->ref(), 0))
|
if(!pparser.validate(this->ref(), 0))
|
||||||
return pparser.build_command_result();
|
return pparser.build_command_result();
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ namespace ts::server {
|
|||||||
|
|
||||||
void broadcast_text_message(const std::string &message);
|
void broadcast_text_message(const std::string &message);
|
||||||
|
|
||||||
void tick(const std::chrono::system_clock::time_point &time) override;
|
void tick_server(const std::chrono::system_clock::time_point &time) override;
|
||||||
std::chrono::system_clock::time_point next_music_tick;
|
std::chrono::system_clock::time_point next_music_tick;
|
||||||
|
|
||||||
void execute_music_tick(const std::shared_ptr<ts::music::PlayableSong>&);
|
void execute_music_tick(const std::shared_ptr<ts::music::PlayableSong>&);
|
||||||
|
@ -138,8 +138,8 @@ void MusicClient::replay_song(const shared_ptr<music::PlayableSong> &entry, cons
|
|||||||
this->notifySongChange(entry);
|
this->notifySongChange(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MusicClient::tick(const std::chrono::system_clock::time_point &time) {
|
void MusicClient::tick_server(const std::chrono::system_clock::time_point &time) {
|
||||||
ConnectedClient::tick(time);
|
ConnectedClient::tick_server(time);
|
||||||
|
|
||||||
if(this->_player_state == ReplayState::SLEEPING)
|
if(this->_player_state == ReplayState::SLEEPING)
|
||||||
this->handle_event_song_dry();
|
this->handle_event_song_dry();
|
||||||
|
@ -2,9 +2,6 @@
|
|||||||
#include <src/server/QueryServer.h>
|
#include <src/server/QueryServer.h>
|
||||||
#include "QueryClient.h"
|
#include "QueryClient.h"
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <src/Configuration.h>
|
|
||||||
#include <log/LogUtils.h>
|
|
||||||
#include <misc/memtracker.h>
|
|
||||||
#include "src/InstanceHandler.h"
|
#include "src/InstanceHandler.h"
|
||||||
#include <pipes/errors.h>
|
#include <pipes/errors.h>
|
||||||
#include <misc/std_unique_ptr.h>
|
#include <misc/std_unique_ptr.h>
|
||||||
@ -13,14 +10,34 @@ using namespace std;
|
|||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using namespace ts;
|
using namespace ts;
|
||||||
using namespace ts::server;
|
using namespace ts::server;
|
||||||
|
using namespace ts::server::query;
|
||||||
|
|
||||||
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
|
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
|
||||||
#define TCP_NOPUSH TCP_CORK
|
#define TCP_NOPUSH TCP_CORK
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//#define DEBUG_TRAFFIC
|
//#define DEBUG_TRAFFIC
|
||||||
|
NetworkBuffer* NetworkBuffer::allocate(size_t length) {
|
||||||
|
auto result = (NetworkBuffer*) malloc(length + sizeof(NetworkBuffer));
|
||||||
|
new (result) NetworkBuffer{};
|
||||||
|
result->length = length;
|
||||||
|
result->ref_count++;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QueryClient::QueryClient(QueryServer* handle, int sockfd) : ConnectedClient(handle->sql, nullptr), handle(handle), clientFd(sockfd) {
|
NetworkBuffer* NetworkBuffer::ref() {
|
||||||
|
this->ref_count++;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkBuffer::unref() {
|
||||||
|
if(--this->ref_count == 0) {
|
||||||
|
this->NetworkBuffer::~NetworkBuffer();
|
||||||
|
::free(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryClient::QueryClient(QueryServer* handle, int sockfd) : ConnectedClient(handle->sql, nullptr), handle(handle), client_file_descriptor(sockfd) {
|
||||||
memtrack::allocated<QueryClient>(this);
|
memtrack::allocated<QueryClient>(this);
|
||||||
int enabled = 1;
|
int enabled = 1;
|
||||||
int disabled = 0;
|
int disabled = 0;
|
||||||
@ -28,30 +45,46 @@ QueryClient::QueryClient(QueryServer* handle, int sockfd) : ConnectedClient(hand
|
|||||||
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0) {
|
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0) {
|
||||||
logError(this->getServerId(), "[Query] Could not disable nopush for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this), errno, strerror(errno));
|
logError(this->getServerId(), "[Query] Could not disable nopush for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this), errno, strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof enabled) < 0) {
|
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof enabled) < 0) {
|
||||||
logError(this->getServerId(), "[Query] Could not disable no delay for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this), errno, strerror(errno));
|
logError(this->getServerId(), "[Query] Could not disable no delay for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this), errno, strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
this->readEvent = event_new(this->handle->eventLoop, this->clientFd, EV_READ | EV_PERSIST, [](int a, short b, void* c){ ((QueryClient*) c)->handleMessageRead(a, b, c); }, this);
|
|
||||||
this->writeEvent = event_new(this->handle->eventLoop, this->clientFd, EV_WRITE, [](int a, short b, void* c){ ((QueryClient*) c)->handleMessageWrite(a, b, c); }, this);
|
|
||||||
|
|
||||||
this->state = ConnectionState::CONNECTED;
|
this->state = ConnectionState::CONNECTED;
|
||||||
connectedTimestamp = system_clock::now();
|
connectedTimestamp = system_clock::now();
|
||||||
|
|
||||||
this->resetEventMask();
|
this->resetEventMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::applySelfLock(const std::shared_ptr<ts::server::QueryClient> &cl) {
|
void QueryClient::initialize_self_reference(const std::shared_ptr<QueryClient> &reference) {
|
||||||
this->_this = cl;
|
this->_this = reference;
|
||||||
|
|
||||||
|
this->command_queue = std::make_unique<ServerCommandQueue>(
|
||||||
|
serverInstance->server_command_executor(),
|
||||||
|
std::make_unique<QueryClientCommandHandler>(reference)
|
||||||
|
);
|
||||||
|
|
||||||
|
this->event_read = event_new(this->handle->event_io_loop, this->client_file_descriptor, EV_READ | EV_PERSIST, [](int a, short b, void* c){
|
||||||
|
((QueryClient *) c)->handle_event_read(a, b, c); }, this);
|
||||||
|
this->event_write = event_new(this->handle->event_io_loop, this->client_file_descriptor, EV_WRITE, [](int a, short b, void* c){
|
||||||
|
((QueryClient *) c)->handle_event_write(a, b, c); }, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryClient::~QueryClient() {
|
QueryClient::~QueryClient() {
|
||||||
memtrack::freed<QueryClient>(this);
|
if(this->line_buffer) {
|
||||||
// if(this->closeLock.tryLock() != 0)
|
free(this->line_buffer);
|
||||||
// logCritical("Query manager deleted, but is still in usage! (closeLock)");
|
this->line_buffer = nullptr;
|
||||||
// if(this->bufferLock.tryLock() != 0)
|
}
|
||||||
// logCritical("Query manager deleted, but is still in usage! (bufferLock)");
|
|
||||||
this->ssl_handler.finalize();
|
this->ssl_handler.finalize();
|
||||||
|
|
||||||
|
while(this->write_buffer_head) {
|
||||||
|
auto buffer = std::exchange(this->write_buffer_head, this->write_buffer_head->next_buffer);
|
||||||
|
buffer->unref();
|
||||||
|
}
|
||||||
|
this->write_buffer_tail = nullptr;
|
||||||
|
|
||||||
|
memtrack::freed<QueryClient>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::preInitialize() {
|
void QueryClient::preInitialize() {
|
||||||
@ -62,7 +95,6 @@ void QueryClient::preInitialize() {
|
|||||||
|
|
||||||
DatabaseHelper::assignDatabaseId(this->sql, this->getServerId(), _this.lock());
|
DatabaseHelper::assignDatabaseId(this->sql, this->getServerId(), _this.lock());
|
||||||
|
|
||||||
|
|
||||||
if(ts::config::query::sslMode == 0) {
|
if(ts::config::query::sslMode == 0) {
|
||||||
this->connectionType = ConnectionType::PLAIN;
|
this->connectionType = ConnectionType::PLAIN;
|
||||||
this->postInitialize();
|
this->postInitialize();
|
||||||
@ -70,11 +102,11 @@ void QueryClient::preInitialize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::postInitialize() {
|
void QueryClient::postInitialize() {
|
||||||
lock_guard<recursive_mutex> lock(this->lock_packet_handle); /* we dont want to handle anything while we're initializing */
|
lock_guard command_lock(this->command_lock);
|
||||||
this->connectTimestamp = system_clock::now();
|
this->connectTimestamp = system_clock::now();
|
||||||
this->properties()[property::CLIENT_LASTCONNECTED] = duration_cast<seconds>(this->connectTimestamp.time_since_epoch()).count();
|
this->properties()[property::CLIENT_LASTCONNECTED] = duration_cast<seconds>(this->connectTimestamp.time_since_epoch()).count();
|
||||||
|
|
||||||
if(ts::config::query::sslMode == 1 && this->connectionType != ConnectionType::SSL_ENCRIPTED) {
|
if(ts::config::query::sslMode == 1 && this->connectionType != ConnectionType::SSL_ENCRYPTED) {
|
||||||
command_result error{error::failed_connection_initialisation, "Please use a SSL encryption!"};
|
command_result error{error::failed_connection_initialisation, "Please use a SSL encryption!"};
|
||||||
this->notifyError(error);
|
this->notifyError(error);
|
||||||
error.release_data();
|
error.release_data();
|
||||||
@ -82,7 +114,7 @@ void QueryClient::postInitialize() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeMessage(config::query::motd);
|
send_message(config::query::motd);
|
||||||
assert(this->handle);
|
assert(this->handle);
|
||||||
|
|
||||||
if(this->handle->ip_whitelist) {
|
if(this->handle->ip_whitelist) {
|
||||||
@ -107,314 +139,418 @@ void QueryClient::postInitialize() {
|
|||||||
debugMessage(LOG_QUERY, "Got new query client from {}. Whitelisted: {}", this->getLoggingPeerIp(), this->whitelisted);
|
debugMessage(LOG_QUERY, "Got new query client from {}. Whitelisted: {}", this->getLoggingPeerIp(), this->whitelisted);
|
||||||
|
|
||||||
if(!this->whitelisted) {
|
if(!this->whitelisted) {
|
||||||
threads::MutexLock lock(this->handle->loginLock);
|
std::lock_guard connect_lock{this->handle->connected_clients_mutex};
|
||||||
if(this->handle->queryBann.count(this->getPeerIp()) > 0) {
|
auto address = this->getPeerIp();
|
||||||
auto ban = this->handle->queryBann[this->getPeerIp()];
|
if(this->handle->client_connect_bans.count(address) > 0) {
|
||||||
|
auto ban = this->handle->client_connect_bans[address];
|
||||||
Command cmd("error");
|
Command cmd("error");
|
||||||
auto err = findError("server_connect_banned");
|
auto err = findError("server_connect_banned");
|
||||||
cmd["id"] = err.errorId;
|
cmd["id"] = err.errorId;
|
||||||
cmd["msg"] = err.message;
|
cmd["msg"] = err.message;
|
||||||
cmd["extra_msg"] = "you may retry in " + to_string(duration_cast<seconds>(ban - system_clock::now()).count()) + " seconds";
|
cmd["extra_msg"] = "you may retry in " + to_string(duration_cast<seconds>(ban - system_clock::now()).count()) + " seconds";
|
||||||
this->sendCommand(cmd);
|
this->sendCommand(cmd);
|
||||||
this->disconnect("");
|
this->close_connection(std::chrono::system_clock::now() + std::chrono::seconds{1});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->update_cached_permissions();
|
this->update_cached_permissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::writeMessage(const std::string& message) {
|
void QueryClient::send_message(const std::string_view& message) {
|
||||||
if(this->state == ConnectionState::DISCONNECTED || !this->handle) return;
|
if(this->state == ConnectionState::DISCONNECTED || !this->handle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(this->connectionType == ConnectionType::PLAIN) this->writeRawMessage(message);
|
if(this->connectionType == ConnectionType::PLAIN) {
|
||||||
else if(this->connectionType == ConnectionType::SSL_ENCRIPTED) this->ssl_handler.send(pipes::buffer_view{(void*) message.data(), message.length()});
|
this->enqueue_write_buffer(message);
|
||||||
else logCritical(LOG_GENERAL, "Invalid query connection type to write to!");
|
} else if(this->connectionType == ConnectionType::SSL_ENCRYPTED) {
|
||||||
|
this->ssl_handler.send(pipes::buffer_view{(void*) message.data(), message.length()});
|
||||||
|
} else {
|
||||||
|
logCritical(LOG_GENERAL, "Invalid query connection type to write to!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool QueryClient::disconnect(const std::string &reason) {
|
bool QueryClient::disconnect(const std::string &reason) {
|
||||||
if(!reason.empty()) {
|
if(!reason.empty()) {
|
||||||
Command cmd("disconnect");
|
ts::command_builder notify{"disconnect"};
|
||||||
cmd["reason"] = reason;
|
notify.put_unchecked(0, "reason", reason);
|
||||||
this->sendCommand(cmd);
|
this->sendCommand(notify, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this->close_connection(system_clock::now() + seconds(1));
|
return this->close_connection(system_clock::now() + seconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QueryClient::close_connection(const std::chrono::system_clock::time_point& flushTimeout) {
|
bool QueryClient::close_connection(const std::chrono::system_clock::time_point& flush_timeout_) {
|
||||||
auto ownLock = dynamic_pointer_cast<QueryClient>(_this.lock());
|
this->flush_timeout = flush_timeout_;
|
||||||
if(!ownLock) return false;
|
|
||||||
|
|
||||||
unique_lock<std::recursive_mutex> handleLock(this->lock_packet_handle);
|
bool should_flush = std::chrono::system_clock::now() < flush_timeout;
|
||||||
unique_lock<threads::Mutex> lock(this->closeLock);
|
{
|
||||||
|
std::lock_guard network_lock{this->network_mutex};
|
||||||
bool flushing = flushTimeout.time_since_epoch().count() != 0;
|
if(this->event_read) {
|
||||||
if(this->state == ConnectionState::DISCONNECTED || (flushing && this->state == ConnectionState::DISCONNECTING)) return false;
|
event_del_noblock(this->event_read);
|
||||||
this->state = flushing ? ConnectionState::DISCONNECTING : ConnectionState::DISCONNECTED;
|
}
|
||||||
|
|
||||||
if(this->readEvent) { //Attention dont trigger this within the read thread!
|
if(!should_flush && this->event_write) {
|
||||||
event_del_block(this->readEvent);
|
event_del_noblock(this->event_write);
|
||||||
event_free(this->readEvent);
|
|
||||||
this->readEvent = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this->server){
|
|
||||||
{
|
|
||||||
unique_lock channel_lock(this->server->channel_tree_lock);
|
|
||||||
this->server->unregisterClient(_this.lock(), "disconnected", channel_lock);
|
|
||||||
}
|
}
|
||||||
this->server->groups->disableCache(this->getClientDatabaseId());
|
|
||||||
this->server = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(flushing){
|
if(should_flush) {
|
||||||
this->flushThread = new threads::Thread(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [ownLock, flushTimeout](){
|
this->handle->enqueue_query_disconnect(dynamic_pointer_cast<QueryClient>(this->ref()));
|
||||||
while(ownLock->state == ConnectionState::DISCONNECTING && flushTimeout > system_clock::now()){
|
|
||||||
{
|
|
||||||
std::lock_guard buffer_lock(ownLock->buffer_lock);
|
|
||||||
if(ownLock->readQueue.empty() && ownLock->writeQueue.empty()) break;
|
|
||||||
}
|
|
||||||
usleep(10 * 1000);
|
|
||||||
}
|
|
||||||
if(ownLock->state == ConnectionState::DISCONNECTING) ownLock->disconnectFinal();
|
|
||||||
});
|
|
||||||
flushThread->name("Flush thread QC").execute();
|
|
||||||
} else {
|
} else {
|
||||||
threads::MutexLock l1(this->flushThreadLock);
|
this->handle->enqueue_query_connection_close(dynamic_pointer_cast<QueryClient>(this->ref()));
|
||||||
handleLock.unlock();
|
|
||||||
lock.unlock();
|
|
||||||
if(this->flushThread){
|
|
||||||
threads::NegatedMutexLock l(this->closeLock);
|
|
||||||
this->flushThread->join();
|
|
||||||
}
|
|
||||||
disconnectFinal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::disconnectFinal() {
|
void QueryClient::execute_final_disconnect() {
|
||||||
lock_guard<recursive_mutex> lock_tick(this->lock_query_tick);
|
assert(!this->server);
|
||||||
lock_guard<recursive_mutex> lock_handle(this->lock_packet_handle);
|
|
||||||
threads::MutexLock lock_close(this->closeLock);
|
|
||||||
std::unique_lock buffer_lock(this->buffer_lock, try_to_lock);
|
|
||||||
|
|
||||||
if(final_disconnected) {
|
|
||||||
logError(LOG_QUERY, "Tried to disconnect a client twice!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final_disconnected = true;
|
|
||||||
this->state = ConnectionState::DISCONNECTED;
|
|
||||||
{
|
{
|
||||||
threads::MutexTryLock l(this->flushThreadLock);
|
std::unique_lock network_lock{this->network_mutex};
|
||||||
if(!!l) {
|
auto event_read_ = std::exchange(this->event_read, nullptr);
|
||||||
if(this->flushThread) {
|
auto event_write_ = std::exchange(this->event_write, nullptr);
|
||||||
this->flushThread->detach();
|
network_lock.unlock();
|
||||||
delete this->flushThread; //Release the captured this lock
|
|
||||||
this->flushThread = nullptr;
|
if(event_read_) {
|
||||||
}
|
event_del_block(event_read_);
|
||||||
|
event_free(event_read_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event_write_) {
|
||||||
|
event_del_block(event_write_);
|
||||||
|
event_free(event_write_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this->writeEvent) {
|
if(this->client_file_descriptor > 0) {
|
||||||
event_del_block(this->writeEvent);
|
if(shutdown(this->client_file_descriptor, SHUT_RDWR) < 0) {
|
||||||
event_free(this->writeEvent);
|
|
||||||
this->writeEvent = nullptr;
|
|
||||||
}
|
|
||||||
if(this->readEvent) {
|
|
||||||
event_del_block(this->readEvent);
|
|
||||||
event_free(this->readEvent);
|
|
||||||
this->readEvent = nullptr;
|
|
||||||
}
|
|
||||||
if(this->clientFd > 0) {
|
|
||||||
if(shutdown(this->clientFd, SHUT_RDWR) < 0)
|
|
||||||
debugMessage(LOG_QUERY, "Could not shutdown query client socket! {} ({})", errno, strerror(errno));
|
debugMessage(LOG_QUERY, "Could not shutdown query client socket! {} ({})", errno, strerror(errno));
|
||||||
if(close(this->clientFd) < 0)
|
}
|
||||||
|
|
||||||
|
if(close(this->client_file_descriptor) < 0) {
|
||||||
debugMessage(LOG_QUERY, "Failed to close the query client socket! {} ({})", errno, strerror(errno));
|
debugMessage(LOG_QUERY, "Failed to close the query client socket! {} ({})", errno, strerror(errno));
|
||||||
this->clientFd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this->server) {
|
|
||||||
{
|
|
||||||
unique_lock channel_lock(this->server->channel_tree_lock);
|
|
||||||
this->server->unregisterClient(_this.lock(), "disconnected", channel_lock);
|
|
||||||
}
|
}
|
||||||
this->server->groups->disableCache(this->getClientDatabaseId());
|
|
||||||
this->server = nullptr;
|
this->client_file_descriptor = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->readQueue.clear();
|
|
||||||
this->writeQueue.clear();
|
|
||||||
|
|
||||||
if(this->handle)
|
|
||||||
this->handle->unregisterConnection(dynamic_pointer_cast<QueryClient>(_this.lock()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::writeRawMessage(const std::string &message) {
|
void QueryClient::enqueue_write_buffer(const std::string_view &message) {
|
||||||
|
auto buffer = NetworkBuffer::allocate(message.length());
|
||||||
|
memcpy(buffer->data(), message.data(), message.length());
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock(this->buffer_lock);
|
std::lock_guard buffer_lock{this->network_mutex};
|
||||||
this->writeQueue.push_back(message);
|
if(this->event_write) {
|
||||||
}
|
*this->write_buffer_tail = buffer;
|
||||||
if(this->writeEvent) event_add(this->writeEvent, nullptr);
|
this->write_buffer_tail = &buffer->next_buffer;
|
||||||
}
|
|
||||||
|
|
||||||
void QueryClient::handleMessageWrite(int fd, short, void *) {
|
event_add(this->event_write, nullptr);
|
||||||
auto ownLock = _this.lock();
|
|
||||||
|
|
||||||
std::unique_lock buffer_lock(this->buffer_lock, try_to_lock);
|
|
||||||
if(this->state == ConnectionState::DISCONNECTED) return;
|
|
||||||
if(!buffer_lock.owns_lock()) {
|
|
||||||
if(this->writeEvent)
|
|
||||||
event_add(this->writeEvent, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int writes = 0;
|
|
||||||
string buffer;
|
|
||||||
while(writes < 10 && !this->writeQueue.empty()) {
|
|
||||||
if(buffer.empty()) {
|
|
||||||
buffer = std::move(this->writeQueue.front());
|
|
||||||
this->writeQueue.pop_front();
|
|
||||||
}
|
|
||||||
auto length = send(fd, buffer.data(), buffer.length(), MSG_NOSIGNAL);
|
|
||||||
#ifdef DEBUG_TRAFFIC
|
|
||||||
debugMessage("Write " + to_string(buffer.length()));
|
|
||||||
hexDump((void *) buffer.data(), buffer.length());
|
|
||||||
#endif
|
|
||||||
if(length == -1) {
|
|
||||||
if (errno == EINTR || errno == EAGAIN) {
|
|
||||||
if(this->writeEvent)
|
|
||||||
event_add(this->writeEvent, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logError(LOG_QUERY, "{} Failed to write message: {} ({} => {})", CLIENT_STR_LOG_PREFIX, length, errno, strerror(errno));
|
|
||||||
threads::Thread([=](){ ownLock->close_connection(chrono::system_clock::now() + chrono::seconds{5}); }).detach();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(buffer.length() == length)
|
|
||||||
buffer = "";
|
|
||||||
else
|
|
||||||
buffer = buffer.substr(length);
|
|
||||||
}
|
|
||||||
writes++;
|
|
||||||
}
|
|
||||||
if(!buffer.empty())
|
|
||||||
this->writeQueue.push_front(buffer);
|
|
||||||
|
|
||||||
if(!this->writeQueue.empty() && this->writeEvent)
|
|
||||||
event_add(this->writeEvent, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueryClient::handleMessageRead(int fd, short, void *) {
|
|
||||||
auto ownLock = dynamic_pointer_cast<QueryClient>(_this.lock());
|
|
||||||
if(!ownLock) {
|
|
||||||
logCritical(LOG_QUERY, "Could not get own lock!");
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
string buffer(1024, 0);
|
|
||||||
|
|
||||||
auto length = read(fd, (void*) buffer.data(), buffer.length());
|
|
||||||
if(length <= 0){
|
|
||||||
if(errno == EINTR || errno == EAGAIN)
|
|
||||||
;//event_add(this->readEvent, nullptr);
|
|
||||||
else if(length == 0 && errno == 0) {
|
|
||||||
logMessage(LOG_QUERY, "{} Connection closed. Client disconnected.", CLIENT_STR_LOG_PREFIX);
|
|
||||||
event_del_noblock(this->readEvent);
|
|
||||||
std::thread([ownLock]{
|
|
||||||
ownLock->close_connection();
|
|
||||||
}).detach();
|
|
||||||
} else {
|
|
||||||
logError(LOG_QUERY, "{} Failed to read! Code: {} errno: {} message: {}", CLIENT_STR_LOG_PREFIX, length, errno, strerror(errno));
|
|
||||||
event_del_noblock(this->readEvent);
|
|
||||||
threads::Thread(THREAD_SAVE_OPERATIONS, [ownLock](){ ownLock->close_connection(); }).detach();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.resize(length);
|
|
||||||
{
|
|
||||||
std::lock_guard buffer_lock(this->buffer_lock);
|
|
||||||
if(this->state == ConnectionState::DISCONNECTED)
|
|
||||||
return;
|
return;
|
||||||
this->readQueue.push_back(std::move(buffer));
|
}
|
||||||
#ifdef DEBUG_TRAFFIC
|
|
||||||
debugMessage("Read " + to_string(buffer.length()));
|
|
||||||
hexDump((void *) buffer.data(), buffer.length());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this->handle)
|
/* We don't have a network write event. Drop the buffer. */
|
||||||
this->handle->executePool()->execute([ownLock]() {
|
buffer->unref();
|
||||||
int counter = 0;
|
|
||||||
while(ownLock->tickIOMessageProgress() && counter++ < 15);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QueryClient::tickIOMessageProgress() {
|
void QueryClient::handle_event_write(int fd, short, void *) {
|
||||||
lock_guard<recursive_mutex> lock(this->lock_packet_handle);
|
NetworkBuffer* write_buffer{nullptr};
|
||||||
if(!this->handle || this->state == ConnectionState::DISCONNECTED || this->state == ConnectionState::DISCONNECTING) return false;
|
|
||||||
|
|
||||||
string message;
|
|
||||||
bool next = false;
|
|
||||||
{
|
{
|
||||||
std::lock_guard buffer_lock(this->buffer_lock);
|
std::lock_guard buffer_lock{this->network_mutex};
|
||||||
if(this->readQueue.empty()) return false;
|
if(this->write_buffer_head) {
|
||||||
message = std::move(this->readQueue.front());
|
write_buffer = this->write_buffer_head->ref();
|
||||||
this->readQueue.pop_front();
|
}
|
||||||
next |= this->readQueue.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while(write_buffer) {
|
||||||
|
auto length = send(fd, (const char*) write_buffer->data() + write_buffer->bytes_written, write_buffer->length - write_buffer->bytes_written, MSG_NOSIGNAL);
|
||||||
|
if(length == -1) {
|
||||||
|
write_buffer->unref();
|
||||||
|
|
||||||
if(this->connectionType == ConnectionType::PLAIN) {
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
int count = 0;
|
std::lock_guard event_lock{this->network_mutex};
|
||||||
while(this->handleMessage(pipes::buffer_view{(void*) message.data(), message.length()}) && count++ < 15) message = "";
|
if(this->event_write) {
|
||||||
next |= count == 15;
|
event_add(this->event_write, nullptr);
|
||||||
} else if(this->connectionType == ConnectionType::SSL_ENCRIPTED) {
|
}
|
||||||
this->ssl_handler.process_incoming_data(pipes::buffer_view{(void*) message.data(), message.length()});
|
} else {
|
||||||
} else if(this->connectionType == ConnectionType::UNKNOWN) {
|
logError(LOG_QUERY, "{} Failed to send message ({}/{}). Closing connection.", CLIENT_STR_LOG_PREFIX, errno, strerror(errno));
|
||||||
if(config::query::sslMode != 0 && pipes::SSL::isSSLHeader(message)) {
|
this->close_connection(std::chrono::system_clock::time_point{});
|
||||||
this->initializeSSL();
|
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock event_lock{this->network_mutex};
|
||||||
|
auto event_write_ = std::exchange(this->event_write, nullptr);
|
||||||
|
event_lock.unlock();
|
||||||
|
|
||||||
|
if(event_write_) {
|
||||||
|
event_del_noblock(event_write_);
|
||||||
|
event_free(event_write_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the "this" ptr might be dangling now since we can't join the write event any more! */
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_buffer->bytes_written += length;
|
||||||
|
assert(write_buffer->bytes_written <= write_buffer->length);
|
||||||
|
|
||||||
|
if(write_buffer->bytes_written == write_buffer->length) {
|
||||||
/*
|
/*
|
||||||
* - Content
|
* Even though we might free the buffer here we could still use the pointer for comparison.
|
||||||
* \x16
|
* If the buffer is still the head buffer it should not have been deallocated since
|
||||||
* -Version (1)
|
* the queue itself holds a reference.
|
||||||
* \x03 \x00
|
|
||||||
* - length (2)
|
|
||||||
* \x00 \x04
|
|
||||||
*
|
|
||||||
* - Header
|
|
||||||
* \x00 -> hello request (3)
|
|
||||||
* \x05 -> length (4)
|
|
||||||
*/
|
*/
|
||||||
|
write_buffer->unref();
|
||||||
|
|
||||||
//this->writeRawMessage(string("\x16\x03\x01\x00\x05\x00\x00\x00\x00\x00", 10));
|
/* Buffer must be freed, but we don't want do that while holding the lock */
|
||||||
} else {
|
NetworkBuffer* cleanup_buffer{nullptr};
|
||||||
this->connectionType = ConnectionType::PLAIN;
|
|
||||||
this->postInitialize();
|
/* Buffer finished, sending next one */
|
||||||
}
|
{
|
||||||
next = true;
|
std::lock_guard buffer_lock{this->network_mutex};
|
||||||
{
|
if(this->write_buffer_head == write_buffer) {
|
||||||
std::lock_guard buffer_lock(this->buffer_lock);
|
/* Buffer successfully send. Sending the next one. */
|
||||||
this->readQueue.push_front(std::move(message));
|
cleanup_buffer = this->write_buffer_head;
|
||||||
|
|
||||||
|
this->write_buffer_head = this->write_buffer_head->next_buffer;
|
||||||
|
if(this->write_buffer_head) {
|
||||||
|
/* we've a next buffer */
|
||||||
|
write_buffer = this->write_buffer_head->ref();
|
||||||
|
} else {
|
||||||
|
assert(this->write_buffer_tail == &write_buffer->next_buffer);
|
||||||
|
write_buffer = nullptr;
|
||||||
|
this->write_buffer_tail = &this->write_buffer_head;
|
||||||
|
}
|
||||||
|
} else if(this->write_buffer_head) {
|
||||||
|
/* Our buffer got dropped (who knows why). Just send the next one. */
|
||||||
|
write_buffer = this->write_buffer_head->ref();
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Nothing more to write.
|
||||||
|
*/
|
||||||
|
write_buffer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cleanup_buffer) {
|
||||||
|
cleanup_buffer->unref();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return next;
|
|
||||||
|
/* This state should only be reached when no more messages are pending to write */
|
||||||
|
assert(!write_buffer);
|
||||||
|
|
||||||
|
if(this->state == ConnectionState::DISCONNECTING) {
|
||||||
|
this->handle->enqueue_query_connection_close(dynamic_pointer_cast<QueryClient>(this->ref()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern InstanceHandler* serverInstance;
|
void QueryClient::handle_event_read(int fd, short, void *) {
|
||||||
|
static const size_t kReadBufferLength = 1024 * 8;
|
||||||
|
uint8_t buffer[kReadBufferLength];
|
||||||
|
|
||||||
|
auto length = read(fd, buffer, kReadBufferLength);
|
||||||
|
if(length <= 0){
|
||||||
|
if(errno == EINTR || errno == EAGAIN) {
|
||||||
|
/* Nothing to read */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(length == 0 && errno == 0) {
|
||||||
|
logMessage(LOG_QUERY, "{} Connection closed. Client disconnected.", CLIENT_STR_LOG_PREFIX);
|
||||||
|
} else {
|
||||||
|
logMessage(LOG_QUERY, "{} Failed to received message ({}/{}). Closing connection.", CLIENT_STR_LOG_PREFIX, errno, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->close_connection(std::chrono::system_clock::time_point{});
|
||||||
|
{
|
||||||
|
std::unique_lock network_lock{this->network_mutex};
|
||||||
|
auto event_read_ = std::exchange(this->event_read, nullptr);
|
||||||
|
network_lock.unlock();
|
||||||
|
|
||||||
|
if(event_read_) {
|
||||||
|
event_del_noblock(event_read_);
|
||||||
|
event_free(event_read_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the "this" ptr might be dangling now since we can't join the read event any more! */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->handle_message_read(std::string_view{(const char *) buffer, (size_t) length});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_ssl_handshake_header(const std::string_view& buffer) {
|
||||||
|
if(buffer.length() < 0x05) return false; //Header too small!
|
||||||
|
|
||||||
|
if(buffer[0] != 0x16) return false; //recordType=handshake
|
||||||
|
|
||||||
|
if(buffer[1] < 1 || buffer[1] > 3) return false; //SSL version
|
||||||
|
if(buffer[2] < 1 || buffer[2] > 3) return false; //TLS version
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryClient::handle_message_read(const std::string_view &message) {
|
||||||
|
if(this->state >= ConnectionState::DISCONNECTING) {
|
||||||
|
/* We don't need to handle any messages. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this->connectionType) {
|
||||||
|
case ConnectionType::PLAIN:
|
||||||
|
this->handle_decoded_message(message);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ConnectionType::SSL_ENCRYPTED:
|
||||||
|
this->ssl_handler.process_incoming_data(pipes::buffer_view{message.data(), message.length()});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ConnectionType::UNKNOWN: {
|
||||||
|
if(config::query::sslMode != 0 && is_ssl_handshake_header(message)) {
|
||||||
|
this->initializeSSL();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - Content
|
||||||
|
* \x16
|
||||||
|
* -Version (1)
|
||||||
|
* \x03 \x00
|
||||||
|
* - length (2)
|
||||||
|
* \x00 \x04
|
||||||
|
*
|
||||||
|
* - Header
|
||||||
|
* \x00 -> hello request (3)
|
||||||
|
* \x05 -> length (4)
|
||||||
|
*/
|
||||||
|
|
||||||
|
this->ssl_handler.process_incoming_data(pipes::buffer_view{message.data(), message.length()});
|
||||||
|
} else {
|
||||||
|
this->connectionType = ConnectionType::PLAIN;
|
||||||
|
this->postInitialize();
|
||||||
|
this->handle_decoded_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t line_buffer_size(size_t target_size) {
|
||||||
|
return target_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryClient::handle_decoded_message(const std::string_view &message) {
|
||||||
|
if(this->line_buffer_length + message.length() > this->line_buffer_capacity) {
|
||||||
|
this->line_buffer_capacity = line_buffer_size(this->line_buffer_length + message.length());
|
||||||
|
|
||||||
|
auto new_buffer = (char*) malloc(this->line_buffer_capacity);
|
||||||
|
memcpy(new_buffer, this->line_buffer, this->line_buffer_length);
|
||||||
|
free(this->line_buffer);
|
||||||
|
this->line_buffer = new_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(this->line_buffer + this->line_buffer_length, message.data(), message.length());
|
||||||
|
this->line_buffer_length += message.length();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we're analyzing the line buffer.
|
||||||
|
* Note: Telnet commands will be executed as empty (idle commands)
|
||||||
|
*/
|
||||||
|
size_t command_start_index{0}, command_end_index, command_start_next;
|
||||||
|
for(; this->line_buffer_scan_offset < this->line_buffer_length; this->line_buffer_scan_offset++) {
|
||||||
|
if(this->line_buffer[this->line_buffer_scan_offset] == '\n') {
|
||||||
|
command_end_index = this->line_buffer_scan_offset;
|
||||||
|
command_start_next = this->line_buffer_scan_offset + 1;
|
||||||
|
} else if((uint8_t) this->line_buffer[this->line_buffer_scan_offset] == 255) {
|
||||||
|
if(this->line_buffer_scan_offset + 3 > this->line_buffer_length) {
|
||||||
|
/* We don't have enough space to fill the telnet command so we use that as the new scan offset */
|
||||||
|
command_end_index = this->line_buffer_scan_offset;
|
||||||
|
command_start_next = this->line_buffer_scan_offset;
|
||||||
|
|
||||||
|
if(command_start_next == command_end_index) {
|
||||||
|
/* We've no prepended data so we're waiting for the tcp command. Loop finished. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
command_end_index = this->line_buffer_scan_offset;
|
||||||
|
command_start_next = this->line_buffer_scan_offset + 3;
|
||||||
|
|
||||||
|
logTrace(LOG_QUERY, "[{}:{}] Received telnet command, code = {}, option = {}",
|
||||||
|
this->getLoggingPeerIp(), this->getPeerPort(),
|
||||||
|
(uint8_t) this->line_buffer[this->line_buffer_scan_offset + 1],
|
||||||
|
(uint8_t) this->line_buffer[this->line_buffer_scan_offset + 2]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No need to check for the upper bounds since there will be \n or 255 before the end of the line */
|
||||||
|
while(this->line_buffer[command_start_index] == '\r') {
|
||||||
|
command_start_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(command_end_index > command_start_index + 1 && this->line_buffer[command_end_index - 1] == '\r') {
|
||||||
|
command_end_index--;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view command_view{this->line_buffer + command_start_index, command_end_index - command_start_index};
|
||||||
|
logTrace(0, "Found command: '{}'", command_view);
|
||||||
|
this->command_queue->enqueue_command_string(command_view);
|
||||||
|
|
||||||
|
command_start_index = command_start_next;
|
||||||
|
if(this->line_buffer_scan_offset + 1 < command_start_next) {
|
||||||
|
this->line_buffer_scan_offset = command_start_next - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(command_start_index > 0) {
|
||||||
|
if(command_start_index == this->line_buffer_length) {
|
||||||
|
this->line_buffer_length = 0;
|
||||||
|
this->line_buffer_scan_offset = 0;
|
||||||
|
} else {
|
||||||
|
assert(this->line_buffer_length > command_start_index);
|
||||||
|
assert(this->line_buffer_scan_offset > command_start_index);
|
||||||
|
memcpy(this->line_buffer, this->line_buffer + command_start_index, this->line_buffer_length - command_start_index);
|
||||||
|
this->line_buffer_length -= command_start_index;
|
||||||
|
this->line_buffer_scan_offset -= command_start_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this->line_buffer_length > ts::config::query::max_line_buffer) {
|
||||||
|
this->line_buffer_length = 0; /* Buffer will be truncated later */
|
||||||
|
logWarning(LOG_QUERY, "[{}] Client exceeded max query line buffer size. Disconnecting client.");
|
||||||
|
this->disconnect("line buffer length exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shrink if possible */
|
||||||
|
if(this->line_buffer_capacity > 8 * 1024 && this->line_buffer_length < 8 * 1024) {
|
||||||
|
this->line_buffer_capacity = 8 * 1024;
|
||||||
|
auto new_buffer = (char*) malloc(this->line_buffer_capacity);
|
||||||
|
memcpy(new_buffer, this->line_buffer, this->line_buffer_length);
|
||||||
|
free(this->line_buffer);
|
||||||
|
this->line_buffer = new_buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void QueryClient::initializeSSL() {
|
void QueryClient::initializeSSL() {
|
||||||
this->connectionType = ConnectionType::SSL_ENCRIPTED;
|
this->connectionType = ConnectionType::SSL_ENCRYPTED;
|
||||||
|
|
||||||
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
|
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
|
||||||
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
|
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
|
||||||
|
|
||||||
this->ssl_handler.callback_data(std::bind(&QueryClient::handleMessage, this, placeholders::_1));
|
this->ssl_handler.callback_data([&](const pipes::buffer_view& buffer) {
|
||||||
this->ssl_handler.callback_write(std::bind(&QueryClient::writeRawMessage, this, placeholders::_1));
|
this->handle_decoded_message(std::string_view{buffer.data_ptr<char>(), buffer.length()});
|
||||||
|
});
|
||||||
|
this->ssl_handler.callback_write([&](const pipes::buffer_view& buffer) {
|
||||||
|
this->enqueue_write_buffer(std::string_view{buffer.data_ptr<char>(), buffer.length()});
|
||||||
|
});
|
||||||
this->ssl_handler.callback_initialized = std::bind(&QueryClient::postInitialize, this);
|
this->ssl_handler.callback_initialized = std::bind(&QueryClient::postInitialize, this);
|
||||||
|
|
||||||
this->ssl_handler.callback_error([&](int code, const std::string& message) {
|
this->ssl_handler.callback_error([&](int code, const std::string& message) {
|
||||||
@ -439,126 +575,23 @@ void QueryClient::initializeSSL() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QueryClient::handleMessage(const pipes::buffer_view& message) {
|
|
||||||
{
|
|
||||||
threads::MutexLock l(this->closeLock);
|
|
||||||
if(this->state == ConnectionState::DISCONNECTED)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_TRAFFIC
|
|
||||||
debugMessage("Handling message " + to_string(message.length()));
|
|
||||||
hexDump((void *) message.data(), message.length());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
string command;
|
|
||||||
{
|
|
||||||
this->lineBuffer += message.string();
|
|
||||||
int length = 2;
|
|
||||||
auto pos = this->lineBuffer.find("\r\n");
|
|
||||||
if(pos == string::npos) pos = this->lineBuffer.find("\n\r");
|
|
||||||
if(pos == string::npos) {
|
|
||||||
length = 1;
|
|
||||||
pos = this->lineBuffer.find('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pos != string::npos){
|
|
||||||
command = this->lineBuffer.substr(0, pos);
|
|
||||||
if(this->lineBuffer.size() > pos + length)
|
|
||||||
this->lineBuffer = this->lineBuffer.substr(pos + length);
|
|
||||||
else
|
|
||||||
this->lineBuffer.clear();
|
|
||||||
}
|
|
||||||
if(pos == string::npos) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(command.empty() || command.find_first_not_of(' ') == string::npos) { //Empty command
|
|
||||||
logTrace(LOG_QUERY, "[{}:{}] Got query idle command (Empty command or spaces)", this->getLoggingPeerIp(), this->getPeerPort());
|
|
||||||
CMD_RESET_IDLE; //if idle time over 5 min than connection drop
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(auto non_escape{command.find_first_not_of('\r')}; non_escape == std::string::npos) {
|
|
||||||
logTrace(LOG_QUERY, "[{}:{}] Got query idle command (\\r)", this->getLoggingPeerIp(), this->getPeerPort());
|
|
||||||
CMD_RESET_IDLE; //if idle time over 5 min than connection drop
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
command = command.substr(non_escape);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(auto non_escape{command.find_first_not_of('\n')}; non_escape == std::string::npos) {
|
|
||||||
logTrace(LOG_QUERY, "[{}:{}] Got query idle command (\\n)", this->getLoggingPeerIp(), this->getPeerPort());
|
|
||||||
CMD_RESET_IDLE; //if idle time over 5 min than connection drop
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
command = command.substr(non_escape);
|
|
||||||
}
|
|
||||||
|
|
||||||
if((uint8_t) command[0] == 255) {
|
|
||||||
string commands{};
|
|
||||||
|
|
||||||
/* we got a telnet command here */
|
|
||||||
while(command.size() >= 2 && (uint8_t) command[0] == 255) {
|
|
||||||
uint8_t code = command[1];
|
|
||||||
uint8_t option = command[2];
|
|
||||||
|
|
||||||
if(!commands.empty())
|
|
||||||
commands += ", ";
|
|
||||||
commands += to_string(code) + ":" + to_string(option);
|
|
||||||
command = command.substr(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
logTrace(LOG_QUERY, "[{}:{}] Received telnet command(s): {}. Ignoring it.",this->getLoggingPeerIp(), this->getPeerPort(), commands);
|
|
||||||
CMD_RESET_IDLE;
|
|
||||||
if(command.empty())
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
unique_ptr<Command> cmd;
|
|
||||||
command_result error{};
|
|
||||||
try {
|
|
||||||
cmd = make_unique<Command>(Command::parse(pipes::buffer_view{(void*) command.data(), command.length()}, true, !ts::config::server::strict_ut8_mode));
|
|
||||||
} catch(std::invalid_argument& ex) {
|
|
||||||
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (invalid argument): {}", this->getLoggingPeerIp(), this->getPeerPort(), command);
|
|
||||||
error.reset(command_result{error::parameter_convert});
|
|
||||||
goto handle_error;
|
|
||||||
} catch(std::exception& ex) {
|
|
||||||
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (exception: {}): {}", this->getLoggingPeerIp(), this->getPeerPort(), ex.what(), command);
|
|
||||||
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
|
|
||||||
goto handle_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this->handleCommandFull(*cmd);
|
|
||||||
} catch(std::exception& ex) {
|
|
||||||
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
|
|
||||||
goto handle_error;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
|
|
||||||
handle_error:
|
|
||||||
this->notifyError(error);
|
|
||||||
error.release_data();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueryClient::sendCommand(const ts::Command &command, bool) {
|
void QueryClient::sendCommand(const ts::Command &command, bool) {
|
||||||
auto cmd = command.build();
|
auto cmd = command.build();
|
||||||
writeMessage(cmd + config::query::newlineCharacter);
|
send_message(cmd + config::query::newlineCharacter);
|
||||||
logTrace(LOG_QUERY, "Send command {}", cmd);
|
logTrace(LOG_QUERY, "Send command {}", cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::sendCommand(const ts::command_builder &command, bool) {
|
void QueryClient::sendCommand(const ts::command_builder &command, bool) {
|
||||||
writeMessage(command.build() + config::query::newlineCharacter);
|
send_message(command.build() + config::query::newlineCharacter);
|
||||||
logTrace(LOG_QUERY, "Send command {}", command.build());
|
logTrace(LOG_QUERY, "Send command {}", command.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::tick(const std::chrono::system_clock::time_point &time) {
|
void QueryClient::tick_server(const std::chrono::system_clock::time_point &time) {
|
||||||
ConnectedClient::tick(time);
|
ConnectedClient::tick_server(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::queryTick() {
|
/* FIXME: TODO: Forbit this while beeing in finalDisconnect! */
|
||||||
lock_guard<recursive_mutex> lock_tick(this->lock_query_tick);
|
void QueryClient::tick_query() {
|
||||||
if(this->idleTimestamp.time_since_epoch().count() > 0 && system_clock::now() - this->idleTimestamp > minutes(5)){
|
if(this->idleTimestamp.time_since_epoch().count() > 0 && system_clock::now() - this->idleTimestamp > minutes(5)){
|
||||||
debugMessage(LOG_QUERY, "Dropping client " + this->getLoggingPeerIp() + "|" + this->getDisplayName() + ". (Timeout)");
|
debugMessage(LOG_QUERY, "Dropping client " + this->getLoggingPeerIp() + "|" + this->getDisplayName() + ". (Timeout)");
|
||||||
this->close_connection(system_clock::now() + seconds(1));
|
this->close_connection(system_clock::now() + seconds(1));
|
||||||
@ -568,37 +601,39 @@ void QueryClient::queryTick() {
|
|||||||
this->connectionType = ConnectionType::PLAIN;
|
this->connectionType = ConnectionType::PLAIN;
|
||||||
this->postInitialize();
|
this->postInitialize();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool QueryClient::notifyChannelSubscribed(const deque<shared_ptr<BasicChannel>> &) {
|
if(this->flush_timeout.time_since_epoch().count() > 0 && std::chrono::system_clock::now() > this->flush_timeout) {
|
||||||
return false;
|
this->handle->enqueue_query_connection_close(dynamic_pointer_cast<QueryClient>(this->ref()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QueryClient::notifyChannelUnsubscribed(const deque<shared_ptr<BasicChannel>> &){
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QueryClient::ignoresFlood() {
|
bool QueryClient::ignoresFlood() {
|
||||||
return this->whitelisted || ConnectedClient::ignoresFlood();
|
return this->whitelisted || ConnectedClient::ignoresFlood();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryClient::disconnect_from_virtual_server() {
|
void QueryClient::disconnect_from_virtual_server(const std::string& reason) {
|
||||||
threads::MutexLock lock(this->command_lock);
|
std::lock_guard command_lock{this->command_lock};
|
||||||
|
|
||||||
auto server_locked = this->server;
|
auto old_server = std::exchange(this->server, nullptr);
|
||||||
if(server_locked) {
|
if(old_server) {
|
||||||
//unregister manager from old server
|
|
||||||
{
|
{
|
||||||
unique_lock tree_lock(this->server->channel_tree_lock);
|
std::unique_lock tree_lock(old_server->channel_tree_lock);
|
||||||
if(this->currentChannel)
|
if(this->currentChannel) {
|
||||||
this->server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock);
|
old_server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock);
|
||||||
this->server->unregisterClient(_this.lock(), "server switch", tree_lock);
|
}
|
||||||
|
|
||||||
|
old_server->unregisterClient(_this.lock(), reason, tree_lock);
|
||||||
}
|
}
|
||||||
server_locked->groups->disableCache(this->getClientDatabaseId());
|
|
||||||
this->channels->reset();
|
{
|
||||||
this->currentChannel = nullptr;
|
std::lock_guard channel_lock{this->channel_lock};
|
||||||
this->server = nullptr;
|
this->channels->reset();
|
||||||
|
this->currentChannel = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_server->groups->disableCache(this->getClientDatabaseId());
|
||||||
this->loadDataForCurrentServer();
|
this->loadDataForCurrentServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
serverInstance->getGroupManager()->enableCache(this->getClientDatabaseId());
|
serverInstance->getGroupManager()->enableCache(this->getClientDatabaseId());
|
||||||
}
|
}
|
@ -5,87 +5,116 @@
|
|||||||
#include <protocol/buffers.h>
|
#include <protocol/buffers.h>
|
||||||
#include <pipes/ssl.h>
|
#include <pipes/ssl.h>
|
||||||
#include "misc/queue.h"
|
#include "misc/queue.h"
|
||||||
|
#include "../shared/ServerCommandExecutor.h"
|
||||||
|
|
||||||
namespace ts::server {
|
namespace ts::server {
|
||||||
class QueryServer;
|
class QueryServer;
|
||||||
class QueryAccount;
|
class QueryAccount;
|
||||||
|
class QueryClientCommandHandler;
|
||||||
|
|
||||||
|
namespace query {
|
||||||
|
struct NetworkBuffer {
|
||||||
|
static NetworkBuffer* allocate(size_t /* length */);
|
||||||
|
|
||||||
|
size_t length;
|
||||||
|
size_t bytes_written{0};
|
||||||
|
NetworkBuffer* next_buffer{nullptr};
|
||||||
|
|
||||||
|
std::atomic_int16_t ref_count{};
|
||||||
|
|
||||||
|
[[nodiscard]] inline const void* data() const { return (const char*) this + sizeof(NetworkBuffer); }
|
||||||
|
[[nodiscard]] inline void* data() { return (char*) this + sizeof(NetworkBuffer); }
|
||||||
|
|
||||||
|
[[nodiscard]] NetworkBuffer* ref();
|
||||||
|
void unref();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class QueryClient : public ConnectedClient {
|
class QueryClient : public ConnectedClient {
|
||||||
friend class QueryServer;
|
friend class QueryServer;
|
||||||
|
friend class QueryClientCommandHandler;
|
||||||
|
|
||||||
|
using NetworkBuffer = query::NetworkBuffer;
|
||||||
|
|
||||||
enum ConnectionType {
|
enum ConnectionType {
|
||||||
PLAIN,
|
PLAIN,
|
||||||
SSL_ENCRIPTED,
|
SSL_ENCRYPTED,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
QueryClient(QueryServer*, int sockfd);
|
QueryClient(QueryServer* /* server handle */, int /* file descriptor */);
|
||||||
~QueryClient() override;
|
~QueryClient() override;
|
||||||
|
|
||||||
|
|
||||||
void writeMessage(const std::string&);
|
|
||||||
|
|
||||||
void sendCommand(const ts::Command &command, bool low = false) override;
|
void sendCommand(const ts::Command &command, bool low = false) override;
|
||||||
void sendCommand(const ts::command_builder &command, bool low) override;
|
void sendCommand(const ts::command_builder &command, bool low) override;
|
||||||
|
|
||||||
bool disconnect(const std::string &reason) override;
|
bool disconnect(const std::string &reason) override;
|
||||||
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
|
bool close_connection(const std::chrono::system_clock::time_point& flush_timeout) override;
|
||||||
void disconnectFinal();
|
|
||||||
|
|
||||||
bool eventActive(QueryEventGroup, QueryEventSpecifier);
|
bool eventActive(QueryEventGroup, QueryEventSpecifier);
|
||||||
void toggleEvent(QueryEventGroup, QueryEventSpecifier, bool);
|
void toggleEvent(QueryEventGroup, QueryEventSpecifier, bool);
|
||||||
void resetEventMask();
|
void resetEventMask();
|
||||||
|
|
||||||
bool ignoresFlood() override;
|
bool ignoresFlood() override;
|
||||||
void disconnect_from_virtual_server();
|
void disconnect_from_virtual_server(const std::string& /* reason */);
|
||||||
|
|
||||||
inline std::shared_ptr<QueryAccount> getQueryAccount() { return this->query_account; }
|
inline std::shared_ptr<QueryAccount> getQueryAccount() { return this->query_account; }
|
||||||
protected:
|
protected:
|
||||||
|
void initialize_self_reference(const std::shared_ptr<QueryClient> &reference);
|
||||||
|
|
||||||
void preInitialize();
|
void preInitialize();
|
||||||
void postInitialize();
|
|
||||||
void tick(const std::chrono::system_clock::time_point &time) override;
|
|
||||||
void queryTick();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void initializeSSL();
|
void initializeSSL();
|
||||||
|
void postInitialize();
|
||||||
|
|
||||||
bool handleMessage(const pipes::buffer_view&);
|
/* Will be called by the query server */
|
||||||
bool tickIOMessageProgress();
|
void execute_final_disconnect();
|
||||||
|
|
||||||
void handleMessageRead(int, short, void*);
|
/* the ticking method will only be called when connected to a server */
|
||||||
void handleMessageWrite(int, short, void*);
|
void tick_server(const std::chrono::system_clock::time_point &time) override;
|
||||||
void writeRawMessage(const std::string&);
|
void tick_query();
|
||||||
|
|
||||||
void applySelfLock(const std::shared_ptr<QueryClient> &cl);
|
/* Methods will be called within the io loop (single thread) */
|
||||||
|
void handle_event_read(int, short, void*);
|
||||||
|
void handle_message_read(const std::string_view& /* message */);
|
||||||
|
void handle_decoded_message(const std::string_view& /* message */);
|
||||||
|
|
||||||
|
/* Methods will be called within the io loop (single thread) */
|
||||||
|
void handle_event_write(int, short, void*);
|
||||||
|
void send_message(const std::string_view&);
|
||||||
|
void enqueue_write_buffer(const std::string_view& /* message */);
|
||||||
private:
|
private:
|
||||||
QueryServer* handle;
|
QueryServer* handle;
|
||||||
|
|
||||||
ConnectionType connectionType = ConnectionType::UNKNOWN;
|
ConnectionType connectionType{ConnectionType::UNKNOWN};
|
||||||
|
|
||||||
bool whitelisted = false;
|
bool whitelisted{false};
|
||||||
int clientFd = -1;
|
int client_file_descriptor{-1};
|
||||||
|
|
||||||
::event* readEvent = nullptr;
|
spin_mutex network_mutex{};
|
||||||
::event* writeEvent = nullptr;
|
::event* event_read{nullptr};
|
||||||
threads::Mutex closeLock;
|
::event* event_write{nullptr};
|
||||||
|
|
||||||
|
/* locked by network_mutex */
|
||||||
|
NetworkBuffer* write_buffer_head{nullptr};
|
||||||
|
NetworkBuffer** write_buffer_tail{&this->write_buffer_head};
|
||||||
|
|
||||||
|
/* pipes::SSL internally thread save */
|
||||||
pipes::SSL ssl_handler;
|
pipes::SSL ssl_handler;
|
||||||
|
|
||||||
std::mutex buffer_lock;
|
std::chrono::system_clock::time_point flush_timeout{};
|
||||||
std::deque<std::string> writeQueue;
|
|
||||||
std::deque<std::string> readQueue;
|
|
||||||
|
|
||||||
threads::Mutex flushThreadLock;
|
/* The line buffer must only be accessed within the io event loop! */
|
||||||
threads::Thread* flushThread = nullptr;
|
char* line_buffer{nullptr};
|
||||||
bool final_disconnected = false;
|
size_t line_buffer_length{0};
|
||||||
|
size_t line_buffer_capacity{0};
|
||||||
|
size_t line_buffer_scan_offset{0};
|
||||||
|
|
||||||
|
/* thread save to access */
|
||||||
|
std::unique_ptr<ServerCommandQueue> command_queue{};
|
||||||
|
|
||||||
std::string lineBuffer;
|
|
||||||
std::chrono::time_point<std::chrono::system_clock> connectedTimestamp;
|
std::chrono::time_point<std::chrono::system_clock> connectedTimestamp;
|
||||||
uint16_t eventMask[QueryEventGroup::QEVENTGROUP_MAX];
|
uint16_t eventMask[QueryEventGroup::QEVENTGROUP_MAX];
|
||||||
|
|
||||||
std::recursive_mutex lock_packet_handle;
|
|
||||||
std::recursive_mutex lock_query_tick;
|
|
||||||
|
|
||||||
std::shared_ptr<QueryAccount> query_account;
|
std::shared_ptr<QueryAccount> query_account;
|
||||||
protected:
|
protected:
|
||||||
command_result handleCommand(Command &command) override;
|
command_result handleCommand(Command &command) override;
|
||||||
@ -181,4 +210,15 @@ namespace ts::server {
|
|||||||
|
|
||||||
command_result handleCommandSetCompressionMode(Command&);
|
command_result handleCommandSetCompressionMode(Command&);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class QueryClientCommandHandler : public ts::server::ServerCommandHandler {
|
||||||
|
public:
|
||||||
|
explicit QueryClientCommandHandler(const std::shared_ptr<QueryClient>& /* client */);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool handle_command(const std::string_view &) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::weak_ptr<QueryClient> client_ref;
|
||||||
|
};
|
||||||
}
|
}
|
@ -21,8 +21,56 @@ using namespace std::chrono;
|
|||||||
using namespace ts;
|
using namespace ts;
|
||||||
using namespace ts::server;
|
using namespace ts::server;
|
||||||
|
|
||||||
constexpr unsigned int string_hash(const char* str, int h = 0) {
|
QueryClientCommandHandler::QueryClientCommandHandler(const std::shared_ptr<QueryClient> &client) : client_ref{client} {}
|
||||||
return !str[h] ? 5381 : (string_hash(str, h + 1U) * 33U) ^ str[h];
|
|
||||||
|
bool QueryClientCommandHandler::handle_command(const std::string_view &command) {
|
||||||
|
auto client = this->client_ref.lock();
|
||||||
|
if(!client) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(command.empty()) {
|
||||||
|
logTrace(LOG_QUERY, "[{}:{}] Got query idle command.", client->getLoggingPeerIp(), client->getPeerPort());
|
||||||
|
client->resetIdleTime();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<Command> cmd;
|
||||||
|
command_result error{};
|
||||||
|
try {
|
||||||
|
cmd = make_unique<Command>(Command::parse(command, true, !ts::config::server::strict_ut8_mode));
|
||||||
|
} catch(std::invalid_argument& ex) {
|
||||||
|
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (invalid argument): {}", client->getLoggingPeerIp(), client->getPeerPort(), command);
|
||||||
|
error.reset(command_result{error::parameter_convert});
|
||||||
|
goto handle_error;
|
||||||
|
} catch(std::exception& ex) {
|
||||||
|
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (exception: {}): {}", client->getLoggingPeerIp(), client->getPeerPort(), ex.what(), command);
|
||||||
|
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
|
||||||
|
goto handle_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::lock_guard execute_lock{client->command_lock};
|
||||||
|
if(client->state >= ConnectionState::DISCONNECTING) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->handleCommandFull(*cmd);
|
||||||
|
} catch(std::exception& ex) {
|
||||||
|
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
|
||||||
|
goto handle_error;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
handle_error:
|
||||||
|
client->notifyError(error);
|
||||||
|
error.release_data();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr unsigned int string_hash(const char* str, unsigned int h = 0) {
|
||||||
|
return !str[h] ? 5381 : (string_hash(str, h + 1U) * 33U) ^ (unsigned int) str[h];
|
||||||
}
|
}
|
||||||
|
|
||||||
command_result QueryClient::handleCommand(Command& cmd) {
|
command_result QueryClient::handleCommand(Command& cmd) {
|
||||||
@ -108,7 +156,9 @@ command_result QueryClient::handleCommand(Command& cmd) {
|
|||||||
#else
|
#else
|
||||||
auto cmd_str = cmd.build();
|
auto cmd_str = cmd.build();
|
||||||
ts::command_parser parser{cmd_str};
|
ts::command_parser parser{cmd_str};
|
||||||
if(!parser.parse(true)) return command_result{error::vs_critical};
|
if(!parser.parse(true)) {
|
||||||
|
return command_result{error::vs_critical};
|
||||||
|
}
|
||||||
|
|
||||||
return this->handleCommandServerSnapshotDeployNew(parser);
|
return this->handleCommandServerSnapshotDeployNew(parser);
|
||||||
#endif
|
#endif
|
||||||
@ -154,7 +204,7 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
|
|||||||
auto account = _account ? serverInstance->getQueryServer()->load_password(_account) : nullptr;
|
auto account = _account ? serverInstance->getQueryServer()->load_password(_account) : nullptr;
|
||||||
|
|
||||||
{
|
{
|
||||||
threads::MutexLock lock(this->handle->loginLock);
|
std::lock_guard connect_lock{this->handle->client_connect_mutex};
|
||||||
|
|
||||||
if(!account) {
|
if(!account) {
|
||||||
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::UNKNOWN_USER);
|
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::UNKNOWN_USER);
|
||||||
@ -163,9 +213,9 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
|
|||||||
|
|
||||||
if (account->password != password) {
|
if (account->password != password) {
|
||||||
if(!this->whitelisted) {
|
if(!this->whitelisted) {
|
||||||
this->handle->loginAttempts[this->getPeerIp()]++;
|
this->handle->client_connect_count[this->getPeerIp()]++;
|
||||||
if(this->handle->loginAttempts[this->getPeerIp()] > 3) {
|
if(this->handle->client_connect_count[this->getPeerIp()] > 3) {
|
||||||
this->handle->queryBann[this->getPeerIp()] = system_clock::now() + seconds(serverInstance->properties()[property::SERVERINSTANCE_SERVERQUERY_BAN_TIME].as<uint64_t>()); //TODO configurable | Disconnect all others?
|
this->handle->client_connect_bans[this->getPeerIp()] = system_clock::now() + seconds(serverInstance->properties()[property::SERVERINSTANCE_SERVERQUERY_BAN_TIME].as<uint64_t>()); //TODO configurable | Disconnect all others?
|
||||||
this->postCommandHandler.emplace_back([&](){
|
this->postCommandHandler.emplace_back([&](){
|
||||||
this->close_connection(system_clock::now() + seconds(1));
|
this->close_connection(system_clock::now() + seconds(1));
|
||||||
});
|
});
|
||||||
@ -334,7 +384,7 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->disconnect_from_virtual_server();
|
this->disconnect_from_virtual_server("server switch");
|
||||||
this->resetEventMask();
|
this->resetEventMask();
|
||||||
|
|
||||||
//register at current server
|
//register at current server
|
||||||
|
@ -225,3 +225,11 @@ bool QueryClient::notifyMusicPlayerSongChange(const std::shared_ptr<MusicClient>
|
|||||||
CHK_EVENT(QEVENTGROUP_MUSIC, QEVENTSPECIFIER_MUSIC_PLAYER);
|
CHK_EVENT(QEVENTGROUP_MUSIC, QEVENTSPECIFIER_MUSIC_PLAYER);
|
||||||
return ConnectedClient::notifyMusicPlayerSongChange(bot, newEntry);
|
return ConnectedClient::notifyMusicPlayerSongChange(bot, newEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QueryClient::notifyChannelSubscribed(const deque<shared_ptr<BasicChannel>> &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QueryClient::notifyChannelUnsubscribed(const deque<shared_ptr<BasicChannel>> &){
|
||||||
|
return false;
|
||||||
|
}
|
19
server/src/client/shared/RawCommand.cpp
Normal file
19
server/src/client/shared/RawCommand.cpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Created by WolverinDEV on 28/01/2021.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "RawCommand.h"
|
||||||
|
|
||||||
|
using namespace ts::server::command;
|
||||||
|
|
||||||
|
ReassembledCommand *ReassembledCommand::allocate(size_t size) {
|
||||||
|
auto instance = (ReassembledCommand*) malloc(sizeof(ReassembledCommand) + size);
|
||||||
|
instance->length_ = size;
|
||||||
|
instance->capacity_ = size;
|
||||||
|
instance->next_command = nullptr;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReassembledCommand::free(ReassembledCommand *command) {
|
||||||
|
::free(command);
|
||||||
|
}
|
48
server/src/client/shared/RawCommand.h
Normal file
48
server/src/client/shared/RawCommand.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string_view>
|
||||||
|
#include <pipes/buffer.h>
|
||||||
|
|
||||||
|
namespace ts::server::command {
|
||||||
|
struct CommandFragment {
|
||||||
|
uint16_t packet_id{0};
|
||||||
|
uint16_t packet_generation{0};
|
||||||
|
|
||||||
|
uint8_t packet_flags{0};
|
||||||
|
uint32_t payload_length : 24;
|
||||||
|
pipes::buffer payload{};
|
||||||
|
|
||||||
|
CommandFragment() : payload_length{0} { }
|
||||||
|
CommandFragment(uint16_t packetId, uint16_t packetGeneration, uint8_t packetFlags, uint32_t payloadLength, pipes::buffer payload)
|
||||||
|
: packet_id{packetId}, packet_generation{packetGeneration}, packet_flags{packetFlags}, payload_length{payloadLength}, payload{std::move(payload)} {}
|
||||||
|
|
||||||
|
CommandFragment& operator=(const CommandFragment&) = default;
|
||||||
|
CommandFragment(const CommandFragment& other) = default;
|
||||||
|
CommandFragment(CommandFragment&&) = default;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(CommandFragment) == 8 + sizeof(pipes::buffer));
|
||||||
|
|
||||||
|
struct ReassembledCommand {
|
||||||
|
public:
|
||||||
|
static ReassembledCommand* allocate(size_t /* command length */);
|
||||||
|
static void free(ReassembledCommand* /* command */);
|
||||||
|
|
||||||
|
[[nodiscard]] inline size_t length() const { return this->length_; }
|
||||||
|
inline void set_length(size_t length) { assert(this->capacity_ >= length); this->length_ = length; }
|
||||||
|
|
||||||
|
[[nodiscard]] inline size_t capacity() const { return this->capacity_; }
|
||||||
|
|
||||||
|
[[nodiscard]] inline const char* command() const { return (const char*) this + sizeof(ReassembledCommand); }
|
||||||
|
[[nodiscard]] inline char* command() { return (char*) this + sizeof(ReassembledCommand); }
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string_view command_view() const { return std::string_view{this->command(), this->length()}; }
|
||||||
|
|
||||||
|
mutable ReassembledCommand* next_command; /* nullptr by default */
|
||||||
|
private:
|
||||||
|
explicit ReassembledCommand() = default;
|
||||||
|
|
||||||
|
size_t capacity_;
|
||||||
|
size_t length_;
|
||||||
|
};
|
||||||
|
}
|
272
server/src/client/shared/ServerCommandExecutor.cpp
Normal file
272
server/src/client/shared/ServerCommandExecutor.cpp
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
//
|
||||||
|
// Created by WolverinDEV on 29/07/2020.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <ThreadPool/ThreadHelper.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include "./ServerCommandExecutor.h"
|
||||||
|
#include "src/client/voice/PacketDecoder.h"
|
||||||
|
#include "src/client/voice/VoiceClientConnection.h"
|
||||||
|
|
||||||
|
using namespace ts;
|
||||||
|
using namespace ts::server;
|
||||||
|
using namespace ts::server::command;
|
||||||
|
|
||||||
|
namespace ts::server {
|
||||||
|
struct ServerCommandQueueInner {
|
||||||
|
spin_mutex pending_commands_lock{};
|
||||||
|
command::ReassembledCommand* pending_commands_head{nullptr};
|
||||||
|
command::ReassembledCommand** pending_commands_tail{&pending_commands_head};
|
||||||
|
bool has_command_handling_scheduled{false};
|
||||||
|
|
||||||
|
~ServerCommandQueueInner() {
|
||||||
|
auto head = this->pending_commands_head;
|
||||||
|
while(head) {
|
||||||
|
auto cmd = head->next_command;
|
||||||
|
ReassembledCommand::free(head);
|
||||||
|
head = cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
std::unique_lock pc_lock{this->pending_commands_lock};
|
||||||
|
auto head = std::exchange(this->pending_commands_head, nullptr);
|
||||||
|
this->pending_commands_tail = &this->pending_commands_head;
|
||||||
|
this->has_command_handling_scheduled = false;
|
||||||
|
pc_lock.unlock();
|
||||||
|
|
||||||
|
while(head) {
|
||||||
|
auto cmd = head->next_command;
|
||||||
|
ReassembledCommand::free(head);
|
||||||
|
head = cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param command The target command to enqueue.
|
||||||
|
* Ownership will be taken.
|
||||||
|
* @returns `true` if command handling has already been schedules and `false` if not
|
||||||
|
*/
|
||||||
|
bool enqueue(ReassembledCommand *command){
|
||||||
|
std::lock_guard pc_lock{this->pending_commands_lock};
|
||||||
|
*this->pending_commands_tail = command;
|
||||||
|
this->pending_commands_tail = &command->next_command;
|
||||||
|
|
||||||
|
return std::exchange(this->has_command_handling_scheduled, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReassembledCommand* pop_command(bool& more_pending) {
|
||||||
|
std::lock_guard pc_lock{this->pending_commands_lock};
|
||||||
|
auto result = this->pending_commands_head;
|
||||||
|
|
||||||
|
if(!result) {
|
||||||
|
more_pending = false;
|
||||||
|
this->has_command_handling_scheduled = false;
|
||||||
|
} else if(result->next_command) {
|
||||||
|
more_pending = true;
|
||||||
|
this->pending_commands_head = result->next_command;
|
||||||
|
} else {
|
||||||
|
/* We assume true here since we might get new commands while the handler itself is still handling our current result. */
|
||||||
|
more_pending = true;
|
||||||
|
this->pending_commands_head = nullptr;
|
||||||
|
this->pending_commands_tail = &this->pending_commands_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerCommandHandler::execute_handling() {
|
||||||
|
bool more_pending;
|
||||||
|
std::unique_ptr<ReassembledCommand, void(*)(ReassembledCommand*)> pending_command{nullptr, ReassembledCommand::free};
|
||||||
|
while(true) {
|
||||||
|
pending_command.reset(this->inner->pop_command(more_pending));
|
||||||
|
if(!pending_command) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto result = this->handle_command(std::string_view{pending_command->command(), pending_command->length()});
|
||||||
|
if(!result) {
|
||||||
|
/* flush all commands */
|
||||||
|
this->inner->reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (std::exception& ex) {
|
||||||
|
logCritical(LOG_GENERAL, "Exception reached command execution root! {}",ex.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
break; /* Maybe handle more than one command? Maybe some kind of time limit? */
|
||||||
|
}
|
||||||
|
|
||||||
|
return more_pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerCommandQueue::ServerCommandQueue(std::shared_ptr<ServerCommandExecutor> executor, std::unique_ptr<ServerCommandHandler> command_handler) :
|
||||||
|
executor{std::move(executor)},
|
||||||
|
command_handler{command_handler.release()} {
|
||||||
|
assert(this->command_handler);
|
||||||
|
this->inner = std::make_shared<ServerCommandQueueInner>();
|
||||||
|
this->command_handler->inner = this->inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerCommandQueue::~ServerCommandQueue() = default;
|
||||||
|
|
||||||
|
void ServerCommandQueue::reset() {
|
||||||
|
this->inner->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommandQueue::enqueue_command_string(const std::string_view &buffer) {
|
||||||
|
auto command = ReassembledCommand::allocate(buffer.length());
|
||||||
|
memcpy(command->command(), buffer.data(), command->length());
|
||||||
|
this->enqueue_command_execution(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommandQueue::enqueue_command_execution(ReassembledCommand *command) {
|
||||||
|
assert(!command->next_command);
|
||||||
|
|
||||||
|
bool command_handling_scheduled = this->inner->enqueue(command);
|
||||||
|
if(!command_handling_scheduled) {
|
||||||
|
this->executor->enqueue_handler(this->command_handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
void ServerCommandQueue::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) {
|
||||||
|
if(!this->client->getServer() || this->client->connectionState() >= ConnectionState::DISCONNECTING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ReassembledCommand, void(*)(ReassembledCommand*)> pending_command{nullptr, ReassembledCommand::free};
|
||||||
|
while(true) {
|
||||||
|
{
|
||||||
|
std::lock_guard pc_lock{this->pending_commands_lock};
|
||||||
|
pending_command.reset(this->pending_commands_head);
|
||||||
|
if(!pending_command) {
|
||||||
|
this->has_command_handling_scheduled = false;
|
||||||
|
return;
|
||||||
|
} else if(pending_command->next_command) {
|
||||||
|
this->pending_commands_head = pending_command->next_command;
|
||||||
|
} else {
|
||||||
|
this->pending_commands_head = nullptr;
|
||||||
|
this->pending_commands_tail = &this->pending_commands_head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto startTime = std::chrono::system_clock::now();
|
||||||
|
try {
|
||||||
|
this->client->handlePacketCommand(pipes::buffer_view{pending_command->command(), pending_command->length()});
|
||||||
|
} catch (std::exception& ex) {
|
||||||
|
logCritical(this->client->getServerId(), "{} Exception reached command execution root! {}", CLIENT_STR_LOG_PREFIX_(this->client), ex.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::system_clock::now();
|
||||||
|
if(end - startTime > std::chrono::milliseconds(10)) {
|
||||||
|
logError(this->client->getServerId(),
|
||||||
|
"{} Handling of command packet needs more than 10ms ({}ms)",
|
||||||
|
CLIENT_STR_LOG_PREFIX_(this->client),
|
||||||
|
duration_cast<std::chrono::milliseconds>(end - startTime).count()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break; /* Maybe handle more than one command? Maybe some kind of time limit? */
|
||||||
|
}
|
||||||
|
|
||||||
|
auto voice_server = this->client->getVoiceServer();
|
||||||
|
if(voice_server) {
|
||||||
|
voice_server->schedule_command_handling(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ServerCommandExecutor::ServerCommandExecutor(size_t threads) : thread_count_{threads} {
|
||||||
|
this->threads_.reserve(threads);
|
||||||
|
|
||||||
|
for(size_t index{0}; index < threads; index++) {
|
||||||
|
auto& thread = this->threads_.emplace_back(&ServerCommandExecutor::thread_entry_point, this);
|
||||||
|
threads::name(thread, "cmd executor " + std::to_string(index + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServerCommandExecutor::~ServerCommandExecutor() {
|
||||||
|
this->shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommandExecutor::shutdown() {
|
||||||
|
{
|
||||||
|
std::lock_guard handler_lock{this->handler_mutex};
|
||||||
|
this->handler_shutdown = true;
|
||||||
|
this->handler_notify.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& thread : this->threads_) {
|
||||||
|
threads::save_join(thread, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommandExecutor::enqueue_handler(const std::shared_ptr<ServerCommandHandler> &handler) {
|
||||||
|
std::lock_guard handler_lock{this->handler_mutex};
|
||||||
|
if(handler->next_handler) {
|
||||||
|
/* handler already scheduled */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(handler->executing) {
|
||||||
|
/* handler currently gets executed */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this->handler_tail == &handler->next_handler) {
|
||||||
|
/* handler already schedules (current head) */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*this->handler_tail = handler;
|
||||||
|
this->handler_tail = &handler->next_handler;
|
||||||
|
|
||||||
|
this->handler_notify.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommandExecutor::thread_entry_point(void *ptr_this) {
|
||||||
|
reinterpret_cast<ServerCommandExecutor*>(ptr_this)->executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCommandExecutor::executor() {
|
||||||
|
std::unique_lock handler_lock{this->handler_mutex};
|
||||||
|
while(!this->handler_shutdown) {
|
||||||
|
this->handler_notify.wait(handler_lock, [&]{
|
||||||
|
return this->handler_shutdown || this->handler_head != nullptr;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this->handler_shutdown) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this->handler_head) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto executor = this->handler_head;
|
||||||
|
if(executor->next_handler) {
|
||||||
|
this->handler_head = executor->next_handler;
|
||||||
|
} else {
|
||||||
|
this->handler_head = nullptr;
|
||||||
|
this->handler_tail = &this->handler_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
executor->executing = true;
|
||||||
|
handler_lock.unlock();
|
||||||
|
auto reschedule = executor->execute_handling();
|
||||||
|
handler_lock.lock();
|
||||||
|
executor->executing = false;
|
||||||
|
|
||||||
|
if(reschedule && !executor->next_handler && this->handler_tail != &executor->next_handler) {
|
||||||
|
*this->handler_tail = executor;
|
||||||
|
this->handler_tail = &executor->next_handler;
|
||||||
|
|
||||||
|
/* No need to notify anybody since we'll be executing him again */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
server/src/client/shared/ServerCommandExecutor.h
Normal file
89
server/src/client/shared/ServerCommandExecutor.h
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <misc/spin_mutex.h>
|
||||||
|
#include <pipes/buffer.h>
|
||||||
|
#include <EventLoop.h>
|
||||||
|
|
||||||
|
namespace ts::server {
|
||||||
|
class VoiceClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ts::server {
|
||||||
|
namespace command {
|
||||||
|
struct ReassembledCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerCommandExecutor;
|
||||||
|
class ServerCommandQueue;
|
||||||
|
struct ServerCommandQueueInner;
|
||||||
|
|
||||||
|
class ServerCommandHandler {
|
||||||
|
friend class ServerCommandQueue;
|
||||||
|
friend class ServerCommandExecutor;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ServerCommandHandler() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Handle a command.
|
||||||
|
* @returns `false` if all other commands should be dropped and no further command handling should be done.
|
||||||
|
* `true` on success.
|
||||||
|
*/
|
||||||
|
virtual bool handle_command(const std::string_view& /* raw command */) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<ServerCommandQueueInner> inner{nullptr};
|
||||||
|
|
||||||
|
/* locked by ServerCommandExecutor::handler_mutex */
|
||||||
|
std::shared_ptr<ServerCommandHandler> next_handler{nullptr};
|
||||||
|
bool executing{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns `true` if more commands need to be handled and `false` if all commands have been handled.
|
||||||
|
*/
|
||||||
|
bool execute_handling();
|
||||||
|
};
|
||||||
|
|
||||||
|
class ServerCommandQueue {
|
||||||
|
public:
|
||||||
|
explicit ServerCommandQueue(std::shared_ptr<ServerCommandExecutor> /* executor */, std::unique_ptr<ServerCommandHandler> /* command handler */);
|
||||||
|
~ServerCommandQueue();
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void enqueue_command_string(const std::string_view& /* payload */);
|
||||||
|
/* Attention: The method will take ownership of the command */
|
||||||
|
void enqueue_command_execution(command::ReassembledCommand*);
|
||||||
|
private:
|
||||||
|
std::shared_ptr<ServerCommandExecutor> executor{};
|
||||||
|
std::shared_ptr<ServerCommandHandler> command_handler{};
|
||||||
|
std::shared_ptr<ServerCommandQueueInner> inner;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ServerCommandExecutor {
|
||||||
|
friend class ServerCommandQueue;
|
||||||
|
public:
|
||||||
|
explicit ServerCommandExecutor(size_t /* threads */);
|
||||||
|
~ServerCommandExecutor();
|
||||||
|
|
||||||
|
[[nodiscard]] inline auto thread_count() const { return this->thread_count_; }
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void enqueue_handler(const std::shared_ptr<ServerCommandHandler>& /* handler */);
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t thread_count_;
|
||||||
|
std::vector<std::thread> threads_{};
|
||||||
|
|
||||||
|
std::mutex handler_mutex{};
|
||||||
|
std::condition_variable handler_notify{};
|
||||||
|
std::shared_ptr<ServerCommandHandler> handler_head{nullptr};
|
||||||
|
std::shared_ptr<ServerCommandHandler>* handler_tail{&this->handler_head};
|
||||||
|
bool handler_shutdown{false};
|
||||||
|
|
||||||
|
static void thread_entry_point(void* /* this ptr */);
|
||||||
|
void executor();
|
||||||
|
};
|
||||||
|
}
|
@ -16,18 +16,6 @@ using namespace ts::protocol;
|
|||||||
using namespace ts::connection;
|
using namespace ts::connection;
|
||||||
using namespace ts::server::server::udp;
|
using namespace ts::server::server::udp;
|
||||||
|
|
||||||
ReassembledCommand *ReassembledCommand::allocate(size_t size) {
|
|
||||||
auto instance = (ReassembledCommand*) malloc(sizeof(ReassembledCommand) + size);
|
|
||||||
instance->length_ = size;
|
|
||||||
instance->capacity_ = size;
|
|
||||||
instance->next_command = nullptr;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReassembledCommand::free(ReassembledCommand *command) {
|
|
||||||
::free(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
PacketDecoder::PacketDecoder(ts::connection::CryptHandler *crypt_handler)
|
PacketDecoder::PacketDecoder(ts::connection::CryptHandler *crypt_handler)
|
||||||
: crypt_handler_{crypt_handler} {
|
: crypt_handler_{crypt_handler} {
|
||||||
memtrack::allocated<PacketDecoder>(this);
|
memtrack::allocated<PacketDecoder>(this);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <protocol/Packet.h>
|
#include <protocol/Packet.h>
|
||||||
#include <protocol/generation.h>
|
#include <protocol/generation.h>
|
||||||
#include <protocol/ringbuffer.h>
|
#include <protocol/ringbuffer.h>
|
||||||
|
#include "../shared/RawCommand.h"
|
||||||
|
|
||||||
namespace ts::connection {
|
namespace ts::connection {
|
||||||
class CryptHandler;
|
class CryptHandler;
|
||||||
@ -17,48 +18,7 @@ namespace ts::stats {
|
|||||||
class ConnectionStatistics;
|
class ConnectionStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ts::server::server::udp {
|
namespace ts::server::server::udp {
|
||||||
struct CommandFragment {
|
|
||||||
uint16_t packet_id{0};
|
|
||||||
uint16_t packet_generation{0};
|
|
||||||
|
|
||||||
uint8_t packet_flags{0};
|
|
||||||
uint32_t payload_length : 24;
|
|
||||||
pipes::buffer payload{};
|
|
||||||
|
|
||||||
CommandFragment() { this->payload_length = 0; }
|
|
||||||
CommandFragment(uint16_t packetId, uint16_t packetGeneration, uint8_t packetFlags, uint32_t payloadLength, pipes::buffer payload)
|
|
||||||
: packet_id{packetId}, packet_generation{packetGeneration}, packet_flags{packetFlags}, payload_length{payloadLength}, payload{std::move(payload)} {}
|
|
||||||
|
|
||||||
CommandFragment& operator=(const CommandFragment&) = default;
|
|
||||||
CommandFragment(const CommandFragment& other) = default;
|
|
||||||
CommandFragment(CommandFragment&&) = default;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(CommandFragment) == 8 + sizeof(pipes::buffer));
|
|
||||||
|
|
||||||
struct ReassembledCommand {
|
|
||||||
public:
|
|
||||||
static ReassembledCommand* allocate(size_t /* command length */);
|
|
||||||
static void free(ReassembledCommand* /* command */);
|
|
||||||
|
|
||||||
[[nodiscard]] inline size_t length() const { return this->length_; }
|
|
||||||
inline void set_length(size_t length) { assert(this->capacity_ >= length); this->length_ = length; }
|
|
||||||
|
|
||||||
[[nodiscard]] inline size_t capacity() const { return this->capacity_; }
|
|
||||||
|
|
||||||
[[nodiscard]] inline const char* command() const { return (const char*) this + sizeof(ReassembledCommand); }
|
|
||||||
[[nodiscard]] inline char* command() { return (char*) this + sizeof(ReassembledCommand); }
|
|
||||||
|
|
||||||
[[nodiscard]] inline std::string_view command_view() const { return std::string_view{this->command(), this->length()}; }
|
|
||||||
|
|
||||||
mutable ReassembledCommand* next_command; /* nullptr by default */
|
|
||||||
private:
|
|
||||||
explicit ReassembledCommand() = default;
|
|
||||||
|
|
||||||
size_t capacity_;
|
|
||||||
size_t length_;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum struct PacketProcessResult {
|
enum struct PacketProcessResult {
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
UNKNOWN_ERROR,
|
UNKNOWN_ERROR,
|
||||||
@ -91,6 +51,9 @@ namespace ts::stats {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class PacketDecoder {
|
class PacketDecoder {
|
||||||
|
using CommandFragment = command::CommandFragment;
|
||||||
|
using ReassembledCommand = command::ReassembledCommand;
|
||||||
|
|
||||||
typedef protocol::FullPacketRingBuffer<CommandFragment, 32, CommandFragment> command_fragment_buffer_t;
|
typedef protocol::FullPacketRingBuffer<CommandFragment, 32, CommandFragment> command_fragment_buffer_t;
|
||||||
typedef std::array<command_fragment_buffer_t, 2> command_packet_reassembler;
|
typedef std::array<command_fragment_buffer_t, 2> command_packet_reassembler;
|
||||||
public:
|
public:
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by WolverinDEV on 29/07/2020.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "./ServerCommandExecutor.h"
|
|
||||||
#include "./PacketDecoder.h"
|
|
||||||
#include "./VoiceClientConnection.h"
|
|
||||||
|
|
||||||
using namespace ts;
|
|
||||||
using namespace ts::server::server::udp;
|
|
||||||
|
|
||||||
ServerCommandExecutor::ServerCommandExecutor(VoiceClient *client) : client{client} {}
|
|
||||||
ServerCommandExecutor::~ServerCommandExecutor() {
|
|
||||||
this->reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCommandExecutor::reset() {
|
|
||||||
std::unique_lock pc_lock{this->pending_commands_lock};
|
|
||||||
auto head = std::exchange(this->pending_commands_head, nullptr);
|
|
||||||
this->pending_commands_tail = &this->pending_commands_head;
|
|
||||||
pc_lock.unlock();
|
|
||||||
|
|
||||||
while(head) {
|
|
||||||
auto cmd = head->next_command;
|
|
||||||
ReassembledCommand::free(head);
|
|
||||||
head = cmd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCommandExecutor::force_insert_command(const pipes::buffer_view &buffer) {
|
|
||||||
auto command = ReassembledCommand::allocate(buffer.length());
|
|
||||||
memcpy(command->command(), buffer.data_ptr(), command->length());
|
|
||||||
this->enqueue_command_execution(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCommandExecutor::enqueue_command_execution(ReassembledCommand *command) {
|
|
||||||
assert(!command->next_command);
|
|
||||||
|
|
||||||
bool command_handling_scheduled;
|
|
||||||
{
|
|
||||||
std::lock_guard pc_lock{this->pending_commands_lock};
|
|
||||||
*this->pending_commands_tail = command;
|
|
||||||
this->pending_commands_tail = &command->next_command;
|
|
||||||
|
|
||||||
command_handling_scheduled = std::exchange(this->has_command_handling_scheduled, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!command_handling_scheduled) {
|
|
||||||
auto voice_server = this->client->getVoiceServer();
|
|
||||||
if(voice_server) {
|
|
||||||
voice_server->schedule_command_handling(&*client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCommandExecutor::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) {
|
|
||||||
if(!this->client->getServer() || this->client->connectionState() >= ConnectionState::DISCONNECTING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<ReassembledCommand, void(*)(ReassembledCommand*)> pending_command{nullptr, ReassembledCommand::free};
|
|
||||||
while(true) {
|
|
||||||
{
|
|
||||||
std::lock_guard pc_lock{this->pending_commands_lock};
|
|
||||||
pending_command.reset(this->pending_commands_head);
|
|
||||||
if(!pending_command) {
|
|
||||||
this->has_command_handling_scheduled = false;
|
|
||||||
return;
|
|
||||||
} else if(pending_command->next_command) {
|
|
||||||
this->pending_commands_head = pending_command->next_command;
|
|
||||||
} else {
|
|
||||||
this->pending_commands_head = nullptr;
|
|
||||||
this->pending_commands_tail = &this->pending_commands_head;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto startTime = std::chrono::system_clock::now();
|
|
||||||
try {
|
|
||||||
this->client->handlePacketCommand(pipes::buffer_view{pending_command->command(), pending_command->length()});
|
|
||||||
} catch (std::exception& ex) {
|
|
||||||
logCritical(this->client->getServerId(), "{} Exception reached root tree! {}", CLIENT_STR_LOG_PREFIX_(this->client), ex.what());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end = std::chrono::system_clock::now();
|
|
||||||
if(end - startTime > std::chrono::milliseconds(10)) {
|
|
||||||
logError(this->client->getServerId(),
|
|
||||||
"{} Handling of command packet needs more than 10ms ({}ms)",
|
|
||||||
CLIENT_STR_LOG_PREFIX_(this->client),
|
|
||||||
duration_cast<std::chrono::milliseconds>(end - startTime).count()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
break; /* Maybe handle more than one command? Maybe some kind of time limit? */
|
|
||||||
}
|
|
||||||
|
|
||||||
auto voice_server = this->client->getVoiceServer();
|
|
||||||
if(voice_server) {
|
|
||||||
voice_server->schedule_command_handling(client);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <misc/spin_mutex.h>
|
|
||||||
#include <pipes/buffer.h>
|
|
||||||
|
|
||||||
namespace ts::server {
|
|
||||||
class VoiceClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ts::server::server::udp {
|
|
||||||
struct ReassembledCommand;
|
|
||||||
|
|
||||||
class ServerCommandExecutor {
|
|
||||||
public:
|
|
||||||
explicit ServerCommandExecutor(VoiceClient*);
|
|
||||||
~ServerCommandExecutor();
|
|
||||||
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
void force_insert_command(const pipes::buffer_view& /* payload */);
|
|
||||||
void enqueue_command_execution(ReassembledCommand*); /* Attention: The method will take ownership of the command */
|
|
||||||
void execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */);
|
|
||||||
private:
|
|
||||||
VoiceClient* client;
|
|
||||||
|
|
||||||
spin_mutex pending_commands_lock{};
|
|
||||||
ReassembledCommand* pending_commands_head{nullptr};
|
|
||||||
ReassembledCommand** pending_commands_tail{&pending_commands_head};
|
|
||||||
bool has_command_handling_scheduled{false}; /* locked by pending_commands_lock */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ReassembledCommand;
|
|
||||||
}
|
|
@ -19,9 +19,10 @@ using namespace ts::protocol;
|
|||||||
|
|
||||||
constexpr static auto kMaxWhisperClientNameLength{30};
|
constexpr static auto kMaxWhisperClientNameLength{30};
|
||||||
constexpr static auto kWhisperClientUniqueIdLength{28}; /* base64 encoded SHA1 hash */
|
constexpr static auto kWhisperClientUniqueIdLength{28}; /* base64 encoded SHA1 hash */
|
||||||
constexpr static auto kWhisperMaxHeaderLength{2 + 2 + 1 + 2 + kWhisperClientUniqueIdLength + 1 + kMaxWhisperClientNameLength};
|
|
||||||
|
|
||||||
VoiceClient::VoiceClient(const std::shared_ptr<VoiceServer>& server, const sockaddr_storage* address) : SpeakingClient(server->server->sql, server->server), voice_server(server) {
|
VoiceClient::VoiceClient(const std::shared_ptr<VoiceServer>& server, const sockaddr_storage* address) :
|
||||||
|
SpeakingClient(server->server->sql, server->server),
|
||||||
|
voice_server(server) {
|
||||||
assert(address);
|
assert(address);
|
||||||
memtrack::allocated<VoiceClient>(this);
|
memtrack::allocated<VoiceClient>(this);
|
||||||
memcpy(&this->remote_address, address, sizeof(sockaddr_storage));
|
memcpy(&this->remote_address, address, sizeof(sockaddr_storage));
|
||||||
@ -30,7 +31,11 @@ VoiceClient::VoiceClient(const std::shared_ptr<VoiceServer>& server, const socka
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VoiceClient::initialize() {
|
void VoiceClient::initialize() {
|
||||||
this->event_handle_packet = make_shared<event::ProxiedEventEntry<VoiceClient>>(dynamic_pointer_cast<VoiceClient>(this->ref()), &VoiceClient::execute_handle_packet);
|
auto ref_self = dynamic_pointer_cast<VoiceClient>(this->ref());
|
||||||
|
this->server_command_queue_ = std::make_unique<ServerCommandQueue>(
|
||||||
|
serverInstance->server_command_executor(),
|
||||||
|
std::make_unique<VoiceClientCommandHandler>(ref_self)
|
||||||
|
);
|
||||||
|
|
||||||
this->properties()[property::CLIENT_TYPE] = ClientType::CLIENT_TEAMSPEAK;
|
this->properties()[property::CLIENT_TYPE] = ClientType::CLIENT_TEAMSPEAK;
|
||||||
this->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEAMSPEAK;
|
this->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEAMSPEAK;
|
||||||
@ -62,8 +67,8 @@ void VoiceClient::sendCommand0(const std::string_view& cmd, bool low, std::uniqu
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceClient::tick(const std::chrono::system_clock::time_point &time) {
|
void VoiceClient::tick_server(const std::chrono::system_clock::time_point &time) {
|
||||||
SpeakingClient::tick(time);
|
SpeakingClient::tick_server(time);
|
||||||
{
|
{
|
||||||
ALARM_TIMER(A1, "VoiceClient::tick", milliseconds(3));
|
ALARM_TIMER(A1, "VoiceClient::tick", milliseconds(3));
|
||||||
if(this->state == ConnectionState::CONNECTED) {
|
if(this->state == ConnectionState::CONNECTED) {
|
||||||
@ -259,10 +264,6 @@ void VoiceClient::finalDisconnect() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceClient::execute_handle_packet(const std::chrono::system_clock::time_point &time) {
|
|
||||||
this->server_command_executor_.execute_handle_command_packets(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoiceClient::send_voice_packet(const pipes::buffer_view &voice_buffer, const SpeakingClient::VoicePacketFlags &flags) {
|
void VoiceClient::send_voice_packet(const pipes::buffer_view &voice_buffer, const SpeakingClient::VoicePacketFlags &flags) {
|
||||||
PacketFlag::PacketFlags packet_flags{PacketFlag::None};
|
PacketFlag::PacketFlags packet_flags{PacketFlag::None};
|
||||||
packet_flags |= flags.encrypted ? 0U : PacketFlag::Unencrypted;
|
packet_flags |= flags.encrypted ? 0U : PacketFlag::Unencrypted;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
#include "VoiceClientConnection.h"
|
#include "VoiceClientConnection.h"
|
||||||
#include "src/server/PrecomputedPuzzles.h"
|
#include "src/server/PrecomputedPuzzles.h"
|
||||||
#include "../../lincense/TeamSpeakLicense.h"
|
#include "../../lincense/TeamSpeakLicense.h"
|
||||||
#include "./ServerCommandExecutor.h"
|
#include "src/client/shared/ServerCommandExecutor.h"
|
||||||
|
|
||||||
//#define LOG_INCOMPING_PACKET_FRAGMENTS
|
//#define LOG_INCOMPING_PACKET_FRAGMENTS
|
||||||
//#define LOG_AUTO_ACK_AUTORESPONSE
|
//#define LOG_AUTO_ACK_AUTORESPONSE
|
||||||
@ -43,6 +43,7 @@ namespace ts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class VirtualServer;
|
class VirtualServer;
|
||||||
|
class VoiceClientCommandHandler;
|
||||||
|
|
||||||
class VoiceClient : public SpeakingClient {
|
class VoiceClient : public SpeakingClient {
|
||||||
friend class VirtualServer;
|
friend class VirtualServer;
|
||||||
@ -53,7 +54,9 @@ namespace ts {
|
|||||||
friend class io::IOServerHandler;
|
friend class io::IOServerHandler;
|
||||||
friend class server::udp::ServerCommandExecutor;
|
friend class server::udp::ServerCommandExecutor;
|
||||||
friend class server::udp::CryptSetupHandler;
|
friend class server::udp::CryptSetupHandler;
|
||||||
using ServerCommandExecutor = ts::server::server::udp::ServerCommandExecutor;
|
friend class VoiceClientCommandHandler;
|
||||||
|
|
||||||
|
using ServerCommandExecutor = ts::server::ServerCommandQueue;
|
||||||
public:
|
public:
|
||||||
VoiceClient(const std::shared_ptr<VoiceServer>& server, const sockaddr_storage*);
|
VoiceClient(const std::shared_ptr<VoiceServer>& server, const sockaddr_storage*);
|
||||||
~VoiceClient() override;
|
~VoiceClient() override;
|
||||||
@ -76,7 +79,7 @@ namespace ts {
|
|||||||
|
|
||||||
[[nodiscard]] float current_packet_loss() const;
|
[[nodiscard]] float current_packet_loss() const;
|
||||||
|
|
||||||
[[nodiscard]] inline auto& server_command_executor() { return this->server_command_executor_; }
|
[[nodiscard]] const auto& server_command_queue() { return this->server_command_queue_; }
|
||||||
private:
|
private:
|
||||||
connection::VoiceClientConnection* connection;
|
connection::VoiceClientConnection* connection;
|
||||||
|
|
||||||
@ -84,10 +87,10 @@ namespace ts {
|
|||||||
std::shared_ptr<VoiceServer> voice_server;
|
std::shared_ptr<VoiceServer> voice_server;
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
virtual void tick(const std::chrono::system_clock::time_point &time) override;
|
virtual void tick_server(const std::chrono::system_clock::time_point &time) override;
|
||||||
|
|
||||||
/* Attention these handle callbacks are not thread save! */
|
/* Attention these handle callbacks are not thread save! */
|
||||||
void handlePacketCommand(const pipes::buffer_view&);
|
void handlePacketCommand(const std::string_view&);
|
||||||
public:
|
public:
|
||||||
void send_voice_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override;
|
void send_voice_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override;
|
||||||
void send_voice(const std::shared_ptr<SpeakingClient>& /* source client */, uint16_t /* seq no */, uint8_t /* codec */, const void* /* payload */, size_t /* payload length */);
|
void send_voice(const std::shared_ptr<SpeakingClient>& /* source client */, uint16_t /* seq no */, uint8_t /* codec */, const void* /* payload */, size_t /* payload length */);
|
||||||
@ -114,10 +117,18 @@ namespace ts {
|
|||||||
//Locked by finalDisconnect, disconnect and close connection
|
//Locked by finalDisconnect, disconnect and close connection
|
||||||
std::shared_ptr<std::thread> flushing_thread;
|
std::shared_ptr<std::thread> flushing_thread;
|
||||||
|
|
||||||
ServerCommandExecutor server_command_executor_{this};
|
std::unique_ptr<ServerCommandQueue> server_command_queue_{};
|
||||||
|
};
|
||||||
|
|
||||||
std::shared_ptr<event::ProxiedEventEntry<VoiceClient>> event_handle_packet;
|
class VoiceClientCommandHandler : public ts::server::ServerCommandHandler {
|
||||||
void execute_handle_packet(const std::chrono::system_clock::time_point& /* scheduled */);
|
public:
|
||||||
|
explicit VoiceClientCommandHandler(const std::shared_ptr<VoiceClient>& /* client */);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool handle_command(const std::string_view &) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::weak_ptr<VoiceClient> client_ref;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,7 +14,18 @@ using namespace ts::server;
|
|||||||
using namespace ts::protocol;
|
using namespace ts::protocol;
|
||||||
using namespace ts;
|
using namespace ts;
|
||||||
|
|
||||||
void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string) {
|
VoiceClientCommandHandler::VoiceClientCommandHandler(const std::shared_ptr<VoiceClient> &client) : client_ref{client} {}
|
||||||
|
bool VoiceClientCommandHandler::handle_command(const std::string_view &command_string) {
|
||||||
|
auto client = this->client_ref.lock();
|
||||||
|
if(!client) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->handlePacketCommand(command_string);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceClient::handlePacketCommand(const std::string_view& command_string) {
|
||||||
std::unique_ptr<Command> command;
|
std::unique_ptr<Command> command;
|
||||||
command_result result{};
|
command_result result{};
|
||||||
try {
|
try {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
#include "./PacketDecoder.h"
|
#include "./PacketDecoder.h"
|
||||||
#include "./PacketEncoder.h"
|
#include "./PacketEncoder.h"
|
||||||
#include "./PacketStatistics.h"
|
#include "./PacketStatistics.h"
|
||||||
#include "./ServerCommandExecutor.h"
|
#include "src/client/shared/ServerCommandExecutor.h"
|
||||||
#include "CryptSetupHandler.h"
|
#include "CryptSetupHandler.h"
|
||||||
#include "PingHandler.h"
|
#include "PingHandler.h"
|
||||||
#include "VoiceClient.h"
|
#include "VoiceClient.h"
|
||||||
@ -49,7 +49,7 @@ namespace ts {
|
|||||||
using PacketEncoder = server::server::udp::PacketEncoder;
|
using PacketEncoder = server::server::udp::PacketEncoder;
|
||||||
using PingHandler = server::server::udp::PingHandler;
|
using PingHandler = server::server::udp::PingHandler;
|
||||||
using CryptSetupHandler = server::server::udp::CryptSetupHandler;
|
using CryptSetupHandler = server::server::udp::CryptSetupHandler;
|
||||||
using ReassembledCommand = server::server::udp::ReassembledCommand;
|
using ReassembledCommand = server::command::ReassembledCommand;
|
||||||
|
|
||||||
using StatisticsCategory = stats::ConnectionStatistics::category;
|
using StatisticsCategory = stats::ConnectionStatistics::category;
|
||||||
public:
|
public:
|
||||||
|
@ -97,9 +97,11 @@ void VoiceClientConnection::handlePacketCommand(ReassembledCommand* command) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CommandHandleResult::CONSUME_COMMAND:
|
case CommandHandleResult::CONSUME_COMMAND:
|
||||||
|
ReassembledCommand::free(command);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case CommandHandleResult::CLOSE_CONNECTION:
|
case CommandHandleResult::CLOSE_CONNECTION:
|
||||||
|
ReassembledCommand::free(command);
|
||||||
auto client = this->getCurrentClient();
|
auto client = this->getCurrentClient();
|
||||||
assert(client); /* FIXME! */
|
assert(client); /* FIXME! */
|
||||||
client->close_connection(std::chrono::system_clock::time_point{});
|
client->close_connection(std::chrono::system_clock::time_point{});
|
||||||
@ -109,9 +111,10 @@ void VoiceClientConnection::handlePacketCommand(ReassembledCommand* command) {
|
|||||||
|
|
||||||
auto client = this->getCurrentClient();
|
auto client = this->getCurrentClient();
|
||||||
if(!client) {
|
if(!client) {
|
||||||
|
ReassembledCommand::free(command);
|
||||||
/* TODO! */
|
/* TODO! */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client->server_command_executor().enqueue_command_execution(command);
|
client->server_command_queue()->enqueue_command_execution(command);
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
#include "WebClient.h"
|
#include "WebClient.h"
|
||||||
|
#include "../shared/RawCommand.h"
|
||||||
#include <log/LogUtils.h>
|
#include <log/LogUtils.h>
|
||||||
#include <src/server/VoiceServer.h>
|
#include <src/server/VoiceServer.h>
|
||||||
#include <src/InstanceHandler.h>
|
#include <src/InstanceHandler.h>
|
||||||
@ -83,38 +84,29 @@ void WebClient::handleMessageRead(int fd, short, void *) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pbuffer = buffer::allocate_buffer((size_t) length);
|
auto command = command::ReassembledCommand::allocate((size_t) length);
|
||||||
pbuffer.write(buffer, length);
|
memcpy(command->command(), buffer, (size_t) length);
|
||||||
|
|
||||||
{
|
this->command_queue->enqueue_command_execution(command);
|
||||||
lock_guard lock(this->queue_mutex);
|
|
||||||
this->queue_read.push_back(std::move(pbuffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
this->registerMessageProcess();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebClient::enqueue_raw_packet(const pipes::buffer_view &msg) {
|
void WebClient::enqueue_raw_packet(const pipes::buffer_view &msg) {
|
||||||
auto buffer = msg.owns_buffer() ? msg.own_buffer() : msg.own_buffer(); /* TODO: Use buffer::allocate_buffer(...) */
|
auto buffer = msg.own_buffer(); /* TODO: Use buffer::allocate_buffer(...) */
|
||||||
{
|
{
|
||||||
lock_guard queue_lock(this->queue_mutex);
|
lock_guard queue_lock(this->queue_mutex);
|
||||||
this->queue_write.push_back(buffer);
|
this->queue_write.push_back(buffer);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
lock_guard lock(this->event_mutex);
|
lock_guard lock(this->event_mutex);
|
||||||
if(this->writeEvent)
|
if(this->writeEvent) {
|
||||||
event_add(this->writeEvent, nullptr);
|
event_add(this->writeEvent, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->connectionStatistics->logOutgoingPacket(stats::ConnectionStatistics::category::COMMAND, buffer.length());
|
this->connectionStatistics->logOutgoingPacket(stats::ConnectionStatistics::category::COMMAND, buffer.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebClient::registerMessageProcess() {
|
inline bool is_ssl_handshake_header(const std::string_view& buffer) {
|
||||||
auto weakLock = this->_this;
|
|
||||||
if(serverInstance->getVoiceServerManager()->getState() == VirtualServerManager::STARTED)
|
|
||||||
serverInstance->getVoiceServerManager()->get_executor_loop()->schedule(this->event_handle_packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool is_ssl_handshake_header(const pipes::buffer_view& buffer) {
|
|
||||||
if(buffer.length() < 0x05) return false; //Header too small!
|
if(buffer.length() < 0x05) return false; //Header too small!
|
||||||
|
|
||||||
if(buffer[0] != 0x16) return false; //recordType=handshake
|
if(buffer[0] != 0x16) return false; //recordType=handshake
|
||||||
@ -125,36 +117,26 @@ inline bool is_ssl_handshake_header(const pipes::buffer_view& buffer) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebClient::processNextMessage(const std::chrono::system_clock::time_point& /* scheduled */) {
|
bool WebClient::process_next_message(const std::string_view &buffer) {
|
||||||
lock_guard execute_lock(this->execute_mutex);
|
lock_guard execute_lock(this->execute_mutex);
|
||||||
if(this->state != ConnectionState::INIT_HIGH && this->state != ConnectionState::INIT_LOW && this->state != ConnectionState::CONNECTED)
|
if(this->state != ConnectionState::INIT_HIGH && this->state != ConnectionState::INIT_LOW && this->state != ConnectionState::CONNECTED) {
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
unique_lock buffer_lock(this->queue_mutex);
|
|
||||||
if(this->queue_read.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto buffer = this->queue_read.front();
|
|
||||||
this->queue_read.pop_front();
|
|
||||||
bool has_next = !this->queue_read.empty();
|
|
||||||
buffer_lock.unlock();
|
|
||||||
|
|
||||||
this->connectionStatistics->logIncomingPacket(stats::ConnectionStatistics::category::COMMAND, buffer.length());
|
this->connectionStatistics->logIncomingPacket(stats::ConnectionStatistics::category::COMMAND, buffer.length());
|
||||||
if(!this->ssl_detected) {
|
if(!this->ssl_detected) {
|
||||||
this->ssl_detected = true;
|
this->ssl_detected = true;
|
||||||
this->ssl_encrypted = is_ssl_handshake_header(buffer);
|
this->ssl_encrypted = is_ssl_handshake_header(buffer);
|
||||||
if(this->ssl_encrypted)
|
if(this->ssl_encrypted) {
|
||||||
logMessage(this->getServerId(), "[{}] Using encrypted basic connection.", CLIENT_STR_LOG_PREFIX_(this));
|
logMessage(this->getServerId(), "[{}] Using encrypted basic connection.", CLIENT_STR_LOG_PREFIX_(this));
|
||||||
else
|
} else {
|
||||||
logMessage(this->getServerId(), "[{}] Using unencrypted basic connection.", CLIENT_STR_LOG_PREFIX_(this));
|
logMessage(this->getServerId(), "[{}] Using unencrypted basic connection.", CLIENT_STR_LOG_PREFIX_(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(this->ssl_encrypted) {
|
if(this->ssl_encrypted) {
|
||||||
this->ssl_handler.process_incoming_data(buffer);
|
this->ssl_handler.process_incoming_data(pipes::buffer_view{buffer.data(), buffer.length()});
|
||||||
} else {
|
} else {
|
||||||
this->ws_handler.process_incoming_data(buffer);
|
this->ws_handler.process_incoming_data(pipes::buffer_view{buffer.data(), buffer.length()});
|
||||||
}
|
|
||||||
|
|
||||||
if(has_next) {
|
|
||||||
this->registerMessageProcess();
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
@ -23,7 +23,10 @@ using namespace ts;
|
|||||||
using namespace ts::server;
|
using namespace ts::server;
|
||||||
using namespace ts::protocol;
|
using namespace ts::protocol;
|
||||||
|
|
||||||
WebClient::WebClient(WebControlServer* server, int fd) : SpeakingClient(server->getTS()->getSql(), server->getTS()), handle(server), whisper_handler_{this} {
|
WebClient::WebClient(WebControlServer* server, int fd) :
|
||||||
|
SpeakingClient(server->getTS()->getSql(), server->getTS()),
|
||||||
|
handle{server},
|
||||||
|
whisper_handler_{this} {
|
||||||
memtrack::allocated<WebClient>(this);
|
memtrack::allocated<WebClient>(this);
|
||||||
|
|
||||||
assert(server->getTS());
|
assert(server->getTS());
|
||||||
@ -34,7 +37,8 @@ WebClient::WebClient(WebControlServer* server, int fd) : SpeakingClient(server->
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebClient::initialize() {
|
void WebClient::initialize() {
|
||||||
this->event_handle_packet = make_shared<event::ProxiedEventEntry<WebClient>>(dynamic_pointer_cast<WebClient>(this->ref()), &WebClient::processNextMessage);
|
auto ref_this = dynamic_pointer_cast<WebClient>(this->ref());
|
||||||
|
this->command_queue = std::make_unique<ServerCommandQueue>(serverInstance->server_command_executor(), std::make_unique<WebClientCommandHandler>(ref_this));
|
||||||
|
|
||||||
int enabled = 1;
|
int enabled = 1;
|
||||||
int disabled = 0;
|
int disabled = 0;
|
||||||
@ -176,7 +180,7 @@ void WebClient::sendCommand(const ts::command_builder &command, bool low) {
|
|||||||
this->sendJson(value);
|
this->sendJson(value);
|
||||||
} else {
|
} else {
|
||||||
auto data = command.build();
|
auto data = command.build();
|
||||||
Command parsed_command = Command::parse(pipes::buffer_view{data.data(), data.length()}, true, false);
|
Command parsed_command = Command::parse(data, true, false);
|
||||||
this->sendCommand(parsed_command, low);
|
this->sendCommand(parsed_command, low);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,7 +225,6 @@ bool WebClient::close_connection(const std::chrono::system_clock::time_point& ti
|
|||||||
|
|
||||||
{
|
{
|
||||||
lock_guard lock(self_lock->queue_mutex);
|
lock_guard lock(self_lock->queue_mutex);
|
||||||
flag_flushed &= self_lock->queue_read.empty();
|
|
||||||
flag_flushed &= self_lock->queue_write.empty();
|
flag_flushed &= self_lock->queue_write.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,8 +277,8 @@ command_result WebClient::handleCommand(Command &command) {
|
|||||||
return SpeakingClient::handleCommand(command);
|
return SpeakingClient::handleCommand(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebClient::tick(const std::chrono::system_clock::time_point& point) {
|
void WebClient::tick_server(const std::chrono::system_clock::time_point& point) {
|
||||||
SpeakingClient::tick(point);
|
SpeakingClient::tick_server(point);
|
||||||
|
|
||||||
if(this->ping.last_request + seconds(1) < point) {
|
if(this->ping.last_request + seconds(1) < point) {
|
||||||
if(this->ping.last_response > this->ping.last_request || this->ping.last_response + this->ping.timeout < point) {
|
if(this->ping.last_response > this->ping.last_request || this->ping.last_response + this->ping.timeout < point) {
|
||||||
@ -420,6 +423,17 @@ void WebClient::disconnectFinal() {
|
|||||||
this->handle->unregisterConnection(static_pointer_cast<WebClient>(self_lock));
|
this->handle->unregisterConnection(static_pointer_cast<WebClient>(self_lock));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebClientCommandHandler::WebClientCommandHandler(const std::shared_ptr<WebClient> &client) : client_ref{client} {}
|
||||||
|
|
||||||
|
bool WebClientCommandHandler::handle_command(const std::string_view &command) {
|
||||||
|
auto client = this->client_ref.lock();
|
||||||
|
if(!client) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return client->process_next_message(command);
|
||||||
|
}
|
||||||
|
|
||||||
Json::CharReaderBuilder json_reader_builder = []() noexcept {
|
Json::CharReaderBuilder json_reader_builder = []() noexcept {
|
||||||
Json::CharReaderBuilder reader_builder;
|
Json::CharReaderBuilder reader_builder;
|
||||||
|
|
||||||
|
@ -11,12 +11,15 @@
|
|||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
#include <EventLoop.h>
|
#include <EventLoop.h>
|
||||||
#include "../shared/WhisperHandler.h"
|
#include "../shared/WhisperHandler.h"
|
||||||
|
#include "../shared/ServerCommandExecutor.h"
|
||||||
|
|
||||||
namespace ts::server {
|
namespace ts::server {
|
||||||
class WebControlServer;
|
class WebControlServer;
|
||||||
|
class WebClientCommandHandler;
|
||||||
|
|
||||||
class WebClient : public SpeakingClient {
|
class WebClient : public SpeakingClient {
|
||||||
friend class WebControlServer;
|
friend class WebControlServer;
|
||||||
|
friend class WebClientCommandHandler;
|
||||||
public:
|
public:
|
||||||
WebClient(WebControlServer*, int socketFd);
|
WebClient(WebControlServer*, int socketFd);
|
||||||
~WebClient() override;
|
~WebClient() override;
|
||||||
@ -34,7 +37,7 @@ namespace ts::server {
|
|||||||
[[nodiscard]] inline std::chrono::nanoseconds client_ping_layer_7() const { return this->js_ping.value; }
|
[[nodiscard]] inline std::chrono::nanoseconds client_ping_layer_7() const { return this->js_ping.value; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void tick(const std::chrono::system_clock::time_point&) override; /* Every 500ms */
|
void tick_server(const std::chrono::system_clock::time_point&) override; /* Every 500ms */
|
||||||
|
|
||||||
void applySelfLock(const std::shared_ptr<WebClient> &cl){ _this = cl; }
|
void applySelfLock(const std::shared_ptr<WebClient> &cl){ _this = cl; }
|
||||||
private:
|
private:
|
||||||
@ -52,8 +55,6 @@ namespace ts::server {
|
|||||||
::event* readEvent;
|
::event* readEvent;
|
||||||
::event* writeEvent;
|
::event* writeEvent;
|
||||||
|
|
||||||
std::shared_ptr<event::ProxiedEventEntry<WebClient>> event_handle_packet;
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint8_t current_id{0};
|
uint8_t current_id{0};
|
||||||
std::chrono::system_clock::time_point last_request;
|
std::chrono::system_clock::time_point last_request;
|
||||||
@ -73,7 +74,7 @@ namespace ts::server {
|
|||||||
} js_ping;
|
} js_ping;
|
||||||
|
|
||||||
std::mutex queue_mutex;
|
std::mutex queue_mutex;
|
||||||
std::deque<pipes::buffer> queue_read;
|
std::unique_ptr<ServerCommandQueue> command_queue{};
|
||||||
std::deque<pipes::buffer> queue_write;
|
std::deque<pipes::buffer> queue_write;
|
||||||
threads::Mutex execute_mutex; /* needs to be recursive! */
|
threads::Mutex execute_mutex; /* needs to be recursive! */
|
||||||
|
|
||||||
@ -88,8 +89,8 @@ namespace ts::server {
|
|||||||
void handleMessageWrite(int, short, void*);
|
void handleMessageWrite(int, short, void*);
|
||||||
void enqueue_raw_packet(const pipes::buffer_view& /* buffer */);
|
void enqueue_raw_packet(const pipes::buffer_view& /* buffer */);
|
||||||
|
|
||||||
void processNextMessage(const std::chrono::system_clock::time_point& /* scheduled */);
|
/* TODO: Put the message processing part into the IO loop and not into command processing! */
|
||||||
void registerMessageProcess();
|
bool process_next_message(const std::string_view& buffer);
|
||||||
|
|
||||||
//WS events
|
//WS events
|
||||||
void onWSConnected();
|
void onWSConnected();
|
||||||
@ -110,5 +111,16 @@ namespace ts::server {
|
|||||||
command_result handleCommandWhisperSessionInitialize(Command &command);
|
command_result handleCommandWhisperSessionInitialize(Command &command);
|
||||||
command_result handleCommandWhisperSessionReset(Command &command);
|
command_result handleCommandWhisperSessionReset(Command &command);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WebClientCommandHandler : public ts::server::ServerCommandHandler {
|
||||||
|
public:
|
||||||
|
explicit WebClientCommandHandler(const std::shared_ptr<WebClient>& /* client */);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool handle_command(const std::string_view &) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::weak_ptr<WebClient> client_ref;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
@ -11,36 +11,43 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace ts;
|
using namespace ts;
|
||||||
|
|
||||||
IpListManager::IpListManager(const std::string& file, const std::deque<std::string>& def) : file(file), default_entries(def) { }
|
IpListManager::IpListManager(std::string file, const std::deque<std::string>& def) : file(std::move(file)), default_entries(def) { }
|
||||||
|
|
||||||
bool file_exists(const std::string& name) {
|
bool file_exists(const std::string& name) {
|
||||||
struct stat buffer{};
|
struct stat buffer{};
|
||||||
return (stat (name.c_str(), &buffer) == 0);
|
return (stat (name.c_str(), &buffer) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline string strip(std::string message) {
|
inline string strip(std::string message) {
|
||||||
while(!message.empty()) {
|
while(!message.empty()) {
|
||||||
if(message[0] == ' ')
|
if(message[0] == ' ') {
|
||||||
message = message.substr(1);
|
message = message.substr(1);
|
||||||
else if(message[message.length() - 1] == ' ')
|
} else if(message[message.length() - 1] == ' ') {
|
||||||
message = message.substr(0, message.length() - 1);
|
message = message.substr(0, message.length() - 1);
|
||||||
else break;
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpListManager::reload(std::string& error) {
|
bool IpListManager::reload(std::string& error) {
|
||||||
if(!file_exists(this->file)) {
|
if(!file_exists(this->file)) {
|
||||||
ofstream os(this->file);
|
ofstream os{this->file};
|
||||||
if(!os) {
|
if(!os) {
|
||||||
error = "Could not create default file!";
|
error = "Could not create default file!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for(const auto& entry : this->default_entries)
|
|
||||||
|
for(const auto& entry : this->default_entries) {
|
||||||
os << entry << endl;
|
os << entry << endl;
|
||||||
|
}
|
||||||
|
|
||||||
os.flush();
|
os.flush();
|
||||||
os.close();
|
os.close();
|
||||||
}
|
}
|
||||||
ifstream stream(this->file);
|
|
||||||
|
ifstream stream{this->file};
|
||||||
if(!stream) {
|
if(!stream) {
|
||||||
error = "Failed to read file!";
|
error = "Failed to read file!";
|
||||||
return false;
|
return false;
|
||||||
@ -51,22 +58,27 @@ bool IpListManager::reload(std::string& error) {
|
|||||||
while(getline(stream, line)) {
|
while(getline(stream, line)) {
|
||||||
line_number++;
|
line_number++;
|
||||||
line = strip(line);
|
line = strip(line);
|
||||||
if(line.empty() || line[0] == '#') continue;
|
if(line.empty() || line[0] == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
IPEntry result{};
|
IPEntry result{};
|
||||||
if(!this->parse_entry(result, line))
|
if(!this->parse_entry(result, line)) {
|
||||||
logError(0, "Failed to parse ip entry at line {} of file {}. Line: '{}'", line_number, this->file, line);
|
logError(0, "Failed to parse ip entry at line {} of file {}. Line: '{}'", line_number, this->file, line);
|
||||||
else
|
} else {
|
||||||
this->entries.push_back(result);
|
this->entries.push_back(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpListManager::contains(const sockaddr_storage &address) {
|
bool IpListManager::contains(const sockaddr_storage &address) {
|
||||||
for(const auto& entry : this->entries)
|
for(const auto& entry : this->entries) {
|
||||||
if(net::address_equal_ranged(address, entry.address, entry.range))
|
if(net::address_equal_ranged(address, entry.address, entry.range)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ namespace ts {
|
|||||||
uint8_t range; /* [0;32] or [0;128] */
|
uint8_t range; /* [0;32] or [0;128] */
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
IpListManager(const std::string&, const std::deque<std::string>&);
|
IpListManager(std::string , const std::deque<std::string>&);
|
||||||
|
|
||||||
bool reload(std::string&);
|
bool reload(std::string&);
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ void POWHandler::handle_puzzle_solve(const std::shared_ptr<ts::server::POWHandle
|
|||||||
|
|
||||||
auto voice_client = this->register_verified_client(client);
|
auto voice_client = this->register_verified_client(client);
|
||||||
if(voice_client) {
|
if(voice_client) {
|
||||||
auto rcommand = server::udp::ReassembledCommand::allocate(command.length());
|
auto rcommand = command::ReassembledCommand::allocate(command.length());
|
||||||
memcpy(rcommand->command(), command.data_ptr(), rcommand->length());
|
memcpy(rcommand->command(), command.data_ptr(), rcommand->length());
|
||||||
voice_client->connection->handlePacketCommand(rcommand);
|
voice_client->connection->handlePacketCommand(rcommand);
|
||||||
client->state = LowHandshakeState::COMPLETED;
|
client->state = LowHandshakeState::COMPLETED;
|
||||||
|
@ -5,14 +5,12 @@
|
|||||||
#include "QueryServer.h"
|
#include "QueryServer.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <poll.h>
|
|
||||||
#include <src/VirtualServer.h>
|
#include <src/VirtualServer.h>
|
||||||
#include <src/client/query/QueryClient.h>
|
#include <src/client/query/QueryClient.h>
|
||||||
#include <src/client/InternalClient.h>
|
#include <src/client/InternalClient.h>
|
||||||
#include <misc/rnd.h>
|
#include <misc/rnd.h>
|
||||||
#include <src/InstanceHandler.h>
|
#include <src/InstanceHandler.h>
|
||||||
#include <log/LogUtils.h>
|
#include <ThreadPool/ThreadHelper.h>
|
||||||
#include <src/client/ConnectedClient.h>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
@ -24,22 +22,19 @@ using namespace ts::server;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QueryServer::QueryServer(sql::SqlManager* db) : sql(db) {
|
QueryServer::QueryServer(sql::SqlManager* db) : sql(db) {
|
||||||
this->_executePool = new threads::ThreadPool(4, "EXEC Query");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryServer::~QueryServer() {
|
QueryServer::~QueryServer() {
|
||||||
stop();
|
stop();
|
||||||
this->_executePool->shutdown();
|
|
||||||
delete this->_executePool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void QueryServer::unregisterConnection(const shared_ptr<QueryClient> &client) {
|
void QueryServer::unregisterConnection(const shared_ptr<QueryClient> &client) {
|
||||||
{
|
{
|
||||||
lock_guard lock(this->connected_clients_lock);
|
lock_guard lock(this->connected_clients_mutex);
|
||||||
auto found = std::find(this->connectedClients.begin(), this->connectedClients.end(), client);
|
auto found = std::find(this->connected_clients.begin(), this->connected_clients.end(), client);
|
||||||
if(found != this->connectedClients.end())
|
if(found != this->connected_clients.end())
|
||||||
this->connectedClients.erase(found);
|
this->connected_clients.erase(found);
|
||||||
else
|
else
|
||||||
logError(LOG_QUERY, "Attempted to unregister an invalid connection!");
|
logError(LOG_QUERY, "Attempted to unregister an invalid connection!");
|
||||||
}
|
}
|
||||||
@ -52,7 +47,7 @@ void QueryServer::unregisterConnection(const shared_ptr<QueryClient> &client) {
|
|||||||
/* client->handle = nullptr; */
|
/* client->handle = nullptr; */
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QueryServer::start(const deque<shared_ptr<QueryServer::Binding>> &bindings, std::string &error) {
|
bool QueryServer::start(const deque<shared_ptr<QueryServer::Binding>> &bindings_, std::string &error) {
|
||||||
if(this->active) {
|
if(this->active) {
|
||||||
error = "already started";
|
error = "already started";
|
||||||
return false;
|
return false;
|
||||||
@ -61,40 +56,49 @@ bool QueryServer::start(const deque<shared_ptr<QueryServer::Binding>> &bindings,
|
|||||||
|
|
||||||
/* load ip black/whitelist */
|
/* load ip black/whitelist */
|
||||||
{
|
{
|
||||||
ip_blacklist.reset(new IpListManager("query_ip_blacklist.txt", {"#A new line separated address blacklist", "#", "#For example if we dont want google:", "8.8.8.8"}));
|
ip_blacklist = std::make_unique<IpListManager>("query_ip_blacklist.txt", std::deque<std::string>{"#A new line separated address blacklist", "#", "#For example if we dont want google:", "8.8.8.8"});
|
||||||
ip_whitelist.reset(new IpListManager("query_ip_whitelist.txt", {"#A new line separated address whitelist", "#Every ip have no flood and login attempt limit!", "127.0.0.1/8", "::1"}));
|
ip_whitelist = std::make_unique<IpListManager>("query_ip_whitelist.txt", std::deque<std::string>{"#A new line separated address whitelist", "#Every ip have no flood and login attempt limit!", "127.0.0.1/8", "::1"});
|
||||||
string error;
|
|
||||||
if(!this->ip_blacklist->reload(error)) logError(LOG_QUERY, "Failed to load query blacklist: {}", error);
|
if(!this->ip_blacklist->reload(error)) {
|
||||||
if(!this->ip_whitelist->reload(error)) logError(LOG_QUERY, "Failed to load query whitelist: {}", error);
|
logError(LOG_QUERY, "Failed to load query blacklist: {}", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this->ip_whitelist->reload(error)) {
|
||||||
|
logError(LOG_QUERY, "Failed to load query whitelist: {}", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
error.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* reserve backup file descriptor in case that the max file descriptors have been reached */
|
/* reserve backup file descriptor in case that the max file descriptors have been reached */
|
||||||
{
|
{
|
||||||
this->server_reserve_fd = dup(1);
|
this->server_reserve_fd = dup(1);
|
||||||
if(this->server_reserve_fd < 0)
|
if(this->server_reserve_fd < 0) {
|
||||||
logWarning(LOG_QUERY, "Failed to reserve a backup accept file descriptor. ({} | {})", errno, strerror(errno));
|
logWarning(LOG_QUERY, "Failed to reserve a backup accept file descriptor. ({} | {})", errno, strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* setup event bases */
|
/* setup event bases */
|
||||||
{
|
{
|
||||||
this->eventLoop = event_base_new();
|
this->event_io_loop = event_base_new();
|
||||||
this->ioThread = new threads::Thread(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [&]{
|
this->event_io_thread = std::thread{[&]{
|
||||||
while(this->active) {
|
while(this->active) {
|
||||||
debugMessage(LOG_QUERY, "Entering event loop ({})", (void*) this->eventLoop);
|
debugMessage(LOG_QUERY, "Entering event loop ({})", (void*) this->event_io_loop);
|
||||||
event_base_loop(this->eventLoop, EVLOOP_NO_EXIT_ON_EMPTY);
|
event_base_loop(this->event_io_loop, EVLOOP_NO_EXIT_ON_EMPTY);
|
||||||
if(this->active) {
|
if(this->active) {
|
||||||
debugMessage(LOG_QUERY, "Event loop exited ({}). No active events. Sleeping 1 seconds", (void*) this->eventLoop);
|
debugMessage(LOG_QUERY, "Event loop exited ({}). No active events. Sleeping 1 seconds", (void*) this->event_io_loop);
|
||||||
this_thread::sleep_for(seconds(1));
|
this_thread::sleep_for(seconds(1));
|
||||||
} else {
|
} else {
|
||||||
debugMessage(LOG_QUERY, "Event loop exited ({})", (void*) this->eventLoop);
|
debugMessage(LOG_QUERY, "Event loop exited ({})", (void*) this->event_io_loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}};
|
||||||
this->ioThread->name("EVENT Query").execute();
|
|
||||||
|
threads::name(this->event_io_thread, "query io");
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto& binding : bindings) {
|
for(auto& binding : bindings_) {
|
||||||
binding->file_descriptor = socket(binding->address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
binding->file_descriptor = socket(binding->address.ss_family, (unsigned) SOCK_STREAM | (unsigned) SOCK_NONBLOCK, 0);
|
||||||
if(binding->file_descriptor < 0) {
|
if(binding->file_descriptor < 0) {
|
||||||
logError(LOG_QUERY, "Failed to bind server to {}. (Failed to create socket: {} | {})", binding->as_string(), errno, strerror(errno));
|
logError(LOG_QUERY, "Failed to bind server to {}. (Failed to create socket: {} | {})", binding->as_string(), errno, strerror(errno));
|
||||||
continue;
|
continue;
|
||||||
@ -102,16 +106,23 @@ bool QueryServer::start(const deque<shared_ptr<QueryServer::Binding>> &bindings,
|
|||||||
|
|
||||||
int enable = 1, disabled = 0;
|
int enable = 1, disabled = 0;
|
||||||
|
|
||||||
if (setsockopt(binding->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
|
if (setsockopt(binding->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
|
||||||
logWarning(LOG_QUERY, "Failed to activate SO_REUSEADDR for binding {} ({} | {})", binding->as_string(), errno, strerror(errno));
|
logWarning(LOG_QUERY, "Failed to activate SO_REUSEADDR for binding {} ({} | {})", binding->as_string(), errno, strerror(errno));
|
||||||
if(setsockopt(binding->file_descriptor, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0)
|
|
||||||
logWarning(LOG_QUERY, "Failed to deactivate TCP_NOPUSH for binding {} ({} | {})", binding->as_string(), errno, strerror(errno));
|
|
||||||
if(binding->address.ss_family == AF_INET6) {
|
|
||||||
if(setsockopt(binding->file_descriptor, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(int)) < 0)
|
|
||||||
logWarning(LOG_QUERY, "Failed to activate IPV6_V6ONLY for IPv6 binding {} ({} | {})", binding->as_string(), errno, strerror(errno));
|
|
||||||
}
|
}
|
||||||
if(fcntl(binding->file_descriptor, F_SETFD, FD_CLOEXEC) < 0)
|
|
||||||
|
if(setsockopt(binding->file_descriptor, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0) {
|
||||||
|
logWarning(LOG_QUERY, "Failed to deactivate TCP_NOPUSH for binding {} ({} | {})", binding->as_string(), errno, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(binding->address.ss_family == AF_INET6) {
|
||||||
|
if(setsockopt(binding->file_descriptor, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(int)) < 0) {
|
||||||
|
logWarning(LOG_QUERY, "Failed to activate IPV6_V6ONLY for IPv6 binding {} ({} | {})", binding->as_string(), errno, strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fcntl(binding->file_descriptor, F_SETFD, FD_CLOEXEC) < 0) {
|
||||||
logWarning(LOG_QUERY, "Failed to set flag FD_CLOEXEC for binding {} ({} | {})", binding->as_string(), errno, strerror(errno));
|
logWarning(LOG_QUERY, "Failed to set flag FD_CLOEXEC for binding {} ({} | {})", binding->as_string(), errno, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (bind(binding->file_descriptor, (struct sockaddr *) &binding->address, sizeof(binding->address)) < 0) {
|
if (bind(binding->file_descriptor, (struct sockaddr *) &binding->address, sizeof(binding->address)) < 0) {
|
||||||
@ -126,7 +137,7 @@ bool QueryServer::start(const deque<shared_ptr<QueryServer::Binding>> &bindings,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
binding->event_accept = event_new(this->eventLoop, binding->file_descriptor, EV_READ | EV_PERSIST, [](int a, short b, void* c){ ((QueryServer *) c)->on_client_receive(a, b, c); }, this);
|
binding->event_accept = event_new(this->event_io_loop, binding->file_descriptor, EV_READ | EV_PERSIST, [](int a, short b, void* c){ ((QueryServer *) c)->on_client_receive(a, b, c); }, this);
|
||||||
event_add(binding->event_accept, nullptr);
|
event_add(binding->event_accept, nullptr);
|
||||||
this->bindings.push_back(binding);
|
this->bindings.push_back(binding);
|
||||||
}
|
}
|
||||||
@ -137,79 +148,104 @@ bool QueryServer::start(const deque<shared_ptr<QueryServer::Binding>> &bindings,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->tickingId = serverInstance->scheduler()->schedule("query", bind(&QueryServer::tick, this), seconds(1));
|
this->tick_active = true;
|
||||||
|
this->tick_thread = std::thread{[&]{ this->tick_executor(); }};
|
||||||
|
threads::name(this->tick_thread, "query tick");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void QueryServer::stop() {
|
void QueryServer::stop() {
|
||||||
if(!this->running())
|
if(!this->active) {
|
||||||
return;
|
return;
|
||||||
active = false;
|
|
||||||
serverInstance->scheduler()->cancelTask("query");
|
|
||||||
|
|
||||||
this->connected_clients_lock.lock();
|
|
||||||
auto connected_clients = this->connectedClients;
|
|
||||||
this->connected_clients_lock.unlock();
|
|
||||||
|
|
||||||
Command cmd("serverstop");
|
|
||||||
cmd["stopped"] = true;
|
|
||||||
for(const auto &client : connected_clients){
|
|
||||||
client->sendCommand(cmd);
|
|
||||||
client->disconnect("server stopped");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
this->active = false;
|
||||||
auto now = system_clock::now();
|
|
||||||
while(!this->connectedClients.empty()) {
|
|
||||||
if(now + seconds(5) < system_clock::now()) {
|
|
||||||
logError(LOG_QUERY, "Failed to disconnect all clients!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
threads::self::sleep_for(milliseconds(100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto thread : this->threads){
|
|
||||||
if(thread->state() == threads::ThreadState::RUNNING)
|
|
||||||
thread->join();
|
|
||||||
delete thread;
|
|
||||||
}
|
|
||||||
this->threads.clear();
|
|
||||||
|
|
||||||
|
/* 1. Shutdown all bindings so we don't get any new queries */
|
||||||
for(auto& binding : this->bindings) {
|
for(auto& binding : this->bindings) {
|
||||||
if(binding->event_accept) {
|
if(binding->event_accept) {
|
||||||
event_del_block(binding->event_accept);
|
event_del_block(binding->event_accept);
|
||||||
event_free(binding->event_accept);
|
event_free(binding->event_accept);
|
||||||
binding->event_accept = nullptr;
|
binding->event_accept = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(binding->file_descriptor > 0) {
|
if(binding->file_descriptor > 0) {
|
||||||
if(shutdown(binding->file_descriptor, SHUT_RDWR) < 0)
|
/* Shutdown not needed since we're not connected. A shutdown would result in "Transport endpoint is not connected". */
|
||||||
logWarning(LOG_QUERY, "Failed to shutdown socket for binding {} ({} | {}).", binding->as_string(), errno, strerror(errno));
|
if(close(binding->file_descriptor) < 0) {
|
||||||
if(close(binding->file_descriptor) < 0)
|
|
||||||
logError(LOG_QUERY, "Failed to close socket for binding {} ({} | {}).", binding->as_string(), errno, strerror(errno));
|
logError(LOG_QUERY, "Failed to close socket for binding {} ({} | {}).", binding->as_string(), errno, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
binding->file_descriptor = -1;
|
binding->file_descriptor = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->bindings.clear();
|
this->bindings.clear();
|
||||||
|
|
||||||
if(this->eventLoop)
|
/* 2. Disconnect all connected query clients */
|
||||||
event_base_loopexit(this->eventLoop, nullptr);
|
{
|
||||||
if(this->ioThread) {
|
ts::command_builder notify{"serverstop"};
|
||||||
if(this->ioThread->join(seconds(3)) != 0) {
|
notify.put_unchecked(0, "stopped", "1");
|
||||||
logCritical(LOG_QUERY, "Failed to terminate event loop!");
|
|
||||||
this->ioThread->detach();
|
std::lock_guard client_lock{this->connected_clients_mutex};
|
||||||
|
for(const auto &client : this->connected_clients) {
|
||||||
|
client->sendCommand(notify, false);
|
||||||
|
client->disconnect("server stopped");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shortcircuiting the disconnect since we don't want the full "server leave" disconnect.
|
||||||
|
* We only wan't to prevent the client form receiving any more notifications.
|
||||||
|
*/
|
||||||
|
this->execute_query_disconnect(client, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete this->ioThread;
|
|
||||||
this->ioThread = nullptr;
|
|
||||||
|
|
||||||
if(this->eventLoop) {
|
/* Await all clients to disconnect within 5 seconds. */
|
||||||
event_base_free(this->eventLoop);
|
{
|
||||||
this->eventLoop = nullptr;
|
std::unique_lock client_lock{this->connected_clients_mutex};
|
||||||
|
this->connected_client_disconnected_notify.wait_for(client_lock, std::chrono::seconds{5}, [&]{
|
||||||
|
return this->connected_clients.empty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 3. Shutdown the query event loop (to finish of client disconnects as well) */
|
||||||
|
{
|
||||||
|
std::lock_guard tick_lock{this->tick_mutex};
|
||||||
|
this->tick_active = false;
|
||||||
|
this->tick_notify.notify_all();
|
||||||
|
}
|
||||||
|
threads::save_join(this->tick_thread, true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 4. Force disconnect pending clients.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
std::unique_lock client_lock{this->connected_clients_mutex};
|
||||||
|
auto connected_clients_ = std::move(this->connected_clients);
|
||||||
|
client_lock.unlock();
|
||||||
|
|
||||||
|
if(!connected_clients_.empty()) {
|
||||||
|
logWarning(LOG_QUERY, "Failed to normally disconnect {} query clients. Closing connection.", connected_clients_.size());
|
||||||
|
|
||||||
|
for(const auto& client : this->connected_clients) {
|
||||||
|
this->execute_query_connection_close(client, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. Shutdown the io event loop */
|
||||||
|
if(this->event_io_loop) {
|
||||||
|
event_base_loopexit(this->event_io_loop, nullptr);
|
||||||
|
}
|
||||||
|
threads::save_join(this->event_io_thread, false);
|
||||||
|
|
||||||
|
if(this->event_io_loop) {
|
||||||
|
event_base_free(this->event_io_loop);
|
||||||
|
this->event_io_loop = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. Cleanup the servers reserve file descriptor */
|
||||||
if(this->server_reserve_fd > 0) {
|
if(this->server_reserve_fd > 0) {
|
||||||
if(close(this->server_reserve_fd) < 0)
|
if(close(this->server_reserve_fd) < 0) {
|
||||||
logError(LOG_QUERY, "Failed to close backup file descriptor ({} | {})", errno, strerror(errno));
|
logError(LOG_QUERY, "Failed to close backup file descriptor ({} | {})", errno, strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this->server_reserve_fd = -1;
|
this->server_reserve_fd = -1;
|
||||||
}
|
}
|
||||||
@ -221,7 +257,7 @@ inline std::string logging_address(const sockaddr_storage& address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void send_direct_disconnect(const sockaddr_storage& address, int file_descriptor, const char* message, size_t message_length) {
|
inline void send_direct_disconnect(const sockaddr_storage& address, int file_descriptor, const char* message, size_t message_length) {
|
||||||
auto _non_block = [&]{
|
auto enable_non_block = [&]{
|
||||||
int flags = fcntl(file_descriptor, F_GETFL, 0);
|
int flags = fcntl(file_descriptor, F_GETFL, 0);
|
||||||
if (flags == -1) {
|
if (flags == -1) {
|
||||||
debugMessage(LOG_QUERY, "[{}] Failed to set socket to nonblock. Flag query failed ({} | {})", logging_address(address), errno, strerror(errno));
|
debugMessage(LOG_QUERY, "[{}] Failed to set socket to nonblock. Flag query failed ({} | {})", logging_address(address), errno, strerror(errno));
|
||||||
@ -234,20 +270,22 @@ inline void send_direct_disconnect(const sockaddr_storage& address, int file_des
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_non_block();
|
enable_non_block();
|
||||||
|
|
||||||
{
|
{
|
||||||
struct timeval timeout{};
|
struct timeval timeout{};
|
||||||
timeout.tv_sec = 5;
|
timeout.tv_sec = 5;
|
||||||
timeout.tv_usec = 0;
|
timeout.tv_usec = 0;
|
||||||
if (setsockopt (file_descriptor, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0)
|
if (setsockopt (file_descriptor, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0) {
|
||||||
debugMessage(LOG_QUERY, "[{}] Failed to set the send timeout on socket", logging_address(address));
|
debugMessage(LOG_QUERY, "[{}] Failed to set the send timeout on socket", logging_address(address));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool broken_pipe = false;
|
bool broken_pipe = false;
|
||||||
auto _send = [&](const char* data, size_t length) {
|
auto send_ = [&](const char* data, size_t length) {
|
||||||
if(broken_pipe)
|
if(broken_pipe) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
size_t written_bytes = 0;
|
size_t written_bytes = 0;
|
||||||
while(written_bytes < length) {
|
while(written_bytes < length) {
|
||||||
@ -263,15 +301,15 @@ inline void send_direct_disconnect(const sockaddr_storage& address, int file_des
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* we could ignore errors here */
|
/* we could ignore errors here */
|
||||||
_send(config::query::motd.data(), config::query::motd.size());
|
send_(config::query::motd.data(), config::query::motd.size());
|
||||||
_send(message, message_length);
|
send_(message, message_length);
|
||||||
|
|
||||||
/* "flush" with the last new line and then close */
|
/* "flush" with the last new line and then close */
|
||||||
int flag = 1;
|
int flag = 1;
|
||||||
if(setsockopt(file_descriptor, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0) {
|
if(setsockopt(file_descriptor, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0) {
|
||||||
debugMessage(LOG_QUERY, "[{}] Failed to enabled TCP no delay to flush the direct query disconnect socket ({} | {}).", logging_address(address), errno, strerror(errno));
|
debugMessage(LOG_QUERY, "[{}] Failed to enabled TCP no delay to flush the direct query disconnect socket ({} | {}).", logging_address(address), errno, strerror(errno));
|
||||||
}
|
}
|
||||||
_send(config::query::newlineCharacter.data(), config::query::newlineCharacter.size());
|
send_(config::query::newlineCharacter.data(), config::query::newlineCharacter.size());
|
||||||
|
|
||||||
if(shutdown(file_descriptor, SHUT_RDWR) < 0) {
|
if(shutdown(file_descriptor, SHUT_RDWR) < 0) {
|
||||||
debugMessage(LOG_QUERY, "[{}] Failed to shutdown socket ({} | {}).", logging_address(address), errno, strerror(errno));
|
debugMessage(LOG_QUERY, "[{}] Failed to shutdown socket ({} | {}).", logging_address(address), errno, strerror(errno));
|
||||||
@ -284,23 +322,25 @@ inline void send_direct_disconnect(const sockaddr_storage& address, int file_des
|
|||||||
//dummyfdflood
|
//dummyfdflood
|
||||||
//dummyfdflood clear
|
//dummyfdflood clear
|
||||||
|
|
||||||
void QueryServer::on_client_receive(int _server_file_descriptor, short ev, void *arg) {
|
void QueryServer::on_client_receive(int server_file_descriptor, short, void *) {
|
||||||
sockaddr_storage remote_address{};
|
sockaddr_storage remote_address{};
|
||||||
memset(&remote_address, 0, sizeof(sockaddr_in));
|
memset(&remote_address, 0, sizeof(sockaddr_in));
|
||||||
socklen_t address_length = sizeof(remote_address);
|
socklen_t address_length = sizeof(remote_address);
|
||||||
|
|
||||||
int file_descriptor = accept(_server_file_descriptor, (struct sockaddr *) &remote_address, &address_length);
|
int client_file_descriptor = accept(server_file_descriptor, (struct sockaddr *) &remote_address, &address_length);
|
||||||
if (file_descriptor < 0) {
|
if (client_file_descriptor < 0) {
|
||||||
if(errno == EAGAIN)
|
if(errno == EAGAIN) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(errno == EMFILE || errno == ENFILE) {
|
if(errno == EMFILE || errno == ENFILE) {
|
||||||
if(errno == EMFILE)
|
if(errno == EMFILE) {
|
||||||
logError(LOG_QUERY, "Server ran out file descriptors. Please increase the process file descriptor limit or decrease the instance variable 'serverinstance_query_max_connections'");
|
logError(LOG_QUERY, "Server ran out file descriptors. Please increase the process file descriptor limit or decrease the instance variable 'serverinstance_query_max_connections'");
|
||||||
else
|
} else {
|
||||||
logError(LOG_QUERY, "Server ran out file descriptors. Please increase the process and system-wide file descriptor limit or decrease the instance variable 'serverinstance_query_max_connections'");
|
logError(LOG_QUERY, "Server ran out file descriptors. Please increase the process and system-wide file descriptor limit or decrease the instance variable 'serverinstance_query_max_connections'");
|
||||||
|
}
|
||||||
|
|
||||||
bool tmp_close_success = false;
|
bool tmp_close_success{false};
|
||||||
{
|
{
|
||||||
lock_guard reserve_fd_lock(server_reserve_fd_lock);
|
lock_guard reserve_fd_lock(server_reserve_fd_lock);
|
||||||
if(this->server_reserve_fd > 0) {
|
if(this->server_reserve_fd > 0) {
|
||||||
@ -314,31 +354,34 @@ void QueryServer::on_client_receive(int _server_file_descriptor, short ev, void
|
|||||||
this->server_reserve_fd = 0;
|
this->server_reserve_fd = 0;
|
||||||
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
file_descriptor = accept(_server_file_descriptor, (struct sockaddr *) &remote_address, &address_length);
|
client_file_descriptor = accept(server_file_descriptor, (struct sockaddr *) &remote_address, &address_length);
|
||||||
if(file_descriptor < 0) {
|
if(client_file_descriptor < 0) {
|
||||||
if(errno == EMFILE || errno == ENFILE)
|
if(errno == EMFILE || errno == ENFILE) {
|
||||||
debugMessage(LOG_QUERY, "[{}] Even with freeing the reserved descriptor accept failed. Attempting to reclaim reserved file descriptor", logging_address(remote_address));
|
debugMessage(LOG_QUERY, "[{}] Even with freeing the reserved descriptor accept failed. Attempting to reclaim reserved file descriptor", logging_address(remote_address));
|
||||||
else if(errno == EAGAIN);
|
} else if(errno == EAGAIN) {
|
||||||
else {
|
/* Nothing to do */
|
||||||
|
} else {
|
||||||
debugMessage(LOG_QUERY, "[{}] Failed to accept client with reserved file descriptor. ({} | {})", logging_address(remote_address), errno, strerror(errno));
|
debugMessage(LOG_QUERY, "[{}] Failed to accept client with reserved file descriptor. ({} | {})", logging_address(remote_address), errno, strerror(errno));
|
||||||
}
|
}
|
||||||
this->server_reserve_fd = dup(1);
|
this->server_reserve_fd = dup(1);
|
||||||
if(this->server_reserve_fd < 0)
|
if(this->server_reserve_fd < 0) {
|
||||||
debugMessage(LOG_QUERY, "[{}] Failed to reclaim reserved file descriptor. Future clients cant be accepted!", logging_address(remote_address));
|
debugMessage(LOG_QUERY, "[{}] Failed to reclaim reserved file descriptor. Future clients cant be accepted!", logging_address(remote_address));
|
||||||
else
|
} else {
|
||||||
tmp_close_success = true;
|
tmp_close_success = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debugMessage(LOG_QUERY, "[{}] Successfully accepted client via reserved descriptor (fd: {}). Initializing socket and sending MOTD and disconnect.", logging_address(remote_address), file_descriptor);
|
debugMessage(LOG_QUERY, "[{}] Successfully accepted client via reserved descriptor (fd: {}). Initializing socket and sending MOTD and disconnect.", logging_address(remote_address), client_file_descriptor);
|
||||||
|
|
||||||
static auto resource_limit_error = R"(error id=57344 msg=query\sserver\sresource\slimit\sreached extra_msg=file\sdescriptor\slimit\sexceeded)";
|
static auto resource_limit_error = R"(error id=57344 msg=query\sserver\sresource\slimit\sreached extra_msg=file\sdescriptor\slimit\sexceeded)";
|
||||||
send_direct_disconnect(remote_address, file_descriptor, resource_limit_error, strlen(resource_limit_error));
|
send_direct_disconnect(remote_address, client_file_descriptor, resource_limit_error, strlen(resource_limit_error));
|
||||||
|
|
||||||
this->server_reserve_fd = dup(1);
|
this->server_reserve_fd = dup(1);
|
||||||
if(this->server_reserve_fd < 0)
|
if(this->server_reserve_fd < 0) {
|
||||||
debugMessage(LOG_QUERY, "Failed to reclaim reserved file descriptor. Future clients cant be accepted!");
|
debugMessage(LOG_QUERY, "Failed to reclaim reserved file descriptor. Future clients cant be accepted!");
|
||||||
else
|
} else {
|
||||||
tmp_close_success = true;
|
tmp_close_success = true;
|
||||||
|
}
|
||||||
logMessage(LOG_QUERY, "[{}] Dropping new query connection attempt because of too many open file descriptors.", logging_address(remote_address));
|
logMessage(LOG_QUERY, "[{}] Dropping new query connection attempt because of too many open file descriptors.", logging_address(remote_address));
|
||||||
};
|
};
|
||||||
_();
|
_();
|
||||||
@ -347,8 +390,9 @@ void QueryServer::on_client_receive(int _server_file_descriptor, short ev, void
|
|||||||
|
|
||||||
if(!tmp_close_success) {
|
if(!tmp_close_success) {
|
||||||
debugMessage(LOG_QUERY, "Sleeping two seconds because we're currently having no resources for this user. (Removing the accept event)");
|
debugMessage(LOG_QUERY, "Sleeping two seconds because we're currently having no resources for this user. (Removing the accept event)");
|
||||||
for(auto& binding : this->bindings)
|
for(auto& binding : this->bindings) {
|
||||||
event_del_noblock(binding->event_accept);
|
event_del_noblock(binding->event_accept);
|
||||||
|
}
|
||||||
accept_event_deleted = system_clock::now();
|
accept_event_deleted = system_clock::now();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -357,21 +401,22 @@ void QueryServer::on_client_receive(int _server_file_descriptor, short ev, void
|
|||||||
logMessage(LOG_QUERY, "Got an error while accepting a new client. (errno: {}, message: {})", errno, strerror(errno));
|
logMessage(LOG_QUERY, "Got an error while accepting a new client. (errno: {}, message: {})", errno, strerror(errno));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
unique_lock lock(this->connected_clients_lock);
|
unique_lock lock{this->connected_clients_mutex};
|
||||||
auto max_connections = serverInstance->properties()[property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS].as<size_t>();
|
auto max_connections = serverInstance->properties()[property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS].as<size_t>();
|
||||||
if(max_connections > 0 && max_connections <= this->connectedClients.size()) {
|
if(max_connections > 0 && max_connections <= this->connected_clients.size()) {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
logMessage(LOG_QUERY, "[{}] Dropping new query connection attempt because of too many connected query clients.", logging_address(remote_address));
|
logMessage(LOG_QUERY, "[{}] Dropping new query connection attempt because of too many connected query clients.", logging_address(remote_address));
|
||||||
static auto query_server_full = R"(error id=4611 msg=max\sclients\sreached)";
|
static auto query_server_full = R"(error id=4611 msg=max\sclients\sreached)";
|
||||||
send_direct_disconnect(remote_address, file_descriptor, query_server_full, strlen(query_server_full));
|
send_direct_disconnect(remote_address, client_file_descriptor, query_server_full, strlen(query_server_full));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto max_ip_connections = serverInstance->properties()[property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP].as<size_t>();
|
auto max_ip_connections = serverInstance->properties()[property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP].as<size_t>();
|
||||||
if(max_ip_connections > 0) {
|
if(max_ip_connections > 0) {
|
||||||
size_t connection_count = 0;
|
size_t connection_count = 0;
|
||||||
for(auto& client : this->connectedClients) {
|
for(auto& client : this->connected_clients) {
|
||||||
if(net::address_equal(client->remote_address, remote_address))
|
if(net::address_equal(client->remote_address, remote_address))
|
||||||
connection_count++;
|
connection_count++;
|
||||||
}
|
}
|
||||||
@ -380,23 +425,24 @@ void QueryServer::on_client_receive(int _server_file_descriptor, short ev, void
|
|||||||
lock.unlock();
|
lock.unlock();
|
||||||
logMessage(LOG_QUERY, "[{}] Dropping new query connection attempt because of too many simultaneously connected session from this ip.", logging_address(remote_address));
|
logMessage(LOG_QUERY, "[{}] Dropping new query connection attempt because of too many simultaneously connected session from this ip.", logging_address(remote_address));
|
||||||
static auto query_server_full = R"(error id=4610 msg=too\smany\ssimultaneously\sconnected\ssessions)";//
|
static auto query_server_full = R"(error id=4610 msg=too\smany\ssimultaneously\sconnected\ssessions)";//
|
||||||
send_direct_disconnect(remote_address, file_descriptor, query_server_full, strlen(query_server_full));
|
send_direct_disconnect(remote_address, client_file_descriptor, query_server_full, strlen(query_server_full));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<QueryClient> client = std::make_shared<QueryClient>(this, file_descriptor);
|
auto client = std::make_shared<QueryClient>(this, client_file_descriptor);
|
||||||
client->applySelfLock(client);
|
|
||||||
memcpy(&client->remote_address, &remote_address, sizeof(remote_address));
|
memcpy(&client->remote_address, &remote_address, sizeof(remote_address));
|
||||||
|
client->initialize_self_reference(client);
|
||||||
|
|
||||||
{
|
{
|
||||||
lock_guard lock(this->connected_clients_lock);
|
lock_guard lock(this->connected_clients_mutex);
|
||||||
this->connectedClients.push_back(client);
|
this->connected_clients.push_back(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
client->preInitialize();
|
client->preInitialize();
|
||||||
if(client->readEvent) {
|
if(client->event_read) {
|
||||||
event_add(client->readEvent, nullptr);
|
event_add(client->event_read, nullptr);
|
||||||
}
|
}
|
||||||
logMessage(LOG_QUERY, "Got new client from {}", client->getLoggingPeerIp() + ":" + to_string(client->getPeerPort()));
|
logMessage(LOG_QUERY, "Got new client from {}", client->getLoggingPeerIp() + ":" + to_string(client->getPeerPort()));
|
||||||
}
|
}
|
||||||
@ -551,43 +597,182 @@ bool QueryServer::change_query_password(const std::shared_ptr<ts::server::QueryA
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryServer::tick() {
|
void QueryServer::tick_clients() {
|
||||||
|
decltype(this->connected_clients) connected_clients_;
|
||||||
decltype(this->connectedClients) clCopy;
|
|
||||||
{
|
{
|
||||||
lock_guard lock(this->connected_clients_lock);
|
lock_guard lock(this->connected_clients_mutex);
|
||||||
clCopy = this->connectedClients;
|
connected_clients_ = this->connected_clients;
|
||||||
}
|
}
|
||||||
for(const auto& cl : clCopy) cl->queryTick();
|
|
||||||
|
for(const auto& cl : connected_clients_) {
|
||||||
|
cl->tick_query();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
threads::MutexLock lock(this->loginLock);
|
std::lock_guard connect_lock{this->client_connect_mutex};
|
||||||
|
|
||||||
vector<std::string> qbanErase;
|
std::vector<std::string> erase_bans{};
|
||||||
for(auto& elm : this->queryBann) {
|
erase_bans.reserve(32);
|
||||||
if(elm.second < system_clock::now())
|
|
||||||
qbanErase.push_back(elm.first);
|
|
||||||
}
|
|
||||||
for(const auto& ip : qbanErase)
|
|
||||||
this->queryBann.erase(ip);
|
|
||||||
|
|
||||||
if(system_clock::now() - seconds(5) < lastDecrease) {
|
for(auto& elm : this->client_connect_bans) {
|
||||||
this->lastDecrease = system_clock::now();
|
if(elm.second < system_clock::now()) {
|
||||||
|
erase_bans.push_back(elm.first);
|
||||||
vector<std::string> lattempErase;
|
}
|
||||||
for(auto& elm : this->loginAttempts) {
|
}
|
||||||
if(elm.second == 0)
|
|
||||||
lattempErase.push_back(elm.first);
|
for(const auto& ip : erase_bans) {
|
||||||
else
|
this->client_connect_bans.erase(ip);
|
||||||
elm.second--;
|
}
|
||||||
|
|
||||||
|
if(system_clock::now() - seconds(5) < client_connect_last_decrease) {
|
||||||
|
this->client_connect_last_decrease = system_clock::now();
|
||||||
|
|
||||||
|
std::vector<std::string> erase_attempts{};
|
||||||
|
for(auto& elm : this->client_connect_count) {
|
||||||
|
if(elm.second == 0) {
|
||||||
|
erase_attempts.push_back(elm.first);
|
||||||
|
} else {
|
||||||
|
elm.second--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& ip : erase_bans) {
|
||||||
|
this->client_connect_count.erase(ip);
|
||||||
}
|
}
|
||||||
for(const auto& ip : qbanErase)
|
|
||||||
this->loginAttempts.erase(ip);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this->accept_event_deleted.time_since_epoch().count() != 0 && accept_event_deleted + seconds(5) < system_clock::now()) {
|
if(this->accept_event_deleted.time_since_epoch().count() != 0 && accept_event_deleted + seconds(5) < system_clock::now()) {
|
||||||
debugMessage(LOG_QUERY, "Readding accept event and try again if we have enough resources again.");
|
debugMessage(LOG_QUERY, "Readding accept event and try again if we have enough resources again.");
|
||||||
for(auto& binding : this->bindings)
|
for(auto& binding : this->bindings) {
|
||||||
event_add(binding->event_accept, nullptr);
|
event_add(binding->event_accept, nullptr);
|
||||||
|
}
|
||||||
accept_event_deleted = system_clock::time_point{};
|
accept_event_deleted = system_clock::time_point{};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryServer::tick_executor() {
|
||||||
|
bool tick_clients;
|
||||||
|
|
||||||
|
while(this->tick_active) {
|
||||||
|
std::unique_lock tick_lock{this->tick_mutex};
|
||||||
|
this->tick_notify.wait_until(tick_lock, this->tick_next_client_timestamp, [&]{
|
||||||
|
return !this->tick_active || !this->tick_pending_disconnects.empty() || !this->tick_pending_connection_close.empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto current_timestamp = std::chrono::system_clock::now();
|
||||||
|
if(current_timestamp > this->tick_next_client_timestamp) {
|
||||||
|
this->tick_next_client_timestamp = current_timestamp;
|
||||||
|
tick_clients = true;
|
||||||
|
} else {
|
||||||
|
tick_clients = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pending_disconnects = std::move(this->tick_pending_disconnects);
|
||||||
|
auto pending_closes = std::move(this->tick_pending_connection_close);
|
||||||
|
|
||||||
|
if(!this->tick_active) {
|
||||||
|
if(this->tick_pending_connection_close.empty() && this->tick_pending_connection_close.empty()) {
|
||||||
|
/* We're done with our work */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tick_lock.unlock();
|
||||||
|
|
||||||
|
if(tick_clients) {
|
||||||
|
this->tick_clients();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& pending_disconnect : pending_disconnects) {
|
||||||
|
auto client = pending_disconnect.lock();
|
||||||
|
if(!client) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->execute_query_disconnect(client, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& pending_close : pending_closes) {
|
||||||
|
auto client = pending_close.lock();
|
||||||
|
if(!client) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->execute_query_connection_close(client, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryServer::enqueue_query_disconnect(const std::shared_ptr<QueryClient> &client) {
|
||||||
|
std::lock_guard lock{this->tick_mutex};
|
||||||
|
if(!this->tick_active) {
|
||||||
|
logCritical(LOG_GENERAL, "Tried to close a query connection without an active query event loop.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->tick_pending_disconnects.push_back(client);
|
||||||
|
this->tick_notify.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryServer::enqueue_query_connection_close(const std::shared_ptr<QueryClient> &client) {
|
||||||
|
std::lock_guard lock{this->tick_mutex};
|
||||||
|
if(!this->tick_active) {
|
||||||
|
logCritical(LOG_GENERAL, "Tried to close a query connection without an active query event loop.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->tick_pending_connection_close.push_back(client);
|
||||||
|
this->tick_notify.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryServer::execute_query_disconnect(const std::shared_ptr<QueryClient> &client, bool shutdown_disconnect) {
|
||||||
|
{
|
||||||
|
std::lock_guard state_lock{client->state_lock};
|
||||||
|
if(client->state >= ConnectionState::DISCONNECTING) {
|
||||||
|
/* client will already be disconnected */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->state = ConnectionState::DISCONNECTING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!shutdown_disconnect) {
|
||||||
|
client->disconnect_from_virtual_server("");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard network_lock{client->network_mutex};
|
||||||
|
if(client->event_write) {
|
||||||
|
event_add(client->event_write, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryServer::execute_query_connection_close(const std::shared_ptr<QueryClient> &client, bool warn_unknown_client) {
|
||||||
|
{
|
||||||
|
std::lock_guard state_lock{client->state_lock};
|
||||||
|
if(client->state == ConnectionState::DISCONNECTED) {
|
||||||
|
/* client has already been disconnected */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->state = ConnectionState::DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->disconnect_from_virtual_server("");
|
||||||
|
client->execute_final_disconnect();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard client_lock{this->connected_clients_mutex};
|
||||||
|
auto index = std::find(this->connected_clients.begin(), this->connected_clients.end(), client);
|
||||||
|
if(index == this->connected_clients.end()) {
|
||||||
|
if(warn_unknown_client) {
|
||||||
|
logWarning(LOG_QUERY, "Closed the connection of an unknown/unregistered query.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->connected_clients.erase(index);
|
||||||
|
this->connected_client_disconnected_notify.notify_all();
|
||||||
|
}
|
||||||
}
|
}
|
@ -78,39 +78,50 @@ namespace ts {
|
|||||||
|
|
||||||
bool rename_query_account(const std::shared_ptr<QueryAccount>& /* account */, const std::string& /* new name */);
|
bool rename_query_account(const std::shared_ptr<QueryAccount>& /* account */, const std::string& /* new name */);
|
||||||
bool change_query_password(const std::shared_ptr<QueryAccount>& /* account */, const std::string& /* new password */);
|
bool change_query_password(const std::shared_ptr<QueryAccount>& /* account */, const std::string& /* new password */);
|
||||||
|
|
||||||
threads::ThreadPool* executePool() { return this->_executePool; }
|
|
||||||
private:
|
private:
|
||||||
sql::SqlManager* sql;
|
sql::SqlManager* sql;
|
||||||
|
|
||||||
bool active = false;
|
bool active{false};
|
||||||
std::deque<std::shared_ptr<Binding>> bindings;
|
std::deque<std::shared_ptr<Binding>> bindings;
|
||||||
std::vector<threads::Thread*> threads;
|
|
||||||
|
|
||||||
std::mutex server_reserve_fd_lock;
|
std::mutex server_reserve_fd_lock{};
|
||||||
int server_reserve_fd = -1; /* -1 = unset | 0 = in use | > 0 ready to use */
|
int server_reserve_fd{-1}; /* -1 = unset | 0 = in use | > 0 ready to use */
|
||||||
|
|
||||||
std::unique_ptr<IpListManager> ip_whitelist;
|
std::unique_ptr<IpListManager> ip_whitelist;
|
||||||
std::unique_ptr<IpListManager> ip_blacklist;
|
std::unique_ptr<IpListManager> ip_blacklist;
|
||||||
|
|
||||||
//IO stuff
|
//IO stuff
|
||||||
event_base* eventLoop = nullptr;
|
event_base* event_io_loop{nullptr};
|
||||||
|
std::thread event_io_thread{};
|
||||||
|
|
||||||
std::chrono::system_clock::time_point accept_event_deleted;
|
std::chrono::system_clock::time_point accept_event_deleted;
|
||||||
|
|
||||||
threads::ThreadPool* _executePool = nullptr;
|
std::mutex connected_clients_mutex{};
|
||||||
|
std::deque<std::shared_ptr<QueryClient>> connected_clients{};
|
||||||
|
std::condition_variable connected_client_disconnected_notify{};
|
||||||
|
|
||||||
std::mutex connected_clients_lock;
|
std::mutex client_connect_mutex{};
|
||||||
std::deque<std::shared_ptr<QueryClient>> connectedClients;
|
std::chrono::system_clock::time_point client_connect_last_decrease{};
|
||||||
|
std::map<std::string, uint32_t> client_connect_count{};
|
||||||
|
std::map<std::string, std::chrono::system_clock::time_point> client_connect_bans{};
|
||||||
|
|
||||||
threads::Mutex loginLock;
|
bool tick_active{false};
|
||||||
std::chrono::system_clock::time_point lastDecrease;
|
std::mutex tick_mutex{};
|
||||||
std::map<std::string, uint32_t> loginAttempts;
|
std::thread tick_thread{};
|
||||||
std::map<std::string, std::chrono::system_clock::time_point> queryBann;
|
std::condition_variable tick_notify{};
|
||||||
|
std::deque<std::weak_ptr<QueryClient>> tick_pending_disconnects{};
|
||||||
|
std::deque<std::weak_ptr<QueryClient>> tick_pending_connection_close{};
|
||||||
|
std::chrono::system_clock::time_point tick_next_client_timestamp{};
|
||||||
|
|
||||||
threads::Thread* ioThread = nullptr;
|
void on_client_receive(int server_file_descriptor, short ev, void *arg);
|
||||||
threads::SchedulingTask tickingId = nullptr;
|
void tick_clients();
|
||||||
void on_client_receive(int fd, short ev, void *arg);
|
void tick_executor();
|
||||||
void tick();
|
|
||||||
|
void enqueue_query_disconnect(const std::shared_ptr<QueryClient>& /* client */);
|
||||||
|
void execute_query_disconnect(const std::shared_ptr<QueryClient>& /* client */, bool /* shutdown disconnect */);
|
||||||
|
|
||||||
|
void enqueue_query_connection_close(const std::shared_ptr<QueryClient>& /* client */);
|
||||||
|
void execute_query_connection_close(const std::shared_ptr<QueryClient>& /* client */, bool /* warn on unknown client */);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -113,7 +113,7 @@ void VoiceIOManager::shutdownGlobally() {
|
|||||||
//TODO also reduce thread pool!
|
//TODO also reduce thread pool!
|
||||||
void VoiceIOManager::adjustExecutors(size_t size) {
|
void VoiceIOManager::adjustExecutors(size_t size) {
|
||||||
lock_guard<mutex> l(this->executorLock);
|
lock_guard<mutex> l(this->executorLock);
|
||||||
size_t targetThreads = size * config::threads::voice::execute_per_server;
|
size_t targetThreads = size * config::threads::voice::events_per_server;
|
||||||
if(targetThreads > config::threads::voice::io_limit)
|
if(targetThreads > config::threads::voice::io_limit)
|
||||||
targetThreads = config::threads::voice::io_limit;
|
targetThreads = config::threads::voice::io_limit;
|
||||||
if(targetThreads < config::threads::voice::io_min)
|
if(targetThreads < config::threads::voice::io_min)
|
||||||
|
@ -120,17 +120,6 @@ void VoiceServer::triggerWrite(const std::shared_ptr<VoiceClient>& client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceServer::schedule_command_handling(const ts::server::VoiceClient *client) {
|
|
||||||
auto vmanager = serverInstance->getVoiceServerManager();
|
|
||||||
if(!vmanager)
|
|
||||||
return;
|
|
||||||
auto evloop = vmanager->get_executor_loop();
|
|
||||||
if(!evloop)
|
|
||||||
return;
|
|
||||||
|
|
||||||
evloop->schedule(client->event_handle_packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VoiceServer::tickHandshakingClients() {
|
void VoiceServer::tickHandshakingClients() {
|
||||||
this->pow_handler->execute_tick();
|
this->pow_handler->execute_tick();
|
||||||
|
|
||||||
@ -141,7 +130,7 @@ void VoiceServer::tickHandshakingClients() {
|
|||||||
}
|
}
|
||||||
for(const auto& client : connections)
|
for(const auto& client : connections)
|
||||||
if(client->state == ConnectionState::INIT_HIGH || client->state == ConnectionState::INIT_LOW)
|
if(client->state == ConnectionState::INIT_HIGH || client->state == ConnectionState::INIT_LOW)
|
||||||
client->tick(system_clock::now());
|
client->tick_server(system_clock::now());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next) {
|
void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next) {
|
||||||
@ -317,7 +306,7 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) {
|
|||||||
auto new_address = net::to_string(remote_address);
|
auto new_address = net::to_string(remote_address);
|
||||||
|
|
||||||
auto command = "dummy_ipchange old_ip=" + old_address + " new_ip=" + new_address;
|
auto command = "dummy_ipchange old_ip=" + old_address + " new_ip=" + new_address;
|
||||||
client->server_command_executor().force_insert_command(pipes::buffer_view{command.data(), command.length()});
|
client->server_command_queue()->enqueue_command_string(command);
|
||||||
memcpy(&client->remote_address, &remote_address, sizeof(remote_address));
|
memcpy(&client->remote_address, &remote_address, sizeof(remote_address));
|
||||||
udp::DatagramPacket::extract_info(message, client->connection->remote_address_info_);
|
udp::DatagramPacket::extract_info(message, client->connection->remote_address_info_);
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,6 @@ namespace ts {
|
|||||||
std::deque<std::shared_ptr<VoiceClient>> activeConnections;
|
std::deque<std::shared_ptr<VoiceClient>> activeConnections;
|
||||||
public:
|
public:
|
||||||
void triggerWrite(const std::shared_ptr<VoiceClient> &);
|
void triggerWrite(const std::shared_ptr<VoiceClient> &);
|
||||||
void schedule_command_handling(VoiceClient const *client);
|
|
||||||
|
|
||||||
void tickHandshakingClients();
|
void tickHandshakingClients();
|
||||||
void execute_resend(const std::chrono::system_clock::time_point& /* now */, std::chrono::system_clock::time_point& /* next resend */);
|
void execute_resend(const std::chrono::system_clock::time_point& /* now */, std::chrono::system_clock::time_point& /* next resend */);
|
||||||
|
@ -73,7 +73,6 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
|
|||||||
threads::MutexLock l(this->instanceLock);
|
threads::MutexLock l(this->instanceLock);
|
||||||
this->instances.push_back(server);
|
this->instances.push_back(server);
|
||||||
}
|
}
|
||||||
this->adjust_executor_threads();
|
|
||||||
|
|
||||||
if(!server->start(error)) {
|
if(!server->start(error)) {
|
||||||
logWarning(server->getServerId(), "Failed to auto start server after snapshot deployment: {}", error);
|
logWarning(server->getServerId(), "Failed to auto start server after snapshot deployment: {}", error);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <ThreadPool/ThreadHelper.h>
|
||||||
#include "src/VirtualServer.h"
|
#include "src/VirtualServer.h"
|
||||||
#include "log/LogUtils.h"
|
#include "log/LogUtils.h"
|
||||||
#include "TeamSpeakWebClient.h"
|
#include "TeamSpeakWebClient.h"
|
||||||
@ -13,33 +14,29 @@ using namespace ts::weblist;
|
|||||||
|
|
||||||
WebListManager::WebListManager() {
|
WebListManager::WebListManager() {
|
||||||
this->event_base = event_base_new();
|
this->event_base = event_base_new();
|
||||||
|
this->event_base_dispatch = std::thread([&]{
|
||||||
this->event_base_dispatch = std::thread([&](){
|
|
||||||
system_clock::time_point start;
|
|
||||||
while(this->event_base) {
|
while(this->event_base) {
|
||||||
::event_base_dispatch(this->event_base);
|
::event_base_loop(this->event_base, EVLOOP_NO_EXIT_ON_EMPTY);
|
||||||
if(event_base_got_break(this->event_base)) return;
|
|
||||||
|
|
||||||
{
|
if(this->event_base) {
|
||||||
std::unique_lock entry_lock{this->entry_lock};
|
logWarning(LOG_GENERAL, "WebList report event loop exited without terminating. Rescheduling....");
|
||||||
this->entry_cv.wait(entry_lock);
|
std::this_thread::sleep_for(std::chrono::seconds{1});
|
||||||
if(!this->event_base) return;
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WebListManager::~WebListManager() {
|
WebListManager::~WebListManager() {
|
||||||
auto base = this->event_base;
|
auto event_base_ = std::exchange(this->event_base, nullptr);
|
||||||
{
|
if(event_base_) {
|
||||||
std::unique_lock entry_lock{this->entry_lock};
|
event_base_loopbreak(event_base_);
|
||||||
this->event_base = nullptr;
|
|
||||||
this->entry_cv.notify_all();
|
|
||||||
entry_lock.unlock();
|
|
||||||
}
|
}
|
||||||
event_base_loopbreak(base);
|
|
||||||
this->event_base_dispatch.join();
|
|
||||||
|
|
||||||
event_base_free(base);
|
threads::save_join(this->event_base_dispatch, true);
|
||||||
|
if(event_base_) {
|
||||||
|
event_base_free(event_base_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebListManager::enable_report(const std::shared_ptr<ts::server::VirtualServer> &server) {
|
void WebListManager::enable_report(const std::shared_ptr<ts::server::VirtualServer> &server) {
|
||||||
@ -55,8 +52,6 @@ void WebListManager::enable_report(const std::shared_ptr<ts::server::VirtualServ
|
|||||||
entry->scheduled_request = system_clock::now();
|
entry->scheduled_request = system_clock::now();
|
||||||
entry->fail_count = 0;
|
entry->fail_count = 0;
|
||||||
this->entries.push_back(entry);
|
this->entries.push_back(entry);
|
||||||
|
|
||||||
this->entry_cv.notify_all();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +77,6 @@ void WebListManager::disable_report(const std::shared_ptr<ts::server::VirtualSer
|
|||||||
if(it != this->entries.end())
|
if(it != this->entries.end())
|
||||||
this->entries.erase(it);
|
this->entries.erase(it);
|
||||||
if(copied_entry->current_request) {
|
if(copied_entry->current_request) {
|
||||||
this->entry_cv.notify_all();
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
copied_entry->current_request->abort_sync();
|
copied_entry->current_request->abort_sync();
|
||||||
}
|
}
|
||||||
@ -142,12 +136,6 @@ void WebListManager::tick() {
|
|||||||
auto ref_request = entry->current_request;
|
auto ref_request = entry->current_request;
|
||||||
ref_request->report();
|
ref_request->report();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
/* lets run the event loop */
|
|
||||||
unique_lock lock(this->entry_lock);
|
|
||||||
this->entry_cv.notify_all();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -43,7 +43,6 @@ namespace ts {
|
|||||||
std::thread event_base_dispatch{};
|
std::thread event_base_dispatch{};
|
||||||
|
|
||||||
std::mutex entry_lock{};
|
std::mutex entry_lock{};
|
||||||
std::condition_variable entry_cv{};
|
|
||||||
std::deque<std::shared_ptr<Entry>> entries{};
|
std::deque<std::shared_ptr<Entry>> entries{};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
2
shared
2
shared
@ -1 +1 @@
|
|||||||
Subproject commit eb77a7fefb454469ed4f0b495959923048a2b768
|
Subproject commit 0cd49a6a2b88c171f8fcf73dc0cab9da44338b74
|
Loading…
Reference in New Issue
Block a user