diff --git a/docs/game_coordinator.md b/docs/game_coordinator.md index e8260f2e1c..8bf07d8027 100644 --- a/docs/game_coordinator.md +++ b/docs/game_coordinator.md @@ -62,3 +62,22 @@ server can continue to talk to each other. Some NAT gateways do not allow this method; for those this attempt will fail, and this also means that it is not possible to create a connection between the client and server. + +## 3) Via TURN + +As a last resort, the Game Coordinator can decide to connect the client and +server together via TURN. TURN is a relay service, relaying the messages +between client and server. + +As the client and server can already connect to the Game Coordinator, it is +very likely this is successful. + +It is important to note that a relay service has full view of the traffic +send between client and server, and as such it is important that you trust +the relay service used. +For official binaries, this relay service is hosted by openttd.org. The relay +service as hosted by openttd.org only validates it is relaying valid OpenTTD +packets and does no further inspection of the payload itself. +Although in our experience most patch-packs also use the services as offered +by openttd.org, it is possible they use different services. Please be mindful +about this. diff --git a/src/lang/english.txt b/src/lang/english.txt index 277190e1a8..44d226cbf2 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1439,6 +1439,12 @@ STR_CONFIG_SETTING_OSK_ACTIVATION_DOUBLE_CLICK :Double click STR_CONFIG_SETTING_OSK_ACTIVATION_SINGLE_CLICK_FOCUS :Single click (when focussed) STR_CONFIG_SETTING_OSK_ACTIVATION_SINGLE_CLICK :Single click (immediately) +STR_CONFIG_SETTING_USE_RELAY_SERVICE :Use relay service: {STRING2} +STR_CONFIG_SETTING_USE_RELAY_SERVICE_HELPTEXT :If creating a connection to the server fails, one can use a relay service to create a connection. "Never" disallows this, "ask" will ask first, "allow" will allow it without asking +STR_CONFIG_SETTING_USE_RELAY_SERVICE_NEVER :Never +STR_CONFIG_SETTING_USE_RELAY_SERVICE_ASK :Ask +STR_CONFIG_SETTING_USE_RELAY_SERVICE_ALLOW :Allow + STR_CONFIG_SETTING_RIGHT_MOUSE_BTN_EMU :Right-click emulation: {STRING2} STR_CONFIG_SETTING_RIGHT_MOUSE_BTN_EMU_HELPTEXT :Select the method to emulate right mouse-button clicks STR_CONFIG_SETTING_RIGHT_MOUSE_BTN_EMU_COMMAND :Command+Click @@ -1791,6 +1797,7 @@ STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES :{ORANGE}Industr STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST :{ORANGE}Cargo distribution STR_CONFIG_SETTING_AI :{ORANGE}Competitors STR_CONFIG_SETTING_AI_NPC :{ORANGE}Computer players +STR_CONFIG_SETTING_NETWORK :{ORANGE}Network STR_CONFIG_SETTING_PATHFINDER_NPF :NPF STR_CONFIG_SETTING_PATHFINDER_YAPF_RECOMMENDED :YAPF {BLUE}(Recommended) @@ -2167,6 +2174,7 @@ 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 STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_STUN :{BLACK}Behind NAT +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TURN :{BLACK}Via relay ############ End of ConnectionType STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Kick @@ -2180,6 +2188,12 @@ STR_NETWORK_CLIENT_LIST_ASK_CLIENT_BAN :{YELLOW}Are you STR_NETWORK_CLIENT_LIST_ASK_COMPANY_RESET :{YELLOW}Are you sure you want to delete company '{COMPANY}'? STR_NETWORK_CLIENT_LIST_ASK_COMPANY_UNLOCK :{YELLOW}Are you sure you want to reset the password of company '{COMPANY}'? +STR_NETWORK_ASK_RELAY_CAPTION :{WHITE}Use relay? +STR_NETWORK_ASK_RELAY_TEXT :{YELLOW}Failed to establish a connection between you and the server.{}Would you like to relay this session via '{RAW_STRING}'? +STR_NETWORK_ASK_RELAY_NO :{BLACK}No +STR_NETWORK_ASK_RELAY_YES_ONCE :{BLACK}Yes, this once +STR_NETWORK_ASK_RELAY_YES_ALWAYS :{BLACK}Yes, don't ask again + STR_NETWORK_SERVER :Server STR_NETWORK_CLIENT :Client STR_NETWORK_SPECTATORS :Spectators diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 8b01579189..ac500a22cf 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -26,6 +26,8 @@ add_files( network_server.h network_stun.cpp network_stun.h + network_turn.cpp + network_turn.h network_type.h network_udp.cpp network_udp.h diff --git a/src/network/core/CMakeLists.txt b/src/network/core/CMakeLists.txt index bcecad38c8..756fa9e8f3 100644 --- a/src/network/core/CMakeLists.txt +++ b/src/network/core/CMakeLists.txt @@ -30,6 +30,8 @@ add_files( tcp_listen.h tcp_stun.cpp tcp_stun.h + tcp_turn.cpp + tcp_turn.h udp.cpp udp.h ) diff --git a/src/network/core/config.h b/src/network/core/config.h index 1b66c26cf9..10ec070f06 100644 --- a/src/network/core/config.h +++ b/src/network/core/config.h @@ -22,6 +22,7 @@ static const char * const NETWORK_CONTENT_MIRROR_URL = "/bananas"; static const uint16 NETWORK_COORDINATOR_SERVER_PORT = 3976; ///< The default port of the Game Coordinator server (TCP) static const uint16 NETWORK_STUN_SERVER_PORT = 3975; ///< The default port of the STUN server (TCP) +static const uint16 NETWORK_TURN_SERVER_PORT = 3974; ///< The default port of the TURN 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) @@ -49,7 +50,7 @@ static const uint16 COMPAT_MTU = 1460; ///< Numbe static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use? static const byte NETWORK_GAME_INFO_VERSION = 6; ///< 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_COORDINATOR_VERSION = 4; ///< What version of game-coordinator-protocol do we use? +static const byte NETWORK_COORDINATOR_VERSION = 5; ///< 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' diff --git a/src/network/core/tcp_coordinator.cpp b/src/network/core/tcp_coordinator.cpp index 75f3f246f0..44395a905b 100644 --- a/src/network/core/tcp_coordinator.cpp +++ b/src/network/core/tcp_coordinator.cpp @@ -43,6 +43,7 @@ bool NetworkCoordinatorSocketHandler::HandlePacket(Packet *p) case PACKET_COORDINATOR_SERCLI_STUN_RESULT: return this->Receive_SERCLI_STUN_RESULT(p); case PACKET_COORDINATOR_GC_STUN_CONNECT: return this->Receive_GC_STUN_CONNECT(p); case PACKET_COORDINATOR_GC_NEWGRF_LOOKUP: return this->Receive_GC_NEWGRF_LOOKUP(p); + case PACKET_COORDINATOR_GC_TURN_CONNECT: return this->Receive_GC_TURN_CONNECT(p); default: Debug(net, 0, "[tcp/coordinator] Received invalid packet type {}", type); @@ -102,3 +103,4 @@ bool NetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet *p) { retur bool NetworkCoordinatorSocketHandler::Receive_SERCLI_STUN_RESULT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERCLI_STUN_RESULT); } bool NetworkCoordinatorSocketHandler::Receive_GC_STUN_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_STUN_CONNECT); } bool NetworkCoordinatorSocketHandler::Receive_GC_NEWGRF_LOOKUP(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_NEWGRF_LOOKUP); } +bool NetworkCoordinatorSocketHandler::Receive_GC_TURN_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_TURN_CONNECT); } diff --git a/src/network/core/tcp_coordinator.h b/src/network/core/tcp_coordinator.h index b5395ad73a..dea61cdec1 100644 --- a/src/network/core/tcp_coordinator.h +++ b/src/network/core/tcp_coordinator.h @@ -42,6 +42,7 @@ enum PacketCoordinatorType { PACKET_COORDINATOR_SERCLI_STUN_RESULT, ///< Client/server informs the Game Coordinator of the result of the STUN request. PACKET_COORDINATOR_GC_STUN_CONNECT, ///< Game Coordinator tells client/server to connect() reusing the STUN local address. PACKET_COORDINATOR_GC_NEWGRF_LOOKUP, ///< Game Coordinator informs client about NewGRF lookup table updates needed for GC_LISTING. + PACKET_COORDINATOR_GC_TURN_CONNECT, ///< Game Coordinator tells client/server to connect to a specific TURN server. PACKET_COORDINATOR_END, ///< Must ALWAYS be on the end of this list!! (period) }; @@ -53,6 +54,7 @@ enum ConnectionType { 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. CONNECTION_TYPE_STUN, ///< The Game Coordinator can connect to your server via a STUN request. + CONNECTION_TYPE_TURN, ///< The Game Coordinator needs you to connect to a relay. }; /** @@ -288,6 +290,20 @@ protected: */ virtual bool Receive_GC_NEWGRF_LOOKUP(Packet *p); + /** + * Game Coordinator requests that we make a connection to the indicated + * peer, which is a TURN server. + * + * string Token to track the current connect request. + * uint8 Tracking number to track current connect request. + * string Ticket to hand over to the TURN server. + * string Connection string of the TURN server. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_TURN_CONNECT(Packet *p); + bool HandlePacket(Packet *p); public: /** diff --git a/src/network/core/tcp_turn.cpp b/src/network/core/tcp_turn.cpp new file mode 100644 index 0000000000..026b641943 --- /dev/null +++ b/src/network/core/tcp_turn.cpp @@ -0,0 +1,71 @@ +/* + * 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_turn.cpp Basic functions to receive and send TURN packets. + */ + +#include "../../stdafx.h" +#include "../../date_func.h" +#include "../../debug.h" +#include "tcp_turn.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 if we should immediately handle further packets, false otherwise + */ +bool NetworkTurnSocketHandler::HandlePacket(Packet *p) +{ + PacketTurnType type = (PacketTurnType)p->Recv_uint8(); + + switch (type) { + case PACKET_TURN_TURN_ERROR: return this->Receive_TURN_ERROR(p); + case PACKET_TURN_SERCLI_CONNECT: return this->Receive_SERCLI_CONNECT(p); + case PACKET_TURN_TURN_CONNECTED: return this->Receive_TURN_CONNECTED(p); + + default: + Debug(net, 0, "[tcp/turn] Received invalid packet type {}", type); + return false; + } +} + +/** + * Receive a packet at TCP level + * @return Whether at least one packet was received. + */ +bool NetworkTurnSocketHandler::ReceivePackets() +{ + Packet *p; + static const int MAX_PACKETS_TO_RECEIVE = 4; + 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 NetworkTurnSocketHandler::ReceiveInvalidPacket(PacketTurnType type) +{ + Debug(net, 0, "[tcp/turn] Received illegal packet type {}", type); + return false; +} + +bool NetworkTurnSocketHandler::Receive_TURN_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_TURN_TURN_ERROR); } +bool NetworkTurnSocketHandler::Receive_SERCLI_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_TURN_SERCLI_CONNECT); } +bool NetworkTurnSocketHandler::Receive_TURN_CONNECTED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_TURN_TURN_CONNECTED); } diff --git a/src/network/core/tcp_turn.h b/src/network/core/tcp_turn.h new file mode 100644 index 0000000000..0823731993 --- /dev/null +++ b/src/network/core/tcp_turn.h @@ -0,0 +1,79 @@ +/* + * 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_turn.h Basic functions to receive and send TCP packets to/from the TURN server. + */ + +#ifndef NETWORK_CORE_TCP_TURN_H +#define NETWORK_CORE_TCP_TURN_H + +#include "os_abstraction.h" +#include "tcp.h" +#include "packet.h" +#include "game_info.h" + +/** Enum with all types of TCP TURN packets. The order MUST not be changed. **/ +enum PacketTurnType { + PACKET_TURN_TURN_ERROR, ///< TURN server is unable to relay. + PACKET_TURN_SERCLI_CONNECT, ///< Client or server is connecting to the TURN server. + PACKET_TURN_TURN_CONNECTED, ///< TURN server indicates the socket is now being relayed. + PACKET_TURN_END, ///< Must ALWAYS be on the end of this list!! (period) +}; + +/** Base socket handler for all TURN TCP sockets. */ +class NetworkTurnSocketHandler : public NetworkTCPSocketHandler { +protected: + bool ReceiveInvalidPacket(PacketTurnType type); + + /** + * TURN server was unable to connect the client or server based on the + * token. Most likely cause is an invalid token or the other side that + * hasn't connected in a reasonable amount of time. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_TURN_ERROR(Packet *p); + + /** + * Client or servers wants to connect to the TURN server (on request by + * the Game Coordinator). + * + * uint8 Game Coordinator protocol version. + * string Token to track the current TURN request. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_SERCLI_CONNECT(Packet *p); + + /** + * TURN server has connected client and server together and will now relay + * all packets to each other. No further TURN packets should be send over + * this socket, and the socket should be handed over to the game protocol. + * + * string Hostname of the peer. This can be used to check if a client is not banned etc. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_TURN_CONNECTED(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. + * @param address IP etc. of the client. + */ + NetworkTurnSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {} + + bool ReceivePackets(); +}; + +#endif /* NETWORK_CORE_TCP_TURN_H */ diff --git a/src/network/network_coordinator.cpp b/src/network/network_coordinator.cpp index 9fe1e78f0e..6456ac9e89 100644 --- a/src/network/network_coordinator.cpp +++ b/src/network/network_coordinator.cpp @@ -18,6 +18,7 @@ #include "network.h" #include "network_coordinator.h" #include "network_gamelist.h" +#include "network_gui.h" #include "network_internal.h" #include "network_server.h" #include "network_stun.h" @@ -193,6 +194,7 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) case CONNECTION_TYPE_ISOLATED: connection_type = "Remote players can't connect"; break; case CONNECTION_TYPE_DIRECT: connection_type = "Public"; break; case CONNECTION_TYPE_STUN: connection_type = "Behind NAT"; break; + case CONNECTION_TYPE_TURN: connection_type = "Via relay"; 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. @@ -355,6 +357,50 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_NEWGRF_LOOKUP(Packet *p) return true; } +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_TURN_CONNECT(Packet *p) +{ + std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH); + uint8 tracking_number = p->Recv_uint8(); + std::string ticket = p->Recv_string(NETWORK_TOKEN_LENGTH); + std::string connection_string = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH); + + /* Ensure all other pending connection attempts are killed. */ + if (this->game_connecter != nullptr) { + this->game_connecter->Kill(); + this->game_connecter = nullptr; + } + + this->turn_handlers[token] = ClientNetworkTurnSocketHandler::Turn(token, tracking_number, ticket, connection_string); + + if (!_network_server) { + switch (_settings_client.network.use_relay_service) { + case URS_NEVER: + this->ConnectFailure(token, 0); + break; + + case URS_ASK: + ShowNetworkAskRelay(connection_string, token); + break; + + case URS_ALLOW: + this->StartTurnConnection(token); + break; + } + } else { + this->StartTurnConnection(token); + } + + return true; +} + +void ClientNetworkCoordinatorSocketHandler::StartTurnConnection(std::string &token) +{ + auto turn_it = this->turn_handlers.find(token); + if (turn_it == this->turn_handlers.end()) return; + + turn_it->second->Connect(); +} + void ClientNetworkCoordinatorSocketHandler::Connect() { /* We are either already connected or are trying to connect. */ @@ -579,14 +625,34 @@ void ClientNetworkCoordinatorSocketHandler::CloseStunHandler(const std::string & } } +/** + * Close the TURN handler. + * @param token The token used for the TURN handler. + */ +void ClientNetworkCoordinatorSocketHandler::CloseTurnHandler(const std::string &token) +{ + CloseWindowByClass(WC_NETWORK_ASK_RELAY); + + auto turn_it = this->turn_handlers.find(token); + if (turn_it == this->turn_handlers.end()) return; + + turn_it->second->CloseConnection(); + turn_it->second->CloseSocket(); + + /* We don't remove turn_handler here, as we can be called from within that + * turn_handler instance, so our object cannot be free'd yet. Instead, we + * check later if the connection is closed, and free the object then. */ +} + /** * Close everything related to this connection token. * @param token The connection token to close. */ void ClientNetworkCoordinatorSocketHandler::CloseToken(const std::string &token) { - /* Close all remaining STUN connections. */ + /* Close all remaining STUN / TURN connections. */ this->CloseStunHandler(token); + this->CloseTurnHandler(token); /* Close the caller of the connection attempt. */ auto connecter_it = this->connecter.find(token); @@ -610,12 +676,14 @@ void ClientNetworkCoordinatorSocketHandler::CloseAllConnections() /* Mark any pending connecters as failed. */ for (auto &[token, it] : this->connecter) { this->CloseStunHandler(token); + this->CloseTurnHandler(token); it->SetFailure(); /* Inform the Game Coordinator he can stop trying to connect us to the server. */ this->ConnectFailure(token, 0); } this->stun_handlers.clear(); + this->turn_handlers.clear(); this->connecter.clear(); /* Also close any pending invite-code requests. */ @@ -697,4 +765,17 @@ void ClientNetworkCoordinatorSocketHandler::SendReceive() stun_handler->SendReceive(); } } + + /* Check for handlers that are not connecting nor connected. Destroy those objects. */ + for (auto turn_it = this->turn_handlers.begin(); turn_it != this->turn_handlers.end(); /* nothing */) { + if (turn_it->second->connect_started && turn_it->second->connecter == nullptr && !turn_it->second->IsConnected()) { + turn_it = this->turn_handlers.erase(turn_it); + } else { + turn_it++; + } + } + + for (const auto &[token, turn_handler] : this->turn_handlers) { + turn_handler->SendReceive(); + } } diff --git a/src/network/network_coordinator.h b/src/network/network_coordinator.h index 6a7c79e70f..42e16d91dc 100644 --- a/src/network/network_coordinator.h +++ b/src/network/network_coordinator.h @@ -12,6 +12,7 @@ #include "core/tcp_coordinator.h" #include "network_stun.h" +#include "network_turn.h" #include /** @@ -42,6 +43,10 @@ * - a) Server/client connect, client sends CLIENT_CONNECTED to Game Coordinator. * - b) Server/client connect fails, both send SERCLI_CONNECT_FAILED to Game Coordinator. * - Game Coordinator tries other combination if available. + * 3) TURN? + * - Game Coordinator sends GC_TURN_CONNECT to server/client. + * - a) Server/client connect, client sends CLIENT_CONNECTED to Game Coordinator. + * - b) Server/client connect fails, both send SERCLI_CONNECT_FAILED to Game Coordinator. * - If all fails, Game Coordinator sends GC_CONNECT_FAILED to indicate no connection is possible. */ @@ -52,6 +57,7 @@ private: std::map connecter; ///< Based on tokens, the current connecters that are pending. std::map connecter_pre; ///< Based on invite codes, the current connecters that are pending. std::map>> stun_handlers; ///< All pending STUN handlers, stored by token:family. + std::map> turn_handlers; ///< Pending TURN handler (if any), stored by token. TCPConnecter *game_connecter = nullptr; ///< Pending connecter to the game server. uint32 newgrf_lookup_table_cursor = 0; ///< Last received cursor for the #GameInfoNewGRFLookupTable updates. @@ -67,6 +73,7 @@ protected: bool Receive_GC_STUN_REQUEST(Packet *p) override; bool Receive_GC_STUN_CONNECT(Packet *p) override; bool Receive_GC_NEWGRF_LOOKUP(Packet *p) override; + bool Receive_GC_TURN_CONNECT(Packet *p) override; public: /** The idle timeout; when to close the connection because it's idle. */ @@ -88,12 +95,14 @@ public: void CloseToken(const std::string &token); void CloseAllConnections(); void CloseStunHandler(const std::string &token, uint8 family = AF_UNSPEC); + void CloseTurnHandler(const std::string &token); void Register(); void SendServerUpdate(); void GetListing(); void ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter); + void StartTurnConnection(std::string &token); }; extern ClientNetworkCoordinatorSocketHandler _network_coordinator_client; diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 1368ac0ad9..a535962b53 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -2699,3 +2699,105 @@ void ShowNetworkCompanyPasswordWindow(Window *parent) new NetworkCompanyPasswordWindow(&_network_company_password_window_desc, parent); } + +/** + * Window used for asking the user if he is okay using a TURN server. + */ +struct NetworkAskRelayWindow : public Window { + std::string connection_string; ///< The TURN server we want to connect to. + std::string token; ///< The token for this connection. + + NetworkAskRelayWindow(WindowDesc *desc, Window *parent, const std::string &connection_string, const std::string &token) : Window(desc), connection_string(connection_string), token(token) + { + this->parent = parent; + this->InitNested(0); + } + + void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override + { + if (widget == WID_NAR_TEXT) { + *size = GetStringBoundingBox(STR_NETWORK_ASK_RELAY_TEXT); + size->height = GetStringHeight(STR_NETWORK_ASK_RELAY_TEXT, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT) + WD_FRAMETEXT_BOTTOM + WD_FRAMETEXT_TOP; + } + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (widget == WID_NAR_TEXT) { + DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMETEXT_TOP, r.bottom - WD_FRAMETEXT_BOTTOM, STR_NETWORK_ASK_RELAY_TEXT, TC_FROMSTRING, SA_CENTER); + } + } + + void FindWindowPlacementAndResize(int def_width, int def_height) override + { + /* Position query window over the calling window, ensuring it's within screen bounds. */ + this->left = Clamp(parent->left + (parent->width / 2) - (this->width / 2), 0, _screen.width - this->width); + this->top = Clamp(parent->top + (parent->height / 2) - (this->height / 2), 0, _screen.height - this->height); + this->SetDirty(); + } + + void SetStringParameters(int widget) const override + { + switch (widget) { + case WID_NAR_TEXT: + SetDParamStr(0, this->connection_string); + break; + } + } + + void OnClick(Point pt, int widget, int click_count) override + { + switch (widget) { + case WID_NAR_NO: + _network_coordinator_client.ConnectFailure(this->token, 0); + this->Close(); + break; + + case WID_NAR_YES_ONCE: + _network_coordinator_client.StartTurnConnection(this->token); + this->Close(); + break; + + case WID_NAR_YES_ALWAYS: + _settings_client.network.use_relay_service = URS_ALLOW; + _network_coordinator_client.StartTurnConnection(this->token); + this->Close(); + break; + } + } +}; + +static const NWidgetPart _nested_network_ask_relay_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_RED), + NWidget(WWT_CAPTION, COLOUR_RED, WID_NAR_CAPTION), SetDataTip(STR_NETWORK_ASK_RELAY_CAPTION, STR_NULL), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_RED), SetPIP(0, 0, 8), + NWidget(WWT_TEXT, COLOUR_RED, WID_NAR_TEXT), SetAlignment(SA_HOR_CENTER), SetFill(1, 1), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 15, 10), + NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NAR_NO), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_RELAY_NO, STR_NULL), + NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NAR_YES_ONCE), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_RELAY_YES_ONCE, STR_NULL), + NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NAR_YES_ALWAYS), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_RELAY_YES_ALWAYS, STR_NULL), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _network_ask_relay_desc( + WDP_CENTER, nullptr, 0, 0, + WC_NETWORK_ASK_RELAY, WC_NONE, + WDF_MODAL, + _nested_network_ask_relay_widgets, lengthof(_nested_network_ask_relay_widgets) +); + +/** + * Show a modal confirmation window with "no" / "yes, once" / "yes, always" buttons. + * @param connection_string The relay server we want to connect to. + * @param token The token for this connection. + */ +void ShowNetworkAskRelay(const std::string &connection_string, const std::string &token) +{ + CloseWindowByClass(WC_NETWORK_ASK_RELAY); + + Window *parent = FindWindowById(WC_MAIN_WINDOW, 0); + new NetworkAskRelayWindow(&_network_ask_relay_desc, parent, connection_string, token); +} diff --git a/src/network/network_gui.h b/src/network/network_gui.h index 855d7d53c6..06b501cb5d 100644 --- a/src/network/network_gui.h +++ b/src/network/network_gui.h @@ -39,5 +39,6 @@ struct NetworkCompanyInfo : NetworkCompanyStats { NetworkCompanyInfo *GetLobbyCompanyInfo(CompanyID company); NetworkGameList *GetLobbyGameInfo(); +void ShowNetworkAskRelay(const std::string &connection_string, const std::string &token); #endif /* NETWORK_GUI_H */ diff --git a/src/network/network_turn.cpp b/src/network/network_turn.cpp new file mode 100644 index 0000000000..e04bec47ca --- /dev/null +++ b/src/network/network_turn.cpp @@ -0,0 +1,135 @@ +/* + * 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_turn.cpp TURN sending/receiving part of the network protocol. */ + +#include "../stdafx.h" +#include "../debug.h" +#include "../error.h" +#include "../strings_func.h" +#include "network_coordinator.h" +#include "network_turn.h" + +#include "table/strings.h" + +#include "../safeguards.h" + +/** Connect to the TURN server. */ +class NetworkTurnConnecter : public TCPConnecter { +private: + ClientNetworkTurnSocketHandler *handler; + +public: + /** + * Initiate the connecting. + * @param connection_string The address of the TURN server. + */ + NetworkTurnConnecter(ClientNetworkTurnSocketHandler *handler, const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_TURN_SERVER_PORT), handler(handler) {} + + void OnFailure() override + { + this->handler->connecter = nullptr; + + this->handler->ConnectFailure(); + } + + void OnConnect(SOCKET s) override + { + this->handler->connecter = nullptr; + + handler->sock = s; + } +}; + +bool ClientNetworkTurnSocketHandler::Receive_TURN_ERROR(Packet *p) +{ + this->ConnectFailure(); + + return false; +} + +bool ClientNetworkTurnSocketHandler::Receive_TURN_CONNECTED(Packet *p) +{ + std::string hostname = p->Recv_string(NETWORK_HOSTNAME_LENGTH); + + /* Act like we no longer have a socket, as we are handing it over to the + * game handler. */ + SOCKET game_sock = this->sock; + this->sock = INVALID_SOCKET; + + NetworkAddress address = NetworkAddress(hostname, NETWORK_DEFAULT_PORT); + _network_coordinator_client.ConnectSuccess(this->token, game_sock, address); + + return false; +} + +/** + * Connect to the TURN server. + */ +void ClientNetworkTurnSocketHandler::Connect() +{ + this->connect_started = true; + this->connecter = new NetworkTurnConnecter(this, this->connection_string); +} + +/** + * Prepare a TURN connection. + * Not until you run Connect() on the resulting instance will it start setting + * up the TURN connection. + * @param token The token as received from the Game Coordinator. + * @param tracking_number The tracking number as recieved from the Game Coordinator. + * @param ticket The ticket as received from the Game Coordinator. + * @param connection_string Connection string of the TURN server. + * @return The handler for this TURN connection. + */ +/* static */ std::unique_ptr ClientNetworkTurnSocketHandler::Turn(const std::string &token, uint8 tracking_number, const std::string &ticket, const std::string &connection_string) +{ + auto turn_handler = std::make_unique(token, tracking_number, connection_string); + + Packet *p = new Packet(PACKET_TURN_SERCLI_CONNECT); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_string(ticket); + + turn_handler->SendPacket(p); + + return turn_handler; +} + +void ClientNetworkTurnSocketHandler::ConnectFailure() +{ + _network_coordinator_client.ConnectFailure(this->token, this->tracking_number); +} + +NetworkRecvStatus ClientNetworkTurnSocketHandler::CloseConnection(bool error) +{ + NetworkTurnSocketHandler::CloseConnection(error); + + /* If our connecter is still pending, shut it down too. Otherwise the + * callback of the connecter can call into us, and our object is most + * likely about to be destroyed. */ + if (this->connecter != nullptr) { + this->connecter->Kill(); + this->connecter = nullptr; + } + + return NETWORK_RECV_STATUS_OKAY; +} + +/** + * Check whether we received/can send some data from/to the TURN server and + * when that's the case handle it appropriately + */ +void ClientNetworkTurnSocketHandler::SendReceive() +{ + if (this->sock == INVALID_SOCKET) return; + + if (this->CanSendReceive()) { + this->ReceivePackets(); + } + + this->SendPackets(); +} diff --git a/src/network/network_turn.h b/src/network/network_turn.h new file mode 100644 index 0000000000..cc569a977d --- /dev/null +++ b/src/network/network_turn.h @@ -0,0 +1,41 @@ +/* + * 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_turn.h Part of the network protocol handling TURN requests. */ + +#ifndef NETWORK_TURN_H +#define NETWORK_TURN_H + +#include "core/tcp_turn.h" + +/** Class for handling the client side of the TURN connection. */ +class ClientNetworkTurnSocketHandler : public NetworkTurnSocketHandler { +private: + std::string token; ///< Token of this connection. + uint8 tracking_number; ///< Tracking number of this connection. + std::string connection_string; ///< The connection string of the TURN server we are connecting to. + +protected: + bool Receive_TURN_ERROR(Packet *p) override; + bool Receive_TURN_CONNECTED(Packet *p) override; + +public: + TCPConnecter *connecter = nullptr; ///< Connecter instance. + bool connect_started = false; ///< Whether we started the connection. + + ClientNetworkTurnSocketHandler(const std::string &token, uint8 tracking_number, const std::string &connection_string) : token(token), tracking_number(tracking_number), connection_string(connection_string) {} + + NetworkRecvStatus CloseConnection(bool error = true) override; + void SendReceive(); + + void Connect(); + void ConnectFailure(); + + static std::unique_ptr Turn(const std::string &token, uint8 tracking_number, const std::string &ticket, const std::string &connection_string); +}; + +#endif /* NETWORK_TURN_H */ diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index a63448aef6..04deb93ebc 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1846,6 +1846,11 @@ static SettingsContainer &GetSettingsTree() ai->Add(new SettingEntry("economy.min_years_for_shares")); } + SettingsPage *network = main->Add(new SettingsPage(STR_CONFIG_SETTING_NETWORK)); + { + network->Add(new SettingEntry("network.use_relay_service")); + } + main->Init(); } return *main; diff --git a/src/settings_type.h b/src/settings_type.h index 8db0febad6..ababe718b2 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -59,6 +59,13 @@ enum IndustryDensity { ID_END, ///< Number of industry density settings. }; +/** Possible values for "userelayservice" setting. */ +enum UseRelayService { + URS_NEVER = 0, + URS_ASK, + URS_ALLOW, +}; + /** Settings related to the difficulty of the game */ struct DifficultySettings { byte competitor_start_time; ///< Unused value, used to load old savegames. @@ -290,6 +297,7 @@ struct NetworkSettings { bool reload_cfg; ///< reload the config file before restarting std::string last_joined; ///< Last joined server bool no_http_content_downloads; ///< do not do content downloads over HTTP + UseRelayService use_relay_service; ///< Use relay service? }; /** Settings related to the creation of games. */ diff --git a/src/table/settings/network_settings.ini b/src/table/settings/network_settings.ini index 552f588234..86f8aafc87 100644 --- a/src/table/settings/network_settings.ini +++ b/src/table/settings/network_settings.ini @@ -10,6 +10,7 @@ static void UpdateClientConfigValues(); static std::initializer_list _server_game_type{"local", "public", "invite-only"}; +static std::initializer_list _use_relay_service{"never", "ask", "allow"}; static const SettingVariant _network_settings_table[] = { [post-amble] @@ -261,3 +262,16 @@ var = network.no_http_content_downloads flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC def = false cat = SC_EXPERT + +[SDTC_OMANY] +var = network.use_relay_service +type = SLE_UINT8 +flags = SF_GUI_DROPDOWN | SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC +def = URS_ASK +min = URS_NO +max = URS_ALLOW +full = _use_relay_service +str = STR_CONFIG_SETTING_USE_RELAY_SERVICE +strhelp = STR_CONFIG_SETTING_USE_RELAY_SERVICE_HELPTEXT +strval = STR_CONFIG_SETTING_USE_RELAY_SERVICE_NEVER +cat = SC_BASIC diff --git a/src/widgets/network_widget.h b/src/widgets/network_widget.h index c8ec22e861..cef564e980 100644 --- a/src/widgets/network_widget.h +++ b/src/widgets/network_widget.h @@ -128,4 +128,13 @@ enum NetworkCompanyPasswordWidgets { WID_NCP_OK, ///< Safe the password etc. }; +/** Widgets of the #NetworkAskRelayWindow class. */ +enum NetworkAskRelayWidgets { + WID_NAR_CAPTION, ///< Caption of the window. + WID_NAR_TEXT, ///< Text in the window. + WID_NAR_NO, ///< "No" button. + WID_NAR_YES_ONCE, ///< "Yes, once" button. + WID_NAR_YES_ALWAYS, ///< "Yes, always" button. +}; + #endif /* WIDGETS_NETWORK_WIDGET_H */ diff --git a/src/window.cpp b/src/window.cpp index 1c2a305ba8..4072885116 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1332,6 +1332,7 @@ static uint GetWindowZPriority(WindowClass wc) case WC_ERRMSG: case WC_CONFIRM_POPUP_QUERY: + case WC_NETWORK_ASK_RELAY: case WC_MODAL_PROGRESS: case WC_NETWORK_STATUS_WINDOW: case WC_SAVE_PRESET: diff --git a/src/window_type.h b/src/window_type.h index 2b486fbdf6..00aaaf1fde 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -478,6 +478,12 @@ enum WindowClass { */ WC_NETWORK_STATUS_WINDOW, + /** + * Network ask relay window; %Window numbers: + * - 0 - #NetworkAskRelayWidgets + */ + WC_NETWORK_ASK_RELAY, + /** * Chatbox; %Window numbers: * - #DestType = #NetWorkChatWidgets