From b874f599632047608bc65c8360ca20b5274c87b8 Mon Sep 17 00:00:00 2001 From: "Nathan J." Date: Thu, 25 Jul 2024 20:07:59 +0200 Subject: [PATCH 1/7] Adding lua --- vcpkg.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index 85b9cb23..2d653eff 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -14,6 +14,7 @@ "openssl", "rapidjson", "sol2", - "toml11" + "toml11", + "lua" ] } From 34d39e9abb6c59a7d8fff3c94623ea53d4e284e2 Mon Sep 17 00:00:00 2001 From: "Nathan J." Date: Wed, 14 Aug 2024 13:59:35 +0200 Subject: [PATCH 2/7] comments --- include/ArgsParser.h | 10 ++++++++++ src/ArgsParser.cpp | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/ArgsParser.h b/include/ArgsParser.h index 75ad4725..aa8a0c77 100644 --- a/include/ArgsParser.h +++ b/include/ArgsParser.h @@ -39,6 +39,9 @@ class ArgsParser { ArgsParser() = default; + /** + * Parse all arguments provided, if they have been register. + */ void Parse(const std::vector& ArgList); // prints errors if any errors occurred, in that case also returns false bool Verify(); @@ -48,8 +51,15 @@ class ArgsParser { std::optional GetValueOfArgument(const std::vector& Names); private: + + /** + * Register an argument with a value. + */ void ConsumeLongAssignment(const std::string& Arg); void ConsumeLongFlag(const std::string& Arg); + /** + * return if the argument asked has been registered previously. + */ bool IsRegistered(const std::string& Name); struct Argument { diff --git a/src/ArgsParser.cpp b/src/ArgsParser.cpp index c3893c0b..13538e24 100644 --- a/src/ArgsParser.cpp +++ b/src/ArgsParser.cpp @@ -24,7 +24,7 @@ void ArgsParser::Parse(const std::vector& ArgList) { for (const auto& Arg : ArgList) { if (Arg.size() > 2 && Arg.substr(0, 2) == "--") { - // long arg + // Arg with value if (Arg.find("=") != Arg.npos) { ConsumeLongAssignment(std::string(Arg)); } else { From 7527bee9822b1503596fd1ceab2cce4b5dc2bd1b Mon Sep 17 00:00:00 2001 From: "Nathan J." Date: Wed, 14 Aug 2024 14:02:54 +0200 Subject: [PATCH 3/7] added ipv6 support Added IPv6 support, added a new arg for the server's config to handle custom IP to listen to --- include/Settings.h | 1 + src/Settings.cpp | 2 ++ src/TConfig.cpp | 5 +++++ src/main.cpp | 24 +++++++++++++++++++++++- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/Settings.h b/include/Settings.h index 1ef8ea12..97013321 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -81,6 +81,7 @@ struct Settings { General_Map, General_AuthKey, General_Private, + General_Ip, General_Port, General_MaxCars, General_LogChat, diff --git a/src/Settings.cpp b/src/Settings.cpp index a82a66b8..a9a05a94 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -28,6 +28,7 @@ Settings::Settings() { { General_Map, std::string("/levels/gridmap_v2/info.json") }, { General_AuthKey, std::string("") }, { General_Private, true }, + { General_Ip, "0.0.0.0" }, { General_Port, 30814 }, { General_MaxCars, 1 }, { General_LogChat, true }, @@ -48,6 +49,7 @@ Settings::Settings() { { { "General", "Map" }, { General_Map, READ_WRITE } }, { { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } }, { { "General", "Private" }, { General_Private, READ_ONLY } }, + { { "General", "Ip" }, { General_Ip, READ_ONLY } }, { { "General", "Port" }, { General_Port, READ_ONLY } }, { { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } }, { { "General", "LogChat" }, { General_LogChat, READ_ONLY } }, diff --git a/src/TConfig.cpp b/src/TConfig.cpp index 95044a73..99027f86 100644 --- a/src/TConfig.cpp +++ b/src/TConfig.cpp @@ -34,6 +34,8 @@ static constexpr std::string_view StrDebug = "Debug"; static constexpr std::string_view EnvStrDebug = "BEAMMP_DEBUG"; static constexpr std::string_view StrPrivate = "Private"; static constexpr std::string_view EnvStrPrivate = "BEAMMP_PRIVATE"; +static constexpr std::string_view StrIp = "Ip"; +static constexpr std::string_view EnvStrIp = "BEAMMP_IP"; static constexpr std::string_view StrPort = "Port"; static constexpr std::string_view EnvStrPort = "BEAMMP_PORT"; static constexpr std::string_view StrMaxCars = "MaxCars"; @@ -134,6 +136,7 @@ void TConfig::FlushToFile() { data["General"][StrPrivate.data()] = Application::Settings.getAsBool(Settings::Key::General_Private); data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests); SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests"); + data["General"][StrIp.data()] = Application::Settings.getAsString(Settings::Key::General_Ip); data["General"][StrPort.data()] = Application::Settings.getAsInt(Settings::Key::General_Port); data["General"][StrName.data()] = Application::Settings.getAsString(Settings::Key::General_Name); SetComment(data["General"][StrTags.data()].comments(), " Add custom identifying tags to your server to make it easier to find. Format should be TagA,TagB,TagC. Note the comma seperation."); @@ -248,6 +251,7 @@ void TConfig::ParseFromFile(std::string_view name) { // Read into new Settings Singleton TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug); TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private); + TryReadValue(data, "General", StrIp, EnvStrIp, Settings::Key::General_Ip); TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port); TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Settings::Key::General_MaxCars); TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers); @@ -299,6 +303,7 @@ void TConfig::PrintDebug() { } beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Debug) ? "true" : "false")); beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false")); + beammp_debug(std::string(StrIp) + ": " + Application::Settings.getAsString(Settings::Key::General_Ip)); beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port))); beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars))); beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers))); diff --git a/src/main.cpp b/src/main.cpp index 2b36e5af..7f96d02c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,9 +42,12 @@ static const std::string sCommandlineArguments = R"( ARGUMENTS: --help Displays this help and exits. + --ip= + Sets the server's ip to listen on. + Overrides ENV and ServerConfig value. --port=1234 Sets the server's listening TCP and - UDP port. Overrides ENV and ServerConfig. + UDP port. Overrides ENV and ServerConfig value. --config=/path/to/ServerConfig.toml Absolute or relative path to the Server Config file, including the @@ -74,6 +77,7 @@ struct MainArguments { int BeamMPServerMain(MainArguments Arguments); int main(int argc, char** argv) { + MainArguments Args { argc, argv, {}, argv[0] }; Args.List.reserve(size_t(argc)); for (int i = 1; i < argc; ++i) { @@ -96,6 +100,7 @@ int BeamMPServerMain(MainArguments Arguments) { Parser.RegisterArgument({ "help" }, ArgsParser::NONE); Parser.RegisterArgument({ "version" }, ArgsParser::NONE); Parser.RegisterArgument({ "config" }, ArgsParser::HAS_VALUE); + Parser.RegisterArgument({ "ip" }, ArgsParser::HAS_VALUE); Parser.RegisterArgument({ "port" }, ArgsParser::HAS_VALUE); Parser.RegisterArgument({ "working-directory" }, ArgsParser::HAS_VALUE); Parser.Parse(Arguments.List); @@ -142,6 +147,21 @@ int BeamMPServerMain(MainArguments Arguments) { return 1; } + // override default IP (0.0.0.0) if provided via arguments + if (Parser.FoundArgument({ "ip" })) { + auto ip = Parser.GetValueOfArgument({ "ip" }); + if (ip.has_value()) { + const std::regex patternIPv4IPv6(R"(((^\h*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\h*(|/([0-9]|[1-2][0-9]|3[0-2]))$)|(^\h*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\h*(|/([0-9]|[0-9][0-9]|1[0-1][0-9]|12[0-8]))$)))"); + if (std::regex_match(ip.value(), patternIPv4IPv6)) { + beammp_errorf("Custom ip requested via --ip is invalid: '{}'", ip.value()); + return 1; + } else { + Application::Settings.set(Settings::Key::General_Ip, ip.value()); + beammp_info("Custom ip requested via commandline arguments: " + ip.value()); + } + } + } + // override port if provided via arguments if (Parser.FoundArgument({ "port" })) { auto Port = Parser.GetValueOfArgument({ "port" }); @@ -206,6 +226,8 @@ int BeamMPServerMain(MainArguments Arguments) { "UpdateCheck" // Ignore as not to confuse users (non-vital system) }; + + bool FullyStarted = false; while (!Shutdown) { if (!FullyStarted) { From 1d6ca8a38c4db7f65517bfd8d356917e241dd6c3 Mon Sep 17 00:00:00 2001 From: "Nathan J." Date: Wed, 14 Aug 2024 14:04:04 +0200 Subject: [PATCH 4/7] TCP & UDP ipv6 support --- src/TNetwork.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index ddecf69f..29c6b266 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -80,8 +80,16 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R void TNetwork::UDPServerMain() { RegisterThread("UDPServer"); - ip::udp::endpoint UdpListenEndpoint(ip::address::from_string("0.0.0.0"), Application::Settings.getAsInt(Settings::Key::General_Port)); + boost::system::error_code ec; + + auto add = ip::make_address(Application::Settings.getAsString(Settings::Key::General_Ip), ec); + if (ec) { + beammp_errorf("Invalid address: {}", ec.message()); + return; + } + + ip::udp::endpoint UdpListenEndpoint(add, Application::Settings.getAsInt(Settings::Key::General_Port)); mUDPSock.open(UdpListenEndpoint.protocol(), ec); if (ec) { beammp_error("open() failed: " + ec.message()); @@ -95,7 +103,8 @@ void TNetwork::UDPServerMain() { Application::GracefullyShutdown(); } Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good); - beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) + (" with a Max of ") + beammp_info(("Vehicle data network online on ") + Application::Settings.getAsString(Settings::Key::General_Ip) + + " on port " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) + (" with a Max of ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients")); while (!Application::IsShuttingDown()) { try { @@ -133,9 +142,16 @@ void TNetwork::UDPServerMain() { void TNetwork::TCPServerMain() { RegisterThread("TCPServer"); - ip::tcp::endpoint ListenEp(ip::address::from_string("0.0.0.0"), Application::Settings.getAsInt(Settings::Key::General_Port)); - ip::tcp::socket Listener(mServer.IoCtx()); boost::system::error_code ec; + + auto add = ip::make_address(Application::Settings.getAsString(Settings::Key::General_Ip), ec); + if (ec) { + beammp_errorf("Invalid address: {}", ec.message()); + return; + } + ip::tcp::endpoint ListenEp(add, Application::Settings.getAsInt(Settings::Key::General_Port)); + ip::tcp::socket Listener(mServer.IoCtx()); + Listener.open(ListenEp.protocol(), ec); if (ec) { beammp_errorf("Failed to open socket: {}", ec.message()); From 563e3d8a5143978997541f8536d73622478813a0 Mon Sep 17 00:00:00 2001 From: "Nathan J." Date: Wed, 14 Aug 2024 14:06:54 +0200 Subject: [PATCH 5/7] small correct When shutting down the server, invalide move on an invalide socket cause crash --- src/TNetwork.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index 29c6b266..c42798c7 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -183,13 +183,19 @@ void TNetwork::TCPServerMain() { break; } ip::tcp::endpoint ClientEp; + //Wait to a client ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec); if (ec) { - beammp_errorf("failed to accept: {}", ec.message()); + if (ec == boost::asio::error::interrupted) { + continue; + }else + beammp_errorf("failed to accept: {}", ec.message()); + } else { + TConnection Conn { std::move(ClientSocket), ClientEp }; + std::thread ID(&TNetwork::Identify, this, std::move(Conn)); + ID.detach(); // TODO: Add to a queue and attempt to join periodically } - TConnection Conn { std::move(ClientSocket), ClientEp }; - std::thread ID(&TNetwork::Identify, this, std::move(Conn)); - ID.detach(); // TODO: Add to a queue and attempt to join periodically + } catch (const std::exception& e) { beammp_error("fatal: " + std::string(e.what())); } From 9edca9642ab7746499b60eb16a81a67437812646 Mon Sep 17 00:00:00 2001 From: "Nathan J." Date: Wed, 14 Aug 2024 15:02:14 +0200 Subject: [PATCH 6/7] sendind IP champ to beammp backend Now the server post the IP to client (as backend beammp) on his heartbeat. --- include/TServer.h | 1 - src/Http.cpp | 6 ++++-- src/THeartbeatThread.cpp | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/TServer.h b/include/TServer.h index 1ff36c6c..6ee0cb68 100644 --- a/include/TServer.h +++ b/include/TServer.h @@ -40,7 +40,6 @@ class TServer final { void InsertClient(const std::shared_ptr& Ptr); void RemoveClient(const std::weak_ptr&); - // in Fn, return true to continue, return false to break void ForEachClient(const std::function)>& Fn); size_t ClientCount() const; diff --git a/src/Http.cpp b/src/Http.cpp index 02a92136..677a8494 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -29,6 +29,7 @@ #include using json = nlohmann::json; + struct Connection { std::string host {}; int port {}; @@ -37,6 +38,7 @@ struct Connection { : host(host) , port(port) {}; }; + constexpr uint8_t CONNECTION_AMOUNT = 10; static thread_local uint8_t write_index = 0; static thread_local std::array connections; @@ -62,7 +64,7 @@ static thread_local std::array, CONNECTION_A std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) { std::shared_ptr client = getClient({ host, port }); client->enable_server_certificate_verification(false); - client->set_address_family(AF_INET); + client->set_address_family(AF_UNSPEC); auto res = client->Get(target.c_str()); if (res) { if (status) { @@ -79,7 +81,7 @@ std::string Http::POST(const std::string& host, int port, const std::string& tar client->set_read_timeout(std::chrono::seconds(10)); beammp_assert(client->is_valid()); client->enable_server_certificate_verification(false); - client->set_address_family(AF_INET); + client->set_address_family(AF_UNSPEC); auto res = client->Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str()); if (res) { if (status) { diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 849b3212..16ccade5 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -142,6 +142,7 @@ std::string THeartbeatThread::GenerateCall() { Ret << "uuid=" << Application::Settings.getAsString(Settings::Key::General_AuthKey) << "&players=" << mServer.ClientCount() << "&maxplayers=" << Application::Settings.getAsInt(Settings::Key::General_MaxPlayers) + << "&ip=" << Application::Settings.getAsString(Settings::Key::General_Ip) // TODO Add on the website the usage of this info << "&port=" << Application::Settings.getAsInt(Settings::Key::General_Port) << "&map=" << Application::Settings.getAsString(Settings::Key::General_Map) << "&private=" << (Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false") From 7d3825c2355a49b516d36fc0b1113bd8216de03d Mon Sep 17 00:00:00 2001 From: "Nathan J." <51249490+Gibus21250@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:15:33 +0200 Subject: [PATCH 7/7] Update linux.yml --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a560375d..8d552c6b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -6,6 +6,7 @@ on: - 'develop' - 'minor' pull_request: + workflow_dispatch: env: VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"