Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8cdee37
use jthread to join thread on scope exit
lionkor Apr 7, 2026
7925caf
catch boost tcp's remote_endpoint() throwing and crashing the server
lionkor Apr 7, 2026
6fca901
implement connection limiter to replace manual limiting code
lionkor Apr 8, 2026
a59f729
avoid logging moved-from ip
lionkor Apr 8, 2026
a46f2d3
set moved-from ip to explicitly obvious moved-from string
lionkor Apr 8, 2026
b564224
accept without EP, explicitly (and fallibly) get the endpoint
lionkor Apr 8, 2026
0ac5da2
add connection limiter stats to `status` command
lionkor Apr 8, 2026
be6f3a2
fix spammy logs on guard release
lionkor Apr 8, 2026
58e5317
refactor ReadWithTimeout to not spawn a thread + use an fd each read
lionkor Apr 8, 2026
a3cfe47
fix http connections eating all fds
lionkor Apr 9, 2026
1f55c35
fix ReadSocketWithTimeout to use dedicated io context polled on a new…
lionkor Apr 9, 2026
be0d8d5
make connection reject msg a debug message, avoiding spam on ddos
lionkor Apr 9, 2026
c08eefd
move TIoPollThread out and use it in TServer
lionkor Apr 9, 2026
b1946ef
use ReadWithTimeout until fully completed auth
lionkor Apr 9, 2026
4da5a74
increase http curl pool to 128
lionkor Apr 9, 2026
d26e53a
handle error cases early
lionkor Apr 9, 2026
e9ce71d
make send file accept a non-blocking socket
lionkor Apr 9, 2026
7089e5d
fix race on disconnect
lionkor Apr 10, 2026
7096fe0
fix PanicHandler crashing itself with another panic inside sol2
lionkor Apr 13, 2026
e260de5
fix sol::error crash
lionkor Apr 15, 2026
66f5f2b
fix error handling from SetErrorMessageFromResult
lionkor Apr 16, 2026
58d9f2e
fix GetIdentifiers()
lionkor Apr 16, 2026
4bb7232
fix GetIdentifiers building the wrong kind of table
lionkor Apr 16, 2026
0c86466
fix missing semicolon
lionkor Apr 16, 2026
b3e8d86
refactor Lua result handling for safety
lionkor Apr 18, 2026
3e12e48
fix success/error handling in lua engine
lionkor Apr 18, 2026
98fb12b
fix holding lock during sleep
lionkor Apr 18, 2026
9ca12fc
fix std::weak_ptr locking and expiry checks
lionkor Apr 18, 2026
f820d75
fix TLuaValue handling to be less odd
lionkor Apr 19, 2026
8f2b8b2
add AGPL headers to more new files
lionkor Apr 19, 2026
dd6d90a
force enable SOL_ALL_SAFETIES_ON
lionkor Apr 19, 2026
c97d7b1
fix server cmake version
lionkor Apr 19, 2026
4045727
fix unit-tests crashing due to sol::object::~object
lionkor Apr 19, 2026
06bd719
clarify/fix std::visit compile time check
lionkor Apr 19, 2026
7c6acfd
fix never incrementing i in lua result print
lionkor Apr 19, 2026
57fe7cb
fix GCC 11 compiler/libstdc++ error
lionkor Apr 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ include(cmake/Vcpkg.cmake) # needs to happen before project()

project(
"BeamMP-Server" # replace this
VERSION 3.3.0
VERSION 3.9.2
)

include(cmake/StandardSettings.cmake)
Expand Down Expand Up @@ -39,6 +39,7 @@ set(PRJ_HEADERS
include/TConfig.h
include/TConsole.h
include/THeartbeatThread.h
include/TIoPollThread.h
include/TLuaEngine.h
include/TLuaPlugin.h
include/TNetwork.h
Expand All @@ -52,6 +53,8 @@ set(PRJ_HEADERS
include/Settings.h
include/Profiling.h
include/ChronoWrapper.h
include/TConnectionLimiter.h
include/TLuaResult.h
)
# add all source files (.cpp) to this, except the one with main()
set(PRJ_SOURCES
Expand All @@ -65,6 +68,7 @@ set(PRJ_SOURCES
src/TConfig.cpp
src/TConsole.cpp
src/THeartbeatThread.cpp
src/TIoPollThread.cpp
src/TLuaEngine.cpp
src/TLuaPlugin.cpp
src/TNetwork.cpp
Expand All @@ -78,6 +82,8 @@ set(PRJ_SOURCES
src/Settings.cpp
src/Profiling.cpp
src/ChronoWrapper.cpp
src/TConnectionLimiter.cpp
src/TLuaResult.cpp
)

find_package(Lua REQUIRED)
Expand Down
2 changes: 2 additions & 0 deletions cmake/Vcpkg.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ if(NOT DEFINED CMAKE_TOOLCHAIN_FILE)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake)
endif()

# ensure SOL2 safeties are all ON
add_compile_definitions(SOL_ALL_SAFETIES_ON=1)
16 changes: 14 additions & 2 deletions include/Client.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#pragma once

#include <atomic>
#include <chrono>
#include <memory>
#include <optional>
Expand Down Expand Up @@ -68,8 +69,11 @@ class TClient final {
std::string GetCarPositionRaw(int Ident);
void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; }
void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
void Disconnect(std::string_view Reason);
bool IsDisconnected() const { return !mSocket.is_open(); }
// Returns true only for the thread that actually performs socket shutdown/close.
[[nodiscard]] bool Disconnect(std::string_view Reason);
bool IsDisconnected() const {
return mDisconnectState.load(std::memory_order_acquire) != EDisconnectState::Connected;
}
// locks
void DeleteCar(int Ident);
[[nodiscard]] const std::unordered_map<std::string, std::string>& GetIdentifiers() const { return mIdentifiers; }
Expand Down Expand Up @@ -106,6 +110,12 @@ class TClient final {
[[nodiscard]] const std::vector<uint8_t>& GetMagic() const { return mMagic; }

private:
enum class EDisconnectState {
Connected,
Disconnecting,
Disconnected
};

void InsertVehicle(int ID, const std::string& Data);

TServer& mServer;
Expand All @@ -121,6 +131,8 @@ class TClient final {
TSetOfVehicleData mVehicleData;
SparseArray<std::string> mVehiclePosition;
std::string mName = "Unknown Client";
// Once disconnect starts, this client is terminal and its socket must be treated as dead.
std::atomic<EDisconnectState> mDisconnectState { EDisconnectState::Connected };
ip::tcp::socket mSocket;
ip::udp::endpoint mUDPAddress {};
int mUnicycleID = -1;
Expand Down
4 changes: 4 additions & 0 deletions include/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ class Application final {
static inline Version mVersion { 3, 9, 2 };
};

/// Used to static_assert in std::visit
template<class>
inline constexpr bool AlwaysFalseV = false;

void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
std::string LowerString(std::string str);

Expand Down
90 changes: 90 additions & 0 deletions include/TConnectionLimiter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2026 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#pragma once

#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>

class TConnectionLimiter {
public:
struct TStats {
size_t CurrentGlobal = 0;
size_t MaxGlobal = 0;
size_t ActiveIpBuckets = 0;
size_t CurrentMaxPerIp = 0;
size_t MaxPerIp = 0;
size_t SaturatedIpBuckets = 0;
};

class TGuard {
public:
TGuard() = default;
TGuard(TConnectionLimiter* owner, std::string ip);

TGuard(const TGuard&) = delete;
TGuard& operator=(const TGuard&) = delete;

~TGuard() { Release(); }

// not threadsafe
TGuard(TGuard&& other) noexcept { *this = std::move(other); }
// not threadsafe
TGuard& operator=(TGuard&& other) noexcept;

private:
friend class TConnectionLimiter;

// not threadsafe
void Release();

TConnectionLimiter* mOwner { nullptr };
std::string mIp;
};

TConnectionLimiter(size_t maxPerIp, size_t maxGlobal);

[[nodiscard]] std::optional<TGuard> TryAcquire(const std::string& ip);
[[nodiscard]] TStats GetStats();

private:
void Release(const std::string& ip) {
std::unique_lock Lock { mMutex };
auto It = mPerIp.find(ip);
if (It != mPerIp.end()) {
// this guard exists to avoid underflow in case something goes wrong and this gets called too many times
if (It->second > 0)
--It->second;
if (It->second == 0)
mPerIp.erase(It);
}
// this guard exists to avoid underflow in case something goes wrong and this gets called too many times
if (mGlobal > 0)
--mGlobal;
}

const size_t mMaxPerIp;
const size_t mMaxGlobal;

std::mutex mMutex { };
std::unordered_map<std::string, size_t> mPerIp { };
size_t mGlobal = 0;
};
36 changes: 36 additions & 0 deletions include/TIoPollThread.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#pragma once

#include <boost/asio/io_context.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <thread>

class TIoPollThread {
public:
TIoPollThread();
~TIoPollThread();

boost::asio::io_context& IoCtx() noexcept { return mIoCtx; }

private:
boost::asio::io_context mIoCtx;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> mWorkGuard;
std::jthread mThread;
};
35 changes: 5 additions & 30 deletions include/TLuaEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@
#pragma once

#include "Profiling.h"
#include "TLuaResult.h"
#include "TNetwork.h"
#include "TServer.h"
#include <any>
#include <chrono>
#include <condition_variable>
#include <filesystem>
#include <initializer_list>
#include <list>
#include <lua.hpp>
#include <memory>
Expand All @@ -34,6 +33,8 @@
#include <queue>
#include <random>
#include <set>
#include <sol/forward.hpp>
#include <sol/protected_function_result.hpp>
#include <toml.hpp>
#include <unordered_map>
#include <vector>
Expand All @@ -58,36 +59,10 @@ namespace fs = std::filesystem;
/**
* std::variant means, that TLuaArgTypes may be one of the Types listed as template args
*/
using TLuaValue = std::variant<std::string, int, JsonString, bool, std::unordered_map<std::string, std::string>, float>;
enum TLuaType {
String = 0,
Int = 1,
Json = 2,
Bool = 3,
StringStringMap = 4,
Float = 5,
};
using TLuaValue = std::variant<std::monostate, std::string, int, JsonString, bool, std::unordered_map<std::string, std::string>, float>;

class TLuaPlugin;

struct TLuaResult {
bool Ready;
bool Error;
std::string ErrorMessage;
sol::object Result { sol::lua_nil };
TLuaStateId StateId;
std::string Function;
std::shared_ptr<std::mutex> ReadyMutex {
std::make_shared<std::mutex>()
};
std::shared_ptr<std::condition_variable> ReadyCondition {
std::make_shared<std::condition_variable>()
};

void MarkAsReady();
void WaitUntilReady();
};

struct TLuaPluginConfig {
static inline const std::string FileName = "PluginConfig.toml";
TLuaStateId StateId;
Expand Down Expand Up @@ -229,7 +204,7 @@ class TLuaEngine : public std::enable_shared_from_this<TLuaEngine>, IThreaded {
std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> Debug_GetEventsForState(TLuaStateId StateId);
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId);
std::vector<QueuedFunction> Debug_GetStateFunctionQueueForState(TLuaStateId StateId);
std::vector<TLuaResult> Debug_GetResultsToCheckForState(TLuaStateId StateId);
std::vector<TLuaResult::DetachedSnapshot> Debug_GetResultsToCheckForState(TLuaStateId StateId);

private:
void CollectAndInitPlugins();
Expand Down
Loading
Loading