diff --git a/docs/game_coordinator.md b/docs/game_coordinator.md
--- a/docs/game_coordinator.md
+++ b/docs/game_coordinator.md
@@ -62,3 +62,22 @@ server can continue to talk to each othe
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
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1439,6 +1439,12 @@ STR_CONFIG_SETTING_OSK_ACTIVATION_DOUBLE
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_INDUSTRIE
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_CONNECTIO
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
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
--- 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
--- 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
--- a/src/network/core/config.h
+++ b/src/network/core/config.h
@@ -22,6 +22,7 @@ static const char * const NETWORK_CONTEN
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
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
--- a/src/network/core/tcp_coordinator.cpp
+++ b/src/network/core/tcp_coordinator.cpp
@@ -43,6 +43,7 @@ bool NetworkCoordinatorSocketHandler::Ha
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::Re
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
--- 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
--- /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
--- /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
--- 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 ClientNetworkCoordinatorSocketHandl
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 ClientNetworkCoordinatorSocketHandl
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. */
@@ -580,13 +626,33 @@ void ClientNetworkCoordinatorSocketHandl
}
/**
+ * 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 ClientNetworkCoordinatorSocketHandl
/* 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 ClientNetworkCoordinatorSocketHandl
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
--- 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