1
0
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:
Ted John
2019-05-05 01:02:20 +00:00
parent 5992b9f76e
commit 694cb7eb3f
5 changed files with 248 additions and 85 deletions

View File

@@ -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()

View File

@@ -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)
{

View File

@@ -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;
}
*/
}
}

View File

@@ -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;
}
};

View File

@@ -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;
};