From 694cb7eb3fd78cac81205c0a0882260c1b3c830d Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 01:02:20 +0000 Subject: [PATCH] Implement UDP socket and broadcasting --- src/openrct2-ui/windows/ServerList.cpp | 67 +++++-- src/openrct2/network/Network.cpp | 6 +- .../network/NetworkServerAdvertiser.cpp | 72 ++++++-- src/openrct2/network/UdpSocket.cpp | 172 +++++++++++++----- src/openrct2/network/UdpSocket.h | 16 +- 5 files changed, 248 insertions(+), 85 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index 16f665d7e8..fde87fa51a 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -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 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 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 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() diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index 55376637ec..8a24933a11 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -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) { diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index 1b8623bfb4..f347d193ba 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -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 # include # include # include @@ -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 _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 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; + } + */ } } diff --git a/src/openrct2/network/UdpSocket.cpp b/src/openrct2/network/UdpSocket.cpp index 63780afcda..80c13e2f26 100644 --- a/src/openrct2/network/UdpSocket.cpp +++ b/src/openrct2/network/UdpSocket.cpp @@ -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(&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* 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((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; } }; diff --git a/src/openrct2/network/UdpSocket.h b/src/openrct2/network/UdpSocket.h index 07beec7a75..a2523e3719 100644 --- a/src/openrct2/network/UdpSocket.h +++ b/src/openrct2/network/UdpSocket.h @@ -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* sender) abstract; virtual void Close() abstract; };