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: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'true' submodules: 'recursive'
- name: Install Dependencies - name: Install Dependencies
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: | run: |
echo ${#beammp_sentry_url}
sudo apt-get update 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 add-apt-repository ppa:mhier/libboost-latest
sudo apt-get install -y libboost1.70-dev libboost1.70 sudo apt-get install -y libboost1.70-dev libboost1.70
@ -27,7 +30,9 @@ jobs:
- name: Configure CMake - name: Configure CMake
shell: bash shell: bash
working-directory: ${{github.workspace}}/build-linux 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 - name: Build
working-directory: ${{github.workspace}}/build-linux working-directory: ${{github.workspace}}/build-linux

View File

@ -12,13 +12,13 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'true' submodules: 'recursive'
- name: Restore artifacts, or run vcpkg, build and cache artifacts - name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@main uses: lukka/run-vcpkg@main
id: runvcpkg id: runvcpkg
with: 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' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb' vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb'
vcpkgTriplet: 'x64-windows-static' vcpkgTriplet: 'x64-windows-static'
@ -29,7 +29,9 @@ jobs:
- name: Configure CMake - name: Configure CMake
shell: bash shell: bash
working-directory: ${{github.workspace}}/build-windows 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 - name: Build
working-directory: ${{github.workspace}}/build-windows working-directory: ${{github.workspace}}/build-windows

View File

@ -37,12 +37,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'true' submodules: 'recursive'
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get update 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 add-apt-repository ppa:mhier/libboost-latest
sudo apt-get install -y libboost1.70-dev libboost1.70 sudo apt-get install -y libboost1.70-dev libboost1.70
@ -52,14 +52,15 @@ jobs:
- name: Configure CMake - name: Configure CMake
shell: bash shell: bash
working-directory: ${{github.workspace}}/build-linux 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 - name: Build
working-directory: ${{github.workspace}}/build-linux working-directory: ${{github.workspace}}/build-linux
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
@ -78,13 +79,13 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'true' submodules: 'recursive'
- name: Restore artifacts, or run vcpkg, build and cache artifacts - name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@main uses: lukka/run-vcpkg@main
id: runvcpkg id: runvcpkg
with: 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' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb' vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb'
vcpkgTriplet: 'x64-windows-static' vcpkgTriplet: 'x64-windows-static'
@ -95,7 +96,9 @@ jobs:
- name: Configure CMake - name: Configure CMake
shell: bash shell: bash
working-directory: ${{github.workspace}}/build-windows 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 - name: Build
working-directory: ${{github.workspace}}/build-windows working-directory: ${{github.workspace}}/build-windows

3
.gitignore vendored
View File

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

15
.gitmodules vendored
View File

@ -1,18 +1,19 @@
[submodule "include/commandline"] [submodule "include/commandline"]
path = deps/commandline path = include/commandline
url = https://github.com/lionkor/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"] [submodule "asio"]
path = deps/asio path = asio
url = https://github.com/chriskohlhoff/asio url = https://github.com/chriskohlhoff/asio
[submodule "rapidjson"] [submodule "rapidjson"]
path = deps/rapidjson path = rapidjson
url = https://github.com/Tencent/rapidjson url = https://github.com/Tencent/rapidjson
[submodule "include/toml11"] [submodule "include/toml11"]
path = include/toml11
url = https://github.com/ToruNiina/toml11
[submodule "include/sentry-native"]
path = include/sentry-native
path = deps/toml11 path = deps/toml11
url = https://github.com/ToruNiina/toml11 url = https://github.com/ToruNiina/toml11
[submodule "deps/sol2"] [submodule "deps/sol2"]
path = 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) 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/asio/asio/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/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") include_directories("${PROJECT_SOURCE_DIR}/deps")
if (WIN32) 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.") message(STATUS "MSVC -> forcing use of statically-linked runtime.")
STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) 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 #-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}) set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include) include_directories(${VcpkgRoot}/include)
@ -22,17 +48,33 @@ elseif (UNIX)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s -fno-builtin") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s -fno-builtin")
if (SANITIZE) if (SANITIZE)
message(STATUS "sanitize is ON") 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 (SANITIZE)
endif () 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 # this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps) add_subdirectory(deps)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
message(STATUS "Looking for Boost")
find_package(Boost REQUIRED COMPONENTS system thread) find_package(Boost REQUIRED COMPONENTS system thread)
add_executable(BeamMP-Server add_executable(BeamMP-Server
@ -49,24 +91,57 @@ add_executable(BeamMP-Server
include/TResourceManager.h src/TResourceManager.cpp include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp include/Http.h src/Http.cpp
include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.cpp include/TNetwork.h src/TNetwork.cpp
include/LuaAPI.h src/LuaAPI.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) 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) 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) find_package(OpenSSL REQUIRED)
target_link_libraries(BeamMP-Server sol2::sol2 ${LUA_LIBRARIES}) target_link_libraries(BeamMP-Server sol2::sol2 ${LUA_LIBRARIES})
message(STATUS "CURL IS ${CURL_LIBRARIES}")
if (UNIX) 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) elseif (WIN32)
include(FindLua) include(FindLua)
message(STATUS "Looking for libz")
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
message(STATUS "Looking for RapidJSON")
find_package(RapidJSON CONFIG REQUIRED) find_package(RapidJSON CONFIG REQUIRED)
target_include_directories(BeamMP-Server PRIVATE ${RAPIDJSON_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) 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 () 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. These values are guesstimated and are subject to change with each release.
* RAM: 50+ MiB usable (not counting OS overhead) * RAM: 50+ MiB usable (not counting OS overhead)
* CPU: Any Hz, preferably multicore * CPU: >1GHz, preferably multicore
* OS: Windows, Linux (theoretically any POSIX) * OS: Windows, Linux (theoretically any POSIX)
* GPU: None * GPU: None
* HDD: 10 MiB + Mods/Plugins * 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` - `make`
- `cmake` - `cmake`
- `g++` - `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). 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` - `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` - `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'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: 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 ### 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 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`. 2. Clone the repository in a location of your choice with `git clone --recurse-submodules https://github.com/BeamMP/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). 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. Run `cmake .` (with `.`) 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 `make` 5. Run `cmake .` (with `.`)
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. 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 &`*.* *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; class TServer;
#ifdef WIN32
// for socklen_t
#include <WS2tcpip.h>
#endif // WIN32
struct TConnection final { struct TConnection final {
SOCKET Socket; SOCKET Socket;
struct sockaddr SockAddr; struct sockaddr SockAddr;

View File

@ -1,5 +1,9 @@
#pragma once #pragma once
#include "TSentry.h"
extern TSentry Sentry;
#include <array>
#include <atomic> #include <atomic>
#include <cstring> #include <cstring>
#include <deque> #include <deque>
@ -17,6 +21,7 @@ struct Version {
uint8_t minor; uint8_t minor;
uint8_t patch; uint8_t patch;
Version(uint8_t major, 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(); std::string AsString();
}; };
@ -67,13 +72,18 @@ public:
static const Version& ServerVersion() { return mVersion; } static const Version& ServerVersion() { return mVersion; }
static std::string ClientVersionString() { return "2.0"; } static std::string ClientVersionString() { return "2.0"; }
static std::string PPS() { return mPPS; } 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 inline TSettings Settings {};
static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; } static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; }
static std::string GetBackendHostname() { return "backend.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 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: private:
static inline std::string mPPS; static inline std::string mPPS;
@ -81,11 +91,11 @@ 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 { 2, 3, 0 }; static inline Version mVersion { 2, 4, 0 };
}; };
std::string ThreadName(); std::string ThreadName(bool DebugModeOverride = false);
void RegisterThread(const std::string str); void RegisterThread(const std::string& str);
#define RegisterThreadAuto() RegisterThread(__func__) #define RegisterThreadAuto() RegisterThread(__func__)
#define KB 1024 #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_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_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 error(x) \
#define beammp_lua_error(x) Application::Console().Write(_this_location + std::string("[LUA 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 luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \ #define beammp_debug(x) \
do { \ do { \
@ -136,6 +149,19 @@ void RegisterThread(const std::string str);
// for those times when you just need to ignore something :^) // for those times when you just need to ignore something :^)
// explicity disables a [[nodiscard]] warning // explicity disables a [[nodiscard]] warning
#define beammp_ignore(x) (void)x #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 #define Biggest 30000
std::string Comp(std::string Data); std::string Comp(std::string Data);

View File

@ -13,9 +13,9 @@ using DWORD = unsigned long;
using PDWORD = unsigned long*; using PDWORD = unsigned long*;
using LPDWORD = unsigned long*; using LPDWORD = unsigned long*;
char _getch(); char _getch();
inline void CloseSocketProper(int socket) { inline void CloseSocketProper(int TheSocket) {
shutdown(socket, SHUT_RDWR); shutdown(TheSocket, SHUT_RDWR);
close(socket); close(TheSocket);
} }
#endif // unix #endif // unix
@ -24,10 +24,10 @@ inline void CloseSocketProper(int socket) {
#ifdef WIN32 #ifdef WIN32
#include <conio.h> #include <conio.h>
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> inline void CloseSocketProper(SOCKET TheSocket) {
inline void CloseSocketProper(SOCKET socket) { shutdown(TheSocket, 2); // 2 == SD_BOTH
shutdown(socket, SD_BOTH); closesocket(TheSocket);
closesocket(socket);
} }
#endif // WIN32 #endif // WIN32
@ -35,4 +35,4 @@ inline void CloseSocketProper(SOCKET socket) {
#if !defined(WIN32) && !defined(__unix) #if !defined(WIN32) && !defined(__unix)
#error "OS not supported" #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_BOLD = "\u001b[1m";
static const char* const ANSI_UNDERLINE = "\u001b[4m"; static const char* const ANSI_UNDERLINE = "\u001b[4m";
#if DEBUG #ifdef DEBUG
#include <iostream> #include <iostream>
inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const char* function, [[maybe_unused]] unsigned line, 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) { [[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 #else
// In release build, these macros turn into NOPs. The compiler will optimize these out. // In release build, these macros turn into NOPs. The compiler will optimize these out.
#define beammp_assert(x) \ #define beammp_assert(x) \
do { \ do { \
bool result = (cond); \
if (!result) { \
Sentry.LogAssert(#cond, _file_basename, _line, __func__); \
} \
} while (false) } while (false)
#define beammp_assert_not_reachable() \ #define beammp_assert_not_reachable() \
do { \ do { \
Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \
} while (false) } while (false)
#endif // DEBUG #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 { namespace Http {
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr); 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); 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() { void TClient::UpdatePingTime() {
mLastPingTime = std::chrono::high_resolution_clock::now(); mLastPingTime = std::chrono::high_resolution_clock::now();
//debug(GetName() + ": " + std::string("ping time updated!: ") + ((SecondsSinceLastPing() == 0) ? "OK" : "ERR"));
} }
int TClient::SecondsSinceLastPing() { int TClient::SecondsSinceLastPing() {
auto seconds = std::chrono::duration_cast<std::chrono::seconds>( auto seconds = std::chrono::duration_cast<std::chrono::seconds>(

View File

@ -2,11 +2,17 @@
#include "TConsole.h" #include "TConsole.h"
#include <array> #include <array>
#include <charconv>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <regex>
#include <sstream>
#include <thread> #include <thread>
#include <zlib.h> #include <zlib.h>
#include "CustomAssert.h"
#include "Http.h"
std::unique_ptr<TConsole> Application::mConsole = std::make_unique<TConsole>(); std::unique_ptr<TConsole> Application::mConsole = std::make_unique<TConsole>();
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) { void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
@ -17,7 +23,7 @@ void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
} }
void Application::GracefullyShutdown() { 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); std::unique_lock Lock(mShutdownHandlersMutex);
for (auto& Handler : mShutdownHandlers) { for (auto& Handler : mShutdownHandlers) {
Handler(); Handler();
@ -28,6 +34,53 @@ std::string Application::ServerVersionString() {
return mVersion.AsString(); 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::string Comp(std::string Data) {
std::array<char, Biggest> C {}; std::array<char, Biggest> C {};
// obsolete // obsolete
@ -74,10 +127,12 @@ std::string DeComp(std::string Compressed) {
// thread name stuff // 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() { std::string ThreadName(bool DebugModeOverride) {
if (Application::Settings.DebugModeEnabled) { auto Lock = std::unique_lock(ThreadNameMapMutex);
if (DebugModeOverride || Application::Settings.DebugModeEnabled) {
auto id = std::this_thread::get_id(); auto id = std::this_thread::get_id();
if (threadNameMap.find(id) != threadNameMap.end()) { if (threadNameMap.find(id) != threadNameMap.end()) {
// found // found
@ -87,7 +142,8 @@ std::string ThreadName() {
return ""; 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; threadNameMap[std::this_thread::get_id()] = str;
} }
@ -96,12 +152,27 @@ Version::Version(uint8_t major, uint8_t minor, uint8_t patch)
, minor(minor) , minor(minor)
, patch(patch) { } , patch(patch) { }
Version::Version(const std::array<uint8_t, 3>& v)
: Version(v[0], v[1], v[2]) {
}
std::string Version::AsString() { std::string Version::AsString() {
std::stringstream ss {}; std::stringstream ss {};
ss << int(major) << "." << int(minor) << "." << int(patch); ss << int(major) << "." << int(minor) << "." << int(patch);
return ss.str(); 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() { std::string GetPlatformAgnosticErrorString() {
#ifdef WIN32 #ifdef WIN32
// This will provide us with the error code and an error message, all in one. // 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); return std::strerror(errno);
#endif #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 "Http.h"
#include "Common.h" #include "Common.h"
#undef beammp_error #undef error
#include <boost/asio/connect.hpp> #include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp> #include <boost/beast.hpp>
#include <boost/beast/ssl.hpp> #include <boost/beast/ssl.hpp>
#include <map>
namespace beast = boost::beast; // from <boost/beast.hpp> namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.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> namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.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) { 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 {
// 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) {
try { try {
net::io_context io; 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"); //debug("IPv6 connect failed, trying IPv4");
bool ok = try_connect_with_protocol(tcp::v4()); bool ok = try_connect_with_protocol(tcp::v4());
if (!ok) { 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"; return "-1";
} }
//} //}
stream.handshake(ssl::stream_base::client); 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); req.set(http::field::host, host);
if (!body.empty()) { if (!body.empty()) {
req.set(http::field::content_type, ContentType); // "application/json" req.set(http::field::content_type, ContentType); // "application/json"
// "application/x-www-form-urlencoded" // "application/x-www-form-urlencoded"
req.set(http::field::content_length, std::to_string(body.size())); req.set(http::field::content_length, std::to_string(body.size()));
req.body() = body; req.body() = body;
// info("body is " + body + " (" + req.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); 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; std::stringstream oss;
oss << req; 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); 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) { if (status) {
*status = response.base().result_int(); *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) { } catch (const std::exception& e) {
Application::Console().Write(__func__ + std::string(": ") + e.what()); 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)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue; 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; Last = Body;
LastNormalUpdateTime = Now; LastNormalUpdateTime = Now;
@ -35,26 +35,48 @@ void THeartbeatThread::operator()() {
Body += "&pps=" + Application::PPS(); 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") { auto Lock = Sentry.CreateExclusiveContext();
//Backend system refused server startup! 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)); std::this_thread::sleep_for(std::chrono::milliseconds(500));
T = Http::POST(Application::GetBackendHostname(), 443, "/heartbeat", {}, Body, "application/x-www-form-urlencoded"); T = Http::POST(Application::GetBackup1Hostname(), 443, Target, {}, Body, "application/x-www-form-urlencoded", &ResponseCode);
// TODO backup2 + HTTP flag (no TSL) if (T.substr(0, 2) != "20" || ResponseCode != 200) {
if (T.substr(0, 2) != "20") { SentryReportError(Application::GetBackup1Hostname() + Target, ResponseCode);
beammp_warn("Backend system refused server! Server might not show in the public list"); std::this_thread::sleep_for(std::chrono::milliseconds(500));
beammp_debug("server returned \"" + T + "\""); T = Http::POST(Application::GetBackup2Hostname(), 443, Target, {}, Body, "application/x-www-form-urlencoded", &ResponseCode);
isAuth = false; 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 (!isAuth) {
if (T == "2000") { if (T == "2000") {
beammp_info(("Authenticated!")); info(("Authenticated!"));
isAuth = true; isAuth = true;
} else if (T == "200") { } else if (T == "200") {
beammp_info(("Resumed authenticated session!")); info(("Resumed authenticated session!"));
isAuth = true; isAuth = true;
} }
} }
@ -62,6 +84,7 @@ void THeartbeatThread::operator()() {
//SocketIO::Get().SetAuthenticated(isAuth); //SocketIO::Get().SetAuthenticated(isAuth);
} }
} }
std::string THeartbeatThread::GenerateCall() { std::string THeartbeatThread::GenerateCall() {
std::stringstream Ret; std::stringstream Ret;
@ -86,10 +109,10 @@ THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& S
, mServer(Server) { , mServer(Server) {
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
if (mThread.joinable()) { if (mThread.joinable()) {
beammp_debug("shutting down Heartbeat"); debug("shutting down Heartbeat");
mShutdown = true; mShutdown = true;
mThread.join(); mThread.join();
beammp_debug("shut down Heartbeat"); debug("shut down Heartbeat");
} }
}); });
Start(); Start();

View File

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

View File

@ -44,7 +44,7 @@ void TPPSMonitor::operator()() {
V += c->GetCarCount(); V += c->GetCarCount();
} }
// kick on "no ping" // 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()); 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); TimedOutClients.push_back(c);
} }
@ -52,7 +52,7 @@ void TPPSMonitor::operator()() {
return true; return true;
}); });
for (auto& ClientToKick : TimedOutClients) { 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(); TimedOutClients.clear();
if (C == 0 || mInternalPPS == 0) { 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" #include "LuaAPI.h"
#undef GetObject //Fixes Windows #undef GetObject // Fixes Windows
#include "Json.h" #include "Json.h"
@ -92,9 +92,8 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
} }
switch (Code) { switch (Code) {
case 'H': // initial connection 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()) + ")"); beammp_debug(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
#endif
if (!Network.SyncClient(Client)) { if (!Network.SyncClient(Client)) {
// TODO handle // TODO handle
} }
@ -116,19 +115,18 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
ParseVehicle(*LockedClient, Packet, Network); ParseVehicle(*LockedClient, Packet, Network);
return; return;
case 'J': 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()) + (")")); beammp_debug(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
Network.SendToAll(LockedClient.get(), Packet, false, true); Network.SendToAll(LockedClient.get(), Packet, false, true);
return; return;
case 'C': { 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()) + (")")); beammp_debug(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos) if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos)
break; break;
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 1)); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 1));
TLuaEngine::WaitForAll(Futures); 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(), if (std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Elem) { [](const std::shared_ptr<TLuaResult>& Elem) {
return !Elem->Error return !Elem->Error
@ -141,14 +139,14 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
return; return;
} }
case 'E': 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()) + (")")); beammp_debug(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
HandleEvent(*LockedClient, Packet); HandleEvent(*LockedClient, Packet);
return; return;
case 'N': 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); Network.SendToAll(LockedClient.get(), Packet, false, true);
return;
default: default:
return; return;
} }
@ -208,9 +206,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
std::string Data = Packet.substr(3), pid, vid; std::string Data = Packet.substr(3), pid, vid;
switch (Code) { //Spawned Destroyed Switched/Moved NotFound Reset switch (Code) { //Spawned Destroyed Switched/Moved NotFound Reset
case 's': case 's':
#ifdef DEBUG trace(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
beammp_debug(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
if (Data.at(0) == '0') { if (Data.at(0) == '0') {
int CarID = c.GetOpenCarID(); int CarID = c.GetOpenCarID();
beammp_debug(c.GetName() + (" created a car with ID ") + std::to_string(CarID)); 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; return;
case 'c': case 'c':
#ifdef DEBUG trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
beammp_debug(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
pid = Data.substr(0, Data.find('-')); pid = Data.substr(0, Data.find('-'));
vid = Data.substr(Data.find('-') + 1, Data.find(':', 1) - Data.find('-') - 1); 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) { 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; return;
case 'd': case 'd':
#ifdef DEBUG trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
beammp_debug(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
pid = Data.substr(0, Data.find('-')); pid = Data.substr(0, Data.find('-'));
vid = Data.substr(Data.find('-') + 1); 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) { 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; return;
case 'r': case 'r':
#ifdef DEBUG trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
beammp_debug(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
Pos = int(Data.find('-')); Pos = int(Data.find('-'));
pid = Data.substr(0, Pos++); pid = Data.substr(0, Pos++);
vid = Data.substr(Pos, Data.find(':') - Pos); vid = Data.substr(Pos, Data.find(':') - Pos);
@ -315,15 +305,11 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
} }
return; return;
case 't': case 't':
#ifdef DEBUG trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
beammp_debug(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
Network.SendToAll(&c, Packet, false, true); Network.SendToAll(&c, Packet, false, true);
return; return;
default: default:
#ifdef DEBUG trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
beammp_warn(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
#endif // DEBUG
return; return;
} }
} }
@ -338,13 +324,22 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
std::string VD = c.GetCarData(VID); std::string VD = c.GetCarData(VID);
if (VD.empty()) { if (VD.empty()) {
beammp_error("Tried to apply change to vehicle that does not exist"); 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; return;
} }
std::string Header = VD.substr(0, VD.find('{')); std::string Header = VD.substr(0, VD.find('{'));
FoundPos = VD.find('{'); FoundPos = VD.find('{');
if (FoundPos == std::string::npos) { 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; return;
} }
VD = VD.substr(FoundPos); VD = VD.substr(FoundPos);

View File

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

View File

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