Teaspeak-Server/server/src/client/voice/VoiceClientConnection.cpp

387 lines
16 KiB
C++

#include <misc/endianness.h>
#include <algorithm>
#include <log/LogUtils.h>
#include <misc/memtracker.h>
#include <protocol/Packet.h>
#include <ThreadPool/Timer.h>
#include "./VoiceClientConnection.h"
#include "./VoiceClient.h"
#include "src/server/udp-server/UDPServer.h"
#include "src/InstanceHandler.h"
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::connection;
using namespace ts::protocol;
using namespace ts::server;
VoiceClientConnection::VoiceClientConnection(server::server::udp::Server* server, const std::shared_ptr<server::VoiceClient>& client, int socket) :
socket{socket},
udp_server{server},
packet_encoder_{&this->crypt_handler, &this->compress_handler, &this->acknowledge_handler},
packet_decoder_{&this->crypt_handler} {
memtrack::allocated<VoiceClientConnection>(this);
this->packet_encoder_.callback_argument = this;
this->packet_encoder_.callback_encoded_buffers = [](auto _this, const auto& a1) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_encoded_buffers(a1);
};
this->packet_encoder_.callback_encode_failed = [](auto _this, const auto& a1, auto a2, const auto& a3) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_encode_error(a1, a2, a3);
};
this->packet_decoder_.callback_argument = this;
this->packet_decoder_.callback_decoded_packet = [](auto _this, const auto& a1) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_decoded_packet(a1);
};
this->packet_decoder_.callback_decode_failed = [](auto _this, auto a1, const auto& a2) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_decode_error(a1, a2);
};
this->packet_decoder_.callback_send_acknowledge = [](auto _this, auto a1, auto a2) {
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_acknowledge(a1, a2);
};
this->ping_handler_.callback_argument = this;
this->ping_handler_.callback_send_ping = [](auto _this, auto& a1) {
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_ping(a1);
};
this->ping_handler_.callback_send_recovery_command = [](auto _this) {
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_ping_recovery();
};
this->ping_handler_.callback_time_outed = [](auto _this) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_ping_timeout();
};
this->server_id = client->getServerId();
this->client_handle_ = client;
this->crypt_handler.reset();
debugMessage(this->server_id, "Allocated new voice client connection at {}", (void*) this);
}
void VoiceClientConnection::initialize(const std::shared_ptr<VoiceClientConnection> &self) {
assert(&*self == this);
this->weak_this = self;
this->event_handle_packet = make_shared<event::ProxiedEventEntry<VoiceClientConnection>>(self, &VoiceClientConnection::execute_handle_command_packets);
}
VoiceClientConnection::~VoiceClientConnection() {
debugMessage(this->server_id, "Deleted voice client connection at {}", (void*) this);
/* locking here should be useless, but just to ensure! */
{
lock_guard wqlock(this->write_queue_lock);
this->write_queue.clear();
}
this->client_handle_ = nullptr;
memtrack::freed<VoiceClientConnection>(this);
}
void VoiceClientConnection::register_for_write() {
auto self = this->weak_this.lock();
assert(self);
this->udp_server->schedule_client_write(self);
}
void VoiceClientConnection::register_for_command_handling() {
auto vmanager = serverInstance->getVoiceServerManager();
if(!vmanager)
return;
auto evloop = vmanager->get_executor_loop();
if(!evloop)
return;
evloop->schedule(this->event_handle_packet);
}
#ifdef CLIENT_LOG_PREFIX
#undef CLIENT_LOG_PREFIX
#endif
#define CLIENT_LOG_PREFIX "[" << this->client->getPeerIp() << ":" << this->client->getPeerPort() << " | " << this->client->getDisplayName() << "]"
//Message handle methods
void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& buffer) {
auto command_pending = this->packet_decoder_.decode_incoming_data(buffer);
if(command_pending) this->register_for_command_handling();
}
bool VoiceClientConnection::verify_encryption(const pipes::buffer_view &buffer) {
return this->packet_decoder_.verify_encryption(buffer);
}
void VoiceClientConnection::handle_decoded_packet(const ts::protocol::ClientPacketParser &packet) {
auto packet_type = packet.type();
if(packet_type == PacketType::VOICE) {
auto client = this->client_handle_;
if(!client) [[unlikely]] {
logWarning(this->server_id, "Received voice data for client, but we've no client associated with the connection.");
return;
}
client->handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
} else if(packet_type == PacketType::VOICE_WHISPER) {
auto client = this->client_handle_;
if(!client) [[unlikely]] {
logWarning(this->server_id, "Received voice whisper data for client, but we've no client associated with the connection.");
return;
}
client->handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
} else if(packet_type == PacketType::ACK || packet_type == PacketType::ACK_LOW) {
string error{};
if(!this->acknowledge_handler.process_acknowledge(packet.type(), packet.payload(), error))
debugMessage(this->server_id, "{} Failed to handle acknowledge: {}", this->client_log_prefix(), error);
this->ping_handler_.received_command_acknowledged();
} else if(packet_type == PacketType::PING) {
/* just send a pong response */
char buffer[2];
le2be16(packet.packet_id(), buffer);
auto pkt = make_shared<ServerPacket>(PacketTypeInfo::Pong, pipes::buffer_view{buffer, 2});
pkt->enable_flag(PacketFlag::Unencrypted);
this->send_packet(pkt);
} else if(packet_type == PacketType::PONG) {
if(packet.payload_length() < 2) return;
this->ping_handler_.received_pong(be2le16((char*) packet.payload().data_ptr()));
} else if(packet_type == PacketType::COMMAND || packet_type == PacketType::COMMAND_LOW) {
logCritical(this->server_id, "{} Received command packet within handle_decoded_packet callback.", this->client_log_prefix());
} else if(packet_type == PacketType::INIT1) {
logCritical(this->server_id, "{} Received init packet within handle_decoded_packet callback.", this->client_log_prefix());
}
}
void VoiceClientConnection::handle_decode_error(ts::server::server::udp::PacketDecodeResult error, const std::string &message) {
using PacketDecodeResult = ts::server::server::udp::PacketDecodeResult;
switch (error) {
case PacketDecodeResult::DECRYPT_FAILED:
logWarning(this->server_id, "{} Dropping incoming packet. Failed to decrypt packet ({}).", this->client_log_prefix(), message);
break;
case PacketDecodeResult::DECRYPT_KEY_GEN_FAILED:
logWarning(this->server_id, "{} Dropping incoming packet. Failed to generate crypto key for packet.", this->client_log_prefix());
break;
case PacketDecodeResult::BUFFER_OVERFLOW:
logWarning(this->server_id, "{} Dropping incoming packet because queue has a buffer overflow ({}).", this->client_log_prefix(), message);
break;
case PacketDecodeResult::COMMAND_INSTERT_FAILED:
logWarning(this->server_id, "{} Dropping incoming packet because we failed to register the command packet.", this->client_log_prefix());
break;
#if 0
case PacketDecodeResult::DUPLICATED_PACKET:
logWarning(this->server_id, "{} Dropping incoming packet because it has already be processed.", this->client_log_prefix());
break;
case PacketDecodeResult::INVALID_PACKET:
logWarning(this->server_id, "{} Dropping incoming packet because its invalid.", this->client_log_prefix());
break;
#else
case PacketDecodeResult::INVALID_PACKET:
case PacketDecodeResult::DUPLICATED_PACKET:
#endif
case PacketDecodeResult::SUCCESS:
break;
}
}
void VoiceClientConnection::send_packet_acknowledge(uint16_t packet_id, bool command_low) {
char buffer[2];
le2be16(packet_id, buffer);
auto packet = make_shared<protocol::ServerPacket>(command_low ? protocol::PacketTypeInfo::AckLow : protocol::PacketTypeInfo::Ack, pipes::buffer_view{buffer, 2});
packet->enable_flag(PacketFlag::Unencrypted);
if(!command_low) packet->enable_flag(protocol::PacketFlag::NewProtocol);
this->send_packet(packet);
#ifdef PKT_LOG_ACK
logTrace(this->getServerId(), "{}[Acknowledge][Server -> Client] Sending acknowledge for {}", CLIENT_STR_LOG_PREFIX, packetId);
#endif
}
void VoiceClientConnection::send_packet_ping(uint16_t& ping_id) {
auto packet = make_shared<ServerPacket>(PacketTypeInfo::Ping, pipes::buffer_view{});
packet->enable_flag(PacketFlag::Unencrypted);
this->send_packet(packet, false, true); /* prepare directly so the packet gets a packet id */
ping_id = packet->packetId();
}
void VoiceClientConnection::send_packet_ping_recovery() {
const char* command = "notifyserverpingrecovery";
auto packet = make_shared<protocol::ServerPacket>(protocol::PacketTypeInfo::Command,
pipes::buffer_view{(void*) command, strlen(command)}
);
this->send_packet(packet);
}
void VoiceClientConnection::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) {
if((int) this->connection_state_ >= (int) ClientConnectionState::DISCONNECTING)
return;
auto client = this->client_handle_;
if(!client) [[unlikely]]
return;
std::lock_guard clock{client->command_lock};
using CommandReassembleResult = ts::server::server::udp::CommandReassembleResult;
pipes::buffer payload{};
bool command_low{false};
auto command_status = this->packet_decoder_.reassemble_command(payload, command_low);
switch (command_status) {
case CommandReassembleResult::SUCCESS:
case CommandReassembleResult::MORE_COMMANDS_PENDING:
break;
case CommandReassembleResult::NO_COMMANDS_PENDING:
return;
case CommandReassembleResult::COMMAND_DECOMPRESS_FAILED:
case CommandReassembleResult::COMMAND_TOO_LARGE:
case CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG:
//TODO: Shutdown connection?
logError(this->server_id, "{} Failed to reassemble next command ({}). This will cause the connection to fail.", this->client_log_prefix(), (int) command_status);
return;
}
auto startTime = system_clock::now();
try {
client->handlePacketCommand(payload);
} catch (std::exception& ex) {
logCritical(this->server_id, "{} An exception has been thrown within command handling, which reached to root handler. This should not happen! (Message: {})", this->client_log_prefix(), ex.what());
}
auto end = system_clock::now();
if(end - startTime > milliseconds(10)) {
auto index = payload.find(" ");
std::string command{};
if(index == std::string::npos)
command = payload.string();
else
command = payload.view(0, index).string();
logWarning(this->server_id, "{} Command handling of command \"{}\" required more than 10ms ({}ms)",this->client_log_prefix(),
command,
std::chrono::duration_cast<std::chrono::milliseconds>(end - startTime).count()
);
}
if(command_status == CommandReassembleResult::MORE_COMMANDS_PENDING)
this->register_for_command_handling();
}
void VoiceClientConnection::send_packet(const shared_ptr<protocol::ServerPacket>& original_packet, bool copy, bool prepare_directly) {
if(this->connection_state_ == ClientConnectionState::DISCONNECTED)
return;
using EncodeFlags = server::server::udp::PacketEncoder::EncodeFlags;
int flags{EncodeFlags::none};
if(!copy)
flags |= (unsigned) EncodeFlags::no_copy;
if(prepare_directly)
flags |= (unsigned) EncodeFlags::sync;
if(this->packet_encoder_.encode_packet(original_packet, (EncodeFlags) flags))
this->register_for_write();
}
void VoiceClientConnection::handle_encode_error(const shared_ptr<protocol::ServerPacket> &packet,
ts::server::server::udp::PacketEncodeResult result, const std::string &message) {
using PacketEncodeResult = ts::server::server::udp::PacketEncodeResult;
switch (result) {
case PacketEncodeResult::PACKET_TOO_LARGE:
logWarning(this->server_id, "{} Dropping packet of type {}. Packet is too large ({}bytes).", this->client_log_prefix(), packet->type().name(), packet->length());
break;
case PacketEncodeResult::COMPRESS_FAILED:
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to compress packet ({}).", this->client_log_prefix(), packet->type().name(), message);
break;
case PacketEncodeResult::ENCRYPT_KEY_GEN_FAILED:
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to generate crypto key for packet.", this->client_log_prefix(), packet->type().name());
break;
case PacketEncodeResult::ENCRYPT_FAILED:
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to encrypt packet ({}).", this->client_log_prefix(), packet->type().name(), message);
break;
case PacketEncodeResult::SUCCESS:
break;
}
}
void VoiceClientConnection::handle_encoded_buffers(const std::vector<pipes::buffer> &buffers) {
{
std::lock_guard lock{this->write_queue_lock};
this->write_queue.insert(this->write_queue.begin(), buffers.begin(), buffers.end());
}
this->register_for_write();
}
bool VoiceClientConnection::encode_packets() {
return this->packet_encoder_.do_encode();
}
WriteBufferStatus VoiceClientConnection::pop_write_buffer(pipes::buffer& target) {
lock_guard wqlock(this->write_queue_lock);
size_t size = this->write_queue.size();
if(size == 0)
return WriteBufferStatus::EMPTY;
target = std::move(this->write_queue.front());
this->write_queue.pop_front();
#ifdef FUZZING_TESTING_OUTGOING
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
if (this->client->state == ConnectionState::CONNECTED) {
#endif
if ((rand() % FUZZING_TESTING_DROP_MAX) < FUZZING_TESTING_DROP) {
debugMessage(this->client->getServerId(), "{}[FUZZING] Dropping outgoing packet", CLIENT_STR_LOG_PREFIX_(this->client));
return 0;
}
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
}
#endif
#endif
return size > 1 ? WriteBufferStatus::BUFFERS_LEFT : WriteBufferStatus::EMPTY;
}
void VoiceClientConnection::reset() {
this->packet_encoder_.reset();
this->packet_decoder_.reset();
this->acknowledge_handler.reset();
this->crypt_handler.reset();
}
void VoiceClientConnection::force_insert_command(const pipes::buffer_view &buffer) {
this->packet_decoder_.force_insert_command(buffer);
this->register_for_command_handling();
}
void VoiceClientConnection::close_connection(const std::chrono::system_clock::time_point &timeout) {
//TODO!
if(timeout.time_since_epoch().count() > 0) {
} else {
this->connection_state_ = ClientConnectionState::DISCONNECTED;
/* Unregister connection from server */
}
}
void VoiceClientConnection::tick() {
auto now = std::chrono::system_clock::now();
this->ping_handler_.tick(now);
if(this->connection_state_ == ClientConnectionState::DISCONNECTING) {
//TODO!
if(now > this->disconnect_timeout_) {
//TODO!
}
}
}