diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index c3337fc..c392bc0 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -47,8 +47,7 @@ set(SERVER_SOURCE_FILES src/client/voice/VoiceClient.cpp src/client/voice/VoiceClientHandschake.cpp src/client/voice/VoiceClientCommandHandler.cpp - src/client/voice/VoiceClientPacketHandler.cpp - src/client/voice/VoiceClientView.cpp + src/client/voice/VoiceClientConnectionPacketHandler.cpp src/client/voice/PacketStatistics.cpp src/TS3ServerClientManager.cpp src/VirtualServer.cpp @@ -150,6 +149,9 @@ set(SERVER_SOURCE_FILES src/client/voice/PacketDecoder.cpp src/client/voice/PacketEncoder.cpp + src/client/voice/ServerCommandExecutor.cpp + src/client/voice/PingHandler.cpp + src/client/voice/CryptSetupHandler.cpp ) if (COMPILE_WEB_CLIENT) diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index 01827f1..a35413d 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -279,6 +279,7 @@ namespace ts { inline int voice_encryption_mode() { return this->_voice_encryption_mode; } inline std::shared_ptr conversation_manager() { return this->_conversation_manager; } + inline auto& get_channel_tree_lock() { return this->channel_tree_lock; } void update_channel_from_permissions(const std::shared_ptr& /* channel */, const std::shared_ptr& /* issuer */); protected: diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 3465086..513bae6 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -603,42 +603,7 @@ bool ConnectedClient::notifyError(const command_result& result, const std::strin } void ConnectedClient::writeCommandResult(ts::command_builder &cmd_builder, const command_result &result, const std::string& errorCodeKey) { - switch(result.type()) { - case command_result_type::error: - write_command_result_error(cmd_builder.bulk(0), result, errorCodeKey); - break; - case command_result_type::detailed: - write_command_result_detailed(cmd_builder.bulk(0), result, errorCodeKey); - break; - - case command_result_type::bulked: { - auto bulks = result.bulks(); - cmd_builder.reserve_bulks(bulks->size()); - for(size_t index{0}; index < bulks->size(); index++) { - auto& entry = bulks->at(index); - switch (entry.type()) { - case command_result_type::error: - write_command_result_error(cmd_builder.bulk(index), entry, errorCodeKey); - break; - case command_result_type::detailed: - write_command_result_detailed(cmd_builder.bulk(index), entry, errorCodeKey); - break; - case command_result_type::bulked: - assert(false); - break; - } - } - if(bulks->empty()) { - logWarning(this->getServerId(), "{} Trying to send empty error bulk.", CLIENT_STR_LOG_PREFIX_(this)); - cmd_builder.put_unchecked(0, errorCodeKey, (uint32_t) error::ok); - cmd_builder.put_unchecked(0, "msg", findError(error::ok).message); - } - break; - } - default: - assert(false); - break; - } + result.build_error_response(cmd_builder, errorCodeKey); } inline std::shared_ptr pop_view_entry(std::deque>& pool, ChannelId id) { diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index b88933f..0745715 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -2135,6 +2135,9 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { }; command_builder result{this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyserverlog" : ""}; + result.put_unchecked(0, "last_pos", 0); + result.put_unchecked(0, "file_size", 0); + size_t index{0}; if(lagacy) { for(const auto& message : log_output) { diff --git a/server/src/client/voice/CryptSetupHandler.cpp b/server/src/client/voice/CryptSetupHandler.cpp new file mode 100644 index 0000000..b9461a6 --- /dev/null +++ b/server/src/client/voice/CryptSetupHandler.cpp @@ -0,0 +1,273 @@ +// +// Created by WolverinDEV on 29/07/2020. +// + +#include +#include +#include +#include +#include +#include "CryptSetupHandler.h" +#include "./VoiceClientConnection.h" + +using namespace ts; +using namespace ts::connection; +using namespace ts::server::server::udp; + +inline void generate_random(uint8_t *destination, size_t length) { + while(length-- > 0) + *(destination++) = (uint8_t) rand(); +} + +CryptSetupHandler::CryptSetupHandler(VoiceClientConnection *connection) : connection{connection} { } + +CryptSetupHandler::CommandHandleResult CryptSetupHandler::handle_command(const std::string_view &payload) { + std::variant(CryptSetupHandler::*command_handler)(const ts::command_parser&) = nullptr; + + if(payload.starts_with("clientinitiv")) + command_handler = &CryptSetupHandler::handleCommandClientInitIv; + else if(payload.starts_with("clientek")) + command_handler = &CryptSetupHandler::handleCommandClientEk; + else if(payload.starts_with("clientinit")) + command_handler = &CryptSetupHandler::handleCommandClientInit; + + if(!command_handler) + return CommandHandleResult::PASS_THROUGH; + + this->last_command_ = std::chrono::system_clock::now(); + + ts::command_parser parser{payload}; + try { + std::unique_lock cmd_lock{this->command_lock}; + auto result = (this->*command_handler)(parser); + + CommandHandleResult handle_result; + if(std::holds_alternative(result)) { + handle_result = std::get(result); + } else { + auto cmd_result = std::move(std::get(result)); + + ts::command_builder notify{"error"}; + cmd_result.build_error_response(notify, "id"); + + if(parser.has_key("return_code")) + notify.put_unchecked(0, "return_code", parser.value("return_code")); + + this->connection->send_command(notify.build(), false, nullptr); + + handle_result = cmd_result.has_error() ? CommandHandleResult::CLOSE_CONNECTION : CommandHandleResult::CONSUME_COMMAND; + cmd_result.release_data(); + } + return handle_result; + } catch (std::exception& ex) { + debugMessage(this->connection->virtual_server_id(), "{} Failed to handle connection command: {}. Closing connection.", this->connection->log_prefix(), ex.what()); + return CommandHandleResult::CLOSE_CONNECTION; + } +} + +CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(const ts::command_parser &cmd) { + auto client = this->connection->getCurrentClient(); + assert(client); + + std::unique_lock state_lock{client->state_lock}; + if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */ + auto lastPingResponse = this->connection->ping_handler().last_ping_response(); + if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) { + logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt", + this->connection->log_prefix(), + duration_cast(std::chrono::system_clock::now() - lastPingResponse).count() + ); + return ts::command_result{error::ok}; + } else if(!config::voice::allow_session_reinitialize) { + logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings", + this->connection->log_prefix(), + duration_cast(std::chrono::system_clock::now() - lastPingResponse).count() + ); + return ts::command_result{error::ok}; + } + logMessage(this->connection->virtual_server_id(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt", + this->connection->log_prefix(), + duration_cast(std::chrono::system_clock::now() - lastPingResponse).count() + ); + + state_lock.unlock(); + + { + std::unique_lock server_channel_lock(client->server->get_channel_tree_lock()); /* we cant get moved if this is locked! */ + if(client->currentChannel) + client->server->client_move(client->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock); + } + + client->finalDisconnect(); + state_lock.lock(); + } else if(client->state >= ConnectionState::DISCONNECTING) { + state_lock.unlock(); + std::shared_lock disconnect_finish{client->finalDisconnectLock}; /* await until the last disconnect has been processed */ + state_lock.lock(); + client->state = ConnectionState::INIT_HIGH; + } else if(client->state == ConnectionState::INIT_HIGH) { + logTrace(client->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX_(client)); + return command_result{error::ok}; + } else { + client->state = ConnectionState::INIT_HIGH; + } + state_lock.unlock(); + + this->connection->reset(); + this->connection->packet_decoder().register_initiv_packet(); + this->connection->packet_statistics().reset_offsets(); + + bool use_teaspeak = cmd.has_switch("teaspeak"); + if(use_teaspeak ? !config::server::clients::teaspeak : !config::server::clients::teamspeak) + return command_result{error::client_type_is_not_allowed}; + + if(use_teaspeak) { + debugMessage(this->connection->virtual_server_id(), "{} Client using TeaSpeak.", this->connection->log_prefix()); + client->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEASPEAK; + } + + /* normal TeamSpeak handling */ + this->seed_client = base64::decode(cmd.value("alpha")); + if(this->seed_client.length() != 10) + return ts::command_result{error::parameter_invalid, "alpha"}; + + std::string clientOmega = base64::decode(cmd.value("omega")); //The identity public key + std::string ip = cmd.value("ip"); + bool ot = cmd.has_key("ot") ? cmd.value_as("ot") : false; + + { + this->remote_key = std::shared_ptr(new ecc_key{}, [](ecc_key* key){ + if(!key) return; + ecc_free(key); + delete key; + }); + + auto state = ecc_import((const unsigned char *) clientOmega.data(), clientOmega.length(), &*this->remote_key); + if(state != CRYPT_OK) { + this->remote_key = nullptr; + return ts::command_result{error::client_could_not_validate_identity}; + } + + client->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(clientOmega)); + } + + this->new_protocol = !use_teaspeak && ot && config::experimental_31 && (this->client_protocol_time_ >= 173265950ULL || this->client_protocol_time_ == (uint32_t) 5680278000ULL); + this->new_protocol = true; + + { + size_t server_seed_length = this->new_protocol ? 54 : 10; + + char beta[server_seed_length]; + generate_random((uint8_t *) beta, server_seed_length); + + this->seed_server = std::string{beta, server_seed_length}; + } + + if(this->new_protocol) { + //Pre setup + //Generate chain + debugMessage(this->connection->virtual_server_id(), "{} Got client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_); + + this->chain_data = serverInstance->getTeamSpeakLicense()->license(); + this->chain_data->chain->addEphemeralEntry(); + + auto crypto_chain = this->chain_data->chain->exportChain(); + auto crypto_chain_hash = digest::sha256(crypto_chain); + + size_t sign_buffer_size{128}; + char sign_buffer[sign_buffer_size]; + + prng_state prng_state{}; + memset(&prng_state, 0, sizeof(prng_state)); + + auto sign_result = ecc_sign_hash( + (u_char*) crypto_chain_hash.data(), crypto_chain_hash.length(), + (u_char*) sign_buffer, &sign_buffer_size, + &prng_state, find_prng("sprng"), + client->getServer()->serverKey()); + + if(sign_result != CRYPT_OK) { + return ts::command_result{error::vs_critical, "failed to sign crypto chain"}; + } + + ts::command_builder answer{"initivexpand2"}; + answer.put_unchecked(0, "time", std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); + answer.put_unchecked(0, "l", base64::encode(crypto_chain)); + answer.put_unchecked(0, "beta", base64::encode(this->seed_server)); + answer.put_unchecked(0, "omega", client->getServer()->publicServerKey()); + answer.put_unchecked(0, "proof", base64::encode((const char*) sign_buffer, sign_buffer_size)); + answer.put_unchecked(0, "tvd", ""); + answer.put_unchecked(0, "root", base64::encode((char*) this->chain_data->public_key, 32)); + answer.put_unchecked(0, "ot", "1"); + + this->connection->send_command(answer.build(), false, nullptr); + client->handshake.state = SpeakingClient::HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */ + + return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */ + } else { + debugMessage(this->connection->virtual_server_id(), "{} Got non client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_, this->client_protocol_time_); + + auto server_public_key = client->getServer()->publicServerKey(); + if(server_public_key.empty()) { + return ts::command_result{error::vs_critical, "failed to export server public key"}; + } + + { + ts::command_builder answer{"initivexpand"}; + answer.put_unchecked(0, "alpha", base64::encode(this->seed_client)); + answer.put_unchecked(0, "beta", base64::encode(this->seed_server)); + answer.put_unchecked(0, "omega", server_public_key); + + if(use_teaspeak) { + answer.put_unchecked(0, "teaspeak", "1"); + client->handshake.state = SpeakingClient::HandshakeState::BEGIN; /* we need to start the handshake */ + } else { + client->handshake.state = SpeakingClient::HandshakeState::SUCCEEDED; /* we're using the provided identity as identity */ + } + + this->connection->send_command(answer.build(), false, nullptr); + this->connection->packet_encoder().encrypt_pending_packets(); + } + + std::string error; + if(!this->connection->getCryptHandler()->setupSharedSecret(this->seed_client, this->seed_server, &*this->remote_key, client->getServer()->serverKey(), error)){ + logError(this->connection->virtual_server_id(), "{} Failed to calculate shared secret {}. Dropping client.", + this->connection->log_prefix(), error); + return ts::command_result{error::vs_critical}; + } + + return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */ + } +} + +CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientEk(const ts::command_parser &cmd) { + debugMessage(this->connection->virtual_server_id(), "{} Got client ek!", this->connection->log_prefix()); + + auto client_key = base64::decode(cmd.value("ek")); + auto private_key = this->chain_data->chain->generatePrivateKey(this->chain_data->root_key, this->chain_data->root_index); + + this->connection->getCryptHandler()->setupSharedSecretNew(this->seed_client, this->seed_server, (char*) private_key.data(), client_key.data()); + this->connection->packet_encoder().acknowledge_manager().reset(); + + { + char buffer[2]; + le2be16(1, buffer); + + auto pflags = protocol::PacketFlag::NewProtocol; + this->connection->send_packet(protocol::PacketType::ACK, (protocol::PacketFlag::PacketFlag) pflags, buffer, 2); + //Send the encrypted acknowledge (most the times the second packet; If not we're going into the resend loop) + //We cant use the send_packet_acknowledge function since it sends the acknowledge unencrypted + } + + return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */ +} + +CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInit(const ts::command_parser &) { + /* the client must have received everything */ + this->connection->packet_encoder().acknowledge_manager().reset(); + this->seed_client.clear(); + this->seed_server.clear(); + this->chain_data = nullptr; + + return CommandHandleResult::PASS_THROUGH; +} \ No newline at end of file diff --git a/server/src/client/voice/CryptSetupHandler.h b/server/src/client/voice/CryptSetupHandler.h new file mode 100644 index 0000000..8231461 --- /dev/null +++ b/server/src/client/voice/CryptSetupHandler.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ts::connection { + class VoiceClientConnection; +} + +namespace ts::server::server::udp { + class CryptSetupHandler { + public: + enum struct CommandHandleResult { + CONSUME_COMMAND, + CLOSE_CONNECTION, + PASS_THROUGH + }; + using CommandResult = std::variant; + + explicit CryptSetupHandler(connection::VoiceClientConnection*); + + [[nodiscard]] inline const auto& identity_key() const { return this->remote_key; } + + void set_client_protocol_time(uint32_t time) { this->client_protocol_time_ = time; } + [[nodiscard]] inline auto client_protocol_time() const { return this->client_protocol_time_; } + + [[nodiscard]] inline auto last_handled_command() const { return this->last_command_; } + + /* Attention this method gets from the voice IO thread. It's not thread save! */ + [[nodiscard]] CommandHandleResult handle_command(const std::string_view& /* command */); + private: + connection::VoiceClientConnection* connection; + + std::chrono::system_clock::time_point last_command_{}; + + std::mutex command_lock{}; + + bool new_protocol{false}; + uint32_t client_protocol_time_{0}; + + std::string seed_client{}; /* alpha */ + std::string seed_server{}; /* beta */ + + std::shared_ptr chain_data{}; + std::shared_ptr remote_key{}; + + CommandResult handleCommandClientInitIv(const ts::command_parser& /* command */); + CommandResult handleCommandClientEk(const ts::command_parser& /* command */); + + CommandResult handleCommandClientInit(const ts::command_parser& /* command */); + }; +} \ No newline at end of file diff --git a/server/src/client/voice/PacketDecoder.h b/server/src/client/voice/PacketDecoder.h index 522315d..0fbad6a 100644 --- a/server/src/client/voice/PacketDecoder.h +++ b/server/src/client/voice/PacketDecoder.h @@ -17,7 +17,7 @@ namespace ts::stats { class ConnectionStatistics; } -namespace ts::server::server::udp { + namespace ts::server::server::udp { struct CommandFragment { uint16_t packet_id{0}; uint16_t packet_generation{0}; @@ -116,7 +116,7 @@ namespace ts::server::server::udp { callback_send_acknowledge_t callback_send_acknowledge{[](auto, auto, auto){}}; /* needs to be valid all the time! */ private: connection::CryptHandler* crypt_handler_{nullptr}; - + spin_mutex incoming_generation_estimator_lock{}; std::array incoming_generation_estimators{}; /* implementation is thread save */ diff --git a/server/src/client/voice/PacketEncoder.cpp b/server/src/client/voice/PacketEncoder.cpp index d5ceac2..bf47ae6 100644 --- a/server/src/client/voice/PacketEncoder.cpp +++ b/server/src/client/voice/PacketEncoder.cpp @@ -77,6 +77,23 @@ void PacketEncoder::send_packet(ts::protocol::OutgoingServerPacket *packet) { this->callback_connection_stats(this->callback_data, category, packet->packet_length() + 96); /* 96 for the UDP packet overhead */ } +void PacketEncoder::send_packet(protocol::PacketType type, protocol::PacketFlag::PacketFlags flag, const void *payload, size_t payload_size) { + auto packet = protocol::allocate_outgoing_packet(payload_size); + + packet->type_and_flags = (uint8_t) type | (uint8_t) flag; + memcpy(packet->payload, payload, payload_size); + + this->send_packet(packet); +} + +void PacketEncoder::send_packet_acknowledge(uint16_t pid, bool low) { + char buffer[2]; + le2be16(pid, buffer); + + auto pflags = PacketFlag::Unencrypted | PacketFlag::NewProtocol; + this->send_packet(low ? protocol::PacketType::ACK_LOW : protocol::PacketType::ACK, (PacketFlag::PacketFlag) pflags, buffer, 2); +} + #define MAX_COMMAND_PACKET_PAYLOAD_LENGTH (487) void PacketEncoder::send_command(const std::string_view &command, bool low, std::unique_ptr> ack_listener) { diff --git a/server/src/client/voice/PacketEncoder.h b/server/src/client/voice/PacketEncoder.h index d057060..4ab6491 100644 --- a/server/src/client/voice/PacketEncoder.h +++ b/server/src/client/voice/PacketEncoder.h @@ -43,8 +43,11 @@ namespace ts::server::server::udp { void reset(); void send_packet(protocol::OutgoingServerPacket* /* packet */); /* will claim ownership */ + void send_packet(protocol::PacketType /* type */, protocol::PacketFlag::PacketFlags /* flags */, const void* /* payload */, size_t /* payload length */); void send_command(const std::string_view& /* build command command */, bool /* command low */, std::unique_ptr> /* acknowledge listener */); + void send_packet_acknowledge(uint16_t /* packet id */, bool /* acknowledge low */); + void execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next); void encrypt_pending_packets(); diff --git a/server/src/client/voice/PingHandler.cpp b/server/src/client/voice/PingHandler.cpp new file mode 100644 index 0000000..fc60bcd --- /dev/null +++ b/server/src/client/voice/PingHandler.cpp @@ -0,0 +1,69 @@ +// +// Created by WolverinDEV on 11/03/2020. +// + +#include "PingHandler.h" + +using namespace ts::server::server::udp; + +void PingHandler::reset() { + this->last_ping_id = 0; + this->current_ping_ = std::chrono::milliseconds{0}; + + this->last_recovery_command_send = std::chrono::system_clock::time_point{}; + this->last_command_acknowledge_ = std::chrono::system_clock::time_point{}; + + this->last_response_ = std::chrono::system_clock::time_point{}; + this->last_request_ = std::chrono::system_clock::time_point{}; +} + +void PingHandler::received_pong(uint16_t ping_id) { + if(this->last_ping_id != ping_id) return; + + auto now = std::chrono::system_clock::now(); + this->current_ping_ = std::chrono::floor(this->last_request_ - now); + + this->last_response_ = now; + this->last_command_acknowledge_ = now; /* That's here for purpose!*/ +} + +void PingHandler::received_command_acknowledged() { + this->last_command_acknowledge_ = std::chrono::system_clock::now(); +} + +void PingHandler::tick(const std::chrono::system_clock::time_point& now) { + if(this->last_request_ + PingHandler::kPingRequestInterval < now) + this->send_ping_request(); /* may update last_response_ */ + + if(this->last_response_ + PingHandler::kPingTimeout < now) { + if(this->last_recovery_command_send + PingHandler::kRecoveryRequestInterval < now) + this->send_recovery_request(); + + if(this->last_command_acknowledge_ + PingHandler::kRecoveryTimeout < now) { + if(auto callback{this->callback_time_outed}; callback) + callback(this->callback_argument); + } + } +} + +void PingHandler::send_ping_request() { + auto now = std::chrono::system_clock::now(); + if(this->last_response_.time_since_epoch().count() == 0) + this->last_response_ = now; + + this->last_request_ = now; + + if(auto callback{this->callback_send_ping}; callback) + callback(this->callback_argument, this->last_ping_id); +} + +void PingHandler::send_recovery_request() { + auto now = std::chrono::system_clock::now(); + if(this->last_command_acknowledge_.time_since_epoch().count() == 0) + this->last_command_acknowledge_ = now; + + this->last_recovery_command_send = now; + + if(auto callback{this->callback_send_recovery_command}; callback) + callback(this->callback_argument); +} diff --git a/server/src/client/voice/PingHandler.h b/server/src/client/voice/PingHandler.h new file mode 100644 index 0000000..a43223e --- /dev/null +++ b/server/src/client/voice/PingHandler.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +namespace ts::server::server::udp { + class PingHandler { + public: + typedef void(*callback_time_outed_t)(void* /* cb data */); + typedef void(*callback_send_ping_t)(void* /* cb data */, uint16_t& /* ping id */); + typedef void(*callback_send_recovery_command_t)(void* /* cb data */); + + void reset(); + + void tick(const std::chrono::system_clock::time_point&); + void received_pong(uint16_t /* ping id */); + void received_command_acknowledged(); + + [[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; } + [[nodiscard]] inline std::chrono::system_clock::time_point last_ping_response() const { return this->last_response_; } + + void* callback_argument{nullptr}; + callback_send_ping_t callback_send_ping{nullptr}; + callback_send_recovery_command_t callback_send_recovery_command{nullptr}; + callback_time_outed_t callback_time_outed{nullptr}; + private: + constexpr static std::chrono::milliseconds kPingRequestInterval{2500}; + constexpr static std::chrono::milliseconds kPingTimeout{15 * 1000}; + + constexpr static std::chrono::milliseconds kRecoveryRequestInterval{1000}; + constexpr static std::chrono::milliseconds kRecoveryTimeout{15 * 1000}; + + std::chrono::milliseconds current_ping_{0}; + + uint16_t last_ping_id{0}; + std::chrono::system_clock::time_point last_response_{}; + std::chrono::system_clock::time_point last_request_{}; + + std::chrono::system_clock::time_point last_command_acknowledge_{}; + std::chrono::system_clock::time_point last_recovery_command_send{}; + + void send_ping_request(); + void send_recovery_request(); + }; +} diff --git a/server/src/client/voice/ServerCommandExecutor.cpp b/server/src/client/voice/ServerCommandExecutor.cpp new file mode 100644 index 0000000..f2ab00d --- /dev/null +++ b/server/src/client/voice/ServerCommandExecutor.cpp @@ -0,0 +1,97 @@ +// +// 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{false}; + { + 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 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(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); +} \ No newline at end of file diff --git a/server/src/client/voice/ServerCommandExecutor.h b/server/src/client/voice/ServerCommandExecutor.h new file mode 100644 index 0000000..d3bf63b --- /dev/null +++ b/server/src/client/voice/ServerCommandExecutor.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +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; +} \ No newline at end of file diff --git a/server/src/client/voice/VoiceClient.cpp b/server/src/client/voice/VoiceClient.cpp index 4a947aa..a8a053b 100644 --- a/server/src/client/voice/VoiceClient.cpp +++ b/server/src/client/voice/VoiceClient.cpp @@ -57,54 +57,22 @@ void VoiceClient::sendCommand0(const std::string_view& cmd, bool low, std::uniqu #endif } -void VoiceClient::sendAcknowledge(uint16_t packetId, bool low) { - char buffer[2]; - le2be16(packetId, buffer); - - auto pflags = PacketFlag::Unencrypted | PacketFlag::NewProtocol; - this->connection->send_packet(low ? protocol::PacketType::ACK_LOW : protocol::PacketType::ACK, (PacketFlag::PacketFlag) pflags, buffer, 2); -#ifdef PKT_LOG_ACK - logTrace(this->getServerId(), "{}[Acknowledge][Server -> Client] Sending acknowledge for {}", CLIENT_STR_LOG_PREFIX, packetId); -#endif -} - void VoiceClient::tick(const std::chrono::system_clock::time_point &time) { SpeakingClient::tick(time); { ALARM_TIMER(A1, "VoiceClient::tick", milliseconds(3)); if(this->state == ConnectionState::CONNECTED) { - if(this->lastPingRequest > this->lastPingResponse) { //Client is behind :) - if(this->lastPingRequest - this->lastPingResponse > chrono::seconds(20)) { - debugMessage(this->getServerId(), "{} Got a ping timeout. (Last successful ping: {}ms ago. Last request {}ms. Last response {}ms). Trying to recover via command acknowledge.", - CLIENT_STR_LOG_PREFIX, - duration_cast(this->lastPingRequest - this->lastPingResponse).count(), - duration_cast(time - this->lastPingRequest).count(), - duration_cast(time - this->lastPingResponse).count()); - - bool force; - this->request_connection_info(nullptr, force); - this->lastPingResponse = system_clock::now(); - return; - } - } - if(time - this->lastPingRequest >= chrono::milliseconds(1000)) { - //TODO calculate the ping smooth - if(this->lastPingResponse < this->lastPingRequest){ - if(time - this->lastPingRequest >= chrono::milliseconds(1500)) { //Max - this->sendPingRequest(); - } - } else - this->sendPingRequest(); - } - + this->connection->ping_handler().tick(time); this->connection->packet_statistics().tick(); } else if(this->state == ConnectionState::INIT_LOW || this->state == ConnectionState::INIT_HIGH) { - if(this->last_packet_handshake.time_since_epoch().count() != 0) { - if(time - this->last_packet_handshake > seconds(5)) { + /* FIXME: Handshake timeout */ + auto last_command = this->connection->crypt_setup_handler().last_handled_command(); + if(last_command.time_since_epoch().count() != 0) { + if(time - last_command > seconds(5)) { debugMessage(this->getServerId(), "{} Got handshake timeout. {}. State: {} Time: {}", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()), this->state == ConnectionState::INIT_HIGH ? "INIT_HIGH" : "INIT_LOW", - duration_cast(time - this->last_packet_handshake).count() + duration_cast(time - last_command).count() ); this->close_connection(system_clock::now() + seconds(1)); } @@ -113,6 +81,10 @@ void VoiceClient::tick(const std::chrono::system_clock::time_point &time) { } } +std::chrono::milliseconds VoiceClient::current_ping() { + return this->connection->ping_handler().current_ping(); +} + bool VoiceClient::disconnect(const std::string &reason) { return this->disconnect(VREASON_SERVER_KICK, reason, this->server->serverRoot, true); } @@ -273,7 +245,7 @@ void VoiceClient::finalDisconnect() { } void VoiceClient::execute_handle_packet(const std::chrono::system_clock::time_point &time) { - this->connection->execute_handle_command_packets(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) { diff --git a/server/src/client/voice/VoiceClient.h b/server/src/client/voice/VoiceClient.h index 5daaa02..f22f25f 100644 --- a/server/src/client/voice/VoiceClient.h +++ b/server/src/client/voice/VoiceClient.h @@ -15,6 +15,7 @@ #include "VoiceClientConnection.h" #include "src/server/PrecomputedPuzzles.h" #include "../../lincense/TeamSpeakLicense.h" +#include "./ServerCommandExecutor.h" //#define LOG_INCOMPING_PACKET_FRAGMENTS //#define LOG_AUTO_ACK_AUTORESPONSE @@ -36,6 +37,11 @@ namespace ts { class VoiceClientConnection; } namespace server { + namespace server::udp { + class ServerCommandExecutor; + class CryptSetupHandler; + } + class VirtualServer; class VoiceClient : public SpeakingClient { @@ -45,6 +51,9 @@ namespace ts { friend class ts::connection::VoiceClientConnection; friend class ConnectedClient; friend class io::IOServerHandler; + friend class server::udp::ServerCommandExecutor; + friend class server::udp::CryptSetupHandler; + using ServerCommandExecutor = ts::server::server::udp::ServerCommandExecutor; public: VoiceClient(const std::shared_ptr& server,const sockaddr_storage*); ~VoiceClient() override; @@ -58,15 +67,16 @@ namespace ts { /* Note: Order is only guaranteed if progressDirectly is on! */ virtual void sendCommand0(const std::string_view& /* data */, bool low = false, std::unique_ptr> listener = nullptr); - virtual void sendAcknowledge(uint16_t packetId, bool low = false); connection::VoiceClientConnection* getConnection(){ return connection; } std::shared_ptr getVoiceServer(){ return voice_server; } - [[nodiscard]] inline std::chrono::milliseconds current_ping(){ return ping; } + [[nodiscard]] std::chrono::milliseconds current_ping(); [[nodiscard]] float current_ping_deviation(); [[nodiscard]] float current_packet_loss() const; + + [[nodiscard]] inline auto& server_command_executor() { return this->server_command_executor_; } private: connection::VoiceClientConnection* connection; @@ -78,29 +88,12 @@ namespace ts { /* Attention these handle callbacks are not thread save! */ void handlePacketCommand(const pipes::buffer_view&); - void handlePacketAck(const protocol::ClientPacketParser&); - void handlePacketVoice(const protocol::ClientPacketParser&); - void handlePacketVoiceWhisper(const protocol::ClientPacketParser&); - void handlePacketPing(const protocol::ClientPacketParser&); - void handlePacketInit(const protocol::ClientPacketParser&); public: void send_voice_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override; void send_voice_whisper_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override; protected: virtual command_result handleCommand(Command &command) override; - - //Some helper method - void sendPingRequest(); - - //Ping/pong - uint16_t lastPingId = 0; - std::chrono::milliseconds ping = std::chrono::milliseconds(0); - std::chrono::system_clock::time_point lastPingResponse; - std::chrono::system_clock::time_point lastPingRequest; - - std::chrono::system_clock::time_point last_packet_handshake; - private: int socket = 0; io::pktinfo_storage address_info; @@ -109,26 +102,13 @@ namespace ts { bool final_disconnected = false; //General TS3 manager commands - command_result handleCommandClientInitIv(Command&); - command_result handleCommandClientEk(Command&); command_result handleCommandClientInit(Command&) override; command_result handleCommandClientDisconnect(Command&); //Locked by finalDisconnect, disconnect and close connection std::shared_ptr flushing_thread; - struct { - bool client_init = false; - bool new_protocol = false; - bool protocol_encrypted = false; - bool is_teaspeak_client = false; - - uint32_t client_time = 0; - std::string alpha; - std::string beta; - std::shared_ptr chain_data; - std::shared_ptr remote_key; - } crypto; + ServerCommandExecutor server_command_executor_{this}; std::shared_ptr> event_handle_packet; void execute_handle_packet(const std::chrono::system_clock::time_point& /* scheduled */); diff --git a/server/src/client/voice/VoiceClientCommandHandler.cpp b/server/src/client/voice/VoiceClientCommandHandler.cpp index 234ffd2..275ba8a 100644 --- a/server/src/client/voice/VoiceClientCommandHandler.cpp +++ b/server/src/client/voice/VoiceClientCommandHandler.cpp @@ -6,7 +6,6 @@ #include #include "../../InstanceHandler.h" -#include "../../geo/GeoLocation.h" #include "VoiceClient.h" using namespace std; @@ -15,6 +14,25 @@ using namespace ts::server; using namespace ts::protocol; using namespace ts; +void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string) { + std::unique_ptr command; + command_result result{}; + try { + command = make_unique(Command::parse(command_string, true, !ts::config::server::strict_ut8_mode)); + } catch(std::invalid_argument& ex) { + result.reset(command_result{error::parameter_convert, std::string{ex.what()}}); + goto handle_error; + } catch(std::exception& ex) { + result.reset(command_result{error::parameter_convert, std::string{ex.what()}}); + goto handle_error; + } + + this->handleCommandFull(*command, true); + return; + handle_error: + this->notifyError(result); + result.release_data(); +} command_result VoiceClient::handleCommand(ts::Command &command) { threads::MutexLock l2(this->command_lock); @@ -56,23 +74,25 @@ inline bool calculate_security_level(int& result, ecc_key* pubKey, size_t offset } command_result VoiceClient::handleCommandClientInit(Command &cmd) { - this->crypto.client_init = true; - this->connection->packet_encoder().acknowledge_manager().reset(); - if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { - int securityLevel; - if(!calculate_security_level(securityLevel, this->crypto.remote_key.get(), cmd["client_key_offset"])) { - logError(this->getServerId(), "[{}] Failed to calculate security level. Error code: {}", CLIENT_STR_LOG_PREFIX, securityLevel); + auto client_identity = this->connection->crypt_setup_handler().identity_key(); + + int security_level; + if(!calculate_security_level(security_level, &*client_identity, cmd["client_key_offset"])) { + logError(this->getServerId(), "[{}] Failed to calculate security level. Error code: {}", CLIENT_STR_LOG_PREFIX, security_level); return command_result{error::vs_critical}; } - if(securityLevel < 8) + + if(security_level < 8) { return command_result{error::client_could_not_validate_identity}; + } auto requiredLevel = this->getServer()->properties()[property::VIRTUALSERVER_NEEDED_IDENTITY_SECURITY_LEVEL].as(); - if(securityLevel < requiredLevel) return command_result{error::client_could_not_validate_identity, to_string(requiredLevel)}; + if(security_level < requiredLevel) { + return command_result{error::client_could_not_validate_identity, to_string(requiredLevel)}; + } } - this->lastPingResponse = std::chrono::system_clock::now(); return SpeakingClient::handleCommandClientInit(cmd); } diff --git a/server/src/client/voice/VoiceClientConnection.cpp b/server/src/client/voice/VoiceClientConnection.cpp index fdd246d..ced8d58 100644 --- a/server/src/client/voice/VoiceClientConnection.cpp +++ b/server/src/client/voice/VoiceClientConnection.cpp @@ -28,7 +28,11 @@ using namespace ts::protocol; using namespace ts::server; VoiceClientConnection::VoiceClientConnection(VoiceClient* client) - : client{client}, crypt_handler{}, packet_decoder_{&this->crypt_handler}, packet_encoder_{&this->crypt_handler, &this->packet_statistics_} { + : current_client{client}, + crypt_handler{}, + packet_decoder_{&this->crypt_handler}, + packet_encoder_{&this->crypt_handler, &this->packet_statistics_}, + crypt_setup_handler_{this} { memtrack::allocated(this); this->packet_decoder_.callback_argument = this; @@ -43,18 +47,31 @@ VoiceClientConnection::VoiceClientConnection(VoiceClient* client) this->packet_encoder_.callback_resend_stats = VoiceClientConnection::callback_resend_statistics; this->packet_encoder_.callback_connection_stats = VoiceClientConnection::callback_outgoing_connection_statistics; + this->ping_handler_.callback_argument = this; + this->ping_handler_.callback_send_ping = VoiceClientConnection::callback_ping_send; + this->ping_handler_.callback_send_recovery_command = VoiceClientConnection::callback_ping_send_recovery; + this->ping_handler_.callback_time_outed = VoiceClientConnection::callback_ping_timeout; + + this->virtual_server_id_ = client->getServerId(); debugMessage(client->getServer()->getServerId(), "Allocated new voice client connection at {}", (void*) this); } VoiceClientConnection::~VoiceClientConnection() { this->reset(); - this->client = nullptr; + this->current_client = nullptr; memtrack::freed(this); } +std::string VoiceClientConnection::log_prefix() { + auto client = this->getCurrentClient(); + if(!client) return "[unknown / unknown]"; /* FIXME: Get the IP address here! */ + + return CLIENT_STR_LOG_PREFIX_(client); +} + void VoiceClientConnection::triggerWrite() { - if(this->client->voice_server) - this->client->voice_server->triggerWrite(dynamic_pointer_cast(this->client->_this.lock())); + if(this->current_client->voice_server) + this->current_client->voice_server->triggerWrite(dynamic_pointer_cast(this->current_client->_this.lock())); } #ifdef CLIENT_LOG_PREFIX @@ -69,8 +86,8 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b return; #ifndef CONNECTION_NO_STATISTICS - if(this->client) { - auto stats = this->client->connectionStatistics; + if(this->current_client) { + auto stats = this->current_client->connectionStatistics; stats->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length() + 96); /* 96 for the UDP packet overhead */ } this->packet_statistics().received_packet((protocol::PacketType) packet_parser.type(), packet_parser.full_packet_id()); @@ -88,39 +105,39 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b case PacketProcessResult::DECRYPT_KEY_GEN_FAILED: /* no action needed, acknowledge should be send */ - logCritical(this->client->getServerId(), "{} Failed to generate decrypt key. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client)); + logCritical(this->virtual_server_id_, "{} Failed to generate decrypt key. Dropping packet.", this->log_prefix()); break; case PacketProcessResult::BUFFER_OVERFLOW: case PacketProcessResult::BUFFER_UNDERFLOW: - debugMessage(this->client->getServerId(), "{} Dropping command packet because command assembly buffer has an {}: {}", - CLIENT_STR_LOG_PREFIX_(this->client), + debugMessage(this->virtual_server_id_, "{} Dropping command packet because command assembly buffer has an {}: {}", + this->log_prefix(), result == PacketProcessResult::BUFFER_UNDERFLOW ? "underflow" : "overflow", error ); break; case PacketProcessResult::UNKNOWN_ERROR: - logCritical(this->client->getServerId(), "{} Having an unknown error while processing a incoming packet: {}", - CLIENT_STR_LOG_PREFIX_(this->client), + logCritical(this->virtual_server_id_, "{} Having an unknown error while processing a incoming packet: {}", + this->log_prefix(), error ); goto disconnect_client; case PacketProcessResult::COMMAND_BUFFER_OVERFLOW: - debugMessage(this->client->getServerId(), "{} Having a command buffer overflow. This might cause the client to drop.", CLIENT_STR_LOG_PREFIX_(this->client)); + debugMessage(this->virtual_server_id_, "{} Having a command buffer overflow. This might cause the client to drop.", this->log_prefix()); break; case PacketProcessResult::COMMAND_DECOMPRESS_FAILED: - logWarning(this->client->getServerId(), "{} Failed to decompress a command packet. Dropping command.", CLIENT_STR_LOG_PREFIX_(this->client)); + logWarning(this->virtual_server_id_, "{} Failed to decompress a command packet. Dropping command.", this->log_prefix()); break; case PacketProcessResult::COMMAND_TOO_LARGE: - logWarning(this->client->getServerId(), "{} Received a too large command. Dropping client.", CLIENT_STR_LOG_PREFIX_(this->client)); + logWarning(this->virtual_server_id_, "{} Received a too large command. Dropping client.", this->log_prefix()); goto disconnect_client; case PacketProcessResult::COMMAND_SEQUENCE_LENGTH_TOO_LONG: - logWarning(this->client->getServerId(), "{} Received a too long command sequence. Dropping client.", CLIENT_STR_LOG_PREFIX_(this->client)); + logWarning(this->virtual_server_id_, "{} Received a too long command sequence. Dropping client.", this->log_prefix()); goto disconnect_client; default: @@ -135,160 +152,72 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b } void VoiceClientConnection::callback_send_acknowledge(void *ptr_this, uint16_t packet_id, bool command_low) { - /* FIXME: Move this to the connection! */ - reinterpret_cast(ptr_this)->client->sendAcknowledge(packet_id, command_low); + reinterpret_cast(ptr_this)->packet_encoder_.send_packet_acknowledge(packet_id, command_low); } void VoiceClientConnection::callback_packet_decoded(void *ptr_this, const ts::protocol::ClientPacketParser &packet) { auto connection = reinterpret_cast(ptr_this); switch (packet.type()) { case protocol::VOICE: - connection->client->handlePacketVoice(packet); + connection->handlePacketVoice(packet); break; case protocol::VOICE_WHISPER: - connection->client->handlePacketVoiceWhisper(packet); + connection->handlePacketVoiceWhisper(packet); break; case protocol::ACK: + connection->handlePacketAck(packet); + break; + case protocol::ACK_LOW: - connection->client->handlePacketAck(packet); + connection->handlePacketAckLow(packet); break; case protocol::PING: + connection->handlePacketPing(packet); + break; + case protocol::PONG: - connection->client->handlePacketPing(packet); + connection->handlePacketPong(packet); break; default: assert(false); - logError(connection->client->getServerId(), "{} Received hand decoded packet, but we've no method to handle it. Dropping packet.", CLIENT_STR_LOG_PREFIX_(connection->client)); + logError(connection->virtual_server_id_, "{} Received hand decoded packet, but we've no method to handle it. Dropping packet.", connection->log_prefix()); break; } } -void VoiceClientConnection::callback_command_decoded(void *ptr_this, ts::server::server::udp::ReassembledCommand *&command) { +void VoiceClientConnection::callback_command_decoded(void *ptr_this, ReassembledCommand *&command) { auto connection = reinterpret_cast(ptr_this); /* we're exchanging the command so we're taking the ownership */ - connection->enqueue_command_execution(std::exchange(command, nullptr)); + connection->handlePacketCommand(std::exchange(command, nullptr)); } bool VoiceClientConnection::verify_encryption(const pipes::buffer_view &buffer /* incl. mac etc */) { return this->packet_decoder_.verify_encryption(buffer); } -void VoiceClientConnection::enqueue_command_execution(ReassembledCommand *command) { - assert(!command->next_command); - - bool command_handling_scheduled{false}; - { - 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->voice_server; - if(voice_server) - voice_server->schedule_command_handling(this->client); - } +std::shared_ptr VoiceClientConnection::getCurrentClient() { + if(!this->current_client) return nullptr; + return std::dynamic_pointer_cast(this->current_client->ref()); } -void VoiceClientConnection::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) { - if(this->client->state >= ConnectionState::DISCONNECTING || !this->client->getServer()) - return; - - std::unique_ptr 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 = 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 = system_clock::now(); - if(end - startTime > milliseconds(10)) { - logError(this->client->getServerId(), - "{} Handling of command packet needs more than 10ms ({}ms)", - CLIENT_STR_LOG_PREFIX_(this->client), - duration_cast(end - startTime).count() - ); - } - - break; /* Maybe handle more than one command? Maybe some kind of time limit? */ - } - - auto voice_server = this->client->voice_server; - if(voice_server) - voice_server->schedule_command_handling(this->client); -} - - bool VoiceClientConnection::wait_empty_write_and_prepare_queue(chrono::time_point until) { return this->packet_encoder_.wait_empty_write_and_prepare_queue(until); } void VoiceClientConnection::reset() { this->crypt_handler.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; - } - } - + this->ping_handler_.reset(); this->packet_decoder_.reset(); this->packet_encoder_.reset(); } -void VoiceClientConnection::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 VoiceClientConnection::send_packet(protocol::OutgoingServerPacket *packet) { - this->packet_encoder_.send_packet(packet); - - auto statistics = this->client ? this->client->connectionStatistics : nullptr; - if(statistics) { - auto category = stats::ConnectionStatistics::category::from_type(packet->packet_type()); - statistics->logOutgoingPacket(category, packet->packet_length() + 96); /* 96 for the UDP packet overhead */ - } -} - void VoiceClientConnection::send_packet(protocol::PacketType type, protocol::PacketFlag::PacketFlags flag, const void *payload, size_t payload_size) { - auto packet = protocol::allocate_outgoing_packet(payload_size); - - packet->type_and_flags = (uint8_t) type | (uint8_t) flag; - memcpy(packet->payload, payload, payload_size); - - this->send_packet(packet); + this->packet_encoder_.send_packet(type, flag, payload, payload_size); } void VoiceClientConnection::send_command(const std::string_view &cmd, bool b, std::unique_ptr> cb) { @@ -301,11 +230,11 @@ void VoiceClientConnection::callback_encode_crypt_error(void *ptr_this, auto connection = reinterpret_cast(ptr_this); switch (error) { case PacketEncoder::CryptError::ENCRYPT_FAILED: - logError(connection->client->getServerId(), "{} Failed to encrypt packet. Error: {}", CLIENT_STR_LOG_PREFIX_(connection->client), detail); + logError(connection->virtual_server_id_, "{} Failed to encrypt packet. Error: {}", connection->log_prefix(), detail); break; case PacketEncoder::CryptError::KEY_GENERATION_FAILED: - logError(connection->client->getServerId(), "{} Failed to generate crypt key/nonce for sending a packet. This should never happen! Dropping packet.", CLIENT_STR_LOG_PREFIX_(connection->client)); + logError(connection->virtual_server_id_, "{} Failed to generate crypt key/nonce for sending a packet. This should never happen! Dropping packet.", connection->log_prefix()); break; default: @@ -322,27 +251,54 @@ void VoiceClientConnection::callback_request_write(void *ptr_this) { void VoiceClientConnection::callback_resend_failed(void *ptr_this, const shared_ptr &entry) { auto connection = reinterpret_cast(ptr_this); - debugMessage(connection->client->getServerId(), "{} Failed to execute packet resend of packet {}. Dropping connection.", CLIENT_STR_LOG_PREFIX_(connection->client), entry->packet_full_id); - - if(connection->client->state == ConnectionState::CONNECTED) { - connection->client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true); + debugMessage(connection->virtual_server_id_, "{} Failed to execute packet resend of packet {}. Dropping connection.", connection->log_prefix(), entry->packet_full_id); + auto client = connection->getCurrentClient(); + assert(client); /* TIXME! */ + if(client->state == ConnectionState::CONNECTED) { + client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true); } else { - connection->client->close_connection(system_clock::now() + seconds(1)); + client->close_connection(system_clock::now() + seconds(1)); } } void VoiceClientConnection::callback_resend_statistics(void *ptr_this, size_t send_count) { auto connection = reinterpret_cast(ptr_this); - logTrace(connection->client->getServerId(), "{} Resending {} packets.", CLIENT_STR_LOG_PREFIX_(connection->client), send_count); + logTrace(connection->virtual_server_id_, "{} Resending {} packets.", connection->log_prefix(), send_count); } void VoiceClientConnection::callback_outgoing_connection_statistics(void *ptr_this, ts::stats::ConnectionStatistics::category::value category, size_t send_count) { auto connection = reinterpret_cast(ptr_this); - auto statistics = connection->client->connectionStatistics; + auto client = connection->getCurrentClient(); + if(!client) return; + + auto statistics = client->connectionStatistics; if(!statistics) return; statistics->logOutgoingPacket(category, send_count); +} + +void VoiceClientConnection::callback_ping_send(void *ptr_this, uint16_t &id) { + auto connection = reinterpret_cast(ptr_this); + + auto packet = protocol::allocate_outgoing_packet(0); + packet->ref(); + + packet->type_and_flags = (uint8_t) PacketType::PING | (uint8_t) PacketFlag::Unencrypted; + connection->packet_encoder_.send_packet(packet); + id = packet->packet_id(); + + packet->unref(); +} + +void VoiceClientConnection::callback_ping_send_recovery(void *ptr_this) { + auto connection = reinterpret_cast(ptr_this); + + connection->send_command("notifyconnectioninforequest invokerids=0", false, nullptr); +} + +void VoiceClientConnection::callback_ping_timeout(void *ptr_this) { + /* doing nothing a packet resend failed will cause the client to disconnect */ } \ No newline at end of file diff --git a/server/src/client/voice/VoiceClientConnection.h b/server/src/client/voice/VoiceClientConnection.h index c011c23..915ea28 100644 --- a/server/src/client/voice/VoiceClientConnection.h +++ b/server/src/client/voice/VoiceClientConnection.h @@ -18,6 +18,9 @@ #include "./PacketStatistics.h" #include "./PacketDecoder.h" #include "./PacketEncoder.h" +#include "./ServerCommandExecutor.h" +#include "CryptSetupHandler.h" +#include "PingHandler.h" //#define LOG_ACK_SYSTEM #ifdef LOG_ACK_SYSTEM @@ -43,37 +46,43 @@ namespace ts { using PacketDecoder = server::server::udp::PacketDecoder; using PacketEncoder = server::server::udp::PacketEncoder; + using PingHandler = server::server::udp::PingHandler; + using CryptSetupHandler = server::server::udp::CryptSetupHandler; using ReassembledCommand = server::server::udp::ReassembledCommand; + using StatisticsCategory = stats::ConnectionStatistics::category; public: explicit VoiceClientConnection(server::VoiceClient*); virtual ~VoiceClientConnection(); - /* Do not send command packets via send_packet! The send_packet will take ownership of the packet! */ - void send_packet(protocol::OutgoingServerPacket* /* packet */); void send_packet(protocol::PacketType /* type */, protocol::PacketFlag::PacketFlags /* flags */, const void* /* payload */, size_t /* payload length */); void send_command(const std::string_view& /* build command command */, bool /* command low */, std::unique_ptr> /* acknowledge listener */); CryptHandler* getCryptHandler(){ return &crypt_handler; } - server::VoiceClient* getClient(){ return client; } + std::shared_ptr getCurrentClient(); bool wait_empty_write_and_prepare_queue(std::chrono::time_point until = std::chrono::time_point()); void reset(); + [[nodiscard]] std::string log_prefix(); - void force_insert_command(const pipes::buffer_view& /* payload */); + [[nodiscard]] inline auto virtual_server_id() const { return this->virtual_server_id_; } [[nodiscard]] inline auto& packet_statistics() { return this->packet_statistics_; } [[nodiscard]] inline auto& packet_decoder() { return this->packet_decoder_; } [[nodiscard]] inline auto& packet_encoder() { return this->packet_encoder_; } + + [[nodiscard]] inline auto& ping_handler() { return this->ping_handler_; } + [[nodiscard]] inline auto& crypt_setup_handler() { return this->crypt_setup_handler_; } protected: void handle_incoming_datagram(const pipes::buffer_view &buffer); bool verify_encryption(const pipes::buffer_view& /* full packet */); void triggerWrite(); private: - server::VoiceClient* client = nullptr; + ServerId virtual_server_id_; + server::VoiceClient* current_client; CryptHandler crypt_handler; /* access to CryptHandler is thread save */ server::client::PacketStatistics packet_statistics_{}; @@ -81,13 +90,8 @@ namespace ts { PacketDecoder packet_decoder_; PacketEncoder packet_encoder_; - 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 */ - - void enqueue_command_execution(ReassembledCommand*); - void execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */); + CryptSetupHandler crypt_setup_handler_; + PingHandler ping_handler_{}; static void callback_packet_decoded(void*, const protocol::ClientPacketParser&); static void callback_command_decoded(void*, ReassembledCommand*&); @@ -97,6 +101,18 @@ namespace ts { static void callback_resend_failed(void*, const std::shared_ptr&); static void callback_resend_statistics(void*, size_t); static void callback_outgoing_connection_statistics(void*, StatisticsCategory::value, size_t /* bytes */); + static void callback_ping_send(void*, uint16_t&); + static void callback_ping_send_recovery(void*); + static void callback_ping_timeout(void*); + + /* Attention: All packet callbacks are called from the IO threads and are not thread save! */ + void handlePacketCommand(ReassembledCommand* /* command */); /* The ownership will be transferred */ + void handlePacketAck(const protocol::ClientPacketParser&); + void handlePacketAckLow(const protocol::ClientPacketParser&); + void handlePacketVoice(const protocol::ClientPacketParser&); + void handlePacketVoiceWhisper(const protocol::ClientPacketParser&); + void handlePacketPing(const protocol::ClientPacketParser&); + void handlePacketPong(const protocol::ClientPacketParser&); }; } } \ No newline at end of file diff --git a/server/src/client/voice/VoiceClientConnectionPacketHandler.cpp b/server/src/client/voice/VoiceClientConnectionPacketHandler.cpp new file mode 100644 index 0000000..e4a21da --- /dev/null +++ b/server/src/client/voice/VoiceClientConnectionPacketHandler.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include "../web/WebClient.h" +#include "VoiceClient.h" + +using namespace std; +using namespace std::chrono; +using namespace ts::connection; +using namespace ts::protocol; + +void VoiceClientConnection::handlePacketPong(const ts::protocol::ClientPacketParser &packet) { + if(packet.payload_length() < 2) return; + + this->ping_handler_.received_pong(be2le16((char*) packet.payload().data_ptr())); +} + +void VoiceClientConnection::handlePacketPing(const protocol::ClientPacketParser& packet) { +#ifdef PKT_LOG_PING + logMessage(this->getServerId(), "{}[Ping] Sending pong for client requested ping {}", CLIENT_STR_LOG_PREFIX, packet->packetId()); +#endif + char buffer[2]; + le2be16(packet.packet_id(), buffer); + this->send_packet(PacketType::PONG, PacketFlag::Unencrypted, buffer, 2); +} + +void VoiceClientConnection::handlePacketVoice(const protocol::ClientPacketParser& packet) { + auto client = this->getCurrentClient(); + if(!client) return; + + client->handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0); +} + +void VoiceClientConnection::handlePacketVoiceWhisper(const ts::protocol::ClientPacketParser &packet) { + auto client = this->getCurrentClient(); + if(!client) return; + + client->handlePacketVoiceWhisper(packet.payload(), (packet.flags() & PacketFlag::NewProtocol) > 0); +} + +void VoiceClientConnection::handlePacketAck(const protocol::ClientPacketParser& packet) { + if(packet.payload_length() < 2) return; + uint16_t target_id{be2le16(packet.payload().data_ptr())}; + + this->ping_handler_.received_command_acknowledged(); + this->packet_statistics().received_acknowledge((protocol::PacketType) packet.type(), target_id | (uint32_t) (packet.estimated_generation() << 16U)); + + string error{}; + if(!this->packet_encoder().acknowledge_manager().process_acknowledge(packet.type(), target_id, error)) + debugMessage(this->virtual_server_id_, "{} Failed to handle acknowledge: {}", this->log_prefix(), error); +} + +void VoiceClientConnection::handlePacketAckLow(const ts::protocol::ClientPacketParser &packet) { + this->handlePacketAck(packet); +} + +void VoiceClientConnection::handlePacketCommand(ReassembledCommand* command) { + { + using CommandHandleResult = CryptSetupHandler::CommandHandleResult ; + + auto result = this->crypt_setup_handler_.handle_command(command->command_view()); + switch (result) { + case CommandHandleResult::PASS_THROUGH: + break; + + case CommandHandleResult::CONSUME_COMMAND: + return; + + case CommandHandleResult::CLOSE_CONNECTION: + auto client = this->getCurrentClient(); + assert(client); /* FIXME! */ + client->close_connection(std::chrono::system_clock::time_point{}); + return; + } + } + + auto client = this->getCurrentClient(); + if(!client) { + /* TODO! */ + return; + } + + client->server_command_executor().enqueue_command_execution(command); +} \ No newline at end of file diff --git a/server/src/client/voice/VoiceClientHandschake.cpp b/server/src/client/voice/VoiceClientHandschake.cpp index 58d4a59..69e17f3 100644 --- a/server/src/client/voice/VoiceClientHandschake.cpp +++ b/server/src/client/voice/VoiceClientHandschake.cpp @@ -14,184 +14,3 @@ using namespace std::chrono; using namespace ts::server; using namespace ts::protocol; using namespace ts::connection; - -inline void generate_random(uint8_t *destination, size_t length) { - while(length-- > 0) - *(destination++) = (uint8_t) rand(); -} - -ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) { - this->last_packet_handshake = system_clock::now(); - - std::unique_lock state_lock{this->state_lock}; - if(this->state == ConnectionState::CONNECTED) { /* we've a reconnect */ - if(system_clock::now() - this->lastPingResponse < seconds(5)) { - logMessage(this->getServerId(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt", - CLIENT_STR_LOG_PREFIX, - duration_cast(system_clock::now() - this->lastPingResponse).count() - ); - return ts::command_result{error::ok}; - } else if(!config::voice::allow_session_reinitialize) { - logMessage(this->getServerId(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings", - CLIENT_STR_LOG_PREFIX, - duration_cast(system_clock::now() - this->lastPingResponse).count() - ); - return ts::command_result{error::ok}; - } - logMessage(this->getServerId(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt", - CLIENT_STR_LOG_PREFIX, - duration_cast(system_clock::now() - this->lastPingResponse).count() - ); - - state_lock.unlock(); - - { - unique_lock server_channel_lock(this->server->channel_tree_lock); /* we cant get moved if this is locked! */ - if(this->currentChannel) - this->server->client_move(this->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock); - } - - this->finalDisconnect(); - state_lock.lock(); - } else if(this->state >= ConnectionState::DISCONNECTING) { - state_lock.unlock(); - std::shared_lock disconnect_finish{this->finalDisconnectLock}; /* await until the last disconnect has been processed */ - state_lock.lock(); - this->state = ConnectionState::INIT_HIGH; - } else if(this->state == ConnectionState::INIT_HIGH) { - logTrace(this->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX); - return command_result{error::ok}; - } else { - this->state = ConnectionState::INIT_HIGH; - } - state_lock.unlock(); - - this->connection->reset(); - this->connection->packet_decoder().register_initiv_packet(); - this->connection->packet_statistics().reset_offsets(); - this->crypto.protocol_encrypted = false; - - bool use_teaspeak = command.hasParm("teaspeak"); - if(use_teaspeak ? !config::server::clients::teaspeak : !config::server::clients::teamspeak) - return command_result{error::client_type_is_not_allowed}; - - if(use_teaspeak) { - debugMessage(this->getServerId(), "{} Client using TeaSpeak with auth type {}", CLIENT_STR_LOG_PREFIX, command["verify_type"].string()); - this->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEASPEAK; - } - - /* normal TeamSpeak handling */ - string clientAlpha = base64::decode(command["alpha"]); //random - if(clientAlpha.length() != 10) return ts::command_result{error::parameter_invalid}; - - string clientOmega = base64::decode(command["omega"]); //The identity public key - string ip = command["ip"]; - bool ot = command[0].has("ot") ? command["ot"] : false; - - this->crypto.remote_key = std::shared_ptr(new ecc_key{}, [](ecc_key* key){ - if(!key) return; - ecc_free(key); - delete key; - }); - - auto state = ecc_import((const unsigned char *) clientOmega.data(), clientOmega.length(), this->crypto.remote_key.get()); - if(state != CRYPT_OK) { - this->crypto.remote_key.reset(); - return ts::command_result{error::client_could_not_validate_identity}; - } - this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(command["omega"].string())); - - this->crypto.alpha = clientAlpha; - this->crypto.new_protocol = !use_teaspeak && ot && config::experimental_31 && (this->crypto.client_time >= 173265950 || this->crypto.client_time == 5680278000UL); - { - size_t beta_length = this->crypto.new_protocol ? 54 : 10; - char beta[beta_length]; - generate_random((uint8_t *) beta, beta_length); - this->crypto.beta = string(beta, beta_length); - } - - if(this->crypto.new_protocol) { - //Pre setup - //Generate chain - debugMessage(this->getServerId(), "{} Got client 3.1 protocol with build timestamp {}", CLIENT_STR_LOG_PREFIX, this->crypto.client_time); - this->crypto.chain_data = serverInstance->getTeamSpeakLicense()->license(); - this->crypto.chain_data->chain->addEphemeralEntry(); - auto rawLicense = this->crypto.chain_data->chain->exportChain(); - - //Sign license - auto serverOmega = this->getServer()->publicServerKey(); - auto rawServerOmega = base64::decode(serverOmega); - - auto licenseHash = digest::sha256(rawLicense); - size_t signBufferLength = 128; - char signBuffer[signBufferLength]; - prng_state prngState{}; - memset(&prngState, 0, sizeof(prngState)); - if(ecc_sign_hash((u_char*) licenseHash.data(), licenseHash.length(), (u_char*) signBuffer, &signBufferLength, &prngState, find_prng("sprng"), this->getServer()->serverKey()) != CRYPT_OK) { - logError(this->getServerId(), "Failed to sign crypto chain!"); - return ts::command_result{error::vs_critical}; - } - auto proof = base64::encode(signBuffer, signBufferLength); - - Command initivexpand2("initivexpand2"); - initivexpand2["time"] = duration_cast(system_clock::now().time_since_epoch()).count(); - initivexpand2["l"] = base64::encode(rawLicense); - initivexpand2["beta"] = base64::encode(this->crypto.beta); - initivexpand2["omega"] = serverOmega; - initivexpand2["proof"] = proof; - initivexpand2["tvd"] = ""; - initivexpand2["root"] = base64::encode((char*) this->crypto.chain_data->public_key, 32); - initivexpand2["ot"] = 1; - - this->sendCommand(initivexpand2); - this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */ - } else { - debugMessage(this->getServerId(), "{} Got non client 3.1 protocol with build timestamp {}", CLIENT_STR_LOG_PREFIX, this->crypto.client_time); - - auto serverOmega = this->getServer()->publicServerKey(); - if(serverOmega.empty()) { - logError(this->getServerId(), "Failed to export server public key!"); - return ts::command_result{error::vs_critical};; - } - - { - Command initivexpand("initivexpand"); - initivexpand["alpha"] = base64::encode(clientAlpha); - initivexpand["beta"] = base64::encode(this->crypto.beta); - initivexpand["omega"] = serverOmega; - if(use_teaspeak) { - initivexpand["teaspeak"] = 1; - this->handshake.state = HandshakeState::BEGIN; /* we need to start the handshake */ - } else { - this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */ - } - this->sendCommand0(initivexpand.build()); //If we setup the encryption now - this->connection->packet_encoder().encrypt_pending_packets(); - } - - { - string error; - if(!this->connection->getCryptHandler()->setupSharedSecret(this->crypto.alpha, this->crypto.beta, this->crypto.remote_key.get(), this->server->serverKey(), error)){ - logError(this->server->getServerId(), "Could not setup shared secret! (" + error + ")"); - return ts::command_result{error::vs_critical}; - } - this->crypto.protocol_encrypted = true; - } - } - return ts::command_result{error::ok}; -} - -ts::command_result VoiceClient::handleCommandClientEk(Command& cmd) { - this->last_packet_handshake = system_clock::now(); - debugMessage(this->getServerId(), "{} Got client ek!", CLIENT_STR_LOG_PREFIX); - - auto client_key = base64::decode(cmd["ek"]); - auto private_key = this->crypto.chain_data->chain->generatePrivateKey(this->crypto.chain_data->root_key, this->crypto.chain_data->root_index); - - this->connection->getCryptHandler()->setupSharedSecretNew(this->crypto.alpha, this->crypto.beta, (char*) private_key.data(), client_key.data()); - this->connection->packet_encoder().acknowledge_manager().reset(); - - this->crypto.protocol_encrypted = true; - this->sendAcknowledge(1); //Send the encrypted acknowledge (most the times the second packet; If not we're going into the resend loop) - return ts::command_result{error::ok}; -} \ No newline at end of file diff --git a/server/src/client/voice/VoiceClientPacketHandler.cpp b/server/src/client/voice/VoiceClientPacketHandler.cpp deleted file mode 100644 index 65e7764..0000000 --- a/server/src/client/voice/VoiceClientPacketHandler.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include -#include -#include -#include "../web/WebClient.h" -#include "VoiceClient.h" - -using namespace std; -using namespace std::chrono; -using namespace ts::server; -using namespace ts::protocol; - -//#define PKT_LOG_PING -/* should never happen! */ -void VoiceClient::handlePacketInit(const ts::protocol::ClientPacketParser &) {} - -//TODO Packet handlers -> move back to voice client? -void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string) { - std::unique_ptr command; - command_result result{}; - try { - command = make_unique(Command::parse(command_string, true, !ts::config::server::strict_ut8_mode)); - } catch(std::invalid_argument& ex) { - result.reset(command_result{error::parameter_convert, std::string{ex.what()}}); - goto handle_error; - } catch(std::exception& ex) { - result.reset(command_result{error::parameter_convert, std::string{ex.what()}}); - goto handle_error; - } - - if(command->command() == "clientek") { - result.reset(this->handleCommandClientEk(*command)); - if(result.has_error()) goto handle_error; - } else if(command->command() == "clientinitiv") { - result.reset(this->handleCommandClientInitIv(*command)); - if(result.has_error()) goto handle_error; - } else this->handleCommandFull(*command, true); - - return; - handle_error: - this->notifyError(result); - result.release_data(); -} - -void VoiceClient::handlePacketPing(const protocol::ClientPacketParser& packet) { - if (packet.type() == protocol::PONG) { - if(packet.payload_length() < 2) return; - - uint16_t id = be2le16((char*) packet.payload().data_ptr()); - if (this->lastPingId == id) { -#ifdef PKT_LOG_PING - logMessage(this->getServerId(), "{}[Ping] Got a valid pong for ping {}. Required time: {}", CLIENT_STR_LOG_PREFIX, id, duration_cast(system_clock::now() - this->lastPingRequest).count() / 1000.f); -#endif - this->lastPingResponse = system_clock::now(); - this->ping = std::chrono::duration_cast(this->lastPingResponse - this->lastPingRequest); - } -#ifdef PKT_LOG_PING - else { - logMessage(this->getServerId(), "{}[Ping] Got invalid pong. (Responded pong id {} but expected {})", CLIENT_STR_LOG_PREFIX, packet->packetId(), this->lastPingId); - } -#endif - return; - } - -#ifdef PKT_LOG_PING - logMessage(this->getServerId(), "{}[Ping] Sending pong for client requested ping {}", CLIENT_STR_LOG_PREFIX, packet->packetId()); -#endif - char buffer[2]; - le2be16(packet.packet_id(), buffer); - this->connection->send_packet(PacketType::PONG, PacketFlag::Unencrypted, buffer, 2); -} - -void VoiceClient::handlePacketVoice(const protocol::ClientPacketParser& packet) { - SpeakingClient::handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0); -} - -void VoiceClient::handlePacketVoiceWhisper(const ts::protocol::ClientPacketParser &packet) { - SpeakingClient::handlePacketVoiceWhisper(packet.payload(), (packet.flags() & PacketFlag::NewProtocol) > 0); -} - -void VoiceClient::handlePacketAck(const protocol::ClientPacketParser& packet) { - if(packet.payload_length() < 2) return; - uint16_t target_id{be2le16(packet.payload().data_ptr())}; - - this->connection->packet_statistics().received_acknowledge((protocol::PacketType) packet.type(), target_id | (packet.estimated_generation() << 16U)); - - string error{}; - if(!this->connection->packet_encoder().acknowledge_manager().process_acknowledge(packet.type(), target_id, error)) - debugMessage(this->getServerId(), "{} Failed to handle acknowledge: {}", CLIENT_STR_LOG_PREFIX, error); -} \ No newline at end of file diff --git a/server/src/client/voice/VoiceClientView.cpp b/server/src/client/voice/VoiceClientView.cpp deleted file mode 100644 index 6f4c36c..0000000 --- a/server/src/client/voice/VoiceClientView.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "../../InstanceHandler.h" -#include "VoiceClient.h" - -using namespace std; -using namespace ts::server; -using namespace ts::protocol; - -void VoiceClient::sendPingRequest() { - this->lastPingRequest = std::chrono::system_clock::now(); - - auto packet = protocol::allocate_outgoing_packet(0); - packet->ref(); /* extra ref for ourself */ - - packet->type_and_flags = (uint8_t) PacketType::PING | (uint8_t) PacketFlag::Unencrypted; - this->connection->send_packet(packet); - - this->lastPingId = packet->packet_id(); - packet->unref(); - -#ifdef PKT_LOG_PING - logMessage(this->getServerId(), "{}[Ping] Sending a ping request with it {}", CLIENT_STR_LOG_PREFIX, this->lastPingId); -#endif -} \ No newline at end of file diff --git a/server/src/server/POWHandler.cpp b/server/src/server/POWHandler.cpp index 09978c0..0969f68 100644 --- a/server/src/server/POWHandler.cpp +++ b/server/src/server/POWHandler.cpp @@ -270,7 +270,9 @@ void POWHandler::handle_puzzle_solve(const std::shared_ptrregister_verified_client(client); if(voice_client) { - voice_client->connection->force_insert_command(command); + auto rcommand = server::udp::ReassembledCommand::allocate(command.length()); + memcpy(rcommand->command(), command.data_ptr(), rcommand->length()); + voice_client->connection->handlePacketCommand(rcommand); client->state = LowHandshakeState::COMPLETED; } else { #ifdef POW_ERROR @@ -307,8 +309,8 @@ shared_ptr POWHandler::register_verified_client(const std::shared_p debugMessage(this->get_server_id(), "Having new voice client. Remote address: {}", voice_client->getLoggingPeerIp() +":" + to_string(voice_client->getPeerPort())); } - voice_client->crypto.client_time = client->client_version; - voice_client->last_packet_handshake = system_clock::now(); + voice_client->getConnection()->crypt_setup_handler().set_client_protocol_time(client->client_version); + //voice_client->last_packet_handshake = system_clock::now(); return voice_client; } \ No newline at end of file diff --git a/server/src/server/VoiceServer.cpp b/server/src/server/VoiceServer.cpp index 57bd765..93e0f01 100644 --- a/server/src/server/VoiceServer.cpp +++ b/server/src/server/VoiceServer.cpp @@ -330,7 +330,7 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) { auto new_address = net::to_string(remote_address); auto command = "dummy_ipchange old_ip=" + old_address + " new_ip=" + new_address; - client->connection->force_insert_command(pipes::buffer_view{command.data(), command.length()}); + client->server_command_executor().force_insert_command(pipes::buffer_view{command.data(), command.length()}); memcpy(&client->remote_address, &remote_address, sizeof(remote_address)); io::DatagramPacket::extract_info(message, client->address_info); } diff --git a/shared b/shared index a15eb9d..1433225 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit a15eb9d25c5b3305bf7b06d6780e9dbf7d7f3bc0 +Subproject commit 143322575d0f6e13d9eff7912bf8ce8c34020838