OpenRCT2/src/openrct2/network/NetworkServerAdvertiser.cpp

363 lines
11 KiB
C++
Raw Normal View History

2016-08-29 16:08:07 +02:00
/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
2016-08-29 16:08:07 +02:00
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
2016-08-29 16:08:07 +02:00
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
2016-08-29 16:08:07 +02:00
*****************************************************************************/
#ifndef DISABLE_NETWORK
2018-07-21 16:17:06 +02:00
# include "NetworkServerAdvertiser.h"
# include "../GameState.h"
2018-07-21 16:17:06 +02:00
# include "../config/Config.h"
# include "../core/Console.hpp"
# include "../core/Guard.hpp"
# include "../core/Http.h"
# include "../core/Json.hpp"
2018-07-21 16:17:06 +02:00
# include "../core/String.hpp"
2021-11-25 22:47:24 +01:00
# include "../entity/Guest.h"
2018-07-21 16:17:06 +02:00
# include "../localisation/Date.h"
# include "../management/Finance.h"
# include "../platform/Platform.h"
2018-07-21 16:17:06 +02:00
# include "../util/Util.h"
# include "../world/Map.h"
# include "../world/Park.h"
2019-05-05 18:54:16 +02:00
# include "Socket.h"
2018-07-21 16:17:06 +02:00
# include "network.h"
2019-05-05 03:02:20 +02:00
# include <cstring>
2018-11-21 23:16:04 +01:00
# include <iterator>
2018-12-17 12:58:12 +01:00
# include <memory>
# include <random>
2018-07-21 16:17:06 +02:00
# include <string>
using namespace OpenRCT2;
enum class MasterServerStatus
2016-08-29 16:08:07 +02:00
{
Ok = 200,
InvalidToken = 401,
ServerNotFound = 404,
InternalError = 500
2016-08-29 16:08:07 +02:00
};
# ifndef DISABLE_HTTP
constexpr int32_t MASTER_SERVER_REGISTER_TIME = 120 * 1000; // 2 minutes
constexpr int32_t MASTER_SERVER_HEARTBEAT_TIME = 60 * 1000; // 1 minute
# endif
2016-08-29 16:08:07 +02:00
class NetworkServerAdvertiser final : public INetworkServerAdvertiser
2016-08-29 16:08:07 +02:00
{
private:
uint16_t _port;
2016-08-29 16:08:07 +02:00
std::unique_ptr<IUdpSocket> _lanListener;
uint32_t _lastListenTime{};
2018-06-22 23:02:47 +02:00
ADVERTISE_STATUS _status = ADVERTISE_STATUS::UNREGISTERED;
# ifndef DISABLE_HTTP
2018-06-22 23:02:47 +02:00
uint32_t _lastAdvertiseTime = 0;
uint32_t _lastHeartbeatTime = 0;
2016-08-29 16:08:07 +02:00
// Our unique token for this server
2018-06-22 23:02:47 +02:00
std::string _token;
2016-08-29 16:08:07 +02:00
// Key received from the master server
2018-06-22 23:02:47 +02:00
std::string _key;
2016-08-29 16:08:07 +02:00
// See https://github.com/OpenRCT2/OpenRCT2/issues/6277 and 4953
2018-06-22 23:02:47 +02:00
bool _forceIPv4 = false;
# endif
2019-05-05 03:02:20 +02:00
2016-08-29 16:08:07 +02:00
public:
explicit NetworkServerAdvertiser(uint16_t port)
2016-08-29 16:08:07 +02:00
{
_port = port;
2019-05-05 03:02:20 +02:00
_lanListener = CreateUdpSocket();
# ifndef DISABLE_HTTP
_key = GenerateAdvertiseKey();
# endif
2016-08-29 16:08:07 +02:00
}
ADVERTISE_STATUS GetStatus() const override
2016-08-29 16:08:07 +02:00
{
return _status;
}
void Update() override
2019-05-05 16:28:10 +02:00
{
UpdateLAN();
# ifndef DISABLE_HTTP
if (gConfigNetwork.Advertise)
2019-05-05 16:28:10 +02:00
{
UpdateWAN();
}
# endif
2019-05-05 16:28:10 +02:00
}
private:
void UpdateLAN()
2016-08-29 16:08:07 +02:00
{
2019-05-05 03:02:20 +02:00
auto ticks = Platform::GetTicks();
if (ticks > _lastListenTime + 500)
2018-06-22 23:02:47 +02:00
{
if (_lanListener->GetStatus() != SocketStatus::Listening)
2019-05-05 03:02:20 +02:00
{
2019-05-05 16:28:10 +02:00
_lanListener->Listen(NETWORK_LAN_BROADCAST_PORT);
2019-05-05 03:02:20 +02:00
}
else
{
char buffer[256]{};
size_t recievedBytes{};
2019-05-05 03:02:20 +02:00
std::unique_ptr<INetworkEndpoint> endpoint;
auto p = _lanListener->ReceiveData(buffer, sizeof(buffer) - 1, &recievedBytes, &endpoint);
if (p == NetworkReadPacket::Success)
2018-06-22 23:02:47 +02:00
{
2019-05-05 03:02:20 +02:00
std::string sender = endpoint->GetHostname();
LOG_VERBOSE("Received %zu bytes from %s on LAN broadcast port", recievedBytes, sender.c_str());
if (String::Equals(buffer, NETWORK_LAN_BROADCAST_MSG))
{
auto body = GetBroadcastJson();
auto bodyDump = body.dump();
size_t sendLen = bodyDump.size() + 1;
LOG_VERBOSE("Sending %zu bytes back to %s", sendLen, sender.c_str());
_lanListener->SendData(*endpoint, bodyDump.c_str(), sendLen);
}
2018-06-22 23:02:47 +02:00
}
2019-05-05 03:02:20 +02:00
}
_lastListenTime = ticks;
}
2019-05-05 16:28:10 +02:00
}
2019-05-05 03:02:20 +02:00
json_t GetBroadcastJson()
{
json_t root = NetworkGetServerInfoAsJson();
root["port"] = _port;
return root;
}
# ifndef DISABLE_HTTP
2019-05-05 16:28:10 +02:00
void UpdateWAN()
{
switch (_status)
2019-05-05 03:02:20 +02:00
{
2019-05-05 16:28:10 +02:00
case ADVERTISE_STATUS::UNREGISTERED:
if (_lastAdvertiseTime == 0 || Platform::GetTicks() > _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME)
2019-05-05 16:28:10 +02:00
{
2021-02-11 22:28:05 +01:00
if (_lastAdvertiseTime == 0)
{
2021-02-13 01:15:27 +01:00
Console::WriteLine("Registering server on master server");
2021-02-11 22:28:05 +01:00
}
2019-05-05 16:28:10 +02:00
SendRegistration(_forceIPv4);
}
break;
case ADVERTISE_STATUS::REGISTERED:
if (Platform::GetTicks() > _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME)
2019-05-05 16:28:10 +02:00
{
SendHeartbeat();
}
break;
// exhaust enum values to satisfy clang
case ADVERTISE_STATUS::DISABLED:
break;
2016-08-29 16:08:07 +02:00
}
}
void SendRegistration(bool forceIPv4)
2016-08-29 16:08:07 +02:00
{
_lastAdvertiseTime = Platform::GetTicks();
2016-08-29 16:08:07 +02:00
// Send the registration request
Http::Request request;
2016-08-29 16:08:07 +02:00
request.url = GetMasterServerUrl();
request.method = Http::Method::POST;
request.forceIPv4 = forceIPv4;
2016-08-29 16:08:07 +02:00
json_t body = {
{ "key", _key },
{ "port", _port },
};
if (!gConfigNetwork.AdvertiseAddress.empty())
{
body["address"] = gConfigNetwork.AdvertiseAddress;
}
request.body = body.dump();
request.header["Content-Type"] = "application/json";
2016-08-29 16:08:07 +02:00
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::Ok)
2016-08-29 16:08:07 +02:00
{
2021-02-11 22:28:05 +01:00
Console::Error::WriteLine("Unable to connect to master server");
return;
2016-08-29 16:08:07 +02:00
}
json_t root = Json::FromString(response.body);
root = Json::AsObject(root);
this->OnRegistrationResponse(root);
2016-08-29 16:08:07 +02:00
});
}
void SendHeartbeat()
{
Http::Request request;
2016-08-29 16:08:07 +02:00
request.url = GetMasterServerUrl();
request.method = Http::Method::PUT;
2016-08-29 16:08:07 +02:00
json_t body = GetHeartbeatJson();
request.body = body.dump();
request.header["Content-Type"] = "application/json";
2016-08-29 16:08:07 +02:00
_lastHeartbeatTime = Platform::GetTicks();
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::Ok)
2016-08-29 16:08:07 +02:00
{
2021-02-11 22:28:05 +01:00
Console::Error::WriteLine("Unable to connect to master server");
return;
2016-08-29 16:08:07 +02:00
}
json_t root = Json::FromString(response.body);
root = Json::AsObject(root);
this->OnHeartbeatResponse(root);
2016-08-29 16:08:07 +02:00
});
}
/**
* @param jsonRoot must be of JSON type object or null
* @note jsonRoot is deliberately left non-const: json_t behaviour changes when const
*/
void OnRegistrationResponse(json_t& jsonRoot)
2016-08-29 16:08:07 +02:00
{
Guard::Assert(jsonRoot.is_object(), "OnRegistrationResponse expects parameter jsonRoot to be object");
auto status = Json::GetEnum<MasterServerStatus>(jsonRoot["status"], MasterServerStatus::InternalError);
if (status == MasterServerStatus::Ok)
2016-08-29 16:08:07 +02:00
{
2021-02-13 01:15:27 +01:00
Console::WriteLine("Server successfully registered on master server");
json_t jsonToken = jsonRoot["token"];
if (jsonToken.is_string())
2016-08-29 16:08:07 +02:00
{
_token = Json::GetString(jsonToken);
_status = ADVERTISE_STATUS::REGISTERED;
2016-08-29 16:08:07 +02:00
}
}
else
{
std::string message = Json::GetString(jsonRoot["message"]);
if (message.empty())
2016-08-29 16:08:07 +02:00
{
message = "Invalid response from server";
}
2021-02-11 22:28:05 +01:00
Console::Error::WriteLine(
"Unable to advertise (%d): %s\n * Check that you have port forwarded %u\n * Try setting "
2021-02-11 22:28:05 +01:00
"advertise_address in config.ini",
status, message.c_str(), _port);
// Hack for https://github.com/OpenRCT2/OpenRCT2/issues/6277
// Master server may not reply correctly if using IPv6, retry forcing IPv4,
// don't wait the full timeout.
if (!_forceIPv4 && status == MasterServerStatus::InternalError)
{
_forceIPv4 = true;
_lastAdvertiseTime = 0;
LOG_INFO("Forcing HTTP(S) over IPv4");
2016-08-29 16:08:07 +02:00
}
}
}
/**
* @param jsonRoot must be of JSON type object or null
* @note jsonRoot is deliberately left non-const: json_t behaviour changes when const
*/
void OnHeartbeatResponse(json_t& jsonRoot)
2016-08-29 16:08:07 +02:00
{
Guard::Assert(jsonRoot.is_object(), "OnHeartbeatResponse expects parameter jsonRoot to be object");
auto status = Json::GetEnum<MasterServerStatus>(jsonRoot["status"], MasterServerStatus::InternalError);
if (status == MasterServerStatus::Ok)
2016-08-29 16:08:07 +02:00
{
// Master server has successfully updated our server status
}
else if (status == MasterServerStatus::InvalidToken)
{
_status = ADVERTISE_STATUS::UNREGISTERED;
_lastAdvertiseTime = 0;
2021-02-11 22:28:05 +01:00
Console::Error::WriteLine("Master server heartbeat failed: Invalid Token");
2016-08-29 16:08:07 +02:00
}
}
json_t GetHeartbeatJson()
2016-08-29 16:08:07 +02:00
{
uint32_t numPlayers = NetworkGetNumVisiblePlayers();
2016-08-29 16:08:07 +02:00
json_t root = {
{ "token", _token },
{ "players", numPlayers },
};
2016-08-29 16:08:07 +02:00
2024-01-25 12:13:14 +01:00
const auto& gameState = GetGameState();
const auto& date = GetDate();
2024-02-12 22:32:08 +01:00
json_t mapSize = { { "x", gameState.MapSize.x - 2 }, { "y", gameState.MapSize.y - 2 } };
json_t gameInfo = {
{ "mapSize", mapSize },
{ "day", date.GetMonthTicks() },
{ "month", date.GetMonthsElapsed() },
{ "guests", gameState.NumGuestsInPark },
{ "parkValue", gameState.Park.Value },
};
if (!(gameState.Park.Flags & PARK_FLAGS_NO_MONEY))
2016-08-29 16:08:07 +02:00
{
gameInfo["cash"] = gameState.Cash;
2016-08-29 16:08:07 +02:00
}
root["gameInfo"] = gameInfo;
2016-08-29 16:08:07 +02:00
return root;
}
static std::string GenerateAdvertiseKey()
{
// Generate a string of 16 random hex characters (64-integer key as a hex formatted string)
static constexpr char hexChars[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
};
std::random_device rd;
2019-05-12 11:32:41 +02:00
std::uniform_int_distribution<int32_t> dist(0, static_cast<int32_t>(std::size(hexChars) - 1));
2016-08-29 16:08:07 +02:00
char key[17];
for (int32_t i = 0; i < 16; i++)
2016-08-29 16:08:07 +02:00
{
int32_t hexCharIndex = dist(rd);
2016-08-29 16:08:07 +02:00
key[i] = hexChars[hexCharIndex];
}
2018-11-21 23:16:04 +01:00
key[std::size(key) - 1] = 0;
2016-08-29 16:08:07 +02:00
return key;
}
2019-02-23 15:54:17 +01:00
static std::string GetMasterServerUrl()
2016-08-29 16:08:07 +02:00
{
2019-02-23 15:54:17 +01:00
std::string result = OPENRCT2_MASTER_SERVER_URL;
if (!gConfigNetwork.MasterServerUrl.empty())
2016-08-29 16:08:07 +02:00
{
result = gConfigNetwork.MasterServerUrl;
2016-08-29 16:08:07 +02:00
}
return result;
}
# endif
2016-08-29 16:08:07 +02:00
};
2018-12-17 12:58:12 +01:00
std::unique_ptr<INetworkServerAdvertiser> CreateServerAdvertiser(uint16_t port)
2016-08-29 16:08:07 +02:00
{
2018-12-17 12:58:12 +01:00
return std::make_unique<NetworkServerAdvertiser>(port);
2016-08-29 16:08:07 +02:00
}
#endif // DISABLE_NETWORK