Changeset - r25827:5a9ded1a0c1a
[Not reviewed]
master
0 16 4
Patric Stout - 3 years ago 2021-04-27 09:51:00
truebrain@openttd.org
Feature: allow the use of STUN to connect client and server together

This method doesn't require port-forwarding to be used, and works for
most common NAT routers in home setups. But, for sure it doesn't work
for all setups, and not everyone will be able to use this.
20 files changed with 617 insertions and 70 deletions:
0 comments (0 inline, 0 general)
src/lang/english.txt
Show inline comments
 
@@ -2145,48 +2145,49 @@ STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CO
 
STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE                  :{BLACK}Connection type
 
STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP          :{BLACK}Whether and how your server can be reached by others
 
STR_NETWORK_CLIENT_LIST_PLAYER                                  :{BLACK}Player
 
STR_NETWORK_CLIENT_LIST_PLAYER_NAME                             :{BLACK}Name
 
STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP                     :{BLACK}Your player name
 
STR_NETWORK_CLIENT_LIST_PLAYER_NAME_EDIT_TOOLTIP                :{BLACK}Edit your player name
 
STR_NETWORK_CLIENT_LIST_PLAYER_NAME_QUERY_CAPTION               :Your player name
 
STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_TOOLTIP                    :{BLACK}Administrative actions to perform for this client
 
STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_TOOLTIP                   :{BLACK}Administrative actions to perform for this company
 
STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP                            :{BLACK}Join this company
 
STR_NETWORK_CLIENT_LIST_CHAT_CLIENT_TOOLTIP                     :{BLACK}Send a message to this player
 
STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP                    :{BLACK}Send a message to all players of this company
 
STR_NETWORK_CLIENT_LIST_CHAT_SPECTATOR_TOOLTIP                  :{BLACK}Send a message to all spectators
 
STR_NETWORK_CLIENT_LIST_SPECTATORS                              :Spectators
 
STR_NETWORK_CLIENT_LIST_NEW_COMPANY                             :(New company)
 
STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP                     :{BLACK}Create a new company and join it
 
STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP                :{BLACK}This is you
 
STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP                :{BLACK}This is the host of the game
 
STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT                    :{BLACK}{NUM} client{P "" s} / {NUM} compan{P y ies}
 

	
 
############ Begin of ConnectionType
 
STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN          :{BLACK}Local
 
STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED         :{RED}Remote players can't connect
 
STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT           :{BLACK}Public
 
STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_STUN             :{BLACK}Behind NAT
 
############ End of ConnectionType
 

	
 
STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK                       :Kick
 
STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN                        :Ban
 
STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET                     :Delete
 
STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_UNLOCK                    :Password unlock
 

	
 
STR_NETWORK_CLIENT_LIST_ASK_CAPTION                             :{WHITE}Admin action
 
STR_NETWORK_CLIENT_LIST_ASK_CLIENT_KICK                         :{YELLOW}Are you sure you want to kick player '{RAW_STRING}'?
 
STR_NETWORK_CLIENT_LIST_ASK_CLIENT_BAN                          :{YELLOW}Are you sure you want to ban player '{RAW_STRING}'?
 
STR_NETWORK_CLIENT_LIST_ASK_COMPANY_RESET                       :{YELLOW}Are you sure you want to delete company '{COMPANY}'?
 
STR_NETWORK_CLIENT_LIST_ASK_COMPANY_UNLOCK                      :{YELLOW}Are you sure you want to reset the password of company '{COMPANY}'?
 

	
 
STR_NETWORK_SERVER                                              :Server
 
STR_NETWORK_CLIENT                                              :Client
 
STR_NETWORK_SPECTATORS                                          :Spectators
 

	
 
# Network set password
 
STR_COMPANY_PASSWORD_CANCEL                                     :{BLACK}Do not save the entered password
 
STR_COMPANY_PASSWORD_OK                                         :{BLACK}Give the company the new password
 
STR_COMPANY_PASSWORD_CAPTION                                    :{WHITE}Company password
 
STR_COMPANY_PASSWORD_MAKE_DEFAULT                               :{BLACK}Default company password
 
STR_COMPANY_PASSWORD_MAKE_DEFAULT_TOOLTIP                       :{BLACK}Use this company password as default for new companies
 

	
src/network/CMakeLists.txt
Show inline comments
 
@@ -3,28 +3,30 @@ add_subdirectory(core)
 
add_files(
 
    network.cpp
 
    network.h
 
    network_admin.cpp
 
    network_admin.h
 
    network_base.h
 
    network_chat_gui.cpp
 
    network_client.cpp
 
    network_client.h
 
    network_command.cpp
 
    network_content.cpp
 
    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
 
    network_gui.cpp
 
    network_gui.h
 
    network_internal.h
 
    network_server.cpp
 
    network_server.h
 
    network_stun.cpp
 
    network_stun.h
 
    network_type.h
 
    network_udp.cpp
 
    network_udp.h
 
)
src/network/core/CMakeLists.txt
Show inline comments
 
@@ -7,27 +7,29 @@ add_files(
 
    core.h
 
    game_info.cpp
 
    game_info.h
 
    host.cpp
 
    host.h
 
    os_abstraction.cpp
 
    os_abstraction.h
 
    packet.cpp
 
    packet.h
 
    tcp.cpp
 
    tcp.h
 
    tcp_admin.cpp
 
    tcp_admin.h
 
    tcp_connect.cpp
 
    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
 
)
src/network/core/address.cpp
Show inline comments
 
@@ -292,55 +292,54 @@ SOCKET NetworkAddress::Resolve(int famil
 
	return sock;
 
}
 

	
 
/**
 
 * Helper function to resolve a listening.
 
 * @param runp information about the socket to try not
 
 * @return the opened socket or INVALID_SOCKET
 
 */
 
static SOCKET ListenLoopProc(addrinfo *runp)
 
{
 
	std::string address = NetworkAddress(runp->ai_addr, (int)runp->ai_addrlen).GetAddressAsString();
 

	
 
	SOCKET sock = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol);
 
	if (sock == INVALID_SOCKET) {
 
		const char *type = NetworkAddress::SocketTypeAsString(runp->ai_socktype);
 
		const char *family = NetworkAddress::AddressFamilyAsString(runp->ai_family);
 
		Debug(net, 0, "Could not create {} {} socket: {}", type, family, NetworkError::GetLast().AsString());
 
		return INVALID_SOCKET;
 
	}
 

	
 
	if (runp->ai_socktype == SOCK_STREAM && !SetNoDelay(sock)) {
 
		Debug(net, 1, "Setting no-delay mode failed: {}", 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: {}", 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: {}", NetworkError::GetLast().AsString());
 
	}
 
#endif
 

	
 
	if (bind(sock, runp->ai_addr, (int)runp->ai_addrlen) != 0) {
 
		Debug(net, 0, "Could not bind socket on {}: {}", address, NetworkError::GetLast().AsString());
 
		closesocket(sock);
 
		return INVALID_SOCKET;
 
	}
 

	
 
	if (runp->ai_socktype != SOCK_DGRAM && listen(sock, 1) != 0) {
 
		Debug(net, 0, "Could not listen on socket: {}", NetworkError::GetLast().AsString());
 
		closesocket(sock);
 
		return INVALID_SOCKET;
 
	}
 

	
 
	/* Connection succeeded */
 

	
 
	if (!SetNonBlocking(sock)) {
 
		Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
 
	}
 

	
 
@@ -380,57 +379,86 @@ void NetworkAddress::Listen(int socktype
 
	switch (socktype) {
 
		case SOCK_STREAM: return "tcp";
 
		case SOCK_DGRAM:  return "udp";
 
		default:          return "unsupported";
 
	}
 
}
 

	
 
/**
 
 * Convert the address family into a string
 
 * @param family the family to convert
 
 * @return the string representation
 
 * @note only works for AF_INET, AF_INET6 and AF_UNSPEC
 
 */
 
/* static */ const char *NetworkAddress::AddressFamilyAsString(int family)
 
{
 
	switch (family) {
 
		case AF_UNSPEC: return "either IPv4 or IPv6";
 
		case AF_INET:   return "IPv4";
 
		case AF_INET6:  return "IPv6";
 
		default:        return "unsupported";
 
	}
 
}
 

	
 
/**
 
 * 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: {}", 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: {}", 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.
 
 * @return The string representation of the peer name.
 
 */
 
/* 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));
 
}
src/network/core/address.h
Show inline comments
 
@@ -154,48 +154,50 @@ public:
 
	}
 
	/**
 
	 * Compare the address of this class with the address of another.
 
	 * @param address the other address.
 
	 * @return true if both do not match.
 
	 */
 
	bool operator != (NetworkAddress address) const
 
	{
 
		return const_cast<NetworkAddress*>(this)->CompareTo(address) != 0;
 
	}
 

	
 
	/**
 
	 * Compare the address of this class with the address of another.
 
	 * @param address the other address.
 
	 */
 
	bool operator < (NetworkAddress &address)
 
	{
 
		return this->CompareTo(address) < 0;
 
	}
 

	
 
	void Listen(int socktype, SocketList *sockets);
 

	
 
	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);
 
};
 

	
 
/**
 
 * 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.
src/network/core/config.cpp
Show inline comments
 
@@ -18,42 +18,52 @@
 

	
 
/**
 
 * 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 game coordinator's connection string.
 
 * @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 game coordinator's connection string.
 
 * @return The content mirror's connection string.
 
 */
 
const char *NetworkContentMirrorConnectionString()
 
{
 
	return GetEnv("OTTD_CONTENT_MIRROR_CS", "binaries.openttd.org");
 
}
src/network/core/config.h
Show inline comments
 
/*
 
 * 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.h Configuration options of the network stuff. It is used even when compiling without network support.
 
 */
 

	
 
#ifndef NETWORK_CORE_CONFIG_H
 
#define NETWORK_CORE_CONFIG_H
 

	
 
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";
 

	
 
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
 
/*
 
 * 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.
 
 *
 
 * Packets up to 32 KiB have the high bit not set:
 
 * 00000000 00000000 0bbbbbbb aaaaaaaa -> aaaaaaaa 0bbbbbbb
 
 * Send_uint16(GB(size, 0, 15)
 
 *
 
 * Packets up to 1 GiB, first uint16 has high bit set so it knows to read a
 
 * next uint16 for the remaining bits of the size.
 
 * 00dddddd cccccccc bbbbbbbb aaaaaaaa -> cccccccc 10dddddd aaaaaaaa bbbbbbbb
 
 * 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 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       =    2;           ///< What version of game-coordinator-protocol do we use?
 
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_PASSWORD_LENGTH           =   33;           ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH)
 
static const uint NETWORK_CLIENTS_LENGTH            =  200;           ///< The maximum length for the list of clients that controls a company, in bytes including '\0'
 
static const uint NETWORK_CLIENT_NAME_LENGTH        =   25;           ///< The maximum length of a client's name, in bytes including '\0'
 
static const uint NETWORK_RCONCOMMAND_LENGTH        =  500;           ///< The maximum length of a rconsole command, in bytes including '\0'
 
static const uint NETWORK_GAMESCRIPT_JSON_LENGTH    = COMPAT_MTU - 3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes)
 
static const uint NETWORK_CHAT_LENGTH               =  900;           ///< The maximum length of a chat message, in bytes including '\0'
 
static const uint NETWORK_CONTENT_FILENAME_LENGTH   =   48;           ///< The maximum length of a content's filename, in bytes including '\0'.
 
static const uint NETWORK_CONTENT_NAME_LENGTH       =   32;           ///< The maximum length of a content's name, in bytes including '\0'.
 
static const uint NETWORK_CONTENT_VERSION_LENGTH    =   16;           ///< The maximum length of a content's version, in bytes including '\0'.
 
static const uint NETWORK_CONTENT_URL_LENGTH        =   96;           ///< The maximum length of a content's url, in bytes including '\0'.
 
static const uint NETWORK_CONTENT_DESC_LENGTH       =  512;           ///< The maximum length of a content's description, in bytes including '\0'.
 
static const uint NETWORK_CONTENT_TAG_LENGTH        =   32;           ///< The maximum length of a content's tag, in bytes including '\0'.
 
static const uint NETWORK_ERROR_DETAIL_LENGTH       =  100;           ///< The maximum length of the error detail, in bytes including '\0'.
 
static const uint NETWORK_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'.
 

	
src/network/core/os_abstraction.cpp
Show inline comments
 
@@ -139,36 +139,53 @@ bool SetNonBlocking(SOCKET d)
 
	return true;
 
#else
 
	int nonblocking = 1;
 
	return ioctl(d, FIONBIO, &nonblocking) == 0;
 
#endif
 
}
 

	
 
/**
 
 * Try to set the socket to not delay sending.
 
 * @param d The socket to disable the delaying for.
 
 * @return True if disabling the delaying succeeded, otherwise false.
 
 */
 
bool SetNoDelay(SOCKET d)
 
{
 
#ifdef __EMSCRIPTEN__
 
	return true;
 
#else
 
	int flags = 1;
 
	/* The (const char*) cast is needed for windows */
 
	return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags)) == 0;
 
#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
 
}
 

	
 
/**
 
 * Get the error from a socket, if any.
 
 * @param d The socket to get the error from.
 
 * @return The errno on the socket.
 
 */
 
NetworkError GetSocketError(SOCKET d)
 
{
 
	int err;
 
	socklen_t len = sizeof(err);
 
	getsockopt(d, SOL_SOCKET, SO_ERROR, (char *)&err, &len);
 

	
 
	return NetworkError(err);
 
}
src/network/core/os_abstraction.h
Show inline comments
 
@@ -175,31 +175,32 @@ typedef unsigned long in_addr_t;
 
/**
 
 * Emscripten doesn't set 'addrlen' for accept(), getsockname(), getpeername()
 
 * and recvfrom(), which confuses other functions and causes them to crash.
 
 * This function needs to be called after these four functions to make sure
 
 * 'addrlen' is patched up.
 
 *
 
 * https://github.com/emscripten-core/emscripten/issues/12996
 
 *
 
 * @param address The address returned by those four functions.
 
 * @return The correct value for addrlen.
 
 */
 
static inline socklen_t FixAddrLenForEmscripten(struct sockaddr_storage &address)
 
{
 
	switch (address.ss_family) {
 
		case AF_INET6: return sizeof(struct sockaddr_in6);
 
		case AF_INET: return sizeof(struct sockaddr_in);
 
		default: NOT_REACHED();
 
	}
 
}
 
#endif
 

	
 

	
 
bool SetNonBlocking(SOCKET d);
 
bool SetNoDelay(SOCKET d);
 
bool SetReusePort(SOCKET d);
 
NetworkError GetSocketError(SOCKET d);
 

	
 
/* Make sure these structures have the size we expect them to be */
 
static_assert(sizeof(in_addr)  ==  4); ///< IPv4 addresses should be 4 bytes.
 
static_assert(sizeof(in6_addr) == 16); ///< IPv6 addresses should be 16 bytes.
 

	
 
#endif /* NETWORK_CORE_OS_ABSTRACTION_H */
src/network/core/tcp.h
Show inline comments
 
@@ -78,64 +78,65 @@ private:
 
	 * lock on the game-state.
 
	 */
 
	enum class Status {
 
		INIT,       ///< TCPConnecter is created but resolving hasn't started.
 
		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.
 
	std::map<SOCKET, NetworkAddress> sock_to_address;   ///< Mapping of a socket to the real address it is connecting to. USed for DEBUG statements.
 
	size_t current_address = 0;                         ///< Current index in addresses we are trying.
 

	
 
	std::vector<SOCKET> sockets;                        ///< Pending connect() attempts.
 
	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);
 
	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() {};
 
	TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address = {});
 
	TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address = {}, int family = AF_UNSPEC);
 
	virtual ~TCPConnecter();
 

	
 
	/**
 
	 * Callback when the connection succeeded.
 
	 * @param s the socket that we opened
 
	 */
 
	virtual void OnConnect(SOCKET s) {}
 

	
 
	/**
 
	 * Callback for when the connection attempt failed.
 
	 */
 
	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;
src/network/core/tcp_connect.cpp
Show inline comments
 
@@ -8,50 +8,51 @@
 
/**
 
 * @file tcp_connect.cpp Basic functions to create connections without blocking.
 
 */
 

	
 
#include "../../stdafx.h"
 
#include "../../thread.h"
 

	
 
#include "tcp.h"
 
#include "../network_coordinator.h"
 
#include "../network_internal.h"
 

	
 
#include <deque>
 

	
 
#include "../../safeguards.h"
 

	
 
/** List of connections that are currently being created */
 
static std::vector<TCPConnecter *> _tcp_connecters;
 

	
 
/**
 
 * 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, NetworkAddress bind_address) :
 
	bind_address(bind_address)
 
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;
 

	
 
@@ -78,48 +79,52 @@ TCPConnecter::~TCPConnecter()
 
}
 

	
 
/**
 
 * 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.
 
 */
 
void TCPConnecter::Connect(addrinfo *address)
 
{
 
	SOCKET sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
 
	if (sock == INVALID_SOCKET) {
 
		Debug(net, 0, "Could not create {} {} socket: {}", NetworkAddress::SocketTypeAsString(address->ai_socktype), NetworkAddress::AddressFamilyAsString(address->ai_family), NetworkError::GetLast().AsString());
 
		return;
 
	}
 

	
 
	if (!SetReusePort(sock)) {
 
		Debug(net, 0, "Setting reuse-port mode failed: {}", 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 {}: {}", this->bind_address.GetAddressAsString(), NetworkError::GetLast().AsString());
 
			closesocket(sock);
 
			return;
 
		}
 
	}
 

	
 
	if (!SetNoDelay(sock)) {
 
		Debug(net, 1, "Setting TCP_NODELAY failed: {}", NetworkError::GetLast().AsString());
 
	}
 
	if (!SetNonBlocking(sock)) {
 
		Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
 
	}
 

	
 
	NetworkAddress network_address = NetworkAddress(address->ai_addr, (int)address->ai_addrlen);
 
	Debug(net, 5, "Attempting to connect to {}", network_address.GetAddressAsString());
 

	
 
	int err = connect(sock, address->ai_addr, (int)address->ai_addrlen);
 
	if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
 
		closesocket(sock);
 

	
 
		Debug(net, 1, "Could not connect to {}: {}", network_address.GetAddressAsString(), NetworkError::GetLast().AsString());
 
		return;
 
@@ -149,48 +154,51 @@ bool TCPConnecter::TryNextAddress()
 
 */
 
void TCPConnecter::OnResolved(addrinfo *ai)
 
{
 
	std::deque<addrinfo *> addresses_ipv4, addresses_ipv6;
 

	
 
	/* Apply "Happy Eyeballs" if it is likely IPv6 is functional. */
 

	
 
	/* Detect if IPv6 is likely to succeed or not. */
 
	bool seen_ipv6 = false;
 
	bool resort = true;
 
	for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
 
		if (runp->ai_family == AF_INET6) {
 
			seen_ipv6 = true;
 
		} else if (!seen_ipv6) {
 
			/* We see an IPv4 before an IPv6; this most likely means there is
 
			 * no IPv6 available on the system, so keep the order of this
 
			 * list. */
 
			resort = false;
 
			break;
 
		}
 
	}
 

	
 
	/* 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);
 
			} else {
 
				addresses_ipv4.emplace_back(runp);
 
			}
 
		} else {
 
			this->addresses.emplace_back(runp);
 
		}
 
	}
 

	
 
	/* If we want to resort, make the list like IPv6 / IPv4 / IPv6 / IPv4 / ..
 
	 * for how ever many (round-robin) DNS entries we have. */
 
	if (resort) {
 
		while (!addresses_ipv4.empty() || !addresses_ipv6.empty()) {
 
			if (!addresses_ipv6.empty()) {
 
				this->addresses.push_back(addresses_ipv6.front());
 
				addresses_ipv6.pop_front();
 
			}
 
			if (!addresses_ipv4.empty()) {
 
				this->addresses.push_back(addresses_ipv4.front());
 
				addresses_ipv4.pop_front();
 
			}
 
		}
src/network/core/tcp_coordinator.cpp
Show inline comments
 
@@ -18,48 +18,51 @@
 

	
 
/**
 
 * 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 {}", type);
 
			return false;
 
	}
 
}
 

	
 
/**
 
 * Receive a packet at TCP level.
 
 * @return Whether at least one packet was received.
 
 */
 
bool NetworkCoordinatorSocketHandler::ReceivePackets()
 
{
 
	/*
 
	 * We read only a few of the packets. This allows the GUI to update when
 
	 * a large set of servers is being received. Otherwise the interface
 
	 * "hangs" while the game is updating the server-list.
 
	 *
 
	 * What arbitrary number to choose is the ultimate question though.
 
	 */
 
	Packet *p;
 
	static const int MAX_PACKETS_TO_RECEIVE = 42;
 
	int i = MAX_PACKETS_TO_RECEIVE;
 
	while (--i != 0 && (p = this->ReceivePacket()) != nullptr) {
 
@@ -73,24 +76,27 @@ bool NetworkCoordinatorSocketHandler::Re
 

	
 
/**
 
 * Helper for logging receiving invalid packets.
 
 * @param type The received packet type.
 
 * @return Always false, as it's an error.
 
 */
 
bool NetworkCoordinatorSocketHandler::ReceiveInvalidPacket(PacketCoordinatorType type)
 
{
 
	Debug(net, 0, "[tcp/coordinator] Received illegal packet type {}", type);
 
	return false;
 
}
 

	
 
bool NetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_ERROR); }
 
bool NetworkCoordinatorSocketHandler::Receive_SERVER_REGISTER(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_REGISTER); }
 
bool NetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_REGISTER_ACK); }
 
bool NetworkCoordinatorSocketHandler::Receive_SERVER_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_UPDATE); }
 
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); }
src/network/core/tcp_coordinator.h
Show inline comments
 
@@ -17,58 +17,62 @@
 
#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.
 
	 *
 
@@ -194,36 +198,80 @@ protected:
 
	 * 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 */
src/network/core/tcp_listen.h
Show inline comments
 
@@ -9,106 +9,108 @@
 
 * @file tcp_listen.h Basic functions to listen for TCP connections.
 
 */
 

	
 
#ifndef NETWORK_CORE_TCP_LISTEN_H
 
#define NETWORK_CORE_TCP_LISTEN_H
 

	
 
#include "tcp.h"
 
#include "../network.h"
 
#include "../../core/pool_type.hpp"
 
#include "../../debug.h"
 
#include "table/strings.h"
 

	
 
/**
 
 * Template for TCP listeners.
 
 * @param Tsocket      The class we create sockets for.
 
 * @param Tfull_packet The packet type to return when we don't allow more sockets.
 
 * @param Tban_packet  The packet type to return when the client is banned.
 
 */
 
template <class Tsocket, PacketType Tfull_packet, PacketType Tban_packet>
 
class TCPListenHandler {
 
	/** List of sockets we listen on. */
 
	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)) {
 
				Packet p(Tban_packet);
 
				p.PrepareToSend();
 

	
 
				Debug(net, 2, "[{}] Banned ip tried to join ({}), refused", Tsocket::GetName(), entry);
 

	
 
				if (p.TransferOut<int>(send, s, 0) < 0) {
 
					Debug(net, 0, "[{}] send failed: {}", 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, "[{}] send failed: {}", Tsocket::GetName(), NetworkError::GetLast().AsString());
 
			}
 
			closesocket(s);
 

	
 
			return false;
 
		}
 

	
 
		return true;
 
	}
 

	
 
	/**
 
	 * Accepts clients from the sockets.
 
	 * @param ls Socket to accept clients from.
 
	 */
 
	static void AcceptClient(SOCKET ls)
 
	{
 
		for (;;) {
 
			struct sockaddr_storage sin;
 
			memset(&sin, 0, sizeof(sin));
 
			socklen_t sin_len = sizeof(sin);
 
			SOCKET s = accept(ls, (struct sockaddr*)&sin, &sin_len);
 
			if (s == INVALID_SOCKET) return;
 
#ifdef __EMSCRIPTEN__
 
			sin_len = FixAddrLenForEmscripten(sin);
 
#endif
 

	
 
			SetNonBlocking(s); // XXX error handling?
 

	
 
			NetworkAddress address(sin, sin_len);
 
			Debug(net, 3, "[{}] Client connected from {} on frame {}", Tsocket::GetName(), address.GetHostname(), _frame_counter);
 

	
 
			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);
 
				if (banned) {
 
					Packet p(Tban_packet);
 
					p.PrepareToSend();
 

	
 
					Debug(net, 2, "[{}] Banned ip tried to join ({}), refused", Tsocket::GetName(), entry);
 

	
 
					if (p.TransferOut<int>(send, s, 0) < 0) {
 
						Debug(net, 0, "[{}] send failed: {}", 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, "[{}] send failed: {}", Tsocket::GetName(), NetworkError::GetLast().AsString());
 
				}
 
				closesocket(s);
 

	
 
				continue;
 
			}
 

	
 
			if (!Tsocket::ValidateClient(s, address)) continue;
 
			Tsocket::AcceptConnection(s, address);
 
		}
 
	}
 

	
 
	/**
 
	 * Handle the receiving of packets.
 
	 * @return true if everything went okay.
 
	 */
 
	static bool Receive()
 
	{
 
		fd_set read_fd, write_fd;
 
		struct timeval tv;
 

	
 
		FD_ZERO(&read_fd);
 
		FD_ZERO(&write_fd);
 

	
 

	
 
		for (Tsocket *cs : Tsocket::Iterate()) {
 
			FD_SET(cs->sock, &read_fd);
 
			FD_SET(cs->sock, &write_fd);
 
		}
 

	
 
		/* take care of listener port */
 
		for (auto &s : sockets) {
src/network/core/tcp_stun.cpp
Show inline comments
 
new file 100644
 
/*
 
 * 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 {}", type);
 
	return false;
 
}
 

	
 
bool NetworkStunSocketHandler::Receive_SERCLI_STUN(Packet *p) { return this->ReceiveInvalidPacket(PACKET_STUN_SERCLI_STUN); }
src/network/core/tcp_stun.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 */
src/network/network_coordinator.cpp
Show inline comments
 
/*
 
 * 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
 
	{
 
		_network_coordinator_client.ConnectSuccess(this->token, s);
 
		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();
 
@@ -80,95 +123,93 @@ public:
 
	}
 
};
 

	
 
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: {
 
			/* Find the connecter based on the invite code. */
 
			auto connecter_it = this->connecter_pre.find(detail);
 
			if (connecter_it == this->connecter_pre.end()) return true;
 
			this->connecter_pre.erase(connecter_it);
 
			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 {} 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:       {}", game_type);
 
		Debug(net, 3, "  Connection type: {}", connection_type);
 
		Debug(net, 3, "  Invite code:     {}", _network_server_invite_code);
 
		Debug(net, 3, "----------------------------------------");
 
	} else {
 
		Debug(net, 3, "Game Coordinator registered our server with invite code '{}'", _network_server_invite_code);
 
	}
 

	
 
@@ -242,48 +283,91 @@ bool ClientNetworkCoordinatorSocketHandl
 

	
 
	/* 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();
 
@@ -378,109 +462,168 @@ void ClientNetworkCoordinatorSocketHandl
 
	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);
 

	
 
	auto connecter_it = this->connecter.find(token);
 
	assert(connecter_it != this->connecter.end());
 

	
 
	connecter_it->second->SetFailure();
 
	this->connecter.erase(connecter_it);
 
	/* 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)
 
void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &token, SOCKET sock, NetworkAddress &address)
 
{
 
	/* Connecter will destroy itself. */
 
	this->game_connecter = nullptr;
 

	
 
	assert(!_network_server);
 
	if (_network_server) {
 
		if (!ServerNetworkGameSocketHandler::ValidateClient(sock, address)) return;
 
		Debug(net, 3, "[{}] Client connected from {} on frame {}", 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);
 

	
 
	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());
 

	
 
	auto connecter_it = this->connecter.find(token);
 
	assert(connecter_it != this->connecter.end());
 

	
 
	connecter_it->second->SetConnected(sock);
 
	this->connecter.erase(connecter_it);
 
		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;
 
@@ -514,25 +657,31 @@ void ClientNetworkCoordinatorSocketHandl
 
		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();
 
		}
 
	}
 
}
src/network/network_coordinator.h
Show inline comments
 
/*
 
 * 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 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? (see https://en.wikipedia.org/wiki/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);
 
	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 */
src/network/network_stun.cpp
Show inline comments
 
new file 100644
 
/*
 
 * 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);
 
	}
 
}
src/network/network_stun.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 */
0 comments (0 inline, 0 general)