mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-27 00:34:46 +01:00
Implement UDP socket and broadcasting
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#include <openrct2/localisation/Localisation.h>
|
||||
#include <openrct2/network/Http.h>
|
||||
#include <openrct2/network/ServerList.h>
|
||||
#include <openrct2/network/UdpSocket.h>
|
||||
#include <openrct2/network/network.h>
|
||||
#include <openrct2/sprites.h>
|
||||
#include <openrct2/util/Util.h>
|
||||
@@ -636,29 +637,61 @@ static void join_server(std::string address)
|
||||
}
|
||||
|
||||
#ifndef DISABLE_HTTP
|
||||
|
||||
static void fetch_lan_servers()
|
||||
{
|
||||
std::string msg = "Are you an OpenRCT2 server?";
|
||||
auto udpSocket = CreateUdpSocket();
|
||||
auto len = udpSocket->SendData("192.168.1.255", 11754, msg.data(), msg.size());
|
||||
if (len == msg.size())
|
||||
{
|
||||
char buffer[256]{};
|
||||
size_t recievedLen{};
|
||||
std::unique_ptr<INetworkEndpoint> endpoint;
|
||||
for (int i = 0; i < 5 * 10; i++)
|
||||
{
|
||||
auto p = udpSocket->ReceiveData(buffer, sizeof(buffer), &recievedLen, &endpoint);
|
||||
if (p == NETWORK_READPACKET_SUCCESS)
|
||||
{
|
||||
auto sender = endpoint->GetHostname();
|
||||
std::printf(">> Recieved packet from %s\n", sender.c_str());
|
||||
std::printf(">> %s\n", buffer);
|
||||
}
|
||||
usleep(100 * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fetch_servers()
|
||||
{
|
||||
std::string masterServerUrl = OPENRCT2_MASTER_SERVER_URL;
|
||||
if (!gConfigNetwork.master_server_url.empty())
|
||||
if (1 == 1)
|
||||
{
|
||||
masterServerUrl = gConfigNetwork.master_server_url;
|
||||
fetch_lan_servers();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
_serverEntries.erase(
|
||||
std::remove_if(
|
||||
_serverEntries.begin(), _serverEntries.end(), [](const server_entry& server) { return !server.favourite; }),
|
||||
_serverEntries.end());
|
||||
sort_servers();
|
||||
}
|
||||
std::string masterServerUrl = OPENRCT2_MASTER_SERVER_URL;
|
||||
if (gConfigNetwork.master_server_url.empty() == false)
|
||||
{
|
||||
masterServerUrl = gConfigNetwork.master_server_url;
|
||||
}
|
||||
|
||||
Http::Request request;
|
||||
request.url = masterServerUrl;
|
||||
request.method = Http::Method::GET;
|
||||
request.header["Accept"] = "application/json";
|
||||
status_text = STR_SERVER_LIST_CONNECTING;
|
||||
Http::DoAsync(request, fetch_servers_callback);
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
_serverEntries.erase(
|
||||
std::remove_if(
|
||||
_serverEntries.begin(), _serverEntries.end(), [](const server_entry& server) { return !server.favourite; }),
|
||||
_serverEntries.end());
|
||||
sort_servers();
|
||||
}
|
||||
|
||||
Http::Request request;
|
||||
request.url = masterServerUrl;
|
||||
request.method = Http::Method::GET;
|
||||
request.header["Accept"] = "application/json";
|
||||
status_text = STR_SERVER_LIST_CONNECTING;
|
||||
Http::DoAsync(request, fetch_servers_callback);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t get_total_player_count()
|
||||
|
||||
@@ -680,11 +680,7 @@ bool Network::BeginServer(uint16_t port, const std::string& address)
|
||||
status = NETWORK_STATUS_CONNECTED;
|
||||
listening_port = port;
|
||||
_serverState.gamestateSnapshotsEnabled = gConfigNetwork.desync_debugging;
|
||||
|
||||
if (gConfigNetwork.advertise)
|
||||
{
|
||||
_advertiser = CreateServerAdvertiser(listening_port);
|
||||
}
|
||||
_advertiser = CreateServerAdvertiser(listening_port);
|
||||
|
||||
if (gConfigNetwork.pause_server_if_no_clients)
|
||||
{
|
||||
|
||||
@@ -18,13 +18,16 @@
|
||||
# include "../localisation/Date.h"
|
||||
# include "../management/Finance.h"
|
||||
# include "../peep/Peep.h"
|
||||
# include "../platform/Platform2.h"
|
||||
# include "../platform/platform.h"
|
||||
# include "../util/Util.h"
|
||||
# include "../world/Map.h"
|
||||
# include "../world/Park.h"
|
||||
# include "Http.h"
|
||||
# include "UdpSocket.h"
|
||||
# include "network.h"
|
||||
|
||||
# include <cstring>
|
||||
# include <iterator>
|
||||
# include <memory>
|
||||
# include <string>
|
||||
@@ -44,6 +47,8 @@ enum MASTER_SERVER_STATUS
|
||||
constexpr int32_t MASTER_SERVER_REGISTER_TIME = 120 * 1000; // 2 minutes
|
||||
constexpr int32_t MASTER_SERVER_HEARTBEAT_TIME = 60 * 1000; // 1 minute
|
||||
|
||||
constexpr int32_t LAN_BROADCAST_PORT = 11754;
|
||||
|
||||
class NetworkServerAdvertiser final : public INetworkServerAdvertiser
|
||||
{
|
||||
private:
|
||||
@@ -62,11 +67,15 @@ private:
|
||||
// See https://github.com/OpenRCT2/OpenRCT2/issues/6277 and 4953
|
||||
bool _forceIPv4 = false;
|
||||
|
||||
std::unique_ptr<IUdpSocket> _lanListener;
|
||||
uint32_t _lastListenTime{};
|
||||
|
||||
public:
|
||||
explicit NetworkServerAdvertiser(uint16_t port)
|
||||
{
|
||||
_port = port;
|
||||
_key = GenerateAdvertiseKey();
|
||||
_lanListener = CreateUdpSocket();
|
||||
}
|
||||
|
||||
ADVERTISE_STATUS GetStatus() const override
|
||||
@@ -76,23 +85,58 @@ public:
|
||||
|
||||
void Update() override
|
||||
{
|
||||
switch (_status)
|
||||
auto ticks = Platform::GetTicks();
|
||||
if (ticks > _lastListenTime + 500)
|
||||
{
|
||||
case ADVERTISE_STATUS::UNREGISTERED:
|
||||
if (_lastAdvertiseTime == 0 || platform_get_ticks() > _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME)
|
||||
if (_lanListener->GetStatus() != SOCKET_STATUS_LISTENING)
|
||||
{
|
||||
_lanListener->Listen(LAN_BROADCAST_PORT);
|
||||
}
|
||||
else
|
||||
{
|
||||
char buffer[256];
|
||||
size_t recievedBytes;
|
||||
std::unique_ptr<INetworkEndpoint> endpoint;
|
||||
auto p = _lanListener->ReceiveData(buffer, sizeof(buffer), &recievedBytes, &endpoint);
|
||||
if (p == NETWORK_READPACKET_SUCCESS)
|
||||
{
|
||||
SendRegistration(_forceIPv4);
|
||||
std::string sender = endpoint->GetHostname();
|
||||
std::printf("\r>> Received %zu bytes from %s\n", recievedBytes, sender.c_str());
|
||||
|
||||
auto body = GetHeartbeatJson();
|
||||
auto bodyDump = json_dumps(body, JSON_COMPACT);
|
||||
size_t sendLen = strlen(bodyDump) + 1;
|
||||
std::printf("\r>> Sending %zu bytes back to %s\n", sendLen, sender.c_str());
|
||||
_lanListener->SendData(*endpoint, bodyDump, sendLen);
|
||||
free(bodyDump);
|
||||
json_decref(body);
|
||||
}
|
||||
break;
|
||||
case ADVERTISE_STATUS::REGISTERED:
|
||||
if (platform_get_ticks() > _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME)
|
||||
{
|
||||
SendHeartbeat();
|
||||
}
|
||||
break;
|
||||
// exhaust enum values to satisfy clang
|
||||
case ADVERTISE_STATUS::DISABLED:
|
||||
break;
|
||||
}
|
||||
_lastListenTime = ticks;
|
||||
}
|
||||
|
||||
if (gConfigNetwork.advertise)
|
||||
{
|
||||
/*
|
||||
switch (_status)
|
||||
{
|
||||
case ADVERTISE_STATUS::UNREGISTERED:
|
||||
if (_lastAdvertiseTime == 0 || platform_get_ticks() > _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME)
|
||||
{
|
||||
SendRegistration(_forceIPv4);
|
||||
}
|
||||
break;
|
||||
case ADVERTISE_STATUS::REGISTERED:
|
||||
if (platform_get_ticks() > _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME)
|
||||
{
|
||||
SendHeartbeat();
|
||||
}
|
||||
break;
|
||||
// exhaust enum values to satisfy clang
|
||||
case ADVERTISE_STATUS::DISABLED:
|
||||
break;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,12 +77,64 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class NetworkEndpoint final : public INetworkEndpoint
|
||||
{
|
||||
private:
|
||||
sockaddr _address{};
|
||||
socklen_t _addressLen{};
|
||||
|
||||
public:
|
||||
NetworkEndpoint()
|
||||
{
|
||||
}
|
||||
|
||||
NetworkEndpoint(const sockaddr* address, socklen_t addressLen)
|
||||
{
|
||||
std::memcpy(&_address, address, addressLen);
|
||||
_addressLen = addressLen;
|
||||
}
|
||||
|
||||
const sockaddr& GetAddress() const
|
||||
{
|
||||
return _address;
|
||||
}
|
||||
|
||||
socklen_t GetAddressLen() const
|
||||
{
|
||||
return _addressLen;
|
||||
}
|
||||
|
||||
int32_t GetPort() const
|
||||
{
|
||||
if (_address.sa_family == AF_INET)
|
||||
{
|
||||
return ((sockaddr_in*)&_address)->sin_port;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((sockaddr_in6*)&_address)->sin6_port;
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetHostname() override
|
||||
{
|
||||
char hostname[256];
|
||||
int res = getnameinfo(&_address, _addressLen, hostname, sizeof(hostname), nullptr, 0, NI_NUMERICHOST);
|
||||
if (res == 0)
|
||||
{
|
||||
return hostname;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class UdpSocket final : public IUdpSocket
|
||||
{
|
||||
private:
|
||||
SOCKET_STATUS _status = SOCKET_STATUS_CLOSED;
|
||||
uint16_t _listeningPort = 0;
|
||||
SOCKET _socket = INVALID_SOCKET;
|
||||
NetworkEndpoint _endpoint;
|
||||
|
||||
std::string _hostName;
|
||||
std::string _error;
|
||||
@@ -118,32 +170,36 @@ public:
|
||||
}
|
||||
|
||||
sockaddr_storage ss{};
|
||||
int32_t ss_len;
|
||||
socklen_t ss_len;
|
||||
if (!ResolveAddress(address, port, &ss, &ss_len))
|
||||
{
|
||||
throw SocketException("Unable to resolve address.");
|
||||
}
|
||||
|
||||
// Create the listening socket
|
||||
_socket = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
_socket = socket(ss.ss_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (_socket == INVALID_SOCKET)
|
||||
{
|
||||
throw SocketException("Unable to create socket.");
|
||||
}
|
||||
|
||||
// Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections
|
||||
int32_t value = 0;
|
||||
if (setsockopt(_socket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&value, sizeof(value)) != 0)
|
||||
if (!SetOption(_socket, IPPROTO_IPV6, IPV6_V6ONLY, false))
|
||||
{
|
||||
log_error("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR());
|
||||
}
|
||||
|
||||
value = 1;
|
||||
if (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&value, sizeof(value)) != 0)
|
||||
if (!SetOption(_socket, SOL_SOCKET, SO_REUSEADDR, true))
|
||||
{
|
||||
log_error("SO_REUSEADDR failed. %d", LAST_SOCKET_ERROR());
|
||||
}
|
||||
|
||||
// Enable send and receiving of broadcast messages
|
||||
if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true))
|
||||
{
|
||||
log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Bind to address:port and listen
|
||||
@@ -151,10 +207,6 @@ public:
|
||||
{
|
||||
throw SocketException("Unable to bind to socket.");
|
||||
}
|
||||
if (listen(_socket, SOMAXCONN) != 0)
|
||||
{
|
||||
throw SocketException("Unable to listen on socket.");
|
||||
}
|
||||
|
||||
if (!SetNonBlocking(_socket, true))
|
||||
{
|
||||
@@ -173,9 +225,49 @@ public:
|
||||
|
||||
size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) override
|
||||
{
|
||||
if (_status != SOCKET_STATUS_CONNECTED)
|
||||
sockaddr_storage ss{};
|
||||
socklen_t ss_len;
|
||||
if (!ResolveAddress(address, port, &ss, &ss_len))
|
||||
{
|
||||
throw std::runtime_error("Socket not connected.");
|
||||
throw SocketException("Unable to resolve address.");
|
||||
}
|
||||
NetworkEndpoint endpoint((const sockaddr*)&ss, ss_len);
|
||||
return SendData(endpoint, buffer, size);
|
||||
}
|
||||
|
||||
size_t SendData(const INetworkEndpoint& destination, const void* buffer, size_t size) override
|
||||
{
|
||||
if (_socket == INVALID_SOCKET)
|
||||
{
|
||||
_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (_socket == INVALID_SOCKET)
|
||||
{
|
||||
throw SocketException("Unable to create socket.");
|
||||
}
|
||||
|
||||
// Enable send and receiving of broadcast messages
|
||||
if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true))
|
||||
{
|
||||
log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR());
|
||||
}
|
||||
|
||||
if (!SetNonBlocking(_socket, true))
|
||||
{
|
||||
throw SocketException("Failed to set non-blocking mode.");
|
||||
}
|
||||
}
|
||||
|
||||
const auto& dest = dynamic_cast<const NetworkEndpoint*>(&destination);
|
||||
if (dest == nullptr)
|
||||
{
|
||||
throw std::invalid_argument("destination is not compatible.");
|
||||
}
|
||||
auto ss = &dest->GetAddress();
|
||||
auto ss_len = dest->GetAddressLen();
|
||||
|
||||
if (_status != SOCKET_STATUS_LISTENING)
|
||||
{
|
||||
_endpoint = *dest;
|
||||
}
|
||||
|
||||
size_t totalSent = 0;
|
||||
@@ -183,7 +275,7 @@ public:
|
||||
{
|
||||
const char* bufferStart = (const char*)buffer + totalSent;
|
||||
size_t remainingSize = size - totalSent;
|
||||
int32_t sentBytes = send(_socket, bufferStart, (int32_t)remainingSize, FLAG_NO_PIPE);
|
||||
int32_t sentBytes = sendto(_socket, bufferStart, (int32_t)remainingSize, FLAG_NO_PIPE, (const sockaddr*)ss, ss_len);
|
||||
if (sentBytes == SOCKET_ERROR)
|
||||
{
|
||||
return totalSent;
|
||||
@@ -193,46 +285,29 @@ public:
|
||||
return totalSent;
|
||||
}
|
||||
|
||||
NETWORK_READPACKET ReceiveData(void* buffer, size_t size, size_t* sizeReceived) override
|
||||
NETWORK_READPACKET ReceiveData(
|
||||
void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr<INetworkEndpoint>* sender) override
|
||||
{
|
||||
if (_status != SOCKET_STATUS_CONNECTED)
|
||||
sockaddr_in senderAddr{};
|
||||
socklen_t senderAddrLen{};
|
||||
if (_status != SOCKET_STATUS_LISTENING)
|
||||
{
|
||||
throw std::runtime_error("Socket not connected.");
|
||||
senderAddrLen = _endpoint.GetAddressLen();
|
||||
std::memcpy(&senderAddr, &_endpoint.GetAddress(), senderAddrLen);
|
||||
}
|
||||
|
||||
int32_t readBytes = recv(_socket, (char*)buffer, (int32_t)size, 0);
|
||||
if (readBytes == 0)
|
||||
auto readBytes = recvfrom(_socket, (char*)buffer, (int32_t)size, 0, (sockaddr*)&senderAddr, &senderAddrLen);
|
||||
if (readBytes <= 0)
|
||||
{
|
||||
*sizeReceived = 0;
|
||||
return NETWORK_READPACKET_DISCONNECTED;
|
||||
}
|
||||
else if (readBytes == SOCKET_ERROR)
|
||||
{
|
||||
*sizeReceived = 0;
|
||||
# ifndef _WIN32
|
||||
// Removing the check for EAGAIN and instead relying on the values being the same allows turning on of
|
||||
// -Wlogical-op warning.
|
||||
// This is not true on Windows, see:
|
||||
// * https://msdn.microsoft.com/en-us/library/windows/desktop/ms737828(v=vs.85).aspx
|
||||
// * https://msdn.microsoft.com/en-us/library/windows/desktop/ms741580(v=vs.85).aspx
|
||||
// * https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx
|
||||
static_assert(
|
||||
EWOULDBLOCK == EAGAIN,
|
||||
"Portability note: your system has different values for EWOULDBLOCK "
|
||||
"and EAGAIN, please extend the condition below");
|
||||
# endif // _WIN32
|
||||
if (LAST_SOCKET_ERROR() != EWOULDBLOCK)
|
||||
{
|
||||
return NETWORK_READPACKET_DISCONNECTED;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NETWORK_READPACKET_NO_DATA;
|
||||
}
|
||||
return NETWORK_READPACKET_NO_DATA;
|
||||
}
|
||||
else
|
||||
{
|
||||
*sizeReceived = readBytes;
|
||||
if (sender != nullptr)
|
||||
{
|
||||
*sender = std::make_unique<NetworkEndpoint>((sockaddr*)&senderAddr, senderAddrLen);
|
||||
}
|
||||
return NETWORK_READPACKET_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -265,7 +340,7 @@ private:
|
||||
_status = SOCKET_STATUS_CLOSED;
|
||||
}
|
||||
|
||||
bool ResolveAddress(const std::string& address, uint16_t port, sockaddr_storage* ss, int32_t* ss_len)
|
||||
bool ResolveAddress(const std::string& address, uint16_t port, sockaddr_storage* ss, socklen_t* ss_len)
|
||||
{
|
||||
std::string serviceName = std::to_string(port);
|
||||
|
||||
@@ -291,7 +366,7 @@ private:
|
||||
else
|
||||
{
|
||||
std::memcpy(ss, result->ai_addr, result->ai_addrlen);
|
||||
*ss_len = (int32_t)result->ai_addrlen;
|
||||
*ss_len = result->ai_addrlen;
|
||||
freeaddrinfo(result);
|
||||
return true;
|
||||
}
|
||||
@@ -308,9 +383,10 @@ private:
|
||||
# endif
|
||||
}
|
||||
|
||||
static bool SetSOBroadcast(SOCKET socket, bool enabled)
|
||||
static bool SetOption(SOCKET socket, int32_t a, int32_t b, bool value)
|
||||
{
|
||||
return setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char*)&enabled, sizeof(enabled)) == 0;
|
||||
int32_t ivalue = value ? 1 : 0;
|
||||
return setsockopt(socket, a, b, (const char*)&ivalue, sizeof(ivalue)) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,18 @@ enum NETWORK_READPACKET
|
||||
NETWORK_READPACKET_DISCONNECTED
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an address and port.
|
||||
*/
|
||||
interface INetworkEndpoint
|
||||
{
|
||||
virtual ~INetworkEndpoint()
|
||||
{
|
||||
}
|
||||
|
||||
virtual std::string GetHostname() abstract;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a UDP socket / listener.
|
||||
*/
|
||||
@@ -49,7 +61,9 @@ public:
|
||||
virtual void Listen(const std::string& address, uint16_t port) abstract;
|
||||
|
||||
virtual size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) abstract;
|
||||
virtual NETWORK_READPACKET ReceiveData(void* buffer, size_t size, size_t* sizeReceived) abstract;
|
||||
virtual size_t SendData(const INetworkEndpoint& destination, const void* buffer, size_t size) abstract;
|
||||
virtual NETWORK_READPACKET ReceiveData(
|
||||
void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr<INetworkEndpoint>* sender) abstract;
|
||||
|
||||
virtual void Close() abstract;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user