mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-07-19 21:25:56 +00:00
implement udp connection
This commit is contained in:
+1
-2
@@ -1,6 +1,5 @@
|
||||
#include "Http.h"
|
||||
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "LuaAPI.h"
|
||||
@@ -181,7 +180,7 @@ void Http::Server::THttpServerInstance::operator()() try {
|
||||
}
|
||||
}
|
||||
res.set_content(
|
||||
json {
|
||||
nlohmann::json {
|
||||
{ "ok", SystemsBad == 0 },
|
||||
}
|
||||
.dump(),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "LuaAPI.h"
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "TLuaEngine.h"
|
||||
|
||||
+198
-3
@@ -2,8 +2,15 @@
|
||||
#include "ClientInfo.h"
|
||||
#include "Common.h"
|
||||
#include "Environment.h"
|
||||
#include "Http.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "ProtocolVersion.h"
|
||||
#include "ServerInfo.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "Util.h"
|
||||
#include <boost/thread/synchronized_value.hpp>
|
||||
#include <cstdlib>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#if defined(BEAMMP_LINUX)
|
||||
#include <cerrno>
|
||||
@@ -30,6 +37,7 @@ Packet Client::tcp_read() {
|
||||
}
|
||||
|
||||
void Client::tcp_write(const Packet& packet) {
|
||||
beammp_tracef("Sending 0x{:x} to {}", int(packet.purpose), id);
|
||||
std::unique_lock lock(m_tcp_write_mtx);
|
||||
auto header = packet.header();
|
||||
std::vector<uint8_t> header_data(bmp::Header::SERIALIZED_SIZE);
|
||||
@@ -73,6 +81,7 @@ Client::~Client() {
|
||||
|
||||
Client::Client(ClientID id, Network& network, ip::tcp::socket&& tcp_socket)
|
||||
: id(id)
|
||||
, udp_magic(id ^ uint64_t(std::rand()) ^ uint64_t(this))
|
||||
, m_tcp_socket(std::forward<ip::tcp::socket&&>(tcp_socket))
|
||||
, m_network(network) {
|
||||
beammp_debugf("Client {} created", id);
|
||||
@@ -116,6 +125,8 @@ Packet Network::udp_read(ip::udp::endpoint& out_ep) {
|
||||
beammp_errorf("Flags are not implemented");
|
||||
return {};
|
||||
}
|
||||
packet.data.resize(header.size);
|
||||
std::copy(s_buffer.begin() + offset, s_buffer.begin() + offset + header.size, packet.data.begin());
|
||||
return packet;
|
||||
}
|
||||
|
||||
@@ -183,13 +194,76 @@ void Network::tcp_listen_main() {
|
||||
}
|
||||
|
||||
void Network::udp_read_main() {
|
||||
m_udp_socket = ip::udp::socket(m_io, ip::udp::endpoint(ip::udp::v4(), Application::Settings.Port));
|
||||
while (!*m_shutdown) {
|
||||
try {
|
||||
ip::udp::endpoint ep;
|
||||
auto packet = udp_read(ep);
|
||||
// special case for new udp connections, only happens once
|
||||
if (packet.purpose == bmp::Purpose::StartUDP) [[unlikely]] {
|
||||
auto all = boost::synchronize(m_clients, m_udp_endpoints, m_client_magics);
|
||||
auto& clients = std::get<0>(all);
|
||||
auto& endpoints = std::get<1>(all);
|
||||
auto& magics = std::get<2>(all);
|
||||
ClientID id = 0xffffffff;
|
||||
uint64_t recv_magic;
|
||||
bmp::deserialize(recv_magic, packet.data);
|
||||
if (magics->contains(recv_magic)) {
|
||||
id = magics->at(recv_magic);
|
||||
magics->erase(recv_magic);
|
||||
} else {
|
||||
beammp_debugf("Invalid magic received on UDP from [{}]:{}, ignoring.", ep.address().to_string(), ep.port());
|
||||
continue;
|
||||
}
|
||||
if (clients->contains(id)) {
|
||||
auto client = clients->at(id);
|
||||
// check if endpoint already exists for this client!
|
||||
auto iter = std::find_if(endpoints->begin(), endpoints->end(), [&](const auto& item) {
|
||||
return item.second == id;
|
||||
});
|
||||
if (iter != endpoints->end()) {
|
||||
// already exists, malicious attempt!
|
||||
beammp_debugf("[{}]:{} tried to replace {}'s UDP endpoint, ignoring.", ep.address().to_string(), ep.port(), id);
|
||||
continue;
|
||||
}
|
||||
// not yet set! nice! set!
|
||||
endpoints->emplace(ep, id);
|
||||
// now transfer them to the next state
|
||||
beammp_debugf("Client {} successfully connected via UDP", client->id);
|
||||
Packet state_change {
|
||||
.purpose = bmp::Purpose::StateChangeModDownload,
|
||||
};
|
||||
client->tcp_write(state_change);
|
||||
client->state = bmp::State::ModDownload;
|
||||
} else {
|
||||
beammp_warnf("Received magic for client who doesn't exist anymore: {}. Ignoring.", id);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Failed to UDP read: {}", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Network::disconnect(ClientID id, const std::string& msg) {
|
||||
beammp_infof("Disconnecting client {}: {}", id, msg);
|
||||
m_clients->erase(id);
|
||||
// deadlock-free algorithm to acquire a lock on all these
|
||||
// this is a little ugly but saves a headache here in the future
|
||||
auto all = boost::synchronize(m_clients, m_udp_endpoints, m_client_magics);
|
||||
auto& clients = std::get<0>(all);
|
||||
auto& endpoints = std::get<1>(all);
|
||||
auto& magics = std::get<2>(all);
|
||||
|
||||
if (clients->contains(id)) {
|
||||
auto client = clients->at(id);
|
||||
beammp_debugf("Removing client udp magic {}", client->udp_magic);
|
||||
magics->erase(client->udp_magic);
|
||||
}
|
||||
std::erase_if(*endpoints, [&](const auto& item) {
|
||||
const auto& [key, value] = item;
|
||||
return value == id;
|
||||
});
|
||||
clients->erase(id);
|
||||
}
|
||||
void Network::handle_packet(ClientID id, const Packet& packet) {
|
||||
std::shared_ptr<Client> client;
|
||||
@@ -211,6 +285,7 @@ void Network::handle_packet(ClientID id, const Packet& packet) {
|
||||
handle_identification(id, packet, client);
|
||||
break;
|
||||
case bmp::State::Authentication:
|
||||
handle_authentication(id, packet, client);
|
||||
break;
|
||||
case bmp::State::ModDownload:
|
||||
break;
|
||||
@@ -283,11 +358,131 @@ void Network::handle_identification(ClientID id, const Packet& packet, std::shar
|
||||
.purpose = bmp::StateChangeAuthentication,
|
||||
};
|
||||
client->tcp_write(auth_state);
|
||||
client->state = bmp::State::Authentication;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
beammp_errorf("Got 0x{:x} in state {}. This is not allowed disconnecting the client", uint16_t(packet.purpose), int(client->state));
|
||||
beammp_errorf("Got 0x{:x} in state {}. This is not allowed. Disconnecting the client", uint16_t(packet.purpose), int(client->state));
|
||||
disconnect(id, "invalid purpose in current state");
|
||||
}
|
||||
}
|
||||
|
||||
void Network::authenticate_user(const std::string& public_key, std::shared_ptr<Client>& client) {
|
||||
nlohmann::json AuthReq {};
|
||||
std::string auth_res_str {};
|
||||
try {
|
||||
AuthReq = nlohmann::json {
|
||||
{ "key", public_key }
|
||||
};
|
||||
|
||||
auto Target = "/pkToUser";
|
||||
unsigned int ResponseCode = 0;
|
||||
auth_res_str = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, AuthReq.dump(), "application/json", &ResponseCode);
|
||||
} catch (const std::exception& e) {
|
||||
beammp_debugf("Invalid key sent by client {}: {}", client->id, e.what());
|
||||
throw std::runtime_error("Public key was of an invalid format");
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json auth_response = nlohmann::json::parse(auth_res_str);
|
||||
|
||||
if (auth_response["username"].is_string() && auth_response["roles"].is_string()
|
||||
&& auth_response["guest"].is_boolean() && auth_response["identifiers"].is_array()) {
|
||||
|
||||
*client->name = auth_response["username"];
|
||||
*client->role = auth_response["roles"];
|
||||
*client->is_guest = auth_response["guest"];
|
||||
for (const auto& identifier : auth_response["identifiers"]) {
|
||||
auto identifier_str = std::string(identifier);
|
||||
auto identifier_sep_idx = identifier_str.find(':');
|
||||
client->identifiers->emplace(identifier_str.substr(0, identifier_sep_idx), identifier_str.substr(identifier_sep_idx + 1));
|
||||
}
|
||||
} else {
|
||||
beammp_errorf("Invalid authentication data received from authentication backend for client {}", client->id);
|
||||
throw std::runtime_error("Backend failed to authenticate the client");
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Client {} sent invalid key. Error was: {}", client->id, e.what());
|
||||
throw std::runtime_error("Invalid public key");
|
||||
}
|
||||
}
|
||||
|
||||
void Network::handle_authentication(ClientID id, const Packet& packet, std::shared_ptr<Client>& client) {
|
||||
switch (packet.purpose) {
|
||||
case bmp::Purpose::PlayerPublicKey: {
|
||||
auto public_key = std::string(packet.data.begin(), packet.data.end());
|
||||
try {
|
||||
authenticate_user(public_key, client);
|
||||
} catch (const std::exception& e) {
|
||||
// propragate to client and disconnect
|
||||
auto err = std::string(e.what());
|
||||
beammp_errorf("Client {} failed to authenticate: {}", id, err);
|
||||
Packet auth_fail_packet {
|
||||
.purpose = bmp::Purpose::AuthFailed,
|
||||
.data = std::vector<uint8_t>(err.begin(), err.end()),
|
||||
};
|
||||
client->tcp_write(auth_fail_packet);
|
||||
disconnect(id, err);
|
||||
return;
|
||||
}
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", client->name.get(), client->role.get(), client->is_guest.get(), client->identifiers.get());
|
||||
TLuaEngine::WaitForAll(Futures);
|
||||
bool NotAllowed = std::any_of(Futures.begin(), Futures.end(),
|
||||
[](const std::shared_ptr<TLuaResult>& Result) {
|
||||
return !Result->Error && Result->Result.is<int>() && bool(Result->Result.as<int>());
|
||||
});
|
||||
std::string Reason;
|
||||
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
|
||||
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
|
||||
if (!Result->Error && Result->Result.is<std::string>()) {
|
||||
Reason = Result->Result.as<std::string>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (NotAllowed) {
|
||||
Packet auth_fail_packet {
|
||||
.purpose = bmp::Purpose::PlayerRejected
|
||||
};
|
||||
client->tcp_write(auth_fail_packet);
|
||||
disconnect(id, "Rejected by a plugin");
|
||||
return;
|
||||
} else if (NotAllowedWithReason) {
|
||||
Packet auth_fail_packet {
|
||||
.purpose = bmp::Purpose::PlayerRejected,
|
||||
.data = std::vector<uint8_t>(Reason.begin(), Reason.end()),
|
||||
};
|
||||
client->tcp_write(auth_fail_packet);
|
||||
disconnect(id, fmt::format("Rejected by a plugin for reason: {}", Reason));
|
||||
return;
|
||||
}
|
||||
beammp_debugf("Client {} successfully authenticated as {} '{}'", id, client->role.get(), client->name.get());
|
||||
// send auth ok since auth succeeded
|
||||
Packet auth_ok {
|
||||
.purpose = bmp::Purpose::AuthOk,
|
||||
.data = std::vector<uint8_t>(4),
|
||||
};
|
||||
// with the player id
|
||||
bmp::serialize(client->id, auth_ok.data);
|
||||
client->tcp_write(auth_ok);
|
||||
|
||||
// save the udp magic
|
||||
m_udp_magics.emplace(client->udp_magic, client->id);
|
||||
|
||||
// send the udp start packet, which should get the client to start udp with
|
||||
// this packet as the first message
|
||||
Packet udp_start {
|
||||
.purpose = bmp::Purpose::StartUDP,
|
||||
.data = std::vector<uint8_t>(8),
|
||||
};
|
||||
bmp::serialize(client->udp_magic, udp_start.data);
|
||||
client->tcp_write(udp_start);
|
||||
// player must start udp to advance now, so no state change
|
||||
break;
|
||||
}
|
||||
default:
|
||||
beammp_errorf("Got 0x{:x} in state {}. This is not allowed. Disconnecting the client", uint16_t(packet.purpose), int(client->state));
|
||||
disconnect(id, "invalid purpose in current state");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "Common.h"
|
||||
#include "Compat.h"
|
||||
|
||||
#include "Client.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "TLuaEngine.h"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "THeartbeatThread.h"
|
||||
|
||||
#include "Client.h"
|
||||
#include "Http.h"
|
||||
//#include "SocketIO.h"
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "TLuaEngine.h"
|
||||
#include "Client.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "Http.h"
|
||||
#include "LuaAPI.h"
|
||||
|
||||
+5
-5
@@ -6,12 +6,9 @@
|
||||
#include "TConfig.h"
|
||||
#include "THeartbeatThread.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "TNetwork.h"
|
||||
#include "TPPSMonitor.h"
|
||||
#include "TPluginMonitor.h"
|
||||
#include "TResourceManager.h"
|
||||
#include "TServer.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
@@ -87,6 +84,9 @@ int BeamMPServerMain(MainArguments Arguments) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// badly seed C's rng - this is only because rand() is used here and there for unimportant stuff
|
||||
std::srand(std::time(0));
|
||||
|
||||
std::string ConfigPath = "ServerConfig.toml";
|
||||
if (Parser.FoundArgument({ "config" })) {
|
||||
auto MaybeConfigPath = Parser.GetValueOfArgument({ "config" });
|
||||
@@ -106,7 +106,7 @@ int BeamMPServerMain(MainArguments Arguments) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TConfig Config(ConfigPath);
|
||||
|
||||
if (Config.Failed()) {
|
||||
|
||||
Reference in New Issue
Block a user