# HG changeset patch # User Patric Stout # Date 2021-07-03 09:04:32 # Node ID 5bcbdca2efafc12507b9c161153132d89e3e1b49 # Parent 67f31839e848523c13fd576f2c6faddaf4d702ec Add: use Game Coordinator to annouce public servers diff --git a/src/lang/english.txt b/src/lang/english.txt --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1995,7 +1995,7 @@ STR_FACE_TIE STR_FACE_EARRING :Earring: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Change tie or earring -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Private +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Local STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Public # Network server list @@ -2136,6 +2136,8 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Name of the server STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Visibility STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Whether other people can see your server in the public listing +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Connection type +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Whether and how your server can be reached by others STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Player STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Name STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Your player name @@ -2154,6 +2156,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}This is the host of the game STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} client{P "" s} / {NUM} compan{P y ies} +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Local +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Remote players can't connect +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Public +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Kick STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Ban STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Delete @@ -2281,6 +2289,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}The server is restarting...{}Please wait... STR_NETWORK_MESSAGE_KICKED :*** {RAW_STRING} was kicked. Reason: ({RAW_STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Server registration failed +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Your server doesn't allow remote connections +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Other players won't be able to connect to your server + # Content downloading window STR_CONTENT_TITLE :{WHITE}Content downloading STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -14,6 +14,8 @@ add_files( network_content.h network_content_gui.cpp network_content_gui.h + network_coordinator.cpp + network_coordinator.h network_func.h network_gamelist.cpp network_gamelist.h diff --git a/src/network/core/CMakeLists.txt b/src/network/core/CMakeLists.txt --- a/src/network/core/CMakeLists.txt +++ b/src/network/core/CMakeLists.txt @@ -20,6 +20,8 @@ add_files( tcp_content.cpp tcp_content.h tcp_content_type.h + tcp_coordinator.cpp + tcp_coordinator.h tcp_game.cpp tcp_game.h tcp_http.cpp diff --git a/src/network/core/config.h b/src/network/core/config.h --- a/src/network/core/config.h +++ b/src/network/core/config.h @@ -14,6 +14,8 @@ /** DNS hostname of the masterserver */ static const char * const NETWORK_MASTER_SERVER_HOST = "master.openttd.org"; +/** DNS hostname of the Game Coordinator server */ +static const char * const NETWORK_COORDINATOR_SERVER_HOST = "coordinator.openttd.org"; /** DNS hostname of the content server */ static const char * const NETWORK_CONTENT_SERVER_HOST = "content.openttd.org"; /** DNS hostname of the HTTP-content mirror server */ @@ -23,14 +25,15 @@ static const char * const NETWORK_CONTEN /** Message sent to the masterserver to 'identify' this client as OpenTTD */ static const char * const NETWORK_MASTER_SERVER_WELCOME_MESSAGE = "OpenTTDRegister"; -static const uint16 NETWORK_MASTER_SERVER_PORT = 3978; ///< The default port of the master server (UDP) -static const uint16 NETWORK_CONTENT_SERVER_PORT = 3978; ///< The default port of the content server (TCP) -static const uint16 NETWORK_CONTENT_MIRROR_PORT = 80; ///< The default port of the content mirror (TCP) -static const uint16 NETWORK_DEFAULT_PORT = 3979; ///< The default port of the game server (TCP & UDP) -static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The default port for admin network -static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP) +static const uint16 NETWORK_MASTER_SERVER_PORT = 3978; ///< The default port of the master server (UDP) +static const uint16 NETWORK_COORDINATOR_SERVER_PORT = 3976; ///< The default port of the Game Coordinator server (TCP) +static const uint16 NETWORK_CONTENT_SERVER_PORT = 3978; ///< The default port of the content server (TCP) +static const uint16 NETWORK_CONTENT_MIRROR_PORT = 80; ///< The default port of the content mirror (TCP) +static const uint16 NETWORK_DEFAULT_PORT = 3979; ///< The default port of the game server (TCP & UDP) +static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The default port for admin network +static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP) -static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet +static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet /* * Technically a TCP packet could become 64kiB, however the high bit is kept so it becomes possible in the future * to go to (significantly) larger packets if needed. This would entail a strategy such as employed for UTF-8. @@ -45,40 +48,42 @@ static const uint16 UDP_MTU * Send_uint16(GB(size, 16, 14) | 0b10 << 14) * Send_uint16(GB(size, 0, 16)) */ -static const uint16 TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet -static const uint16 COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility +static const uint16 TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet +static const uint16 COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility -static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use? -static const byte NETWORK_GAME_INFO_VERSION = 4; ///< What version of game-info do we use? -static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What version of company info is this? -static const byte NETWORK_MASTER_SERVER_VERSION = 2; ///< What version of master-server-protocol do we use? +static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use? +static const byte NETWORK_GAME_INFO_VERSION = 4; ///< What version of game-info do we use? +static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What version of company info is this? +static const byte NETWORK_MASTER_SERVER_VERSION = 2; ///< What version of master-server-protocol do we use? +static const byte NETWORK_COORDINATOR_VERSION = 1; ///< What version of game-coordinator-protocol do we use? -static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0' -static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0' -static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0' -static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536) -static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0' -static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0' -static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH) -static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0' -static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0' -static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0' -static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = COMPAT_MTU-3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes) -static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0' -static const uint NETWORK_CONTENT_FILENAME_LENGTH = 48; ///< The maximum length of a content's filename, in bytes including '\0'. -static const uint NETWORK_CONTENT_NAME_LENGTH = 32; ///< The maximum length of a content's name, in bytes including '\0'. -static const uint NETWORK_CONTENT_VERSION_LENGTH = 16; ///< The maximum length of a content's version, in bytes including '\0'. -static const uint NETWORK_CONTENT_URL_LENGTH = 96; ///< The maximum length of a content's url, in bytes including '\0'. -static const uint NETWORK_CONTENT_DESC_LENGTH = 512; ///< The maximum length of a content's description, in bytes including '\0'. -static const uint NETWORK_CONTENT_TAG_LENGTH = 32; ///< The maximum length of a content's tag, in bytes including '\0'. +static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0' +static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0' +static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0' +static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536) +static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0' +static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0' +static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH) +static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0' +static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0' +static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0' +static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = COMPAT_MTU - 3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes) +static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0' +static const uint NETWORK_CONTENT_FILENAME_LENGTH = 48; ///< The maximum length of a content's filename, in bytes including '\0'. +static const uint NETWORK_CONTENT_NAME_LENGTH = 32; ///< The maximum length of a content's name, in bytes including '\0'. +static const uint NETWORK_CONTENT_VERSION_LENGTH = 16; ///< The maximum length of a content's version, in bytes including '\0'. +static const uint NETWORK_CONTENT_URL_LENGTH = 96; ///< The maximum length of a content's url, in bytes including '\0'. +static const uint NETWORK_CONTENT_DESC_LENGTH = 512; ///< The maximum length of a content's description, in bytes including '\0'. +static const uint NETWORK_CONTENT_TAG_LENGTH = 32; ///< The maximum length of a content's tag, in bytes including '\0'. +static const uint NETWORK_ERROR_DETAIL_LENGTH = 100; ///< The maximum length of the error detail, in bytes including '\0' -static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF +static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF /** * Maximum number of GRFs that can be sent. * This limit is reached when PACKET_UDP_SERVER_RESPONSE reaches the maximum size of UDP_MTU bytes. */ -static const uint NETWORK_MAX_GRF_COUNT = 62; +static const uint NETWORK_MAX_GRF_COUNT = 62; /** * The number of landscapes in OpenTTD. @@ -88,6 +93,6 @@ static const uint NETWORK_MAX_GRF_COUNT * there is a compile assertion to check that this NUM_LANDSCAPE is equal * to NETWORK_NUM_LANDSCAPES. */ -static const uint NETWORK_NUM_LANDSCAPES = 4; +static const uint NETWORK_NUM_LANDSCAPES = 4; #endif /* NETWORK_CORE_CONFIG_H */ diff --git a/src/network/core/tcp_coordinator.cpp b/src/network/core/tcp_coordinator.cpp new file mode 100644 --- /dev/null +++ b/src/network/core/tcp_coordinator.cpp @@ -0,0 +1,80 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file tcp_coordinator.cpp Basic functions to receive and send Game Coordinator packets. + */ + +#include "../../stdafx.h" +#include "../../date_func.h" +#include "../../debug.h" +#include "tcp_coordinator.h" + +#include "../../safeguards.h" + +/** + * Handle the given packet, i.e. pass it to the right. + * parser receive command. + * @param p The packet to handle. + * @return True iff we should immediately handle further packets. + */ +bool NetworkCoordinatorSocketHandler::HandlePacket(Packet *p) +{ + PacketCoordinatorType type = (PacketCoordinatorType)p->Recv_uint8(); + + switch (type) { + case PACKET_COORDINATOR_GC_ERROR: return this->Receive_GC_ERROR(p); + case PACKET_COORDINATOR_SERVER_REGISTER: return this->Receive_SERVER_REGISTER(p); + case PACKET_COORDINATOR_GC_REGISTER_ACK: return this->Receive_GC_REGISTER_ACK(p); + case PACKET_COORDINATOR_SERVER_UPDATE: return this->Receive_SERVER_UPDATE(p); + + default: + Debug(net, 0, "[tcp/coordinator] Received invalid packet type {}", type); + return false; + } +} + +/** + * Receive a packet at TCP level. + * @return Whether at least one packet was received. + */ +bool NetworkCoordinatorSocketHandler::ReceivePackets() +{ + /* + * We read only a few of the packets. This allows the GUI to update when + * a large set of servers is being received. Otherwise the interface + * "hangs" while the game is updating the server-list. + * + * What arbitrary number to choose is the ultimate question though. + */ + Packet *p; + static const int MAX_PACKETS_TO_RECEIVE = 42; + int i = MAX_PACKETS_TO_RECEIVE; + while (--i != 0 && (p = this->ReceivePacket()) != nullptr) { + bool cont = this->HandlePacket(p); + delete p; + if (!cont) return true; + } + + return i != MAX_PACKETS_TO_RECEIVE - 1; +} + +/** + * Helper for logging receiving invalid packets. + * @param type The received packet type. + * @return Always false, as it's an error. + */ +bool NetworkCoordinatorSocketHandler::ReceiveInvalidPacket(PacketCoordinatorType type) +{ + Debug(net, 0, "[tcp/coordinator] Received illegal packet type {}", type); + return false; +} + +bool NetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_ERROR); } +bool NetworkCoordinatorSocketHandler::Receive_SERVER_REGISTER(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_REGISTER); } +bool NetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_REGISTER_ACK); } +bool NetworkCoordinatorSocketHandler::Receive_SERVER_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_UPDATE); } diff --git a/src/network/core/tcp_coordinator.h b/src/network/core/tcp_coordinator.h new file mode 100644 --- /dev/null +++ b/src/network/core/tcp_coordinator.h @@ -0,0 +1,115 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file tcp_coordinator.h Basic functions to receive and send TCP packets to/from the Game Coordinator server. + */ + +#ifndef NETWORK_CORE_TCP_COORDINATOR_H +#define NETWORK_CORE_TCP_COORDINATOR_H + +#include "os_abstraction.h" +#include "tcp.h" +#include "packet.h" +#include "game_info.h" + +/** + * Enum with all types of TCP Game Coordinator packets. The order MUST not be changed. + * + * GC -> packets from Game Coordinator to either Client or Server. + * SERVER -> packets from Server to Game Coordinator. + * CLIENT -> packets from Client to Game Coordinator. + **/ +enum PacketCoordinatorType { + PACKET_COORDINATOR_GC_ERROR, ///< Game Coordinator indicates there was an error. + PACKET_COORDINATOR_SERVER_REGISTER, ///< Server registration. + PACKET_COORDINATOR_GC_REGISTER_ACK, ///< Game Coordinator accepts the registration. + PACKET_COORDINATOR_SERVER_UPDATE, ///< Server sends an set intervals an update of the server. + PACKET_COORDINATOR_END, ///< Must ALWAYS be on the end of this list!! (period). +}; + +/** + * The type of connection the Game Coordinator can detect we have. + */ +enum ConnectionType { + CONNECTION_TYPE_UNKNOWN, ///< The Game Coordinator hasn't informed us yet what type of connection we have. + CONNECTION_TYPE_ISOLATED, ///< The Game Coordinator failed to find a way to connect to your server. Nobody will be able to join. + CONNECTION_TYPE_DIRECT, ///< The Game Coordinator can directly connect to your server. +}; + +/** + * The type of error from the Game Coordinator. + */ +enum NetworkCoordinatorErrorType { + NETWORK_COORDINATOR_ERROR_UNKNOWN, ///< There was an unknown error. + NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED, ///< Your request for registration failed. +}; + +/** Base socket handler for all Game Coordinator TCP sockets. */ +class NetworkCoordinatorSocketHandler : public NetworkTCPSocketHandler { +protected: + bool ReceiveInvalidPacket(PacketCoordinatorType type); + + /** + * Game Coordinator indicates there was an error. This can either be a + * permanent error causing the connection to be dropped, or in response + * to a request that is invalid. + * + * uint8 Type of error (see NetworkCoordinatorErrorType). + * string Details of the error. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_ERROR(Packet *p); + + /** + * Server is starting a multiplayer game and wants to let the + * Game Coordinator know. + * + * uint8 Game Coordinator protocol version. + * uint8 Type of game (see ServerGameType). + * uint16 Local port of the server. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_SERVER_REGISTER(Packet *p); + + /** + * Game Coordinator acknowledges the registration. + * + * uint8 Type of connection was detected (see ConnectionType). + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_REGISTER_ACK(Packet *p); + + /** + * Send an update of the current state of the server to the Game Coordinator. + * + * uint8 Game Coordinator protocol version. + * Serialized NetworkGameInfo. See game_info.hpp for details. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_SERVER_UPDATE(Packet *p); + + bool HandlePacket(Packet *p); +public: + /** + * Create a new cs socket handler for a given cs. + * @param s The socket we are connected with. + */ + NetworkCoordinatorSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {} + + bool ReceivePackets(); +}; + +#endif /* NETWORK_CORE_TCP_COORDINATOR_H */ diff --git a/src/network/network.cpp b/src/network/network.cpp --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -19,6 +19,7 @@ #include "network_udp.h" #include "network_gamelist.h" #include "network_base.h" +#include "network_coordinator.h" #include "core/udp.h" #include "core/host.h" #include "network_gui.h" @@ -591,6 +592,8 @@ void NetworkClose(bool close_admins) } ServerNetworkGameSocketHandler::CloseListeners(); ServerNetworkAdminSocketHandler::CloseListeners(); + + _network_coordinator_client.CloseConnection(); } else if (MyClient::my_client != nullptr) { MyClient::SendQuit(); MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT); @@ -932,6 +935,10 @@ bool NetworkServerStart() NetworkInitGameInfo(); + if (_settings_client.network.server_advertise) { + _network_coordinator_client.Register(); + } + /* execute server initialization script */ IConsoleCmdExec("exec scripts/on_server.scr 0"); /* if the server is dedicated ... add some other script */ @@ -1032,6 +1039,7 @@ static void NetworkSend() void NetworkBackgroundLoop() { _network_content_client.SendReceive(); + _network_coordinator_client.SendReceive(); TCPConnecter::CheckCallbacks(); NetworkHTTPSocketHandler::HTTPReceive(); diff --git a/src/network/network_coordinator.cpp b/src/network/network_coordinator.cpp new file mode 100644 --- /dev/null +++ b/src/network/network_coordinator.cpp @@ -0,0 +1,234 @@ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_coordinator.cpp Game Coordinator sending/receiving part of the network protocol. */ + +#include "../stdafx.h" +#include "../debug.h" +#include "../error.h" +#include "../rev.h" +#include "../settings_type.h" +#include "../strings_func.h" +#include "../window_func.h" +#include "../window_type.h" +#include "network.h" +#include "network_coordinator.h" +#include "network_gamelist.h" +#include "table/strings.h" + +#include "../safeguards.h" + +static const auto NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES = std::chrono::seconds(30); ///< How many time between updates the server sends to the Game Coordinator. +ClientNetworkCoordinatorSocketHandler _network_coordinator_client; ///< The connection to the Game Coordinator. +ConnectionType _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; ///< What type of connection the Game Coordinator detected we are on. + +/** Connect to the Game Coordinator server. */ +class NetworkCoordinatorConnecter : TCPConnecter { +public: + /** + * Initiate the connecting. + * @param address The address of the Game Coordinator server. + */ + NetworkCoordinatorConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_COORDINATOR_SERVER_PORT) {} + + void OnFailure() override + { + _network_coordinator_client.connecting = false; + _network_coordinator_client.CloseConnection(true); + } + + void OnConnect(SOCKET s) override + { + assert(_network_coordinator_client.sock == INVALID_SOCKET); + + _network_coordinator_client.sock = s; + _network_coordinator_client.connecting = false; + } +}; + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p) +{ + NetworkCoordinatorErrorType error = (NetworkCoordinatorErrorType)p->Recv_uint8(); + std::string detail = p->Recv_string(NETWORK_ERROR_DETAIL_LENGTH); + + switch (error) { + case NETWORK_COORDINATOR_ERROR_UNKNOWN: + this->CloseConnection(); + return false; + + case NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED: + SetDParamStr(0, detail); + ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED, STR_JUST_RAW_STRING, WL_ERROR); + + /* To prevent that we constantly try to reconnect, switch to private game. */ + _settings_client.network.server_advertise = false; + + this->CloseConnection(); + return false; + + default: + Debug(net, 0, "Invalid error type {} received from Game Coordinator", error); + this->CloseConnection(); + return false; + } +} + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) +{ + /* Schedule sending an update. */ + this->next_update = std::chrono::steady_clock::now(); + + _network_server_connection_type = (ConnectionType)p->Recv_uint8(); + + if (_network_server_connection_type == CONNECTION_TYPE_ISOLATED) { + ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_ISOLATED, STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL, WL_ERROR); + } + + SetWindowDirty(WC_CLIENT_LIST, 0); + + if (_network_dedicated) { + std::string connection_type; + switch (_network_server_connection_type) { + case CONNECTION_TYPE_ISOLATED: connection_type = "Remote players can't connect"; break; + case CONNECTION_TYPE_DIRECT: connection_type = "Public"; break; + + case CONNECTION_TYPE_UNKNOWN: // Never returned from Game Coordinator. + default: connection_type = "Unknown"; break; // Should never happen, but don't fail if it does. + } + + Debug(net, 3, "----------------------------------------"); + Debug(net, 3, "Your server is now registered with the Game Coordinator:"); + Debug(net, 3, " Game type: Public"); + Debug(net, 3, " Connection type: {}", connection_type); + Debug(net, 3, "----------------------------------------"); + } + + return true; +} + +void ClientNetworkCoordinatorSocketHandler::Connect() +{ + /* We are either already connected or are trying to connect. */ + if (this->sock != INVALID_SOCKET || this->connecting) return; + + this->Reopen(); + + this->connecting = true; + new NetworkCoordinatorConnecter(NETWORK_COORDINATOR_SERVER_HOST); +} + +NetworkRecvStatus ClientNetworkCoordinatorSocketHandler::CloseConnection(bool error) +{ + NetworkCoordinatorSocketHandler::CloseConnection(error); + + this->CloseSocket(); + this->connecting = false; + + _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; + this->next_update = {}; + + SetWindowDirty(WC_CLIENT_LIST, 0); + + return NETWORK_RECV_STATUS_OKAY; +} + +/** + * Register our server to receive our join-key. + */ +void ClientNetworkCoordinatorSocketHandler::Register() +{ + _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; + this->next_update = {}; + + SetWindowDirty(WC_CLIENT_LIST, 0); + + this->Connect(); + + Packet *p = new Packet(PACKET_COORDINATOR_SERVER_REGISTER); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_uint8(SERVER_GAME_TYPE_PUBLIC); + p->Send_uint16(_settings_client.network.server_port); + + this->SendPacket(p); +} + +/** + * Send an update of our server status to the Game Coordinator. + */ +void ClientNetworkCoordinatorSocketHandler::SendServerUpdate() +{ + Debug(net, 6, "Sending server update to Game Coordinator"); + this->next_update = std::chrono::steady_clock::now() + NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES; + + Packet *p = new Packet(PACKET_COORDINATOR_SERVER_UPDATE); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + SerializeNetworkGameInfo(p, GetCurrentNetworkServerGameInfo()); + + this->SendPacket(p); +} + +/** + * Check whether we received/can send some data from/to the Game Coordinator server and + * when that's the case handle it appropriately. + */ +void ClientNetworkCoordinatorSocketHandler::SendReceive() +{ + /* Private games are not listed via the Game Coordinator. */ + if (_network_server && !_settings_client.network.server_advertise) { + if (this->sock != INVALID_SOCKET) { + this->CloseConnection(); + } + return; + } + + static int last_attempt_backoff = 1; + static bool first_reconnect = true; + + if (this->sock == INVALID_SOCKET) { + static std::chrono::steady_clock::time_point last_attempt = {}; + + /* Don't auto-reconnect when we are not a server. */ + if (!_network_server) return; + /* Don't reconnect if we are connecting. */ + if (this->connecting) return; + /* Throttle how often we try to reconnect. */ + if (std::chrono::steady_clock::now() < last_attempt + std::chrono::seconds(1) * last_attempt_backoff) return; + + last_attempt = std::chrono::steady_clock::now(); + /* Delay reconnecting with up to 32 seconds. */ + if (last_attempt_backoff < 32) { + last_attempt_backoff *= 2; + } + + /* Do not reconnect on the first attempt, but only initialize the + * last_attempt variables. Otherwise after an outage all servers + * reconnect at the same time, potentially overwhelming the + * Game Coordinator. */ + if (first_reconnect) { + first_reconnect = false; + return; + } + + Debug(net, 1, "Connection with Game Coordinator lost; reconnecting..."); + this->Register(); + return; + } + + last_attempt_backoff = 1; + first_reconnect = true; + + if (_network_server && _network_server_connection_type != CONNECTION_TYPE_UNKNOWN && std::chrono::steady_clock::now() > this->next_update) { + this->SendServerUpdate(); + } + + if (this->CanSendReceive()) { + this->ReceivePackets(); + } + + this->SendPackets(); +} diff --git a/src/network/network_coordinator.h b/src/network/network_coordinator.h new file mode 100644 --- /dev/null +++ b/src/network/network_coordinator.h @@ -0,0 +1,51 @@ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_coordinator.h Part of the network protocol handling Game Coordinator requests. */ + +#ifndef NETWORK_COORDINATOR_H +#define NETWORK_COORDINATOR_H + +#include "core/tcp_coordinator.h" + +/** + * Game Coordinator communication. + * + * For servers: + * - Server sends SERVER_REGISTER. + * - Game Coordinator probes server to check if it can directly connect. + * - Game Coordinator sends GC_REGISTER_ACK with type of connection. + * - Server sends every 30 seconds SERVER_UPDATE. + */ + +/** Class for handling the client side of the Game Coordinator connection. */ +class ClientNetworkCoordinatorSocketHandler : public NetworkCoordinatorSocketHandler { +private: + std::chrono::steady_clock::time_point next_update; ///< When to send the next update (if server and public). + +protected: + bool Receive_GC_ERROR(Packet *p) override; + bool Receive_GC_REGISTER_ACK(Packet *p) override; + +public: + bool connecting; ///< Are we connecting to the Game Coordinator? + + ClientNetworkCoordinatorSocketHandler() : connecting(false) {} + + NetworkRecvStatus CloseConnection(bool error = true) override; + void SendReceive(); + + void Connect(); + + void Register(); + void SendServerUpdate(); +}; + +extern ClientNetworkCoordinatorSocketHandler _network_coordinator_client; + +#endif /* NETWORK_COORDINATOR_H */ diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -62,7 +62,7 @@ static CompanyID _admin_company_id = INV * do not. */ static const StringID _server_visibility_dropdown[] = { - STR_NETWORK_SERVER_VISIBILITY_PRIVATE, + STR_NETWORK_SERVER_VISIBILITY_LOCAL, STR_NETWORK_SERVER_VISIBILITY_PUBLIC, INVALID_STRING_ID }; @@ -1607,21 +1607,26 @@ static const NWidgetPart _nested_client_ NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER, STR_NULL), SetPadding(4, 4, 0, 4), SetPIP(0, 2, 0), NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_NAME, STR_NULL), - NWidget(NWID_SPACER), SetMinimalSize(20, 0), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_SERVER_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP), EndContainer(), NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY, STR_NULL), - NWidget(NWID_SPACER), SetMinimalSize(20, 0), SetFill(1, 0), SetResize(1, 0), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(1, 0), SetResize(1, 0), NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_CL_SERVER_VISIBILITY), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP), EndContainer(), + NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), + NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_CONNECTION_TYPE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), + EndContainer(), EndContainer(), EndContainer(), NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER, STR_NULL), SetPadding(4, 4, 4, 4), SetPIP(0, 2, 0), NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER_NAME, STR_NULL), - NWidget(NWID_SPACER), SetMinimalSize(20, 0), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_CLIENT_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_CLIENT_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_EDIT_TOOLTIP), EndContainer(), @@ -2050,6 +2055,10 @@ public: SetDParam(0, _server_visibility_dropdown[_settings_client.network.server_advertise]); break; + case WID_CL_SERVER_CONNECTION_TYPE: + SetDParam(0, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN + _network_server_connection_type); + break; + case WID_CL_CLIENT_NAME: SetDParamStr(0, _settings_client.network.client_name); break; diff --git a/src/network/network_internal.h b/src/network/network_internal.h --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -11,6 +11,7 @@ #define NETWORK_INTERNAL_H #include "network_func.h" +#include "core/tcp_coordinator.h" #include "core/tcp_game.h" #include "../command_type.h" @@ -82,6 +83,7 @@ extern NetworkJoinStatus _network_join_s extern uint8 _network_join_waiting; extern uint32 _network_join_bytes; extern uint32 _network_join_bytes_total; +extern ConnectionType _network_server_connection_type; extern uint8 _network_reconnect; diff --git a/src/network/network_type.h b/src/network/network_type.h --- a/src/network/network_type.h +++ b/src/network/network_type.h @@ -35,6 +35,15 @@ enum NetworkVehicleType { NETWORK_VEH_END }; +/** + * Game type the server can be using. + * Used on the network protocol to communicate with Game Coordinator. + */ +enum ServerGameType : uint8 { + SERVER_GAME_TYPE_LOCAL = 0, + SERVER_GAME_TYPE_PUBLIC, +}; + /** 'Unique' identifier to be given to clients */ enum ClientID : uint32 { INVALID_CLIENT_ID = 0, ///< Client is not part of anything diff --git a/src/widgets/network_widget.h b/src/widgets/network_widget.h --- a/src/widgets/network_widget.h +++ b/src/widgets/network_widget.h @@ -101,6 +101,7 @@ enum ClientListWidgets { WID_CL_SERVER_NAME, ///< Server name. WID_CL_SERVER_NAME_EDIT, ///< Edit button for server name. WID_CL_SERVER_VISIBILITY, ///< Server visibility. + WID_CL_SERVER_CONNECTION_TYPE, ///< The type of connection the Game Coordinator detected for this server. WID_CL_CLIENT_NAME, ///< Client name. WID_CL_CLIENT_NAME_EDIT, ///< Edit button for client name. WID_CL_MATRIX, ///< Company/client list.