Compare commits

..

23 Commits

Author SHA1 Message Date
Tixx d56d9892c4 Bump version to 3.9.2 2026-04-01 00:40:30 +02:00
SaltySnail 82102fcd4b Add ip limiting (#478)
By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-04-01 00:13:45 +02:00
SaltySnail 66884920db Remove clients when shutting down the socket 2026-03-31 23:44:43 +02:00
SaltySnail 5c2a0db7ef Added timeout on read 2026-03-31 22:32:57 +02:00
Tixx b139a6aaad Fix skip invalid socket (#477)
Removes continue in socket accept flow

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-03-28 17:04:04 +01:00
SaltySnail 75e9c52ce8 Rewrite accept code to not use continue 2026-03-28 16:23:29 +01:00
SaltySnail b4b262196b Revert "Skip invalid socket when accept() fails"
This reverts commit 83afafc0c3.
2026-03-28 16:15:42 +01:00
Tixx 4af0e4ccac feature: BEAMMP_PROVIDER_DISABLE_MP_SET environment variable (#461)
This pull request adds BEAMMP_PROVIDER_DISABLE_MP_SET variable
(true/false/1/0) to disable MP::Set for providers.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-03-21 20:11:51 +01:00
Tixx 5d2b198fb8 adding logchat boolean to MP.SendChatMessage (#471)
This PR adds a boolean parameter to the MP.SendChatMessage function that
allows logging the message or not (default true).

I took an example from the ``set_function("SendNotification" ...`` to
make the default value working.

It's my first time actually doing C++, I hope it's alright!

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-03-21 20:11:38 +01:00
Tixx 89392a67c2 Bump version to 3.9.1 2026-03-08 18:16:10 +01:00
Tixx 86a1c50c8b Add dynamic export (#470)
Testing builds with -Wl,--export-dynamic

Closes #464 

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-03-08 18:09:42 +01:00
Tixx d97010c8ca Harden malformed vehicle packet parsing (#475)
## Summary
Reject malformed vehicle reset and paint packets before slicing
structured payloads, and catch parser exceptions in the TCP client loop.

## Problem
Or:<pid>-<vid>:null and similarly malformed Op packets could reach
substr(npos) in ParseVehicle, throwing std::out_of_range. On TCP, that
exception could escape the client loop and kill the thread.

## Changes
- Guard Or reset packets when no { payload exists
- Guard Op paint packets when no [ payload exists
- Add a small helper and unit test for structured payload extraction
- Catch std::exception around GlobalParser in the TCP client loop and
disconnect the offending client

## Verification
- git diff --check
- Full build/tests not run in this shell because cmake was not installed
2026-03-08 17:29:55 +01:00
Tixx 8a613e0b16 Prevent log spam and simplify code 2026-03-08 17:24:30 +01:00
alex 48d721f602 Harden malformed vehicle packet parsing 2026-03-07 21:23:00 -07:00
TechStreet f38797a9ab feature: BEAMMP_PROVIDER_DISABLE_MP_SET environment variable 2026-02-24 17:39:22 +00:00
0R3Z da0089fb2e add export-dynamic flag 2026-02-19 21:15:14 +01:00
Tixx 3befc84f00 Add build deb13 (#469)
add/test Debian 13 build to github actions

Closes #464 

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-02-19 20:07:28 +01:00
boubouleuh 595247d51d adding logchat boolean to MP.SendChatMessage 2026-02-02 22:33:58 +01:00
0R3Z 48a77e9ea6 add debian 13 to release workflow 2026-02-02 12:52:42 +01:00
0R3Z f9c85a1f35 fix release workflow formatting 2026-02-02 12:51:01 +01:00
0R3Z 168a410129 add build Debian 13 for ARM64 2026-01-25 18:11:33 +01:00
0R3Z 4926e1aebd add support for Debian 13 on github workflows 2026-01-25 17:59:02 +01:00
0R3Z 1e2d8688a8 add installation scripts for Debian 13 2026-01-25 17:58:26 +01:00
24 changed files with 307 additions and 96 deletions
+8 -5
View File
@@ -3,8 +3,8 @@ name: Linux
on: on:
push: push:
branches: branches:
- 'develop' - "develop"
- 'minor' - "minor"
pull_request: pull_request:
env: env:
@@ -21,6 +21,8 @@ jobs:
include: include:
- distro: debian - distro: debian
version: 12 version: 12
- distro: debian
version: 13
- distro: ubuntu - distro: ubuntu
version: 22.04 version: 22.04
- distro: ubuntu - distro: ubuntu
@@ -45,7 +47,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: "recursive"
fetch-depth: 0 fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
@@ -91,6 +93,8 @@ jobs:
include: include:
- distro: debian - distro: debian
version: 12 version: 12
- distro: debian
version: 13
- distro: ubuntu - distro: ubuntu
version: 22.04 version: 22.04
- distro: ubuntu - distro: ubuntu
@@ -115,7 +119,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: "recursive"
fetch-depth: 0 fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
@@ -151,4 +155,3 @@ jobs:
- name: Test - name: Test
run: ./bin/BeamMP-Server-tests run: ./bin/BeamMP-Server-tests
+8 -4
View File
@@ -3,7 +3,7 @@ on:
push: push:
# Sequence of patterns matched against refs/tags # Sequence of patterns matched against refs/tags
tags: tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
env: env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
@@ -40,6 +40,8 @@ jobs:
include: include:
- distro: debian - distro: debian
version: 12 version: 12
- distro: debian
version: 13
- distro: ubuntu - distro: ubuntu
version: 22.04 version: 22.04
- distro: ubuntu - distro: ubuntu
@@ -64,7 +66,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: "recursive"
fetch-depth: 0 fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
@@ -110,6 +112,8 @@ jobs:
include: include:
- distro: debian - distro: debian
version: 12 version: 12
- distro: debian
version: 13
- distro: ubuntu - distro: ubuntu
version: 22.04 version: 22.04
- distro: ubuntu - distro: ubuntu
@@ -137,7 +141,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: "recursive"
fetch-depth: 0 fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
@@ -191,7 +195,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: "recursive"
fetch-depth: 0 fetch-depth: 0
- name: Create Build Environment - name: Create Build Environment
+3 -6
View File
@@ -80,15 +80,14 @@ set(PRJ_SOURCES
src/ChronoWrapper.cpp src/ChronoWrapper.cpp
) )
# fix for sol2 find_package(Lua REQUIRED)
find_path(LUAJIT_INCLUDE_DIR lua.hpp PATH_SUFFIXES luajit)
# set the source file containing main() # set the source file containing main()
set(PRJ_MAIN src/main.cpp) set(PRJ_MAIN src/main.cpp)
# set the source file containing the test's main # set the source file containing the test's main
set(PRJ_TEST_MAIN test/test_main.cpp) set(PRJ_TEST_MAIN test/test_main.cpp)
# set include paths not part of libraries # set include paths not part of libraries
set(PRJ_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIR}) set(PRJ_INCLUDE_DIRS ${LUA_INCLUDE_DIR})
# set compile features (e.g. standard version) # set compile features (e.g. standard version)
set(PRJ_COMPILE_FEATURES cxx_std_20) set(PRJ_COMPILE_FEATURES cxx_std_20)
# set #defines (test enable/disable not included here) # set #defines (test enable/disable not included here)
@@ -106,11 +105,9 @@ set(PRJ_LIBRARIES
libzip::zip libzip::zip
OpenSSL::SSL OpenSSL::Crypto OpenSSL::SSL OpenSSL::Crypto
CURL::libcurl CURL::libcurl
lua51 ${LUA_LIBRARIES}
) )
link_directories(${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib)
# add dependency find_package calls and similar here # add dependency find_package calls and similar here
find_package(fmt CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
+1 -1
View File
@@ -129,7 +129,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {}; static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {}; static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 9, 0 }; static inline Version mVersion { 3, 9, 2 };
}; };
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out); void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
+1
View File
@@ -26,6 +26,7 @@ enum class Key {
// provider settings // provider settings
PROVIDER_UPDATE_MESSAGE, PROVIDER_UPDATE_MESSAGE,
PROVIDER_DISABLE_CONFIG, PROVIDER_DISABLE_CONFIG,
PROVIDER_DISABLE_MP_SET,
PROVIDER_PORT_ENV, PROVIDER_PORT_ENV,
PROVIDER_IP_ENV PROVIDER_IP_ENV
}; };
+1 -1
View File
@@ -34,7 +34,7 @@ namespace MP {
std::pair<bool, std::string> TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data); std::pair<bool, std::string> TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data);
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); } inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
std::pair<bool, std::string> DropPlayer(int ID, std::optional<std::string> MaybeReason); std::pair<bool, std::string> DropPlayer(int ID, std::optional<std::string> MaybeReason);
std::pair<bool, std::string> SendChatMessage(int ID, const std::string& Message); std::pair<bool, std::string> SendChatMessage(int ID, const std::string& Message, const bool& LogChat = true);
std::pair<bool, std::string> SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category); std::pair<bool, std::string> SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category);
std::pair<bool, std::string> ConfirmationDialog(int ID, const std::string& Title, const std::string& Body, const sol::table& buttons, const std::string& InteractionID, const bool& warning = false, const bool& reportToServer = true, const bool& reportToExtensions = true); std::pair<bool, std::string> ConfirmationDialog(int ID, const std::string& Title, const std::string& Body, const sol::table& buttons, const std::string& InteractionID, const bool& warning = false, const bool& reportToServer = true, const bool& reportToExtensions = true);
std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID); std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID);
+11 -6
View File
@@ -34,9 +34,11 @@ public:
[[nodiscard]] bool TCPSend(TClient& c, const std::vector<uint8_t>& Data, bool IsSync = false); [[nodiscard]] bool TCPSend(TClient& c, const std::vector<uint8_t>& Data, bool IsSync = false);
[[nodiscard]] bool SendLarge(TClient& c, std::vector<uint8_t> Data, bool isSync = false); [[nodiscard]] bool SendLarge(TClient& c, std::vector<uint8_t> Data, bool isSync = false);
[[nodiscard]] bool Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bool isSync = false); [[nodiscard]] bool Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bool isSync = false);
std::shared_ptr<TClient> CreateClient(ip::tcp::socket&& TCPSock); std::shared_ptr<TClient> CreateClient(boost::asio::ip::tcp::socket&& TCPSock);
std::vector<uint8_t> TCPRcv(TClient& c); std::vector<uint8_t> TCPRcv(TClient& c);
void ClientKick(TClient& c, const std::string& R); void ClientKick(TClient& c, const std::string& R);
void DisconnectClient(const std::weak_ptr<TClient>& c, const std::string& R);
void DisconnectClient(TClient& c, const std::string& R);
[[nodiscard]] bool SyncClient(const std::weak_ptr<TClient>& c); [[nodiscard]] bool SyncClient(const std::weak_ptr<TClient>& c);
void Identify(TConnection&& client); void Identify(TConnection&& client);
std::shared_ptr<TClient> Authentication(TConnection&& ClientConnection); std::shared_ptr<TClient> Authentication(TConnection&& ClientConnection);
@@ -44,6 +46,7 @@ public:
[[nodiscard]] bool UDPSend(TClient& Client, std::vector<uint8_t> Data); [[nodiscard]] bool UDPSend(TClient& Client, std::vector<uint8_t> Data);
void SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self, bool Rel); void SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self, bool Rel);
void UpdatePlayer(TClient& Client); void UpdatePlayer(TClient& Client);
boost::system::error_code ReadWithTimeout(TConnection& Connection, void* Buf, size_t Len, std::chrono::steady_clock::duration Timeout);
TResourceManager& ResourceManager() const { return mResourceManager; } TResourceManager& ResourceManager() const { return mResourceManager; }
@@ -53,13 +56,15 @@ private:
TServer& mServer; TServer& mServer;
TPPSMonitor& mPPSMonitor; TPPSMonitor& mPPSMonitor;
ip::udp::socket mUDPSock; boost::asio::ip::udp::socket mUDPSock;
TResourceManager& mResourceManager; TResourceManager& mResourceManager;
std::thread mUDPThread; std::thread mUDPThread;
std::thread mTCPThread; std::thread mTCPThread;
std::mutex mOpenIDMutex; std::mutex mOpenIDMutex;
std::map<std::string, uint16_t> mClientMap;
std::mutex mClientMapMutex;
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint); std::vector<uint8_t> UDPRcvFromClient(boost::asio::ip::udp::endpoint& ClientEndpoint);
void OnConnect(const std::weak_ptr<TClient>& c); void OnConnect(const std::weak_ptr<TClient>& c);
void TCPClient(const std::weak_ptr<TClient>& c); void TCPClient(const std::weak_ptr<TClient>& c);
void Looper(const std::weak_ptr<TClient>& c); void Looper(const std::weak_ptr<TClient>& c);
@@ -67,9 +72,9 @@ private:
void OnDisconnect(const std::weak_ptr<TClient>& ClientPtr); void OnDisconnect(const std::weak_ptr<TClient>& ClientPtr);
void Parse(TClient& c, const std::vector<uint8_t>& Packet); void Parse(TClient& c, const std::vector<uint8_t>& Packet);
void SendFile(TClient& c, const std::string& Name); void SendFile(TClient& c, const std::string& Name);
static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size); static bool TCPSendRaw(TClient& C, boost::asio::ip::tcp::socket& socket, const uint8_t* Data, size_t Size);
static void SendFileToClient(TClient& c, size_t Size, const std::string& Name); void SendFileToClient(TClient& c, size_t Size, const std::string& Name);
static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size); static const uint8_t* SendSplit(TClient& c, boost::asio::ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size);
}; };
std::string HashPassword(const std::string& str); std::string HashPassword(const std::string& str);
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh ./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh ./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -ex
apt-get update -y
apt-get install -y liblua5.3-0 liblua5.3-dev curl zip unzip tar cmake make git g++ ninja-build
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -ex
git config --global --add safe.directory $(pwd)
git config --global --add safe.directory $(pwd)/vcpkg
git config --global --add safe.directory $(pwd)/deps/commandline
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -ex
./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -ex
cmake --build bin --parallel -t BeamMP-Server-tests
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
set -ex
cmake --build bin --parallel -t BeamMP-Server
objcopy --only-keep-debug bin/BeamMP-Server bin/BeamMP-Server.debug
objcopy --add-gnu-debuglink bin/BeamMP-Server bin/BeamMP-Server.debug
strip -s bin/BeamMP-Server
@@ -0,0 +1,8 @@
#!/bin/bash
set -ex
apt-get update -y
apt-get install -y liblua5.3-0 curl
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh ./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh ./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+3
View File
@@ -36,6 +36,9 @@ std::string_view Env::ToString(Env::Key key) {
case Key::PROVIDER_DISABLE_CONFIG: case Key::PROVIDER_DISABLE_CONFIG:
return "BEAMMP_PROVIDER_DISABLE_CONFIG"; return "BEAMMP_PROVIDER_DISABLE_CONFIG";
break; break;
case Key::PROVIDER_DISABLE_MP_SET:
return "BEAMMP_PROVIDER_DISABLE_MP_SET";
break;
case Key::PROVIDER_PORT_ENV: case Key::PROVIDER_PORT_ENV:
return "BEAMMP_PROVIDER_PORT_ENV"; return "BEAMMP_PROVIDER_PORT_ENV";
break; break;
+7 -3
View File
@@ -178,11 +178,13 @@ std::pair<bool, std::string> LuaAPI::MP::DropPlayer(int ID, std::optional<std::s
return { true, "" }; return { true, "" };
} }
std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::string& Message) { std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::string& Message, const bool& LogChat) {
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
std::string Packet = "C:Server: " + Message; std::string Packet = "C:Server: " + Message;
if (ID == -1) { if (ID == -1) {
LogChatMessage("<Server> (to everyone) ", -1, Message); if (LogChat) {
LogChatMessage("<Server> (to everyone) ", -1, Message);
}
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true); Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
Result.first = true; Result.first = true;
} else { } else {
@@ -194,7 +196,9 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
Result.second = "Player still syncing data"; Result.second = "Player still syncing data";
return Result; return Result;
} }
LogChatMessage("<Server> (to \"" + c->GetName() + "\")", -1, Message); if (LogChat) {
LogChatMessage("<Server> (to \"" + c->GetName() + "\")", -1, Message);
}
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) { if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
beammp_errorf("Failed to send chat message back to sender (id {}) - did the sender disconnect?", ID); beammp_errorf("Failed to send chat message back to sender (id {}) - did the sender disconnect?", ID);
// TODO: should we return an error here? // TODO: should we return an error here?
+2 -1
View File
@@ -260,7 +260,8 @@ void TConsole::Command_Version(const std::string& cmd, const std::vector<std::st
Application::Console().WriteRaw("Platform: " + platform); Application::Console().WriteRaw("Platform: " + platform);
Application::Console().WriteRaw("Server: v" + Application::ServerVersionString()); Application::Console().WriteRaw("Server: v" + Application::ServerVersionString());
Application::Console().WriteRaw(LUA_RELEASE); std::string lua_version = fmt::format("Lua: v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
Application::Console().WriteRaw(lua_version);
std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH); std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH);
Application::Console().WriteRaw(openssl_version); Application::Console().WriteRaw(openssl_version);
} }
+37 -3
View File
@@ -22,6 +22,7 @@
#include "CustomAssert.h" #include "CustomAssert.h"
#include "Http.h" #include "Http.h"
#include "LuaAPI.h" #include "LuaAPI.h"
#include "Env.h"
#include "Profiling.h" #include "Profiling.h"
#include "TLuaPlugin.h" #include "TLuaPlugin.h"
#include "sol/object.hpp" #include "sol/object.hpp"
@@ -65,7 +66,7 @@ TEST_CASE("TLuaEngine ctor & dtor") {
void TLuaEngine::operator()() { void TLuaEngine::operator()() {
RegisterThread("LuaEngine"); RegisterThread("LuaEngine");
// lua engine main thread // lua engine main thread
beammp_infof(LUA_RELEASE); beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
CollectAndInitPlugins(); CollectAndInitPlugins();
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good); Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
@@ -726,6 +727,25 @@ static void AddToTable(sol::table& table, const std::string& left, const T& valu
} }
} }
static bool mDisableMPSet = [] {
auto DisableMPSet = Env::Get(Env::Key::PROVIDER_DISABLE_MP_SET).value_or("false");
return DisableMPSet == "true" || DisableMPSet == "1";
}();
static auto GetSettingName = [](int id) -> const char* {
switch (id) {
case 0: return "Debug";
case 1: return "Private";
case 2: return "MaxCars";
case 3: return "MaxPlayers";
case 4: return "Map";
case 5: return "Name";
case 6: return "Description";
case 7: return "InformationPacket";
default: return "Unknown";
}
};
static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) { static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) {
switch (right.type()) { switch (right.type()) {
case nlohmann::detail::value_t::null: case nlohmann::detail::value_t::null:
@@ -857,7 +877,15 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
MPTable.set_function("GetPositionRaw", [&](int PID, int VID) -> std::pair<sol::table, std::string> { MPTable.set_function("GetPositionRaw", [&](int PID, int VID) -> std::pair<sol::table, std::string> {
return Lua_GetPositionRaw(PID, VID); return Lua_GetPositionRaw(PID, VID);
}); });
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage); MPTable.set_function("SendChatMessage", [&](sol::variadic_args Args) {
if (Args.size() == 2) {
LuaAPI::MP::SendChatMessage(Args.get<int>(0), Args.get<std::string>(1));
} else if (Args.size() == 3) {
LuaAPI::MP::SendChatMessage(Args.get<int>(0), Args.get<std::string>(1), Args.get<bool>(2));
} else {
beammp_lua_error("SendChatMessage expects 2 or 3 arguments.");
}
});
MPTable.set_function("SendNotification", [&](sol::variadic_args Args) { MPTable.set_function("SendNotification", [&](sol::variadic_args Args) {
if (Args.size() == 2) { if (Args.size() == 2) {
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), "", Args.get<std::string>(1)); LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), "", Args.get<std::string>(1));
@@ -918,7 +946,13 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) { MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) {
mEngine->CancelEventTimers(EventName, mStateId); mEngine->CancelEventTimers(EventName, mStateId);
}); });
MPTable.set_function("Set", &LuaAPI::MP::Set); if (mDisableMPSet) {
MPTable.set_function("Set", [this](int ConfigID, sol::object NewValue) {
beammp_lua_errorf("A script ({}) tried to call MP.Set to change setting '{}' but this was blocked by your server provider.", mStateId, GetSettingName(ConfigID));
});
} else {
MPTable.set_function("Set", &LuaAPI::MP::Set);
}
MPTable.set_function("Get", &LuaAPI::MP::Get); MPTable.set_function("Get", &LuaAPI::MP::Get);
auto UtilTable = StateView.create_named_table("Util"); auto UtilTable = StateView.create_named_table("Util");
+146 -47
View File
@@ -38,6 +38,10 @@
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option; typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
static constexpr uint8_t MAX_CONCURRENT_CONNECTIONS = 10;
static constexpr uint8_t MAX_GLOBAL_CONNECTIONS = 128;
static constexpr uint8_t READ_TIMEOUT_S = 10; //seconds
std::vector<uint8_t> StringToVector(const std::string& Str) { std::vector<uint8_t> StringToVector(const std::string& Str) {
return std::vector<uint8_t>(Str.data(), Str.data() + Str.size()); return std::vector<uint8_t>(Str.data(), Str.data() + Str.size());
} }
@@ -89,14 +93,14 @@ void TNetwork::UDPServerMain() {
RegisterThread("UDPServer"); RegisterThread("UDPServer");
boost::system::error_code ec; boost::system::error_code ec;
auto address = ip::make_address(Application::Settings.getAsString(Settings::Key::General_IP), ec); auto address = boost::asio::ip::make_address(Application::Settings.getAsString(Settings::Key::General_IP), ec);
if (ec) { if (ec) {
beammp_errorf("Failed to parse IP: {}", ec.message()); beammp_errorf("Failed to parse IP: {}", ec.message());
Application::GracefullyShutdown(); Application::GracefullyShutdown();
} }
ip::udp::endpoint UdpListenEndpoint(address, Application::Settings.getAsInt(Settings::Key::General_Port)); boost::asio::ip::udp::endpoint UdpListenEndpoint(address, Application::Settings.getAsInt(Settings::Key::General_Port));
mUDPSock.open(UdpListenEndpoint.protocol(), ec); mUDPSock.open(UdpListenEndpoint.protocol(), ec);
if (ec) { if (ec) {
@@ -121,13 +125,13 @@ void TNetwork::UDPServerMain() {
+ std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients")); + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients"));
while (!Application::IsShuttingDown()) { while (!Application::IsShuttingDown()) {
try { try {
ip::udp::endpoint remote_client_ep {}; boost::asio::ip::udp::endpoint remote_client_ep {};
std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep); std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep);
if (Data.empty()) { if (Data.empty()) {
continue; continue;
} }
if (Data.size() == 1 && Data.at(0) == 'P') { if (Data.size() == 1 && Data.at(0) == 'P') {
mUDPSock.send_to(const_buffer("P", 1), remote_client_ep, {}, ec); mUDPSock.send_to(boost::asio::const_buffer("P", 1), remote_client_ep, {}, ec);
// ignore errors // ignore errors
(void)ec; (void)ec;
continue; continue;
@@ -148,7 +152,7 @@ void TNetwork::UDPServerMain() {
} }
if (Client->GetID() == ID) { if (Client->GetID() == ID) {
if (Client->GetUDPAddr() == ip::udp::endpoint {} && !Client->IsUDPConnected() && !Client->GetMagic().empty()) { if (Client->GetUDPAddr() == boost::asio::ip::udp::endpoint {} && !Client->IsUDPConnected() && !Client->GetMagic().empty()) {
if (Data.size() != 66) { if (Data.size() != 66) {
beammp_debugf("Invalid size for UDP value. IP: {} ID: {}", remote_client_ep.address().to_string(), ID); beammp_debugf("Invalid size for UDP value. IP: {} ID: {}", remote_client_ep.address().to_string(), ID);
return false; return false;
@@ -188,16 +192,16 @@ void TNetwork::TCPServerMain() {
RegisterThread("TCPServer"); RegisterThread("TCPServer");
boost::system::error_code ec; boost::system::error_code ec;
auto address = ip::make_address(Application::Settings.getAsString(Settings::Key::General_IP), ec); auto address = boost::asio::ip::make_address(Application::Settings.getAsString(Settings::Key::General_IP), ec);
if (ec) { if (ec) {
beammp_errorf("Failed to parse IP: {}", ec.message()); beammp_errorf("Failed to parse IP: {}", ec.message());
return; return;
} }
ip::tcp::endpoint ListenEp(address, boost::asio::ip::tcp::endpoint ListenEp(address,
uint16_t(Application::Settings.getAsInt(Settings::Key::General_Port))); uint16_t(Application::Settings.getAsInt(Settings::Key::General_Port)));
ip::tcp::socket Listener(mServer.IoCtx()); boost::asio::ip::tcp::socket Listener(mServer.IoCtx());
Listener.open(ListenEp.protocol(), ec); Listener.open(ListenEp.protocol(), ec);
if (ec) { if (ec) {
beammp_errorf("Failed to open socket: {}", ec.message()); beammp_errorf("Failed to open socket: {}", ec.message());
@@ -222,7 +226,7 @@ void TNetwork::TCPServerMain() {
ec.message()); ec.message());
} }
ip::tcp::acceptor Acceptor(mServer.IoCtx(), ListenEp); boost::asio::ip::tcp::acceptor Acceptor(mServer.IoCtx(), ListenEp);
Acceptor.listen(socket_base::max_listen_connections, ec); Acceptor.listen(socket_base::max_listen_connections, ec);
if (ec) { if (ec) {
beammp_errorf("listen() failed, which is needed for the server to operate. " beammp_errorf("listen() failed, which is needed for the server to operate. "
@@ -239,15 +243,28 @@ void TNetwork::TCPServerMain() {
beammp_debug("shutdown during TCP wait for accept loop"); beammp_debug("shutdown during TCP wait for accept loop");
break; break;
} }
ip::tcp::endpoint ClientEp; boost::asio::ip::tcp::endpoint ClientEp;
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec); boost::asio::ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec);
if (ec) { std::string ClientIP = ClientEp.address().to_string();
beammp_errorf("Failed to accept() new client: {}", ec.message()); if (!ec) {
continue; mClientMapMutex.lock();
if (mClientMap[ClientIP] >= MAX_CONCURRENT_CONNECTIONS) {
beammp_debugf("The connection was rejected for {}, as it had {} concurrent connections.", ClientIP, mClientMap[ClientIP]);
}
else if (mClientMap.size() >= MAX_GLOBAL_CONNECTIONS) {
beammp_debugf("The connection was rejected for {}, as there are {} global connections.", ClientIP, mClientMap.size());
}
else {
TConnection Conn { std::move(ClientSocket), ClientEp };
std::thread ID(&TNetwork::Identify, this, std::move(Conn));
ID.detach(); // TODO: Add to a queue and attempt to join periodically
mClientMap[ClientIP]++;
}
mClientMapMutex.unlock();
}
else {
beammp_errorf("Failed to accept() new client: {}", ec.message());
} }
TConnection Conn { std::move(ClientSocket), ClientEp };
std::thread ID(&TNetwork::Identify, this, std::move(Conn));
ID.detach();
} catch (const std::exception& e) { } catch (const std::exception& e) {
beammp_errorf("Exception in accept routine: {}", e.what()); beammp_errorf("Exception in accept routine: {}", e.what());
} }
@@ -263,11 +280,22 @@ void TNetwork::Identify(TConnection&& RawConnection) {
RegisterThreadAuto(); RegisterThreadAuto();
char Code; char Code;
boost::system::error_code ec; boost::system::error_code ec = ReadWithTimeout(RawConnection, &Code, 1, std::chrono::seconds(READ_TIMEOUT_S));
read(RawConnection.Socket, buffer(&Code, 1), ec);
if (ec) { if (ec) {
// TODO: is this right?! // TODO: is this right?!
beammp_debug("Error occured reading code");
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec); RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
mClientMapMutex.lock();
{
std::string ClientIP = RawConnection.SockAddr.address().to_string();
if (mClientMap[ClientIP] > 0) {
mClientMap[ClientIP]--;
}
if (mClientMap[ClientIP] == 0) {
mClientMap.erase(ClientIP);
}
}
mClientMapMutex.unlock();
return; return;
} }
std::shared_ptr<TClient> Client { nullptr }; std::shared_ptr<TClient> Client { nullptr };
@@ -278,8 +306,7 @@ void TNetwork::Identify(TConnection&& RawConnection) {
beammp_errorf("Old download packet detected - the client is wildly out of date, this will be ignored"); beammp_errorf("Old download packet detected - the client is wildly out of date, this will be ignored");
return; return;
} else if (Code == 'P') { } else if (Code == 'P') {
boost::system::error_code ec; boost::asio::write(RawConnection.Socket, boost::asio::buffer("P"), ec);
write(RawConnection.Socket, buffer("P"), ec);
return; return;
} else if (Code == 'I') { } else if (Code == 'I') {
const std::string Data = Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? THeartbeatThread::lastCall : ""; const std::string Data = Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? THeartbeatThread::lastCall : "";
@@ -291,14 +318,25 @@ void TNetwork::Identify(TConnection&& RawConnection) {
std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size()); std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size());
boost::system::error_code ec; boost::system::error_code ec;
write(RawConnection.Socket, buffer(ToSend), ec); boost::asio::write(RawConnection.Socket, boost::asio::buffer(ToSend), ec);
} else { } else {
beammp_errorf("Invalid code got in Identify: '{}'", Code); beammp_errorf("Invalid code got in Identify: '{}'", Code);
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket: {}", Code, e.what()); beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket: {}", Code, e.what());
boost::system::error_code ec; boost::system::error_code ec;
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec); RawConnection.Socket.shutdown(boost::asio::socket_base::shutdown_both, ec);
mClientMapMutex.lock();
{
std::string ClientIP = RawConnection.SockAddr.address().to_string();
if (mClientMap[ClientIP] > 0) {
mClientMap[ClientIP]--;
}
if (mClientMap[ClientIP] == 0) {
mClientMap.erase(ClientIP);
}
}
mClientMapMutex.unlock();
if (ec) { if (ec) {
beammp_debugf("Failed to shutdown client socket: {}", ec.message()); beammp_debugf("Failed to shutdown client socket: {}", ec.message());
} }
@@ -396,7 +434,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
try { try {
nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr); nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr);
if (AuthRes["username"].is_string() && AuthRes["roles"].is_string() if (AuthRes["username"].is_string() && AuthRes["username"].size() > 0 && AuthRes["roles"].is_string()
&& AuthRes["guest"].is_boolean() && AuthRes["identifiers"].is_array()) { && AuthRes["guest"].is_boolean() && AuthRes["identifiers"].is_array()) {
Client->SetName(AuthRes["username"]); Client->SetName(AuthRes["username"]);
@@ -430,7 +468,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
return true; return true;
} }
if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) { if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) {
Cl->Disconnect("Stale Client (not a real player)"); DisconnectClient(Cl, "Stale Client (not a real player)");
return false; return false;
} }
@@ -493,7 +531,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
return Client; return Client;
} }
std::shared_ptr<TClient> TNetwork::CreateClient(ip::tcp::socket&& TCPSock) { std::shared_ptr<TClient> TNetwork::CreateClient(boost::asio::ip::tcp::socket&& TCPSock) {
auto c = std::make_shared<TClient>(mServer, std::move(TCPSock)); auto c = std::make_shared<TClient>(mServer, std::move(TCPSock));
return c; return c;
} }
@@ -526,10 +564,10 @@ bool TNetwork::TCPSend(TClient& c, const std::vector<uint8_t>& Data, bool IsSync
std::memcpy(ToSend.data(), &Size, sizeof(Size)); std::memcpy(ToSend.data(), &Size, sizeof(Size));
std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size()); std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size());
boost::system::error_code ec; boost::system::error_code ec;
write(Sock, buffer(ToSend), ec); boost::asio::write(Sock, boost::asio::buffer(ToSend), ec);
if (ec) { if (ec) {
beammp_debugf("write(): {}", ec.message()); beammp_debugf("write(): {}", ec.message());
c.Disconnect("write() failed"); DisconnectClient(c, "write() failed");
return false; return false;
} }
c.UpdatePingTime(); c.UpdatePingTime();
@@ -547,7 +585,7 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
boost::system::error_code ec; boost::system::error_code ec;
std::array<uint8_t, sizeof(Header)> HeaderData; std::array<uint8_t, sizeof(Header)> HeaderData;
read(Sock, buffer(HeaderData), ec); boost::asio::read(Sock, boost::asio::buffer(HeaderData), ec);
if (ec) { if (ec) {
// TODO: handle this case (read failed) // TODO: handle this case (read failed)
beammp_debugf("TCPRcv: Reading header failed: {}", ec.message()); beammp_debugf("TCPRcv: Reading header failed: {}", ec.message());
@@ -563,14 +601,16 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
std::vector<uint8_t> Data; std::vector<uint8_t> Data;
// TODO: This is arbitrary, this needs to be handled another way // TODO: This is arbitrary, this needs to be handled another way
if (Header < int32_t(100 * MB)) { bool isUnauthenticated = c.GetName().empty();
int32_t maxHeaderSize = isUnauthenticated ? 4096 : int32_t(100 * MB);
if (Header < maxHeaderSize) {
Data.resize(Header); Data.resize(Header);
} else { } else {
ClientKick(c, "Header size limit exceeded"); ClientKick(c, "Header size limit exceeded");
beammp_warn("Client " + c.GetName() + " (" + std::to_string(c.GetID()) + ") sent header of >100MB - assuming malicious intent and disconnecting the client."); beammp_warn("Client " + c.GetName() + " (" + std::to_string(c.GetID()) + ") sent header larger than expected - assuming malicious intent and disconnecting the client.");
return {}; return {};
} }
auto N = read(Sock, buffer(Data), ec); auto N = boost::asio::read(Sock, boost::asio::buffer(Data), ec);
if (ec) { if (ec) {
// TODO: handle this case properly // TODO: handle this case properly
beammp_debugf("TCPRcv: Reading data failed: {}", ec.message()); beammp_debugf("TCPRcv: Reading data failed: {}", ec.message());
@@ -605,7 +645,32 @@ void TNetwork::ClientKick(TClient& c, const std::string& R) {
if (!TCPSend(c, StringToVector("K" + R))) { if (!TCPSend(c, StringToVector("K" + R))) {
beammp_debugf("tried to kick player '{}' (id {}), but was already disconnected", c.GetName(), c.GetID()); beammp_debugf("tried to kick player '{}' (id {}), but was already disconnected", c.GetName(), c.GetID());
} }
c.Disconnect("Kicked"); DisconnectClient(c, "Kicked");
}
void TNetwork::DisconnectClient(const std::weak_ptr<TClient> &c, const std::string &R)
{
if (auto locked = c.lock()) {
DisconnectClient(*locked, R);
}
else {
beammp_debugf("Tried to disconnect a non existant client with reason: {}", R);
}
}
void TNetwork::DisconnectClient(TClient &c, const std::string &R)
{
if (c.IsDisconnected()) return;
std::string ClientIP = c.GetTCPSock().remote_endpoint().address().to_string();
mClientMapMutex.lock();
if (mClientMap[ClientIP] > 0) {
mClientMap[ClientIP]--;
}
if (mClientMap[ClientIP] == 0) {
mClientMap.erase(ClientIP);
}
mClientMapMutex.unlock();
c.Disconnect(R);
} }
void TNetwork::Looper(const std::weak_ptr<TClient>& c) { void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
@@ -630,7 +695,7 @@ void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
} // end locked context } // end locked context
// beammp_debug("sending a missed packet: " + QData); // beammp_debug("sending a missed packet: " + QData);
if (!TCPSend(*Client, QData, true)) { if (!TCPSend(*Client, QData, true)) {
Client->Disconnect("Failed to TCPSend while clearing the missed packet queue"); DisconnectClient(Client, "Failed to TCPSend while clearing the missed packet queue");
std::unique_lock lock(Client->MissedPacketQueueMutex()); std::unique_lock lock(Client->MissedPacketQueueMutex());
while (!Client->MissedPacketQueue().empty()) { while (!Client->MissedPacketQueue().empty()) {
Client->MissedPacketQueue().pop(); Client->MissedPacketQueue().pop();
@@ -667,10 +732,16 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
auto res = TCPRcv(*Client); auto res = TCPRcv(*Client);
if (res.empty()) { if (res.empty()) {
beammp_debug("TCPRcv empty"); beammp_debug("TCPRcv empty");
Client->Disconnect("TCPRcv failed"); DisconnectClient(Client, "TCPRcv failed");
break;
}
try {
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this, false);
} catch (const std::exception& e) {
beammp_warnf("Failed to receive/parse packet via TCP from client {}: {}", Client->GetID(), e.what());
DisconnectClient(Client, "Failed to parse packet");
break; break;
} }
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this, false);
} }
if (QueueSync.joinable()) if (QueueSync.joinable())
@@ -699,6 +770,34 @@ void TNetwork::UpdatePlayer(TClient& Client) {
//(void)Respond(Client, Packet, true); //(void)Respond(Client, Packet, true);
} }
boost::system::error_code TNetwork::ReadWithTimeout(TConnection& Connection, void *Buf, size_t Len, std::chrono::steady_clock::duration Timeout)
{
io_context TimerIO;
steady_timer Timer(TimerIO);
Timer.expires_after(Timeout);
std::atomic<bool> TimedOut = false;
Timer.async_wait([&](const boost::system::error_code& ec) {
if (!ec) {
TimedOut = true;
Connection.Socket.cancel();
}
});
std::thread TimerThread([&]() { TimerIO.run(); });
boost::system::error_code ReadEc;
boost::asio::read(Connection.Socket, boost::asio::buffer(Buf, Len), ReadEc);
TimerIO.stop();
TimerThread.join();
if (TimedOut.load()) {
return error::timed_out; // synthesize a clean timeout error
}
return ReadEc; //Succes!
}
void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) { void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
std::shared_ptr<TClient> LockedClientPtr { nullptr }; std::shared_ptr<TClient> LockedClientPtr { nullptr };
try { try {
@@ -726,7 +825,7 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
Packet.clear(); Packet.clear();
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerDisconnect", "", c.GetID()); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerDisconnect", "", c.GetID());
LuaAPI::MP::Engine->WaitForAll(Futures); LuaAPI::MP::Engine->WaitForAll(Futures);
c.Disconnect("Already Disconnected (OnDisconnect)"); DisconnectClient(c, "Already Disconnected (OnDisconnect)");
mServer.RemoveClient(ClientPtr); mServer.RemoveClient(ClientPtr);
} }
@@ -834,7 +933,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
for (auto mod : mResourceManager.GetMods()) { for (auto mod : mResourceManager.GetMods()) {
if (mod["file_name"].get<std::string>() == FileName && mod["protected"] == true) { if (mod["file_name"].get<std::string>() == FileName && mod["protected"] == true) {
beammp_warn("Client tried to access protected file " + UnsafeName); beammp_warn("Client tried to access protected file " + UnsafeName);
c.Disconnect("Mod is protected thus cannot be downloaded"); DisconnectClient(c, "Mod is protected thus cannot be downloaded");
return; return;
} }
} }
@@ -898,7 +997,7 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
Data.resize(Split); Data.resize(Split);
else else
Data.resize(Size); Data.resize(Size);
ip::tcp::socket* TCPSock = &c.GetTCPSock(); boost::asio::ip::tcp::socket* TCPSock = &c.GetTCPSock();
std::streamsize Sent = 0; std::streamsize Sent = 0;
while (!c.IsDisconnected() && Sent < Size) { while (!c.IsDisconnected() && Sent < Size) {
size_t Diff = Size - Sent; size_t Diff = Size - Sent;
@@ -907,7 +1006,7 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
f.read(reinterpret_cast<char*>(Data.data()), Split); f.read(reinterpret_cast<char*>(Data.data()), Split);
if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) { if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) {
if (!c.IsDisconnected()) if (!c.IsDisconnected())
c.Disconnect("TCPSendRaw failed in mod download (1)"); DisconnectClient(c, "TCPSendRaw failed in mod download (1)");
break; break;
} }
Sent += Split; Sent += Split;
@@ -916,7 +1015,7 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
f.read(reinterpret_cast<char*>(Data.data()), Diff); f.read(reinterpret_cast<char*>(Data.data()), Diff);
if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) { if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) {
if (!c.IsDisconnected()) if (!c.IsDisconnected())
c.Disconnect("TCPSendRaw failed in mod download (2)"); DisconnectClient(c, "TCPSendRaw failed in mod download (2)");
break; break;
} }
Sent += Diff; Sent += Diff;
@@ -925,9 +1024,9 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
#endif #endif
} }
bool TNetwork::TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size) { bool TNetwork::TCPSendRaw(TClient& C, boost::asio::ip::tcp::socket& socket, const uint8_t* Data, size_t Size) {
boost::system::error_code ec; boost::system::error_code ec;
write(socket, buffer(Data, Size), ec); boost::asio::write(socket, boost::asio::buffer(Data, Size), ec);
if (ec) { if (ec) {
beammp_errorf("Failed to send raw data to client: {}", ec.message()); beammp_errorf("Failed to send raw data to client: {}", ec.message());
return false; return false;
@@ -1068,20 +1167,20 @@ bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) {
CompressProperly(Data); CompressProperly(Data);
} }
boost::system::error_code ec; boost::system::error_code ec;
mUDPSock.send_to(buffer(Data), Addr, 0, ec); mUDPSock.send_to(boost::asio::buffer(Data), Addr, 0, ec);
if (ec) { if (ec) {
beammp_debugf("UDP sendto() failed: {}", ec.message()); beammp_debugf("UDP sendto() failed: {}", ec.message());
if (!Client.IsDisconnected()) if (!Client.IsDisconnected())
Client.Disconnect("UDP send failed"); DisconnectClient(Client, "UDP send failed");
return false; return false;
} }
return true; return true;
} }
std::vector<uint8_t> TNetwork::UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint) { std::vector<uint8_t> TNetwork::UDPRcvFromClient(boost::asio::ip::udp::endpoint& ClientEndpoint) {
std::array<char, 1024> Ret {}; std::array<char, 1024> Ret {};
boost::system::error_code ec; boost::system::error_code ec;
const auto Rcv = mUDPSock.receive_from(mutable_buffer(Ret.data(), Ret.size()), ClientEndpoint, 0, ec); const auto Rcv = mUDPSock.receive_from(boost::asio::mutable_buffer(Ret.data(), Ret.size()), ClientEndpoint, 0, ec);
if (ec) { if (ec) {
beammp_errorf("UDP recvfrom() failed: {}", ec.message()); beammp_errorf("UDP recvfrom() failed: {}", ec.message());
return {}; return {};
+16 -4
View File
@@ -28,6 +28,7 @@
#include <any> #include <any>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <utility>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -53,7 +54,6 @@ static std::optional<std::pair<int, int>> GetPidVid(const std::string& str) {
} }
return std::nullopt; return std::nullopt;
} }
TEST_CASE("GetPidVid") { TEST_CASE("GetPidVid") {
SUBCASE("Valid singledigit") { SUBCASE("Valid singledigit") {
const auto MaybePidVid = GetPidVid("0-1"); const auto MaybePidVid = GetPidVid("0-1");
@@ -120,7 +120,6 @@ TEST_CASE("GetPidVid") {
CHECK(!MaybePidVid); CHECK(!MaybePidVid);
} }
} }
TServer::TServer(const std::vector<std::string_view>& Arguments) { TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString()); beammp_info("BeamMP Server v" + Application::ServerVersionString());
Application::SetSubsystemStatus("Server", Application::Status::Starting); Application::SetSubsystemStatus("Server", Application::Status::Starting);
@@ -472,7 +471,13 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
} }
if (PID != -1 && VID != -1 && PID == c.GetID()) { if (PID != -1 && VID != -1 && PID == c.GetID()) {
Data = Data.substr(Data.find('{')); auto BracketPos = Data.find('{');
if (BracketPos == std::string::npos) {
beammp_debugf("Invalid 'Or' packet body from client {}", c.GetID());
return;
}
Data = Data.substr(BracketPos);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleReset", "", c.GetID(), VID, Data)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleReset", "", c.GetID(), VID, Data));
Network.SendToAll(&c, StringToVector(Packet), false, true); Network.SendToAll(&c, StringToVector(Packet), false, true);
} }
@@ -501,7 +506,14 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
} }
if (PID != -1 && VID != -1 && PID == c.GetID()) { if (PID != -1 && VID != -1 && PID == c.GetID()) {
Data = Data.substr(Data.find('[')); auto BracketPos = Data.find('[');
if (BracketPos == std::string::npos) {
beammp_debugf("Invalid 'Op' packet body from client {}", c.GetID());
return;
}
Data = Data.substr(BracketPos);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data));
Network.SendToAll(&c, StringToVector(Packet), false, true); Network.SendToAll(&c, StringToVector(Packet), false, true);
+5 -1
View File
@@ -15,12 +15,16 @@
"rapidjson", "rapidjson",
"sol2", "sol2",
"curl", "curl",
"luajit" "lua"
], ],
"overrides": [ "overrides": [
{ {
"name": "sol2", "name": "sol2",
"version": "3.3.1" "version": "3.3.1"
},
{
"name": "lua",
"version": "5.3.5#6"
} }
], ],
"builtin-baseline": "5bf0c55239da398b8c6f450818c9e28d36bf9966" "builtin-baseline": "5bf0c55239da398b8c6f450818c9e28d36bf9966"