From 769c19b8112e69c2043571004b0ffd111a3b21c2 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Wed, 13 Jan 2021 21:34:09 +0100 Subject: [PATCH 01/15] possibly fixes segfault issue on connect --- src/Network/Http.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 7a605f9..7a98050 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -30,7 +30,7 @@ static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* use return size * nmemb; } std::string HttpRequest(const std::string& IP, int port) { - static thread_local CurlManager M; + CurlManager M; std::string readBuffer; CURL* curl = M.Get(); CURLcode res; @@ -49,9 +49,9 @@ std::string HttpRequest(const std::string& IP, int port) { std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json) { auto header = curl_slist { (char*)"Content-Type: application/json" }; - static thread_local CurlManager M; static std::mutex Lock; std::scoped_lock Guard(Lock); + CurlManager M; CURL* curl = M.Get(); CURLcode res; std::string readBuffer; From c387cc36101ba00bd900edcb5124f2c66bd9021c Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Wed, 13 Jan 2021 21:45:26 +0100 Subject: [PATCH 02/15] possible hotfix? --- src/Network/Http.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 7a98050..4823b32 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -48,13 +48,14 @@ std::string HttpRequest(const std::string& IP, int port) { } std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json) { - auto header = curl_slist { (char*)"Content-Type: application/json" }; + auto header = curl_slist { (char*)"Content-Type: application/json", nullptr }; static std::mutex Lock; std::scoped_lock Guard(Lock); CurlManager M; CURL* curl = M.Get(); CURLcode res; std::string readBuffer; + readBuffer.resize(1000, 0); Assert(curl); if (curl) { @@ -64,7 +65,7 @@ std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer[0]); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); res = curl_easy_perform(curl); From 2571cb147856cb3b860069082c2f3b97e3bbc4c8 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Wed, 13 Jan 2021 21:51:07 +0100 Subject: [PATCH 03/15] possible fix no 2 --- src/Network/Http.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 4823b32..426850b 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -54,9 +54,8 @@ std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json CurlManager M; CURL* curl = M.Get(); CURLcode res; - std::string readBuffer; - readBuffer.resize(1000, 0); - + char readBuffer[5000]; + Assert(curl); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, IP.c_str()); @@ -65,7 +64,7 @@ std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer[0]); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); res = curl_easy_perform(curl); From 4e1d2a7dddbff054bc2712343da7499006cf2a52 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Wed, 13 Jan 2021 21:57:13 +0100 Subject: [PATCH 04/15] try once more --- src/Network/Http.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 426850b..748d66c 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -10,25 +10,27 @@ #include #include -class CurlManager{ +class CurlManager { public: - CurlManager(){ + CurlManager() { curl = curl_easy_init(); } - ~CurlManager(){ + ~CurlManager() { curl_easy_cleanup(curl); } - inline CURL* Get(){ + inline CURL* Get() { return curl; } + private: - CURL *curl; + CURL* curl; }; static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { - ((std::string*)userp)->append((char*)contents, size * nmemb); + std::string((char*)userp).append((char*)contents, size * nmemb); return size * nmemb; } + std::string HttpRequest(const std::string& IP, int port) { CurlManager M; std::string readBuffer; @@ -55,7 +57,7 @@ std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json CURL* curl = M.Get(); CURLcode res; char readBuffer[5000]; - + Assert(curl); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, IP.c_str()); From d394d7b5a6128a48271f1684f8cdd799f5d53c3e Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Wed, 13 Jan 2021 22:39:45 +0100 Subject: [PATCH 05/15] use boost beast instead of curl --- CMakeLists.txt | 2 +- include/Curl/Http.h | 4 +- src/Init/Heartbeat.cpp | 10 ++- src/Network/Auth.cpp | 3 +- src/Network/Http.cpp | 149 +++++++++++++++++++++++++---------------- 5 files changed, 99 insertions(+), 69 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3452086..70ecc49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ endif () file(GLOB source_files "src/*.cpp" "include/*.h" "include/*/*.h" "include/*/*/*.h" "include/*.hpp" "include/*/*.hpp" "src/*/*.cpp") add_executable(BeamMP-Server ${source_files}) -target_include_directories(BeamMP-Server PUBLIC $) +target_include_directories(BeamMP-Server PUBLIC $ cpp-httplib) find_package(Lua REQUIRED) target_include_directories(BeamMP-Server PUBLIC ${LUA_INCLUDE_DIR}) diff --git a/include/Curl/Http.h b/include/Curl/Http.h index b11a88a..46744b7 100644 --- a/include/Curl/Http.h +++ b/include/Curl/Http.h @@ -7,5 +7,5 @@ /// #pragma once #include -std::string HttpRequest(const std::string& IP, int port); -std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json); \ No newline at end of file +std::string HttpRequest(const std::string& host, int port, const std::string& target); +std::string PostHTTP(const std::string& host, int port, const std::string& target, const std::string& Fields, bool json); diff --git a/src/Init/Heartbeat.cpp b/src/Init/Heartbeat.cpp index 99cd88c..0adde69 100644 --- a/src/Init/Heartbeat.cpp +++ b/src/Init/Heartbeat.cpp @@ -36,8 +36,8 @@ std::string GenerateCall() { << "&desc=" << ServerDesc; return Ret.str(); } -std::string RunPromise(const std::string& IP, const std::string& R) { - std::packaged_task task([&] { return PostHTTP(IP, R, false); }); +std::string RunPromise(const std::string& host, const std::string& target, const std::string& R) { + std::packaged_task task([&] { return PostHTTP(host, 443, target, R, false); }); std::future f1 = task.get_future(); std::thread t(std::move(task)); t.detach(); @@ -56,14 +56,12 @@ std::string RunPromise(const std::string& IP, const std::string& R) { R = GenerateCall(); if (!CustomIP.empty()) R += "&ip=" + CustomIP; - std::string link = "https://beammp.com/heartbeatv2"; - T = RunPromise(link, R); + T = RunPromise("https://beammp.com", "/heartbeatv2", R); if (T.substr(0, 2) != "20") { //Backend system refused server startup! std::this_thread::sleep_for(std::chrono::seconds(10)); - std::string Backup = "https://backup1.beammp.com/heartbeatv2"; - T = RunPromise(Backup, R); + T = RunPromise("https://backup1.beammp.com", "/heartbeatv2", R); if (T.substr(0, 2) != "20") { warn("Backend system refused server! Server might not show in the public list"); } diff --git a/src/Network/Auth.cpp b/src/Network/Auth.cpp index e1f436c..bab6ac7 100644 --- a/src/Network/Auth.cpp +++ b/src/Network/Auth.cpp @@ -20,8 +20,7 @@ std::string GetClientInfo(const std::string& PK) { if (!PK.empty()) { - return PostHTTP("https://auth.beammp.com/pkToUser", R"({"key":")" + PK + "\"}", true); - ; + return PostHTTP("https://auth.beammp.com", 443, "/pkToUser", R"({"key":")" + PK + "\"}", true); } return ""; } diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 748d66c..0296346 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -7,71 +7,104 @@ /// #include "CustomAssert.h" -#include #include -class CurlManager { -public: - CurlManager() { - curl = curl_easy_init(); - } - ~CurlManager() { - curl_easy_cleanup(curl); - } - inline CURL* Get() { - return curl; - } +#include +#include +#include +#include +#include +#include +#include +#include +#include -private: - CURL* curl; -}; +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace net = boost::asio; // from +using tcp = net::ip::tcp; // from -static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { - std::string((char*)userp).append((char*)contents, size * nmemb); - return size * nmemb; +// UNUSED?! +std::string HttpRequest(const std::string& host, int port, const std::string& target) { + 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); + + http::request req { http::verb::get, target, 11 /* http 1.1 */ }; + + req.set(http::field::host, host); + // tell the server what we are (boost beast) + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + http::write(stream, req); + + // used for reading + beast::flat_buffer buffer; + http::response response; + + http::read(stream, buffer, response); + + 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 + } + + return result; + + } catch (const std::exception& e) { + error(e.what()); + return "-1"; + } } -std::string HttpRequest(const std::string& IP, int port) { - CurlManager M; - std::string readBuffer; - CURL* curl = M.Get(); - CURLcode res; - Assert(curl); - if (curl) { - curl_easy_setopt(curl, CURLOPT_URL, IP.c_str()); - curl_easy_setopt(curl, CURLOPT_PORT, port); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - res = curl_easy_perform(curl); - if (res != CURLE_OK) - return "-1"; - } - return readBuffer; -} +std::string PostHTTP(const std::string& host, int port, const std::string& target, const std::string& Fields, bool json) { + 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); -std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json) { - auto header = curl_slist { (char*)"Content-Type: application/json", nullptr }; - static std::mutex Lock; - std::scoped_lock Guard(Lock); - CurlManager M; - CURL* curl = M.Get(); - CURLcode res; - char readBuffer[5000]; + http::request req { http::verb::post, target, 11 /* http 1.1 */ }; - Assert(curl); - if (curl) { - curl_easy_setopt(curl, CURLOPT_URL, IP.c_str()); - if (json) - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); - res = curl_easy_perform(curl); - if (res != CURLE_OK) - return "-1"; + req.set(http::field::host, host); + if (json) { + req.set(http::field::content_type, "application/json"); + } + req.set(http::field::content_length, boost::lexical_cast(Fields.size())); + req.set(http::field::body, Fields.c_str()); + // tell the server what we are (boost beast) + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + req.prepare_payload(); + + stream.expires_after(std::chrono::seconds(5)); + + http::write(stream, req); + + // used for reading + beast::flat_buffer buffer; + http::response response; + + http::read(stream, buffer, response); + + 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 + } + + return result; + + } catch (const std::exception& e) { + error(e.what()); + return "-1"; } - return readBuffer; } From c75acbff76811d25288e3b3c515cb5e6fcf063c3 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Wed, 13 Jan 2021 22:41:38 +0100 Subject: [PATCH 06/15] fix hostnames --- src/Init/Heartbeat.cpp | 4 ++-- src/Network/Auth.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Init/Heartbeat.cpp b/src/Init/Heartbeat.cpp index 0adde69..fb38486 100644 --- a/src/Init/Heartbeat.cpp +++ b/src/Init/Heartbeat.cpp @@ -56,12 +56,12 @@ std::string RunPromise(const std::string& host, const std::string& target, const R = GenerateCall(); if (!CustomIP.empty()) R += "&ip=" + CustomIP; - T = RunPromise("https://beammp.com", "/heartbeatv2", R); + T = RunPromise("beammp.com", "/heartbeatv2", R); if (T.substr(0, 2) != "20") { //Backend system refused server startup! std::this_thread::sleep_for(std::chrono::seconds(10)); - T = RunPromise("https://backup1.beammp.com", "/heartbeatv2", R); + T = RunPromise("backup1.beammp.com", "/heartbeatv2", R); if (T.substr(0, 2) != "20") { warn("Backend system refused server! Server might not show in the public list"); } diff --git a/src/Network/Auth.cpp b/src/Network/Auth.cpp index bab6ac7..8c5b18c 100644 --- a/src/Network/Auth.cpp +++ b/src/Network/Auth.cpp @@ -20,7 +20,7 @@ std::string GetClientInfo(const std::string& PK) { if (!PK.empty()) { - return PostHTTP("https://auth.beammp.com", 443, "/pkToUser", R"({"key":")" + PK + "\"}", true); + return PostHTTP("auth.beammp.com", 443, "/pkToUser", R"({"key":")" + PK + "\"}", true); } return ""; } From f6121704dfb3bed0c40e883a305d66229b422f4d Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Thu, 14 Jan 2021 00:17:47 +0100 Subject: [PATCH 07/15] implement SSL POST --- CMakeLists.txt | 4 +- include/Curl/Http.h | 3 +- src/Init/Heartbeat.cpp | 43 ++++++++++++--------- src/Network/Auth.cpp | 2 +- src/Network/Http.cpp | 84 +++++++++++++++++++++++++++--------------- 5 files changed, 86 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70ecc49..98d287f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,13 +25,13 @@ endif () file(GLOB source_files "src/*.cpp" "include/*.h" "include/*/*.h" "include/*/*/*.h" "include/*.hpp" "include/*/*.hpp" "src/*/*.cpp") add_executable(BeamMP-Server ${source_files}) -target_include_directories(BeamMP-Server PUBLIC $ cpp-httplib) +target_include_directories(BeamMP-Server PUBLIC $) find_package(Lua REQUIRED) target_include_directories(BeamMP-Server PUBLIC ${LUA_INCLUDE_DIR}) if (UNIX) - target_link_libraries(BeamMP-Server z pthread stdc++fs ${Boost_LINK_DIRS} ${LUA_LIBRARIES} curl dl) + target_link_libraries(BeamMP-Server z pthread stdc++fs ${Boost_LINK_DIRS} ${LUA_LIBRARIES} curl dl crypto ssl) elseif (WIN32) include(FindLua) find_package(ZLIB REQUIRED) diff --git a/include/Curl/Http.h b/include/Curl/Http.h index 46744b7..714543d 100644 --- a/include/Curl/Http.h +++ b/include/Curl/Http.h @@ -7,5 +7,6 @@ /// #pragma once #include +#include std::string HttpRequest(const std::string& host, int port, const std::string& target); -std::string PostHTTP(const std::string& host, int port, const std::string& target, const std::string& Fields, bool json); +std::string PostHTTP(const std::string& host, const std::string& target, const std::unordered_map& fields, const std::string& body, bool json); diff --git a/src/Init/Heartbeat.cpp b/src/Init/Heartbeat.cpp index fb38486..080b7c8 100644 --- a/src/Init/Heartbeat.cpp +++ b/src/Init/Heartbeat.cpp @@ -13,6 +13,7 @@ #include #include #include +#include void WebsocketInit(); std::string GetPlayers() { @@ -24,20 +25,25 @@ std::string GetPlayers() { } return Return; } -std::string GenerateCall() { - std::stringstream Ret; - Ret << "uuid=" << Key << "&players=" << CI->Size() - << "&maxplayers=" << MaxPlayers << "&port=" << Port - << "&map=" << MapName << "&private=" << (Private ? "true" : "false") - << "&version=" << GetSVer() << "&clientversion=" << GetCVer() - << "&name=" << ServerName << "&pps=" << StatReport - << "&modlist=" << FileList << "&modstotalsize=" << MaxModSize - << "&modstotal=" << ModsLoaded << "&playerslist=" << GetPlayers() - << "&desc=" << ServerDesc; - return Ret.str(); +std::unordered_map GenerateCall() { + return { { "uuid", Key }, + { "players", std::to_string(CI->Size()) }, + { "maxplayers", std::to_string(MaxPlayers) }, + { "port", std::to_string(Port) }, + { "map", MapName }, + { "private", (Private ? "true" : "false") }, + { "version", GetSVer() }, + { "clientversion", GetCVer() }, + { "name", ServerName }, + { "pps", StatReport }, + { "modlist", FileList }, + { "modstotalsize", std::to_string(MaxModSize) }, + { "modstotal", std::to_string(ModsLoaded) }, + { "playerslist", GetPlayers() }, + { "desc", ServerDesc } }; } -std::string RunPromise(const std::string& host, const std::string& target, const std::string& R) { - std::packaged_task task([&] { return PostHTTP(host, 443, target, R, false); }); +std::string RunPromise(const std::string& host, const std::string& target, const std::unordered_map& R) { + std::packaged_task task([&] { return PostHTTP(host, target, R, "", false); }); std::future f1 = task.get_future(); std::thread t(std::move(task)); t.detach(); @@ -50,12 +56,13 @@ std::string RunPromise(const std::string& host, const std::string& target, const [[noreturn]] void Heartbeat() { DebugPrintTID(); - std::string R, T; + std::unordered_map R; + std::string T; bool isAuth = false; while (true) { R = GenerateCall(); if (!CustomIP.empty()) - R += "&ip=" + CustomIP; + R.insert({ "ip", CustomIP }); T = RunPromise("beammp.com", "/heartbeatv2", R); if (T.substr(0, 2) != "20") { @@ -69,8 +76,10 @@ std::string RunPromise(const std::string& host, const std::string& target, const //Server Authenticated if (!isAuth) { WebsocketInit(); - if (T.length() == 4)info(("Authenticated!")); - else info(("Resumed authenticated session!")); + if (T.length() == 4) + info(("Authenticated!")); + else + info(("Resumed authenticated session!")); isAuth = true; } //std::this_thread::sleep_for(std::chrono::seconds(5)); diff --git a/src/Network/Auth.cpp b/src/Network/Auth.cpp index 8c5b18c..570e774 100644 --- a/src/Network/Auth.cpp +++ b/src/Network/Auth.cpp @@ -20,7 +20,7 @@ std::string GetClientInfo(const std::string& PK) { if (!PK.empty()) { - return PostHTTP("auth.beammp.com", 443, "/pkToUser", R"({"key":")" + PK + "\"}", true); + return PostHTTP("auth.beammp.com", "/pkToUser", {}, R"({"key":")" + PK + "\"}", true); } return ""; } diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 0296346..6fea3b1 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -11,17 +11,22 @@ #include #include +#include +#include #include #include +#include #include #include #include #include +#include #include namespace beast = boost::beast; // from namespace http = beast::http; // from namespace net = boost::asio; // from +namespace ssl = net::ssl; // from using tcp = net::ip::tcp; // from // UNUSED?! @@ -63,48 +68,69 @@ std::string HttpRequest(const std::string& host, int port, const std::string& ta } } -std::string PostHTTP(const std::string& host, int port, const std::string& target, const std::string& Fields, bool json) { - 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); +std::string PostHTTP(const std::string& host, const std::string& target, const std::unordered_map& fields, const std::string& body, bool json) { + //try { + net::io_context io; - http::request req { http::verb::post, target, 11 /* http 1.1 */ }; + // The SSL context is required, and holds certificates + ssl::context ctx(ssl::context::tlsv13_client); - req.set(http::field::host, host); + ctx.set_verify_mode(ssl::verify_none); + + tcp::resolver resolver(io); + beast::ssl_stream stream(io, ctx); + auto const results = resolver.resolve(host, std::to_string(443)); + if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) { + boost::system::error_code ec { static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; + throw boost::system::system_error { ec }; + } + beast::get_lowest_layer(stream).connect(results); + stream.handshake(ssl::stream_base::client); + + http::request req { http::verb::post, target, 11 /* http 1.1 */ }; + + req.set(http::field::host, host); + req.set("X-Forwarded-For", HttpRequest("api.ipify.org", 80, "/")); + if (!body.empty()) { if (json) { req.set(http::field::content_type, "application/json"); } - req.set(http::field::content_length, boost::lexical_cast(Fields.size())); - req.set(http::field::body, Fields.c_str()); - // tell the server what we are (boost beast) - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - req.prepare_payload(); + req.set(http::field::content_length, boost::lexical_cast(body.size())); + req.set(http::field::body, body); + } + for (const auto& pair : fields) { + info("setting " + pair.first + " to " + pair.second); + req.set(pair.first, pair.second); + } + // tell the server what we are (boost beast) + req.prepare_payload(); - stream.expires_after(std::chrono::seconds(5)); + std::stringstream oss; + oss << req; + warn(oss.str()); - http::write(stream, req); + beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(5)); - // used for reading - beast::flat_buffer buffer; - http::response response; + http::write(stream, req); - http::read(stream, buffer, response); + // used for reading + beast::flat_buffer buffer; + http::response response; - std::string result(response.body()); + http::read(stream, buffer, response); - 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 - } + std::stringstream result; + result << response; - return result; + beast::error_code ec; + stream.shutdown(ec); + // IGNORING ec - } catch (const std::exception& e) { + info(result.str()); + return result.str(); + + /*} catch (const std::exception& e) { error(e.what()); return "-1"; - } + }*/ } From 480c78c9f24dfb9e42758c662662d323f86fc27a Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sat, 30 Jan 2021 01:11:33 +0100 Subject: [PATCH 08/15] Working boost::beast POST request --- src/Init/Heartbeat.cpp | 39 +++++++++++++++++---------------------- src/Network/Http.cpp | 29 +++++++++++++++++++---------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/Init/Heartbeat.cpp b/src/Init/Heartbeat.cpp index 080b7c8..536b0e4 100644 --- a/src/Init/Heartbeat.cpp +++ b/src/Init/Heartbeat.cpp @@ -25,25 +25,20 @@ std::string GetPlayers() { } return Return; } -std::unordered_map GenerateCall() { - return { { "uuid", Key }, - { "players", std::to_string(CI->Size()) }, - { "maxplayers", std::to_string(MaxPlayers) }, - { "port", std::to_string(Port) }, - { "map", MapName }, - { "private", (Private ? "true" : "false") }, - { "version", GetSVer() }, - { "clientversion", GetCVer() }, - { "name", ServerName }, - { "pps", StatReport }, - { "modlist", FileList }, - { "modstotalsize", std::to_string(MaxModSize) }, - { "modstotal", std::to_string(ModsLoaded) }, - { "playerslist", GetPlayers() }, - { "desc", ServerDesc } }; +std::string GenerateCall() { + std::stringstream Ret; + Ret << "uuid=" << Key << "&players=" << CI->Size() + << "&maxplayers=" << MaxPlayers << "&port=" << Port + << "&map=" << MapName << "&private=" << (Private ? "true" : "false") + << "&version=" << GetSVer() << "&clientversion=" << GetCVer() + << "&name=" << ServerName << "&pps=" << StatReport + << "&modlist=" << FileList << "&modstotalsize=" << MaxModSize + << "&modstotal=" << ModsLoaded << "&playerslist=" << GetPlayers() + << "&desc=" << ServerDesc; + return Ret.str(); } -std::string RunPromise(const std::string& host, const std::string& target, const std::unordered_map& R) { - std::packaged_task task([&] { return PostHTTP(host, target, R, "", false); }); +std::string RunPromise(const std::string& host, const std::string& target, const std::unordered_map& R, const std::string& body) { + std::packaged_task task([&] { DebugPrintTIDInternal("Heartbeat_POST"); return PostHTTP(host, target, R, body, false); }); std::future f1 = task.get_future(); std::thread t(std::move(task)); t.detach(); @@ -56,19 +51,19 @@ std::string RunPromise(const std::string& host, const std::string& target, const [[noreturn]] void Heartbeat() { DebugPrintTID(); - std::unordered_map R; + std::string R; std::string T; bool isAuth = false; while (true) { R = GenerateCall(); if (!CustomIP.empty()) - R.insert({ "ip", CustomIP }); - T = RunPromise("beammp.com", "/heartbeatv2", R); + R += "&ip=" + CustomIP; + T = RunPromise("beammp.com", "/heartbeatv2", {}, R); if (T.substr(0, 2) != "20") { //Backend system refused server startup! std::this_thread::sleep_for(std::chrono::seconds(10)); - T = RunPromise("backup1.beammp.com", "/heartbeatv2", R); + T = RunPromise("backup1.beammp.com", "/heartbeatv2", {}, R); if (T.substr(0, 2) != "20") { warn("Backend system refused server! Server might not show in the public list"); } diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 6fea3b1..7e16011 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -31,6 +31,8 @@ using tcp = net::ip::tcp; // from // UNUSED?! std::string HttpRequest(const std::string& host, int port, const std::string& target) { + // FIXME: this is likely not very well written. + // if it causes issues, yell at me and I'll fix it asap. - Lion try { net::io_context io; tcp::resolver resolver(io); @@ -73,41 +75,45 @@ std::string PostHTTP(const std::string& host, const std::string& target, const s net::io_context io; // The SSL context is required, and holds certificates - ssl::context ctx(ssl::context::tlsv13_client); + ssl::context ctx(ssl::context::tlsv13); ctx.set_verify_mode(ssl::verify_none); tcp::resolver resolver(io); beast::ssl_stream stream(io, ctx); - auto const results = resolver.resolve(host, std::to_string(443)); + auto const results = resolver.resolve(tcp::v6(), host, std::to_string(443)); if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) { boost::system::error_code ec { static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; - throw boost::system::system_error { ec }; + // FIXME: we could throw and crash, if we like + // throw boost::system::system_error { ec }; + debug("POST " + host + target + " failed."); + return "-1"; } beast::get_lowest_layer(stream).connect(results); stream.handshake(ssl::stream_base::client); - http::request req { http::verb::post, target, 11 /* http 1.1 */ }; req.set(http::field::host, host); req.set("X-Forwarded-For", HttpRequest("api.ipify.org", 80, "/")); 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"); } req.set(http::field::content_length, boost::lexical_cast(body.size())); - req.set(http::field::body, body); + req.body() = body; + // info("body is " + body + " (" + req.body() + ")"); + // info("content size is " + std::to_string(body.size()) + " (" + boost::lexical_cast(body.size()) + ")"); } for (const auto& pair : fields) { - info("setting " + pair.first + " to " + pair.second); + // info("setting " + pair.first + " to " + pair.second); req.set(pair.first, pair.second); } - // tell the server what we are (boost beast) - req.prepare_payload(); std::stringstream oss; oss << req; - warn(oss.str()); beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(5)); @@ -126,7 +132,10 @@ std::string PostHTTP(const std::string& host, const std::string& target, const s stream.shutdown(ec); // IGNORING ec - info(result.str()); + // info(result.str()); + std::string debug_response_str; + std::getline(result, debug_response_str); + debug("POST " + host + target + ": " + debug_response_str); return result.str(); /*} catch (const std::exception& e) { From 33a7d0e1a1e2d2a65ca6ad123e13b7d8d882b19b Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sat, 30 Jan 2021 01:23:54 +0100 Subject: [PATCH 09/15] start fixing behavior when IPv6 not supported --- src/Network/Http.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 7e16011..2983998 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -81,7 +81,12 @@ std::string PostHTTP(const std::string& host, const std::string& target, const s tcp::resolver resolver(io); beast::ssl_stream stream(io, ctx); - auto const results = resolver.resolve(tcp::v6(), host, std::to_string(443)); + decltype(resolver)::results_type results; + try { + results = resolver.resolve(tcp::v6(), host, std::to_string(443)); + } catch (const boost::system::system_error&) { + error("ipv6 not supported!"); + } if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) { boost::system::error_code ec { static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; // FIXME: we could throw and crash, if we like @@ -94,7 +99,6 @@ std::string PostHTTP(const std::string& host, const std::string& target, const s http::request req { http::verb::post, target, 11 /* http 1.1 */ }; req.set(http::field::host, host); - req.set("X-Forwarded-For", HttpRequest("api.ipify.org", 80, "/")); if (!body.empty()) { if (json) { // FIXME: json is untested. From d47e721b38afe97a31477ad3a71f072155c9a8b7 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sat, 30 Jan 2021 01:30:22 +0100 Subject: [PATCH 10/15] PostHTTP: try IPv6, then IPv4 on failure to connect() --- src/Network/Http.cpp | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 2983998..2cc60a7 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -82,19 +82,31 @@ std::string PostHTTP(const std::string& host, const std::string& target, const s tcp::resolver resolver(io); beast::ssl_stream stream(io, ctx); decltype(resolver)::results_type results; - try { - results = resolver.resolve(tcp::v6(), host, std::to_string(443)); - } catch (const boost::system::system_error&) { - error("ipv6 not supported!"); + auto try_connect_with_protocol = [&](tcp protocol) { + try { + results = resolver.resolve(protocol, host, std::to_string(443)); + if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) { + boost::system::error_code ec { static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; + // FIXME: we could throw and crash, if we like + // throw boost::system::system_error { ec }; + debug("POST " + host + target + " failed."); + return false; + } + beast::get_lowest_layer(stream).connect(results); + } catch (const boost::system::system_error&) { + return false; + } + return true; + }; + bool ok = try_connect_with_protocol(tcp::v6()); + if (!ok) { + debug("IPv6 connect failed, trying IPv4"); + ok = try_connect_with_protocol(tcp::v4()); + if (!ok) { + error("failed to resolve or connect in POST " + host + target); + return "-1"; + } } - if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) { - boost::system::error_code ec { static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; - // FIXME: we could throw and crash, if we like - // throw boost::system::system_error { ec }; - debug("POST " + host + target + " failed."); - return "-1"; - } - beast::get_lowest_layer(stream).connect(results); stream.handshake(ssl::stream_base::client); http::request req { http::verb::post, target, 11 /* http 1.1 */ }; From 7e8b86cf571a43a220dacdb765b627fe8edecc50 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sat, 30 Jan 2021 01:39:55 +0100 Subject: [PATCH 11/15] return actual body instead of entire response (whoopsie!) --- src/Network/Http.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 2cc60a7..369789b 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -137,7 +137,7 @@ std::string PostHTTP(const std::string& host, const std::string& target, const s // used for reading beast::flat_buffer buffer; - http::response response; + http::response response; http::read(stream, buffer, response); @@ -152,7 +152,7 @@ std::string PostHTTP(const std::string& host, const std::string& target, const s std::string debug_response_str; std::getline(result, debug_response_str); debug("POST " + host + target + ": " + debug_response_str); - return result.str(); + return std::string(response.body()); /*} catch (const std::exception& e) { error(e.what()); From 00b7d1ca9619b8a635fc04cfabe59bb83ddc8bc2 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sat, 30 Jan 2021 02:01:53 +0100 Subject: [PATCH 12/15] Fix POST spamming issue --- src/Init/Heartbeat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Init/Heartbeat.cpp b/src/Init/Heartbeat.cpp index 536b0e4..b6f5f07 100644 --- a/src/Init/Heartbeat.cpp +++ b/src/Init/Heartbeat.cpp @@ -77,7 +77,7 @@ std::string RunPromise(const std::string& host, const std::string& target, const info(("Resumed authenticated session!")); isAuth = true; } - //std::this_thread::sleep_for(std::chrono::seconds(5)); + std::this_thread::sleep_for(std::chrono::seconds(5)); } } void HBInit() { From 710bb939adb938e8c8cd8311fde75d6f339c8e92 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sat, 30 Jan 2021 02:02:47 +0100 Subject: [PATCH 13/15] change hearbeat update rate to 10 seconds --- src/Init/Heartbeat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Init/Heartbeat.cpp b/src/Init/Heartbeat.cpp index b6f5f07..727b2a5 100644 --- a/src/Init/Heartbeat.cpp +++ b/src/Init/Heartbeat.cpp @@ -77,7 +77,7 @@ std::string RunPromise(const std::string& host, const std::string& target, const info(("Resumed authenticated session!")); isAuth = true; } - std::this_thread::sleep_for(std::chrono::seconds(5)); + std::this_thread::sleep_for(std::chrono::seconds(10)); } } void HBInit() { From 5f3fecb92c528a8b4c9bcf0bde0be3eae19a4e88 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sat, 30 Jan 2021 02:40:26 +0100 Subject: [PATCH 14/15] heartbeat every 30 seconds, or if anything changed, with a limit of once every 5 seconds --- src/Init/Heartbeat.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Init/Heartbeat.cpp b/src/Init/Heartbeat.cpp index 727b2a5..8177ed0 100644 --- a/src/Init/Heartbeat.cpp +++ b/src/Init/Heartbeat.cpp @@ -53,16 +53,29 @@ std::string RunPromise(const std::string& host, const std::string& target, const DebugPrintTID(); std::string R; std::string T; + + // these are "hot-change" related variables + static std::string LastR = ""; + + static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now(); bool isAuth = false; while (true) { R = GenerateCall(); + // a hot-change occurs when a setting has changed, to update the backend of that change. + auto Now = std::chrono::high_resolution_clock::now(); + if (LastR == R && (Now - LastNormalUpdateTime) < std::chrono::seconds(30)) { + std::this_thread::sleep_for(std::chrono::seconds(5)); + continue; + } + LastR = R; + LastNormalUpdateTime = Now; if (!CustomIP.empty()) R += "&ip=" + CustomIP; T = RunPromise("beammp.com", "/heartbeatv2", {}, R); if (T.substr(0, 2) != "20") { //Backend system refused server startup! - std::this_thread::sleep_for(std::chrono::seconds(10)); + std::this_thread::sleep_for(std::chrono::seconds(5)); T = RunPromise("backup1.beammp.com", "/heartbeatv2", {}, R); if (T.substr(0, 2) != "20") { warn("Backend system refused server! Server might not show in the public list"); @@ -77,7 +90,6 @@ std::string RunPromise(const std::string& host, const std::string& target, const info(("Resumed authenticated session!")); isAuth = true; } - std::this_thread::sleep_for(std::chrono::seconds(10)); } } void HBInit() { From d5541ae154a0c5070d49840309b9e5d9e6178bab Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Mon, 1 Feb 2021 23:13:58 +0100 Subject: [PATCH 15/15] add try/catch to PostHTTP --- src/Network/Http.cpp | 134 +++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 369789b..c7bbfeb 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -71,91 +71,91 @@ std::string HttpRequest(const std::string& host, int port, const std::string& ta } std::string PostHTTP(const std::string& host, const std::string& target, const std::unordered_map& fields, const std::string& body, bool json) { - //try { - net::io_context io; + try { + net::io_context io; - // The SSL context is required, and holds certificates - ssl::context ctx(ssl::context::tlsv13); + // The SSL context is required, and holds certificates + ssl::context ctx(ssl::context::tlsv13); - ctx.set_verify_mode(ssl::verify_none); + ctx.set_verify_mode(ssl::verify_none); - tcp::resolver resolver(io); - beast::ssl_stream stream(io, ctx); - decltype(resolver)::results_type results; - auto try_connect_with_protocol = [&](tcp protocol) { - try { - results = resolver.resolve(protocol, host, std::to_string(443)); - if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) { - boost::system::error_code ec { static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; - // FIXME: we could throw and crash, if we like - // throw boost::system::system_error { ec }; - debug("POST " + host + target + " failed."); + tcp::resolver resolver(io); + beast::ssl_stream stream(io, ctx); + decltype(resolver)::results_type results; + auto try_connect_with_protocol = [&](tcp protocol) { + try { + results = resolver.resolve(protocol, host, std::to_string(443)); + if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) { + boost::system::error_code ec { static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; + // FIXME: we could throw and crash, if we like + // throw boost::system::system_error { ec }; + debug("POST " + host + target + " failed."); + return false; + } + beast::get_lowest_layer(stream).connect(results); + } catch (const boost::system::system_error&) { return false; } - beast::get_lowest_layer(stream).connect(results); - } catch (const boost::system::system_error&) { - return false; - } - return true; - }; - bool ok = try_connect_with_protocol(tcp::v6()); - if (!ok) { - debug("IPv6 connect failed, trying IPv4"); - ok = try_connect_with_protocol(tcp::v4()); + return true; + }; + bool ok = try_connect_with_protocol(tcp::v6()); if (!ok) { - error("failed to resolve or connect in POST " + host + target); - return "-1"; + debug("IPv6 connect failed, trying IPv4"); + ok = try_connect_with_protocol(tcp::v4()); + if (!ok) { + error("failed to resolve or connect in POST " + host + target); + return "-1"; + } } - } - stream.handshake(ssl::stream_base::client); - http::request req { http::verb::post, target, 11 /* http 1.1 */ }; + stream.handshake(ssl::stream_base::client); + http::request req { http::verb::post, target, 11 /* http 1.1 */ }; - 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"); + 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"); + } + req.set(http::field::content_length, boost::lexical_cast(body.size())); + req.body() = body; + // info("body is " + body + " (" + req.body() + ")"); + // info("content size is " + std::to_string(body.size()) + " (" + boost::lexical_cast(body.size()) + ")"); + } + for (const auto& pair : fields) { + // info("setting " + pair.first + " to " + pair.second); + req.set(pair.first, pair.second); } - req.set(http::field::content_length, boost::lexical_cast(body.size())); - req.body() = body; - // info("body is " + body + " (" + req.body() + ")"); - // info("content size is " + std::to_string(body.size()) + " (" + boost::lexical_cast(body.size()) + ")"); - } - for (const auto& pair : fields) { - // info("setting " + pair.first + " to " + pair.second); - req.set(pair.first, pair.second); - } - std::stringstream oss; - oss << req; + std::stringstream oss; + oss << req; - beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(5)); + beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(5)); - http::write(stream, req); + http::write(stream, req); - // used for reading - beast::flat_buffer buffer; - http::response response; + // used for reading + beast::flat_buffer buffer; + http::response response; - http::read(stream, buffer, response); + http::read(stream, buffer, response); - std::stringstream result; - result << response; + std::stringstream result; + result << response; - beast::error_code ec; - stream.shutdown(ec); - // IGNORING ec + beast::error_code ec; + stream.shutdown(ec); + // IGNORING ec - // info(result.str()); - std::string debug_response_str; - std::getline(result, debug_response_str); - debug("POST " + host + target + ": " + debug_response_str); - return std::string(response.body()); + // info(result.str()); + std::string debug_response_str; + std::getline(result, debug_response_str); + debug("POST " + host + target + ": " + debug_response_str); + return std::string(response.body()); - /*} catch (const std::exception& e) { + } catch (const std::exception& e) { error(e.what()); return "-1"; - }*/ + } }