Compare commits

...

127 Commits

Author SHA1 Message Date
Lion Kortlepel
aaefea1d01 update commandline 2021-11-09 22:05:04 +01:00
Lion Kortlepel
863e8eb8cf Bump version 2021-10-09 23:53:59 +02:00
Lion Kortlepel
d0431c0b9d Use v2 api 2021-09-27 15:46:37 +02:00
Lion Kortlepel
0961f86662 Add heartbeat-api-v API version header 2021-09-27 15:46:37 +02:00
Lion Kortlepel
7f2ca025f8 Start using new heartbeat response format 2021-09-27 15:46:37 +02:00
Lion Kortlepel
2355327c21 CMake: Fix typo in SANITIZE codepath 2021-09-15 17:55:34 +02:00
Lion Kortlepel
3837e101e2 CMake: Use gzipped debug info on linux 2021-09-15 17:55:34 +02:00
Lion Kortlepel
fa19ba08e3 Sentry: Properly store DSN 2021-09-15 17:55:34 +02:00
Lion Kortlepel
57d0eb735e Add cryptography header for the future 2021-09-15 17:55:34 +02:00
Lion Kortlepel
15704abf6c Http, Heartbeat: Process status < 0 differently, report as "Invalid
Response Code"
2021-09-15 17:55:34 +02:00
Lion Kortlepel
6883c96d33 Http: Add Sentry error breadcrumbs on internal https POST errors 2021-09-15 17:55:34 +02:00
Lion Kortlepel
f632606d76 Heartbeat: Dont report 200 + INVALID_KEY to Sentry 2021-09-15 17:55:34 +02:00
Lion Kortlepel
c70ada2926 Config: private by default 2021-09-15 17:55:34 +02:00
Lion Kortlepel
80aebcb9a7 Actions: prerelease by default 2021-09-13 11:58:01 +03:00
Lion Kortlepel
3fc397814c Move update check to after initialization (since its blocking) 2021-09-11 11:38:06 +03:00
Lion Kortlepel
6542be09ee Clarify what sentry sends, add a way to turn off the warning 2021-09-11 11:38:06 +03:00
Lion Kortlepel
38b934bc0f Move signal handling into its own translation unit to limit overlap 2021-09-11 11:38:06 +03:00
Lion Kortlepel
a2f92b5791 Update changelog, use std::exit instead of exit 2021-09-11 11:38:06 +03:00
Lion Kortlepel
30624c77a2 Update commandline; reset terminal before exit 2021-09-11 11:38:06 +03:00
Lion Kortlepel
b1664bb184 Application: Perform hard-shutdown after 3 Ctrl+C's 2021-09-11 11:38:06 +03:00
Lion Kortlepel
ffac000cd2 Config: Add basic opt-out for Sentry 2021-09-11 11:38:06 +03:00
Lion Kortlepel
3cd94380e2 Changelog: Add recent additions 2021-09-11 11:38:06 +03:00
Lion Kortlepel
b055fd8bda GracefullyShutdown: Add "subsystem x/y shutting down" message
Remove old "X shutting down", "X shut down" messages, they were bad and
confusing
2021-09-11 11:38:06 +03:00
Lion Kortlepel
d43ee4b7b6 Bump version to 2.3.2 2021-09-11 11:38:06 +03:00
Lion Kortlepel
a514591650 Main: Add Ctrl+C handler for windows 2021-09-11 11:38:06 +03:00
Lion Kortlepel
0f9f81e9fa Http: Add cloudflare 5XX status code strings 2021-09-11 11:38:06 +03:00
Lion Kortlepel
11e94e91a7 Update Changelog.md to reflect latest changes in recent merge from #44 2021-09-10 13:41:45 +03:00
Lion Kortlepel
cacdc004da Sentry: remove url length print 2021-09-08 19:55:03 +03:00
Lion Kortlepel
8250d5876f bump to 2.3.1, fix sentry
Fix sentry url length print

remove quotes

github actions is cursed

add debug print

test action

Dont use curl on windows

I dont know why the windows build doesnt report to sentry, so ill try
this.

Change timeout to 20 minutes instead of 5

this is a hacky workaround anyways, so i really dont see why it should
only be 5. 5 is barely enough.

temporarily enable debug mode on sentry

CMake: Use breakpad on windows instead of crashpad

CMake: Sentry: use inproc backend

Since cmake refuses to set my variables, I will do it this way.

I am so tired of this github workflow garbage

Sentry: disable debug again, set
sentry_options_set_symbolize_stacktraces to true, fix memory leak

Sentry: hotfix: dont free options

somehow that causes it to crash, and i cannot be bothered to find out
why right now
2021-09-08 19:55:03 +03:00
Lion Kortlepel
a7b02c459e Actions: update release action 2021-09-08 19:55:03 +03:00
Lion Kortlepel
0d5ef404f6 Sentry: ensure user is set up before version check 2021-09-08 19:55:03 +03:00
Lion Kortlepel
07cf7d7c21 Actions: Replace " with ' might fix the sentry issue 2021-09-08 19:55:03 +03:00
Lion Kortlepel
809a851c71 add trace() as DEBUG debug()
Replace DEBUG debug() with trace() everywhere
2021-09-08 19:55:03 +03:00
Lion Kortlepel
fe36191baf fix github actions
Possible fix for sentry url not showing up in windows build

possible fix for sentry, again

add static_assert in attempt to fix issue with sentry

use target_compile_definitions instead of add_compile_definitions
2021-09-08 19:55:03 +03:00
Lion Kortlepel
7d137eb496 Common: Make threadNameMap static
good practice
2021-09-08 19:55:03 +03:00
Lion Kortlepel
fd6234bd21 Minor fixes 2021-09-08 19:55:03 +03:00
Lion Kortlepel
9f0b057c14 TNetwork: Fix crash when auth response is not a JSON object 2021-09-08 19:55:03 +03:00
Lion Kortlepel
0143748953 TLuaFile: Remove RegisterThread calls since it overrides thread names
This is due to the horrible design of TLuaFile. Everything may be called
at any time from any thread. FIXME.
2021-09-08 19:55:03 +03:00
Lion Kortlepel
8ec90d5186 Add Defer<FnT> type to defer actions to the end of scope. 2021-09-08 19:55:03 +03:00
Lion Kortlepel
d054214b7f Various fixes 2021-09-08 19:55:03 +03:00
Lion Kortlepel
003a8269aa Fix url 2021-09-08 19:55:03 +03:00
Lion Kortlepel
59b1b45625 Sentry: don't report id=authkey unless it's likely to be valid 2021-09-08 19:55:03 +03:00
Lion Kortlepel
15e5cee166 Common: fix compile error 2021-09-08 19:55:03 +03:00
Lion Kortlepel
12123582ad add non-working GET for version 2021-09-08 19:55:03 +03:00
Lion Kortlepel
3fb227e468 TNetwork: Fix crash on wrong backend response 2021-09-08 19:55:03 +03:00
Lion Kortlepel
31e9004011 CMakeLists: Win32: Set runtime linking mode before compiling sentry
in an attempt to fix windows compile issue
2021-09-08 19:55:03 +03:00
Lion Kortlepel
f98c8dabb0 Http: add Status::ToString method, use to report errors to sentry for custom fingerprint 2021-09-08 19:55:03 +03:00
Lion Kortlepel
e8665bfb72 CMakeLists: build runtime static on msvc 2021-09-08 19:55:03 +03:00
Lion Kortlepel
3d13381abd CMakeLists: build sentry static 2021-09-08 19:55:03 +03:00
Lion Kortlepel
5352e4ff03 CMakeLists: link against sentry on windows 2021-09-08 19:55:03 +03:00
Lion Kortlepel
c571e218c7 Compat: add back types 2021-09-08 19:55:03 +03:00
Lion Kortlepel
5725717e29 possible windows compiler fix, again 2021-09-08 19:55:03 +03:00
Lion Kortlepel
7f5447f25e Sentry: add debug prints for hard-debug mode 2021-09-08 19:55:03 +03:00
Lion Kortlepel
b33b396089 Sentry: add debug, remove wrong exception to sentry logging 2021-09-08 19:55:03 +03:00
Lion Kortlepel
ff3cbebac0 Sentry: more macro replacements 2021-09-08 19:55:03 +03:00
Lion Kortlepel
0f9a994c10 Sentry: Fix compile error in AssertNotReachable, release version 2021-09-08 19:55:03 +03:00
Lion Kortlepel
c4b72be50a debug prints 2021-09-08 19:55:03 +03:00
Lion Kortlepel
5a3140c84a THeartbeatThread: fix error reporting 2021-09-08 19:55:03 +03:00
Lion Kortlepel
bea8006a26 Sentry: reword 2021-09-08 19:55:03 +03:00
Lion Kortlepel
a2dc42c5f5 THeartbeatThread: fix missing response code 2021-09-08 19:55:03 +03:00
Lion Kortlepel
4b92532203 Sentry: sort by response code 2021-09-08 19:55:03 +03:00
Lion Kortlepel
683e13a4a0 CustomAssert: fix build error in release mode 2021-09-08 19:55:03 +03:00
Lion Kortlepel
9d6dbefb9d Sentry: add request headers 2021-09-08 19:55:03 +03:00
Lion Kortlepel
981b56b846 CustomAssert: fix macro in release being borked 2021-09-08 19:55:03 +03:00
Lion Kortlepel
9f52ab2e54 Senty: add threadname to context 2021-09-08 19:55:03 +03:00
Lion Kortlepel
8fada3ac04 Sentry: add multiple more logging mechanisms, add [CHAT] 2021-09-08 19:55:03 +03:00
Lion Kortlepel
5330013dc3 possible compiler fix by not using SD_BOTH 2021-09-08 19:55:03 +03:00
Lion Kortlepel
c7e0461a86 possible compiler fix 2021-09-08 19:55:03 +03:00
Lion Kortlepel
032c1daf30 possible fix for windows compile issue 2021-09-08 19:55:03 +03:00
Lion Kortlepel
d1efebe068 handle Rc == "0" case, add sentry info event 2021-09-08 19:55:03 +03:00
Lion Kortlepel
c77e2b3fd8 possible fix for windows build 2 2021-09-08 19:55:03 +03:00
Lion Kortlepel
e92847e628 possible windows compiler fix 2021-09-08 19:55:03 +03:00
Lion Kortlepel
afb18ccff7 THeartbeatThread: fix isAuth being in the wrong scope 2021-09-08 19:55:03 +03:00
Lion Kortlepel
4659a9362d Fix missing semi
didnt do this in a while, wow
2021-09-08 19:55:03 +03:00
Lion Kortlepel
fe6e1e6266 Heartbeat: Try backup1 and backup2, refactor sentry reporting 2021-09-08 19:55:03 +03:00
Lion Kortlepel
c0faff5b05 THeartbeatThread: remove second try to heartbeat url 2021-09-08 19:55:03 +03:00
Lion Kortlepel
f4ffa2cdda Sentry: remove authkey, use id instead 2021-09-08 19:55:03 +03:00
Lion Kortlepel
1409d4ef80 Sentry: use locked contexts to send data to avoid races 2021-09-08 19:55:03 +03:00
Lion Kortlepel
51e662fdda Compat: fix clash with socket() macro, Sentry: Add LogDebug 2021-09-08 19:55:03 +03:00
Lion Kortlepel
72950fdab2 CMakeLists: attempt to use curl on windows instead of winhttp for sentry transport 2021-09-08 19:55:03 +03:00
Lion Kortlepel
b9f594896a Sentry: setup user after config init so that we can sent the authkey 2021-09-08 19:55:03 +03:00
Lion Kortlepel
8551e56e42 Sentry: users: add authkey, username 2021-09-08 19:55:03 +03:00
Lion Kortlepel
f4fc182d5e movre TSentry include up, possibly fixing windows actions issue with
macros

im really starting to appreciate that windows API includes clash with
each other because of macros, its super fun
2021-09-08 19:55:03 +03:00
Lion Kortlepel
ee1e948a65 Sentry: remove IP from user data 2021-09-08 19:55:03 +03:00
Lion Kortlepel
e3081a971e CMakeLists, Actions: Introduce env secret url 2021-09-08 19:55:03 +03:00
Lion Kortlepel
7c5bf9473e CMakeLists: fix include paths 2021-09-08 19:55:03 +03:00
Lion Kortlepel
5a44a8e9c5 Sentry: init before setting user 2021-09-08 19:55:03 +03:00
Lion Kortlepel
d1a0eaffab Sentry: move print as it was blocking (?) 2021-09-08 19:55:03 +03:00
Lion Kortlepel
10322bf24e THeartbeatThread: Use Target var in both places 2021-09-08 19:55:03 +03:00
Lion Kortlepel
969cd93358 rename Sentry to TSentry
windows filenames arent case-sensitive so it doesnt know which
sentry.h we mean.
2021-09-08 19:55:03 +03:00
Lion Kortlepel
b3a8b1a682 CMakeLists: move sentry back in, add C to languages 2021-09-08 19:55:03 +03:00
Lion Kortlepel
2774a73d83 Sentry: move welcome message to after version print 2021-09-08 19:55:03 +03:00
Lion Kortlepel
739eaad199 Sentry: print welcome string on startup 2021-09-08 19:55:03 +03:00
Lion Kortlepel
b53b72d604 Sentry: Build sentry before starting project definition
since Sentry is C and we're CXX
2021-09-08 19:55:03 +03:00
Lion Kortlepel
85fd9e9ee3 Sentry: add loads more information 2021-09-08 19:55:03 +03:00
Lion Kortlepel
57e6e98423 Sentry: discern between auth.* and backend.* errors
TNetwork: error for CheckBytes is now warn
2021-09-08 19:55:03 +03:00
Lion Kortlepel
e3c6bdb50d add changelog file 2021-09-08 19:55:03 +03:00
Lion Kortlepel
1f89b202b4 git: properly remove curl (woops) 2021-09-08 19:55:03 +03:00
Lion Kortlepel
0a31107e56 Sentry: add x-upstream 2021-09-08 19:55:03 +03:00
Lion Kortlepel
9237f0dd43 Http: add debug print 2021-09-08 19:55:03 +03:00
Lion Kortlepel
ce834a634c Actions: add curl 2021-09-08 19:55:03 +03:00
Lion Kortlepel
f65607cb00 README: update to include new dependency 2021-09-08 19:55:03 +03:00
Lion Kortlepel
b0475f262f remove curl submodule, add it as an external dependency 2021-09-08 19:55:03 +03:00
Lion Kortlepel
cee824ad46 Actions: possible fix for the cmake build error C/CXX issue on GH actions 2021-09-08 19:55:03 +03:00
Lion Kortlepel
530e977d9d README: fix git submodule update command 2021-09-08 19:55:03 +03:00
Lion Kortlepel
807cb68f0f README: ensure that submodules are initialized recursively 2021-09-08 19:55:03 +03:00
Lion Kortlepel
3850740ded Actions: checkout submodules recursively 2021-09-08 19:55:03 +03:00
Lion Kortlepel
cfc7b302fe fix include path, libcurl library path 2021-09-08 19:55:03 +03:00
Lion Kortlepel
a7c28a8d0d fix curl, print segfault 2021-09-08 19:55:03 +03:00
Lion Kortlepel
9fe1a94d42 CURL::libcurl instead of libcurl 2021-09-08 19:55:03 +03:00
Lion Kortlepel
1eee56a666 Maybe curl works now? 2021-09-08 19:55:03 +03:00
Lion Kortlepel
28fe6e9634 add curl as in-tree dependency, add sentry ok/not ok notice on startup 2021-09-08 19:55:03 +03:00
Lion Kortlepel
4512f44ea1 bump version to 2.3.0 for next release 2021-09-08 19:55:03 +03:00
Lion Kortlepel
3efd31bc84 add curl to dependencies 2021-09-08 19:55:03 +03:00
Lion Kortlepel
5684134894 Sentry: report any issue with backend.beammp or auth.beammp responses 2021-09-08 19:55:03 +03:00
Lion Kortlepel
e6c97de3c4 CMakeLists: improve documentation, emit warning if no Sentry URL is
supplied
2021-09-08 19:55:03 +03:00
Lion Kortlepel
f550d0bba9 add todo 2021-09-08 19:55:03 +03:00
Lion Kortlepel
2b4fec6d11 Sentry: implement basic exception reporting, error breadcrumbs 2021-09-08 19:55:03 +03:00
Lion Kortlepel
550c658ac5 update gitignore 2021-09-08 19:55:03 +03:00
Lion Kortlepel
da41862f49 working sentry-native 2021-09-08 19:55:03 +03:00
Lion Kortlepel
d5769ce9be add sentry as submodule 2021-09-08 19:55:03 +03:00
Lion
4bf9244005 Update README.md 2021-08-30 18:42:48 +03:00
Lion
89f63024ab Update README.md 2021-08-30 18:42:48 +03:00
Lion
f258678751 Update README.md 2021-08-30 18:42:48 +03:00
Lion
4d2f68068d Add issue templates 2021-08-02 16:23:42 +03:00
Anonymous-275
9f636345ef Removed abort that causes server crash 2021-08-01 15:42:23 +03:00
Lion Kortlepel
3d0d5e9e4c minor fixes, version bump 2021-07-31 21:57:06 +03:00
39 changed files with 1233 additions and 268 deletions

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

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

View File

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

View File

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

View File

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

View File

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

2
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

@@ -13,3 +13,6 @@
[submodule "include/tomlplusplus"]
path = include/tomlplusplus
url = https://github.com/marzer/tomlplusplus
[submodule "include/sentry-native"]
path = include/sentry-native
url = https://github.com/getsentry/sentry-native

View File

@@ -1,10 +1,30 @@
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)
if (WIN32)
# this has to happen before sentry, so that crashpad on windows links with these settings.
message(STATUS "MSVC -> forcing use of statically-linked runtime.")
STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})
endif()
include_directories("include/sentry-native/include")
set(SENTRY_BUILD_SHARED_LIBS OFF)
if (MSVC)
set(SENTRY_BUILD_RUNTIMESTATIC ON)
endif()
set(SENTRY_BACKEND breakpad)
add_subdirectory("include/sentry-native")
message(STATUS "Setting compiler flags")
if (WIN32)
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include)
@@ -12,13 +32,26 @@ if (WIN32)
elseif (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s -fno-builtin")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -gz -fno-builtin")
if (SANITIZE)
message(STATUS "sanitize is ON")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread")
endif (SANITIZE)
endif ()
message(STATUS "Checking for Sentry URL")
# this is set by the build system.
# IMPORTANT: if you're building from source, just leave this empty
if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL)
message(WARNING "No sentry URL configured. Sentry logging is disabled for this build. \
This is not an error, and if you're building the BeamMP-Server yourself, this is expected and can be ignored.")
set(BEAMMP_SECRET_SENTRY_URL "")
else()
string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN)
message(STATUS "Sentry URL is length ${URL_LEN}")
endif()
message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
include_directories("asio/asio/include")
include_directories("rapidjson/include")
@@ -27,9 +60,9 @@ add_subdirectory("socket.io-client-cpp")
add_subdirectory("include/commandline")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
message(STATUS "Looking for Boost")
find_package(Boost REQUIRED COMPONENTS system thread)
add_executable(BeamMP-Server
@@ -46,23 +79,57 @@ add_executable(BeamMP-Server
include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp
#include/SocketIO.h src/SocketIO.cpp
include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.cpp)
include/TNetwork.h src/TNetwork.cpp
include/SignalHandling.h src/SignalHandling.cpp)
target_include_directories(BeamMP-Server PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/commandline")
target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}")
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)
target_include_directories(BeamMP-Server PUBLIC ${Boost_INCLUDE_DIRS} ${LUA_INCLUDE_DIR} "socket.io-client-cpp/src" "include/tomlplusplus")
target_include_directories(BeamMP-Server PUBLIC
${Boost_INCLUDE_DIRS}
${LUA_INCLUDE_DIR}
${CURL_INCLUDE_DIRS}
"socket.io-client-cpp/src"
"include/tomlplusplus"
"include/sentry-native/include"
"include/curl/include")
message(STATUS "Looking for SSL")
find_package(OpenSSL REQUIRED)
message(STATUS "CURL IS ${CURL_LIBRARIES}")
if (UNIX)
target_link_libraries(BeamMP-Server z pthread stdc++fs ${LUA_LIBRARIES} crypto ${OPENSSL_LIBRARIES} commandline sioclient_tls)
target_link_libraries(BeamMP-Server
z
pthread
stdc++fs
${LUA_LIBRARIES}
crypto
${OPENSSL_LIBRARIES}
commandline
sioclient_tls
sentry)
elseif (WIN32)
include(FindLua)
message(STATUS "Looking for libz")
find_package(ZLIB REQUIRED)
message(STATUS "Looking for RapidJSON")
find_package(RapidJSON CONFIG REQUIRED)
target_include_directories(BeamMP-Server PRIVATE ${RAPIDJSON_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
target_link_libraries(BeamMP-Server PRIVATE ws2_32 ZLIB::ZLIB ${LUA_LIBRARIES} ${OPENSSL_LIBRARIES} commandline sioclient_tls)
target_link_libraries(BeamMP-Server PRIVATE
ws2_32
ZLIB::ZLIB
${LUA_LIBRARIES}
${OPENSSL_LIBRARIES}
commandline
sioclient_tls
sentry)
endif ()

82
Changelog.md Normal file
View File

@@ -0,0 +1,82 @@
# v2.3.3
- CHANGED servers to be private by default
# v2.3.2
- ADDED Ctrl+C causes a graceful shutdown on windows (did already on linux)
- ADDED more meaningful shutdown messages
- ADDED even better backend connection error reporting
- ADDED `SendErrors` config in `ServerConfig.toml` to opt-out of error reporting
- ADDED hard-shutdown if Ctrl+C pressed 3 times
- FIXED issue with shells like bash being unusable after server exit
# v2.3.1
- CHANGED join/sync timeout to 20 minutes, players wont drop if loading takes >5 mins
# v2.3.0
- ADDED version check - the server will now let you know when a new release is out
- ADDED logging of various errors, crashes and exceptions to the backend
- ADDED chat messages are now logged to the server console as [CHAT]
- ADDED debug message telling you when the server heartbeats to the backend
- REMOVED various [DEBUG] messages which were confusing (such as "breaking client loop")
- FIXED various crashes and issues with handling unexpected backend responses
- FIXED minor bugs due to code correctness
# v2.2.0
- FIXED major security flaw
- FIXED minor bugs
# v2.1.4
- ADDED debug heartbeat print
- ADDED kicking every player before shutdown
- FIXED rare bug which led to violent crash
- FIXED minor bugs
# v2.1.3
- FIXED Lua events not cancelling properly on Linux
# v2.1.2
- CHANGED default map to gridmap v2
- FIXED version number display
# v2.1.1
# v2.1.0 (pre-v2.1.1)
# v2.0.4 (pre-v2.1.0)
- REMOVED boost as a runtime dependency
- FIXED Lua plugins on Linux
- FIXED console history on Windows
- CHANGED to new config format TOML
# v2.0.3
- WORKAROUND for timeout bug / ghost player bug
- FIXED 100% CPU spin when stdin is /dev/null.
# v2.0.2
- ADDED fully new commandline
- ADDED new backend
- ADDED automated build system
- ADDED lua GetPlayerIdentifiers
- ADDED lots of debug info
- ADDED better POSTing and GETing
- ADDED a license
- FIXED ghost players in player list issue
- FIXED ghost vehicle after joining issue
- FIXED missing vehicle after joining issue
- FIXED a lot of desync issues
- FIXED some memory leaks
- FIXED various crashes
- FIXED various data-races
- FIXED some linux-specific crashes
- FIXED some linux-specific issues
- FIXED bug which caused kicking to be logged as leaving
- FIXED various internal developer quality-of-life things

View File

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

View File

@@ -1,5 +1,9 @@
#pragma once
#include "TSentry.h"
extern TSentry Sentry;
#include <array>
#include <atomic>
#include <deque>
#include <functional>
@@ -23,10 +27,12 @@ public:
, Resource("Resources")
, MapName("/levels/gridmap_v2/info.json")
, MaxPlayers(10)
, Private(false)
, Private(true)
, MaxCars(1)
, DebugModeEnabled(false)
, Port(30814) { }
, Port(30814)
, SendErrors(true)
, SendErrorsMessageEnabled(true) { }
std::string ServerName;
std::string ServerDesc;
std::string Resource;
@@ -38,6 +44,8 @@ public:
bool DebugModeEnabled;
int Port;
std::string CustomIP;
bool SendErrors;
bool SendErrorsMessageEnabled;
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
};
using TShutdownHandler = std::function<void()>;
@@ -50,16 +58,21 @@ public:
// Causes all threads to finish up and exit gracefull gracefully
static void GracefullyShutdown();
static TConsole& Console() { return *mConsole; }
static std::string ServerVersion() { return "2.1.4"; }
static std::string ServerVersion() { return "2.3.3"; }
static std::string ClientVersion() { return "2.0"; }
static std::string PPS() { return mPPS; }
static void SetPPS(std::string NewPPS) { mPPS = NewPPS; }
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
static inline TSettings Settings {};
static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; }
static std::string GetBackendHostname() { return "backend.beammp.com"; }
static std::string GetBackup1Hostname() { return "backup1.beammp.com"; }
static std::string GetBackup2Hostname() { return "backup2.beammp.com"; }
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
static void CheckForUpdates();
static std::array<int, 3> VersionStrToInts(const std::string& str);
static bool IsOutdated(const std::array<int, 3>& Current, const std::array<int, 3>& Newest);
private:
static inline std::string mPPS;
@@ -68,12 +81,13 @@ private:
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
};
std::string ThreadName();
void RegisterThread(const std::string str);
std::string ThreadName(bool DebugModeOverride = false);
void RegisterThread(const std::string& str);
#define RegisterThreadAuto() RegisterThread(__func__)
#define KB 1024
#define MB (KB * 1024)
#define SSU_UNRAW SECRET_SENTRY_URL
#define _file_basename std::filesystem::path(__FILE__).filename().string()
#define _line std::to_string(__LINE__)
@@ -99,16 +113,22 @@ void RegisterThread(const std::string str);
#else
#define _this_location (ThreadName() + _file_basename + ":" + _line + " ")
#endif
#define SU_RAW SSU_UNRAW
#else // !defined(DEBUG)
#define SU_RAW RAWIFY(SSU_UNRAW)
#define _this_location (ThreadName())
#endif // defined(DEBUG)
#define warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
#define info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
#define error(x) Application::Console().Write(_this_location + std::string("[ERROR] ") + (x))
#define error(x) \
do { \
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
} while (false)
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define debug(x) \
do { \
@@ -116,8 +136,22 @@ void RegisterThread(const std::string str);
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
} \
} while (false)
// trace() is a debug-build debug()
#if defined(DEBUG)
#define trace(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \
} while (false)
#else
#define trace(x)
#endif // defined(DEBUG)
void LogChatMessage(const std::string& name, int id, const std::string& msg);
#define Biggest 30000
std::string Comp(std::string Data);
std::string DeComp(std::string Compressed);
#define S_DSN SU_RAW

View File

@@ -12,9 +12,9 @@ using DWORD = unsigned long;
using PDWORD = unsigned long*;
using LPDWORD = unsigned long*;
char _getch();
inline void CloseSocketProper(int socket) {
shutdown(socket, SHUT_RDWR);
close(socket);
inline void CloseSocketProper(int TheSocket) {
shutdown(TheSocket, SHUT_RDWR);
close(TheSocket);
}
#endif // unix
@@ -23,9 +23,9 @@ inline void CloseSocketProper(int socket) {
#ifdef WIN32
#include <conio.h>
#include <winsock2.h>
inline void CloseSocketProper(SOCKET socket) {
shutdown(socket, SD_BOTH);
closesocket(socket);
inline void CloseSocketProper(SOCKET TheSocket) {
shutdown(TheSocket, 2); // 2 == SD_BOTH
closesocket(TheSocket);
}
#endif // WIN32

114
include/Cryptography.h Normal file
View File

@@ -0,0 +1,114 @@
// Copyright Anonymous275 8/11/2020
#pragma once
#include <array>
#include <cstdarg>
#include <string>
namespace Crypto {
constexpr auto time = __TIME__;
constexpr auto seed = static_cast<int>(time[7]) + static_cast<int>(time[6]) * 10 + static_cast<int>(time[4]) * 60 + static_cast<int>(time[3]) * 600 + static_cast<int>(time[1]) * 3600 + static_cast<int>(time[0]) * 36000;
// 1988, Stephen Park and Keith Miller
// "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard"
// Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation:
// with 32-bit math and without division
template <int N>
struct RandomGenerator {
private:
static constexpr unsigned a = 16807; // 7^5
static constexpr unsigned m = 2147483647; // 2^31 - 1
static constexpr unsigned s = RandomGenerator<N - 1>::value;
static constexpr unsigned lo = a * (s & 0xFFFFu); // Multiply lower 16 bits by 16807
static constexpr unsigned hi = a * (s >> 16u); // Multiply higher 16 bits by 16807
static constexpr unsigned lo2 = lo + ((hi & 0x7FFFu) << 16u); // Combine lower 15 bits of hi with lo's upper bits
static constexpr unsigned hi2 = hi >> 15u; // Discard lower 15 bits of hi
static constexpr unsigned lo3 = lo2 + hi;
public:
static constexpr unsigned max = m;
static constexpr unsigned value = lo3 > m ? lo3 - m : lo3;
};
template <>
struct RandomGenerator<0> {
static constexpr unsigned value = seed;
};
template <int N, int M>
struct RandomInt {
static constexpr auto value = RandomGenerator<N + 1>::value % M;
};
template <int N>
struct RandomChar {
static const char value = static_cast<char>(1 + RandomInt<N, 0x7F - 1>::value);
};
template <size_t N, int K, typename Char>
struct MangleString {
private:
const char _key;
std::array<Char, N + 1> _encrypted;
constexpr Char enc(Char c) const {
return c ^ _key;
}
Char dec(Char c) const {
return c ^ _key;
}
public:
template <size_t... Is>
constexpr MangleString(const Char* str, std::index_sequence<Is...>)
: _key(RandomChar<K>::value)
, _encrypted { enc(str[Is])... } { }
decltype(auto) decrypt() {
for (size_t i = 0; i < N; ++i) {
_encrypted[i] = dec(_encrypted[i]);
}
_encrypted[N] = '\0';
return _encrypted.data();
}
};
static auto w_printf = [](const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
};
static auto w_printf_s = [](const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
};
static auto w_sprintf_s = [](char* buf, size_t buf_size, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vsprintf(buf, fmt, args);
va_end(args);
};
static auto w_sprintf_s_ret = [](char* buf, size_t buf_size, const char* fmt, ...) {
int ret;
va_list args;
va_start(args, fmt);
ret = vsprintf(buf, fmt, args);
va_end(args);
return ret;
};
#define XOR_C(s) [] { constexpr Crypto::MangleString< sizeof(s)/sizeof(char) - 1, __COUNTER__, char > expr( s, std::make_index_sequence< sizeof(s)/sizeof(char) - 1>() ); return expr; }().decrypt()
#define XOR_W(s) [] { constexpr Crypto::MangleString< sizeof(s)/sizeof(wchar_t) - 1, __COUNTER__, wchar_t > expr( s, std::make_index_sequence< sizeof(s)/sizeof(wchar_t) - 1>() ); return expr; }().decrypt()
#define RAWIFY(s) XOR_C(s)
}

View File

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

18
include/Defer.h Normal file
View File

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

View File

@@ -4,6 +4,9 @@
#include <unordered_map>
namespace Http {
std::string GET(const std::string& host, int port, const std::string& target);
std::string POST(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, bool json);
}
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr);
std::string POST(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, bool json, int* status = nullptr);
namespace Status {
std::string ToString(int code);
}
}

3
include/SignalHandling.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
void SetupSignalHandlers();

View File

@@ -8,13 +8,13 @@ class TConfig {
public:
explicit TConfig();
bool Failed() const { return mFailed; }
[[nodiscard]] bool Failed() const { return mFailed; }
private:
void CreateConfigFile(std::string_view name);
void ParseFromFile(std::string_view name);
void PrintDebug();
void ParseOldFormat();
bool mFailed { false };

View File

@@ -2,6 +2,7 @@
#include "commandline/commandline.h"
#include "TLuaFile.h"
#include "Cryptography.h"
#include <atomic>
#include <fstream>

View File

@@ -27,12 +27,13 @@ public:
private:
void FolderList(const std::string& Path, bool HotSwap);
void RegisterFiles(const std::string& Path, bool HotSwap);
bool NewFile(const std::string& Path);
void RegisterFiles(const fs::path& Path, bool HotSwap);
bool IsNewFile(const std::string& Path);
TNetwork& mNetwork;
TServer& mServer;
std::string mPath;
bool mShutdown { false };
TSetOfLuaFile mLuaFiles;
std::mutex mListMutex;
};

View File

@@ -54,6 +54,7 @@ private:
bool mStopThread = false;
bool mConsole = false;
void Load();
std::mutex mInitMutex;
};
std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaFile* Caller, std::shared_ptr<TLuaArg> arg, bool Wait);

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

Submodule include/sentry-native added at 521860373d

View File

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

View File

@@ -2,11 +2,17 @@
#include "TConsole.h"
#include <array>
#include <charconv>
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <thread>
#include <zlib.h>
#include "CustomAssert.h"
#include "Http.h"
std::unique_ptr<TConsole> Application::mConsole = std::make_unique<TConsole>();
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
@@ -17,10 +23,74 @@ void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
}
void Application::GracefullyShutdown() {
info("please wait while all subsystems are shutting down...");
static bool AlreadyShuttingDown = false;
static uint8_t ShutdownAttempts = 0;
if (AlreadyShuttingDown) {
++ShutdownAttempts;
// hard shutdown at 2 additional tries
if (ShutdownAttempts == 2) {
info("hard shutdown forced by multiple shutdown requests");
std::exit(0);
}
info("already shutting down!");
return;
} else {
AlreadyShuttingDown = true;
}
trace("waiting for lock release");
std::unique_lock Lock(mShutdownHandlersMutex);
for (auto& Handler : mShutdownHandlers) {
Handler();
info("please wait while all subsystems are shutting down...");
for (size_t i = 0; i < mShutdownHandlers.size(); ++i) {
info("Subsystem " + std::to_string(i + 1) + "/" + std::to_string(mShutdownHandlers.size()) + " shutting down");
mShutdownHandlers[i]();
}
}
std::array<int, 3> Application::VersionStrToInts(const std::string& str) {
std::array<int, 3> Version;
std::stringstream ss(str);
for (int& i : Version) {
std::string Part;
std::getline(ss, Part, '.');
std::from_chars(&*Part.begin(), &*Part.begin() + Part.size(), i);
}
return Version;
}
bool Application::IsOutdated(const std::array<int, 3>& Current, const std::array<int, 3>& Newest) {
if (Newest[0] > Current[0]) {
return true;
} else if (Newest[0] == Current[0] && Newest[1] > Current[1]) {
return true;
} else if (Newest[0] == Current[0] && Newest[1] == Current[1] && Newest[2] > Current[2]) {
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 = VersionStrToInts(ServerVersion());
auto RemoteVersion = VersionStrToInts(Response);
if (IsOutdated(MyVersion, RemoteVersion)) {
std::string RealVersionString = std::to_string(RemoteVersion[0]) + ".";
RealVersionString += std::to_string(RemoteVersion[1]) + ".";
RealVersionString += std::to_string(RemoteVersion[2]);
warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For more info 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);
}
}
@@ -70,10 +140,12 @@ std::string DeComp(std::string Compressed) {
// thread name stuff
std::map<std::thread::id, std::string> threadNameMap;
static std::map<std::thread::id, std::string> threadNameMap {};
static std::mutex ThreadNameMapMutex {};
std::string ThreadName() {
if (Application::Settings.DebugModeEnabled) {
std::string ThreadName(bool DebugModeOverride) {
auto Lock = std::unique_lock(ThreadNameMapMutex);
if (DebugModeOverride || Application::Settings.DebugModeEnabled) {
auto id = std::this_thread::get_id();
if (threadNameMap.find(id) != threadNameMap.end()) {
// found
@@ -83,6 +155,19 @@ std::string ThreadName() {
return "";
}
void RegisterThread(const std::string str) {
void RegisterThread(const std::string& str) {
auto Lock = std::unique_lock(ThreadNameMapMutex);
threadNameMap[std::this_thread::get_id()] = str;
}
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

@@ -7,6 +7,7 @@
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <map>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
@@ -14,47 +15,85 @@ namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
std::string Http::GET(const std::string& host, int port, const std::string& target) {
// FIXME: doesn't support https
// if it causes issues, yell at me and I'll fix it asap. - Lion
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
try {
net::io_context io;
tcp::resolver resolver(io);
beast::tcp_stream stream(io);
auto const results = resolver.resolve(host, std::to_string(port));
stream.connect(results);
// Check command line arguments.
int version = 11;
http::request<http::string_body> req { http::verb::get, target, 11 /* http 1.1 */ };
// The io_context is required for all I/O
net::io_context ioc;
req.set(http::field::host, host);
// tell the server what we are (boost beast)
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// The SSL context is required, and holds certificates
ssl::context ctx(ssl::context::tlsv12_client);
http::write(stream, req);
// This holds the root certificate used for verification
// we don't do / have this
// load_root_certificates(ctx);
// used for reading
beast::flat_buffer buffer;
http::response<http::string_body> response;
// Verify the remote server's certificate
ctx.set_verify_mode(ssl::verify_none);
http::read(stream, buffer, response);
// These objects perform our I/O
tcp::resolver resolver(ioc);
beast::ssl_stream<beast::tcp_stream> stream(ioc, ctx);
std::string result(response.body());
beast::error_code ec;
stream.socket().shutdown(tcp::socket::shutdown_both, ec);
if (ec && ec != beast::errc::not_connected) {
throw beast::system_error { ec }; // goes down to `return "-1"` anyways
// 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 };
}
return result;
// Look up the domain name
auto const results = resolver.resolve(host.c_str(), std::to_string(port));
} catch (const std::exception& e) {
Application::Console().Write(e.what());
// 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();
}
// ignore 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 "-1";
}
}
std::string Http::POST(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, bool json) {
std::string Http::POST(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, bool json, int* status) {
try {
net::io_context io;
@@ -87,7 +126,8 @@ std::string Http::POST(const std::string& host, const std::string& target, const
//debug("IPv6 connect failed, trying IPv4");
bool ok = try_connect_with_protocol(tcp::v4());
if (!ok) {
//error("failed to resolve or connect in POST " + host + target);
Application::Console().Write("[ERROR] failed to resolve or connect in POST " + host + target);
Sentry.AddErrorBreadcrumb("failed to resolve or connect to " + host + target, __FILE__, std::to_string(__LINE__)); // FIXME: this is ugly.
return "-1";
}
//}
@@ -97,7 +137,6 @@ std::string Http::POST(const std::string& host, const std::string& target, const
req.set(http::field::host, host);
if (!body.empty()) {
if (json) {
// FIXME: json is untested.
req.set(http::field::content_type, "application/json");
} else {
req.set(http::field::content_type, "application/x-www-form-urlencoded");
@@ -112,6 +151,16 @@ std::string Http::POST(const std::string& host, const std::string& target, const
req.set(pair.first, pair.second);
}
std::unordered_map<std::string, std::string> request_data;
for (const auto& header : req.base()) {
// need to do explicit casts to convert string_view to string
// since string_view may not be null-terminated (and in fact isn't, here)
std::string KeyString(header.name_string());
std::string ValueString(header.value());
request_data[KeyString] = ValueString;
}
Sentry.SetContext("https-post-request-data", request_data);
std::stringstream oss;
oss << req;
@@ -125,6 +174,20 @@ std::string Http::POST(const std::string& host, const std::string& target, const
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);
std::stringstream result;
result << response;
@@ -140,6 +203,92 @@ std::string Http::POST(const std::string& host, const std::string& target, const
} catch (const std::exception& e) {
Application::Console().Write(e.what());
Sentry.AddErrorBreadcrumb(e.what(), __FILE__, std::to_string(__LINE__)); // FIXME: this is ugly.
return "-1";
}
}
// RFC 2616, RFC 7231
static std::map<size_t, const char*> Map = {
{ -1, "Invalid Response Code"},
{ 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" },
// cloudflare status codes
{ 520, "(CDN) Web Server Returns An Unknown Error" },
{ 521, "(CDN) Web Server Is Down" },
{ 522, "(CDN) Connection Timed Out" },
{ 523, "(CDN) Origin Is Unreachable" },
{ 524, "(CDN) A Timeout Occurred" },
{ 525, "(CDN) SSL Handshake Failed" },
{ 526, "(CDN) Invalid SSL Certificate" },
{ 527, "(CDN) Railgun Listener To Origin Error" },
{ 530, "(CDN) 1XXX Internal Error" },
};
std::string Http::Status::ToString(int code) {
if (Map.find(code) != Map.end()) {
return Map.at(code);
} else {
return std::to_string(code);
}
}

65
src/SignalHandling.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "SignalHandling.h"
#include "Common.h"
#ifdef __unix
#include <csignal>
static void UnixSignalHandler(int sig) {
switch (sig) {
case SIGPIPE:
warn("ignoring SIGPIPE");
break;
case SIGTERM:
info("gracefully shutting down via SIGTERM");
Application::GracefullyShutdown();
break;
case SIGINT:
info("gracefully shutting down via SIGINT");
Application::GracefullyShutdown();
break;
default:
debug("unhandled signal: " + std::to_string(sig));
break;
}
}
#endif // __unix
#ifdef WIN32
#include <windows.h>
// return TRUE if handled, FALSE if not
BOOL WINAPI Win32CtrlC_Handler(DWORD CtrlType) {
switch (CtrlType) {
case CTRL_C_EVENT:
info("gracefully shutting down via CTRL+C");
Application::GracefullyShutdown();
return TRUE;
case CTRL_BREAK_EVENT:
info("gracefully shutting down via CTRL+BREAK");
Application::GracefullyShutdown();
return TRUE;
case CTRL_CLOSE_EVENT:
info("gracefully shutting down via close");
Application::GracefullyShutdown();
return TRUE;
}
// we dont care for any others like CTRL_LOGOFF_EVENT and CTRL_SHUTDOWN_EVENT
return FALSE;
}
#endif // WIN32
void SetupSignalHandlers() {
// signal handlers for unix#include <windows.h>
#ifdef __unix
trace("registering handlers for SIGINT, SIGTERM, SIGPIPE");
signal(SIGPIPE, UnixSignalHandler);
signal(SIGTERM, UnixSignalHandler);
#ifndef DEBUG
signal(SIGINT, UnixSignalHandler);
#endif // DEBUG
#endif // __unix
// signal handlers for win32
#ifdef WIN32
trace("registering handlers for CTRL_*_EVENTs");
SetConsoleCtrlHandler(Win32CtrlC_Handler, TRUE);
#endif // WIN32
}

View File

@@ -18,6 +18,8 @@ static constexpr std::string_view StrName = "Name";
static constexpr std::string_view StrDescription = "Description";
static constexpr std::string_view StrResourceFolder = "ResourceFolder";
static constexpr std::string_view StrAuthKey = "AuthKey";
static constexpr std::string_view StrSendErrors = "SendErrors";
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
TConfig::TConfig() {
if (!fs::exists(ConfigFileName) || !fs::is_regular_file(ConfigFileName)) {
@@ -32,6 +34,15 @@ TConfig::TConfig() {
}
}
void WriteSendErrors(const std::string& name) {
std::ofstream CfgFile { name, std::ios::out | std::ios::app };
CfgFile << "# You can turn on/off the SendErrors message you get on startup here" << std::endl
<< StrSendErrorsMessageEnabled << " = true" << std::endl
<< "# If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`."
<< std::endl
<< StrSendErrors << " = true" << std::endl;
}
void TConfig::CreateConfigFile(std::string_view name) {
// build from old config Server.cfg
@@ -59,6 +70,7 @@ void TConfig::CreateConfigFile(std::string_view name) {
{ StrDescription, Application::Settings.ServerDesc },
{ StrResourceFolder, Application::Settings.Resource },
{ StrAuthKey, Application::Settings.Key },
//{ StrSendErrors, Application::Settings.SendErrors },
} } },
@@ -72,6 +84,8 @@ void TConfig::CreateConfigFile(std::string_view name) {
ofs << tbl << '\n';
error("There was no \"" + std::string(ConfigFileName) + "\" file (this is normal for the first time running the server), so one was generated for you. It was automatically filled with the settings from your Server.cfg, if you have one. Please open ServerConfig.toml and ensure your AuthKey and other settings are filled in and correct, then restart the server. The old Server.cfg file will no longer be used and causes a warning if it exists from now on.");
mFailed = true;
ofs.close();
WriteSendErrors(std::string(name));
} else {
error("Couldn't create " + std::string(name) + ". Check permissions, try again, and contact support if it continues not to work.");
mFailed = true;
@@ -132,6 +146,19 @@ void TConfig::ParseFromFile(std::string_view name) {
} else {
throw std::runtime_error(std::string(StrAuthKey));
}
// added later, so behaves differently
if (auto val = GeneralTable[StrSendErrors].value<bool>(); val.has_value()) {
Application::Settings.SendErrors = val.value();
} else {
// dont throw, instead write it into the file and use default
WriteSendErrors(std::string(name));
}
if (auto val = GeneralTable[StrSendErrorsMessageEnabled].value<bool>(); val.has_value()) {
Application::Settings.SendErrorsMessageEnabled = val.value();
} else {
// no idea what to do here, ignore...?
// this entire toml parser sucks and is replaced in the upcoming lua.
}
} catch (const std::exception& err) {
error("Error parsing config file value: " + std::string(err.what()));
mFailed = true;

View File

@@ -3,8 +3,12 @@
#include "Client.h"
#include "Http.h"
//#include "SocketIO.h"
#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>
#include <sstream>
namespace json = rapidjson;
void THeartbeatThread::operator()() {
RegisterThread("Heartbeat");
std::string Body;
@@ -35,33 +39,92 @@ void THeartbeatThread::operator()() {
Body += "&pps=" + Application::PPS();
T = Http::POST(Application::GetBackendHostname(), "/heartbeat", {}, Body, false);
auto SentryReportError = [&](const std::string& transaction, int status) {
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("heartbeat",
{ { "response-body", T },
{ "request-body", Body } });
Sentry.SetTransaction(transaction);
Sentry.Log(SentryLevel::Error, "default", Http::Status::ToString(status) + " (" + std::to_string(status) + ")");
};
if (T.substr(0, 2) != "20") {
//Backend system refused server startup!
auto Target = "/heartbeat";
int ResponseCode = -1;
const std::vector<std::string> Urls = {
Application::GetBackendHostname(),
Application::GetBackup1Hostname(),
Application::GetBackup2Hostname(),
};
json::Document Doc;
bool Ok = false;
for (const auto& Url : Urls) {
T = Http::POST(Url, Target, { { "api-v", "2" } }, Body, false, &ResponseCode);
trace(T);
Doc.Parse(T.data(), T.size());
if (Doc.HasParseError() || !Doc.IsObject()) {
error("Backend response failed to parse as valid json");
debug("Response was: `" + T + "`");
Sentry.SetContext("JSON Response", { { "reponse", T } });
SentryReportError(Url + Target, ResponseCode);
} else if (ResponseCode != 200) {
SentryReportError(Url + Target, ResponseCode);
} else {
// all ok
Ok = true;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
T = Http::POST(Application::GetBackendHostname(), "/heartbeat", {}, Body, false);
// TODO backup2 + HTTP flag (no TSL)
if (T.substr(0, 2) != "20") {
warn("Backend system refused server! Server might not show in the public list");
debug("server returned \"" + T + "\"");
isAuth = false;
}
std::string Status {};
std::string Code {};
std::string Message {};
const auto StatusKey = "status";
const auto CodeKey = "code";
const auto MessageKey = "msg";
if (Ok) {
if (Doc.HasMember(StatusKey) && Doc[StatusKey].IsString()) {
Status = Doc[StatusKey].GetString();
} else {
Sentry.SetContext("JSON Response", { { StatusKey, "invalid string / missing" } });
Ok = false;
}
if (Doc.HasMember(CodeKey) && Doc[CodeKey].IsString()) {
Code = Doc[CodeKey].GetString();
} else {
Sentry.SetContext("JSON Response", { { CodeKey, "invalid string / missing" } });
Ok = false;
}
if (Doc.HasMember(MessageKey) && Doc[MessageKey].IsString()) {
Message = Doc[MessageKey].GetString();
} else {
Sentry.SetContext("JSON Response", { { MessageKey, "invalid string / missing" } });
Ok = false;
}
if (!Ok) {
error("Missing/invalid json members in backend response");
Sentry.LogError("Missing/invalid json members in backend response", __FILE__, std::to_string(__LINE__));
}
}
if (!isAuth) {
if (T == "2000") {
if (Ok && !isAuth) {
if (Status == "2000") {
info(("Authenticated!"));
isAuth = true;
} else if (T == "200") {
} else if (Status == "200") {
info(("Resumed authenticated session!"));
isAuth = true;
} else {
if (Message.empty()) {
Message = "Backend didn't provide a reason";
}
error("Backend REFUSED the auth key. " + Message);
}
}
//SocketIO::Get().SetAuthenticated(isAuth);
}
}
std::string THeartbeatThread::GenerateCall() {
std::stringstream Ret;
@@ -86,10 +149,8 @@ THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& S
, mServer(Server) {
Application::RegisterShutdownHandler([&] {
if (mThread.joinable()) {
debug("shutting down Heartbeat");
mShutdown = true;
mThread.join();
debug("shut down Heartbeat");
}
});
Start();

View File

@@ -23,10 +23,8 @@ TLuaEngine::TLuaEngine(TServer& Server, TNetwork& Network)
FolderList(Path, false);
mPath = Path;
Application::RegisterShutdownHandler([&] {if (mThread.joinable()) {
debug("shutting down LuaEngine");
mShutdown = true;
mThread.join();
debug("shut down LuaEngine");
} });
Start();
}
@@ -66,37 +64,47 @@ std::optional<std::reference_wrapper<TLuaFile>> TLuaEngine::GetScript(lua_State*
}
void TLuaEngine::FolderList(const std::string& Path, bool HotSwap) {
auto Lock = std::unique_lock(mListMutex);
for (const auto& entry : fs::directory_iterator(Path)) {
auto pos = entry.path().filename().string().find('.');
if (pos == std::string::npos) {
RegisterFiles(entry.path().string(), HotSwap);
if (fs::is_directory(entry)) {
RegisterFiles(entry.path(), HotSwap);
}
}
}
void TLuaEngine::RegisterFiles(const std::string& Path, bool HotSwap) {
std::string Name = Path.substr(Path.find_last_of('\\') + 1);
void TLuaEngine::RegisterFiles(const fs::path& Path, bool HotSwap) {
std::string Name = Path.filename().string();
if (!HotSwap)
info(("Loading plugin : ") + Name);
std::vector<fs::path> Entries;
for (const auto& entry : fs::directory_iterator(Path)) {
auto pos = entry.path().string().find((".lua"));
if (pos != std::string::npos && entry.path().string().length() - pos == 4) {
if (!HotSwap || NewFile(entry.path().string())) {
auto FileName = entry.path().string();
std::unique_ptr<TLuaFile> ScriptToInsert(new TLuaFile(*this));
auto& Script = *ScriptToInsert;
mLuaFiles.insert(std::move(ScriptToInsert));
Script.Init(Name, FileName, fs::last_write_time(FileName));
if (HotSwap)
info(("[HOTSWAP] Added : ") + Script.GetFileName().substr(Script.GetFileName().find('\\')));
}
if (entry.path().extension() == ".lua") {
Entries.push_back(entry);
}
}
std::sort(Entries.begin(), Entries.end(), [](const fs::path& first, const fs::path& second) {
auto firstStr = first.string();
auto secondStr = second.string();
std::transform(firstStr.begin(), firstStr.end(), firstStr.begin(), ::tolower);
std::transform(secondStr.begin(), secondStr.end(), secondStr.begin(), ::tolower);
return firstStr < secondStr;
});
for (const fs::path& Entry : Entries) {
if (!HotSwap || IsNewFile(Entry.string())) {
auto FileName = Entry.string();
std::unique_ptr<TLuaFile> ScriptToInsert(new TLuaFile(*this));
auto& Script = *ScriptToInsert;
mLuaFiles.insert(std::move(ScriptToInsert));
Script.Init(Name, FileName, fs::last_write_time(FileName));
if (HotSwap)
info(("[HOTSWAP] Added : ") + Script.GetFileName().substr(Script.GetFileName().find('\\')));
}
}
}
bool TLuaEngine::NewFile(const std::string& Path) {
bool TLuaEngine::IsNewFile(const std::string& Path) {
for (auto& Script : mLuaFiles) {
if (Path == Script->GetFileName())
if (fs::absolute(Path) == fs::absolute(Script->GetFileName()))
return false;
}
return true;

View File

@@ -2,6 +2,7 @@
#include "Client.h"
#include "Common.h"
#include "CustomAssert.h"
#include "Defer.h"
#include "TLuaEngine.h"
#include "TNetwork.h"
#include "TServer.h"
@@ -44,7 +45,6 @@ void ClearStack(lua_State* L) {
}
std::any Trigger(TLuaFile* lua, const std::string& R, std::shared_ptr<TLuaArg> arg) {
RegisterThread(lua->GetFileName());
std::lock_guard<std::mutex> lockGuard(lua->Lock);
std::packaged_task<std::any(std::shared_ptr<TLuaArg>)> task([lua, R](std::shared_ptr<TLuaArg> arg) { return CallFunction(lua, R, arg); });
std::future<std::any> f1 = task.get_future();
@@ -110,7 +110,13 @@ bool ConsoleCheck(lua_State* L, int r) {
bool CheckLua(lua_State* L, int r) {
if (r != LUA_OK) {
std::string msg = lua_tostring(L, -1);
std::string msg = "Unknown";
if (lua_isstring(L, -1)) {
auto MsgMaybe = lua_tostring(L, -1);
if (MsgMaybe) {
msg = MsgMaybe;
}
}
auto MaybeS = Engine().GetScript(L);
if (MaybeS.has_value()) {
TLuaFile& S = MaybeS.value();
@@ -118,7 +124,7 @@ bool CheckLua(lua_State* L, int r) {
warn(a + " | " + msg);
return false;
}
// What the fuck, what do we do?!
// This should never happen since it's not directly called from "userspace" Lua.
AssertNotReachable();
}
return true;
@@ -127,7 +133,10 @@ bool CheckLua(lua_State* L, int r) {
int lua_RegisterEvent(lua_State* L) {
int Args = lua_gettop(L);
auto MaybeScript = Engine().GetScript(L);
Assert(MaybeScript.has_value());
if (!MaybeScript.has_value()) {
error("RegisterEvent: There is no script associated with this lua_State.");
return 0;
}
TLuaFile& Script = MaybeScript.value();
if (Args == 2 && lua_isstring(L, 1) && lua_isstring(L, 2)) {
Script.RegisterEvent(lua_tostring(L, 1), lua_tostring(L, 2));
@@ -139,7 +148,10 @@ int lua_RegisterEvent(lua_State* L) {
int lua_TriggerEventL(lua_State* L) {
int Args = lua_gettop(L);
auto MaybeScript = Engine().GetScript(L);
Assert(MaybeScript.has_value());
if (!MaybeScript.has_value()) {
error("TriggerEvent: There is no script associated with this lua_State.");
return 0;
}
TLuaFile& Script = MaybeScript.value();
if (Args > 0) {
if (lua_isstring(L, 1)) {
@@ -155,7 +167,10 @@ int lua_TriggerEventL(lua_State* L) {
int lua_TriggerEventG(lua_State* L) {
int Args = lua_gettop(L);
auto MaybeScript = Engine().GetScript(L);
Assert(MaybeScript.has_value());
if (!MaybeScript.has_value()) {
error("TriggerGlobalEvent: There is no script associated with this lua_State.");
return 0;
}
TLuaFile& Script = MaybeScript.value();
if (Args > 0) {
if (lua_isstring(L, 1)) {
@@ -183,7 +198,6 @@ void ExecuteAsync(TLuaFile* lua, const std::string& FuncName) {
}
void CallAsync(TLuaFile* lua, const std::string& Func, int U) {
RegisterThread(lua->GetFileName());
lua->SetStopThread(false);
int D = 1000 / U;
while (!lua->GetStopThread()) {
@@ -194,8 +208,11 @@ void CallAsync(TLuaFile* lua, const std::string& Func, int U) {
int lua_StopThread(lua_State* L) {
auto MaybeScript = Engine().GetScript(L);
Assert(MaybeScript.has_value());
// ugly, but whatever, this is safe as fuck
if (!MaybeScript.has_value()) {
error("StopThread: There is no script associated with this lua_State.");
return 0;
}
// ugly, but whatever, this is very safe
MaybeScript.value().get().SetStopThread(true);
return 0;
}
@@ -209,7 +226,10 @@ int lua_CreateThread(lua_State* L) {
int U = int(lua_tointeger(L, 2));
if (U > 0 && U < 501) {
auto MaybeScript = Engine().GetScript(L);
Assert(MaybeScript.has_value());
if (!MaybeScript.has_value()) {
error("CreateThread: There is no script associated with this lua_State.");
return 0;
}
TLuaFile& Script = MaybeScript.value();
std::thread t1(CallAsync, &Script, STR, U);
t1.detach();
@@ -397,7 +417,9 @@ int lua_sendChat(lua_State* L) {
if (lua_isstring(L, 2)) {
int ID = int(lua_tointeger(L, 1));
if (ID == -1) {
std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2));
auto msg = std::string(lua_tostring(L, 2));
LogChatMessage("<Server> (to everyone) ", -1, msg);
std::string Packet = "C:Server: " + msg;
Engine().Network().SendToAll(nullptr, Packet, true, true);
} else {
auto MaybeClient = GetClient(Engine().Server(), ID);
@@ -405,7 +427,9 @@ int lua_sendChat(lua_State* L) {
auto c = MaybeClient.value().lock();
if (!c->IsSynced())
return 0;
std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2));
auto msg = std::string(lua_tostring(L, 2));
LogChatMessage("<Server> (to \"" + c->GetName() + "\")", -1, msg);
std::string Packet = "C:Server: " + msg;
Engine().Network().Respond(*c, Packet, true);
} else
SendError(Engine(), L, ("SendChatMessage invalid argument [1] invalid ID"));
@@ -604,6 +628,7 @@ int lua_Print(lua_State* L) {
int lua_TempFix(lua_State* L);
void TLuaFile::Init(const std::string& PluginName, const std::string& FileName, fs::file_time_type LastWrote) {
auto Lock = std::unique_lock(mInitMutex);
// set global engine for lua_* functions
if (!TheEngine) {
TheEngine = &mEngine;
@@ -645,7 +670,6 @@ std::string TLuaFile::GetOrigin() {
}
std::any CallFunction(TLuaFile* lua, const std::string& FuncName, std::shared_ptr<TLuaArg> Arg) {
RegisterThread(lua->GetFileName());
lua_State* luaState = lua->GetState();
lua_getglobal(luaState, FuncName.c_str());
if (lua_isfunction(luaState, -1)) {

View File

@@ -20,18 +20,14 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
});
Application::RegisterShutdownHandler([&] {
if (mUDPThread.joinable()) {
debug("shutting down TCPServer");
mShutdown = true;
mUDPThread.detach();
debug("shut down TCPServer");
}
});
Application::RegisterShutdownHandler([&] {
if (mTCPThread.joinable()) {
debug("shutting down TCPServer");
mShutdown = true;
mTCPThread.detach();
debug("shut down TCPServer");
}
});
mTCPThread = std::thread(&TNetwork::TCPServerMain, this);
@@ -280,8 +276,12 @@ void TNetwork::Authentication(SOCKET TCPSock) {
return;
}
auto RequestString = R"({"key":")" + Rc + "\"}";
auto Target = "/pkToUser";
int ResponseCode = -1;
if (!Rc.empty()) {
Rc = Http::POST(Application::GetBackendUrlForAuth(), "/pkToUser", {}, R"({"key":")" + Rc + "\"}", true);
Rc = Http::POST(Application::GetBackendUrlForAuth(), Target, {}, RequestString, true, &ResponseCode);
}
json::Document AuthResponse;
@@ -292,8 +292,23 @@ void TNetwork::Authentication(SOCKET TCPSock) {
}
if (!AuthResponse.IsObject()) {
ClientKick(*Client, "Backend returned invalid auth response format.");
error("Backend returned invalid auth response format. This should never happen.");
if (Rc == "0") {
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("auth",
{ { "response-body", Rc },
{ "key", RequestString } });
Sentry.SetTransaction(Application::GetBackendUrlForAuth() + Target);
Sentry.Log(SentryLevel::Info, "default", "backend returned 0 instead of json (" + std::to_string(ResponseCode) + ")");
} else { // Rc != "0"
ClientKick(*Client, "Backend returned invalid auth response format.");
error("Backend returned invalid auth response format. This should never happen.");
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("auth",
{ { "response-body", Rc },
{ "key", RequestString } });
Sentry.SetTransaction(Application::GetBackendUrlForAuth() + Target);
Sentry.Log(SentryLevel::Error, "default", "unexpected backend response (" + std::to_string(ResponseCode) + ")");
}
return;
}
@@ -312,7 +327,6 @@ void TNetwork::Authentication(SOCKET TCPSock) {
}
debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
debug("There are " + std::to_string(mServer.ClientCount()) + " known clients");
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
std::shared_ptr<TClient> Cl;
{
@@ -322,10 +336,7 @@ void TNetwork::Authentication(SOCKET TCPSock) {
} else
return true;
}
info("Client Iteration: Name -> " + Cl->GetName() + ", Guest -> " + std::to_string(Cl->IsGuest()) + ", Roles -> " + Cl->GetRoles());
if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) {
info("New client matched with current iteration");
info("Old client (" + Cl->GetName() + ") kicked: Reconnecting");
CloseSocketProper(Cl->GetTCPSock());
Cl->SetStatus(-2);
return false;
@@ -403,7 +414,7 @@ bool TNetwork::TCPSend(TClient& c, const std::string& Data, bool IsSync) {
bool TNetwork::CheckBytes(TClient& c, int32_t BytesRcv) {
if (BytesRcv == 0) {
debug("(TCP) Connection closing...");
trace("(TCP) Connection closing...");
if (c.GetStatus() > -1)
c.SetStatus(-1);
return false;
@@ -415,7 +426,6 @@ bool TNetwork::CheckBytes(TClient& c, int32_t BytesRcv) {
#endif // WIN32
if (c.GetStatus() > -1)
c.SetStatus(-1);
info(("Closing socket in CheckBytes, BytesRcv < 0"));
CloseSocketProper(c.GetTCPSock());
return false;
}
@@ -431,22 +441,13 @@ std::string TNetwork::TCPRcv(TClient& c) {
do {
Temp = recv(c.GetTCPSock(), &Data[BytesRcv], 4 - BytesRcv, 0);
if (!CheckBytes(c, Temp)) {
#ifdef DEBUG
error(std::string(__func__) + (": failed on CheckBytes in while(BytesRcv < 4)"));
#endif // DEBUG
return "";
}
BytesRcv += Temp;
} while (size_t(BytesRcv) < sizeof(Header));
memcpy(&Header, &Data[0], sizeof(Header));
#ifdef DEBUG
//debug(std::string(__func__) + (": expecting ") + std::to_string(Header) + (" bytes."));
#endif // DEBUG
if (!CheckBytes(c, BytesRcv)) {
#ifdef DEBUG
error(std::string(__func__) + (": failed on CheckBytes"));
#endif // DEBUG
return "";
}
if (Header < 100 * MB) {
@@ -460,29 +461,15 @@ std::string TNetwork::TCPRcv(TClient& c) {
do {
Temp = recv(c.GetTCPSock(), &Data[BytesRcv], Header - BytesRcv, 0);
if (!CheckBytes(c, Temp)) {
#ifdef DEBUG
error(std::string(__func__) + (": failed on CheckBytes in while(BytesRcv < Header)"));
#endif // DEBUG
return "";
}
#ifdef DEBUG
//debug(std::string(__func__) + (": Temp: ") + std::to_string(Temp) + (", BytesRcv: ") + std::to_string(BytesRcv));
#endif // DEBUG
BytesRcv += Temp;
} while (BytesRcv < Header);
#ifdef DEBUG
//debug(std::string(__func__) + (": finished recv with Temp: ") + std::to_string(Temp) + (", BytesRcv: ") + std::to_string(BytesRcv));
#endif // DEBUG
std::string Ret(Data.data(), Header);
if (Ret.substr(0, 4) == "ABG:") {
Ret = DeComp(Ret.substr(4));
}
#ifdef DEBUG
//debug("Parsing from " + c->GetName() + " -> " +std::to_string(Ret.size()));
#endif
return Ret;
}
@@ -702,19 +689,29 @@ void TNetwork::Parse(TClient& c, const std::string& Packet) {
}
}
void TNetwork::SendFile(TClient& c, const std::string& Name) {
info(c.GetName() + " requesting : " + Name.substr(Name.find_last_of('/')));
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
if (!std::filesystem::exists(Name)) {
if (!fs::path(UnsafeName).has_filename()) {
if (!TCPSend(c, "CO")) {
// TODO: handle
}
warn("File " + Name + " could not be accessed!");
warn("File " + UnsafeName + " is not a file!");
return;
} else {
if (!TCPSend(c, "AG")) {
}
auto FileName = fs::path(UnsafeName).filename().string();
FileName = Application::Settings.Resource + "/Client/" + FileName;
if (!std::filesystem::exists(FileName)) {
if (!TCPSend(c, "CO")) {
// TODO: handle
}
warn("File " + UnsafeName + " could not be accessed!");
return;
}
if (!TCPSend(c, "AG")) {
// TODO: handle
}
///Wait for connections
@@ -731,14 +728,14 @@ void TNetwork::SendFile(TClient& c, const std::string& Name) {
return;
}
size_t Size = size_t(std::filesystem::file_size(Name)), MSize = Size / 2;
size_t Size = size_t(std::filesystem::file_size(FileName)), MSize = Size / 2;
std::thread SplitThreads[2] {
std::thread([&] {
SplitLoad(c, 0, MSize, false, Name);
SplitLoad(c, 0, MSize, false, FileName);
}),
std::thread([&] {
SplitLoad(c, MSize, Size, true, Name);
SplitLoad(c, MSize, Size, true, FileName);
})
};

View File

@@ -7,10 +7,8 @@ TPPSMonitor::TPPSMonitor(TServer& Server)
Application::SetPPS("-");
Application::RegisterShutdownHandler([&] {
if (mThread.joinable()) {
debug("shutting down PPSMonitor");
mShutdown = true;
mThread.join();
debug("shut down PPSMonitor");
}
});
Start();
@@ -44,7 +42,7 @@ void TPPSMonitor::operator()() {
V += c->GetCarCount();
}
// kick on "no ping"
if (c->SecondsSinceLastPing() > (5 * 60)) {
if (c->SecondsSinceLastPing() > (20 * 60)) {
debug("client " + std::string("(") + std::to_string(c->GetID()) + ")" + c->GetName() + " timing out: " + std::to_string(c->SecondsSinceLastPing()) + ", pps: " + Application::PPS());
TimedOutClients.push_back(c);
}
@@ -52,7 +50,7 @@ void TPPSMonitor::operator()() {
return true;
});
for (auto& ClientToKick : TimedOutClients) {
Network().ClientKick(*ClientToKick, "Timeout (no ping for >5 min)");
Network().ClientKick(*ClientToKick, "Timeout (no ping for way too long)");
}
TimedOutClients.clear();
if (C == 0 || mInternalPPS == 0) {

View File

@@ -19,7 +19,7 @@ TResourceManager::TResourceManager() {
++i;
File = File.substr(i,pos-i);
}
mTrimmedList += File + ';';
mTrimmedList += "/" + fs::path(File).filename().string() + ';';
mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';';
mMaxModSize += size_t(fs::file_size(entry.path()));
mModsLoaded++;

138
src/TSentry.cpp Normal file
View File

@@ -0,0 +1,138 @@
#include "TSentry.h"
#include "Common.h"
#include <cstring>
#include <sentry.h>
#include <sstream>
TSentry::TSentry() {
if (std::strlen(S_DSN) == 0) {
mValid = false;
} else {
mValid = true;
sentry_options_t* options = sentry_options_new();
sentry_options_set_dsn(options, S_DSN);
sentry_options_set_debug(options, false); // needs to always be false
sentry_options_set_symbolize_stacktraces(options, true);
auto ReleaseString = "BeamMP-Server@" + Application::ServerVersion();
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) {
if (!Application::Settings.SendErrors) {
mValid = false;
if (Application::Settings.SendErrorsMessageEnabled) {
info("Opted out of error reporting (SendErrors), Sentry disabled.");
} else {
info("Sentry disabled");
}
} else {
if (Application::Settings.SendErrorsMessageEnabled) {
info("Sentry started! Reporting errors automatically. This sends data to the developers in case of errors and crashes. You can learn more, turn this message off or opt-out of this in the ServerConfig.toml.");
} else {
info("Sentry started");
}
}
} else {
if (Application::Settings.SendErrorsMessageEnabled) {
info("Sentry disabled in unofficial build. Automatic error reporting disabled.");
} 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

@@ -7,7 +7,7 @@
#include <any>
#include <sstream>
#undef GetObject //Fixes Windows
#undef GetObject // Fixes Windows
#include "Json.h"
@@ -65,7 +65,7 @@ size_t TServer::ClientCount() const {
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Packet, TPPSMonitor& PPSMonitor, TNetwork& Network) {
if (Packet.find("Zp") != std::string::npos && Packet.size() > 500) {
abort();
//abort();
}
if (Packet.substr(0, 4) == "ABG:") {
Packet = DeComp(Packet.substr(4));
@@ -90,9 +90,7 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
}
switch (Code) {
case 'H': // initial connection
#ifdef DEBUG
debug(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
#endif
trace(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
if (!Network.SyncClient(Client)) {
// TODO handle
}
@@ -114,31 +112,27 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
ParseVehicle(*LockedClient, Packet, Network);
return;
case 'J':
#ifdef DEBUG
debug(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Network.SendToAll(LockedClient.get(), Packet, false, true);
return;
case 'C':
#ifdef DEBUG
debug(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos)
break;
Res = TriggerLuaEvent("onChatMessage", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 1) } }), true);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1)); // FIXME: this needs to be adjusted once lua is merged
if (std::any_cast<int>(Res))
break;
Network.SendToAll(nullptr, Packet, true, true);
return;
case 'E':
#ifdef DEBUG
debug(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
HandleEvent(*LockedClient, Packet);
return;
case 'N':
debug("got 'N' packet (" + std::to_string(Packet.size()) + ")");
trace("got 'N' packet (" + std::to_string(Packet.size()) + ")");
Network.SendToAll(LockedClient.get(), Packet, false, true);
return;
default:
return;
}
@@ -198,9 +192,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
std::string Data = Packet.substr(3), pid, vid;
switch (Code) { //Spawned Destroyed Switched/Moved NotFound Reset
case 's':
#ifdef DEBUG
debug(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
if (Data.at(0) == '0') {
int CarID = c.GetOpenCarID();
debug(c.GetName() + (" created a car with ID ") + std::to_string(CarID));
@@ -225,9 +217,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
return;
case 'c':
#ifdef DEBUG
debug(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
pid = Data.substr(0, Data.find('-'));
vid = Data.substr(Data.find('-') + 1, Data.find(':', 1) - Data.find('-') - 1);
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
@@ -258,9 +248,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
return;
case 'd':
#ifdef DEBUG
debug(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
pid = Data.substr(0, Data.find('-'));
vid = Data.substr(Data.find('-') + 1);
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
@@ -279,9 +267,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
return;
case 'r':
#ifdef DEBUG
debug(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Pos = int(Data.find('-'));
pid = Data.substr(0, Pos++);
vid = Data.substr(Pos, Data.find(':') - Pos);
@@ -300,15 +286,11 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
return;
case 't':
#ifdef DEBUG
debug(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
#endif
trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Network.SendToAll(&c, Packet, false, true);
return;
default:
#ifdef DEBUG
warn(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
#endif // DEBUG
trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
return;
}
}
@@ -323,12 +305,22 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
std::string VD = c.GetCarData(VID);
if (VD.empty()) {
error("Tried to apply change to vehicle that does not exist");
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("vehicle-change",
{ { "packet", Packet },
{ "vehicle-id", std::to_string(VID) },
{ "client-car-count", std::to_string(c.GetCarCount()) } });
Sentry.LogError("attempt to apply change to nonexistent vehicle", _file_basename, _line);
return;
}
std::string Header = VD.substr(0, VD.find('{'));
FoundPos = VD.find('{');
if (FoundPos == std::string::npos) {
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("vehicle-change-packet",
{ { "packet", VD } });
Sentry.LogError("malformed packet", _file_basename, _line);
error("Malformed packet received, no '{' found");
return;
}

View File

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

View File

@@ -1,5 +1,9 @@
#include "TSentry.h"
#include "Common.h"
#include "CustomAssert.h"
#include "Http.h"
#include "SignalHandling.h"
#include "TConfig.h"
#include "THeartbeatThread.h"
#include "TLuaEngine.h"
@@ -7,45 +11,19 @@
#include "TPPSMonitor.h"
#include "TResourceManager.h"
#include "TServer.h"
#include <iostream>
#include <thread>
#ifdef __unix
#include <csignal>
void UnixSignalHandler(int sig) {
switch (sig) {
case SIGPIPE:
warn("ignoring SIGPIPE");
break;
case SIGTERM:
info("gracefully shutting down via SIGTERM");
Application::GracefullyShutdown();
break;
case SIGINT:
info("gracefully shutting down via SIGINT");
Application::GracefullyShutdown();
break;
default:
debug("unhandled signal: " + std::to_string(sig));
break;
}
}
#endif // __unix
int main(int argc, char** argv) {
#ifdef __unix
#if DEBUG
info("registering handlers for SIGINT, SIGTERM, SIGPIPE");
#endif // DEBUG
signal(SIGPIPE, UnixSignalHandler);
signal(SIGTERM, UnixSignalHandler);
#ifndef DEBUG
signal(SIGINT, UnixSignalHandler);
#endif // DEBUG
#endif // __unix
// 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 {};
int main(int argc, char** argv) try {
setlocale(LC_ALL, "C");
SetupSignalHandlers();
bool Shutdown = false;
Application::RegisterShutdownHandler([&Shutdown] { Shutdown = true; });
@@ -54,11 +32,19 @@ int main(int argc, char** argv) {
if (Config.Failed()) {
info("Closing in 10 seconds");
std::this_thread::sleep_for(std::chrono::seconds(10));
// loop to make it possible to ctrl+c instead
for (size_t i = 0; i < 20; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return 1;
}
RegisterThread("Main");
trace("Running in debug mode on a debug build");
Sentry.SetupUser();
Sentry.PrintWelcome();
TResourceManager ResourceManager;
TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server);
@@ -66,9 +52,14 @@ int main(int argc, char** argv) {
TLuaEngine LuaEngine(Server, Network);
PPSMonitor.SetNetwork(Network);
Application::Console().InitializeLuaConsole(LuaEngine);
Application::CheckForUpdates();
// TODO: replace
while (!Shutdown) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
info("Shutdown.");
} catch (const std::exception& e) {
error(e.what());
Sentry.LogException(e, _file_basename, _line);
}