529 lines
21 KiB
C++
529 lines
21 KiB
C++
#define TIMING_DISABLED
|
|
#include "POWHandler.h"
|
|
#include <thread>
|
|
#include <algorithm>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include "VoiceServer.h"
|
|
#include "../client/voice/VoiceClient.h"
|
|
#include "../Configuration.h"
|
|
#include <log/LogUtils.h>
|
|
#include "src/VirtualServer.h"
|
|
#include <misc/endianness.h>
|
|
#include "src/VirtualServerManager.h"
|
|
#include "../InstanceHandler.h"
|
|
#include <ThreadPool/Timer.h>
|
|
#include <pipes/buffer.h>
|
|
#include <netinet/in.h>
|
|
#include <misc/sassert.h>
|
|
#include "misc/timer.h"
|
|
|
|
using namespace std;
|
|
using namespace std::chrono;
|
|
using namespace ts::server;
|
|
using namespace ts::buffer;
|
|
using namespace ts;
|
|
|
|
extern InstanceHandler* serverInstance;
|
|
|
|
VoiceServer::VoiceServer(const std::shared_ptr<VirtualServer>& server) {
|
|
this->server = server;
|
|
this->pow_handler = make_unique<POWHandler>(this);
|
|
}
|
|
|
|
VoiceServer::~VoiceServer() { }
|
|
|
|
#define SET_OPTION(type, option, flag, error) \
|
|
if(setsockopt(bind->file_descriptor, type, option, &flag, sizeof(flag)) < 0) { \
|
|
error; \
|
|
::close(bind->file_descriptor); \
|
|
bind->file_descriptor = 0; \
|
|
continue; \
|
|
}
|
|
|
|
bool VoiceServer::start(const std::deque<std::shared_ptr<VoiceServerBinding>>& binding, std::string& error) {
|
|
if(this->running) return false;
|
|
if(binding.empty()) {
|
|
error = "Missing bindings!";
|
|
return false;
|
|
}
|
|
this->running = true;
|
|
this->bindings = binding;
|
|
|
|
int enable = 1, disable = 0;
|
|
for (auto &bind : binding) {
|
|
bind->file_descriptor = socket(bind->address.ss_family, SOCK_DGRAM, 0);
|
|
if(!bind->file_descriptor) {
|
|
logError(this->server->getServerId(), "Failed to create socket for {}", bind->address_string());
|
|
continue;
|
|
}
|
|
|
|
if(setsockopt(bind->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &disable, sizeof(int)) < 0) logError(this->server->getServerId(), "Could not disable flag reuse address for bound {}!", bind->address_string());
|
|
//if(setsockopt(bind->file_descriptor, SOL_SOCKET, SO_REUSEPORT, &disable, sizeof(int)) < 0) logError(this->server->getServerId(), "Could not disable flag reuse port for bound {}!", bind->address_string());
|
|
|
|
/* We're never sending over MTU size packets! */
|
|
int pmtu = IP_PMTUDISC_DO;
|
|
setsockopt(bind->file_descriptor, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu));
|
|
|
|
if(fcntl(bind->file_descriptor, F_SETFD, FD_CLOEXEC) < 0)
|
|
logError(this->server->getServerId(), "Failed to enable FD_CLOEXEC for {} ({}) (VoiceServer)", bind->file_descriptor, bind->address_string());
|
|
|
|
if(bind->address.ss_family == AF_INET6) {
|
|
SET_OPTION(IPPROTO_IPV6, IPV6_RECVPKTINFO, enable, {
|
|
logError(this->server->getServerId(), "Failed to enable packet info (v6) for {}", bind->address_string());
|
|
});
|
|
SET_OPTION(IPPROTO_IPV6, IPV6_V6ONLY, enable, {
|
|
logError(this->server->getServerId(), "Failed to enable ip v6 only for {}", bind->address_string());
|
|
});
|
|
} else {
|
|
SET_OPTION(IPPROTO_IP, IP_PKTINFO, enable, {
|
|
logError(this->server->getServerId(), "Failed to enable packet info for {}", bind->address_string());
|
|
});
|
|
}
|
|
|
|
if(::bind(bind->file_descriptor, (const sockaddr*) &bind->address, net::address_size(bind->address)) < 0) {
|
|
logError(this->server->getServerId(), "Failed to bind to {} ({} => {})", bind->address_string(), errno, strerror(errno));
|
|
::close(bind->file_descriptor);
|
|
bind->file_descriptor = 0;
|
|
continue;
|
|
}
|
|
fcntl(bind->file_descriptor, F_SETFL, fcntl(bind->file_descriptor, F_GETFL, 0) | O_NONBLOCK);
|
|
}
|
|
{
|
|
auto bindings = this->activeBindings();
|
|
if(bindings.empty()) {
|
|
error = "Failed to bind any address!";
|
|
this->running = false;
|
|
return false;
|
|
}
|
|
|
|
string str;
|
|
for(auto it = bindings.begin(); it != bindings.end(); it++) {
|
|
str += net::to_string((*it)->address) + (it + 1 == bindings.end() ? "" : " | ");
|
|
}
|
|
logMessage(this->server->getServerId(), "Started server on {}.", str);
|
|
}
|
|
|
|
|
|
this->io = serverInstance->getVoiceServerManager()->ioManager()->enableIo(this->server.get());
|
|
return true;
|
|
}
|
|
|
|
void VoiceServer::triggerWrite(const std::shared_ptr<VoiceClient>& client) {
|
|
if(!client) {
|
|
logError(this->server->getServerId(), "Invalid client for triggerWrite()");
|
|
return;
|
|
}
|
|
|
|
if(auto io{this->io}; io) {
|
|
io->invoke_write(client);
|
|
}
|
|
}
|
|
|
|
void VoiceServer::schedule_command_handling(const ts::server::VoiceClient *client) {
|
|
auto vmanager = serverInstance->getVoiceServerManager();
|
|
if(!vmanager)
|
|
return;
|
|
auto evloop = vmanager->get_executor_loop();
|
|
if(!evloop)
|
|
return;
|
|
|
|
evloop->schedule(client->event_handle_packet);
|
|
}
|
|
|
|
void VoiceServer::tickHandshakingClients() {
|
|
this->pow_handler->execute_tick();
|
|
|
|
decltype(this->activeConnections) connections;
|
|
{
|
|
lock_guard lock(this->connectionLock);
|
|
connections = this->activeConnections;
|
|
}
|
|
for(const auto& client : connections)
|
|
if(client->state == ConnectionState::INIT_HIGH || client->state == ConnectionState::INIT_LOW)
|
|
client->tick(system_clock::now());
|
|
}
|
|
|
|
void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next) {
|
|
decltype(this->activeConnections) connections;
|
|
{
|
|
lock_guard lock(this->connectionLock);
|
|
connections = this->activeConnections;
|
|
}
|
|
string error;
|
|
for(const auto& client : connections) {
|
|
auto connection = client->getConnection();
|
|
sassert(connection); /* its not possible that a client hasn't a connection! */
|
|
connection->packet_encoder().execute_resend(now, next);
|
|
}
|
|
}
|
|
|
|
bool VoiceServer::stop(const std::chrono::milliseconds& flushTimeout) {
|
|
if(!this->running) return false;
|
|
this->running = false;
|
|
|
|
this->connectionLock.lock();
|
|
auto list = this->activeConnections;
|
|
this->connectionLock.unlock();
|
|
for(const auto &e : list)
|
|
e->close_connection(system_clock::now() + seconds(1));
|
|
|
|
auto beg = system_clock::now();
|
|
while(!this->activeConnections.empty() && flushTimeout.count() != 0 && system_clock::now() - beg < flushTimeout)
|
|
threads::self::sleep_for(milliseconds(10));
|
|
|
|
for(const auto& connection : this->activeConnections)
|
|
connection->voice_server = nullptr;
|
|
this->activeConnections.clear();
|
|
|
|
serverInstance->getVoiceServerManager()->ioManager()->disableIo(this->server.get());
|
|
this->io = nullptr;
|
|
for(const auto& bind : this->bindings) {
|
|
if(bind->file_descriptor > 0){
|
|
if(!shutdown(bind->file_descriptor, SHUT_RDWR)) logError(this->server->getServerId(), "Failed to shutdown socket {} ({}) Reason: {}/{}", bind->file_descriptor, bind->address_string(), errno, strerror(errno));
|
|
if(!close(bind->file_descriptor)) {
|
|
if(errno != ENOTCONN)
|
|
logError(this->server->getServerId(), "Failed to close socket {} ({}) Reason: {}/{}", bind->file_descriptor, bind->address_string(), errno, strerror(errno));
|
|
}
|
|
bind->file_descriptor = 0;
|
|
}
|
|
}
|
|
this->bindings.clear();
|
|
return true;
|
|
}
|
|
|
|
std::shared_ptr<VoiceClient> VoiceServer::findClient(ts::ClientId client) {
|
|
lock_guard lock(this->connectionLock);
|
|
|
|
for(const auto& elm : this->activeConnections) {
|
|
if(elm->getClientId() == client)
|
|
return elm;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<VoiceClient> VoiceServer::findClient(sockaddr_in *addr, bool) {
|
|
lock_guard lock(this->connectionLock);
|
|
|
|
for(const auto& elm : this->activeConnections) {
|
|
if(elm->isAddressV4())
|
|
if(elm->getAddressV4()->sin_addr.s_addr == addr->sin_addr.s_addr)
|
|
if(elm->getAddressV4()->sin_port == addr->sin_port)
|
|
return elm;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<VoiceClient> VoiceServer::findClient(sockaddr_in6 *addr, bool) {
|
|
lock_guard lock(this->connectionLock);
|
|
|
|
for(const auto& elm : this->activeConnections) {
|
|
if(elm->isAddressV6())
|
|
if(memcmp(elm->getAddressV6()->sin6_addr.__in6_u.__u6_addr8, addr->sin6_addr.__in6_u.__u6_addr8, 16) == 0)
|
|
if(elm->getAddressV6()->sin6_port == addr->sin6_port)
|
|
return elm;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool VoiceServer::unregisterConnection(std::shared_ptr<VoiceClient> connection) {
|
|
lock_guard lock(this->connectionLock);
|
|
|
|
auto found = std::find(this->activeConnections.begin(), this->activeConnections.end(), connection);
|
|
if(found != activeConnections.end())
|
|
this->activeConnections.erase(found);
|
|
else logError(LOG_GENERAL, "unregisterConnection(...) -> could not find client");
|
|
return true;
|
|
}
|
|
|
|
static union {
|
|
char literal[8] = {'T', 'S', '3', 'I', 'N', 'I', 'T', '1'};
|
|
uint64_t integral;
|
|
} TS3INIT;
|
|
|
|
void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) {
|
|
auto event_handle = (io::IOEventLoopEntry*) _event_handle;
|
|
auto voice_server = event_handle->voice_server;
|
|
auto ts_server = event_handle->server;
|
|
|
|
size_t raw_read_buffer_length = event_handle->family == AF_INET ? 600 : 1600; //IPv6 MTU: 1500 | IPv4 MTU: 576
|
|
uint8_t raw_read_buffer[raw_read_buffer_length]; //Allocate on stack, so we dont need heap here
|
|
|
|
ssize_t bytes_read = 0;
|
|
pipes::buffer_view read_buffer{raw_read_buffer, raw_read_buffer_length}; /* will not allocate anything, just sets its mode to ptr and thats it :) */
|
|
|
|
sockaddr_storage remote_address{};
|
|
iovec io_vector{};
|
|
io_vector.iov_base = (void*) raw_read_buffer;
|
|
io_vector.iov_len = raw_read_buffer_length;
|
|
|
|
char message_headers[0x100];
|
|
|
|
msghdr message{};
|
|
message.msg_name = &remote_address;
|
|
message.msg_namelen = sizeof(remote_address);
|
|
message.msg_iov = &io_vector;
|
|
message.msg_iovlen = 1;
|
|
message.msg_control = message_headers;
|
|
message.msg_controllen = 0x100;
|
|
|
|
auto read_timeout = system_clock::now() + microseconds(2500); /* read 2.5ms long at a time or 'till nothing more is there */
|
|
while(system_clock::now() <= read_timeout){
|
|
message.msg_flags = 0;
|
|
bytes_read = recvmsg(fd, &message, 0);
|
|
|
|
if((message.msg_flags & MSG_TRUNC) > 0)
|
|
logError(ts_server->getServerId(), "Received truncated message from {}", net::to_string(remote_address));
|
|
|
|
if(bytes_read < 0) {
|
|
if(errno == EAGAIN)
|
|
break;
|
|
//Nothing more to read
|
|
logCritical(ts_server->getServerId(), "Could not receive datagram packet! Code: {} Reason: {}", errno, strerror(errno));
|
|
break;
|
|
} else if(bytes_read == 0){
|
|
//This should never happen
|
|
break;
|
|
}
|
|
|
|
if(bytes_read < MAC_SIZE + CLIENT_HEADER_SIZE) {
|
|
/* reenable for debug. else short packages could be a dos attach */
|
|
//logError(ts_server->getServerId(), "Received an too short packet!");
|
|
continue;
|
|
}
|
|
|
|
shared_ptr<VoiceClient> client;
|
|
{
|
|
if(*(uint64_t*) raw_read_buffer == TS3INIT.integral) {
|
|
//Handle ddos protection...
|
|
voice_server->pow_handler->handle_datagram(event_handle->socket_id, remote_address, message, read_buffer.view(0, bytes_read));
|
|
} else {
|
|
auto client_id = (ClientId) be2le16(&raw_read_buffer[10]);
|
|
if(client_id > 0) {
|
|
client = dynamic_pointer_cast<VoiceClient>(voice_server->server->find_client_by_id(client_id));
|
|
} else {
|
|
client = voice_server->findClient(&remote_address, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!client)
|
|
continue;
|
|
|
|
if(memcmp(&client->remote_address, &remote_address, sizeof(sockaddr_storage)) != 0) { /* verify the remote address */
|
|
if((read_buffer[12] & 0x80) == 0 && client->state == ConnectionState::CONNECTED) { /* only encrypted packets are allowed */
|
|
if(client->connection->verify_encryption(read_buffer.view(0, bytes_read))) { /* the ip had changed */
|
|
auto old_address = net::to_string(client->remote_address);
|
|
auto new_address = net::to_string(remote_address);
|
|
|
|
auto command = "dummy_ipchange old_ip=" + old_address + " new_ip=" + new_address;
|
|
client->server_command_executor().force_insert_command(pipes::buffer_view{command.data(), command.length()});
|
|
memcpy(&client->remote_address, &remote_address, sizeof(remote_address));
|
|
udp::DatagramPacket::extract_info(message, client->connection->remote_address_info_);
|
|
}
|
|
} else {
|
|
continue; /* we've no clue */
|
|
}
|
|
}
|
|
|
|
if(client->state != ConnectionState::DISCONNECTED){
|
|
client->connection->handle_incoming_datagram(read_buffer.view(0, bytes_read));
|
|
client = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef USE_TIMER
|
|
#ifdef ALARM_TIMER
|
|
#undef ALARM_TIMER
|
|
#endif
|
|
#define ALARM_TIMER(...)
|
|
#endif
|
|
|
|
template <int MHS>
|
|
struct IOData {
|
|
int file_descriptor = 0;
|
|
iovec vector{};
|
|
struct msghdr message{};
|
|
char message_headers[MHS]{};
|
|
|
|
IOData() {
|
|
/* Speed is key here, we dont need to zero paddings!
|
|
memset(&this->vector, 0, sizeof(this->vector));
|
|
memset(&this->message, 0, sizeof(this->message));
|
|
memset(this->message_headers, 0, sizeof(this->message_headers));
|
|
*/
|
|
|
|
this->vector.iov_base = nullptr;
|
|
this->vector.iov_len = 0;
|
|
|
|
this->message.msg_name = nullptr;
|
|
this->message.msg_namelen = 0;
|
|
|
|
this->message.msg_iov = &vector;
|
|
this->message.msg_iovlen = 1;
|
|
|
|
this->message.msg_control = this->message_headers;
|
|
this->message.msg_controllen = sizeof(this->message_headers);
|
|
}
|
|
};
|
|
|
|
template <int MHS>
|
|
inline ssize_t write_datagram(IOData<MHS>& io, const sockaddr_storage& address, udp::pktinfo_storage* info, size_t length, const void* buffer) {
|
|
io.message.msg_flags = 0;
|
|
io.message.msg_name = (void*) &address;
|
|
io.message.msg_namelen = address.ss_family == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
|
|
|
|
io.vector.iov_len = length;
|
|
io.vector.iov_base = (void*) buffer;
|
|
|
|
if(info) {
|
|
auto cmsg = CMSG_FIRSTHDR(&io.message);
|
|
if(address.ss_family == AF_INET) {
|
|
cmsg->cmsg_level = IPPROTO_IP;
|
|
cmsg->cmsg_type = IP_PKTINFO;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
|
|
|
|
memcpy(CMSG_DATA(cmsg), info, sizeof(in_pktinfo));
|
|
|
|
io.message.msg_controllen = CMSG_SPACE(sizeof(in_pktinfo));
|
|
} else if(address.ss_family == AF_INET6) {
|
|
cmsg->cmsg_level = IPPROTO_IPV6;
|
|
cmsg->cmsg_type = IPV6_PKTINFO;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
|
|
|
|
memcpy(CMSG_DATA(cmsg), info, sizeof(in6_pktinfo));
|
|
|
|
io.message.msg_controllen = CMSG_SPACE(sizeof(in6_pktinfo));
|
|
} else if(address.ss_family == 0)
|
|
return length; /* address is unset (testing ip loss i guess) */
|
|
} else {
|
|
io.message.msg_controllen = 0;
|
|
}
|
|
|
|
auto status = sendmsg(io.file_descriptor, &io.message, 0);
|
|
if(status< 0 && errno == EINVAL) {
|
|
/* may something is wrong here */
|
|
status = send(io.file_descriptor, buffer, length, 0);
|
|
if(status < 0)
|
|
return -0xFEB;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
void VoiceServer::handleMessageWrite(int fd, short events, void *_event_handle) {
|
|
using WBufferPopResult = server::udp::PacketEncoder::BufferPopResult;
|
|
auto event_handle = (io::IOEventLoopEntry*) _event_handle;
|
|
auto voice_server = event_handle->voice_server;
|
|
|
|
bool retrigger = false;
|
|
|
|
IOData<0x100> io{};
|
|
io.file_descriptor = fd;
|
|
|
|
{ /* write and process clients */
|
|
shared_ptr<VoiceClient> client;
|
|
protocol::OutgoingServerPacket* packet;
|
|
WBufferPopResult client_wbuffer_state;
|
|
bool more_clients;
|
|
|
|
auto write_timeout = system_clock::now() + microseconds(2500); /* read 2.5ms long at a time or 'till nothing more is there */
|
|
while(system_clock::now() <= write_timeout){
|
|
if(!client) {
|
|
auto client_queue_state = event_handle->pop_voice_write_queue(client); /* we need a new client, the old client has nothing more to do */
|
|
if(client_queue_state == 2)
|
|
break;
|
|
|
|
assert(client);
|
|
more_clients = (bool) client_queue_state;
|
|
}
|
|
|
|
while(system_clock::now() <= write_timeout) {
|
|
packet = nullptr;
|
|
client_wbuffer_state = client->connection->packet_encoder().pop_write_buffer(packet);
|
|
if(!packet) {
|
|
assert(client_wbuffer_state == WBufferPopResult::DRAINED);
|
|
break;
|
|
}
|
|
|
|
ssize_t res = write_datagram(io, client->remote_address, &client->connection->remote_address_info_, packet->packet_length(), packet->packet_data());
|
|
if(res != packet->packet_length()) {
|
|
if(errno == EAGAIN) {
|
|
logCritical(voice_server->server->getServerId(), "Failed to write datagram packet for client {} (EAGAIN).", client->getLoggingPeerIp() + ":" + to_string(client->getPeerPort()));
|
|
packet->unref();
|
|
return;
|
|
} else if(errno == EINVAL || res == -0xFEB) {
|
|
/* needs more debug */
|
|
auto voice_client = dynamic_pointer_cast<VoiceClient>(client);
|
|
logCritical(
|
|
voice_server->server->getServerId(),
|
|
"Failed to write datagram packet ({} @ {}) for client {} ({}) {}. Dropping packet! Extra data: [fd: {}/{}, supposed socket: {}/{} => {}, client family: {}, socket family: {}]",
|
|
packet->packet_length(), packet->packet_data(),
|
|
client->getLoggingPeerIp() + ":" + to_string(client->getPeerPort()),
|
|
strerror(errno),
|
|
res,
|
|
fd,
|
|
event_handle->file_descriptor,
|
|
voice_client->connection->socket_id(),
|
|
event_handle->socket_id,
|
|
voice_server->io->resolve_file_descriptor(voice_client),
|
|
voice_client->isAddressV4() ? "v4" : voice_client->isAddressV6() ? "v6" : "v?",
|
|
event_handle->family == AF_INET ? "v4" : "v6"
|
|
);
|
|
} else {
|
|
logCritical(
|
|
voice_server->server->getServerId(),
|
|
"Failed to write datagram packet for client {} (errno: {} message: {}). Dropping packet!",
|
|
client->getLoggingPeerIp() + ":" + to_string(client->getPeerPort()),
|
|
errno,
|
|
strerror(errno)
|
|
);
|
|
}
|
|
packet->unref();
|
|
break;
|
|
}
|
|
packet->unref();
|
|
if(client_wbuffer_state == WBufferPopResult::DRAINED)
|
|
break;
|
|
}
|
|
|
|
if(client_wbuffer_state == WBufferPopResult::MORE_AVAILABLE) {
|
|
/* we exceeded the max write time, rescheduling write */
|
|
voice_server->triggerWrite(client);
|
|
}
|
|
client.reset();
|
|
}
|
|
|
|
retrigger |= more_clients;
|
|
|
|
}
|
|
|
|
/* write all manually specified datagram packets */
|
|
{
|
|
auto write_timeout = system_clock::now() + microseconds(2500); /* read 2.5ms long at a time or 'till nothing more is there */
|
|
udp::DatagramPacket* packet;
|
|
|
|
while(system_clock::now() <= write_timeout && (packet = event_handle->pop_dg_write_queue())) {
|
|
ssize_t res = write_datagram(io, packet->address, &packet->pktinfo, packet->data_length, packet->data);
|
|
if(res != packet->data_length) {
|
|
if(errno == EAGAIN) {
|
|
event_handle->push_dg_write_queue(packet);
|
|
} else
|
|
udp::DatagramPacket::destroy(packet);
|
|
|
|
logError(voice_server->server->getServerId(), "Failed to send datagram. Wrote {} out of {}. {}/{}", res, packet->data_length, errno, strerror(errno));
|
|
retrigger = false;
|
|
break;
|
|
}
|
|
udp::DatagramPacket::destroy(packet);
|
|
}
|
|
|
|
retrigger |= packet != nullptr; /* memory stored at packet is not accessible anymore. But anyways pop_dg_write_queue returns 0 if there is nothing more */
|
|
}
|
|
if(retrigger)
|
|
event_add(event_handle->event_write, nullptr);
|
|
}
|
|
|
|
void VoiceServer::send_datagram(int socket, udp::DatagramPacket* packet) {
|
|
this->io->send_datagram(packet, socket);
|
|
} |