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

313 lines
12 KiB
C++

//
// Created by WolverinDEV on 10/03/2020.
//
#include "PacketDecoder.h"
#include <protocol/buffers.h>
#include <protocol/AcknowledgeManager.h>
#include <protocol/CompressionHandler.h>
#include <protocol/CryptHandler.h>
#include "../../ConnectionStatistics.h"
using namespace ts;
using namespace ts::protocol;
using namespace ts::connection;
using namespace ts::server::server::udp;
ReassembledCommand *ReassembledCommand::allocate(size_t size) {
auto instance = (ReassembledCommand*) malloc(sizeof(ReassembledCommand) + size);
instance->length_ = size;
instance->capacity_ = size;
instance->next_command = nullptr;
return instance;
}
void ReassembledCommand::free(ReassembledCommand *command) {
::free(command);
}
PacketDecoder::PacketDecoder(ts::connection::CryptHandler *crypt_handler)
: crypt_handler_{crypt_handler} {
memtrack::allocated<PacketDecoder>(this);
}
PacketDecoder::~PacketDecoder() {
memtrack::freed<PacketDecoder>(this);
this->reset();
}
void PacketDecoder::reset() {
std::lock_guard buffer_lock(this->packet_buffer_lock);
for(auto& buffer : this->_command_fragment_buffers) {
buffer.reset();
}
}
PacketProcessResult PacketDecoder::process_incoming_data(ClientPacketParser &packet_parser, std::string& error) {
#ifdef FUZZING_TESTING_INCOMMING
if(rand() % 100 < 20)
return PacketProcessResult::FUZZ_DROPPED;
#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 incoming packet of length {}", CLIENT_STR_LOG_PREFIX_(this->client), buffer.length());
return;
}
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
}
#endif
#endif
auto result = this->decode_incoming_packet(error, packet_parser);
if(result != PacketProcessResult::SUCCESS)
return result;
#ifdef LOG_INCOMPING_PACKET_FRAGMENTS
debugMessage(lstream << CLIENT_LOG_PREFIX << "Recived packet. PacketId: " << packet->packetId() << " PacketType: " << packet->type().name() << " Flags: " << packet->flags() << " - " << packet->data() << endl);
#endif
auto is_command = packet_parser.type() == protocol::COMMAND || packet_parser.type() == protocol::COMMAND_LOW;
if(is_command) {
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
CommandFragment fragment_entry{
packet_parser.packet_id(),
packet_parser.estimated_generation(),
packet_parser.flags(),
(uint32_t) packet_parser.payload_length(),
packet_parser.payload().own_buffer()
};
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
auto insert_result = fragment_buffer.insert_index2(packet_parser.full_packet_id(), std::move(fragment_entry));
if(insert_result != 0) {
queue_lock.unlock();
error = "pid: " + std::to_string(packet_parser.packet_id()) + ", ";
error += "bidx: " + std::to_string(fragment_buffer.current_index()) + ", ";
error += "bcap: " + std::to_string(fragment_buffer.capacity());
if(insert_result == -2) {
return PacketProcessResult::DUPLICATED_PACKET;
} else if(insert_result == -1) {
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
return PacketProcessResult::BUFFER_UNDERFLOW;
} else if(insert_result == 1) {
return PacketProcessResult::BUFFER_OVERFLOW;
}
assert(false);
return PacketProcessResult::UNKNOWN_ERROR;
}
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
ReassembledCommand* command{nullptr};
CommandReassembleResult assemble_result;
do {
if(!queue_lock.owns_lock())
queue_lock.lock();
assemble_result = this->try_reassemble_ordered_packet(fragment_buffer, queue_lock, command);
if(assemble_result == CommandReassembleResult::SUCCESS || assemble_result == CommandReassembleResult::MORE_COMMANDS_PENDING)
this->callback_decoded_command(this->callback_argument, command);
if(command) {
/* ownership hasn't transferred */
ReassembledCommand::free(command);
command = nullptr;
}
switch (assemble_result) {
case CommandReassembleResult::NO_COMMANDS_PENDING:
case CommandReassembleResult::SUCCESS:
case CommandReassembleResult::MORE_COMMANDS_PENDING:
break;
case CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG:
return PacketProcessResult::COMMAND_BUFFER_OVERFLOW;
case CommandReassembleResult::COMMAND_TOO_LARGE:
return PacketProcessResult::COMMAND_TOO_LARGE;
case CommandReassembleResult::COMMAND_DECOMPRESS_FAILED:
return PacketProcessResult::COMMAND_DECOMPRESS_FAILED;
default:
assert(false);
break;
}
} while(assemble_result == CommandReassembleResult::MORE_COMMANDS_PENDING);
} else {
this->callback_decoded_packet(this->callback_argument, packet_parser);
}
return PacketProcessResult::SUCCESS;
}
PacketProcessResult PacketDecoder::decode_incoming_packet(std::string& error, ClientPacketParser &packet_parser) {
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
auto& generation_estimator = this->incoming_generation_estimators[packet_parser.type()];
{
std::lock_guard glock{this->incoming_generation_estimator_lock};
packet_parser.set_estimated_generation(generation_estimator.visit_packet(packet_parser.packet_id()));
}
/* decrypt the packet if needed */
if(packet_parser.is_encrypted()) {
CryptHandler::key_t crypt_key{};
CryptHandler::nonce_t crypt_nonce{};
auto data = (uint8_t*) packet_parser.mutable_data_ptr();
bool use_default_key{!this->crypt_handler_->encryption_initialized()}, decrypt_result;
decrypt_packet:
if(use_default_key) {
crypt_key = CryptHandler::kDefaultKey;
crypt_nonce = CryptHandler::kDefaultNonce;
} else {
if(!this->crypt_handler_->generate_key_nonce(true, packet_parser.type(), packet_parser.packet_id(), packet_parser.estimated_generation(), crypt_key, crypt_nonce))
return PacketProcessResult::DECRYPT_KEY_GEN_FAILED;
}
decrypt_result = this->crypt_handler_->decrypt(
data + ClientPacketParser::kHeaderOffset, ClientPacketParser::kHeaderLength,
data + ClientPacketParser::kPayloadOffset, packet_parser.payload_length(),
data,
crypt_key, crypt_nonce,
error
);
if(!decrypt_result) {
if(packet_parser.packet_id() < 10 && packet_parser.estimated_generation() == 0) {
if(use_default_key) {
return PacketProcessResult::DECRYPT_FAILED;
} else {
use_default_key = true;
goto decrypt_packet;
}
} else {
return PacketProcessResult::DECRYPT_FAILED;
}
}
packet_parser.set_decrypted();
}
return PacketProcessResult::SUCCESS;
}
bool PacketDecoder::verify_encryption(const pipes::buffer_view &buffer) {
ClientPacketParser packet_parser{buffer};
if(!packet_parser.valid() || !packet_parser.is_encrypted()) return false;
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
return this->crypt_handler_->verify_encryption(buffer, packet_parser.packet_id(), this->incoming_generation_estimators[packet_parser.type()].generation());
}
void PacketDecoder::register_initiv_packet() {
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
std::unique_lock buffer_lock(fragment_buffer.buffer_lock);
fragment_buffer.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */
}
CommandReassembleResult PacketDecoder::try_reassemble_ordered_packet(
command_fragment_buffer_t &buffer,
std::unique_lock<std::mutex> &buffer_lock,
ReassembledCommand *&assembled_command) {
assert(buffer_lock.owns_lock());
if(!buffer.front_set()) {
return CommandReassembleResult::NO_COMMANDS_PENDING;
}
uint8_t packet_flags;
std::unique_ptr<ReassembledCommand, void(*)(ReassembledCommand*)> rcommand{nullptr, ReassembledCommand::free};
/* lets find out if we've to reassemble the packet */
auto& first_buffer = buffer.slot_value(0);
if(first_buffer.packet_flags & PacketFlag::Fragmented) {
uint16_t sequence_length{1};
size_t total_payload_length{first_buffer.payload_length};
do {
if(sequence_length >= buffer.capacity()) {
return CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG;
}
if(!buffer.slot_set(sequence_length)) {
return CommandReassembleResult::NO_COMMANDS_PENDING; /* we need more packets */
}
auto& packet = buffer.slot_value(sequence_length++);
total_payload_length += packet.payload_length;
if(packet.packet_flags & PacketFlag::Fragmented) {
/* yep we find the end */
break;
}
} while(true);
/* ok we have all fragments lets reassemble */
/*
* Packet sequence could never be so long. If it is so then the data_length() returned an invalid value.
* We're checking it here because we dont want to make a huge allocation
*/
assert(total_payload_length < 512 * 1024 * 1024);
rcommand.reset(ReassembledCommand::allocate(total_payload_length));
char* packet_buffer_ptr = rcommand->command();
size_t packet_count{0};
packet_flags = buffer.slot_value(0).packet_flags;
while(packet_count < sequence_length) {
auto fragment = buffer.pop_front();
memcpy(packet_buffer_ptr, fragment.payload.data_ptr(), fragment.payload_length);
packet_buffer_ptr += fragment.payload_length;
packet_count++;
}
#ifndef _NDEBUG
if((packet_buffer_ptr - 1) != &rcommand->command()[rcommand->length() - 1]) {
logCritical(0,
"Buffer over/underflow: packet_buffer_ptr != &packet_buffer[packet_buffer.length() - 1]; packet_buffer_ptr := {}; packet_buffer.end() := {}",
(void*) packet_buffer_ptr,
(void*) &rcommand->command()[rcommand->length() - 1]
);
}
#endif
} else {
auto packet = buffer.pop_front();
packet_flags = packet.packet_flags;
rcommand.reset(ReassembledCommand::allocate(packet.payload_length));
memcpy(rcommand->command(), packet.payload.data_ptr(), packet.payload_length);
}
auto more_commands_pending = buffer.front_set(); /* set the more flag if we have more to process */
buffer_lock.unlock();
if(packet_flags & PacketFlag::Compressed) {
std::string error{};
auto compressed_command = std::move(rcommand);
auto decompressed_size = compression::qlz_decompressed_size(compressed_command->command(), compressed_command->length());
if(decompressed_size > 64 * 1024 * 1024) {
return CommandReassembleResult::COMMAND_TOO_LARGE;
}
rcommand.reset(ReassembledCommand::allocate(decompressed_size));
if(!compression::qlz_decompress_payload(compressed_command->command(), rcommand->command(), &decompressed_size)) {
return CommandReassembleResult::COMMAND_DECOMPRESS_FAILED;
}
rcommand->set_length(decompressed_size);
}
assembled_command = rcommand.release();
return more_commands_pending ? CommandReassembleResult::MORE_COMMANDS_PENDING : CommandReassembleResult::SUCCESS;
}