summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/game_coordinator.md19
-rw-r--r--src/lang/english.txt14
-rw-r--r--src/network/CMakeLists.txt2
-rw-r--r--src/network/core/CMakeLists.txt2
-rw-r--r--src/network/core/config.h3
-rw-r--r--src/network/core/tcp_coordinator.cpp2
-rw-r--r--src/network/core/tcp_coordinator.h16
-rw-r--r--src/network/core/tcp_turn.cpp71
-rw-r--r--src/network/core/tcp_turn.h79
-rw-r--r--src/network/network_coordinator.cpp83
-rw-r--r--src/network/network_coordinator.h9
-rw-r--r--src/network/network_gui.cpp102
-rw-r--r--src/network/network_gui.h1
-rw-r--r--src/network/network_turn.cpp135
-rw-r--r--src/network/network_turn.h41
-rw-r--r--src/settings_gui.cpp5
-rw-r--r--src/settings_type.h8
-rw-r--r--src/table/settings/network_settings.ini14
-rw-r--r--src/widgets/network_widget.h9
-rw-r--r--src/window.cpp1
-rw-r--r--src/window_type.h6
21 files changed, 620 insertions, 2 deletions
diff --git a/docs/game_coordinator.md b/docs/game_coordinator.md
index e8260f2e1..8bf07d802 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 277190e1a..44d226cbf 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 8b0157918..ac500a22c 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 bcecad38c..756fa9e8f 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 1b66c26cf..10ec070f0 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 75f3f246f..44395a905 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 b5395ad73..dea61cdec 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 000000000..026b64194
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @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 000000000..082373199
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @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 9fe1e78f0..6456ac9e8 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. */
@@ -580,13 +626,33 @@ 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 6a7c79e70..42e16d91d 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 <map>
/**
@@ -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<std::string, TCPServerConnecter *> connecter; ///< Based on tokens, the current connecters that are pending.
std::map<std::string, TCPServerConnecter *> connecter_pre; ///< Based on invite codes, the current connecters that are pending.
std::map<std::string, std::map<int, std::unique_ptr<ClientNetworkStunSocketHandler>>> stun_handlers; ///< All pending STUN handlers, stored by token:family.
+ std::map<std::string, std::unique_ptr<ClientNetworkTurnSocketHandler>> 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 1368ac0ad..a535962b5 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 855d7d53c..06b501cb5 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 000000000..e04bec47c
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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> ClientNetworkTurnSocketHandler::Turn(const std::string &token, uint8 tracking_number, const std::string &ticket, const std::string &connection_string)
+{
+ auto turn_handler = std::make_unique<ClientNetworkTurnSocketHandler>(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 000000000..cc569a977
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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<ClientNetworkTurnSocketHandler> 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 a63448aef..04deb93eb 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 8db0febad..ababe718b 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 552f58823..86f8aafc8 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<const char*> _server_game_type{"local", "public", "invite-only"};
+static std::initializer_list<const char*> _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 c8ec22e86..cef564e98 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 1c2a305ba..407288511 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 2b486fbdf..00aaaf1fd 100644
--- a/src/window_type.h
+++ b/src/window_type.h
@@ -479,6 +479,12 @@ enum WindowClass {
WC_NETWORK_STATUS_WINDOW,
/**
+ * Network ask relay window; %Window numbers:
+ * - 0 - #NetworkAskRelayWidgets
+ */
+ WC_NETWORK_ASK_RELAY,
+
+ /**
* Chatbox; %Window numbers:
* - #DestType = #NetWorkChatWidgets
*/