Merge remote-tracking branch 'origin/new-lua-features' into rewrite-lua

This is the first of a few commits to merge the new lua features and the
rewrite
This commit is contained in:
Lion Kortlepel 2021-09-17 13:29:44 +02:00
commit 1d3958817f
No known key found for this signature in database
GPG Key ID: 4322FF2B4C71259B
35 changed files with 845 additions and 489 deletions

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,29 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug] Enter issue title here"
labels: bug
assignees: ''
---
**Fill out general information**
OS (windows, linux, ...):
BeamMP-Server Version:
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Do x ...
2. Do y ...
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
Please attach the `Server.log` from the run in which the issue appeared, preferably with Debug turned on in the `ServerConfig.toml`.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. For example: "I'm always frustrated when ...".
**Describe the solution you'd like**
A clear and concise description of what you want to happen. Also supply OS information if relevant, for example "*On Linux*, I would like to be able to...".
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context about the feature request here.

View File

@ -12,12 +12,15 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
submodules: 'recursive'
- name: Install Dependencies
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: |
echo ${#beammp_sentry_url}
sudo apt-get update
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev
sudo add-apt-repository ppa:mhier/libboost-latest
sudo apt-get install -y libboost1.70-dev libboost1.70
@ -27,7 +30,9 @@ jobs:
- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build-linux
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build
working-directory: ${{github.workspace}}/build-linux

View File

@ -12,13 +12,13 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
submodules: 'recursive'
- name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@main
id: runvcpkg
with:
vcpkgArguments: 'lua zlib rapidjson boost-beast boost-asio openssl websocketpp'
vcpkgArguments: 'lua zlib rapidjson boost-beast boost-asio openssl websocketpp curl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb'
vcpkgTriplet: 'x64-windows-static'
@ -29,7 +29,9 @@ jobs:
- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build-windows
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DSENTRY_BACKEND=breakpad -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build
working-directory: ${{github.workspace}}/build-windows

View File

@ -37,12 +37,12 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
submodules: 'recursive'
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev
sudo add-apt-repository ppa:mhier/libboost-latest
sudo apt-get install -y libboost1.70-dev libboost1.70
@ -52,14 +52,15 @@ jobs:
- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build-linux
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build
working-directory: ${{github.workspace}}/build-linux
shell: bash
run: cmake --build . --config $BUILD_TYPE
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
@ -78,13 +79,13 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
submodules: 'recursive'
- name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@main
id: runvcpkg
with:
vcpkgArguments: 'lua zlib rapidjson boost-beast boost-asio openssl websocketpp'
vcpkgArguments: 'lua zlib rapidjson boost-beast boost-asio openssl websocketpp curl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb'
vcpkgTriplet: 'x64-windows-static'
@ -95,7 +96,9 @@ jobs:
- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build-windows
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DSENTRY_BACKEND=breakpad -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build
working-directory: ${{github.workspace}}/build-windows

3
.gitignore vendored
View File

@ -1,7 +1,10 @@
.idea/
.sentry-native/
*.orig
*.toml
boost_*
Resources
run-in-env.sh
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##

15
.gitmodules vendored
View File

@ -1,18 +1,19 @@
[submodule "include/commandline"]
path = deps/commandline
path = include/commandline
url = https://github.com/lionkor/commandline
[submodule "socket.io-client-cpp"]
path = deps/socket.io-client-cpp
url = https://github.com/socketio/socket.io-client-cpp
[submodule "asio"]
path = deps/asio
path = asio
url = https://github.com/chriskohlhoff/asio
[submodule "rapidjson"]
path = deps/rapidjson
path = rapidjson
url = https://github.com/Tencent/rapidjson
[submodule "include/toml11"]
path = include/toml11
url = https://github.com/ToruNiina/toml11
[submodule "include/sentry-native"]
path = include/sentry-native
path = deps/toml11
url = https://github.com/ToruNiina/toml11
[submodule "deps/sol2"]
path = deps/sol2
url = https://github.com/ThePhD/sol2
url = https://github.com/getsentry/sentry-native

View File

@ -1,5 +1,17 @@
cmake_minimum_required(VERSION 3.0)
project(Server)
message(STATUS "You can find build instructions and a list of dependencies in the README at \
https://github.com/BeamMP/BeamMP-Server")
project(BeamMP-Server
DESCRIPTION "Server for BeamMP - The Multiplayer Mod for BeamNG.drive"
HOMEPAGE_URL https://beammp.com
LANGUAGES CXX C)
include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include")
@ -9,9 +21,23 @@ include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include")
include_directories("${PROJECT_SOURCE_DIR}/deps")
if (WIN32)
# this has to happen before sentry, so that crashpad on windows links with these settings.
message(STATUS "MSVC -> forcing use of statically-linked runtime.")
STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})
endif()
include_directories("include/sentry-native/include")
set(SENTRY_BUILD_SHARED_LIBS OFF)
if (MSVC)
set(SENTRY_BUILD_RUNTIMESTATIC ON)
endif()
set(SENTRY_BACKEND breakpad)
add_subdirectory("include/sentry-native")
message(STATUS "Setting compiler flags")
if (WIN32)
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include)
@ -22,17 +48,33 @@ elseif (UNIX)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s -fno-builtin")
if (SANITIZE)
message(STATUS "sanitize is ON")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLanAGS} -fsanitize=undefined,thread")
endif (SANITIZE)
endif ()
message(STATUS "Checking for Sentry URL")
# this is set by the build system.
# IMPORTANT: if you're building from source, just leave this empty
if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL)
message(WARNING "No sentry URL configured. Sentry logging is disabled for this build. \
This is not an error, and if you're building the BeamMP-Server yourself, this is expected and can be ignored.")
set(BEAMMP_SECRET_SENTRY_URL "")
else()
string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN)
message(STATUS "Sentry URL is length ${URL_LEN}")
endif()
message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
message(STATUS "Looking for Boost")
find_package(Boost REQUIRED COMPONENTS system thread)
add_executable(BeamMP-Server
@ -49,24 +91,57 @@ add_executable(BeamMP-Server
include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp
include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.cpp
include/LuaAPI.h src/LuaAPI.cpp)
target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}")
include_directories(BeamMP-Server PUBLIC ${Boost_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(BeamMP-Server PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/commandline")
message(STATUS "Looking for Lua")
find_package(Lua REQUIRED)
include_directories(${LUA_INCLUDE_DIR})
target_include_directories(BeamMP-Server PUBLIC
${Boost_INCLUDE_DIRS}
${LUA_INCLUDE_DIR}
${CURL_INCLUDE_DIRS}
"include/tomlplusplus"
"include/sentry-native/include"
"include/curl/include")
message(STATUS "Looking for SSL")
find_package(OpenSSL REQUIRED)
target_link_libraries(BeamMP-Server sol2::sol2 ${LUA_LIBRARIES})
message(STATUS "CURL IS ${CURL_LIBRARIES}")
if (UNIX)
target_link_libraries(BeamMP-Server z pthread stdc++fs crypto ${OPENSSL_LIBRARIES} commandline sioclient_tls)
target_link_libraries(BeamMP-Server
z
pthread
stdc++fs
${LUA_LIBRARIES}
crypto
${OPENSSL_LIBRARIES}
commandline
sentry)
elseif (WIN32)
include(FindLua)
message(STATUS "Looking for libz")
find_package(ZLIB REQUIRED)
message(STATUS "Looking for RapidJSON")
find_package(RapidJSON CONFIG REQUIRED)
target_include_directories(BeamMP-Server PRIVATE ${RAPIDJSON_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
target_link_libraries(BeamMP-Server ws2_32 ZLIB::ZLIB ${OPENSSL_LIBRARIES} commandline sioclient_tls)
target_link_libraries(BeamMP-Server PRIVATE
ws2_32
ZLIB::ZLIB
${LUA_LIBRARIES}
${OPENSSL_LIBRARIES}
commandline
sentry)
endif ()

65
Changelog.md Normal file
View File

@ -0,0 +1,65 @@
# v2.3.0
- ADDED version check - the server will now let you know when a new release is out
- ADDED logging of various errors, crashes and exceptions to the backend
- ADDED chat messages are now logged to the server console as [CHAT]
- ADDED debug message telling you when the server heartbeats to the backend
- REMOVED various [DEBUG] messages which were confusing (such as "breaking client loop")
- FIXED various crashes and issues with handling unexpected backend responses
- FIXED minor bugs due to code correctness
# v2.2.0
- FIXED major security flaw
- FIXED minor bugs
# v2.1.4
- ADDED debug heartbeat print
- ADDED kicking every player before shutdown
- FIXED rare bug which led to violent crash
- FIXED minor bugs
# v2.1.3
- FIXED Lua events not cancelling properly on Linux
# v2.1.2
- CHANGED default map to gridmap v2
- FIXED version number display
# v2.1.1
# v2.1.0 (pre-v2.1.1)
# v2.0.4 (pre-v2.1.0)
- REMOVED boost as a runtime dependency
- FIXED Lua plugins on Linux
- FIXED console history on Windows
- CHANGED to new config format TOML
# v2.0.3
- WORKAROUND for timeout bug / ghost player bug
- FIXED 100% CPU spin when stdin is /dev/null.
# v2.0.2
- ADDED fully new commandline
- ADDED new backend
- ADDED automated build system
- ADDED lua GetPlayerIdentifiers
- ADDED lots of debug info
- ADDED better POSTing and GETing
- ADDED a license
- FIXED ghost players in player list issue
- FIXED ghost vehicle after joining issue
- FIXED missing vehicle after joining issue
- FIXED a lot of desync issues
- FIXED some memory leaks
- FIXED various crashes
- FIXED various data-races
- FIXED some linux-specific crashes
- FIXED some linux-specific issues
- FIXED bug which caused kicking to be logged as leaving
- FIXED various internal developer quality-of-life things

View File

@ -11,7 +11,7 @@ The server is the point throug which all clients communicate. You can write lua
These values are guesstimated and are subject to change with each release.
* RAM: 50+ MiB usable (not counting OS overhead)
* CPU: Any Hz, preferably multicore
* CPU: >1GHz, preferably multicore
* OS: Windows, Linux (theoretically any POSIX)
* GPU: None
* HDD: 10 MiB + Mods/Plugins
@ -61,6 +61,7 @@ These package names are in the debian / ubuntu style. Feel free to PR your own g
- `make`
- `cmake`
- `g++`
- `libcurl4-openssl-dev` or similar (search for `libcurl` and look for one with `-dev`)
Must support ISO C++17. If your distro's `g++` doesn't support C++17, chances are that it has a `g++-8` or `g++-10` package that does. If this is the case. you just need to run CMake with `-DCMAKE_CXX_COMPILER=g++-10` (replace `g++-10` with your compiler's name).
- `liblua5.3-dev`
@ -72,6 +73,13 @@ These package names are in the debian / ubuntu style. Feel free to PR your own g
- `libopenssl-dev` or `libssl-dev`
**If** you're building it from source, you'll need `libboost1.70-all-dev` or `libboost1.71-all-dev` or higher as well.
If you can't find this version of boost (only 1.6x, for example), you can either update to a newer version of your distro, build boost yourself, or use an unstable rolling release (like Debian `sid` aka `unstable`).
In the end you should end up with a command something like this:
```sh
sudo apt install git make cmake g++-10 liblua5.3 libz-dev rapidjson-dev libopenssl-dev libboost1.71-all-dev
```
In the end you should end up with a command something like this:
@ -81,14 +89,15 @@ sudo apt install git make cmake g++-10 liblua5.3 libz-dev rapidjson-dev libopens
### How to build
On windows. use git-bash for these commands. On linux, these should work in your shell.
On windows, use git-bash for these commands. On Linux, these should work in your shell.
1. Make sure you have all [prerequisites](#prerequisites) installed
2. Clone the repository in a location of your choice with `git clone --recursive https://github.com/BeamMP/BeamMP-Server`. Now change into the cloned directory by running `cd BeamMP-Server`.
3. Checkout the branch of the release you want to compile (`master` is often unstable), for example `git checkout tags/v1.20` for version 1.20. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags).
4. Run `cmake .` (with `.`)
5. Run `make`
6. You will now have a `BeamMP-Server` file in your directory, which is executable with `./BeamMP-Server` (`.\BeamMP-Server.exe` for windows). Follow the (windows or linux, doesnt matter) instructions on the [wiki](https://wiki.beammp.com/en/home/Server_Mod) for further setup after installation (which we just did), such as port-forwarding and getting a key to actually run the server.
2. Clone the repository in a location of your choice with `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Server`.
3. Ensure that all submodules are initialized by running `git submodule update --init --recursive`. Then change into the cloned directory by running `cd BeamMP-Server`.
4. Checkout the branch of the release you want to compile (`master` is often unstable), for example `git checkout tags/v1.20` for version 1.20. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags).
5. Run `cmake .` (with `.`)
6. Run `make`
7. You will now have a `BeamMP-Server` file in your directory, which is executable with `./BeamMP-Server` (`.\BeamMP-Server.exe` for windows). Follow the (windows or linux, doesnt matter) instructions on the [wiki](https://wiki.beammp.com/en/home/Server_Mod) for further setup after installation (which we just did), such as port-forwarding and getting a key to actually run the server.
*tip: to run the server in the background, simply (in bash, zsh, etc) run:* `nohup ./BeamMP-Server &`*.*

1
deps/asio vendored

@ -1 +0,0 @@
Subproject commit 230c0d2ae035c5ce1292233fcab03cea0d341264

1
deps/commandline vendored

@ -1 +0,0 @@
Subproject commit c34703df11ca00b7c4b04478bc566bd9744ac858

1
deps/rapidjson vendored

@ -1 +0,0 @@
Subproject commit 13dfc96c9c2b104be7b0b09a9f6e06871ed3e81d

@ -1 +0,0 @@
Subproject commit b196fa7537cd3f7bed626ead873a7b71d1293c0d

View File

@ -13,6 +13,11 @@
class TServer;
#ifdef WIN32
// for socklen_t
#include <WS2tcpip.h>
#endif // WIN32
struct TConnection final {
SOCKET Socket;
struct sockaddr SockAddr;

View File

@ -1,5 +1,9 @@
#pragma once
#include "TSentry.h"
extern TSentry Sentry;
#include <array>
#include <atomic>
#include <cstring>
#include <deque>
@ -17,6 +21,7 @@ struct Version {
uint8_t minor;
uint8_t patch;
Version(uint8_t major, uint8_t minor, uint8_t patch);
Version(const std::array<uint8_t, 3>& v);
std::string AsString();
};
@ -67,13 +72,18 @@ public:
static const Version& ServerVersion() { return mVersion; }
static std::string ClientVersionString() { return "2.0"; }
static std::string PPS() { return mPPS; }
static void SetPPS(std::string NewPPS) { mPPS = NewPPS; }
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
static inline TSettings Settings {};
static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; }
static std::string GetBackendHostname() { return "backend.beammp.com"; }
static std::string GetBackup1Hostname() { return "backup1.beammp.com"; }
static std::string GetBackup2Hostname() { return "backup2.beammp.com"; }
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
static void CheckForUpdates();
static std::array<uint8_t, 3> VersionStrToInts(const std::string& str);
static bool IsOutdated(const Version& Current, const Version& Newest);
private:
static inline std::string mPPS;
@ -81,11 +91,11 @@ private:
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 2, 3, 0 };
static inline Version mVersion { 2, 4, 0 };
};
std::string ThreadName();
void RegisterThread(const std::string str);
std::string ThreadName(bool DebugModeOverride = false);
void RegisterThread(const std::string& str);
#define RegisterThreadAuto() RegisterThread(__func__)
#define KB 1024
@ -124,8 +134,11 @@ void RegisterThread(const std::string str);
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
#define beammp_error(x) Application::Console().Write(_this_location + std::string("[ERROR] ") + (x))
#define beammp_lua_error(x) Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x))
#define error(x) \
do { \
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
} while (false)
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \
do { \
@ -136,6 +149,19 @@ void RegisterThread(const std::string str);
// for those times when you just need to ignore something :^)
// explicity disables a [[nodiscard]] warning
#define beammp_ignore(x) (void)x
// trace() is a debug-build debug()
#if defined(DEBUG)
#define trace(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \
} while (false)
#else
#define trace(x)
#endif // defined(DEBUG)
void LogChatMessage(const std::string& name, int id, const std::string& msg);
#define Biggest 30000
std::string Comp(std::string Data);

View File

@ -13,9 +13,9 @@ using DWORD = unsigned long;
using PDWORD = unsigned long*;
using LPDWORD = unsigned long*;
char _getch();
inline void CloseSocketProper(int socket) {
shutdown(socket, SHUT_RDWR);
close(socket);
inline void CloseSocketProper(int TheSocket) {
shutdown(TheSocket, SHUT_RDWR);
close(TheSocket);
}
#endif // unix
@ -24,10 +24,10 @@ inline void CloseSocketProper(int socket) {
#ifdef WIN32
#include <conio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
inline void CloseSocketProper(SOCKET socket) {
shutdown(socket, SD_BOTH);
closesocket(socket);
inline void CloseSocketProper(SOCKET TheSocket) {
shutdown(TheSocket, 2); // 2 == SD_BOTH
closesocket(TheSocket);
}
#endif // WIN32
@ -35,4 +35,4 @@ inline void CloseSocketProper(SOCKET socket) {
#if !defined(WIN32) && !defined(__unix)
#error "OS not supported"
#endif
#endif

View File

@ -40,7 +40,7 @@ static const char* const ANSI_WHITE_BOLD = "\u001b[37;1m";
static const char* const ANSI_BOLD = "\u001b[1m";
static const char* const ANSI_UNDERLINE = "\u001b[4m";
#if DEBUG
#ifdef DEBUG
#include <iostream>
inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const char* function, [[maybe_unused]] unsigned line,
[[maybe_unused]] const char* condition_string, [[maybe_unused]] bool result) {
@ -60,9 +60,14 @@ inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const ch
#else
// In release build, these macros turn into NOPs. The compiler will optimize these out.
#define beammp_assert(x) \
do { \
do { \
bool result = (cond); \
if (!result) { \
Sentry.LogAssert(#cond, _file_basename, _line, __func__); \
} \
} while (false)
#define beammp_assert_not_reachable() \
do { \
do { \
Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \
} while (false)
#endif // DEBUG

18
include/Defer.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <functional>
template <typename FnT>
class Defer final {
public:
Defer(FnT fn)
: mFunction([&fn] { (void)fn(); }) { }
~Defer() {
if (mFunction) {
mFunction();
}
}
private:
std::function<void()> mFunction;
};

View File

@ -6,5 +6,8 @@
namespace Http {
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr);
std::string POST(const std::string& host, int port, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr);
static inline const char* ErrorString = "!BeamMPHttpsError!";
namespace Status {
std::string ToString(int code);
}
const std::string ErrorString = "-1";
}

View File

@ -1,69 +0,0 @@
#pragma once
#include <atomic>
#include <deque>
#include <mutex>
#include <sio_client.h>
#include <thread>
#include <memory>
/*
* We send relevant server events over socket.io to the backend.
*
* We send all events to `backend.beammp.com`, to the room `/key`
* where `key` is the currently active auth-key.
*/
enum class SocketIOEvent {
ConsoleOut,
CPUUsage,
MemoryUsage,
NetworkUsage,
PlayerList,
};
enum class SocketIORoom {
None,
Stats,
Player,
Info,
Console,
};
class SocketIO final {
private:
struct Event;
public:
enum class EventType {
};
// Singleton pattern
static SocketIO& Get();
void Emit(SocketIOEvent Event, const std::string& Data);
~SocketIO();
void SetAuthenticated(bool auth) { mAuthenticated = auth; }
private:
SocketIO() noexcept;
void ThreadMain();
struct Event {
std::string Name;
std::string Data;
};
bool mAuthenticated { false };
sio::client mClient;
std::thread mThread;
std::atomic_bool mCloseThread { false };
std::mutex mQueueMutex;
std::deque<Event> mQueue;
friend std::unique_ptr<SocketIO> std::make_unique<SocketIO>();
};

38
include/TSentry.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef SENTRY_H
#define SENTRY_H
#include <mutex>
#include <string>
#include <unordered_map>
enum class SentryLevel {
Debug = -1,
Info = 0,
Warning = 1,
Error = 2,
Fatal = 3,
};
// singleton, dont make this twice
class TSentry final {
public:
TSentry();
~TSentry();
void PrintWelcome();
void SetupUser();
void Log(SentryLevel level, const std::string& logger, const std::string& text);
void LogError(const std::string& text, const std::string& file, const std::string& line);
void SetContext(const std::string& context_name, const std::unordered_map<std::string, std::string>& map);
void LogException(const std::exception& e, const std::string& file, const std::string& line);
void LogAssert(const std::string& condition_string, const std::string& file, const std::string& line, const std::string& function);
void AddErrorBreadcrumb(const std::string& msg, const std::string& file, const std::string& line);
// cleared when Logged
void SetTransaction(const std::string& id);
[[nodiscard]] std::unique_lock<std::mutex> CreateExclusiveContext();
private:
bool mValid { true };
std::mutex mMutex;
};
#endif // SENTRY_H

1
include/sentry-native Submodule

@ -0,0 +1 @@
Subproject commit 521860373df1cc02607546c3474d78b42042b459

1
include/toml11 Submodule

@ -0,0 +1 @@
Subproject commit 647381020ef04b5d41d540ec489eba45e82d90a7

View File

@ -94,7 +94,6 @@ TClient::TClient(TServer& Server)
void TClient::UpdatePingTime() {
mLastPingTime = std::chrono::high_resolution_clock::now();
//debug(GetName() + ": " + std::string("ping time updated!: ") + ((SecondsSinceLastPing() == 0) ? "OK" : "ERR"));
}
int TClient::SecondsSinceLastPing() {
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(

View File

@ -2,11 +2,17 @@
#include "TConsole.h"
#include <array>
#include <charconv>
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <thread>
#include <zlib.h>
#include "CustomAssert.h"
#include "Http.h"
std::unique_ptr<TConsole> Application::mConsole = std::make_unique<TConsole>();
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
@ -17,7 +23,7 @@ void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
}
void Application::GracefullyShutdown() {
beammp_info("please wait while all subsystems are shutting down...");
info("please wait while all subsystems are shutting down...");
std::unique_lock Lock(mShutdownHandlersMutex);
for (auto& Handler : mShutdownHandlers) {
Handler();
@ -28,6 +34,53 @@ std::string Application::ServerVersionString() {
return mVersion.AsString();
}
std::array<uint8_t, 3> Application::VersionStrToInts(const std::string& str) {
std::array<uint8_t, 3> Version;
std::stringstream ss(str);
for (uint8_t& i : Version) {
std::string Part;
std::getline(ss, Part, '.');
std::from_chars(&*Part.begin(), &*Part.begin() + Part.size(), i);
}
return Version;
}
// FIXME: This should be used by operator< on Version
bool Application::IsOutdated(const Version& Current, const Version& Newest) {
if (Newest.major > Current.major) {
return true;
} else if (Newest.major == Current.major && Newest.minor > Current.minor) {
return true;
} else if (Newest.major == Current.major && Newest.minor == Current.minor && Newest.patch > Current.patch) {
return true;
} else {
return false;
}
}
void Application::CheckForUpdates() {
// checks current version against latest version
std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" };
auto Response = Http::GET(GetBackendHostname(), 443, "/v/s");
bool Matches = std::regex_match(Response, VersionRegex);
if (Matches) {
auto MyVersion = ServerVersion();
auto RemoteVersion = Version(VersionStrToInts(Response));
if (IsOutdated(MyVersion, RemoteVersion)) {
std::string RealVersionString = RemoteVersion.AsString();
warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For info on how to update your server, visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server." + std::string(ANSI_RESET));
} else {
info("Server up-to-date!");
}
} else {
warn("Unable to fetch version from backend.");
trace("got " + Response);
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("get-response", { { "response", Response } });
Sentry.LogError("failed to get server version", _file_basename, _line);
}
}
std::string Comp(std::string Data) {
std::array<char, Biggest> C {};
// obsolete
@ -74,10 +127,12 @@ std::string DeComp(std::string Compressed) {
// thread name stuff
std::map<std::thread::id, std::string> threadNameMap;
static std::map<std::thread::id, std::string> threadNameMap {};
static std::mutex ThreadNameMapMutex {};
std::string ThreadName() {
if (Application::Settings.DebugModeEnabled) {
std::string ThreadName(bool DebugModeOverride) {
auto Lock = std::unique_lock(ThreadNameMapMutex);
if (DebugModeOverride || Application::Settings.DebugModeEnabled) {
auto id = std::this_thread::get_id();
if (threadNameMap.find(id) != threadNameMap.end()) {
// found
@ -87,7 +142,8 @@ std::string ThreadName() {
return "";
}
void RegisterThread(const std::string str) {
void RegisterThread(const std::string& str) {
auto Lock = std::unique_lock(ThreadNameMapMutex);
threadNameMap[std::this_thread::get_id()] = str;
}
@ -96,12 +152,27 @@ Version::Version(uint8_t major, uint8_t minor, uint8_t patch)
, minor(minor)
, patch(patch) { }
Version::Version(const std::array<uint8_t, 3>& v)
: Version(v[0], v[1], v[2]) {
}
std::string Version::AsString() {
std::stringstream ss {};
ss << int(major) << "." << int(minor) << "." << int(patch);
return ss.str();
}
void LogChatMessage(const std::string& name, int id, const std::string& msg) {
std::stringstream ss;
ss << "[CHAT] ";
if (id != -1) {
ss << "(" << id << ") <" << name << ">";
} else {
ss << name << "";
}
ss << msg;
}
std::string GetPlatformAgnosticErrorString() {
#ifdef WIN32
// This will provide us with the error code and an error message, all in one.
@ -128,15 +199,3 @@ std::string GetPlatformAgnosticErrorString() {
return std::strerror(errno);
#endif
}
void LogChatMessage(const std::string& name, int id, const std::string& msg) {
std::stringstream ss;
ss << "[CHAT] ";
if (id != -1) {
ss << "(" << id << ") <" << name << ">";
} else {
ss << name << "";
}
ss << msg;
Application::Console().Write(ss.str());
}

View File

@ -1,12 +1,13 @@
#include "Http.h"
#include "Common.h"
#undef beammp_error
#undef error
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <map>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
@ -14,85 +15,7 @@ namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
try {
// Check command line arguments.
int version = 11;
// The io_context is required for all I/O
net::io_context ioc;
// The SSL context is required, and holds certificates
ssl::context ctx(ssl::context::tlsv12_client);
// This holds the root certificate used for verification
// we don't do / have this
// load_root_certificates(ctx);
// Verify the remote server's certificate
ctx.set_verify_mode(ssl::verify_none);
// These objects perform our I/O
tcp::resolver resolver(ioc);
beast::ssl_stream<beast::tcp_stream> stream(ioc, ctx);
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
beast::error_code ec { static_cast<int>(::ERR_get_error()), net::error::get_ssl_category() };
throw beast::system_error { ec };
}
// Look up the domain name
auto const results = resolver.resolve(host.c_str(), std::to_string(port));
// Make the connection on the IP address we get from a lookup
beast::get_lowest_layer(stream).connect(results);
// Perform the SSL handshake
stream.handshake(ssl::stream_base::client);
// Set up an HTTP GET request message
http::request<http::string_body> req { http::verb::get, target, version };
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::write(stream, req);
// This buffer is used for reading and must be persisted
beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::string_body> res;
// Receive the HTTP response
http::read(stream, buffer, res);
// Gracefully close the stream
beast::error_code ec;
stream.shutdown(ec);
if (ec == net::error::eof) {
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec = {};
}
if (status) {
*status = res.base().result_int();
}
if (ec)
throw beast::system_error { ec };
// If we get here then the connection is closed gracefully
return std::string(res.body());
} catch (std::exception const& e) {
Application::Console().Write(__func__ + std::string(": ") + e.what());
return ErrorString;
}
}
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, unsigned int* status) {
std::string GenericRequest(http::verb verb, const std::string& host, int port, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, unsigned int* status) {
try {
net::io_context io;
@ -125,17 +48,18 @@ std::string Http::POST(const std::string& host, int port, const std::string& tar
//debug("IPv6 connect failed, trying IPv4");
bool ok = try_connect_with_protocol(tcp::v4());
if (!ok) {
//error("failed to resolve or connect in POST " + host + target);
Application::Console().Write("[ERROR] failed to resolve or connect in POST " + host + target);
return "-1";
}
//}
stream.handshake(ssl::stream_base::client);
http::request<http::string_body> req { http::verb::post, target, 11 /* http 1.1 */ };
http::request<http::string_body> req { verb, target, 11 /* http 1.1 */ };
req.set(http::field::host, host);
if (!body.empty()) {
req.set(http::field::content_type, ContentType); // "application/json"
// "application/x-www-form-urlencoded"
req.set(http::field::content_length, std::to_string(body.size()));
req.body() = body;
// info("body is " + body + " (" + req.body() + ")");
@ -146,6 +70,16 @@ std::string Http::POST(const std::string& host, int port, const std::string& tar
req.set(pair.first, pair.second);
}
std::unordered_map<std::string, std::string> request_data;
for (const auto& header : req.base()) {
// need to do explicit casts to convert string_view to string
// since string_view may not be null-terminated (and in fact isn't, here)
std::string KeyString(header.name_string());
std::string ValueString(header.value());
request_data[KeyString] = ValueString;
}
Sentry.SetContext("https-post-request-data", request_data);
std::stringstream oss;
oss << req;
@ -159,6 +93,20 @@ std::string Http::POST(const std::string& host, int port, const std::string& tar
http::read(stream, buffer, response);
std::unordered_map<std::string, std::string> response_data;
response_data["reponse-code"] = std::to_string(response.result_int());
if (status) {
*status = response.result_int();
}
for (const auto& header : response.base()) {
// need to do explicit casts to convert string_view to string
// since string_view may not be null-terminated (and in fact isn't, here)
std::string KeyString(header.name_string());
std::string ValueString(header.value());
response_data[KeyString] = ValueString;
}
Sentry.SetContext("https-post-response-data", response_data);
if (status) {
*status = response.base().result_int();
}
@ -179,6 +127,88 @@ std::string Http::POST(const std::string& host, int port, const std::string& tar
} catch (const std::exception& e) {
Application::Console().Write(__func__ + std::string(": ") + e.what());
return ErrorString;
return Http::ErrorString;
}
}
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
return GenericRequest(http::verb::get, host, port, target, {}, {}, {}, status);
}
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, unsigned int* status) {
return GenericRequest(http::verb::post, host, port, target, fields, body, ContentType, status);
}
// RFC 2616, RFC 7231
static std::map<size_t, const char*> Map = {
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
{ 103, "Early Hints" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
{ 226, "IM Used" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "(Unused)" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Payload Too Large" },
{ 414, "URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 421, "Misdirected Request" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
{ 425, "Too Early" },
{ 426, "Upgrade Required" },
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
{ 431, "Request Header Fields Too Large" },
{ 451, "Unavailable For Legal Reasons" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" },
};
std::string Http::Status::ToString(int code) {
if (Map.find(code) != Map.end()) {
return Map.at(code);
} else {
return "Unassigned";
}
}

View File

@ -1,98 +0,0 @@
#include "SocketIO.h"
#include "Common.h"
#include <iostream>
//TODO Default disabled with config option
static std::unique_ptr<SocketIO> SocketIOInstance = std::make_unique<SocketIO>();
SocketIO& SocketIO::Get() {
return *SocketIOInstance;
}
SocketIO::SocketIO() noexcept
: mThread([this] { ThreadMain(); }) {
mClient.socket()->on("network", [&](sio::event&e) {
if(e.get_message()->get_string() == "Welcome"){
info("SocketIO Authenticated!");
mAuthenticated = true;
}
});
mClient.socket()->on("welcome", [&](sio::event&) {
info("Got welcome from backend! Authenticating SocketIO...");
mClient.socket()->emit("onInitConnection", Application::Settings.Key);
});
mClient.set_logs_quiet();
mClient.set_reconnect_delay(10000);
mClient.connect(Application::GetBackendUrlForSocketIO());
}
SocketIO::~SocketIO() {
mCloseThread.store(true);
mThread.join();
}
static constexpr auto EventNameFromEnum(SocketIOEvent Event) {
switch (Event) {
case SocketIOEvent::CPUUsage:
return "cpu usage";
case SocketIOEvent::MemoryUsage:
return "memory usage";
case SocketIOEvent::ConsoleOut:
return "console out";
case SocketIOEvent::NetworkUsage:
return "network usage";
case SocketIOEvent::PlayerList:
return "player list";
default:
error("unreachable code reached (developer error)");
abort();
}
}
void SocketIO::Emit(SocketIOEvent Event, const std::string& Data) {
if (!mAuthenticated) {
debug("trying to emit a socket.io event when not yet authenticated");
return;
}
std::string EventName = EventNameFromEnum(Event);
debug("emitting event \"" + EventName + "\" with data: \"" + Data);
std::unique_lock Lock(mQueueMutex);
mQueue.push_back({EventName, Data });
debug("queue now has " + std::to_string(mQueue.size()) + " events");
}
void SocketIO::ThreadMain() {
while (!mCloseThread.load()) {
bool empty;
{ // queue lock scope
std::unique_lock Lock(mQueueMutex);
empty = mQueue.empty();
} // end queue lock scope
if (empty || !mClient.opened()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
} else {
Event TheEvent;
{ // queue lock scope
std::unique_lock Lock(mQueueMutex);
TheEvent = mQueue.front();
mQueue.pop_front();
} // end queue lock scope
debug("sending \"" + TheEvent.Name + "\" event");
mClient.socket()->emit(TheEvent.Name, TheEvent.Data);
debug("sent \"" + TheEvent.Name + "\" event");
}
}
// using std::cout as this happens during static destruction and the logger might be dead already
std::cout << "closing " + std::string(__func__) << std::endl;
mClient.sync_close();
mClient.clear_con_listeners();
std::cout << "closed" << std::endl;
}

View File

@ -26,7 +26,7 @@ void THeartbeatThread::operator()() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
beammp_debug("heartbeat (after " + std::to_string(std::chrono::duration_cast<std::chrono::seconds>(TimePassed).count()) + "s)");
debug("heartbeat (after " + std::to_string(std::chrono::duration_cast<std::chrono::seconds>(TimePassed).count()) + "s)");
Last = Body;
LastNormalUpdateTime = Now;
@ -35,26 +35,48 @@ void THeartbeatThread::operator()() {
Body += "&pps=" + Application::PPS();
T = Http::POST(Application::GetBackendHostname(), 443, "/heartbeat", {}, Body, "application/x-www-form-urlencoded");
auto SentryReportError = [&](const std::string& transaction, int status) {
if (status < 0) {
status = 0;
}
if (T.substr(0, 2) != "20") {
//Backend system refused server startup!
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("heartbeat",
{ { "response-body", T },
{ "request-body", Body } });
Sentry.SetTransaction(transaction);
trace("sending log to sentry: " + std::to_string(status) + " for " + transaction);
Sentry.Log(SentryLevel::Error, "default", Http::Status::ToString(status) + " (" + std::to_string(status) + ")");
};
auto Target = "/heartbeat";
unsigned int ResponseCode = 0;
T = Http::POST(Application::GetBackendHostname(), 443, Target, {}, Body, "application/x-www-form-urlencoded", &ResponseCode);
if (T.substr(0, 2) != "20" || ResponseCode != 200) {
trace("got " + T + " from backend");
SentryReportError(Application::GetBackendHostname() + Target, ResponseCode);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
T = Http::POST(Application::GetBackendHostname(), 443, "/heartbeat", {}, Body, "application/x-www-form-urlencoded");
// TODO backup2 + HTTP flag (no TSL)
if (T.substr(0, 2) != "20") {
beammp_warn("Backend system refused server! Server might not show in the public list");
beammp_debug("server returned \"" + T + "\"");
isAuth = false;
T = Http::POST(Application::GetBackup1Hostname(), 443, Target, {}, Body, "application/x-www-form-urlencoded", &ResponseCode);
if (T.substr(0, 2) != "20" || ResponseCode != 200) {
SentryReportError(Application::GetBackup1Hostname() + Target, ResponseCode);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
T = Http::POST(Application::GetBackup2Hostname(), 443, Target, {}, Body, "application/x-www-form-urlencoded", &ResponseCode);
if (T.substr(0, 2) != "20" || ResponseCode != 200) {
warn("Backend system refused server! Server will not show in the public server list.");
isAuth = false;
SentryReportError(Application::GetBackup2Hostname() + Target, ResponseCode);
}
}
}
if (!isAuth) {
if (T == "2000") {
beammp_info(("Authenticated!"));
info(("Authenticated!"));
isAuth = true;
} else if (T == "200") {
beammp_info(("Resumed authenticated session!"));
info(("Resumed authenticated session!"));
isAuth = true;
}
}
@ -62,6 +84,7 @@ void THeartbeatThread::operator()() {
//SocketIO::Get().SetAuthenticated(isAuth);
}
}
std::string THeartbeatThread::GenerateCall() {
std::stringstream Ret;
@ -86,10 +109,10 @@ THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& S
, mServer(Server) {
Application::RegisterShutdownHandler([&] {
if (mThread.joinable()) {
beammp_debug("shutting down Heartbeat");
debug("shutting down Heartbeat");
mShutdown = true;
mThread.join();
beammp_debug("shut down Heartbeat");
debug("shut down Heartbeat");
}
});
Start();

View File

@ -2,18 +2,15 @@
#include "Client.h"
#include <CustomAssert.h>
#include <Http.h>
#include <TLuaPlugin.h>
#include <array>
#include <cstring>
#include "LuaAPI.h"
TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager)
: mServer(Server)
, mPPSMonitor(PPSMonitor)
, mResourceManager(ResourceManager) {
Application::RegisterShutdownHandler([&] {
beammp_debug("Kicking all players due to shutdown");
debug("Kicking all players due to shutdown");
Server.ForEachClient([&](std::weak_ptr<TClient> client) -> bool {
if (!client.expired()) {
ClientKick(*client.lock(), "Server shutdown");
@ -23,18 +20,18 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
});
Application::RegisterShutdownHandler([&] {
if (mUDPThread.joinable()) {
beammp_debug("shutting down TCPServer");
debug("shutting down TCPServer");
mShutdown = true;
mUDPThread.detach();
beammp_debug("shut down TCPServer");
debug("shut down TCPServer");
}
});
Application::RegisterShutdownHandler([&] {
if (mTCPThread.joinable()) {
beammp_debug("shutting down TCPServer");
debug("shutting down TCPServer");
mShutdown = true;
mTCPThread.detach();
beammp_debug("shut down TCPServer");
debug("shut down TCPServer");
}
});
mTCPThread = std::thread(&TNetwork::TCPServerMain, this);
@ -46,7 +43,7 @@ void TNetwork::UDPServerMain() {
#ifdef WIN32
WSADATA data;
if (WSAStartup(514, &data)) {
beammp_error(("Can't start Winsock!"));
error(("Can't start Winsock!"));
//return;
}
#endif // WIN32
@ -59,13 +56,13 @@ void TNetwork::UDPServerMain() {
// Try and bind the socket to the IP and port
if (bind(mUDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) {
beammp_error("bind() failed: " + GetPlatformAgnosticErrorString());
error("bind() failed: " + GetPlatformAgnosticErrorString());
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(-1);
//return;
}
beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ")
info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ")
+ std::to_string(Application::Settings.MaxPlayers) + (" Clients"));
while (!mShutdown) {
try {
@ -97,7 +94,7 @@ void TNetwork::UDPServerMain() {
return true;
});
} catch (const std::exception& e) {
beammp_error(("fatal: ") + std::string(e.what()));
error(("fatal: ") + std::string(e.what()));
}
}
}
@ -107,7 +104,7 @@ void TNetwork::TCPServerMain() {
#ifdef WIN32
WSADATA wsaData;
if (WSAStartup(514, &wsaData)) {
beammp_error("Can't start Winsock!");
error("Can't start Winsock!");
return;
}
#endif // WIN32
@ -115,54 +112,55 @@ void TNetwork::TCPServerMain() {
SOCKET Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int optval = 1;
#ifdef WIN32
setsockopt(Listener, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&optval), sizeof(optval));
const char* optval_ptr = reinterpret_cast<const char*>(&optval);
#else
setsockopt(Listener, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void*>(&optval), sizeof(optval));
void* optval_ptr = reinterpret_cast<void*>(&optval);
#endif
setsockopt(Listener, SOL_SOCKET, SO_REUSEADDR, optval_ptr, sizeof(optval));
// TODO: check optval or return value idk
sockaddr_in addr {};
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_family = AF_INET;
addr.sin_port = htons(uint16_t(Application::Settings.Port));
if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) != 0) {
beammp_error("bind() failed: " + GetPlatformAgnosticErrorString());
error("bind() failed: " + GetPlatformAgnosticErrorString());
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(-1);
}
if (Listener == -1) {
beammp_error("Invalid listening socket");
error("Invalid listening socket");
return;
}
if (listen(Listener, SOMAXCONN)) {
beammp_error("listen() failed: " + GetPlatformAgnosticErrorString());
error("listen() failed: " + GetPlatformAgnosticErrorString());
// FIXME leak Listener
return;
}
beammp_info(("Vehicle event network online"));
info(("Vehicle event network online"));
do {
try {
if (mShutdown) {
beammp_debug("shutdown during TCP wait for accept loop");
debug("shutdown during TCP wait for accept loop");
break;
}
client.SockAddrLen = sizeof(client.SockAddr);
client.Socket = accept(Listener, &client.SockAddr, &client.SockAddrLen);
if (client.Socket == -1) {
beammp_warn(("Got an invalid client socket on connect! Skipping..."));
warn(("Got an invalid client socket on connect! Skipping..."));
continue;
}
std::thread ID(&TNetwork::Identify, this, client);
ID.detach(); // TODO: Add to a queue and attempt to join periodically
} catch (const std::exception& e) {
beammp_error(("fatal: ") + std::string(e.what()));
error(("fatal: ") + std::string(e.what()));
}
} while (client.Socket);
beammp_debug("all ok, arrived at " + std::string(__func__) + ":" + std::to_string(__LINE__));
debug("all ok, arrived at " + std::string(__func__) + ":" + std::to_string(__LINE__));
CloseSocketProper(client.Socket);
#ifdef WIN32
CloseSocketProper(client.Socket);
CloseSocketProper(client);
WSACleanup();
#endif // WIN32
}
@ -215,7 +213,7 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
Client->SetIdentifier("ip", str);
std::string Rc;
beammp_info("Identifying new ClientConnection...");
info("Identifying new ClientConnection...");
Rc = TCPRcv(*Client);
@ -240,8 +238,12 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
return;
}
auto RequestString = R"({"key":")" + Rc + "\"}";
auto Target = "/pkToUser";
unsigned int ResponseCode = 0;
if (!Rc.empty()) {
Rc = Http::POST(Application::GetBackendUrlForAuth(), 443, "/pkToUser", {}, R"({"key":")" + Rc + "\"}", "application/json");
Rc = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, {}, RequestString, "application/json", &ResponseCode);
}
json::Document AuthResponse;
@ -252,8 +254,23 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
}
if (!AuthResponse.IsObject()) {
ClientKick(*Client, "Backend returned invalid auth response format.");
beammp_error("Backend returned invalid auth response format. This should never happen.");
if (Rc == "0") {
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("auth",
{ { "response-body", Rc },
{ "key", RequestString } });
Sentry.SetTransaction(Application::GetBackendUrlForAuth() + Target);
Sentry.Log(SentryLevel::Info, "default", "backend returned 0 instead of json (" + std::to_string(ResponseCode) + ")");
} else { // Rc != "0"
ClientKick(*Client, "Backend returned invalid auth response format.");
error("Backend returned invalid auth response format. This should never happen.");
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("auth",
{ { "response-body", Rc },
{ "key", RequestString } });
Sentry.SetTransaction(Application::GetBackendUrlForAuth() + Target);
Sentry.Log(SentryLevel::Error, "default", "unexpected backend response (" + std::to_string(ResponseCode) + ")");
}
return;
}
@ -273,8 +290,7 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
return;
}
beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
beammp_debug("There are " + std::to_string(mServer.ClientCount()) + " known clients");
debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
std::shared_ptr<TClient> Cl;
{
@ -284,10 +300,7 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
} else
return true;
}
beammp_info("Client Iteration: Name -> " + Cl->GetName() + ", Guest -> " + std::to_string(Cl->IsGuest()) + ", Roles -> " + Cl->GetRoles());
if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) {
beammp_info("New ClientConnection matched with current iteration");
beammp_info("Old ClientConnection (" + Cl->GetName() + ") kicked: Reconnecting");
CloseSocketProper(Cl->GetTCPSock());
Cl->SetStatus(-2);
return false;
@ -295,32 +308,19 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
return true;
});
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", Client->GetName(), Client->GetRoles(), Client->IsGuest());
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) {
auto arg = std::make_unique<TLuaArg>(TLuaArg { { Client->GetName(), Client->GetRoles(), Client->IsGuest() } });
std::any Res = TriggerLuaEvent("onPlayerAuth", false, nullptr, std::move(arg), true);
if (Res.type() == typeid(int) && std::any_cast<int>(Res)) {
ClientKick(*Client, "you are not allowed on the server!");
return;
} else if (NotAllowedWithReason) {
ClientKick(*Client, Reason);
} else if (Res.type() == typeid(std::string)) {
ClientKick(*Client, std::any_cast<std::string>(Res));
return;
}
if (mServer.ClientCount() < size_t(Application::Settings.MaxPlayers)) {
beammp_info("Identification success");
info("Identification success");
mServer.InsertClient(Client);
TCPClient(Client);
} else
@ -359,12 +359,12 @@ bool TNetwork::TCPSend(TClient& c, const std::string& Data, bool IsSync) {
int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, MSG_NOSIGNAL);
#endif //WIN32
if (Temp == 0) {
beammp_debug("send() == 0: " + GetPlatformAgnosticErrorString());
debug("send() == 0: " + GetPlatformAgnosticErrorString());
if (c.GetStatus() > -1)
c.SetStatus(-1);
return false;
} else if (Temp < 0) {
beammp_debug("send() < 0: " + GetPlatformAgnosticErrorString()); //TODO fix it was spamming yet everyone stayed on the server
debug("send() < 0: " + GetPlatformAgnosticErrorString()); //TODO fix it was spamming yet everyone stayed on the server
if (c.GetStatus() > -1)
c.SetStatus(-1);
CloseSocketProper(c.GetTCPSock());
@ -378,15 +378,14 @@ bool TNetwork::TCPSend(TClient& c, const std::string& Data, bool IsSync) {
bool TNetwork::CheckBytes(TClient& c, int32_t BytesRcv) {
if (BytesRcv == 0) {
beammp_debug("(TCP) Connection closing...");
trace("(TCP) Connection closing...");
if (c.GetStatus() > -1)
c.SetStatus(-1);
return false;
} else if (BytesRcv < 0) {
beammp_debug("(TCP) recv() failed: " + GetPlatformAgnosticErrorString());
debug("(TCP) recv() failed: " + GetPlatformAgnosticErrorString());
if (c.GetStatus() > -1)
c.SetStatus(-1);
beammp_info(("Closing socket in CheckBytes, BytesRcv < 0"));
CloseSocketProper(c.GetTCPSock());
return false;
}
@ -402,63 +401,40 @@ std::string TNetwork::TCPRcv(TClient& c) {
do {
Temp = recv(c.GetTCPSock(), &Data[BytesRcv], 4 - BytesRcv, 0);
if (!CheckBytes(c, Temp)) {
#ifdef DEBUG
beammp_error(std::string(__func__) + (": failed on CheckBytes in while(BytesRcv < 4)"));
#endif // DEBUG
return "";
}
BytesRcv += Temp;
} while (size_t(BytesRcv) < sizeof(Header));
memcpy(&Header, &Data[0], sizeof(Header));
#ifdef DEBUG
//debug(std::string(__func__) + (": expecting ") + std::to_string(Header) + (" bytes."));
#endif // DEBUG
if (!CheckBytes(c, BytesRcv)) {
#ifdef DEBUG
beammp_error(std::string(__func__) + (": failed on CheckBytes"));
#endif // DEBUG
return "";
}
if (Header < 100 * MB) {
Data.resize(Header);
} else {
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.");
warn("Client " + c.GetName() + " (" + std::to_string(c.GetID()) + ") sent header of >100MB - assuming malicious intent and disconnecting the client.");
return "";
}
BytesRcv = 0;
do {
Temp = recv(c.GetTCPSock(), &Data[BytesRcv], Header - BytesRcv, 0);
if (!CheckBytes(c, Temp)) {
#ifdef DEBUG
beammp_error(std::string(__func__) + (": failed on CheckBytes in while(BytesRcv < Header)"));
#endif // DEBUG
return "";
}
#ifdef DEBUG
//debug(std::string(__func__) + (": Temp: ") + std::to_string(Temp) + (", BytesRcv: ") + std::to_string(BytesRcv));
#endif // DEBUG
BytesRcv += Temp;
} while (BytesRcv < Header);
#ifdef DEBUG
//debug(std::string(__func__) + (": finished recv with Temp: ") + std::to_string(Temp) + (", BytesRcv: ") + std::to_string(BytesRcv));
#endif // DEBUG
std::string Ret(Data.data(), Header);
if (Ret.substr(0, 4) == "ABG:") {
Ret = DeComp(Ret.substr(4));
}
#ifdef DEBUG
//debug("Parsing from " + c->GetName() + " -> " +std::to_string(Ret.size()));
#endif
return Ret;
}
void TNetwork::ClientKick(TClient& c, const std::string& R) {
beammp_info("Client kicked: " + R);
info("Client kicked: " + R);
if (!TCPSend(c, "E" + R)) {
// TODO handle
}
@ -474,7 +450,7 @@ void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
while (!c.expired()) {
auto Client = c.lock();
if (Client->GetStatus() < 0) {
beammp_debug("client status < 0, breaking client loop");
debug("client status < 0, breaking client loop");
break;
}
if (!Client->IsSyncing() && Client->IsSynced() && Client->MissedPacketQueueSize() != 0) {
@ -524,13 +500,13 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
break;
auto Client = c.lock();
if (Client->GetStatus() < 0) {
beammp_debug("client status < 0, breaking client loop");
debug("client status < 0, breaking client loop");
break;
}
auto res = TCPRcv(*Client);
if (res == "") {
beammp_debug("TCPRcv error, break client loop");
debug("TCPRcv error, break client loop");
break;
}
TServer::GlobalParser(c, res, mPPSMonitor, *this);
@ -542,7 +518,7 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
auto Client = c.lock();
OnDisconnect(c, Client->GetStatus() == -2);
} else {
beammp_warn("client expired in TCPClient, should never happen");
warn("client expired in TCPClient, should never happen");
}
}
@ -562,10 +538,10 @@ void TNetwork::UpdatePlayer(TClient& Client) {
}
void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked) {
beammp_assert(!ClientPtr.expired());
Assert(!ClientPtr.expired());
auto LockedClientPtr = ClientPtr.lock();
TClient& c = *LockedClientPtr;
beammp_info(c.GetName() + (" Connection Terminated"));
info(c.GetName() + (" Connection Terminated"));
std::string Packet;
TClient::TSetOfVehicleData VehicleData;
{ // Vehicle Data Lock Scope
@ -582,8 +558,7 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked
Packet = ("L") + c.GetName() + (" left the server!");
SendToAll(&c, Packet, false, true);
Packet.clear();
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerDisconnect", c.GetID());
beammp_ignore(Futures);
TriggerLuaEvent(("onPlayerDisconnect"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID() } }), false);
if (c.GetTCPSock())
CloseSocketProper(c.GetTCPSock());
if (c.GetDownSock())
@ -612,18 +587,18 @@ int TNetwork::OpenID() {
}
void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
beammp_assert(!c.expired());
beammp_info("Client connected");
Assert(!c.expired());
info("Client connected");
auto LockedClient = c.lock();
LockedClient->SetID(OpenID());
beammp_info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName());
beammp_ignore(LuaAPI::MP::Engine->TriggerEvent("onPlayerConnecting", LockedClient->GetID()));
info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName());
TriggerLuaEvent("onPlayerConnecting", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
SyncResources(*LockedClient);
if (LockedClient->GetStatus() < 0)
return;
(void)Respond(*LockedClient, "M" + Application::Settings.MapName, true); //Send the Map on connect
beammp_info(LockedClient->GetName() + " : Connected");
beammp_ignore(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", LockedClient->GetID()));
info(LockedClient->GetName() + " : Connected");
TriggerLuaEvent("onPlayerJoining", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
}
void TNetwork::SyncResources(TClient& c) {
@ -642,7 +617,7 @@ void TNetwork::SyncResources(TClient& c) {
}
#ifndef DEBUG
} catch (std::exception& e) {
beammp_error("Exception! : " + std::string(e.what()));
error("Exception! : " + std::string(e.what()));
c.SetStatus(-1);
}
#endif
@ -660,7 +635,7 @@ void TNetwork::Parse(TClient& c, const std::string& Packet) {
return;
case 'S':
if (SubCode == 'R') {
beammp_debug("Sending Mod Info");
debug("Sending Mod Info");
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
if (ToSend.empty())
ToSend = "-";
@ -675,13 +650,13 @@ void TNetwork::Parse(TClient& c, const std::string& Packet) {
}
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
beammp_info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
if (!fs::path(UnsafeName).has_filename()) {
if (!TCPSend(c, "CO")) {
// TODO: handle
}
beammp_warn("File " + UnsafeName + " is not a file!");
warn("File " + UnsafeName + " is not a file!");
return;
}
auto FileName = fs::path(UnsafeName).filename().string();
@ -691,7 +666,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
if (!TCPSend(c, "CO")) {
// TODO: handle
}
beammp_warn("File " + UnsafeName + " could not be accessed!");
warn("File " + UnsafeName + " could not be accessed!");
return;
}
@ -707,7 +682,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
}
if (c.GetDownSock() < 1) {
beammp_error("Client doesn't have a download socket!");
error("Client doesn't have a download socket!");
if (c.GetStatus() > -1)
c.SetStatus(-1);
return;
@ -744,7 +719,7 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
TCPSock = c.GetDownSock();
else
TCPSock = c.GetTCPSock();
beammp_info("Split load Socket " + std::to_string(TCPSock));
info("Split load Socket " + std::to_string(TCPSock));
while (c.GetStatus() > -1 && Sent < Size) {
size_t Diff = Size - Sent;
if (Diff > Split) {
@ -776,7 +751,7 @@ bool TNetwork::TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size) {
do {
intmax_t Temp = send(socket, &Data[Sent], int(Size - Sent), 0);
if (Temp < 1) {
beammp_info("Socket Closed! " + std::to_string(socket));
info("Socket Closed! " + std::to_string(socket));
CloseSocketProper(socket);
return false;
}
@ -822,7 +797,7 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
// ignore error
(void)SendToAll(LockedClient.get(), ("JWelcome ") + LockedClient->GetName() + "!", false, true);
beammp_ignore(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoin", LockedClient->GetID()));
TriggerLuaEvent(("onPlayerJoin"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
LockedClient->SetIsSyncing(true);
bool Return = false;
bool res = true;
@ -858,13 +833,13 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
return res;
}
LockedClient->SetIsSynced(true);
beammp_info(LockedClient->GetName() + (" is now synced!"));
info(LockedClient->GetName() + (" is now synced!"));
return true;
}
void TNetwork::SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel) {
if (!Self)
beammp_assert(c);
Assert(c);
char C = Data.at(0);
bool ret = true;
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
@ -928,12 +903,12 @@ bool TNetwork::UDPSend(TClient& Client, std::string Data) const {
sendOk = sendto(mUDPSock, Data.c_str(), len, 0, (sockaddr*)&Addr, int(AddrSize));
if (sendOk == -1) {
beammp_debug("(UDP) sendto() failed: " + GetPlatformAgnosticErrorString());
debug("(UDP) sendto() failed: " + GetPlatformAgnosticErrorString());
if (Client.GetStatus() > -1)
Client.SetStatus(-1);
return false;
} else if (sendOk == 0) {
beammp_debug(("(UDP) sendto() returned 0"));
debug(("(UDP) sendto() returned 0"));
if (Client.GetStatus() > -1)
Client.SetStatus(-1);
return false;
@ -951,7 +926,7 @@ std::string TNetwork::UDPRcvFromClient(sockaddr_in& client) const {
#endif // WIN32
if (Rcv == -1) {
beammp_error("(UDP) Error receiving from client! recvfrom() failed: " + GetPlatformAgnosticErrorString());
error("(UDP) Error receiving from client! recvfrom() failed: " + GetPlatformAgnosticErrorString());
return "";
}
return std::string(Ret.begin(), Ret.begin() + Rcv);

View File

@ -44,7 +44,7 @@ void TPPSMonitor::operator()() {
V += c->GetCarCount();
}
// kick on "no ping"
if (c->SecondsSinceLastPing() > (5 * 60)) {
if (c->SecondsSinceLastPing() > (20 * 60)) {
beammp_debug("client " + std::string("(") + std::to_string(c->GetID()) + ")" + c->GetName() + " timing out: " + std::to_string(c->SecondsSinceLastPing()) + ", pps: " + Application::PPS());
TimedOutClients.push_back(c);
}
@ -52,7 +52,7 @@ void TPPSMonitor::operator()() {
return true;
});
for (auto& ClientToKick : TimedOutClients) {
Network().ClientKick(*ClientToKick, "Timeout (no ping for >5 min)");
Network().ClientKick(*ClientToKick, "Timeout (no ping for way too long)");
}
TimedOutClients.clear();
if (C == 0 || mInternalPPS == 0) {

125
src/TSentry.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "TSentry.h"
#include "Common.h"
#include <sentry.h>
#include <sstream>
// compile-time length of a string/array
template <size_t N>
constexpr size_t ConstexprLength(char const (&)[N]) {
return N - 1;
}
TSentry::TSentry() {
if constexpr (ConstexprLength(SECRET_SENTRY_URL) == 0) {
mValid = false;
} else {
mValid = true;
sentry_options_t* options = sentry_options_new();
sentry_options_set_dsn(options, SECRET_SENTRY_URL);
auto ReleaseString = "BeamMP-Server@" + Application::ServerVersionString();
sentry_options_set_symbolize_stacktraces(options, true);
sentry_options_set_release(options, ReleaseString.c_str());
sentry_options_set_max_breadcrumbs(options, 10);
sentry_init(options);
}
}
TSentry::~TSentry() {
if (mValid) {
sentry_close();
}
}
void TSentry::PrintWelcome() {
if (mValid) {
info("Sentry started");
} else {
info("Sentry disabled in unofficial build");
}
}
void TSentry::SetupUser() {
if (!mValid) {
return;
}
sentry_value_t user = sentry_value_new_object();
if (Application::Settings.Key.size() == 36) {
sentry_value_set_by_key(user, "id", sentry_value_new_string(Application::Settings.Key.c_str()));
} else {
sentry_value_set_by_key(user, "id", sentry_value_new_string("unauthenticated"));
}
sentry_set_user(user);
}
void TSentry::Log(SentryLevel level, const std::string& logger, const std::string& text) {
if (!mValid) {
return;
}
SetContext("threads", { { "thread-name", ThreadName(true) } });
auto Msg = sentry_value_new_message_event(sentry_level_t(level), logger.c_str(), text.c_str());
sentry_capture_event(Msg);
sentry_remove_transaction();
}
void TSentry::LogError(const std::string& text, const std::string& file, const std::string& line) {
if (!mValid) {
return;
}
SetTransaction(file + ":" + line);
Log(SentryLevel::Error, "default", file + ": " + text);
}
void TSentry::SetContext(const std::string& context_name, const std::unordered_map<std::string, std::string>& map) {
if (!mValid) {
return;
}
auto ctx = sentry_value_new_object();
for (const auto& pair : map) {
std::string key = pair.first;
if (key == "type") {
// `type` is reserved
key = "_type";
}
sentry_value_set_by_key(ctx, key.c_str(), sentry_value_new_string(pair.second.c_str()));
}
sentry_set_context(context_name.c_str(), ctx);
}
void TSentry::LogException(const std::exception& e, const std::string& file, const std::string& line) {
if (!mValid) {
return;
}
SetTransaction(file + ":" + line);
Log(SentryLevel::Fatal, "exceptions", std::string(e.what()) + " @ " + file + ":" + line);
}
void TSentry::LogAssert(const std::string& condition_string, const std::string& file, const std::string& line, const std::string& function) {
if (!mValid) {
return;
}
SetTransaction(file + ":" + line + ":" + function);
std::stringstream ss;
ss << "\"" << condition_string << "\" failed @ " << file << ":" << line;
Log(SentryLevel::Fatal, "asserts", ss.str());
}
void TSentry::AddErrorBreadcrumb(const std::string& msg, const std::string& file, const std::string& line) {
if (!mValid) {
return;
}
auto crumb = sentry_value_new_breadcrumb("default", (msg + " @ " + file + ":" + line).c_str());
sentry_value_set_by_key(crumb, "level", sentry_value_new_string("error"));
sentry_add_breadcrumb(crumb);
}
void TSentry::SetTransaction(const std::string& id) {
if (!mValid) {
return;
}
sentry_set_transaction(id.c_str());
}
std::unique_lock<std::mutex> TSentry::CreateExclusiveContext() {
return std::unique_lock<std::mutex>(mMutex);
}

View File

@ -9,7 +9,7 @@
#include "LuaAPI.h"
#undef GetObject //Fixes Windows
#undef GetObject // Fixes Windows
#include "Json.h"
@ -92,9 +92,8 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
}
switch (Code) {
case 'H': // initial connection
#ifdef DEBUG
trace(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
beammp_debug(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
#endif
if (!Network.SyncClient(Client)) {
// TODO handle
}
@ -116,19 +115,18 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
ParseVehicle(*LockedClient, Packet, Network);
return;
case 'J':
#ifdef DEBUG
trace(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
beammp_debug(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
Network.SendToAll(LockedClient.get(), Packet, false, true);
return;
case 'C': {
#ifdef DEBUG
trace(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
beammp_debug(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos)
break;
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 1));
TLuaEngine::WaitForAll(Futures);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1)); // FIXME: this needs to be adjusted once lua is merged
if (std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Elem) {
return !Elem->Error
@ -141,14 +139,14 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
return;
}
case 'E':
#ifdef DEBUG
trace(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
beammp_debug(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
HandleEvent(*LockedClient, Packet);
return;
case 'N':
beammp_debug("got 'N' packet (" + std::to_string(Packet.size()) + ")");
trace("got 'N' packet (" + std::to_string(Packet.size()) + ")");
Network.SendToAll(LockedClient.get(), Packet, false, true);
return;
default:
return;
}
@ -208,9 +206,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
std::string Data = Packet.substr(3), pid, vid;
switch (Code) { //Spawned Destroyed Switched/Moved NotFound Reset
case 's':
#ifdef DEBUG
beammp_debug(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
if (Data.at(0) == '0') {
int CarID = c.GetOpenCarID();
beammp_debug(c.GetName() + (" created a car with ID ") + std::to_string(CarID));
@ -240,9 +236,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
return;
case 'c':
#ifdef DEBUG
beammp_debug(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
pid = Data.substr(0, Data.find('-'));
vid = Data.substr(Data.find('-') + 1, Data.find(':', 1) - Data.find('-') - 1);
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
@ -276,9 +270,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
return;
case 'd':
#ifdef DEBUG
beammp_debug(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
pid = Data.substr(0, Data.find('-'));
vid = Data.substr(Data.find('-') + 1);
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
@ -296,9 +288,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
return;
case 'r':
#ifdef DEBUG
beammp_debug(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Pos = int(Data.find('-'));
pid = Data.substr(0, Pos++);
vid = Data.substr(Pos, Data.find(':') - Pos);
@ -315,15 +305,11 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
return;
case 't':
#ifdef DEBUG
beammp_debug(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Network.SendToAll(&c, Packet, false, true);
return;
default:
#ifdef DEBUG
beammp_warn(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
#endif // DEBUG
trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
return;
}
}
@ -338,13 +324,22 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
std::string VD = c.GetCarData(VID);
if (VD.empty()) {
beammp_error("Tried to apply change to vehicle that does not exist");
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("vehicle-change",
{ { "packet", Packet },
{ "vehicle-id", std::to_string(VID) },
{ "client-car-count", std::to_string(c.GetCarCount()) } });
Sentry.LogError("attempt to apply change to nonexistent vehicle", _file_basename, _line);
return;
}
std::string Header = VD.substr(0, VD.find('{'));
FoundPos = VD.find('{');
if (FoundPos == std::string::npos) {
beammp_error("Malformed packet received, no '{' found");
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("vehicle-change-packet",
{ { "packet", VD } });
Sentry.LogError("malformed packet", _file_basename, _line);
return;
}
VD = VD.substr(FoundPos);

View File

@ -1,18 +1,14 @@
#include "VehicleData.h"
#include <utility>
#include "Common.h"
#include <utility>
TVehicleData::TVehicleData(int ID, std::string Data)
: mID(ID)
, mData(std::move(Data)) {
#ifdef DEBUG
beammp_debug("vehicle " + std::to_string(mID) + " constructed");
#endif
trace("vehicle " + std::to_string(mID) + " constructed");
}
TVehicleData::~TVehicleData() {
#ifdef DEBUG
beammp_debug("vehicle " + std::to_string(mID) + " destroyed");
#endif
trace("vehicle " + std::to_string(mID) + " destroyed");
}

View File

@ -1,4 +1,7 @@
#include "TSentry.h"
#include "Common.h"
#include "CustomAssert.h"
#include "Http.h"
#include "LuaAPI.h"
#include "TConfig.h"
@ -8,6 +11,7 @@
#include "TPPSMonitor.h"
#include "TResourceManager.h"
#include "TServer.h"
#include <thread>
#ifdef __unix
@ -33,18 +37,21 @@ void UnixSignalHandler(int sig) {
}
#endif // __unix
int main(int argc, char** argv) {
// this is provided by the build system, leave empty for source builds
// global, yes, this is ugly, no, it cant be done another way
TSentry Sentry {};
#include <iostream>
int main(int argc, char** argv) try {
#ifdef __unix
#if DEBUG
beammp_info("registering handlers for SIGINT, SIGTERM, SIGPIPE");
#endif // DEBUG
trace("registering handlers for SIGINT, SIGTERM, SIGPIPE");
signal(SIGPIPE, UnixSignalHandler);
signal(SIGTERM, UnixSignalHandler);
#ifndef DEBUG
signal(SIGINT, UnixSignalHandler);
#endif // DEBUG
#endif // __unix
setlocale(LC_ALL, "C");
bool Shutdown = false;
@ -66,6 +73,12 @@ int main(int argc, char** argv) {
}
RegisterThread("Main");
trace("Running in debug mode on a debug build");
Sentry.SetupUser();
Sentry.PrintWelcome();
Application::CheckForUpdates();
TResourceManager ResourceManager;
TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server);
@ -78,4 +91,7 @@ int main(int argc, char** argv) {
while (!Shutdown) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
} catch (const std::exception& e) {
error(e.what());
Sentry.LogException(e, _file_basename, _line);
}