v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
25
source/server/CMakeLists.txt
Normal file
25
source/server/CMakeLists.txt
Normal file
|
@ -0,0 +1,25 @@
|
|||
INCLUDE_DIRECTORIES (
|
||||
${STAR_EXTERN_INCLUDES}
|
||||
${STAR_CORE_INCLUDES}
|
||||
${STAR_BASE_INCLUDES}
|
||||
${STAR_PLATFORM_INCLUDES}
|
||||
${STAR_GAME_INCLUDES}
|
||||
)
|
||||
|
||||
SET (star_server_HEADERS
|
||||
StarServerQueryThread.hpp
|
||||
StarServerRconClient.hpp
|
||||
StarServerRconThread.hpp
|
||||
)
|
||||
|
||||
SET (star_server_SOURCES
|
||||
StarServerQueryThread.cpp
|
||||
StarServerRconClient.cpp
|
||||
StarServerRconThread.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
ADD_EXECUTABLE (starbound_server
|
||||
$<TARGET_OBJECTS:star_extern> $<TARGET_OBJECTS:star_core> $<TARGET_OBJECTS:star_base> $<TARGET_OBJECTS:star_game>
|
||||
${star_server_HEADERS} ${star_server_SOURCES})
|
||||
TARGET_LINK_LIBRARIES (starbound_server ${STAR_EXT_LIBS})
|
278
source/server/StarServerQueryThread.cpp
Normal file
278
source/server/StarServerQueryThread.cpp
Normal file
|
@ -0,0 +1,278 @@
|
|||
#include "StarServerQueryThread.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarConfiguration.hpp"
|
||||
#include "StarVersion.hpp"
|
||||
#include "StarUniverseServer.hpp"
|
||||
#include "StarIterator.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ServerQueryThread::ServerQueryThread(UniverseServer* universe, HostAddressWithPort const& bindAddress)
|
||||
: Thread("QueryServer"),
|
||||
m_universe(universe),
|
||||
m_queryServer(bindAddress),
|
||||
m_stop(true),
|
||||
m_lastChallengeCheck(Time::monotonicMilliseconds()) {
|
||||
m_playersResponse.resize(A2S_PACKET_SIZE);
|
||||
m_playersResponse.setByteOrder(ByteOrder::LittleEndian);
|
||||
m_playersResponse.setNullTerminatedStrings(true);
|
||||
|
||||
m_rulesResponse.resize(A2S_PACKET_SIZE);
|
||||
m_rulesResponse.setByteOrder(ByteOrder::LittleEndian);
|
||||
m_rulesResponse.setNullTerminatedStrings(true);
|
||||
|
||||
m_generalResponse.resize(A2S_PACKET_SIZE);
|
||||
m_generalResponse.setByteOrder(ByteOrder::LittleEndian);
|
||||
m_generalResponse.setNullTerminatedStrings(true);
|
||||
|
||||
m_serverPort = 0;
|
||||
m_lastActiveTime = 0;
|
||||
|
||||
auto& root = Root::singleton();
|
||||
auto cfg = root.configuration();
|
||||
|
||||
m_maxPlayers = cfg->get("maxPlayers").toUInt();
|
||||
m_serverName = cfg->get("serverName").toString();
|
||||
|
||||
m_lastPlayersResponse = 0;
|
||||
m_lastRulesResponse = 0;
|
||||
}
|
||||
|
||||
ServerQueryThread::~ServerQueryThread() {
|
||||
stop();
|
||||
join();
|
||||
}
|
||||
|
||||
void ServerQueryThread::start() {
|
||||
m_stop = false;
|
||||
Thread::start();
|
||||
m_lastActiveTime = Time::monotonicMilliseconds();
|
||||
}
|
||||
|
||||
void ServerQueryThread::stop() {
|
||||
m_stop = true;
|
||||
m_queryServer.close();
|
||||
}
|
||||
|
||||
void ServerQueryThread::sendTo(HostAddressWithPort const& address, DataStreamBuffer* ds) {
|
||||
m_queryServer.send(address, ds->ptr(), ds->size());
|
||||
}
|
||||
|
||||
uint8_t ServerQueryThread::serverPlayerCount() {
|
||||
return m_universe->numberOfClients();
|
||||
}
|
||||
|
||||
bool ServerQueryThread::serverPassworded() {
|
||||
// TODO: implement
|
||||
return false;
|
||||
}
|
||||
|
||||
String ServerQueryThread::serverWorldNames() {
|
||||
auto activeWorlds = m_universe->activeWorlds();
|
||||
if (activeWorlds.empty())
|
||||
return String("Unknown");
|
||||
|
||||
return StringList(activeWorlds.transformed(printWorldId)).join(",");
|
||||
}
|
||||
|
||||
const char* ServerQueryThread::serverPlugins() {
|
||||
// TODO: implement
|
||||
return "none";
|
||||
}
|
||||
|
||||
bool ServerQueryThread::processPacket(HostAddressWithPort const& address, char const* data, size_t length) {
|
||||
uint8_t* buf = (uint8_t*)data;
|
||||
if (length < 5 || buf[0] != 0xff || buf[1] != 0xff || buf[2] != 0xff || buf[3] != 0xff) {
|
||||
// short packet or missing header
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process packet
|
||||
switch (buf[4]) {
|
||||
case A2S_INFO_REQUEST: {
|
||||
// We use -6 and not -5 as the string should be NULL terminated
|
||||
// but instead of the std::string constructor stopping at the NULL
|
||||
// it includes it :(
|
||||
std::string str((const char*)(buf + 5), length - 6);
|
||||
if (str.compare(A2S_INFO_REQUEST_STRING) != 0) {
|
||||
// Invalid request
|
||||
return false;
|
||||
}
|
||||
|
||||
m_generalResponse.clear();
|
||||
m_generalResponse << A2S_HEAD_INT << A2S_INFO_REPLY << A2S_VERSION << m_serverName << serverWorldNames()
|
||||
<< GAME_DIR << GAME_DESC << A2S_APPID // Should be SteamAppId but this isn't a short :(
|
||||
<< serverPlayerCount() << m_maxPlayers << (uint8_t)0x00 // bots
|
||||
<< A2S_TYPE_DEDICATED // dedicated
|
||||
#ifdef STAR_SYSTEM_FAMILY_WINDOWS
|
||||
<< A2S_ENV_WINDOWS // os
|
||||
#else
|
||||
<< A2S_ENV_LINUX // os
|
||||
#endif
|
||||
<< serverPassworded() << A2S_VAC_OFF // secure
|
||||
<< StarVersionString << A2S_EDF_PORT // EDF
|
||||
<< m_serverPort;
|
||||
|
||||
sendTo(address, &m_generalResponse);
|
||||
return true;
|
||||
}
|
||||
case A2S_CHALLENGE_REQUEST:
|
||||
sendChallenge(address);
|
||||
return true;
|
||||
|
||||
case A2S_PLAYER_REQUEST:
|
||||
if (challengeRequest(address, data, length))
|
||||
return true;
|
||||
|
||||
if (!validChallenge(address, data, length))
|
||||
return false;
|
||||
|
||||
buildPlayerResponse();
|
||||
sendTo(address, &m_playersResponse);
|
||||
return true;
|
||||
|
||||
case A2S_RULES_REQUEST:
|
||||
if (challengeRequest(address, data, length))
|
||||
return true;
|
||||
|
||||
if (!validChallenge(address, data, length))
|
||||
return false;
|
||||
|
||||
buildRuleResponse();
|
||||
sendTo(address, &m_rulesResponse);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ServerQueryThread::buildPlayerResponse() {
|
||||
int64_t now = Time::monotonicMilliseconds();
|
||||
if (now < m_lastPlayersResponse + responseCacheTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto clientIds = m_universe->clientIds();
|
||||
uint8_t cnt = (uint8_t)clientIds.count();
|
||||
int32_t kills = 0; // Not currently supported
|
||||
float timeConnected = 60; // Not supported defaults to 1min
|
||||
|
||||
m_playersResponse.clear();
|
||||
m_playersResponse << A2S_HEAD_INT << A2S_PLAYER_REPLY << cnt;
|
||||
|
||||
uint8_t i = 0;
|
||||
for (auto clientId : clientIds) {
|
||||
m_playersResponse << i++ << m_universe->clientNick(clientId) << kills << timeConnected;
|
||||
}
|
||||
|
||||
m_lastPlayersResponse = now;
|
||||
}
|
||||
|
||||
void ServerQueryThread::buildRuleResponse() {
|
||||
int64_t now = Time::monotonicMilliseconds();
|
||||
if (now < m_lastRulesResponse + responseCacheTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t cnt = 1;
|
||||
m_rulesResponse.clear();
|
||||
m_rulesResponse << A2S_HEAD_INT << A2S_RULES_REPLY << cnt << "plugins" << serverPlugins();
|
||||
|
||||
m_lastRulesResponse = now;
|
||||
}
|
||||
|
||||
void ServerQueryThread::sendChallenge(HostAddressWithPort const& address) {
|
||||
auto challenge = make_shared<RequestChallenge>();
|
||||
|
||||
m_validChallenges[address.address()] = challenge;
|
||||
m_generalResponse.clear();
|
||||
m_generalResponse << A2S_HEAD_INT << A2S_CHALLENGE_RESPONSE << challenge->getChallenge();
|
||||
|
||||
sendTo(address, &m_generalResponse);
|
||||
}
|
||||
|
||||
void ServerQueryThread::pruneChallenges() {
|
||||
int64_t now = Time::monotonicMilliseconds();
|
||||
if (now < m_lastChallengeCheck + challengeCheckInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto expire = now - challengeCheckInterval;
|
||||
auto it = makeSMutableMapIterator(m_validChallenges);
|
||||
while (it.hasNext()) {
|
||||
auto const& pair = it.next();
|
||||
if (pair.second->before(expire)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
m_lastChallengeCheck = now;
|
||||
}
|
||||
|
||||
void ServerQueryThread::run() {
|
||||
HostAddressWithPort udpAddress;
|
||||
char udpData[MaxUdpData];
|
||||
while (!m_stop) {
|
||||
try {
|
||||
auto len = m_queryServer.receive(&udpAddress, udpData, MaxUdpData, 100);
|
||||
pruneChallenges();
|
||||
if (len != 0)
|
||||
processPacket(udpAddress, udpData, len);
|
||||
} catch (SocketClosedException const&) {
|
||||
} catch (std::exception const& e) {
|
||||
Logger::error("ServerQueryThread exception caught: %s", outputException(e, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerQueryThread::RequestChallenge::RequestChallenge()
|
||||
: m_time(Time::monotonicMilliseconds()), m_challenge(Random::randi32()) {}
|
||||
|
||||
bool ServerQueryThread::RequestChallenge::before(uint64_t time) {
|
||||
return m_time < time;
|
||||
}
|
||||
|
||||
int ServerQueryThread::RequestChallenge::getChallenge() {
|
||||
return m_challenge;
|
||||
}
|
||||
|
||||
bool ServerQueryThread::validChallenge(HostAddressWithPort const& address, char const* data, size_t len) {
|
||||
if (len != 9) {
|
||||
// too much or too little data
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_validChallenges.count(address.address()) == 0) {
|
||||
// Don't know this source address ignore
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t const* b = (uint8_t const*)data;
|
||||
int32_t challenge = ((int32_t)b[8] & 0xff) << 24 | ((int32_t)b[7] & 0xff) << 16 | ((int32_t)b[6] & 0xff) << 8
|
||||
| ((int32_t)b[5] & 0xff);
|
||||
// Note: No byte order swapping needed as protcol performs no conversion
|
||||
if (m_validChallenges.get(address.address())->getChallenge() != challenge) {
|
||||
// Challenges didnt match ignore
|
||||
return false;
|
||||
}
|
||||
|
||||
// All good
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ServerQueryThread::challengeRequest(HostAddressWithPort const& address, char const* data, size_t len) {
|
||||
if (len != 9) {
|
||||
// too much or too little data
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t const* buf = (uint8_t const*)data;
|
||||
if ((buf[5] == 0xff) && (buf[6] == 0xff) && (buf[7] == 0xff) && (buf[8] == 0xff)) {
|
||||
sendChallenge(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
108
source/server/StarServerQueryThread.hpp
Normal file
108
source/server/StarServerQueryThread.hpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#ifndef STAR_SERVER_QUERY_THREAD_HPP
|
||||
#define STAR_SERVER_QUERY_THREAD_HPP
|
||||
|
||||
#include "StarThread.hpp"
|
||||
#include "StarHostAddress.hpp"
|
||||
#include "StarUdp.hpp"
|
||||
#include "StarMap.hpp"
|
||||
#include "StarDataStreamDevices.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(UniverseServer);
|
||||
STAR_CLASS(ServerQueryThread);
|
||||
|
||||
class ServerQueryThread : public Thread {
|
||||
public:
|
||||
ServerQueryThread(UniverseServer* universe, HostAddressWithPort const& bindAddress);
|
||||
~ServerQueryThread();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
virtual void run();
|
||||
|
||||
private:
|
||||
static const uint8_t A2A_PING_REQUEST = 0x69;
|
||||
static const uint8_t A2A_PING_REPLY = 0x6a;
|
||||
static const uint8_t A2S_CHALLENGE_REQUEST = 0x57;
|
||||
static const uint8_t A2S_CHALLENGE_RESPONSE = 0x41;
|
||||
static const uint8_t A2S_INFO_REQUEST = 0x54;
|
||||
static const uint8_t A2S_INFO_REPLY = 0x49;
|
||||
static const uint8_t A2S_PLAYER_REQUEST = 0x55;
|
||||
static const uint8_t A2S_PLAYER_REPLY = 0x44;
|
||||
static const uint8_t A2S_RULES_REQUEST = 0x56;
|
||||
static const uint8_t A2S_RULES_REPLY = 0x45;
|
||||
static const uint8_t A2S_VERSION = 0x07;
|
||||
static const uint8_t A2S_STR_TERM = 0x00;
|
||||
static const uint8_t A2S_EDF_GID = 0x01;
|
||||
static const uint8_t A2S_EDF_SID = 0x10;
|
||||
static const uint8_t A2S_EDF_TAGS = 0x20;
|
||||
static const uint8_t A2S_EDF_STV = 0x40;
|
||||
static const uint8_t A2S_EDF_PORT = 0x80;
|
||||
static const uint8_t A2S_ENV_WINDOWS = 'W';
|
||||
static const uint8_t A2S_ENV_LINUX = 'L';
|
||||
static const uint8_t A2S_TYPE_DEDICATED = 'D';
|
||||
static const uint8_t A2S_TYPE_LISTEN = 'L';
|
||||
static const uint8_t A2S_TYPE_TV = 'P';
|
||||
static const uint8_t A2S_VAC_OFF = 0x00;
|
||||
static const uint8_t A2S_VAC_ON = 0x01;
|
||||
static constexpr const char* A2S_INFO_REQUEST_STRING = "Source Engine Query";
|
||||
static const uint16_t A2S_APPID = (uint16_t)0xfffe;
|
||||
static const uint16_t A2S_PACKET_SIZE = (uint16_t)0x4e0;
|
||||
static const uint32_t A2S_HEAD_INT = 0xffffffff;
|
||||
static constexpr const char* GAME_DIR = "starbound";
|
||||
static constexpr const char* GAME_DESC = "Starbound";
|
||||
static constexpr const char* GAME_TYPE = "SMP";
|
||||
static const int32_t challengeCheckInterval = 30000;
|
||||
static const int32_t responseCacheTime = 5000;
|
||||
|
||||
void sendTo(HostAddressWithPort const& address, DataStreamBuffer* ds);
|
||||
bool processPacket(HostAddressWithPort const& address, char const* data, size_t length);
|
||||
void buildPlayerResponse();
|
||||
void buildRuleResponse();
|
||||
bool validChallenge(HostAddressWithPort const& address, char const* data, size_t length);
|
||||
void sendChallenge(HostAddressWithPort const& address);
|
||||
void pruneChallenges();
|
||||
bool challengeRequest(HostAddressWithPort const& address, char const* data, size_t length);
|
||||
|
||||
// Server API
|
||||
uint8_t serverPlayerCount();
|
||||
bool serverPassworded();
|
||||
const char* serverPlugins();
|
||||
String serverWorldNames();
|
||||
|
||||
UniverseServer* m_universe;
|
||||
UdpServer m_queryServer;
|
||||
bool m_stop;
|
||||
DataStreamBuffer m_playersResponse;
|
||||
DataStreamBuffer m_rulesResponse;
|
||||
DataStreamBuffer m_generalResponse;
|
||||
|
||||
class RequestChallenge {
|
||||
public:
|
||||
RequestChallenge();
|
||||
bool before(uint64_t time);
|
||||
int32_t getChallenge();
|
||||
|
||||
private:
|
||||
uint64_t m_time;
|
||||
int32_t m_challenge;
|
||||
};
|
||||
|
||||
uint16_t m_serverPort;
|
||||
uint8_t m_maxPlayers;
|
||||
String m_serverName;
|
||||
HashMap<HostAddress, shared_ptr<RequestChallenge>> m_validChallenges;
|
||||
int64_t m_lastChallengeCheck;
|
||||
int64_t m_lastPlayersResponse;
|
||||
int64_t m_lastRulesResponse;
|
||||
int64_t m_lastActiveTime;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
145
source/server/StarServerRconClient.cpp
Normal file
145
source/server/StarServerRconClient.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
#include "StarServerRconThread.hpp"
|
||||
#include "StarServerRconClient.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarConfiguration.hpp"
|
||||
#include "StarUniverseServer.hpp"
|
||||
#include "StarLexicalCast.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ServerRconClient::ServerRconClient(UniverseServer* universe, TcpSocketPtr socket)
|
||||
: Thread("RconClient"),
|
||||
m_universe(universe),
|
||||
m_socket(socket),
|
||||
m_packetBuffer(MaxPacketSize),
|
||||
m_stop(true),
|
||||
m_authed(false) {
|
||||
auto& root = Root::singleton();
|
||||
auto cfg = root.configuration();
|
||||
|
||||
m_packetBuffer.setByteOrder(ByteOrder::LittleEndian);
|
||||
m_packetBuffer.setNullTerminatedStrings(true);
|
||||
|
||||
m_rconPassword = cfg->get("rconServerPassword").toString();
|
||||
}
|
||||
|
||||
ServerRconClient::~ServerRconClient() {
|
||||
stop();
|
||||
join();
|
||||
}
|
||||
|
||||
String ServerRconClient::handleCommand(String commandLine) {
|
||||
String command = commandLine.extract();
|
||||
|
||||
if (command == "echo") {
|
||||
return commandLine;
|
||||
} else if (command == "broadcast" || command == "say") {
|
||||
m_universe->adminBroadcast(commandLine);
|
||||
return strf("OK: said %s", commandLine);
|
||||
} else if (command == "stop") {
|
||||
m_universe->stop();
|
||||
return "OK: shutting down";
|
||||
} else {
|
||||
return m_universe->adminCommand(strf("%s %s", command, commandLine));
|
||||
}
|
||||
}
|
||||
|
||||
void ServerRconClient::receive(size_t size) {
|
||||
m_packetBuffer.reset(size);
|
||||
auto ptr = m_packetBuffer.ptr();
|
||||
while (size > 0) {
|
||||
size_t r = m_socket->receive(ptr, size);
|
||||
if (r == 0)
|
||||
throw NoMoreRequests();
|
||||
size -= r;
|
||||
ptr += r;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerRconClient::send(uint32_t requestId, uint32_t cmd, String str) {
|
||||
m_packetBuffer.clear();
|
||||
m_packetBuffer << (uint32_t)(str.utf8Size() + 10) << requestId << cmd << str << (uint8_t)0x00;
|
||||
m_socket->send(m_packetBuffer.ptr(), m_packetBuffer.size());
|
||||
}
|
||||
|
||||
void ServerRconClient::sendAuthFailure() {
|
||||
send(SERVERDATA_AUTH_FAILURE, SERVERDATA_AUTH_RESPONSE, "");
|
||||
}
|
||||
|
||||
void ServerRconClient::sendCmdResponse(uint32_t requestId, String response) {
|
||||
size_t len = response.length();
|
||||
// Always send at least one packet even if the response was blank
|
||||
do {
|
||||
auto dataLen = (len >= MaxPacketSize) ? MaxPacketSize : len;
|
||||
send(requestId, SERVERDATA_RESPONSE_VALUE, response.substr(0, dataLen));
|
||||
response = response.substr(dataLen);
|
||||
len = response.length();
|
||||
} while (len > 0);
|
||||
}
|
||||
|
||||
void ServerRconClient::start() {
|
||||
m_stop = false;
|
||||
Thread::start();
|
||||
}
|
||||
|
||||
void ServerRconClient::stop() {
|
||||
m_stop = true;
|
||||
m_socket->close();
|
||||
}
|
||||
|
||||
void ServerRconClient::processRequest() {
|
||||
receive(4);
|
||||
uint32_t size = m_packetBuffer.read<uint32_t>();
|
||||
|
||||
receive(size);
|
||||
uint32_t requestId;
|
||||
m_packetBuffer >> requestId;
|
||||
|
||||
uint32_t cmd;
|
||||
m_packetBuffer >> cmd;
|
||||
|
||||
switch (cmd) {
|
||||
case SERVERDATA_AUTH: {
|
||||
String password;
|
||||
m_packetBuffer >> password;
|
||||
if (!m_rconPassword.empty() && m_rconPassword.equals(password)) {
|
||||
m_authed = true;
|
||||
send(requestId, SERVERDATA_RESPONSE_VALUE);
|
||||
send(requestId, SERVERDATA_AUTH_RESPONSE);
|
||||
} else {
|
||||
m_authed = false;
|
||||
sendAuthFailure();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SERVERDATA_EXECCOMMAND:
|
||||
if (m_authed) {
|
||||
String command;
|
||||
m_packetBuffer >> command;
|
||||
try {
|
||||
Logger::info("RCON %s: %s", m_socket->remoteAddress(), command);
|
||||
sendCmdResponse(requestId, handleCommand(command));
|
||||
} catch (std::exception const& e) {
|
||||
sendCmdResponse(requestId, strf("RCON: Error executing: %s: %s", command, outputException(e, true)));
|
||||
}
|
||||
} else {
|
||||
sendAuthFailure();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sendCmdResponse(requestId, strf("Unknown request %06x", cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void ServerRconClient::run() {
|
||||
try {
|
||||
while (!m_stop)
|
||||
processRequest();
|
||||
} catch (NoMoreRequests const&) {
|
||||
} catch (std::exception const& e) {
|
||||
Logger::error("ServerRconClient exception caught: %s", outputException(e, false));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
51
source/server/StarServerRconClient.hpp
Normal file
51
source/server/StarServerRconClient.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef STAR_SERVER_RCON_CLIENT_HPP
|
||||
#define STAR_SERVER_RCON_CLIENT_HPP
|
||||
|
||||
#include "StarThread.hpp"
|
||||
#include "StarTcp.hpp"
|
||||
#include "StarMap.hpp"
|
||||
#include "StarDataStreamDevices.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class UniverseServer;
|
||||
|
||||
class ServerRconClient : public Thread {
|
||||
public:
|
||||
static const uint32_t SERVERDATA_AUTH = 0x03;
|
||||
static const uint32_t SERVERDATA_EXECCOMMAND = 0x02;
|
||||
static const uint32_t SERVERDATA_RESPONSE_VALUE = 0x00;
|
||||
static const uint32_t SERVERDATA_AUTH_RESPONSE = 0x02;
|
||||
static const uint32_t SERVERDATA_AUTH_FAILURE = 0xffffffff;
|
||||
ServerRconClient(UniverseServer* universe, TcpSocketPtr socket);
|
||||
~ServerRconClient();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
virtual void run();
|
||||
|
||||
private:
|
||||
static size_t const MaxPacketSize = 4096;
|
||||
STAR_EXCEPTION(NoMoreRequests, StarException);
|
||||
|
||||
void receive(size_t size);
|
||||
void send(uint32_t requestId, uint32_t cmd, String str = "");
|
||||
void sendAuthFailure();
|
||||
void sendCmdResponse(uint32_t requestId, String response);
|
||||
void closeSocket();
|
||||
void processRequest();
|
||||
String handleCommand(String commandLine);
|
||||
|
||||
UniverseServer* m_universe;
|
||||
TcpSocketPtr m_socket;
|
||||
DataStreamBuffer m_packetBuffer;
|
||||
bool m_stop;
|
||||
bool m_authed;
|
||||
String m_rconPassword;
|
||||
};
|
||||
typedef shared_ptr<ServerRconClient> ServerRconClientPtr;
|
||||
}
|
||||
|
||||
#endif
|
62
source/server/StarServerRconThread.cpp
Normal file
62
source/server/StarServerRconThread.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include "StarServerRconThread.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarConfiguration.hpp"
|
||||
#include "StarUniverseServer.hpp"
|
||||
#include "StarServerRconClient.hpp"
|
||||
#include "StarIterator.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ServerRconThread::ServerRconThread(UniverseServer* universe, HostAddressWithPort const& address)
|
||||
: Thread("RconServer"), m_universe(universe), m_rconServer(address), m_stop(true) {
|
||||
if (Root::singleton().configuration()->get("rconServerPassword").toString().empty())
|
||||
Logger::warn("rconServerPassword is not configured requests will NOT be processed");
|
||||
}
|
||||
|
||||
ServerRconThread::~ServerRconThread() {
|
||||
stop();
|
||||
join();
|
||||
}
|
||||
|
||||
void ServerRconThread::clearClients(bool all) {
|
||||
auto it = makeSMutableMapIterator(m_clients);
|
||||
while (it.hasNext()) {
|
||||
auto const& pair = it.next();
|
||||
auto client = pair.second;
|
||||
if (all)
|
||||
client->stop();
|
||||
else if (!client->isRunning())
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
void ServerRconThread::start() {
|
||||
m_stop = false;
|
||||
Thread::start();
|
||||
}
|
||||
|
||||
void ServerRconThread::stop() {
|
||||
m_stop = true;
|
||||
m_rconServer.stop();
|
||||
clearClients(true);
|
||||
}
|
||||
|
||||
void ServerRconThread::run() {
|
||||
try {
|
||||
auto timeout = Root::singleton().configuration()->get("rconServerTimeout").toInt();
|
||||
while (!m_stop) {
|
||||
if (auto client = m_rconServer.accept(100)) {
|
||||
client->setTimeout(timeout);
|
||||
auto rconClient = make_shared<ServerRconClient>(m_universe, client);
|
||||
rconClient->start();
|
||||
m_clients[client->remoteAddress().address()] = rconClient;
|
||||
clearClients();
|
||||
}
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
Logger::error("ServerRconThread exception caught: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
36
source/server/StarServerRconThread.hpp
Normal file
36
source/server/StarServerRconThread.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef STAR_SERVER_RCON_THREAD_HPP
|
||||
#define STAR_SERVER_RCON_THREAD_HPP
|
||||
|
||||
#include "StarThread.hpp"
|
||||
#include "StarTcp.hpp"
|
||||
#include "StarMap.hpp"
|
||||
#include "StarServerRconClient.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(UniverseServer);
|
||||
STAR_CLASS(ServerRconThread);
|
||||
|
||||
class ServerRconThread : public Thread {
|
||||
public:
|
||||
ServerRconThread(UniverseServer* universe, HostAddressWithPort const& address);
|
||||
~ServerRconThread();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
virtual void run();
|
||||
|
||||
private:
|
||||
void clearClients(bool all = false);
|
||||
|
||||
UniverseServer* m_universe;
|
||||
TcpServer m_rconServer;
|
||||
bool m_stop;
|
||||
HashMap<HostAddress, ServerRconClientPtr> m_clients;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
94
source/server/main.cpp
Normal file
94
source/server/main.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#include "StarFile.hpp"
|
||||
#include "StarRandom.hpp"
|
||||
#include "StarLexicalCast.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarUniverseServer.hpp"
|
||||
#include "StarRootLoader.hpp"
|
||||
#include "StarConfiguration.hpp"
|
||||
#include "StarVersion.hpp"
|
||||
#include "StarServerQueryThread.hpp"
|
||||
#include "StarServerRconThread.hpp"
|
||||
#include "StarSignalHandler.hpp"
|
||||
|
||||
using namespace Star;
|
||||
|
||||
Json const AdditionalDefaultConfiguration = Json::parseJson(R"JSON(
|
||||
{
|
||||
"configurationVersion" : {
|
||||
"server" : 4
|
||||
},
|
||||
|
||||
"runQueryServer" : false,
|
||||
"queryServerPort" : 21025,
|
||||
"queryServerBind" : "::",
|
||||
|
||||
"runRconServer" : false,
|
||||
"rconServerPort" : 21026,
|
||||
"rconServerBind" : "::",
|
||||
"rconServerPassword" : "",
|
||||
"rconServerTimeout" : 1000,
|
||||
|
||||
"allowAssetsMismatch" : true,
|
||||
"serverOverrideAssetsDigest" : null
|
||||
}
|
||||
)JSON");
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
RootLoader rootLoader({{}, AdditionalDefaultConfiguration, String("starbound_server.log"), LogLevel::Info, false, String("starbound_server.config")});
|
||||
RootUPtr root = rootLoader.commandInitOrDie(argc, argv).first;
|
||||
root->fullyLoad();
|
||||
|
||||
SignalHandler signalHandler;
|
||||
signalHandler.setHandleFatal(true);
|
||||
signalHandler.setHandleInterrupt(true);
|
||||
|
||||
auto configuration = root->configuration();
|
||||
{
|
||||
Logger::info("Server Version %s (%s) Source ID: %s Protocol: %s", StarVersionString, StarArchitectureString, StarSourceIdentifierString, StarProtocolVersion);
|
||||
|
||||
UniverseServerUPtr server = make_unique<UniverseServer>(root->toStoragePath("universe"));
|
||||
server->setListeningTcp(true);
|
||||
server->start();
|
||||
|
||||
ServerQueryThreadUPtr queryServer;
|
||||
if (configuration->get("runQueryServer").toBool()) {
|
||||
queryServer = make_unique<ServerQueryThread>(server.get(), HostAddressWithPort(configuration->get("queryServerBind").toString(), configuration->get("queryServerPort").toInt()));
|
||||
queryServer->start();
|
||||
}
|
||||
|
||||
ServerRconThreadUPtr rconServer;
|
||||
if (configuration->get("runRconServer").toBool()) {
|
||||
rconServer = make_unique<ServerRconThread>(server.get(), HostAddressWithPort(configuration->get("rconServerBind").toString(), configuration->get("rconServerPort").toInt()));
|
||||
rconServer->start();
|
||||
}
|
||||
|
||||
while (server->isRunning()) {
|
||||
if (signalHandler.interruptCaught()) {
|
||||
Logger::info("Interrupt caught!");
|
||||
server->stop();
|
||||
break;
|
||||
}
|
||||
Thread::sleep(100);
|
||||
}
|
||||
|
||||
server->join();
|
||||
|
||||
if (queryServer) {
|
||||
queryServer->stop();
|
||||
queryServer->join();
|
||||
}
|
||||
|
||||
if (rconServer) {
|
||||
rconServer->stop();
|
||||
rconServer->join();
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info("Server shutdown gracefully");
|
||||
} catch (std::exception const& e) {
|
||||
fatalException(e, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue