Merge branch 'master' into jgrpp-beta

# Conflicts:
#	src/console_cmds.cpp
#	src/debug.cpp
#	src/lang/vietnamese.txt
#	src/network/core/address.cpp
#	src/network/core/address.h
#	src/network/core/config.h
#	src/network/core/os_abstraction.cpp
#	src/network/core/os_abstraction.h
#	src/network/core/tcp_listen.h
#	src/network/core/udp.cpp
#	src/network/core/udp.h
#	src/network/network.cpp
#	src/network/network_client.cpp
#	src/network/network_gamelist.cpp
#	src/network/network_server.cpp
#	src/network/network_udp.cpp
#	src/newgrf.cpp
#	src/openttd.cpp
#	src/saveload/saveload.h
#	src/settings.cpp
#	src/settings_table.cpp
#	src/settings_type.h
#	src/table/settings/network_settings.ini
This commit is contained in:
Jonathan G Rennison
2021-11-02 00:51:54 +00:00
124 changed files with 3050 additions and 1274 deletions

View File

@@ -14,6 +14,8 @@ add_files(
network_content.h
network_content_gui.cpp
network_content_gui.h
network_coordinator.cpp
network_coordinator.h
network_func.h
network_gamelist.cpp
network_gamelist.h
@@ -22,6 +24,8 @@ add_files(
network_internal.h
network_server.cpp
network_server.h
network_stun.cpp
network_stun.h
network_type.h
network_udp.cpp
network_udp.h

View File

@@ -1,6 +1,7 @@
add_files(
address.cpp
address.h
config.cpp
config.h
core.cpp
core.h
@@ -20,11 +21,15 @@ add_files(
tcp_content.cpp
tcp_content.h
tcp_content_type.h
tcp_coordinator.cpp
tcp_coordinator.h
tcp_game.cpp
tcp_game.h
tcp_http.cpp
tcp_http.h
tcp_listen.h
tcp_stun.cpp
tcp_stun.h
udp.cpp
udp.h
)

View File

@@ -10,6 +10,7 @@
#include "../../stdafx.h"
#include "address.h"
#include "../network_internal.h"
#include "../../debug.h"
#include "../../safeguards.h"
@@ -335,13 +336,12 @@ static SOCKET ListenLoopProc(addrinfo *runp)
DEBUG(net, 1, "Setting no-delay mode failed: %s", NetworkError::GetLast().AsString());
}
int on = 1;
/* The (const char*) cast is needed for windows!! */
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)) == -1) {
if (!SetReusePort(sock)) {
DEBUG(net, 0, "Setting reuse-address mode failed: %s", NetworkError::GetLast().AsString());
}
#ifndef __OS2__
int on = 1;
if (runp->ai_family == AF_INET6 &&
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&on, sizeof(on)) == -1) {
DEBUG(net, 3, "Could not disable IPv4 over IPv6: %s", NetworkError::GetLast().AsString());
@@ -422,6 +422,38 @@ void NetworkAddress::Listen(int socktype, SocketList *sockets)
}
}
/**
* Get the peer address of a socket as NetworkAddress.
* @param sock The socket to get the peer address of.
* @return The NetworkAddress of the peer address.
*/
/* static */ NetworkAddress NetworkAddress::GetPeerAddress(SOCKET sock)
{
sockaddr_storage addr = {};
socklen_t addr_len = sizeof(addr);
if (getpeername(sock, (sockaddr *)&addr, &addr_len) != 0) {
DEBUG(net, 0, "Failed to get address of the peer: %s", NetworkError::GetLast().AsString());
return NetworkAddress();
}
return NetworkAddress(addr, addr_len);
}
/**
* Get the local address of a socket as NetworkAddress.
* @param sock The socket to get the local address of.
* @return The NetworkAddress of the local address.
*/
/* static */ NetworkAddress NetworkAddress::GetSockAddress(SOCKET sock)
{
sockaddr_storage addr = {};
socklen_t addr_len = sizeof(addr);
if (getsockname(sock, (sockaddr *)&addr, &addr_len) != 0) {
DEBUG(net, 0, "Failed to get address of the socket: %s", NetworkError::GetLast().AsString());
return NetworkAddress();
}
return NetworkAddress(addr, addr_len);
}
/**
* Get the peer name of a socket in string format.
* @param sock The socket to get the peer name of.
@@ -429,8 +461,27 @@ void NetworkAddress::Listen(int socktype, SocketList *sockets)
*/
/* static */ const std::string NetworkAddress::GetPeerName(SOCKET sock)
{
sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
getpeername(sock, (sockaddr *)&addr, &addr_len);
return NetworkAddress(addr, addr_len).GetAddressAsString();
return NetworkAddress::GetPeerAddress(sock).GetAddressAsString();
}
/**
* Convert a string containing either "hostname", "hostname:port" or invite code
* to a ServerAddress, where the string can be postfixed with "#company" to
* indicate the requested company.
*
* @param connection_string The string to parse.
* @param default_port The default port to set port to if not in connection_string.
* @param company Pointer to the company variable to set iff indicated.
* @return A valid ServerAddress of the parsed information.
*/
/* static */ ServerAddress ServerAddress::Parse(const std::string &connection_string, uint16 default_port, CompanyID *company_id)
{
if (StrStartsWith(connection_string, "+")) {
std::string_view invite_code = ParseCompanyFromConnectionString(connection_string, company_id);
return ServerAddress(SERVER_ADDRESS_INVITE_CODE, std::string(invite_code));
}
uint16 port = default_port;
std::string_view ip = ParseFullConnectionString(connection_string, port, company_id);
return ServerAddress(SERVER_ADDRESS_DIRECT, std::string(ip) + ":" + std::to_string(port));
}

View File

@@ -12,6 +12,7 @@
#include "os_abstraction.h"
#include "config.h"
#include "../../company_type.h"
#include "../../string_func.h"
#include "../../core/smallmap_type.hpp"
@@ -175,6 +176,8 @@ public:
static const char *SocketTypeAsString(int socktype);
static const char *AddressFamilyAsString(int family);
static NetworkAddress GetPeerAddress(SOCKET sock);
static NetworkAddress GetSockAddress(SOCKET sock);
static const std::string GetPeerName(SOCKET sock);
};
@@ -191,4 +194,38 @@ private:
char buf[NETWORK_HOSTNAME_PORT_LENGTH + 7];
};
/**
* Types of server addresses we know.
*
* Sorting will prefer entries at the top of this list above ones at the bottom.
*/
enum ServerAddressType {
SERVER_ADDRESS_DIRECT, ///< Server-address is based on an hostname:port.
SERVER_ADDRESS_INVITE_CODE, ///< Server-address is based on an invite code.
};
/**
* Address to a game server.
*
* This generalises addresses which are based on different identifiers.
*/
class ServerAddress {
private:
/**
* Create a new ServerAddress object.
*
* Please use ServerAddress::Parse() instead of calling this directly.
*
* @param type The type of the ServerAdress.
* @param connection_string The connection_string that belongs to this ServerAddress type.
*/
ServerAddress(ServerAddressType type, const std::string &connection_string) : type(type), connection_string(connection_string) {}
public:
ServerAddressType type; ///< The type of this ServerAddress.
std::string connection_string; ///< The connection string for this ServerAddress.
static ServerAddress Parse(const std::string &connection_string, uint16 default_port, CompanyID *company_id = nullptr);
};
#endif /* NETWORK_CORE_ADDRESS_H */

View File

@@ -0,0 +1,69 @@
/*
* 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 config.cpp Configuration of the connection strings for network stuff using environment variables.
*/
#include "../../stdafx.h"
#include <cstdlib>
#include "../../string_func.h"
#include "../../safeguards.h"
/**
* Get the environment variable using std::getenv and when it is an empty string (or nullptr), return a fallback value instead.
* @param variable The environment variable to read from.
* @param fallback The fallback in case the environment variable is not set.
* @return The environment value, or when that does not exist the given fallback value.
*/
static const char *GetEnv(const char *variable, const char *fallback)
{
const char *value = std::getenv(variable);
return StrEmpty(value) ? fallback : value;
}
/**
* Get the connection string for the game coordinator from the environment variable OTTD_COORDINATOR_CS,
* or when it has not been set a hard coded default DNS hostname of the production server.
* @return The game coordinator's connection string.
*/
const char *NetworkCoordinatorConnectionString()
{
return GetEnv("OTTD_COORDINATOR_CS", "coordinator.openttd.org");
}
/**
* Get the connection string for the STUN server from the environment variable OTTD_STUN_CS,
* or when it has not been set a hard coded default DNS hostname of the production server.
* @return The STUN server's connection string.
*/
const char *NetworkStunConnectionString()
{
return GetEnv("OTTD_STUN_CS", "stun.openttd.org");
}
/**
* Get the connection string for the content server from the environment variable OTTD_CONTENT_SERVER_CS,
* or when it has not been set a hard coded default DNS hostname of the production server.
* @return The content server's connection string.
*/
const char *NetworkContentServerConnectionString()
{
return GetEnv("OTTD_CONTENT_SERVER_CS", "content.openttd.org");
}
/**
* Get the connection string for the content mirror from the environment variable OTTD_CONTENT_MIRROR_CS,
* or when it has not been set a hard coded default DNS hostname of the production server.
* @return The content mirror's connection string.
*/
const char *NetworkContentMirrorConnectionString()
{
return GetEnv("OTTD_CONTENT_MIRROR_CS", "binaries.openttd.org");
}

View File

@@ -12,26 +12,24 @@
#ifndef NETWORK_CORE_CONFIG_H
#define NETWORK_CORE_CONFIG_H
/** DNS hostname of the masterserver */
static const char * const NETWORK_MASTER_SERVER_HOST = "master.openttd.org";
/** DNS hostname of the content server */
static const char * const NETWORK_CONTENT_SERVER_HOST = "content.openttd.org";
/** DNS hostname of the HTTP-content mirror server */
static const char * const NETWORK_CONTENT_MIRROR_HOST = "binaries.openttd.org";
const char *NetworkCoordinatorConnectionString();
const char *NetworkStunConnectionString();
const char *NetworkContentServerConnectionString();
const char *NetworkContentMirrorConnectionString();
/** URL of the HTTP mirror system */
static const char * const NETWORK_CONTENT_MIRROR_URL = "/bananas";
/** Message sent to the masterserver to 'identify' this client as OpenTTD */
static const char * const NETWORK_MASTER_SERVER_WELCOME_MESSAGE = "OpenTTDRegister";
static const uint16 NETWORK_MASTER_SERVER_PORT = 3978; ///< The default port of the master server (UDP)
static const uint16 NETWORK_CONTENT_SERVER_PORT = 3978; ///< The default port of the content server (TCP)
static const uint16 NETWORK_CONTENT_MIRROR_PORT = 80; ///< The default port of the content mirror (TCP)
static const uint16 NETWORK_DEFAULT_PORT = 3979; ///< The default port of the game server (TCP & UDP)
static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The default port for admin network
static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP)
static const uint16 NETWORK_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_CONTENT_SERVER_PORT = 3978; ///< The default port of the content server (TCP)
static const uint16 NETWORK_CONTENT_MIRROR_PORT = 80; ///< The default port of the content mirror (TCP)
static const uint16 NETWORK_DEFAULT_PORT = 3979; ///< The default port of the game server (TCP & UDP)
static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The default port for admin network
static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP)
static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet
static const uint16 UDP_MTU_SHORT = 1400; ///< Number of bytes we can pack in a single UDP packet (conservative)
static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet
static const uint16 UDP_MTU_SHORT = 1400; ///< Number of bytes we can pack in a single UDP packet (conservative)
/*
* Technically a TCP packet could become 64kiB, however the high bit is kept so it becomes possible in the future
* to go to (significantly) larger packets if needed. This would entail a strategy such as employed for UTF-8.
@@ -46,42 +44,56 @@ static const uint16 UDP_MTU_SHORT = 1400; ///< Number of
* Send_uint16(GB(size, 16, 14) | 0b10 << 14)
* Send_uint16(GB(size, 0, 16))
*/
static const uint16 TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet
static const uint16 COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility
static const uint16 TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet
static const uint16 COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility
static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use?
static const byte NETWORK_GAME_INFO_VERSION = 4; ///< What version of game-info do we use?
static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What version of company info is this?
static const byte NETWORK_MASTER_SERVER_VERSION = 2; ///< What version of master-server-protocol do we use?
static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use?
static const byte NETWORK_GAME_INFO_VERSION = 5; ///< 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 = 3; ///< What version of game-coordinator-protocol do we use?
static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0'
static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0'
static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0'
static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536)
static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0'
static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0'
static const uint NETWORK_LONG_REVISION_LENGTH = 64; ///< The maximum length of the revision, in bytes including '\0'
static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH)
static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0'
static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0'
static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0'
static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = COMPAT_MTU-3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes)
static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0'
static const uint NETWORK_CONTENT_FILENAME_LENGTH = 48; ///< The maximum length of a content's filename, in bytes including '\0'.
static const uint NETWORK_CONTENT_NAME_LENGTH = 64; ///< The maximum length of a content's name, in bytes including '\0'.
static const uint NETWORK_CONTENT_VERSION_LENGTH = 16; ///< The maximum length of a content's version, in bytes including '\0'.
static const uint NETWORK_CONTENT_URL_LENGTH = 96; ///< The maximum length of a content's url, in bytes including '\0'.
static const uint NETWORK_CONTENT_DESC_LENGTH = 512; ///< The maximum length of a content's description, in bytes including '\0'.
static const uint NETWORK_CONTENT_TAG_LENGTH = 32; ///< The maximum length of a content's tag, in bytes including '\0'.
static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0'
static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0'
static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0'
static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536)
static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0'
static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0'
static const uint NETWORK_LONG_REVISION_LENGTH = 64; ///< The maximum length of the revision, in bytes including '\0'
static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH)
static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0'
static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0'
static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0'
static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = COMPAT_MTU - 3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes)
static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0'
static const uint NETWORK_CONTENT_FILENAME_LENGTH = 48; ///< The maximum length of a content's filename, in bytes including '\0'.
static const uint NETWORK_CONTENT_NAME_LENGTH = 64; ///< The maximum length of a content's name, in bytes including '\0'.
static const uint NETWORK_CONTENT_VERSION_LENGTH = 16; ///< The maximum length of a content's version, in bytes including '\0'.
static const uint NETWORK_CONTENT_URL_LENGTH = 96; ///< The maximum length of a content's url, in bytes including '\0'.
static const uint NETWORK_CONTENT_DESC_LENGTH = 512; ///< The maximum length of a content's description, in bytes including '\0'.
static const uint NETWORK_CONTENT_TAG_LENGTH = 32; ///< The maximum length of a content's tag, in bytes including '\0'.
static const uint NETWORK_ERROR_DETAIL_LENGTH = 100; ///< The maximum length of the error detail, in bytes including '\0'.
static const uint NETWORK_INVITE_CODE_LENGTH = 64; ///< The maximum length of the invite code, in bytes including '\0'.
static const uint NETWORK_INVITE_CODE_SECRET_LENGTH = 80; ///< The maximum length of the invite code secret, in bytes including '\0'.
static const uint NETWORK_TOKEN_LENGTH = 64; ///< The maximum length of a token, in bytes including '\0'.
static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF
static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF
/**
* Maximum number of GRFs that can be sent.
* This limit is reached when PACKET_UDP_SERVER_RESPONSE reaches the maximum size of UDP_MTU bytes.
*
* This limit exists to avoid that the SERVER_INFO packet exceeding the
* maximum MTU. At the time of writing this limit is 32767 (TCP_MTU).
*
* In the SERVER_INFO packet is the NetworkGameInfo struct, which is
* 142 bytes + 100 per NewGRF (under the assumption strings are used to
* their max). This brings us to roughly 326 possible NewGRFs. Round it
* down so people don't freak out because they see a weird value, and you
* get the limit: 255.
*
* PS: in case you ever want to raise this number, please be mindful that
* "amount of NewGRFs" in NetworkGameInfo is currently an uint8.
*/
static const uint NETWORK_MAX_GRF_COUNT = 62;
static const uint NETWORK_MAX_GRF_COUNT_SHORT = 59;
static const uint NETWORK_MAX_GRF_COUNT = 255;
/**
* The number of landscapes in OpenTTD.
@@ -91,6 +103,6 @@ static const uint NETWORK_MAX_GRF_COUNT_SHORT = 59;
* there is a compile assertion to check that this NUM_LANDSCAPE is equal
* to NETWORK_NUM_LANDSCAPES.
*/
static const uint NETWORK_NUM_LANDSCAPES = 4;
static const uint NETWORK_NUM_LANDSCAPES = 4;
#endif /* NETWORK_CORE_CONFIG_H */

View File

@@ -16,6 +16,8 @@
#include "../../date_func.h"
#include "../../debug.h"
#include "../../map_func.h"
#include "../../game/game.hpp"
#include "../../game/game_info.hpp"
#include "../../settings_type.h"
#include "../../string_func.h"
#include "../../rev.h"
@@ -149,7 +151,11 @@ void FillStaticNetworkServerGameInfo()
*/
const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo()
{
/* Client_on is used as global variable to keep track on the number of clients. */
/* These variables are updated inside _network_game_info as if they are global variables:
* - clients_on
* - invite_code
* These don't need to be updated manually here.
*/
_network_game_info.companies_on = (byte)Company::GetNumItems();
_network_game_info.spectators_on = NetworkSpectatorCount();
_network_game_info.game_date = _date;
@@ -199,6 +205,11 @@ void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info)
/* Update the documentation in game_info.h on changes
* to the NetworkGameInfo wire-protocol! */
/* NETWORK_GAME_INFO_VERSION = 5 */
GameInfo *game_info = Game::GetInfo();
p->Send_uint32(game_info == nullptr ? -1 : (uint32)game_info->GetVersion());
p->Send_string(game_info == nullptr ? "" : game_info->GetName());
/* NETWORK_GAME_INFO_VERSION = 4 */
{
/* Only send the GRF Identification (GRF_ID and MD5 checksum) of
@@ -218,12 +229,7 @@ void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info)
uint index = 0;
for (c = info->grfconfig; c != nullptr; c = c->next) {
if (!HasBit(c->flags, GCF_STATIC)) {
if (index == NETWORK_MAX_GRF_COUNT - 1 && count > NETWORK_MAX_GRF_COUNT) {
/* Send fake GRF ID */
p->Send_uint32(0x56D2B000);
p->Send_binary((const char*) _out_of_band_grf_md5, 16);
} else if (index >= NETWORK_MAX_GRF_COUNT) {
if (index >= NETWORK_MAX_GRF_COUNT) {
break;
} else {
SerializeGRFIdentifier(p, &c->ident);
@@ -327,6 +333,12 @@ void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info)
* to the NetworkGameInfo wire-protocol! */
switch (game_info_version) {
case 5: {
info->gamescript_version = (int)p->Recv_uint32();
info->gamescript_name = p->Recv_string(NETWORK_NAME_LENGTH);
FALLTHROUGH;
}
case 4: {
GRFConfig **dst = &info->grfconfig;
uint i;

View File

@@ -76,6 +76,8 @@ struct NetworkServerGameInfo {
byte spectators_on; ///< How many spectators do we have?
byte spectators_max; ///< Max spectators allowed on server
byte landscape; ///< The used landscape
int gamescript_version; ///< Version of the gamescript.
std::string gamescript_name; ///< Name of the gamescript.
};
/**

View File

@@ -180,6 +180,23 @@ bool SetNoDelay(SOCKET d)
#endif
}
/**
* Try to set the socket to reuse ports.
* @param d The socket to reuse ports on.
* @return True if disabling the delaying succeeded, otherwise false.
*/
bool SetReusePort(SOCKET d)
{
#ifdef _WIN32
/* Windows has no SO_REUSEPORT, but for our usecases SO_REUSEADDR does the same job. */
int reuse_port = 1;
return setsockopt(d, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse_port, sizeof(reuse_port)) == 0;
#else
int reuse_port = 1;
return setsockopt(d, SOL_SOCKET, SO_REUSEPORT, &reuse_port, sizeof(reuse_port)) == 0;
#endif
}
/**
* Try to shutdown the socket in one or both directions.
* @param d The socket to disable the delaying for.

View File

@@ -215,6 +215,7 @@ static inline socklen_t FixAddrLenForEmscripten(struct sockaddr_storage &address
bool SetNonBlocking(SOCKET d);
bool SetBlocking(SOCKET d);
bool SetNoDelay(SOCKET d);
bool SetReusePort(SOCKET d);
bool ShutdownSocket(SOCKET d, bool read, bool write, uint linger_timeout);
NetworkError GetSocketError(SOCKET d);

View File

@@ -95,10 +95,12 @@ private:
RESOLVING, ///< The hostname is being resolved (threaded).
FAILURE, ///< Resolving failed.
CONNECTING, ///< We are currently connecting.
CONNECTED, ///< The connection is established.
};
std::thread resolve_thread; ///< Thread used during resolving.
std::atomic<Status> status = Status::INIT; ///< The current status of the connecter.
std::atomic<bool> killed = false; ///< Whether this connecter is marked as killed.
addrinfo *ai = nullptr; ///< getaddrinfo() allocated linked-list of resolved addresses.
std::vector<addrinfo *> addresses; ///< Addresses we can connect to.
@@ -109,17 +111,24 @@ private:
std::chrono::steady_clock::time_point last_attempt; ///< Time we last tried to connect.
std::string connection_string; ///< Current address we are connecting to (before resolving).
NetworkAddress bind_address; ///< Address we're binding to, if any.
int family = AF_UNSPEC; ///< Family we are using to connect with.
void Resolve();
void OnResolved(addrinfo *ai);
bool TryNextAddress();
void Connect(addrinfo *address);
bool CheckActivity();
virtual bool CheckActivity();
/* We do not want any other derived classes from this class being able to
* access these private members, but it is okay for TCPServerConnecter. */
friend class TCPServerConnecter;
static void ResolveThunk(TCPConnecter *connecter);
public:
TCPConnecter(const std::string &connection_string, uint16 default_port);
TCPConnecter() {};
TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address = {}, int family = AF_UNSPEC);
virtual ~TCPConnecter();
/**
@@ -133,8 +142,25 @@ public:
*/
virtual void OnFailure() {}
void Kill();
static void CheckCallbacks();
static void KillAll();
};
class TCPServerConnecter : public TCPConnecter {
private:
SOCKET socket = INVALID_SOCKET; ///< The socket when a connection is established.
bool CheckActivity() override;
public:
ServerAddress server_address; ///< Address we are connecting to.
TCPServerConnecter(const std::string &connection_string, uint16 default_port);
void SetConnected(SOCKET sock);
void SetFailure();
};
#endif /* NETWORK_CORE_TCP_H */

View File

@@ -13,6 +13,7 @@
#include "../../thread.h"
#include "tcp.h"
#include "../network_coordinator.h"
#include "../network_internal.h"
#include <deque>
@@ -23,16 +24,45 @@
static std::vector<TCPConnecter *> _tcp_connecters;
/**
* Create a new connecter for the given address
* @param connection_string the address to connect to
* Create a new connecter for the given address.
* @param connection_string The address to connect to.
* @param default_port If not indicated in connection_string, what port to use.
* @param bind_address The local bind address to use. Defaults to letting the OS find one.
*/
TCPConnecter::TCPConnecter(const std::string &connection_string, uint16 default_port)
TCPConnecter::TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address, int family) :
bind_address(bind_address),
family(family)
{
this->connection_string = NormalizeConnectionString(connection_string, default_port);
_tcp_connecters.push_back(this);
}
/**
* Create a new connecter for the server.
* @param connection_string The address to connect to.
* @param default_port If not indicated in connection_string, what port to use.
*/
TCPServerConnecter::TCPServerConnecter(const std::string &connection_string, uint16 default_port) :
server_address(ServerAddress::Parse(connection_string, default_port))
{
switch (this->server_address.type) {
case SERVER_ADDRESS_DIRECT:
this->connection_string = this->server_address.connection_string;
break;
case SERVER_ADDRESS_INVITE_CODE:
this->status = Status::CONNECTING;
_network_coordinator_client.ConnectToServer(this->server_address.connection_string, this);
break;
default:
NOT_REACHED();
}
_tcp_connecters.push_back(this);
}
TCPConnecter::~TCPConnecter()
{
if (this->resolve_thread.joinable()) {
@@ -48,6 +78,16 @@ TCPConnecter::~TCPConnecter()
if (this->ai != nullptr) freeaddrinfo(this->ai);
}
/**
* Kill this connecter.
* It will abort as soon as it can and not call any of the callbacks.
*/
void TCPConnecter::Kill()
{
/* Delay the removing of the socket till the next CheckActivity(). */
this->killed = true;
}
/**
* Start a connection to the indicated address.
* @param address The address to connection to.
@@ -60,6 +100,18 @@ void TCPConnecter::Connect(addrinfo *address)
return;
}
if (!SetReusePort(sock)) {
DEBUG(net, 0, "Setting reuse-port mode failed: %s", NetworkError::GetLast().AsString());
}
if (this->bind_address.GetPort() > 0) {
if (bind(sock, (const sockaddr *)this->bind_address.GetAddress(), this->bind_address.GetAddressLength()) != 0) {
DEBUG(net, 1, "Could not bind socket on %s: %s", NetworkAddressDumper().GetAddressAsString(&(this->bind_address)), NetworkError::GetLast().AsString());
closesocket(sock);
return;
}
}
if (!SetNoDelay(sock)) {
DEBUG(net, 1, "Setting TCP_NODELAY failed: %s", NetworkError::GetLast().AsString());
}
@@ -123,6 +175,9 @@ void TCPConnecter::OnResolved(addrinfo *ai)
/* Convert the addrinfo into NetworkAddresses. */
for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
/* Skip entries if the family is set and it is not matching. */
if (this->family != AF_UNSPEC && this->family != runp->ai_family) continue;
if (resort) {
if (runp->ai_family == AF_INET6) {
addresses_ipv6.emplace_back(runp);
@@ -219,7 +274,9 @@ void TCPConnecter::Resolve()
*/
bool TCPConnecter::CheckActivity()
{
switch (this->status.load()) {
if (this->killed) return true;
switch (this->status) {
case Status::INIT:
/* Start the thread delayed, so the vtable is loaded. This allows classes
* to overload functions used by Resolve() (in case threading is disabled). */
@@ -246,6 +303,7 @@ bool TCPConnecter::CheckActivity()
return true;
case Status::CONNECTING:
case Status::CONNECTED:
break;
}
@@ -344,9 +402,63 @@ bool TCPConnecter::CheckActivity()
}
this->OnConnect(connected_socket);
this->status = Status::CONNECTED;
return true;
}
/**
* Check if there was activity for this connecter.
* @return True iff the TCPConnecter is done and can be cleaned up.
*/
bool TCPServerConnecter::CheckActivity()
{
if (this->killed) return true;
switch (this->server_address.type) {
case SERVER_ADDRESS_DIRECT:
return TCPConnecter::CheckActivity();
case SERVER_ADDRESS_INVITE_CODE:
/* Check if a result has come in. */
switch (this->status) {
case Status::FAILURE:
this->OnFailure();
return true;
case Status::CONNECTED:
this->OnConnect(this->socket);
return true;
default:
break;
}
return false;
default:
NOT_REACHED();
}
}
/**
* The connection was successfully established.
* This socket is fully setup and ready to send/recv game protocol packets.
* @param sock The socket of the established connection.
*/
void TCPServerConnecter::SetConnected(SOCKET sock)
{
this->socket = sock;
this->status = Status::CONNECTED;
}
/**
* The connection couldn't be established.
*/
void TCPServerConnecter::SetFailure()
{
this->status = Status::FAILURE;
}
/**
* Check whether we need to call the callback, i.e. whether we
* have connected or aborted and call the appropriate callback

View File

@@ -0,0 +1,101 @@
/*
* 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_coordinator.cpp Basic functions to receive and send Game Coordinator packets.
*/
#include "../../stdafx.h"
#include "../../date_func.h"
#include "../../debug.h"
#include "tcp_coordinator.h"
#include "../../safeguards.h"
/**
* Handle the given packet, i.e. pass it to the right.
* parser receive command.
* @param p The packet to handle.
* @return True iff we should immediately handle further packets.
*/
bool NetworkCoordinatorSocketHandler::HandlePacket(Packet *p)
{
PacketCoordinatorType type = (PacketCoordinatorType)p->Recv_uint8();
switch (type) {
case PACKET_COORDINATOR_GC_ERROR: return this->Receive_GC_ERROR(p);
case PACKET_COORDINATOR_SERVER_REGISTER: return this->Receive_SERVER_REGISTER(p);
case PACKET_COORDINATOR_GC_REGISTER_ACK: return this->Receive_GC_REGISTER_ACK(p);
case PACKET_COORDINATOR_SERVER_UPDATE: return this->Receive_SERVER_UPDATE(p);
case PACKET_COORDINATOR_CLIENT_LISTING: return this->Receive_CLIENT_LISTING(p);
case PACKET_COORDINATOR_GC_LISTING: return this->Receive_GC_LISTING(p);
case PACKET_COORDINATOR_CLIENT_CONNECT: return this->Receive_CLIENT_CONNECT(p);
case PACKET_COORDINATOR_GC_CONNECTING: return this->Receive_GC_CONNECTING(p);
case PACKET_COORDINATOR_SERCLI_CONNECT_FAILED: return this->Receive_SERCLI_CONNECT_FAILED(p);
case PACKET_COORDINATOR_GC_CONNECT_FAILED: return this->Receive_GC_CONNECT_FAILED(p);
case PACKET_COORDINATOR_CLIENT_CONNECTED: return this->Receive_CLIENT_CONNECTED(p);
case PACKET_COORDINATOR_GC_DIRECT_CONNECT: return this->Receive_GC_DIRECT_CONNECT(p);
case PACKET_COORDINATOR_GC_STUN_REQUEST: return this->Receive_GC_STUN_REQUEST(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);
default:
DEBUG(net, 0, "[tcp/coordinator] Received invalid packet type %u", type);
return false;
}
}
/**
* Receive a packet at TCP level.
* @return Whether at least one packet was received.
*/
bool NetworkCoordinatorSocketHandler::ReceivePackets()
{
/*
* We read only a few of the packets. This allows the GUI to update when
* a large set of servers is being received. Otherwise the interface
* "hangs" while the game is updating the server-list.
*
* What arbitrary number to choose is the ultimate question though.
*/
std::unique_ptr<Packet> p;
static const int MAX_PACKETS_TO_RECEIVE = 42;
int i = MAX_PACKETS_TO_RECEIVE;
while (--i != 0 && (p = this->ReceivePacket()) != nullptr) {
bool cont = this->HandlePacket(p.get());
if (!cont) return true;
}
return i != MAX_PACKETS_TO_RECEIVE - 1;
}
/**
* Helper for logging receiving invalid packets.
* @param type The received packet type.
* @return Always false, as it's an error.
*/
bool NetworkCoordinatorSocketHandler::ReceiveInvalidPacket(PacketCoordinatorType type)
{
DEBUG(net, 0, "[tcp/coordinator] Received illegal packet type %u", type);
return false;
}
bool NetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_ERROR); }
bool NetworkCoordinatorSocketHandler::Receive_SERVER_REGISTER(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_REGISTER); }
bool NetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_REGISTER_ACK); }
bool NetworkCoordinatorSocketHandler::Receive_SERVER_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_UPDATE); }
bool NetworkCoordinatorSocketHandler::Receive_CLIENT_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_LISTING); }
bool NetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_LISTING); }
bool NetworkCoordinatorSocketHandler::Receive_CLIENT_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_CONNECT); }
bool NetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_CONNECTING); }
bool NetworkCoordinatorSocketHandler::Receive_SERCLI_CONNECT_FAILED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED); }
bool NetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_CONNECT_FAILED); }
bool NetworkCoordinatorSocketHandler::Receive_CLIENT_CONNECTED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_CONNECTED); }
bool NetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_DIRECT_CONNECT); }
bool NetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_STUN_REQUEST); }
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); }

View File

@@ -0,0 +1,277 @@
/*
* 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_coordinator.h Basic functions to receive and send TCP packets to/from the Game Coordinator server.
*/
#ifndef NETWORK_CORE_TCP_COORDINATOR_H
#define NETWORK_CORE_TCP_COORDINATOR_H
#include "os_abstraction.h"
#include "tcp.h"
#include "packet.h"
#include "game_info.h"
/**
* Enum with all types of TCP Game Coordinator packets. The order MUST not be changed.
*
* GC -> packets from Game Coordinator to either Client or Server.
* SERVER -> packets from Server to Game Coordinator.
* CLIENT -> packets from Client to Game Coordinator.
* SERCLI -> packets from either the Server or Client to Game Coordinator.
**/
enum PacketCoordinatorType {
PACKET_COORDINATOR_GC_ERROR, ///< Game Coordinator indicates there was an error.
PACKET_COORDINATOR_SERVER_REGISTER, ///< Server registration.
PACKET_COORDINATOR_GC_REGISTER_ACK, ///< Game Coordinator accepts the registration.
PACKET_COORDINATOR_SERVER_UPDATE, ///< Server sends an set intervals an update of the server.
PACKET_COORDINATOR_CLIENT_LISTING, ///< Client is requesting a listing of all public servers.
PACKET_COORDINATOR_GC_LISTING, ///< Game Coordinator returns a listing of all public servers.
PACKET_COORDINATOR_CLIENT_CONNECT, ///< Client wants to connect to a server based on an invite code.
PACKET_COORDINATOR_GC_CONNECTING, ///< Game Coordinator informs the client of the token assigned to the connection attempt.
PACKET_COORDINATOR_SERCLI_CONNECT_FAILED, ///< Client/server tells the Game Coordinator the current connection attempt failed.
PACKET_COORDINATOR_GC_CONNECT_FAILED, ///< Game Coordinator informs client/server it has given up on the connection attempt.
PACKET_COORDINATOR_CLIENT_CONNECTED, ///< Client informs the Game Coordinator the connection with the server is established.
PACKET_COORDINATOR_GC_DIRECT_CONNECT, ///< Game Coordinator tells client to directly connect to the hostname:port of the server.
PACKET_COORDINATOR_GC_STUN_REQUEST, ///< Game Coordinator tells client/server to initiate a STUN request.
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_END, ///< Must ALWAYS be on the end of this list!! (period)
};
/**
* The type of connection the Game Coordinator can detect we have.
*/
enum ConnectionType {
CONNECTION_TYPE_UNKNOWN, ///< The Game Coordinator hasn't informed us yet what type of connection we have.
CONNECTION_TYPE_ISOLATED, ///< The Game Coordinator failed to find a way to connect to your server. Nobody will be able to join.
CONNECTION_TYPE_DIRECT, ///< The Game Coordinator can directly connect to your server.
CONNECTION_TYPE_STUN, ///< The Game Coordinator can connect to your server via a STUN request.
};
/**
* The type of error from the Game Coordinator.
*/
enum NetworkCoordinatorErrorType {
NETWORK_COORDINATOR_ERROR_UNKNOWN, ///< There was an unknown error.
NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED, ///< Your request for registration failed.
NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE, ///< The invite code given is invalid.
};
/** Base socket handler for all Game Coordinator TCP sockets. */
class NetworkCoordinatorSocketHandler : public NetworkTCPSocketHandler {
protected:
bool ReceiveInvalidPacket(PacketCoordinatorType type);
/**
* Game Coordinator indicates there was an error. This can either be a
* permanent error causing the connection to be dropped, or in response
* to a request that is invalid.
*
* uint8 Type of error (see NetworkCoordinatorErrorType).
* string Details of the error.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_GC_ERROR(Packet *p);
/**
* Server is starting a multiplayer game and wants to let the
* Game Coordinator know.
*
* uint8 Game Coordinator protocol version.
* uint8 Type of game (see ServerGameType).
* uint16 Local port of the server.
* string Invite code the server wants to use (can be empty; coordinator will assign a new invite code).
* string Secret that belongs to the invite code (empty if invite code is empty).
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_SERVER_REGISTER(Packet *p);
/**
* Game Coordinator acknowledges the registration.
*
* string Invite code that can be used to join this server.
* string Secret that belongs to the invite code (only needed if reusing the invite code on next SERVER_REGISTER).
* uint8 Type of connection was detected (see ConnectionType).
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_GC_REGISTER_ACK(Packet *p);
/**
* Send an update of the current state of the server to the Game Coordinator.
*
* uint8 Game Coordinator protocol version.
* Serialized NetworkGameInfo. See game_info.hpp for details.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_SERVER_UPDATE(Packet *p);
/**
* Client requests a list of all public servers.
*
* uint8 Game Coordinator protocol version.
* uint8 Game-info version used by this client.
* string Revision of the client.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_CLIENT_LISTING(Packet *p);
/**
* Game Coordinator replies with a list of all public servers. Multiple
* of these packets are received after a request till all servers are
* sent over. Last packet will have server count of 0.
*
* uint16 Amount of public servers in this packet.
* For each server:
* string Connection string for this server.
* Serialized NetworkGameInfo. See game_info.hpp for details.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_GC_LISTING(Packet *p);
/**
* Client wants to connect to a Server.
*
* uint8 Game Coordinator protocol version.
* string Invite code of the Server to join.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_CLIENT_CONNECT(Packet *p);
/**
* Game Coordinator informs the Client under what token it will start the
* attempt to connect the Server and Client together.
*
* string Token to track the current connect request.
* string Invite code of the Server to join.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_GC_CONNECTING(Packet *p);
/**
* Client or Server failed to connect to the remote side.
*
* uint8 Game Coordinator protocol version.
* string Token to track the current connect request.
* uint8 Tracking number to track current connect request.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_SERCLI_CONNECT_FAILED(Packet *p);
/**
* Game Coordinator informs the Client that it failed to find a way to
* connect the Client to the Server. Any open connections for this token
* should be closed now.
*
* string Token to track the current connect request.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_GC_CONNECT_FAILED(Packet *p);
/**
* Client informs the Game Coordinator the connection with the Server is
* established. The Client will disconnect from the Game Coordinator next.
*
* uint8 Game Coordinator protocol version.
* string Token to track the current connect request.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_CLIENT_CONNECTED(Packet *p);
/**
* Game Coordinator requests that the Client makes a direct connection to
* the indicated peer, which is a Server.
*
* string Token to track the current connect request.
* uint8 Tracking number to track current connect request.
* string Hostname of the peer.
* uint16 Port of the peer.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_GC_DIRECT_CONNECT(Packet *p);
/**
* Game Coordinator requests the client/server to do a STUN request to the
* STUN server. Important is to remember the local port these STUN requests
* are sent from, as this will be needed for later conenctions too.
* The client/server should do multiple STUN requests for every available
* interface that connects to the Internet (e.g., once for IPv4 and once
* for IPv6).
*
* string Token to track the current connect request.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_GC_STUN_REQUEST(Packet *p);
/**
* Client/server informs the Game Coordinator the result of a STUN request.
*
* uint8 Game Coordinator protocol version.
* string Token to track the current connect request.
* uint8 Interface number, as given during STUN request.
* bool Whether the STUN connection was successful.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_SERCLI_STUN_RESULT(Packet *p);
/**
* Game Coordinator informs the client/server of its STUN peer (the host:ip
* of the other side). It should start a connect() to this peer ASAP with
* the local address as used with the STUN request.
*
* string Token to track the current connect request.
* uint8 Tracking number to track current connect request.
* uint8 Interface number, as given during STUN request.
* string Host of the peer.
* uint16 Port of the peer.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_GC_STUN_CONNECT(Packet *p);
bool HandlePacket(Packet *p);
public:
/**
* Create a new cs socket handler for a given cs.
* @param s The socket we are connected with.
*/
NetworkCoordinatorSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {}
bool ReceivePackets();
};
#endif /* NETWORK_CORE_TCP_COORDINATOR_H */

View File

@@ -30,6 +30,42 @@ class TCPListenHandler {
static SocketList sockets;
public:
static bool ValidateClient(SOCKET s, NetworkAddress &address)
{
/* Check if the client is banned. */
for (const auto &entry : _network_ban_list) {
if (address.IsInNetmask(entry.c_str())) {
Packet p(Tban_packet);
p.PrepareToSend();
DEBUG(net, 2, "[%s] Banned ip tried to join (%s), refused", Tsocket::GetName(), entry.c_str());
if (p.TransferOut<int>(send, s, 0) < 0) {
DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString());
}
closesocket(s);
return false;
}
}
/* Can we handle a new client? */
if (!Tsocket::AllowConnection()) {
/* No more clients allowed?
* Send to the client that we are full! */
Packet p(Tfull_packet);
p.PrepareToSend();
if (p.TransferOut<int>(send, s, 0) < 0) {
DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString());
}
closesocket(s);
return false;
}
return true;
}
/**
* Accepts clients from the sockets.
* @param ls Socket to accept clients from.
@@ -53,41 +89,7 @@ public:
SetNoDelay(s); // XXX error handling?
/* Check if the client is banned */
bool banned = false;
for (const auto &entry : _network_ban_list) {
banned = address.IsInNetmask(entry.c_str());
if (banned) {
Packet p(Tban_packet);
p.PrepareToSend();
DEBUG(net, 2, "[%s] Banned ip tried to join (%s), refused", Tsocket::GetName(), entry.c_str());
if (p.TransferOut<int>(send, s, 0) < 0) {
DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString());
}
closesocket(s);
break;
}
}
/* If this client is banned, continue with next client */
if (banned) continue;
/* Can we handle a new client? */
if (!Tsocket::AllowConnection()) {
/* no more clients allowed?
* Send to the client that we are full! */
Packet p(Tfull_packet);
p.PrepareToSend();
if (p.TransferOut<int>(send, s, 0) < 0) {
DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString());
}
closesocket(s);
continue;
}
if (!Tsocket::ValidateClient(s, address)) continue;
Tsocket::AcceptConnection(s, address);
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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_stun.cpp Basic functions to receive and send STUN packets.
*/
#include "../../stdafx.h"
#include "../../debug.h"
#include "tcp_stun.h"
#include "../../safeguards.h"
/**
* Helper for logging receiving invalid packets.
* @param type The received packet type.
* @return Always false, as it's an error.
*/
bool NetworkStunSocketHandler::ReceiveInvalidPacket(PacketStunType type)
{
DEBUG(net, 0, "[tcp/stun] Received illegal packet type %u", type);
return false;
}
bool NetworkStunSocketHandler::Receive_SERCLI_STUN(Packet *p) { return this->ReceiveInvalidPacket(PACKET_STUN_SERCLI_STUN); }

View File

@@ -0,0 +1,53 @@
/*
* 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_stun.h Basic functions to receive and send TCP packets to/from the STUN server.
*/
#ifndef NETWORK_CORE_TCP_STUN_H
#define NETWORK_CORE_TCP_STUN_H
#include "os_abstraction.h"
#include "tcp.h"
#include "packet.h"
/** Enum with all types of TCP STUN packets. The order MUST not be changed. **/
enum PacketStunType {
PACKET_STUN_SERCLI_STUN, ///< Send a STUN request to the STUN server.
PACKET_STUN_END, ///< Must ALWAYS be on the end of this list!! (period)
};
/** Base socket handler for all STUN TCP sockets. */
class NetworkStunSocketHandler : public NetworkTCPSocketHandler {
protected:
bool ReceiveInvalidPacket(PacketStunType type);
/**
* Send a STUN request to the STUN server letting the Game Coordinator know
* what our actually public IP:port is.
*
* uint8 Game Coordinator protocol version.
* string Token to track the current STUN request.
* uint8 Which interface number this is (for example, IPv4 or IPv6).
* The Game Coordinator relays this number back in later packets.
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
*/
virtual bool Receive_SERCLI_STUN(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.
*/
NetworkStunSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {}
};
#endif /* NETWORK_CORE_TCP_STUN_H */

View File

@@ -194,16 +194,6 @@ void NetworkUDPSocketHandler::HandleUDPPacket(Packet *p, NetworkAddress *client_
switch (this->HasClientQuit() ? PACKET_UDP_END : type) {
case PACKET_UDP_CLIENT_FIND_SERVER: this->Receive_CLIENT_FIND_SERVER(p, client_addr); break;
case PACKET_UDP_SERVER_RESPONSE: this->Receive_SERVER_RESPONSE(p, client_addr); break;
case PACKET_UDP_CLIENT_DETAIL_INFO: this->Receive_CLIENT_DETAIL_INFO(p, client_addr); break;
case PACKET_UDP_SERVER_DETAIL_INFO: this->Receive_SERVER_DETAIL_INFO(p, client_addr); break;
case PACKET_UDP_SERVER_REGISTER: this->Receive_SERVER_REGISTER(p, client_addr); break;
case PACKET_UDP_MASTER_ACK_REGISTER: this->Receive_MASTER_ACK_REGISTER(p, client_addr); break;
case PACKET_UDP_CLIENT_GET_LIST: this->Receive_CLIENT_GET_LIST(p, client_addr); break;
case PACKET_UDP_MASTER_RESPONSE_LIST: this->Receive_MASTER_RESPONSE_LIST(p, client_addr); break;
case PACKET_UDP_SERVER_UNREGISTER: this->Receive_SERVER_UNREGISTER(p, client_addr); break;
case PACKET_UDP_CLIENT_GET_NEWGRFS: this->Receive_CLIENT_GET_NEWGRFS(p, client_addr); break;
case PACKET_UDP_SERVER_NEWGRFS: this->Receive_SERVER_NEWGRFS(p, client_addr); break;
case PACKET_UDP_MASTER_SESSION_KEY: this->Receive_MASTER_SESSION_KEY(p, client_addr); break;
case PACKET_UDP_EX_MULTI: this->Receive_EX_MULTI(p, client_addr); break;
case PACKET_UDP_EX_SERVER_RESPONSE: this->Receive_EX_SERVER_RESPONSE(p, client_addr); break;
@@ -301,13 +291,3 @@ void NetworkUDPSocketHandler::ReceiveInvalidPacket(PacketUDPType type, NetworkAd
void NetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_FIND_SERVER, client_addr); }
void NetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_RESPONSE, client_addr); }
void NetworkUDPSocketHandler::Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_EX_SERVER_RESPONSE, client_addr); }
void NetworkUDPSocketHandler::Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_DETAIL_INFO, client_addr); }
void NetworkUDPSocketHandler::Receive_SERVER_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_DETAIL_INFO, client_addr); }
void NetworkUDPSocketHandler::Receive_SERVER_REGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_REGISTER, client_addr); }
void NetworkUDPSocketHandler::Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_ACK_REGISTER, client_addr); }
void NetworkUDPSocketHandler::Receive_CLIENT_GET_LIST(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_GET_LIST, client_addr); }
void NetworkUDPSocketHandler::Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_RESPONSE_LIST, client_addr); }
void NetworkUDPSocketHandler::Receive_SERVER_UNREGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_UNREGISTER, client_addr); }
void NetworkUDPSocketHandler::Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_GET_NEWGRFS, client_addr); }
void NetworkUDPSocketHandler::Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_NEWGRFS, client_addr); }
void NetworkUDPSocketHandler::Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_SESSION_KEY, client_addr); }

View File

@@ -23,31 +23,12 @@
enum PacketUDPType {
PACKET_UDP_CLIENT_FIND_SERVER, ///< Queries a game server for game information
PACKET_UDP_SERVER_RESPONSE, ///< Reply of the game server with game information
PACKET_UDP_CLIENT_DETAIL_INFO, ///< Queries a game server about details of the game, such as companies
PACKET_UDP_SERVER_DETAIL_INFO, ///< Reply of the game server about details of the game, such as companies
PACKET_UDP_SERVER_REGISTER, ///< Packet to register itself to the master server
PACKET_UDP_MASTER_ACK_REGISTER, ///< Packet indicating registration has succeeded
PACKET_UDP_CLIENT_GET_LIST, ///< Request for serverlist from master server
PACKET_UDP_MASTER_RESPONSE_LIST, ///< Response from master server with server ip's + port's
PACKET_UDP_SERVER_UNREGISTER, ///< Request to be removed from the server-list
PACKET_UDP_CLIENT_GET_NEWGRFS, ///< Requests the name for a list of GRFs (GRF_ID and MD5)
PACKET_UDP_SERVER_NEWGRFS, ///< Sends the list of NewGRF's requested.
PACKET_UDP_MASTER_SESSION_KEY, ///< Sends a fresh session key to the client
PACKET_UDP_END, ///< Must ALWAYS be the last non-extended item in the list!! (period)
PACKET_UDP_EX_MULTI = 128, ///< Extended/multi packet type
PACKET_UDP_EX_SERVER_RESPONSE, ///< Reply of the game server with extended game information
};
/** The types of server lists we can get */
enum ServerListType {
SLT_IPv4 = 0, ///< Get the IPv4 addresses
SLT_IPv6 = 1, ///< Get the IPv6 addresses
SLT_AUTODETECT, ///< Autodetect the type based on the connection
SLT_END = SLT_AUTODETECT, ///< End of 'arrays' marker
};
/** Base socket handler for all UDP sockets */
class NetworkUDPSocketHandler : public NetworkSocketHandler {
protected:
@@ -76,8 +57,7 @@ protected:
virtual void Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr);
/**
* Return of server information to the client.
* Serialized NetworkGameInfo. See game_info.h for details.
* Response to a query letting the client know we are here.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
@@ -85,120 +65,6 @@ protected:
virtual void Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr);
/**
* Query for detailed information about companies.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr);
/**
* Reply with detailed company information.
* uint8 Version of the packet.
* uint8 Number of companies.
* For each company:
* uint8 ID of the company.
* string Name of the company.
* uint32 Year the company was inaugurated.
* uint64 Value.
* uint64 Money.
* uint64 Income.
* uint16 Performance (last quarter).
* bool Company is password protected.
* uint16 Number of trains.
* uint16 Number of lorries.
* uint16 Number of busses.
* uint16 Number of planes.
* uint16 Number of ships.
* uint16 Number of train stations.
* uint16 Number of lorry stations.
* uint16 Number of bus stops.
* uint16 Number of airports and heliports.
* uint16 Number of harbours.
* bool Company is an AI.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_SERVER_DETAIL_INFO(Packet *p, NetworkAddress *client_addr);
/**
* Registers the server to the master server.
* string The "welcome" message to root out other binary packets.
* uint8 Version of the protocol.
* uint16 The port to unregister.
* uint64 The session key.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_SERVER_REGISTER(Packet *p, NetworkAddress *client_addr);
/**
* The master server acknowledges the registration.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr);
/**
* The client requests a list of servers.
* uint8 The protocol version.
* uint8 The type of server to look for: IPv4, IPv6 or based on the received packet.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_CLIENT_GET_LIST(Packet *p, NetworkAddress *client_addr);
/**
* The server sends a list of servers.
* uint8 The protocol version.
* For each server:
* 4 or 16 bytes of IPv4 or IPv6 address.
* uint8 The port.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr);
/**
* A server unregisters itself at the master server.
* uint8 Version of the protocol.
* uint16 The port to unregister.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_SERVER_UNREGISTER(Packet *p, NetworkAddress *client_addr);
/**
* The client requests information about some NewGRFs.
* uint8 The number of NewGRFs information is requested about.
* For each NewGRF:
* uint32 The GRFID.
* 16 * uint8 MD5 checksum of the GRF.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr);
/**
* The server returns information about some NewGRFs.
* uint8 The number of NewGRFs information is requested about.
* For each NewGRF:
* uint32 The GRFID.
* 16 * uint8 MD5 checksum of the GRF.
* string The name of the NewGRF.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr);
/**
* The master server sends us a session key.
* uint64 The session key.
* @param p The received packet.
* @param client_addr The origin of the packet.
*/
virtual void Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr);
void HandleUDPPacket(Packet *p, NetworkAddress *client_addr);
virtual void Receive_EX_MULTI(Packet *p, NetworkAddress *client_addr);

View File

@@ -19,6 +19,7 @@
#include "network_udp.h"
#include "network_gamelist.h"
#include "network_base.h"
#include "network_coordinator.h"
#include "core/udp.h"
#include "core/host.h"
#include "network_gui.h"
@@ -62,7 +63,6 @@ bool _network_settings_access; ///< Can this client change server settings?
NetworkCompanyState *_network_company_states = nullptr; ///< Statistics about some companies.
ClientID _network_own_client_id; ///< Our client identifier.
ClientID _redirect_console_to_client; ///< If not invalid, redirect the console output to a client.
bool _network_need_advertise; ///< Whether we need to advertise.
uint8 _network_reconnect; ///< Reconnect timeout
StringList _network_bind_list; ///< The addresses to bind on.
StringList _network_host_list; ///< The servers we know.
@@ -478,6 +478,41 @@ static void CheckPauseOnJoin()
CheckPauseHelper(NetworkHasJoiningClient(), PM_PAUSED_JOIN);
}
/**
* Parse the company part ("#company" postfix) of a connecting string.
* @param connection_string The string with the connection data.
* @param company_id The company ID to set, if available.
* @return A std::string_view into the connection string without the company part.
*/
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id)
{
std::string_view ip = connection_string;
if (company_id == nullptr) return ip;
size_t offset = ip.find_last_of('#');
if (offset != std::string::npos) {
std::string_view company_string = ip.substr(offset + 1);
ip = ip.substr(0, offset);
uint8 company_value;
auto [_, err] = std::from_chars(company_string.data(), company_string.data() + company_string.size(), company_value);
if (err == std::errc()) {
if (company_value != COMPANY_NEW_COMPANY && company_value != COMPANY_SPECTATOR) {
if (company_value > MAX_COMPANIES || company_value == 0) {
*company_id = COMPANY_SPECTATOR;
} else {
/* "#1" means the first company, which has index 0. */
*company_id = (CompanyID)(company_value - 1);
}
} else {
*company_id = (CompanyID)company_value;
}
}
}
return ip;
}
/**
* Converts a string to ip/port/company
* Format: IP:port#company
@@ -495,29 +530,7 @@ static void CheckPauseOnJoin()
*/
std::string_view ParseFullConnectionString(const std::string &connection_string, uint16 &port, CompanyID *company_id)
{
std::string_view ip = connection_string;
if (company_id != nullptr) {
size_t offset = ip.find_last_of('#');
if (offset != std::string::npos) {
std::string_view company_string = ip.substr(offset + 1);
ip = ip.substr(0, offset);
uint8 company_value;
auto [_, err] = std::from_chars(company_string.data(), company_string.data() + company_string.size(), company_value);
if (err == std::errc()) {
if (company_value != COMPANY_NEW_COMPANY && company_value != COMPANY_SPECTATOR) {
if (company_value > MAX_COMPANIES || company_value == 0) {
*company_id = COMPANY_SPECTATOR;
} else {
/* "#1" means the first company, which has index 0. */
*company_id = (CompanyID)(company_value - 1);
}
} else {
*company_id = (CompanyID)company_value;
}
}
}
}
std::string_view ip = ParseCompanyFromConnectionString(connection_string, company_id);
size_t port_offset = ip.find_last_of(':');
size_t ipv6_close = ip.find_last_of(']');
@@ -557,23 +570,6 @@ NetworkAddress ParseConnectionString(const std::string &connection_string, uint1
return NetworkAddress(ip, port);
}
/**
* Convert a string containing either "hostname" or "hostname:ip" to a
* NetworkAddress, where the string can be postfixed with "#company" to
* indicate the requested company.
*
* @param connection_string The string to parse.
* @param default_port The default port to set port to if not in connection_string.
* @param company Pointer to the company variable to set iff indicted.
* @return A valid NetworkAddress of the parsed information.
*/
static NetworkAddress ParseGameConnectionString(const std::string &connection_string, uint16 default_port, CompanyID *company)
{
uint16 port = default_port;
std::string_view ip = ParseFullConnectionString(connection_string, port, company);
return NetworkAddress(ip, port);
}
/**
* Handle the accepting of a connection to the server.
* @param s The socket of the new connection.
@@ -617,9 +613,15 @@ void NetworkClose(bool close_admins)
}
ServerNetworkGameSocketHandler::CloseListeners();
ServerNetworkAdminSocketHandler::CloseListeners();
} else if (MyClient::my_client != nullptr) {
MyClient::SendQuit();
MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
_network_coordinator_client.CloseConnection();
} else {
if (MyClient::my_client != nullptr) {
MyClient::SendQuit();
MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
}
_network_coordinator_client.CloseAllTokens();
}
TCPConnecter::KillAll();
@@ -639,7 +641,6 @@ void NetworkClose(bool close_admins)
static void NetworkInitialize(bool close_admins = true)
{
InitializeNetworkPools(close_admins);
NetworkUDPInitialize();
_sync_frame = 0;
_network_first_time = true;
@@ -652,12 +653,12 @@ static void NetworkInitialize(bool close_admins = true)
}
/** Non blocking connection to query servers for their game info. */
class TCPQueryConnecter : TCPConnecter {
class TCPQueryConnecter : TCPServerConnecter {
private:
std::string connection_string;
public:
TCPQueryConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
TCPQueryConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
void OnFailure() override
{
@@ -689,12 +690,12 @@ void NetworkQueryServer(const std::string &connection_string)
}
/** Non blocking connection to query servers for their game and company info. */
class TCPLobbyQueryConnecter : TCPConnecter {
class TCPLobbyQueryConnecter : TCPServerConnecter {
private:
std::string connection_string;
public:
TCPLobbyQueryConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
TCPLobbyQueryConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
void OnFailure() override
{
@@ -729,9 +730,11 @@ void NetworkQueryLobbyServer(const std::string &connection_string)
* the list. If you use this function, the games will be marked
* as manually added.
* @param connection_string The IP:port of the server to add.
* @param manually Whether the enter should be marked as manual added.
* @param never_expire Whether the entry can expire (removed when no longer found in the public listing).
* @return The entry on the game list.
*/
NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually)
NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually, bool never_expire)
{
if (connection_string.empty()) return nullptr;
@@ -747,6 +750,7 @@ NetworkGameList *NetworkAddServer(const std::string &connection_string, bool man
}
if (manually) item->manually = true;
if (never_expire) item->version = INT32_MAX;
return item;
}
@@ -781,12 +785,12 @@ void NetworkRebuildHostList()
}
/** Non blocking connection create to actually connect to servers */
class TCPClientConnecter : TCPConnecter {
class TCPClientConnecter : TCPServerConnecter {
private:
std::string connection_string;
public:
TCPClientConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
TCPClientConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
void OnFailure() override
{
@@ -822,7 +826,7 @@ public:
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password, const std::string &join_company_password)
{
CompanyID join_as = default_company;
std::string resolved_connection_string = ParseGameConnectionString(connection_string, NETWORK_DEFAULT_PORT, &join_as).GetAddressAsString(false);
std::string resolved_connection_string = ServerAddress::Parse(connection_string, NETWORK_DEFAULT_PORT, &join_as).connection_string;
if (!_network_available) return false;
if (!NetworkValidateOurClientName()) return false;
@@ -932,6 +936,7 @@ bool NetworkServerStart()
NetworkDisconnect(false, false);
NetworkInitialize(false);
NetworkUDPInitialize();
DEBUG(net, 5, "Starting listeners for clients");
if (!ServerNetworkGameSocketHandler::Listen(_settings_client.network.server_port)) return false;
@@ -959,15 +964,15 @@ bool NetworkServerStart()
NetworkInitGameInfo();
if (_settings_client.network.server_game_type != SERVER_GAME_TYPE_LOCAL) {
_network_coordinator_client.Register();
}
/* execute server initialization script */
IConsoleCmdExec("exec scripts/on_server.scr 0");
/* if the server is dedicated ... add some other script */
if (_network_dedicated) IConsoleCmdExec("exec scripts/on_dedicated.scr 0");
/* Try to register us to the master server */
_network_need_advertise = true;
NetworkUDPAdvertise();
/* welcome possibly still connected admins - this can only happen on a dedicated server. */
if (_network_dedicated) ServerNetworkAdminSocketHandler::WelcomeAll();
@@ -1016,8 +1021,6 @@ void NetworkDisconnect(bool blocking, bool close_admins)
}
}
if (_settings_client.network.server_advertise) NetworkUDPRemoveAdvertise(blocking);
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
NetworkClose(close_admins);
@@ -1026,6 +1029,29 @@ void NetworkDisconnect(bool blocking, bool close_admins)
NetworkUDPInitialize();
}
/**
* The setting server_game_type was updated; possibly we need to take some
* action.
*/
void NetworkUpdateServerGameType()
{
if (!_networking) return;
switch (_settings_client.network.server_game_type) {
case SERVER_GAME_TYPE_LOCAL:
_network_coordinator_client.CloseConnection();
break;
case SERVER_GAME_TYPE_INVITE_ONLY:
case SERVER_GAME_TYPE_PUBLIC:
_network_coordinator_client.Register();
break;
default:
NOT_REACHED();
}
}
/**
* Receives something from the network.
* @return true if everything went fine, false when the connection got closed.
@@ -1059,6 +1085,7 @@ static void NetworkSend()
void NetworkBackgroundLoop()
{
_network_content_client.SendReceive();
_network_coordinator_client.SendReceive();
TCPConnecter::CheckCallbacks();
NetworkHTTPSocketHandler::HTTPReceive();
@@ -1297,7 +1324,6 @@ void NetworkStartUp()
/* Network is available */
_network_available = NetworkCoreInitialize();
_network_dedicated = false;
_network_need_advertise = true;
/* Generate an server id when there is none yet */
if (_settings_client.network.network_id.empty()) NetworkGenerateServerId();
@@ -1305,6 +1331,7 @@ void NetworkStartUp()
_network_game_info = {};
NetworkInitialize();
NetworkUDPInitialize();
DEBUG(net, 3, "Network online, multiplayer available");
NetworkFindBroadcastIPs(&_broadcast_list);
}
@@ -1327,7 +1354,7 @@ extern "C" {
void CDECL em_openttd_add_server(const char *connection_string)
{
NetworkAddServer(connection_string, false);
NetworkAddServer(connection_string, false, true);
}
}

View File

@@ -140,9 +140,8 @@ void ClientNetworkEmergencySave()
if (!_networking) return;
if (!ClientNetworkGameSocketHandler::EmergencySavePossible()) return;
const char *filename = "netsave.sav";
DEBUG(net, 0, "Client: Performing emergency save (%s)", filename);
SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, false, SMF_ZSTD_OK);
static int _netsave_ctr = 0;
DoAutoOrNetsave(_netsave_ctr, true);
}
@@ -715,7 +714,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packe
item->online = true;
/* It could be either window, but only one is open, so redraw both. */
SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
UpdateNetworkGameWindow();
SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY);
/* We will receive company info next, so keep connection open. */

View File

@@ -367,7 +367,7 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const Conten
this->http_response_index = -1;
new NetworkHTTPContentConnecter(NETWORK_CONTENT_MIRROR_HOST, this, NETWORK_CONTENT_MIRROR_URL, content_request);
new NetworkHTTPContentConnecter(NetworkContentMirrorConnectionString(), this, NETWORK_CONTENT_MIRROR_URL, content_request);
/* NetworkHTTPContentConnecter takes over freeing of content_request! */
}
@@ -799,7 +799,7 @@ void ClientNetworkContentSocketHandler::Connect()
{
if (this->sock != INVALID_SOCKET || this->isConnecting) return;
this->isConnecting = true;
new NetworkContentConnecter(NETWORK_CONTENT_SERVER_HOST);
new NetworkContentConnecter(NetworkContentServerConnectionString());
}
/**

View File

@@ -0,0 +1,687 @@
/*
* 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_coordinator.cpp Game Coordinator sending/receiving part of the network protocol. */
#include "../stdafx.h"
#include "../debug.h"
#include "../error.h"
#include "../rev.h"
#include "../settings_type.h"
#include "../strings_func.h"
#include "../window_func.h"
#include "../window_type.h"
#include "network.h"
#include "network_coordinator.h"
#include "network_gamelist.h"
#include "network_internal.h"
#include "network_server.h"
#include "network_stun.h"
#include "table/strings.h"
#include "../safeguards.h"
static const auto NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES = std::chrono::seconds(30); ///< How many time between updates the server sends to the Game Coordinator.
ClientNetworkCoordinatorSocketHandler _network_coordinator_client; ///< The connection to the Game Coordinator.
ConnectionType _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; ///< What type of connection the Game Coordinator detected we are on.
std::string _network_server_invite_code = ""; ///< Our invite code as indicated by the Game Coordinator.
/** Connect to a game server by IP:port. */
class NetworkDirectConnecter : public TCPConnecter {
private:
std::string token; ///< Token of this connection.
uint8 tracking_number; ///< Tracking number of this connection.
public:
/**
* Try to establish a direct (hostname:port based) connection.
* @param hostname The hostname of the server.
* @param port The port of the server.
* @param token The token as given by the Game Coordinator to track this connection attempt.
* @param tracking_number The tracking number as given by the Game Coordinator to track this connection attempt.
*/
NetworkDirectConnecter(const std::string &hostname, uint16 port, const std::string &token, uint8 tracking_number) : TCPConnecter(hostname, port), token(token), tracking_number(tracking_number) {}
void OnFailure() override
{
_network_coordinator_client.ConnectFailure(this->token, this->tracking_number);
}
void OnConnect(SOCKET s) override
{
NetworkAddress address = NetworkAddress::GetPeerAddress(s);
_network_coordinator_client.ConnectSuccess(this->token, s, address);
}
};
/** Connecter used after STUN exchange to connect from both sides to each other. */
class NetworkReuseStunConnecter : public TCPConnecter {
private:
std::string token; ///< Token of this connection.
uint8 tracking_number; ///< Tracking number of this connection.
uint8 family; ///< Family of this connection.
public:
/**
* Try to establish a STUN-based connection.
* @param hostname The hostname of the peer.
* @param port The port of the peer.
* @param bind_address The local bind address used for this connection.
* @param token The connection token.
* @param tracking_number The tracking number of the connection.
* @param family The family this connection is using.
*/
NetworkReuseStunConnecter(const std::string &hostname, uint16 port, const NetworkAddress &bind_address, std::string token, uint8 tracking_number, uint8 family) :
TCPConnecter(hostname, port, bind_address),
token(token),
tracking_number(tracking_number),
family(family)
{
}
void OnFailure() override
{
/* Close the STUN connection too, as it is no longer of use. */
_network_coordinator_client.CloseStunHandler(this->token, this->family);
_network_coordinator_client.ConnectFailure(this->token, this->tracking_number);
}
void OnConnect(SOCKET s) override
{
NetworkAddress address = NetworkAddress::GetPeerAddress(s);
_network_coordinator_client.ConnectSuccess(this->token, s, address);
}
};
/** Connect to the Game Coordinator server. */
class NetworkCoordinatorConnecter : TCPConnecter {
public:
/**
* Initiate the connecting.
* @param connection_string The address of the Game Coordinator server.
*/
NetworkCoordinatorConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_COORDINATOR_SERVER_PORT) {}
void OnFailure() override
{
_network_coordinator_client.connecting = false;
_network_coordinator_client.CloseConnection(true);
}
void OnConnect(SOCKET s) override
{
assert(_network_coordinator_client.sock == INVALID_SOCKET);
_network_coordinator_client.sock = s;
_network_coordinator_client.last_activity = std::chrono::steady_clock::now();
_network_coordinator_client.connecting = false;
}
};
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p)
{
NetworkCoordinatorErrorType error = (NetworkCoordinatorErrorType)p->Recv_uint8();
std::string detail = p->Recv_string(NETWORK_ERROR_DETAIL_LENGTH);
switch (error) {
case NETWORK_COORDINATOR_ERROR_UNKNOWN:
this->CloseConnection();
return false;
case NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED:
SetDParamStr(0, detail);
ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED, STR_JUST_RAW_STRING, WL_ERROR);
/* To prevent that we constantly try to reconnect, switch to local game. */
_settings_client.network.server_game_type = SERVER_GAME_TYPE_LOCAL;
this->CloseConnection();
return false;
case NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE: {
this->CloseToken(detail);
/* Mark the server as offline. */
NetworkGameList *item = NetworkGameListAddItem(detail);
item->online = false;
UpdateNetworkGameWindow();
return true;
}
default:
DEBUG(net, 0, "Invalid error type %u received from Game Coordinator", error);
this->CloseConnection();
return false;
}
}
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p)
{
/* Schedule sending an update. */
this->next_update = std::chrono::steady_clock::now();
_settings_client.network.server_invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH);
_settings_client.network.server_invite_code_secret = p->Recv_string(NETWORK_INVITE_CODE_SECRET_LENGTH);
_network_server_connection_type = (ConnectionType)p->Recv_uint8();
if (_network_server_connection_type == CONNECTION_TYPE_ISOLATED) {
ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_ISOLATED, STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL, WL_ERROR);
}
/* Users can change the invite code in the settings, but this has no effect
* on the invite code as assigned by the server. So
* _network_server_invite_code contains the current invite code,
* and _settings_client.network.server_invite_code contains the one we will
* attempt to re-use when registering again. */
_network_server_invite_code = _settings_client.network.server_invite_code;
SetWindowDirty(WC_CLIENT_LIST, 0);
if (_network_dedicated) {
std::string connection_type;
switch (_network_server_connection_type) {
case CONNECTION_TYPE_ISOLATED: connection_type = "Remote players can't connect"; break;
case CONNECTION_TYPE_DIRECT: connection_type = "Public"; break;
case CONNECTION_TYPE_STUN: connection_type = "Behind NAT"; 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.
}
std::string game_type;
switch (_settings_client.network.server_game_type) {
case SERVER_GAME_TYPE_INVITE_ONLY: game_type = "Invite only"; break;
case SERVER_GAME_TYPE_PUBLIC: game_type = "Public"; break;
case SERVER_GAME_TYPE_LOCAL: // Impossible to register local servers.
default: game_type = "Unknown"; break; // Should never happen, but don't fail if it does.
}
DEBUG(net, 3, "----------------------------------------");
DEBUG(net, 3, "Your server is now registered with the Game Coordinator:");
DEBUG(net, 3, " Game type: %s", game_type.c_str());
DEBUG(net, 3, " Connection type: %s", connection_type.c_str());
DEBUG(net, 3, " Invite code: %s", _network_server_invite_code.c_str());
DEBUG(net, 3, "----------------------------------------");
} else {
DEBUG(net, 3, "Game Coordinator registered our server with invite code '%s'", _network_server_invite_code.c_str());
}
return true;
}
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p)
{
uint8 servers = p->Recv_uint16();
/* End of list; we can now remove all expired items from the list. */
if (servers == 0) {
NetworkGameListRemoveExpired();
return true;
}
for (; servers > 0; servers--) {
std::string connection_string = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH);
/* Read the NetworkGameInfo from the packet. */
NetworkGameInfo ngi = {};
DeserializeNetworkGameInfo(p, &ngi);
/* Now we know the connection string, we can add it to our list. */
NetworkGameList *item = NetworkGameListAddItem(connection_string);
/* Clear any existing GRFConfig chain. */
ClearGRFConfigList(&item->info.grfconfig);
/* Copy the new NetworkGameInfo info. */
item->info = ngi;
/* Check for compatability with the client. */
CheckGameCompatibility(item->info);
/* Mark server as online. */
item->online = true;
/* Mark the item as up-to-date. */
item->version = _network_game_list_version;
}
UpdateNetworkGameWindow();
return true;
}
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p)
{
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
std::string invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH);
/* Find the connecter based on the invite code. */
auto connecter_it = this->connecter_pre.find(invite_code);
if (connecter_it == this->connecter_pre.end()) {
this->CloseConnection();
return false;
}
/* Now store it based on the token. */
this->connecter[token] = connecter_it->second;
this->connecter_pre.erase(connecter_it);
return true;
}
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p)
{
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
auto connecter_it = this->connecter.find(token);
if (connecter_it != this->connecter.end()) {
connecter_it->second->SetFailure();
this->connecter.erase(connecter_it);
}
/* Close all remaining connections. */
this->CloseToken(token);
return true;
}
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p)
{
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
uint8 tracking_number = p->Recv_uint8();
std::string hostname = p->Recv_string(NETWORK_HOSTNAME_LENGTH);
uint16 port = p->Recv_uint16();
/* Ensure all other pending connection attempts are killed. */
if (this->game_connecter != nullptr) {
this->game_connecter->Kill();
this->game_connecter = nullptr;
}
this->game_connecter = new NetworkDirectConnecter(hostname, port, token, tracking_number);
return true;
}
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet *p)
{
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
this->stun_handlers[token][AF_INET6] = ClientNetworkStunSocketHandler::Stun(token, AF_INET6);
this->stun_handlers[token][AF_INET] = ClientNetworkStunSocketHandler::Stun(token, AF_INET);
return true;
}
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_CONNECT(Packet *p)
{
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
uint8 tracking_number = p->Recv_uint8();
uint8 family = p->Recv_uint8();
std::string host = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH);
uint16 port = p->Recv_uint16();
/* Check if we know this token. */
auto stun_it = this->stun_handlers.find(token);
if (stun_it == this->stun_handlers.end()) return true;
auto family_it = stun_it->second.find(family);
if (family_it == stun_it->second.end()) return true;
/* Ensure all other pending connection attempts are killed. */
if (this->game_connecter != nullptr) {
this->game_connecter->Kill();
this->game_connecter = nullptr;
}
/* We now mark the connection as closed, but we do not really close the
* socket yet. We do this when the NetworkReuseStunConnecter is connected.
* This prevents any NAT to already remove the route while we create the
* second connection on top of the first. */
family_it->second->CloseConnection(false);
/* Connect to our peer from the same local address as we use for the
* STUN server. This means that if there is any NAT in the local network,
* the public ip:port is still pointing to the local address, and as such
* a connection can be established. */
this->game_connecter = new NetworkReuseStunConnecter(host, port, family_it->second->local_addr, token, tracking_number, family);
return true;
}
void ClientNetworkCoordinatorSocketHandler::Connect()
{
/* We are either already connected or are trying to connect. */
if (this->sock != INVALID_SOCKET || this->connecting) return;
this->Reopen();
this->connecting = true;
this->last_activity = std::chrono::steady_clock::now();
new NetworkCoordinatorConnecter(NetworkCoordinatorConnectionString());
}
NetworkRecvStatus ClientNetworkCoordinatorSocketHandler::CloseConnection(bool error)
{
NetworkCoordinatorSocketHandler::CloseConnection(error);
this->CloseSocket();
this->connecting = false;
_network_server_connection_type = CONNECTION_TYPE_UNKNOWN;
this->next_update = {};
this->CloseAllTokens();
SetWindowDirty(WC_CLIENT_LIST, 0);
return NETWORK_RECV_STATUS_OKAY;
}
/**
* Register our server to receive our invite code.
*/
void ClientNetworkCoordinatorSocketHandler::Register()
{
_network_server_connection_type = CONNECTION_TYPE_UNKNOWN;
this->next_update = {};
SetWindowDirty(WC_CLIENT_LIST, 0);
this->Connect();
Packet *p = new Packet(PACKET_COORDINATOR_SERVER_REGISTER);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_uint8(_settings_client.network.server_game_type);
p->Send_uint16(_settings_client.network.server_port);
if (_settings_client.network.server_invite_code.empty() || _settings_client.network.server_invite_code_secret.empty()) {
p->Send_string("");
p->Send_string("");
} else {
p->Send_string(_settings_client.network.server_invite_code);
p->Send_string(_settings_client.network.server_invite_code_secret);
}
this->SendPacket(p);
}
/**
* Send an update of our server status to the Game Coordinator.
*/
void ClientNetworkCoordinatorSocketHandler::SendServerUpdate()
{
DEBUG(net, 6, "Sending server update to Game Coordinator");
this->next_update = std::chrono::steady_clock::now() + NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES;
Packet *p = new Packet(PACKET_COORDINATOR_SERVER_UPDATE, TCP_MTU);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
SerializeNetworkGameInfo(p, GetCurrentNetworkServerGameInfo());
this->SendPacket(p);
}
/**
* Request a listing of all public servers.
*/
void ClientNetworkCoordinatorSocketHandler::GetListing()
{
this->Connect();
_network_game_list_version++;
Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_LISTING);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_uint8(NETWORK_GAME_INFO_VERSION);
p->Send_string(_openttd_revision);
this->SendPacket(p);
}
/**
* Join a server based on an invite code.
* @param invite_code The invite code of the server to connect to.
* @param connecter The connecter of the request.
*/
void ClientNetworkCoordinatorSocketHandler::ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter)
{
assert(StrStartsWith(invite_code, "+"));
if (this->connecter_pre.find(invite_code) != this->connecter_pre.end()) {
/* If someone is hammering the refresh key, one can sent out two
* requests for the same invite code. There isn't really a great way
* of handling this, so just ignore this request. */
connecter->SetFailure();
return;
}
/* Initially we store based on invite code; on first reply we know the
* token, and will start using that key instead. */
this->connecter_pre[invite_code] = connecter;
this->Connect();
Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECT);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(invite_code);
this->SendPacket(p);
}
/**
* Callback from a Connecter to let the Game Coordinator know the connection failed.
* @param token Token of the connecter that failed.
* @param tracking_number Tracking number of the connecter that failed.
*/
void ClientNetworkCoordinatorSocketHandler::ConnectFailure(const std::string &token, uint8 tracking_number)
{
/* Connecter will destroy itself. */
this->game_connecter = nullptr;
Packet *p = new Packet(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(token);
p->Send_uint8(tracking_number);
this->SendPacket(p);
/* We do not close the associated connecter here yet, as the
* Game Coordinator might have other methods of connecting available. */
}
/**
* Callback from a Connecter to let the Game Coordinator know the connection
* to the game server is established.
* @param token Token of the connecter that succeeded.
* @param sock The socket that the connecter can now use.
*/
void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &token, SOCKET sock, NetworkAddress &address)
{
/* Connecter will destroy itself. */
this->game_connecter = nullptr;
if (_network_server) {
if (!ServerNetworkGameSocketHandler::ValidateClient(sock, address)) return;
DEBUG(net, 3, "[%s] Client connected from %s on frame %u", ServerNetworkGameSocketHandler::GetName(), address.GetHostname(), _frame_counter);
ServerNetworkGameSocketHandler::AcceptConnection(sock, address);
} else {
/* The client informs the Game Coordinator about the success. The server
* doesn't have to, as it is implied by the client telling. */
Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECTED);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(token);
this->SendPacket(p);
auto connecter_it = this->connecter.find(token);
assert(connecter_it != this->connecter.end());
connecter_it->second->SetConnected(sock);
this->connecter.erase(connecter_it);
}
/* Close all remaining connections. */
this->CloseToken(token);
}
/**
* Callback from the STUN connecter to inform the Game Coordinator about the
* result of the STUN.
*
* This helps the Game Coordinator not to wait for a timeout on its end, but
* rather react as soon as the client/server knows the result.
*/
void ClientNetworkCoordinatorSocketHandler::StunResult(const std::string &token, uint8 family, bool result)
{
Packet *p = new Packet(PACKET_COORDINATOR_SERCLI_STUN_RESULT);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(token);
p->Send_uint8(family);
p->Send_bool(result);
this->SendPacket(p);
}
void ClientNetworkCoordinatorSocketHandler::CloseStunHandler(const std::string &token, uint8 family)
{
auto stun_it = this->stun_handlers.find(token);
if (stun_it == this->stun_handlers.end()) return;
if (family == AF_UNSPEC) {
for (auto &[family, stun_handler] : stun_it->second) {
stun_handler->CloseConnection();
stun_handler->CloseSocket();
}
this->stun_handlers.erase(stun_it);
} else {
auto family_it = stun_it->second.find(family);
if (family_it == stun_it->second.end()) return;
family_it->second->CloseConnection();
family_it->second->CloseSocket();
stun_it->second.erase(family_it);
}
}
/**
* Close everything related to this connection token.
* @param token The connection token to close.
*/
void ClientNetworkCoordinatorSocketHandler::CloseToken(const std::string &token)
{
/* Ensure all other pending connection attempts are also killed. */
if (this->game_connecter != nullptr) {
this->game_connecter->Kill();
this->game_connecter = nullptr;
}
/* Close all remaining STUN connections. */
this->CloseStunHandler(token);
/* Close the caller of the connection attempt. */
auto connecter_it = this->connecter.find(token);
if (connecter_it != this->connecter.end()) {
connecter_it->second->SetFailure();
this->connecter.erase(connecter_it);
}
auto connecter_pre_it = this->connecter_pre.find(token);
if (connecter_pre_it != this->connecter_pre.end()) {
connecter_pre_it->second->SetFailure();
this->connecter_pre.erase(connecter_pre_it);
}
}
/**
* Close all pending connection tokens.
*/
void ClientNetworkCoordinatorSocketHandler::CloseAllTokens()
{
/* Ensure all other pending connection attempts are also killed. */
if (this->game_connecter != nullptr) {
this->game_connecter->Kill();
this->game_connecter = nullptr;
}
/* Mark any pending connecters as failed. */
for (auto &[token, it] : this->connecter) {
this->CloseStunHandler(token);
it->SetFailure();
}
for (auto &[invite_code, it] : this->connecter_pre) {
it->SetFailure();
}
this->connecter.clear();
this->connecter_pre.clear();
}
/**
* Check whether we received/can send some data from/to the Game Coordinator server and
* when that's the case handle it appropriately.
*/
void ClientNetworkCoordinatorSocketHandler::SendReceive()
{
/* Private games are not listed via the Game Coordinator. */
if (_network_server && _settings_client.network.server_game_type == SERVER_GAME_TYPE_LOCAL) {
if (this->sock != INVALID_SOCKET) {
this->CloseConnection();
}
return;
}
static int last_attempt_backoff = 1;
static bool first_reconnect = true;
if (this->sock == INVALID_SOCKET) {
static std::chrono::steady_clock::time_point last_attempt = {};
/* Don't auto-reconnect when we are not a server. */
if (!_network_server) return;
/* Don't reconnect if we are connecting. */
if (this->connecting) return;
/* Throttle how often we try to reconnect. */
if (std::chrono::steady_clock::now() < last_attempt + std::chrono::seconds(1) * last_attempt_backoff) return;
last_attempt = std::chrono::steady_clock::now();
/* Delay reconnecting with up to 32 seconds. */
if (last_attempt_backoff < 32) {
last_attempt_backoff *= 2;
}
/* Do not reconnect on the first attempt, but only initialize the
* last_attempt variables. Otherwise after an outage all servers
* reconnect at the same time, potentially overwhelming the
* Game Coordinator. */
if (first_reconnect) {
first_reconnect = false;
return;
}
DEBUG(net, 1, "Connection with Game Coordinator lost; reconnecting...");
this->Register();
return;
}
last_attempt_backoff = 1;
first_reconnect = true;
if (_network_server && _network_server_connection_type != CONNECTION_TYPE_UNKNOWN && std::chrono::steady_clock::now() > this->next_update) {
this->SendServerUpdate();
}
if (!_network_server && std::chrono::steady_clock::now() > this->last_activity + IDLE_TIMEOUT) {
this->CloseConnection();
return;
}
if (this->CanSendReceive()) {
if (this->ReceivePackets()) {
this->last_activity = std::chrono::steady_clock::now();
}
}
this->SendPackets();
for (const auto &[token, families] : this->stun_handlers) {
for (const auto &[family, stun_handler] : families) {
stun_handler->SendReceive();
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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_coordinator.h Part of the network protocol handling Game Coordinator requests. */
#ifndef NETWORK_COORDINATOR_H
#define NETWORK_COORDINATOR_H
#include "core/tcp_coordinator.h"
#include "network_stun.h"
#include <map>
/**
* Game Coordinator communication.
* For more detail about what the Game Coordinator does, please see
* docs/game_coordinator.md.
*
* For servers:
* - Server sends SERVER_REGISTER.
* - Game Coordinator probes server to check if it can directly connect.
* - Game Coordinator sends GC_REGISTER_ACK with type of connection.
* - Server sends every 30 seconds SERVER_UPDATE.
*
* For clients (listing):
* - Client sends CLIENT_LISTING.
* - Game Coordinator returns the full list of public servers via GC_LISTING (multiple packets).
*
* For clients (connecting):
* - Client sends CLIENT_CONNECT.
* - Game Coordinator checks what type of connections the servers supports:
* 1) Direct connect?
* - Send the client a GC_CONNECT with the peer address.
* - a) Client connects, client sends CLIENT_CONNECTED to Game Coordinator.
* - b) Client connect fails, client sends CLIENT_CONNECT_FAILED to Game Coordinator.
* 2) STUN?
* - Game Coordinator sends GC_STUN_REQUEST to server/client (asking for both IPv4 and IPv6 STUN requests).
* - Game Coordinator collects what combination works and sends GC_STUN_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.
* - Game Coordinator tries other combination if available.
* - If all fails, Game Coordinator sends GC_CONNECT_FAILED to indicate no connection is possible.
*/
/** Class for handling the client side of the Game Coordinator connection. */
class ClientNetworkCoordinatorSocketHandler : public NetworkCoordinatorSocketHandler {
private:
std::chrono::steady_clock::time_point next_update; ///< When to send the next update (if server and public).
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.
TCPConnecter *game_connecter = nullptr; ///< Pending connecter to the game server.
protected:
bool Receive_GC_ERROR(Packet *p) override;
bool Receive_GC_REGISTER_ACK(Packet *p) override;
bool Receive_GC_LISTING(Packet *p) override;
bool Receive_GC_CONNECTING(Packet *p) override;
bool Receive_GC_CONNECT_FAILED(Packet *p) override;
bool Receive_GC_DIRECT_CONNECT(Packet *p) override;
bool Receive_GC_STUN_REQUEST(Packet *p) override;
bool Receive_GC_STUN_CONNECT(Packet *p) override;
public:
/** The idle timeout; when to close the connection because it's idle. */
static constexpr std::chrono::seconds IDLE_TIMEOUT = std::chrono::seconds(60);
std::chrono::steady_clock::time_point last_activity; ///< The last time there was network activity.
bool connecting; ///< Are we connecting to the Game Coordinator?
ClientNetworkCoordinatorSocketHandler() : connecting(false) {}
NetworkRecvStatus CloseConnection(bool error = true) override;
void SendReceive();
void ConnectFailure(const std::string &token, uint8 tracking_number);
void ConnectSuccess(const std::string &token, SOCKET sock, NetworkAddress &address);
void StunResult(const std::string &token, uint8 family, bool result);
void Connect();
void CloseToken(const std::string &token);
void CloseAllTokens();
void CloseStunHandler(const std::string &token, uint8 family = AF_UNSPEC);
void Register();
void SendServerUpdate();
void GetListing();
void ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter);
};
extern ClientNetworkCoordinatorSocketHandler _network_coordinator_client;
#endif /* NETWORK_COORDINATOR_H */

View File

@@ -28,7 +28,6 @@ extern NetworkCompanyState *_network_company_states;
extern ClientID _network_own_client_id;
extern ClientID _redirect_console_to_client;
extern bool _network_need_advertise;
extern uint8 _network_reconnect;
extern StringList _network_bind_list;
extern StringList _network_host_list;
@@ -40,6 +39,7 @@ bool NetworkValidateOurClientName();
bool NetworkValidateClientName(std::string &client_name);
bool NetworkValidateServerName(std::string &server_name);
void NetworkUpdateClientName(const std::string &client_name);
void NetworkUpdateServerGameType();
bool NetworkCompanyHasClients(CompanyID company);
std::string NetworkChangeCompanyPassword(CompanyID company_id, std::string password);
void NetworkReboot();

View File

@@ -20,51 +20,13 @@
#include "../safeguards.h"
NetworkGameList *_network_game_list = nullptr;
/** The games to insert when the GUI thread has time for us. */
static std::atomic<NetworkGameList *> _network_game_delayed_insertion_list(nullptr);
/**
* Add a new item to the linked gamelist, but do it delayed in the next tick
* or so to prevent race conditions.
* @param item the item to add. Will be freed once added.
*/
void NetworkGameListAddItemDelayed(NetworkGameList *item)
{
item->next = _network_game_delayed_insertion_list.load(std::memory_order_relaxed);
while (!_network_game_delayed_insertion_list.compare_exchange_weak(item->next, item, std::memory_order_acq_rel)) {}
}
/** Perform the delayed (thread safe) insertion into the game list */
static void NetworkGameListHandleDelayedInsert()
{
while (true) {
NetworkGameList *ins_item = _network_game_delayed_insertion_list.load(std::memory_order_acquire);
while (ins_item != nullptr && !_network_game_delayed_insertion_list.compare_exchange_weak(ins_item, ins_item->next, std::memory_order_acq_rel)) {}
if (ins_item == nullptr) break; // No item left.
NetworkGameList *item = NetworkGameListAddItem(ins_item->connection_string);
if (item != nullptr) {
if (item->info.server_name.empty()) {
ClearGRFConfigList(&item->info.grfconfig);
item->info = {};
item->info.server_name = ins_item->info.server_name;
item->online = false;
}
item->manually |= ins_item->manually;
if (item->manually) NetworkRebuildHostList();
UpdateNetworkGameWindow();
}
delete ins_item;
}
}
NetworkGameList *_network_game_list = nullptr; ///< Game list of this client.
int _network_game_list_version = 0; ///< Current version of all items in the list.
/**
* Add a new item to the linked gamelist. If the IP and Port match
* return the existing item instead of adding it again
* @param address the address of the to-be added item
* @param connection_string the address of the to-be added item
* @return a point to the newly added or already existing item
*/
NetworkGameList *NetworkGameListAddItem(const std::string &connection_string)
@@ -72,7 +34,7 @@ NetworkGameList *NetworkGameListAddItem(const std::string &connection_string)
NetworkGameList *item, *prev_item;
/* Parse the connection string to ensure the default port is there. */
const std::string resolved_connection_string = ParseConnectionString(connection_string, NETWORK_DEFAULT_PORT).GetAddressAsString(false);
const std::string resolved_connection_string = ServerAddress::Parse(connection_string, NETWORK_DEFAULT_PORT).connection_string;
prev_item = nullptr;
for (item = _network_game_list; item != nullptr; item = item->next) {
@@ -81,6 +43,7 @@ NetworkGameList *NetworkGameListAddItem(const std::string &connection_string)
}
item = new NetworkGameList(resolved_connection_string);
item->version = _network_game_list_version;
if (prev_item == nullptr) {
_network_game_list = item;
@@ -120,29 +83,31 @@ void NetworkGameListRemoveItem(NetworkGameList *remove)
}
}
static const uint MAX_GAME_LIST_REQUERY_COUNT = 10; ///< How often do we requery in number of times per server?
static const uint REQUERY_EVERY_X_GAMELOOPS = 60; ///< How often do we requery in time?
static const uint REFRESH_GAMEINFO_X_REQUERIES = 50; ///< Refresh the game info itself after REFRESH_GAMEINFO_X_REQUERIES * REQUERY_EVERY_X_GAMELOOPS game loops
/** Requeries the (game) servers we have not gotten a reply from */
void NetworkGameListRequery()
/**
* Remove all servers that have not recently been updated.
* Call this after you received all the servers from the Game Coordinator, so
* the ones that are no longer listed are removed.
*/
void NetworkGameListRemoveExpired()
{
NetworkGameListHandleDelayedInsert();
NetworkGameList **prev_item = &_network_game_list;
static uint8 requery_cnt = 0;
for (NetworkGameList *item = _network_game_list; item != nullptr;) {
if (!item->manually && item->version < _network_game_list_version) {
NetworkGameList *remove = item;
item = item->next;
*prev_item = item;
if (++requery_cnt < REQUERY_EVERY_X_GAMELOOPS) return;
requery_cnt = 0;
for (NetworkGameList *item = _network_game_list; item != nullptr; item = item->next) {
item->retries++;
if (item->retries < REFRESH_GAMEINFO_X_REQUERIES && (item->online || item->retries >= MAX_GAME_LIST_REQUERY_COUNT)) continue;
/* item gets mostly zeroed by NetworkUDPQueryServer */
uint8 retries = item->retries;
NetworkUDPQueryServer(item->connection_string);
item->retries = (retries >= REFRESH_GAMEINFO_X_REQUERIES) ? 0 : retries;
/* Remove GRFConfig information */
ClearGRFConfigList(&remove->info.grfconfig);
delete remove;
} else {
prev_item = &item->next;
item = item->next;
}
}
UpdateNetworkGameWindow();
}
/**

View File

@@ -16,25 +16,22 @@
/** Structure with information shown in the game list (GUI) */
struct NetworkGameList {
NetworkGameList(const std::string &connection_string, bool manually = false) :
connection_string(connection_string), manually(manually)
{
}
NetworkGameList(const std::string &connection_string) : connection_string(connection_string) {}
NetworkGameInfo info = {}; ///< The game information of this server
std::string connection_string; ///< Address of the server
bool online = false; ///< False if the server did not respond (default status)
bool manually = false; ///< True if the server was added manually
uint8 retries = 0; ///< Number of retries (to stop requerying)
int version = 0; ///< Used to see which servers are no longer available on the Game Coordinator and can be removed.
NetworkGameList *next = nullptr; ///< Next pointer to make a linked game list
};
/** Game list of this client */
extern NetworkGameList *_network_game_list;
extern int _network_game_list_version;
void NetworkGameListAddItemDelayed(NetworkGameList *item);
NetworkGameList *NetworkGameListAddItem(const std::string &connection_string);
void NetworkGameListRemoveItem(NetworkGameList *remove);
void NetworkGameListRequery();
void NetworkGameListRemoveExpired();
#endif /* NETWORK_GAMELIST_H */

View File

@@ -18,6 +18,7 @@
#include "network_base.h"
#include "network_content.h"
#include "network_server.h"
#include "network_coordinator.h"
#include "../gui.h"
#include "network_udp.h"
#include "../window_func.h"
@@ -56,19 +57,11 @@
static void ShowNetworkStartServerWindow();
static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
static const int NETWORK_LIST_REFRESH_DELAY = 30; ///< Time, in seconds, between updates of the network list.
static ClientID _admin_client_id = INVALID_CLIENT_ID; ///< For what client a confirmation window is open.
static CompanyID _admin_company_id = INVALID_COMPANY; ///< For what company a confirmation window is open.
/**
* Visibility of the server. Public servers advertise, where private servers
* do not.
*/
static const StringID _server_visibility_dropdown[] = {
STR_NETWORK_SERVER_VISIBILITY_PRIVATE,
STR_NETWORK_SERVER_VISIBILITY_PUBLIC,
INVALID_STRING_ID
};
/**
* Update the network new window because a new server is
* found on the network.
@@ -78,6 +71,17 @@ void UpdateNetworkGameWindow()
InvalidateWindowData(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME, 0);
}
static DropDownList BuildVisibilityDropDownList()
{
DropDownList list;
list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_LOCAL, SERVER_GAME_TYPE_LOCAL, false));
list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY, SERVER_GAME_TYPE_INVITE_ONLY, false));
list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_PUBLIC, SERVER_GAME_TYPE_PUBLIC, false));
return list;
}
typedef GUIList<NetworkGameList*, StringFilter&> GUIGameServerList;
typedef int ServerListPosition;
static const ServerListPosition SLP_INVALID = -1;
@@ -234,14 +238,15 @@ protected:
static GUIGameServerList::SortFunction * const sorter_funcs[];
static GUIGameServerList::FilterFunction * const filter_funcs[];
NetworkGameList *server; ///< selected server
NetworkGameList *last_joined; ///< the last joined server
GUIGameServerList servers; ///< list with game servers.
ServerListPosition list_pos; ///< position of the selected server
Scrollbar *vscroll; ///< vertical scrollbar of the list of servers
QueryString name_editbox; ///< Client name editbox.
QueryString filter_editbox; ///< Editbox for filter on servers
GUITimer requery_timer; ///< Timer for network requery
NetworkGameList *server; ///< Selected server.
NetworkGameList *last_joined; ///< The last joined server.
GUIGameServerList servers; ///< List with game servers.
ServerListPosition list_pos; ///< Position of the selected server.
Scrollbar *vscroll; ///< Vertical scrollbar of the list of servers.
QueryString name_editbox; ///< Client name editbox.
QueryString filter_editbox; ///< Editbox for filter on servers.
GUITimer requery_timer; ///< Timer for network requery.
bool searched_internet = false; ///< Did we ever press "Search Internet" button?
int lock_offset; ///< Left offset for lock icon.
int blot_offset; ///< Left offset for green/yellow/red compatibility icon.
@@ -259,8 +264,18 @@ protected:
/* Create temporary array of games to use for listing */
this->servers.clear();
bool found_current_server = false;
for (NetworkGameList *ngl = _network_game_list; ngl != nullptr; ngl = ngl->next) {
this->servers.push_back(ngl);
if (ngl == this->server) {
found_current_server = true;
}
}
/* A refresh can cause the current server to be delete; so unselect. */
if (!found_current_server) {
if (this->server == this->last_joined) this->last_joined = nullptr;
this->server = nullptr;
this->list_pos = SLP_INVALID;
}
/* Apply the filter condition immediately, if a search string has been provided. */
@@ -494,7 +509,7 @@ public:
this->last_joined = NetworkAddServer(_settings_client.network.last_joined, false);
this->server = this->last_joined;
this->requery_timer.SetInterval(MILLISECONDS_PER_TICK);
this->requery_timer.SetInterval(NETWORK_LIST_REFRESH_DELAY * 1000);
this->servers.SetListing(this->last_sorting);
this->servers.SetSortFuncs(this->sorter_funcs);
@@ -675,6 +690,13 @@ public:
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CURRENT_DATE); // current date
y += FONT_HEIGHT_NORMAL;
if (sel->info.gamescript_version != -1) {
SetDParamStr(0, sel->info.gamescript_name);
SetDParam(1, sel->info.gamescript_version);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_GAMESCRIPT); // gamescript name and version
y += FONT_HEIGHT_NORMAL;
}
y += WD_PAR_VSEP_NORMAL;
if (!sel->info.compatible) {
@@ -740,7 +762,8 @@ public:
}
case WID_NG_SEARCH_INTERNET:
NetworkUDPQueryMasterServer();
_network_coordinator_client.GetListing();
this->searched_internet = true;
break;
case WID_NG_SEARCH_LAN:
@@ -751,7 +774,7 @@ public:
SetDParamStr(0, _settings_client.network.connect_to_ip);
ShowQueryString(
STR_JUST_RAW_STRING,
STR_NETWORK_SERVER_LIST_ENTER_IP,
STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS,
NETWORK_HOSTNAME_PORT_LENGTH, // maximum number of characters including '\0'
this, CS_ALPHANUMERAL, QSF_ACCEPT_UNCHANGED);
break;
@@ -856,10 +879,11 @@ public:
void OnRealtimeTick(uint delta_ms) override
{
if (!this->searched_internet) return;
if (!this->requery_timer.Elapsed(delta_ms)) return;
this->requery_timer.SetInterval(MILLISECONDS_PER_TICK);
this->requery_timer.SetInterval(NETWORK_LIST_REFRESH_DELAY * 1000);
NetworkGameListRequery();
_network_coordinator_client.GetListing();
}
};
@@ -1014,7 +1038,7 @@ struct NetworkStartServerWindow : public Window {
{
switch (widget) {
case WID_NSS_CONNTYPE_BTN:
SetDParam(0, _server_visibility_dropdown[_settings_client.network.server_advertise]);
SetDParam(0, STR_NETWORK_SERVER_VISIBILITY_LOCAL + _settings_client.network.server_game_type);
break;
case WID_NSS_CLIENTS_TXT:
@@ -1035,7 +1059,7 @@ struct NetworkStartServerWindow : public Window {
{
switch (widget) {
case WID_NSS_CONNTYPE_BTN:
*size = maxdim(GetStringBoundingBox(_server_visibility_dropdown[0]), GetStringBoundingBox(_server_visibility_dropdown[1]));
*size = maxdim(maxdim(GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_LOCAL), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_PUBLIC)), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY));
size->width += padding.width;
size->height += padding.height;
break;
@@ -1065,7 +1089,7 @@ struct NetworkStartServerWindow : public Window {
break;
case WID_NSS_CONNTYPE_BTN: // Connection type
ShowDropDownMenu(this, _server_visibility_dropdown, _settings_client.network.server_advertise, WID_NSS_CONNTYPE_BTN, 0, 0); // do it for widget WID_NSS_CONNTYPE_BTN
ShowDropDownList(this, BuildVisibilityDropDownList(), _settings_client.network.server_game_type, WID_NSS_CONNTYPE_BTN);
break;
case WID_NSS_CLIENTS_BTND: case WID_NSS_CLIENTS_BTNU: // Click on up/down button for number of clients
@@ -1143,7 +1167,7 @@ struct NetworkStartServerWindow : public Window {
{
switch (widget) {
case WID_NSS_CONNTYPE_BTN:
_settings_client.network.server_advertise = (index != 0);
_settings_client.network.server_game_type = (ServerGameType)index;
break;
default:
NOT_REACHED();
@@ -1622,21 +1646,31 @@ static const NWidgetPart _nested_client_list_widgets[] = {
NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER, STR_NULL), SetPadding(4, 4, 0, 4), SetPIP(0, 2, 0),
NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_NAME, STR_NULL),
NWidget(NWID_SPACER), SetMinimalSize(20, 0),
NWidget(NWID_SPACER), SetMinimalSize(10, 0),
NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_SERVER_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY, STR_NULL),
NWidget(NWID_SPACER), SetMinimalSize(20, 0), SetFill(1, 0), SetResize(1, 0),
NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(1, 0), SetResize(1, 0),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_CL_SERVER_VISIBILITY), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE, STR_NULL),
NWidget(NWID_SPACER), SetMinimalSize(10, 0),
NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_INVITE_CODE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE, STR_NULL),
NWidget(NWID_SPACER), SetMinimalSize(10, 0),
NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_CONNECTION_TYPE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER, STR_NULL), SetPadding(4, 4, 4, 4), SetPIP(0, 2, 0),
NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER_NAME, STR_NULL),
NWidget(NWID_SPACER), SetMinimalSize(20, 0),
NWidget(NWID_SPACER), SetMinimalSize(10, 0),
NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_CLIENT_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_CLIENT_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_EDIT_TOOLTIP),
EndContainer(),
@@ -2030,7 +2064,7 @@ public:
{
switch (widget) {
case WID_CL_SERVER_VISIBILITY:
*size = maxdim(GetStringBoundingBox(_server_visibility_dropdown[0]), GetStringBoundingBox(_server_visibility_dropdown[1]));
*size = maxdim(maxdim(GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_LOCAL), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_PUBLIC)), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY));
size->width += padding.width;
size->height += padding.height;
break;
@@ -2062,7 +2096,17 @@ public:
break;
case WID_CL_SERVER_VISIBILITY:
SetDParam(0, _server_visibility_dropdown[_settings_client.network.server_advertise]);
SetDParam(0, STR_NETWORK_SERVER_VISIBILITY_LOCAL + _settings_client.network.server_game_type);
break;
case WID_CL_SERVER_INVITE_CODE: {
static std::string empty = {};
SetDParamStr(0, _network_server_connection_type == CONNECTION_TYPE_UNKNOWN ? empty : _network_server_invite_code);
break;
}
case WID_CL_SERVER_CONNECTION_TYPE:
SetDParam(0, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN + _network_server_connection_type);
break;
case WID_CL_CLIENT_NAME:
@@ -2096,7 +2140,7 @@ public:
case WID_CL_SERVER_VISIBILITY:
if (!_network_server) break;
ShowDropDownMenu(this, _server_visibility_dropdown, _settings_client.network.server_advertise, WID_CL_SERVER_VISIBILITY, 0, 0);
ShowDropDownList(this, BuildVisibilityDropDownList(), _settings_client.network.server_game_type, WID_CL_SERVER_VISIBILITY);
break;
case WID_CL_MATRIX: {
@@ -2162,7 +2206,8 @@ public:
case WID_CL_SERVER_VISIBILITY:
if (!_network_server) break;
_settings_client.network.server_advertise = (index != 0);
_settings_client.network.server_game_type = (ServerGameType)index;
NetworkUpdateServerGameType();
break;
case WID_CL_MATRIX: {

View File

@@ -11,6 +11,7 @@
#define NETWORK_INTERNAL_H
#include "network_func.h"
#include "core/tcp_coordinator.h"
#include "core/tcp_game.h"
#include "../command_type.h"
@@ -89,6 +90,8 @@ extern NetworkJoinStatus _network_join_status;
extern uint8 _network_join_waiting;
extern uint32 _network_join_bytes;
extern uint32 _network_join_bytes_total;
extern ConnectionType _network_server_connection_type;
extern std::string _network_server_invite_code;
extern uint8 _network_reconnect;
@@ -98,7 +101,7 @@ void NetworkQueryServer(const std::string &connection_string);
void NetworkQueryLobbyServer(const std::string &connection_string);
void GetBindAddresses(NetworkAddressList *addresses, uint16 port);
struct NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually = true);
struct NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually = true, bool never_expire = false);
void NetworkRebuildHostList();
void UpdateNetworkGameWindow();
@@ -127,6 +130,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err);
bool NetworkMakeClientNameUnique(std::string &new_name);
std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32 password_game_seed);
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id);
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16 default_port);
std::string NormalizeConnectionString(const std::string &connection_string, uint16 default_port);

View File

@@ -360,7 +360,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendClientInfo(NetworkClientIn
/** Send the client information about the server. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendGameInfo()
{
Packet *p = new Packet(PACKET_SERVER_GAME_INFO, SHRT_MAX);
Packet *p = new Packet(PACKET_SERVER_GAME_INFO, TCP_MTU);
SerializeNetworkGameInfo(p, GetCurrentNetworkServerGameInfo());
this->SendPacket(p);
@@ -504,7 +504,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendDesyncLog(const std::strin
/** Send the check for the NewGRFs. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck()
{
Packet *p = new Packet(PACKET_SERVER_CHECK_NEWGRFS, SHRT_MAX);
Packet *p = new Packet(PACKET_SERVER_CHECK_NEWGRFS, TCP_MTU);
const GRFConfig *c;
uint grf_count = 0;
@@ -2058,9 +2058,6 @@ void NetworkServer_Tick(bool send_frame)
#endif
}
}
/* See if we need to advertise */
NetworkUDPAdvertise();
}
/** Yearly "callback". Called whenever the year changes. */

View File

@@ -0,0 +1,140 @@
/*
* 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_stun.cpp STUN sending/receiving part of the network protocol. */
#include "../stdafx.h"
#include "../debug.h"
#include "network.h"
#include "network_coordinator.h"
#include "network_stun.h"
#include "../safeguards.h"
/** Connect to the STUN server. */
class NetworkStunConnecter : public TCPConnecter {
private:
ClientNetworkStunSocketHandler *stun_handler;
std::string token;
uint8 family;
public:
/**
* Initiate the connecting.
* @param stun_handler The handler for this request.
* @param connection_string The address of the server.
*/
NetworkStunConnecter(ClientNetworkStunSocketHandler *stun_handler, const std::string &connection_string, const std::string &token, uint8 family) :
TCPConnecter(connection_string, NETWORK_STUN_SERVER_PORT, NetworkAddress(), family),
stun_handler(stun_handler),
token(token),
family(family)
{
}
void OnFailure() override
{
this->stun_handler->connecter = nullptr;
/* Connection to STUN server failed. For example, the client doesn't
* support IPv6, which means it will fail that attempt. */
_network_coordinator_client.StunResult(this->token, this->family, false);
}
void OnConnect(SOCKET s) override
{
this->stun_handler->connecter = nullptr;
assert(this->stun_handler->sock == INVALID_SOCKET);
this->stun_handler->sock = s;
/* Store the local address; later connects will reuse it again.
* This is what makes STUN work for most NATs. */
this->stun_handler->local_addr = NetworkAddress::GetSockAddress(s);
/* We leave the connection open till the real connection is setup later. */
}
};
/**
* Connect to the STUN server over either IPv4 or IPv6.
* @param token The token as received from the Game Coordinator.
* @param family What IP family to use.
*/
void ClientNetworkStunSocketHandler::Connect(const std::string &token, uint8 family)
{
this->token = token;
this->family = family;
this->connecter = new NetworkStunConnecter(this, NetworkStunConnectionString(), token, family);
}
/**
* Send a STUN packet to the STUN server.
* @param token The token as received from the Game Coordinator.
* @param family What IP family this STUN request is for.
* @return The handler for this STUN request.
*/
std::unique_ptr<ClientNetworkStunSocketHandler> ClientNetworkStunSocketHandler::Stun(const std::string &token, uint8 family)
{
auto stun_handler = std::make_unique<ClientNetworkStunSocketHandler>();
stun_handler->Connect(token, family);
Packet *p = new Packet(PACKET_STUN_SERCLI_STUN);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(token);
p->Send_uint8(family);
stun_handler->SendPacket(p);
return stun_handler;
}
NetworkRecvStatus ClientNetworkStunSocketHandler::CloseConnection(bool error)
{
NetworkStunSocketHandler::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 STUN server and
* when that's the case handle it appropriately.
*/
void ClientNetworkStunSocketHandler::SendReceive()
{
if (this->sock == INVALID_SOCKET) return;
/* We never attempt to receive anything on a STUN socket. After
* connecting a STUN connection, the local address will be reused to
* to establish the connection with the real server. If we would be to
* read this socket, some OSes get confused and deliver us packets meant
* for the real connection. It appears most OSes play best when we simply
* never attempt to read it to start with (and the packets will remain
* available on the other socket).
* Protocol-wise, the STUN server will never send any packet back anyway. */
this->CanSendReceive();
if (this->SendPackets() == SPS_ALL_SENT && !this->sent_result) {
/* We delay giving the GC the result this long, as to make sure we
* have sent the STUN packet first. This means the GC is more likely
* to have the result ready by the time our StunResult() packet
* arrives. */
this->sent_result = true;
_network_coordinator_client.StunResult(this->token, this->family, true);
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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_stun.h Part of the network protocol handling STUN requests. */
#ifndef NETWORK_STUN_H
#define NETWORK_STUN_H
#include "core/tcp_stun.h"
/** Class for handling the client side of the STUN connection. */
class ClientNetworkStunSocketHandler : public NetworkStunSocketHandler {
private:
std::string token; ///< Token of this STUN handler.
uint8 family = AF_UNSPEC; ///< Family of this STUN handler.
bool sent_result = false; ///< Did we sent the result of the STUN connection?
public:
TCPConnecter *connecter = nullptr; ///< Connecter instance.
NetworkAddress local_addr; ///< Local addresses of the socket.
NetworkRecvStatus CloseConnection(bool error = true) override;
void SendReceive();
void Connect(const std::string &token, uint8 family);
static std::unique_ptr<ClientNetworkStunSocketHandler> Stun(const std::string &token, uint8 family);
};
#endif /* NETWORK_STUN_H */

View File

@@ -10,8 +10,6 @@
#ifndef NETWORK_TYPE_H
#define NETWORK_TYPE_H
#include "core/config.h"
/** How many clients can we have */
static const uint MAX_CLIENTS = 255;
@@ -35,6 +33,16 @@ enum NetworkVehicleType {
NETWORK_VEH_END
};
/**
* Game type the server can be using.
* Used on the network protocol to communicate with Game Coordinator.
*/
enum ServerGameType : uint8 {
SERVER_GAME_TYPE_LOCAL = 0,
SERVER_GAME_TYPE_PUBLIC,
SERVER_GAME_TYPE_INVITE_ONLY,
};
/** 'Unique' identifier to be given to clients */
enum ClientID : uint32 {
INVALID_CLIENT_ID = 0, ///< Client is not part of anything

View File

@@ -8,7 +8,7 @@
/**
* @file network_udp.cpp This file handles the UDP related communication.
*
* This is the GameServer <-> MasterServer and GameServer <-> GameClient
* This is the GameServer <-> GameClient
* communication before the game is being joined.
*/
@@ -23,15 +23,10 @@
#include "network.h"
#include "../core/endian_func.hpp"
#include "../company_base.h"
#include "../thread.h"
#include "../rev.h"
#include "../newgrf_text.h"
#include "../strings_func.h"
#include "table/strings.h"
#include <mutex>
#if defined(__MINGW32__)
#include "../3rdparty/mingw-std-threads/mingw.mutex.h"
#endif
#include "core/udp.h"
@@ -39,54 +34,31 @@
#include "../safeguards.h"
extern const uint8 _out_of_band_grf_md5[16] { 0x00, 0xB0, 0xC0, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xC0, 0xDE, 0x00, 0x00, 0x00, 0x00 };
/** Session key to register ourselves to the master server */
static uint64 _session_key = 0;
static const std::chrono::minutes ADVERTISE_NORMAL_INTERVAL(15); ///< interval between advertising.
static const std::chrono::seconds ADVERTISE_RETRY_INTERVAL(10); ///< re-advertise when no response after this amount of time.
static const uint32 ADVERTISE_RETRY_TIMES = 3; ///< give up re-advertising after this much failed retries
static bool _network_udp_server; ///< Is the UDP server started?
static uint16 _network_udp_broadcast; ///< Timeout for the UDP broadcasts.
static uint8 _network_advertise_retries; ///< The number of advertisement retries we did.
/** Some information about a socket, which exists before the actual socket has been created to provide locking and the likes. */
struct UDPSocket {
const std::string name; ///< The name of the socket.
std::mutex mutex; ///< Mutex for everything that (indirectly) touches the sockets within the handler.
NetworkUDPSocketHandler *socket; ///< The actual socket, which may be nullptr when not initialized yet.
std::atomic<int> receive_iterations_locked; ///< The number of receive iterations the mutex was locked.
UDPSocket(const std::string &name_) : name(name_), socket(nullptr) {}
UDPSocket(const std::string &name) : name(name), socket(nullptr) {}
void CloseSocket()
{
std::lock_guard<std::mutex> lock(mutex);
socket->CloseSocket();
delete socket;
socket = nullptr;
this->socket->CloseSocket();
delete this->socket;
this->socket = nullptr;
}
void ReceivePackets()
{
std::unique_lock<std::mutex> lock(mutex, std::defer_lock);
if (!lock.try_lock()) {
if (++receive_iterations_locked % 32 == 0) {
DEBUG(net, 0, "%s background UDP loop processing appears to be blocked. Your OS may be low on UDP send buffers.", name.c_str());
}
return;
}
receive_iterations_locked.store(0);
socket->ReceivePackets();
this->socket->ReceivePackets();
}
};
static UDPSocket _udp_client("Client"); ///< udp client socket
static UDPSocket _udp_server("Server"); ///< udp server socket
static UDPSocket _udp_master("Master"); ///< udp master socket
static Packet PrepareUdpClientFindServerPacket()
{
@@ -97,79 +69,13 @@ static Packet PrepareUdpClientFindServerPacket()
return p;
}
/**
* Helper function doing the actual work for querying the server.
* @param connection_string The address of the server.
* @param needs_mutex Whether we need to acquire locks when sending the packet or not.
* @param manually Whether the address was entered manually.
*/
static void DoNetworkUDPQueryServer(const std::string &connection_string, bool needs_mutex, bool manually)
{
/* Clear item in gamelist */
NetworkGameList *item = new NetworkGameList(connection_string, manually);
item->info.server_name = connection_string;
NetworkGameListAddItemDelayed(item);
std::unique_lock<std::mutex> lock(_udp_client.mutex, std::defer_lock);
if (needs_mutex) lock.lock();
/* Init the packet */
NetworkAddress address = NetworkAddress(ParseConnectionString(connection_string, NETWORK_DEFAULT_PORT));
Packet p = PrepareUdpClientFindServerPacket();
if (_udp_client.socket != nullptr) _udp_client.socket->SendPacket(&p, &address);
}
/**
* Query a specific server.
* @param connection_string The address of the server.
* @param manually Whether the address was entered manually.
*/
void NetworkUDPQueryServer(const std::string &connection_string, bool manually)
{
if (!StartNewThread(nullptr, "ottd:udp-query", &DoNetworkUDPQueryServer, std::move(connection_string), true, std::move(manually))) {
DoNetworkUDPQueryServer(connection_string, true, manually);
}
}
///*** Communication with the masterserver ***/
/** Helper class for connecting to the master server. */
class MasterNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
protected:
void Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr) override;
void Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr) override;
public:
/**
* Create the socket.
* @param addresses The addresses to bind on.
*/
MasterNetworkUDPSocketHandler(NetworkAddressList *addresses) : NetworkUDPSocketHandler(addresses) {}
virtual ~MasterNetworkUDPSocketHandler() {}
};
void MasterNetworkUDPSocketHandler::Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr)
{
_network_advertise_retries = 0;
DEBUG(net, 3, "Advertising on master server successful (%s)", NetworkAddress::AddressFamilyAsString(client_addr->GetAddress()->ss_family));
/* We are advertised, but we don't want to! */
if (!_settings_client.network.server_advertise) NetworkUDPRemoveAdvertise(false);
}
void MasterNetworkUDPSocketHandler::Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr)
{
_session_key = p->Recv_uint64();
DEBUG(net, 6, "Received new session key from master server (%s)", NetworkAddress::AddressFamilyAsString(client_addr->GetAddress()->ss_family));
}
///*** Communication with clients (we are server) ***/
/** Helper class for handling all server side communication. */
class ServerNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
protected:
void Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr) override;
void Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) override;
void Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr) override;
void Reply_CLIENT_FIND_SERVER_extended(Packet *p, NetworkAddress *client_addr, const NetworkServerGameInfo *ngi);
void Reply_CLIENT_FIND_SERVER_extended(Packet *p, NetworkAddress *client_addr);
public:
/**
* Create the socket.
@@ -181,375 +87,64 @@ public:
void ServerNetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr)
{
/* Just a fail-safe.. should never happen */
if (!_network_udp_server) {
return;
}
if (p->CanReadFromPacket(8) && p->Recv_uint32() == FIND_SERVER_EXTENDED_TOKEN) {
this->Reply_CLIENT_FIND_SERVER_extended(p, client_addr, GetCurrentNetworkServerGameInfo());
this->Reply_CLIENT_FIND_SERVER_extended(p, client_addr);
return;
}
Packet packet(PACKET_UDP_SERVER_RESPONSE);
SerializeNetworkGameInfo(&packet, GetCurrentNetworkServerGameInfo());
/* Let the client know that we are here */
this->SendPacket(&packet, client_addr);
DEBUG(net, 7, "Queried from %s", client_addr->GetHostname());
}
void ServerNetworkUDPSocketHandler::Reply_CLIENT_FIND_SERVER_extended(Packet *p, NetworkAddress *client_addr, const NetworkServerGameInfo *ngi)
void ServerNetworkUDPSocketHandler::Reply_CLIENT_FIND_SERVER_extended(Packet *p, NetworkAddress *client_addr)
{
uint16 flags = p->Recv_uint16();
[[maybe_unused]] uint16 flags = p->Recv_uint16();
uint16 version = p->Recv_uint16();
Packet packet(PACKET_UDP_EX_SERVER_RESPONSE, SHRT_MAX);
SerializeNetworkGameInfoExtended(&packet, ngi, flags, version);
/* Let the client know that we are here */
this->SendPacket(&packet, client_addr, false, false, true);
DEBUG(net, 2, "[udp] queried (extended: %u) from %s", version, client_addr->GetHostname());
}
void ServerNetworkUDPSocketHandler::Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr)
{
/* Just a fail-safe.. should never happen */
if (!_network_udp_server) return;
Packet packet(PACKET_UDP_SERVER_DETAIL_INFO);
/* Send the amount of active companies */
packet.Send_uint8 (NETWORK_COMPANY_INFO_VERSION);
packet.Send_uint8 ((uint8)Company::GetNumItems());
/* Fetch the latest version of the stats */
NetworkCompanyStats company_stats[MAX_COMPANIES];
NetworkPopulateCompanyStats(company_stats);
/* The minimum company information "blob" size. */
static const uint MIN_CI_SIZE = 54;
uint max_cname_length = NETWORK_COMPANY_NAME_LENGTH;
if (!packet.CanWriteToPacket(Company::GetNumItems() * (MIN_CI_SIZE + NETWORK_COMPANY_NAME_LENGTH))) {
/* Assume we can at least put the company information in the packets. */
assert(packet.CanWriteToPacket(Company::GetNumItems() * MIN_CI_SIZE));
/* At this moment the company names might not fit in the
* packet. Check whether that is really the case. */
for (;;) {
size_t required = 0;
for (const Company *company : Company::Iterate()) {
char company_name[NETWORK_COMPANY_NAME_LENGTH];
SetDParam(0, company->index);
GetString(company_name, STR_COMPANY_NAME, company_name + max_cname_length - 1);
required += MIN_CI_SIZE;
required += strlen(company_name);
}
if (packet.CanWriteToPacket(required)) break;
/* Try again, with slightly shorter strings. */
assert(max_cname_length > 0);
max_cname_length--;
}
}
/* Go through all the companies */
for (const Company *company : Company::Iterate()) {
/* Send the information */
this->SendCompanyInformation(&packet, company, &company_stats[company->index], max_cname_length);
}
Packet packet(PACKET_UDP_EX_SERVER_RESPONSE);
this->SendPacket(&packet, client_addr);
}
/**
* A client has requested the names of some NewGRFs.
*
* Replying this can be tricky as we have a limit of UDP_MTU bytes
* in the reply packet and we can send up to 100 bytes per NewGRF
* (GRF ID, MD5sum and NETWORK_GRF_NAME_LENGTH bytes for the name).
* As UDP_MTU is _much_ less than 100 * NETWORK_MAX_GRF_COUNT, it
* could be that a packet overflows. To stop this we only reply
* with the first N NewGRFs so that if the first N + 1 NewGRFs
* would be sent, the packet overflows.
* in_reply and in_reply_count are used to keep a list of GRFs to
* send in the reply.
*/
void ServerNetworkUDPSocketHandler::Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr)
{
uint8 num_grfs;
uint i;
struct GRFInfo {
GRFIdentifier ident;
const char *name = nullptr;
};
std::vector<GRFInfo> in_reply;
DEBUG(net, 7, "NewGRF data request from %s", client_addr->GetAddressAsString().c_str());
size_t packet_len = 0;
num_grfs = p->Recv_uint8 ();
DEBUG(net, 6, "[udp] newgrf data request (%u) from %s", num_grfs, NetworkAddressDumper().GetAddressAsString(client_addr));
if (num_grfs > NETWORK_MAX_GRF_COUNT) return;
auto flush_response = [&]() {
if (in_reply.empty()) return;
Packet packet(PACKET_UDP_SERVER_NEWGRFS);
packet.Send_uint8(static_cast<uint8>(in_reply.size()));
for (const GRFInfo &info : in_reply) {
char name[NETWORK_GRF_NAME_LENGTH];
/* The name could be an empty string, if so take the filename */
strecpy(name, info.name, lastof(name));
SerializeGRFIdentifier(&packet, &info.ident);
packet.Send_string(name);
}
this->SendPacket(&packet, client_addr);
DEBUG(net, 6, "[udp] sent newgrf data response (%u of %u) to %s", (uint) in_reply.size(), num_grfs, NetworkAddressDumper().GetAddressAsString(client_addr));
packet_len = 0;
in_reply.clear();
};
for (i = 0; i < num_grfs; i++) {
GRFInfo info;
DeserializeGRFIdentifier(p, &info.ident);
if (memcmp(info.ident.md5sum, _out_of_band_grf_md5, 16) == 0) {
if (info.ident.grfid == 0x56D2B000) {
info.name = "=*=*= More than 62 GRFs in total =*=*=";
} else {
continue;
}
} else {
const GRFConfig *f;
/* Find the matching GRF file */
f = FindGRFConfig(info.ident.grfid, FGCM_EXACT, info.ident.md5sum);
if (f == nullptr) continue; // The GRF is unknown to this server
info.ident = f->ident;
info.name = f->GetName();
}
/* If the reply might exceed the size of the packet, only reply
* the current list and do not send the other data.
* The name could be an empty string, if so take the filename. */
size_t required_length = sizeof(info.ident.grfid) + sizeof(info.ident.md5sum) +
std::min(strlen(info.name) + 1, (size_t)NETWORK_GRF_NAME_LENGTH);
if (packet_len + required_length > UDP_MTU_SHORT - 4) { // 4 is 3 byte header + grf count in reply
flush_response();
}
packet_len += required_length;
in_reply.push_back(info);
}
flush_response();
DEBUG(net, 7, "Queried (extended: %u) from %s", version, client_addr->GetHostname());
}
///*** Communication with servers (we are client) ***/
/** Helper class for handling all client side communication. */
class ClientNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
private:
void Receive_SERVER_RESPONSE_Common(Packet *p, NetworkAddress *client_addr, bool extended);
protected:
void Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override;
void Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override;
void Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr) override;
void Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr) override;
public:
virtual ~ClientNetworkUDPSocketHandler() {}
};
void ClientNetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr)
{
this->Receive_SERVER_RESPONSE_Common(p, client_addr, false);
DEBUG(net, 3, "Server response from %s", NetworkAddressDumper().GetAddressAsString(client_addr));
NetworkAddServer(client_addr->GetAddressAsString(false), false, true);
}
void ClientNetworkUDPSocketHandler::Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr)
{
this->Receive_SERVER_RESPONSE_Common(p, client_addr, true);
}
DEBUG(net, 3, "Extended server response from %s", NetworkAddressDumper().GetAddressAsString(client_addr));
void ClientNetworkUDPSocketHandler::Receive_SERVER_RESPONSE_Common(Packet *p, NetworkAddress *client_addr, bool extended)
{
NetworkGameList *item;
/* Just a fail-safe.. should never happen */
if (_network_udp_server) return;
DEBUG(net, 3, "%s server response from %s", extended ? " extended" : "", NetworkAddressDumper().GetAddressAsString(client_addr));
/* Find next item */
item = NetworkGameListAddItem(client_addr->GetAddressAsString(false));
/* Clear any existing GRFConfig chain. */
ClearGRFConfigList(&item->info.grfconfig);
if (extended) {
/* Retrieve the NetworkGameInfo from the packet. */
DeserializeNetworkGameInfoExtended(p, &item->info);
} else {
/* Retrieve the NetworkGameInfo from the packet. */
DeserializeNetworkGameInfo(p, &item->info);
}
/* Check for compatability with the client. */
CheckGameCompatibility(item->info, extended);
/* Ensure we consider the server online. */
item->online = true;
{
/* Checks whether there needs to be a request for names of GRFs and makes
* the request if necessary. GRFs that need to be requested are the GRFs
* that do not exist on the clients system and we do not have the name
* resolved of, i.e. the name is still UNKNOWN_GRF_NAME_PLACEHOLDER.
* The in_request array and in_request_count are used so there is no need
* to do a second loop over the GRF list, which can be relatively expensive
* due to the string comparisons. */
const GRFConfig *in_request[NETWORK_MAX_GRF_COUNT];
const GRFConfig *c;
uint in_request_count = 0;
auto flush_request = [&]() {
/* There are 'unknown' GRFs, now send a request for them */
uint i;
Packet packet(PACKET_UDP_CLIENT_GET_NEWGRFS);
packet.Send_uint8(in_request_count);
for (i = 0; i < in_request_count; i++) {
SerializeGRFIdentifier(&packet, &in_request[i]->ident);
}
NetworkAddress address = NetworkAddress(ParseConnectionString(item->connection_string, NETWORK_DEFAULT_PORT));
this->SendPacket(&packet, &address);
DEBUG(net, 4, "[udp] sent newgrf data request (%u) to %s", in_request_count, NetworkAddressDumper().GetAddressAsString(client_addr));
in_request_count = 0;
};
for (c = item->info.grfconfig; c != nullptr; c = c->next) {
if (c->status == GCS_NOT_FOUND) item->info.compatible = false;
if (c->status != GCS_NOT_FOUND || strcmp(c->GetName(), UNKNOWN_GRF_NAME_PLACEHOLDER) != 0) continue;
in_request[in_request_count] = c;
in_request_count++;
if (in_request_count == NETWORK_MAX_GRF_COUNT_SHORT) flush_request();
}
if (in_request_count > 0) flush_request();
}
if (client_addr->GetAddress()->ss_family == AF_INET6) {
item->info.server_name.append(" (IPv6)");
}
UpdateNetworkGameWindow();
}
void ClientNetworkUDPSocketHandler::Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr)
{
/* packet begins with the protocol version (uint8)
* then an uint16 which indicates how many
* ip:port pairs are in this packet, after that
* an uint32 (ip) and an uint16 (port) for each pair.
*/
ServerListType type = (ServerListType)(p->Recv_uint8() - 1);
if (type < SLT_END) {
for (int i = p->Recv_uint16(); i != 0 ; i--) {
sockaddr_storage addr_storage;
memset(&addr_storage, 0, sizeof(addr_storage));
if (type == SLT_IPv4) {
addr_storage.ss_family = AF_INET;
((sockaddr_in*)&addr_storage)->sin_addr.s_addr = TO_LE32(p->Recv_uint32());
} else {
assert(type == SLT_IPv6);
addr_storage.ss_family = AF_INET6;
byte *addr = (byte*)&((sockaddr_in6*)&addr_storage)->sin6_addr;
for (uint i = 0; i < sizeof(in6_addr); i++) *addr++ = p->Recv_uint8();
}
NetworkAddress addr(addr_storage, type == SLT_IPv4 ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
addr.SetPort(p->Recv_uint16());
/* Somehow we reached the end of the packet */
if (this->HasClientQuit()) return;
DoNetworkUDPQueryServer(addr.GetAddressAsString(false), false, false);
}
}
}
/** The return of the client's request of the names of some NewGRFs */
void ClientNetworkUDPSocketHandler::Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr)
{
uint8 num_grfs;
uint i;
num_grfs = p->Recv_uint8 ();
DEBUG(net, 7, "NewGRF data reply (%u) from %s", num_grfs, NetworkAddressDumper().GetAddressAsString(client_addr));
if (num_grfs > NETWORK_MAX_GRF_COUNT) return;
for (i = 0; i < num_grfs; i++) {
GRFIdentifier c;
DeserializeGRFIdentifier(p, &c);
std::string name = p->Recv_string(NETWORK_GRF_NAME_LENGTH);
/* An empty name is not possible under normal circumstances
* and causes problems when showing the NewGRF list. */
if (name.empty()) continue;
/* Try to find the GRFTextWrapper for the name of this GRF ID and MD5sum tuple.
* If it exists and not resolved yet, then name of the fake GRF is
* overwritten with the name from the reply. */
GRFTextWrapper unknown_name = FindUnknownGRFName(c.grfid, c.md5sum, false);
if (unknown_name && strcmp(GetGRFStringFromGRFText(unknown_name), UNKNOWN_GRF_NAME_PLACEHOLDER) == 0) {
AddGRFTextToList(unknown_name, name);
}
}
NetworkAddServer(client_addr->GetAddressAsString(false), false, true); // TODO, mark as extended
}
/** Broadcast to all ips */
static void NetworkUDPBroadCast(NetworkUDPSocketHandler *socket)
{
for (NetworkAddress &addr : _broadcast_list) {
Packet p = PrepareUdpClientFindServerPacket();
DEBUG(net, 5, "Broadcasting to %s", addr.GetHostname());
Packet p = PrepareUdpClientFindServerPacket();
socket->SendPacket(&p, &addr, true, true);
}
}
/** Request the the server-list from the master server */
void NetworkUDPQueryMasterServer()
{
Packet p(PACKET_UDP_CLIENT_GET_LIST);
NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT);
/* packet only contains protocol version */
p.Send_uint8(NETWORK_MASTER_SERVER_VERSION);
p.Send_uint8(SLT_AUTODETECT);
std::lock_guard<std::mutex> lock(_udp_client.mutex);
_udp_client.socket->SendPacket(&p, &out_addr, true);
DEBUG(net, 6, "Master server queried at %s", NetworkAddressDumper().GetAddressAsString(&out_addr));
}
/** Find all servers */
void NetworkUDPSearchGame()
{
@@ -562,113 +157,6 @@ void NetworkUDPSearchGame()
_network_udp_broadcast = 300; // Stay searching for 300 ticks
}
/**
* Thread entry point for de-advertising.
*/
static void NetworkUDPRemoveAdvertiseThread()
{
DEBUG(net, 3, "Removing advertise from master server");
/* Find somewhere to send */
NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT);
/* Send the packet */
Packet p(PACKET_UDP_SERVER_UNREGISTER);
/* Packet is: Version, server_port */
p.Send_uint8 (NETWORK_MASTER_SERVER_VERSION);
p.Send_uint16(_settings_client.network.server_port);
std::lock_guard<std::mutex> lock(_udp_master.mutex);
if (_udp_master.socket != nullptr) _udp_master.socket->SendPacket(&p, &out_addr, true);
}
/**
* Remove our advertise from the master-server.
* @param blocking whether to wait until the removal has finished.
*/
void NetworkUDPRemoveAdvertise(bool blocking)
{
/* Check if we are advertising */
if (!_networking || !_network_server || !_network_udp_server) return;
if (blocking || !StartNewThread(nullptr, "ottd:udp-advert", &NetworkUDPRemoveAdvertiseThread)) {
NetworkUDPRemoveAdvertiseThread();
}
}
/**
* Thread entry point for advertising.
*/
static void NetworkUDPAdvertiseThread()
{
/* Find somewhere to send */
NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT);
DEBUG(net, 3, "Advertising to master server");
/* Add a bit more messaging when we cannot get a session key */
static byte session_key_retries = 0;
if (_session_key == 0 && session_key_retries++ == 2) {
DEBUG(net, 0, "Advertising to the master server is failing");
DEBUG(net, 0, " we are not receiving the session key from the server");
DEBUG(net, 0, " please allow udp packets from %s to you to be delivered", NetworkAddressDumper().GetAddressAsString(&out_addr, false));
DEBUG(net, 0, " please allow udp packets from you to %s to be delivered", NetworkAddressDumper().GetAddressAsString(&out_addr, false));
}
if (_session_key != 0 && _network_advertise_retries == 0) {
DEBUG(net, 0, "Advertising to the master server is failing");
DEBUG(net, 0, " we are not receiving the acknowledgement from the server");
DEBUG(net, 0, " this usually means that the master server cannot reach us");
DEBUG(net, 0, " please allow udp and tcp packets to port %u to be delivered", _settings_client.network.server_port);
DEBUG(net, 0, " please allow udp and tcp packets from port %u to be delivered", _settings_client.network.server_port);
}
/* Send the packet */
Packet p(PACKET_UDP_SERVER_REGISTER);
/* Packet is: WELCOME_MESSAGE, Version, server_port */
p.Send_string(NETWORK_MASTER_SERVER_WELCOME_MESSAGE);
p.Send_uint8 (NETWORK_MASTER_SERVER_VERSION);
p.Send_uint16(_settings_client.network.server_port);
p.Send_uint64(_session_key);
std::lock_guard<std::mutex> lock(_udp_master.mutex);
if (_udp_master.socket != nullptr) _udp_master.socket->SendPacket(&p, &out_addr, true);
}
/**
* Register us to the master server
* This function checks if it needs to send an advertise
*/
void NetworkUDPAdvertise()
{
static std::chrono::steady_clock::time_point _last_advertisement = {}; ///< The last time we performed an advertisement.
/* Check if we should send an advertise */
if (!_networking || !_network_server || !_network_udp_server || !_settings_client.network.server_advertise) return;
if (_network_need_advertise) {
/* Forced advertisement. */
_network_need_advertise = false;
_network_advertise_retries = ADVERTISE_RETRY_TIMES;
} else {
/* Only send once every ADVERTISE_NORMAL_INTERVAL ticks */
if (_network_advertise_retries == 0) {
if (std::chrono::steady_clock::now() <= _last_advertisement + ADVERTISE_NORMAL_INTERVAL) return;
_network_advertise_retries = ADVERTISE_RETRY_TIMES;
} else {
/* An actual retry. */
if (std::chrono::steady_clock::now() <= _last_advertisement + ADVERTISE_RETRY_INTERVAL) return;
}
}
_network_advertise_retries--;
_last_advertisement = std::chrono::steady_clock::now();
if (!StartNewThread(nullptr, "ottd:udp-advert", &NetworkUDPAdvertiseThread)) {
NetworkUDPAdvertiseThread();
}
}
/** Initialize the whole UDP bit. */
void NetworkUDPInitialize()
{
@@ -676,15 +164,7 @@ void NetworkUDPInitialize()
if (_udp_server.socket != nullptr) NetworkUDPClose();
DEBUG(net, 3, "Initializing UDP listeners");
assert(_udp_client.socket == nullptr && _udp_server.socket == nullptr && _udp_master.socket == nullptr);
// std::scoped_lock lock(_udp_client.mutex, _udp_server.mutex, _udp_master.mutex);
/* Avoid std::scoped_lock for MacOS 10.12 compatibility */
std::unique_lock<std::mutex> lock1(_udp_client.mutex, std::defer_lock);
std::unique_lock<std::mutex> lock2(_udp_server.mutex, std::defer_lock);
std::unique_lock<std::mutex> lock3(_udp_master.mutex, std::defer_lock);
std::lock(lock1, lock2, lock3);
assert(_udp_client.socket == nullptr && _udp_server.socket == nullptr);
_udp_client.socket = new ClientNetworkUDPSocketHandler();
@@ -692,19 +172,13 @@ void NetworkUDPInitialize()
GetBindAddresses(&server, _settings_client.network.server_port);
_udp_server.socket = new ServerNetworkUDPSocketHandler(&server);
server.clear();
GetBindAddresses(&server, 0);
_udp_master.socket = new MasterNetworkUDPSocketHandler(&server);
_network_udp_server = false;
_network_udp_broadcast = 0;
_network_advertise_retries = 0;
}
/** Start the listening of the UDP server component. */
void NetworkUDPServerListen()
{
std::lock_guard<std::mutex> lock(_udp_server.mutex);
_network_udp_server = _udp_server.socket->Listen();
}
@@ -713,7 +187,6 @@ void NetworkUDPClose()
{
_udp_client.CloseSocket();
_udp_server.CloseSocket();
_udp_master.CloseSocket();
_network_udp_server = false;
_network_udp_broadcast = 0;
@@ -725,7 +198,6 @@ void NetworkBackgroundUDPLoop()
{
if (_network_udp_server) {
_udp_server.ReceivePackets();
_udp_master.ReceivePackets();
} else {
_udp_client.ReceivePackets();
if (_network_udp_broadcast > 0) _network_udp_broadcast--;

View File

@@ -14,10 +14,6 @@
void NetworkUDPInitialize();
void NetworkUDPSearchGame();
void NetworkUDPQueryMasterServer();
void NetworkUDPQueryServer(const std::string &connection_string, bool manually = false);
void NetworkUDPAdvertise();
void NetworkUDPRemoveAdvertise(bool blocking);
void NetworkUDPClose();
void NetworkUDPServerListen();
void NetworkBackgroundUDPLoop();