← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/net-relay into lp:widelands

 

Notabilis has proposed merging lp:~widelands-dev/widelands/net-relay into lp:widelands with lp:~widelands-dev/widelands/nethost-split as a prerequisite.

Commit message:
When running an Internet game, do not use a local server but relay all traffic over the metaserver.
Avoids problems with firewalls and NAT.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1689087 in widelands: "Implementing a relay server"
  https://bugs.launchpad.net/widelands/+bug/1689087

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/net-relay/+merge/332386

Finally: Ready for review!
When running an Internet game, do not use a local host but relay all traffic over the metaserver, avoiding problems with firewalls and routers/NAT.
Probably best start reviewing the code with src/network/relay_protocol.h for an overview how it works.

This change does not affect build19 clients. Those are still required to host a game locally.

Merge this together with deploying the metaserver version at:
https://github.com/widelands/widelands_metaserver/pull/12

This branch is not quite ready for merge yet: I am waiting for the net-uuid branch to continue the increasing protocol version numbers. Technically it would also be possible to merge this one first and adapt the version numbers in the other branch. Setting the version in this branch is required anyway.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/net-relay into lp:widelands.
=== modified file 'src/network/CMakeLists.txt'
--- src/network/CMakeLists.txt	2017-06-26 11:35:50 +0000
+++ src/network/CMakeLists.txt	2017-10-17 20:59:13 +0000
@@ -10,10 +10,18 @@
     gameclient.h
     gamehost.cc
     gamehost.h
+    netclient_interface.h
     netclient.cc
     netclient.h
+    netclientproxy.cc
+    netclientproxy.h
+    nethost_interface.h
     nethost.cc
     nethost.h
+    nethostproxy.cc
+    nethostproxy.h
+    netrelayconnection.cc
+    netrelayconnection.h
     network.cc
     network.h
     network_gaming_messages.cc
@@ -23,6 +31,7 @@
     network_player_settings_backend.cc
     network_player_settings_backend.h
     network_protocol.h
+    relay_protocol.h
   DEPENDS
     ai
     base_exceptions

=== modified file 'src/network/gameclient.cc'
--- src/network/gameclient.cc	2017-08-18 10:23:45 +0000
+++ src/network/gameclient.cc	2017-10-17 20:59:13 +0000
@@ -40,6 +40,8 @@
 #include "logic/playersmanager.h"
 #include "map_io/widelands_map_loader.h"
 #include "network/internet_gaming.h"
+#include "network/netclient.h"
+#include "network/netclientproxy.h"
 #include "network/network_gaming_messages.h"
 #include "network/network_protocol.h"
 #include "scripting/lua_interface.h"
@@ -57,7 +59,7 @@
 
 	std::string localplayername;
 
-	std::unique_ptr<NetClient> net;
+	std::unique_ptr<NetClientInterface> net;
 
 	/// Currently active modal panel. Receives an end_modal on disconnect
 	UI::Panel* modal;
@@ -90,13 +92,22 @@
 
 GameClient::GameClient(const std::pair<NetAddress, NetAddress>& host,
                        const std::string& playername,
-                       bool internet)
+                       bool internet, const std::string& gamename)
    : d(new GameClientImpl), internet_(internet) {
 
-	d->net = NetClient::connect(host.first);
+	if (internet) {
+		assert(!gamename.empty());
+		d->net = NetClientProxy::connect(host.first, gamename);
+	} else {
+		d->net = NetClient::connect(host.first);
+	}
 	if ((!d->net || !d->net->is_connected()) && host.second.is_valid()) {
 		// First IP did not work? Try the second IP
-		d->net = NetClient::connect(host.second);
+		if (internet) {
+			d->net = NetClientProxy::connect(host.first, gamename);
+		} else {
+			d->net = NetClient::connect(host.second);
+		}
 	}
 	if (!d->net || !d->net->is_connected()) {
 		throw WLWarning(_("Could not establish connection to host"),

=== modified file 'src/network/gameclient.h'
--- src/network/gameclient.h	2017-08-11 15:30:42 +0000
+++ src/network/gameclient.h	2017-10-17 20:59:13 +0000
@@ -24,7 +24,7 @@
 #include "logic/game_controller.h"
 #include "logic/game_settings.h"
 #include "logic/player_end_result.h"
-#include "network/netclient.h"
+#include "network/netclient_interface.h"
 
 struct GameClientImpl;
 
@@ -39,7 +39,7 @@
 struct GameClient : public GameController, public GameSettingsProvider, public ChatProvider {
 	GameClient(const std::pair<NetAddress, NetAddress>& host,
 	           const std::string& playername,
-	           bool internet = false);
+			   bool internet = false, const std::string& gamename = "");
 
 	virtual ~GameClient();
 

=== modified file 'src/network/gamehost.cc'
--- src/network/gamehost.cc	2017-09-20 21:27:25 +0000
+++ src/network/gamehost.cc	2017-10-17 20:59:13 +0000
@@ -50,6 +50,8 @@
 #include "map_io/widelands_map_loader.h"
 #include "network/constants.h"
 #include "network/internet_gaming.h"
+#include "network/nethost.h"
+#include "network/nethostproxy.h"
 #include "network/network_gaming_messages.h"
 #include "network/network_lan_promotion.h"
 #include "network/network_player_settings_backend.h"
@@ -387,7 +389,7 @@
 };
 
 struct Client {
-	NetHost::ConnectionId sock_id;
+	NetHostInterface::ConnectionId sock_id;
 	uint8_t playernum;
 	int16_t usernum;
 	std::string build_id;
@@ -411,7 +413,7 @@
 	NetworkPlayerSettingsBackend npsb;
 
 	LanGamePromoter* promoter;
-	std::unique_ptr<NetHost> net;
+	std::unique_ptr<NetHostInterface> net;
 
 	/// List of connected clients. Note that clients are not in the same
 	/// order as players. In fact, a client must not be assigned to a player.
@@ -478,21 +480,30 @@
    : d(new GameHostImpl(this)), internet_(internet), forced_pause_(false) {
 	log("[Host]: starting up.\n");
 
-	if (internet) {
-		InternetGaming::ref().open_game();
-	}
-
 	d->localplayername = playername;
 
 	// create a listening socket
-	d->net = NetHost::listen(WIDELANDS_PORT);
-	if (d->net == nullptr) {
-		// This might happen when the widelands socket is already in use
-		throw WLWarning(_("Failed to start the server!"),
-		                _("Widelands could not start a server.\n"
-		                  "Probably some other process is already running a server on our port."));
+	if (internet) {
+		// No real listening socket. Instead, connect to the relay server
+		d->net = NetHostProxy::connect(InternetGaming::ref().ips(),
+						InternetGaming::ref().get_local_servername(), InternetGaming::ref().relay_password());
+		if (d->net == nullptr) {
+			// Some kind of problem with the relay server. Bad luck :(
+			throw WLWarning(_("Failed to host the server!"),
+							_("Widelands could not start hosting a server.\n"
+							  "This should not happen and is unfortunately most likely "
+							  "a bug of the metaserver. There is nothing you can do."));
+		}
+	} else {
+		d->net = NetHost::listen(WIDELANDS_PORT);
+		if (d->net == nullptr) {
+			// This might happen when the widelands socket is already in use
+			throw WLWarning(_("Failed to start the server!"),
+							_("Widelands could not start a server.\n"
+							  "Probably some other process is already running a server on our port."));
+		}
+		d->promoter = new LanGamePromoter();
 	}
-	d->promoter = new LanGamePromoter();
 	d->game = nullptr;
 	d->pseudo_networktime = 0;
 	d->waiting = true;
@@ -523,7 +534,10 @@
 
 	// close all open sockets
 	d->net.reset();
-	delete d->promoter;
+	if (d->promoter != nullptr) {
+		delete d->promoter;
+		d->promoter = nullptr;
+	}
 	delete d;
 	delete file_;
 }
@@ -1420,12 +1434,14 @@
 
 // Send the packet to all properly connected clients
 void GameHost::broadcast(SendPacket& packet) {
+	std::vector<NetHostInterface::ConnectionId> receivers;
 	for (const Client& client : d->clients) {
 		if (client.playernum != UserSettings::not_connected()) {
 			assert(client.sock_id > 0);
-			d->net->send(client.sock_id, packet);
+			receivers.push_back(client.sock_id);
 		}
 	}
+	d->net->send(receivers, packet);
 }
 
 void GameHost::write_setting_map(SendPacket& packet) {
@@ -2195,7 +2211,7 @@
 	}
 }
 
-void GameHost::send_file_part(NetHost::ConnectionId csock_id, uint32_t part) {
+void GameHost::send_file_part(NetHostInterface::ConnectionId csock_id, uint32_t part) {
 	assert(part < file_->parts.size());
 
 	uint32_t left = file_->bytes - NETFILEPARTSIZE * part;

=== modified file 'src/network/gamehost.h'
--- src/network/gamehost.h	2017-08-11 15:30:42 +0000
+++ src/network/gamehost.h	2017-10-17 20:59:13 +0000
@@ -24,7 +24,7 @@
 #include "logic/game_settings.h"
 #include "logic/player_end_result.h"
 #include "logic/widelands.h"
-#include "network/nethost.h"
+#include "network/nethost_interface.h"
 #include "network/network.h"
 
 struct ChatMessage;
@@ -127,7 +127,7 @@
 
 	void handle_packet(uint32_t i, RecvPacket&);
 	void handle_network();
-	void send_file_part(NetHost::ConnectionId client_sock_id, uint32_t part);
+	void send_file_part(NetHostInterface::ConnectionId client_sock_id, uint32_t part);
 
 	void check_hung_clients();
 	void broadcast_real_speed(uint32_t speed);

=== modified file 'src/network/internet_gaming.cc'
--- src/network/internet_gaming.cc	2017-08-16 04:31:56 +0000
+++ src/network/internet_gaming.cc	2017-10-17 20:59:13 +0000
@@ -603,6 +603,15 @@
 			if (waitcmd_ == IGPCMD_GAME_OPEN) {
 				waitcmd_ = "";
 			}
+			// Save the received IP(s), so the client can connect to the game
+			NetAddress::parse_ip(&gameips_.first, packet.string(), INTERNET_RELAY_PORT);
+			// If the next value is true, a secondary IP follows
+			if (packet.string() == bool2str(true)) {
+				NetAddress::parse_ip(&gameips_.second, packet.string(), INTERNET_RELAY_PORT);
+			}
+			log("InternetGaming: Received ips of the relay to host: %s %s.\n",
+			    gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str());
+			state_ = IN_GAME;
 		}
 
 		else if (cmd == IGPCMD_GAME_CONNECT) {
@@ -610,10 +619,10 @@
 			assert(waitcmd_ == IGPCMD_GAME_CONNECT);
 			waitcmd_ = "";
 			// Save the received IP(s), so the client can connect to the game
-			NetAddress::parse_ip(&gameips_.first, packet.string(), WIDELANDS_PORT);
+			NetAddress::parse_ip(&gameips_.first, packet.string(), INTERNET_RELAY_PORT);
 			// If the next value is true, a secondary IP follows
 			if (packet.string() == bool2str(true)) {
-				NetAddress::parse_ip(&gameips_.second, packet.string(), WIDELANDS_PORT);
+				NetAddress::parse_ip(&gameips_.second, packet.string(), INTERNET_RELAY_PORT);
 			}
 			log("InternetGaming: Received ips of the game to join: %s %s.\n",
 			    gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str());
@@ -670,11 +679,18 @@
 	return gameips_;
 }
 
+const std::string InternetGaming::relay_password() {
+	return pwd_;
+}
+
 /// called by a client to join the game \arg gamename
 void InternetGaming::join_game(const std::string& gamename) {
 	if (!logged_in())
 		return;
 
+	// Reset the game ips, we should receive new ones shortly
+	gameips_ = std::make_pair(NetAddress(), NetAddress());
+
 	SendPacket s;
 	s.string(IGPCMD_GAME_CONNECT);
 	s.string(gamename);
@@ -693,13 +709,15 @@
 	if (!logged_in())
 		return;
 
+	// Reset the game ips, we should receive new ones shortly
+	gameips_ = std::make_pair(NetAddress(), NetAddress());
+
 	SendPacket s;
 	s.string(IGPCMD_GAME_OPEN);
 	s.string(gamename_);
 	s.string("1024");  // Used to be maxclients, no longer used.
 	net->send(s);
 	log("InternetGaming: Client opened a game with the name %s.\n", gamename_.c_str());
-	state_ = IN_GAME;
 
 	// From now on we wait for a reply from the metaserver
 	waitcmd_ = IGPCMD_GAME_OPEN;

=== modified file 'src/network/internet_gaming.h'
--- src/network/internet_gaming.h	2017-07-01 08:22:54 +0000
+++ src/network/internet_gaming.h	2017-10-17 20:59:13 +0000
@@ -87,10 +87,17 @@
 	 * Contains two addresses when the host supports IPv4 and IPv6, one address when the host
 	 * only supports one of the protocols, no addresses when no join-request was sent to
 	 * the metaserver. "No address" means a default constructed address.
+	 * Also returns the IPs of the relay server when trying to host a game.
 	 * Use NetAddress::is_valid() to check whether a NetAddress has been default constructed.
 	 * @return The addresses.
 	 */
 	const std::pair<NetAddress, NetAddress>& ips();
+
+	/**
+	 * Returns the password required to connect to the relay server as host.
+	 */
+	const std::string relay_password();
+
 	void join_game(const std::string& gamename);
 	void open_game();
 	void set_game_playing();

=== modified file 'src/network/internet_gaming_protocol.h'
--- src/network/internet_gaming_protocol.h	2017-07-01 08:22:54 +0000
+++ src/network/internet_gaming_protocol.h	2017-10-17 20:59:13 +0000
@@ -32,6 +32,8 @@
  * Used Versions:
  * 0: Build 19 and before
  * 1: Between build 19 and build 20 - IPv6 support added
+ * NOCOM(Notabilis): Update this comment and the protocol version after merging net-uuid
+ * 3: Between build 19 and build 20 - Added network relay[version supported]
  */
 #define INTERNET_GAMING_PROTOCOL_VERSION 1
 
@@ -66,7 +68,15 @@
 
 /// Metaserver connection details
 static const std::string INTERNET_GAMING_METASERVER = "widelands.org";
+// Default port for connecting to the metaserver
 #define INTERNET_GAMING_PORT 7395
+// Default port for connecting to the relay
+#define INTERNET_RELAY_PORT 7397
+// The following ones are only used between metaserver and relay
+// Port used by the metaserver to contact the relay
+// INTERNET_RELAY_RPC_PORT 7398
+// Port used by the relay to contact the metaserver
+// INTERNET_GAMING_RPC_PORT 7399
 
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * CLIENT RIGHTS                                                           *
@@ -356,12 +366,13 @@
  * \li string:    number of maximal clients
  * \note build_id is not necessary, as this is in every way the build_id of the hosting client.
  *
- * Sent by the metaserver to acknowledge the startup of a new game without payload. The metaserver
- * will
- * list the new game, but set it as not connectable and recheck the connectability for
- * INTERNET_GAMING_TIMEOUT ms.
- * If the game gets connectable in time, the metaserver lists the game as connectable, else it
- * removes the game from the list of games.
+ * Sent by the metaserver to acknowledge the startup of a new game with the following payload:
+ * \li string:    primary ip of relay server for the game.
+ * \li string:    whether a secondary ip for the relay follows ("true" or "false" as string)
+ * \li string:    secondary ip of the relay - only valid if previous was true
+ * The metaserver will list the new game, but set it as not connectable.
+ * When the client connects to the relay within INTERNET_GAMING_TIMEOUT milliseconds,
+ * the metaserver lists the game as connectable, else it removes the game from the list of games.
  */
 static const std::string IGPCMD_GAME_OPEN = "GAME_OPEN";
 

=== modified file 'src/network/netclient.h'
--- src/network/netclient.h	2017-07-01 08:22:54 +0000
+++ src/network/netclient.h	2017-10-17 20:59:13 +0000
@@ -22,6 +22,7 @@
 
 #include <memory>
 
+#include "network/netclient_interface.h"
 #include "network/network.h"
 
 /**
@@ -30,7 +31,7 @@
  * This class only tries to create a single socket, either for IPv4 and IPv6.
  * Which is used depends on what kind of address is given on call to connect().
  */
-class NetClient {
+class NetClient : public NetClientInterface {
 public:
 	/**
 	 * Tries to establish a connection to the given host.
@@ -39,10 +40,6 @@
 	 */
 	static std::unique_ptr<NetClient> connect(const NetAddress& host);
 
-	/**
-	 * Closes the connection.
-	 * If you want to send a goodbye-message to the host, do so before freeing the object.
-	 */
 	~NetClient();
 
 	/**
@@ -53,33 +50,11 @@
 	 */
 	bool get_remote_address(NetAddress* addr) const;
 
-	/**
-	 * Returns whether the client is connected.
-	 * \return \c true if the connection is open, \c false otherwise.
-	 */
-	bool is_connected() const;
-
-	/**
-	 * Closes the connection.
-	 * If you want to send a goodbye-message to the host, do so before calling this.
-	 */
-	void close();
-
-	/**
-	 * Tries to receive a packet.
-	 * \param packet A packet that should be overwritten with the received data.
-	 * \return \c true if a packet is available, \c false otherwise.
-	 *   The given packet is only modified when \c true is returned.
-	 *   Calling this on a closed connection will return false.
-	 */
-	bool try_receive(RecvPacket* packet);
-
-	/**
-	 * Sends a packet.
-	 * Calling this on a closed connection will silently fail.
-	 * \param packet The packet to send.
-	 */
-	void send(const SendPacket& packet);
+	// Inherited from NetClientInterface
+	bool is_connected() const override;
+	void close() override;
+	bool try_receive(RecvPacket* packet) override;
+	void send(const SendPacket& packet) override;
 
 private:
 	/**

=== added file 'src/network/netclient_interface.h'
--- src/network/netclient_interface.h	1970-01-01 00:00:00 +0000
+++ src/network/netclient_interface.h	2017-10-17 20:59:13 +0000
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008-2017 by the Widelands Development Team
+ *
+ * This program 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_NETWORK_NETCLIENT_INTERFACE_H
+#define WL_NETWORK_NETCLIENT_INTERFACE_H
+
+#include <memory>
+
+#include "network/network.h"
+
+/**
+ * NetClient manages the network connection for a network game in which this computer
+ * participates as a client.
+ * This class provides the interface all NetClient implementation have to follow.
+ * Currently two implementations exists: A "real" NetClient for local games and a
+ * NetClientProxy which relays commands over a relay server.
+ */
+class NetClientInterface {
+public:
+
+	/**
+	 * Closes the connection.
+	 * If you want to send a goodbye-message to the host, do so before freeing the object.
+	 */
+	virtual ~NetClientInterface() {
+	}
+
+	/**
+	 * Returns whether the client is connected.
+	 * \return \c true if the connection is open, \c false otherwise.
+	 */
+	virtual bool is_connected() const = 0;
+
+	/**
+	 * Closes the connection.
+	 * If you want to send a goodbye-message to the host, do so before calling this.
+	 */
+	virtual void close() = 0;
+
+	/**
+	 * Tries to receive a packet.
+	 * \param packet A packet that should be overwritten with the received data.
+	 * \return \c true if a packet is available, \c false otherwise.
+	 *   The given packet is only modified when \c true is returned.
+	 *   Calling this on a closed connection will return false.
+	 */
+	virtual bool try_receive(RecvPacket* packet) = 0;
+
+	/**
+	 * Sends a packet.
+	 * Calling this on a closed connection will silently fail.
+	 * \param packet The packet to send.
+	 */
+	virtual void send(const SendPacket& packet) = 0;
+};
+
+#endif  // end of include guard: WL_NETWORK_NETCLIENT_INTERFACE_H

=== added file 'src/network/netclientproxy.cc'
--- src/network/netclientproxy.cc	1970-01-01 00:00:00 +0000
+++ src/network/netclientproxy.cc	2017-10-17 20:59:13 +0000
@@ -0,0 +1,133 @@
+#include "network/netclientproxy.h"
+
+#include <memory>
+
+#include "base/log.h"
+#include "network/relay_protocol.h"
+
+std::unique_ptr<NetClientProxy> NetClientProxy::connect(const NetAddress& address, const std::string& name) {
+	std::unique_ptr<NetClientProxy> ptr(new NetClientProxy(address, name));
+	if (ptr->conn_ != nullptr && ptr->conn_->is_connected()) {
+		return ptr;
+	} else {
+		ptr.reset();
+		return ptr;
+	}
+}
+
+NetClientProxy::~NetClientProxy() {
+	close();
+}
+
+
+bool NetClientProxy::is_connected() const {
+	return conn_ && conn_->is_connected();
+}
+
+void NetClientProxy::close() {
+	if (conn_ && conn_->is_connected()) {
+		conn_->close();
+	}
+}
+
+bool NetClientProxy::try_receive(RecvPacket* packet) {
+	receive_commands();
+
+	// Now check whether there is data
+	if (received_.empty())
+		return false;
+
+	*packet = std::move(received_.front());
+	received_.pop();
+	return true;
+}
+
+void NetClientProxy::send(const SendPacket& packet) {
+	conn_->send(RelayCommand::kToHost);
+	conn_->send(packet);
+}
+
+NetClientProxy::NetClientProxy(const NetAddress& address, const std::string& name)
+	: conn_(NetRelayConnection::connect(address)) {
+
+   	if (conn_ == nullptr || !conn_->is_connected()) {
+		return;
+   	}
+
+   	conn_->send(RelayCommand::kHello);
+   	conn_->send(kRelayProtocolVersion);
+   	conn_->send(name);
+   	conn_->send("client");
+
+   	// Wait for answer
+	// Don't like it.
+   	while (!conn_->peek_cmd());
+
+	RelayCommand cmd;
+	conn_->receive(&cmd);
+
+   	if (cmd != RelayCommand::kWelcome) {
+		conn_->close();
+		conn_.reset();
+		return;
+   	}
+
+   	// Check version
+   	while (!conn_->peek_uint8_t());
+	uint8_t relay_proto_version;
+	conn_->receive(&relay_proto_version);
+   	if (relay_proto_version != kRelayProtocolVersion) {
+		conn_->close();
+		conn_.reset();
+   	}
+
+   	// Check game name
+   	while (!conn_->peek_string());
+	std::string game_name;
+	conn_->receive(&game_name);
+   	if (game_name != name) {
+		conn_->close();
+		conn_.reset();
+		return;
+   	}
+}
+
+void NetClientProxy::receive_commands() {
+	if (!conn_->is_connected()) {
+		return;
+	}
+
+	// Receive all available commands
+	RelayCommand cmd;
+	conn_->peek_reset();
+	if (!conn_->peek_cmd(&cmd)) {
+		// No command to receive
+		return;
+	}
+	switch (cmd) {
+		case RelayCommand::kDisconnect:
+			if (conn_->peek_string()) {
+				// Command is completely in the buffer, handle it
+				conn_->receive(&cmd);
+				std::string reason;
+				conn_->receive(&reason);
+				// TODO(Notabilis): Handle the reason for the disconnect
+				conn_->close();
+			}
+			break;
+		case RelayCommand::kFromHost:
+			if (conn_->peek_recvpacket()) {
+				conn_->receive(&cmd);
+				RecvPacket packet;
+				conn_->receive(&packet);
+                received_.push(std::move(packet));
+			}
+			break;
+		default:
+			// Other commands should not be possible.
+			// Then is either something wrong with the protocol or there is an implementation mistake
+			log("Received command code %i from relay server, do not know what to do with it\n",
+					static_cast<uint8_t>(cmd));
+			NEVER_HERE();
+	}
+}

=== added file 'src/network/netclientproxy.h'
--- src/network/netclientproxy.h	1970-01-01 00:00:00 +0000
+++ src/network/netclientproxy.h	2017-10-17 20:59:13 +0000
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2008-2017 by the Widelands Development Team
+ *
+ * This program 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_NETWORK_NETCLIENTPROXY_H
+#define WL_NETWORK_NETCLIENTPROXY_H
+
+#include <map>
+#include <memory>
+
+#include "network/netclient_interface.h"
+#include "network/netrelayconnection.h"
+
+/**
+ * NetClientProxy manages is a NetHostInterface implementation communicating to
+ * the game clients about a relay server.
+ */
+class NetClientProxy : public NetClientInterface {
+public:
+
+	/**
+	 * Tries to connect to the relay at the given address.
+	 * \param address The address to connect to.
+	 * \param name The name of the game.
+	 * \return A pointer to a ready \c NetClientProxy object or a nullptr if the connection failed.
+	 */
+	static std::unique_ptr<NetClientProxy> connect(const NetAddress& address, const std::string& name);
+
+	/**
+	 * Closes the server.
+	 */
+	~NetClientProxy();
+
+
+	// Inherited from NetClientInterface
+	bool is_connected() const override;
+	void close() override;
+	bool try_receive(RecvPacket* packet) override;
+	void send(const SendPacket& packet) override;
+
+private:
+
+	/**
+	 * Tries to connect to the relay at the given address.
+	 * If it fails, is_connected() will return \c false.
+	 * \param address The address to connect to.
+	 * \param name The name of the game.
+	 */
+	explicit NetClientProxy(const NetAddress& address, const std::string& name);
+
+	void receive_commands();
+
+	std::unique_ptr<NetRelayConnection> conn_;
+
+	/// For each connected client, the packages that have been received from him.
+	std::queue<RecvPacket> received_;
+};
+
+#endif  // end of include guard: WL_NETWORK_NETCLIENTPROXY_H

=== modified file 'src/network/nethost.cc'
--- src/network/nethost.cc	2017-06-19 18:32:06 +0000
+++ src/network/nethost.cc	2017-10-17 20:59:13 +0000
@@ -191,6 +191,12 @@
 	}
 }
 
+void NetHost::send(const std::vector<ConnectionId>& ids, const SendPacket& packet) {
+	for (ConnectionId id : ids) {
+		send(id, packet);
+	}
+}
+
 NetHost::NetHost(const uint16_t port)
    : clients_(), next_id_(1), io_service_(), acceptor_v4_(io_service_), acceptor_v6_(io_service_) {
 

=== modified file 'src/network/nethost.h'
--- src/network/nethost.h	2017-06-24 13:44:38 +0000
+++ src/network/nethost.h	2017-10-17 20:59:13 +0000
@@ -23,17 +23,15 @@
 #include <map>
 #include <memory>
 
-#include "network/network.h"
+#include "network/nethost_interface.h"
 
 /**
  * NetHost manages the client connections of a network game in which this computer
  * participates as a server.
- * This class tries to create sockets for IPv4 and IPv6.
+ * This class tries to create sockets for IPv4 and IPv6 for gaming in the local network.
  */
-class NetHost {
+class NetHost : public NetHostInterface {
 public:
-	/// IDs used to enumerate the clients.
-	using ConnectionId = uint32_t;
 
 	/**
 	 * Tries to listen on the given port.
@@ -47,60 +45,30 @@
 	 */
 	~NetHost();
 
+	// Inherited from NetHostInterface
+	bool is_connected(ConnectionId id) const override;
+	void close(ConnectionId id) override;
+	bool try_accept(ConnectionId* new_id) override;
+	bool try_receive(ConnectionId id, RecvPacket* packet) override;
+	void send(ConnectionId id, const SendPacket& packet) override;
+	void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override;
+
+private:
+
 	/**
 	 * Returns whether the server is started and is listening.
 	 * \return \c true if the server is listening, \c false otherwise.
 	 */
+	// Feel free to make this method public if you need it
 	bool is_listening() const;
 
 	/**
-	 * Returns whether the given client is connected.
-	 * \param The id of the client to check.
-	 * \return \c true if the connection is open, \c false otherwise.
-	 */
-	bool is_connected(ConnectionId id) const;
-
-	/**
 	 * Stops listening for connections.
 	 */
+	// Feel free to make this method public if you need it
 	void stop_listening();
 
 	/**
-	 * Closes the connection to the given client.
-	 * \param id The id of the client to close the connection to.
-	 */
-	void close(ConnectionId id);
-
-	/**
-	 * Tries to accept a new client.
-	 * \param new_id The connection id of the new client will be stored here.
-	 * \return \c true if a client has connected, \c false otherwise.
-	 *   The given id is only modified when \c true is returned.
-	 *   Calling this on a closed server will return false.
-	 *   The returned id is always greater than 0.
-	 */
-	bool try_accept(ConnectionId* new_id);
-
-	/**
-	 * Tries to receive a packet.
-	 * \param id The connection id of the client that should be received.
-	 * \param packet A packet that should be overwritten with the received data.
-	 * \return \c true if a packet is available, \c false otherwise.
-	 *   The given packet is only modified when \c true is returned.
-	 *   Calling this on a closed connection will return false.
-	 */
-	bool try_receive(ConnectionId id, RecvPacket* packet);
-
-	/**
-	 * Sends a packet.
-	 * Calling this on a closed connection will silently fail.
-	 * \param id The connection id of the client that should be sent to.
-	 * \param packet The packet to send.
-	 */
-	void send(ConnectionId id, const SendPacket& packet);
-
-private:
-	/**
 	 * Tries to listen on the given port.
 	 * If it fails, is_listening() will return \c false.
 	 * \param port The port to listen on.
@@ -129,9 +97,9 @@
 
 	/// A map linking client ids to the respective data about the clients.
 	/// Client ids not in this map should be considered invalid.
-	std::map<NetHost::ConnectionId, Client> clients_;
+	std::map<NetHostInterface::ConnectionId, Client> clients_;
 	/// The next client id that will be used
-	NetHost::ConnectionId next_id_;
+	NetHostInterface::ConnectionId next_id_;
 	/// An io_service needed by boost.asio. Primary needed for async operations.
 	boost::asio::io_service io_service_;
 	/// The acceptor we get IPv4 connection requests to.

=== added file 'src/network/nethost_interface.h'
--- src/network/nethost_interface.h	1970-01-01 00:00:00 +0000
+++ src/network/nethost_interface.h	2017-10-17 20:59:13 +0000
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2008-2017 by the Widelands Development Team
+ *
+ * This program 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_NETWORK_NETHOST_INTERFACE_H
+#define WL_NETWORK_NETHOST_INTERFACE_H
+
+#include "network/network.h"
+
+/**
+ * A NetHost manages the client connections of a network game in
+ * which this computer participates as a server.
+ * This class provides the interface all NetHost implementation have to follow.
+ * Currently two implementations exists: A "real" NetHost for local games and a
+ * NetHostProxy which relays commands over a relay server.
+ */
+class NetHostInterface {
+public:
+	/// IDs used to enumerate the clients.
+	using ConnectionId = uint8_t;
+
+	/**
+	 * Closes the server.
+	 */
+	virtual ~NetHostInterface() {
+	}
+
+	/**
+	 * Returns whether the given client is connected.
+	 * \param The id of the client to check.
+	 * \return \c true if the connection is open, \c false otherwise.
+	 */
+	virtual bool is_connected(ConnectionId id) const = 0;
+
+	/**
+	 * Closes the connection to the given client.
+	 * \param id The id of the client to close the connection to.
+	 */
+	virtual void close(ConnectionId id) = 0;
+
+	/**
+	 * Tries to accept a new client.
+	 * \param new_id The connection id of the new client will be stored here.
+	 * \return \c true if a client has connected, \c false otherwise.
+	 *   The given id is only modified when \c true is returned.
+	 *   Calling this on a closed server will return false.
+	 *   The returned id is always greater than 0.
+	 */
+	virtual bool try_accept(ConnectionId* new_id) = 0;
+
+	/**
+	 * Tries to receive a packet.
+	 * \param id The connection id of the client that should be received.
+	 * \param packet A packet that should be overwritten with the received data.
+	 * \return \c true if a packet is available, \c false otherwise.
+	 *   The given packet is only modified when \c true is returned.
+	 *   Calling this on a closed connection will return false.
+	 */
+	virtual bool try_receive(ConnectionId id, RecvPacket* packet) = 0;
+
+	/**
+	 * Sends a packet.
+	 * Calling this on a closed connection will silently fail.
+	 * \param id The connection id of the client that should be sent to.
+	 * \param packet The packet to send.
+	 */
+	virtual void send(ConnectionId id, const SendPacket& packet) = 0;
+
+	/**
+	 * Sends a packet to a group of clients.
+	 * Calling this on a closed connection will silently fail.
+	 * \param ids The connection ids of the clients that should be sent to.
+	 * \param packet The packet to send.
+	 */
+	virtual void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) = 0;
+};
+
+#endif  // end of include guard: WL_NETWORK_NETHOST_INTERFACE_H

=== added file 'src/network/nethostproxy.cc'
--- src/network/nethostproxy.cc	1970-01-01 00:00:00 +0000
+++ src/network/nethostproxy.cc	2017-10-17 20:59:13 +0000
@@ -0,0 +1,258 @@
+#include "network/nethostproxy.h"
+
+#include <memory>
+
+#include "base/log.h"
+#include "network/relay_protocol.h"
+
+std::unique_ptr<NetHostProxy> NetHostProxy::connect(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password) {
+	std::unique_ptr<NetHostProxy> ptr(new NetHostProxy(addresses, name, password));
+	if (ptr->conn_ != nullptr && ptr->conn_->is_connected()) {
+		return ptr;
+	} else {
+		ptr.reset();
+		return ptr;
+	}
+}
+
+NetHostProxy::~NetHostProxy() {
+	if (conn_ && conn_->is_connected()) {
+		while (!clients_.empty()) {
+			close(clients_.begin()->first);
+			clients_.erase(clients_.begin());
+		}
+		conn_->close();
+	}
+}
+
+bool NetHostProxy::is_connected(const ConnectionId id) const {
+	return clients_.count(id) > 0 && clients_.at(id).state_ == Client::State::kConnected;
+}
+
+void NetHostProxy::close(const ConnectionId id) {
+	auto iter_client = clients_.find(id);
+	if (iter_client == clients_.end()) {
+		// Not connected anyway
+		return;
+	}
+    conn_->send(RelayCommand::kDisconnectClient);
+    conn_->send(id);
+    if (iter_client->second.received_.empty()) {
+		// No pending messages, remove the client
+		clients_.erase(iter_client);
+    } else {
+    	// Still messages pending. Keep the structure so the host can receive them
+		iter_client->second.state_ = Client::State::kDisconnected;
+    }
+}
+
+bool NetHostProxy::try_accept(ConnectionId* new_id) {
+	// Always read all available data into buffers
+	receive_commands();
+
+	for (auto& entry : clients_) {
+		if (entry.second.state_ == Client::State::kConnecting) {
+			*new_id = entry.first;
+			entry.second.state_ = Client::State::kConnected;
+			return true;
+		}
+	}
+	return false;
+}
+
+bool NetHostProxy::try_receive(const ConnectionId id, RecvPacket* packet) {
+	receive_commands();
+
+	// Check whether client is not (yet) connected
+	if (clients_.count(id) == 0 || clients_.at(id).state_ == Client::State::kConnecting)
+		return false;
+
+	std::queue<RecvPacket>& packet_list = clients_.at(id).received_;
+
+	// Now check whether there is data for the requested client
+	if (packet_list.empty()) {
+		// If the client is already disconnected it should not be in the map anymore
+		assert(clients_.at(id).state_ == Client::State::kConnected);
+		return false;
+	}
+
+	*packet = std::move(packet_list.front());
+	packet_list.pop();
+	if (packet_list.empty() && clients_.at(id).state_ == Client::State::kDisconnected) {
+		// If the receive buffer is empty now, remove client
+		clients_.erase(id);
+	}
+	return true;
+}
+
+void NetHostProxy::send(const ConnectionId id, const SendPacket& packet) {
+	std::vector<ConnectionId> vec;
+	vec.push_back(id);
+	send(vec, packet);
+}
+
+void NetHostProxy::send(const std::vector<ConnectionId>& ids, const SendPacket& packet) {
+	if (ids.empty()) {
+		return;
+	}
+
+	receive_commands();
+
+	conn_->send(RelayCommand::kToClients);
+	for (ConnectionId id : ids) {
+		if (is_connected(id)) {
+			// This should be but is not always the case. It can happen that we receive a client disconnect
+			// on receive_commands() above and the GameHost did not have the chance to react to it yet.
+			conn_->send(id);
+		}
+	}
+	conn_->send(0);
+	conn_->send(packet);
+}
+
+NetHostProxy::NetHostProxy(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password)
+	: conn_(NetRelayConnection::connect(addresses.first)) {
+
+	if ((conn_ == nullptr || !conn_->is_connected()) && addresses.second.is_valid()) {
+		conn_ = NetRelayConnection::connect(addresses.second);
+	}
+
+   	if (conn_ == nullptr || !conn_->is_connected()) {
+		return;
+   	}
+
+   	conn_->send(RelayCommand::kHello);
+   	conn_->send(kRelayProtocolVersion);
+   	conn_->send(name);
+   	conn_->send(password);
+   	conn_->send(password);
+
+   	// Wait 10 seconds for an answer
+	uint32_t endtime = time(nullptr) + 10;
+	while (!conn_->peek_cmd()) {
+		if (time(nullptr) > endtime) {
+			// No message received in time
+			conn_->close();
+			conn_.reset();
+			return;
+		}
+	}
+
+	RelayCommand cmd;
+	conn_->receive(&cmd);
+
+   	if (cmd != RelayCommand::kWelcome) {
+		conn_->close();
+		conn_.reset();
+		return;
+   	}
+
+   	// Check version
+	endtime = time(nullptr) + 10;
+	while (!conn_->peek_uint8_t()) {
+		if (time(nullptr) > endtime) {
+			// No message received in time
+			conn_->close();
+			conn_.reset();
+			return;
+		}
+	}
+	uint8_t relay_proto_version;
+	conn_->receive(&relay_proto_version);
+   	if (relay_proto_version != kRelayProtocolVersion) {
+		conn_->close();
+		conn_.reset();
+		return;
+   	}
+
+   	// Check game name
+	endtime = time(nullptr) + 10;
+	while (!conn_->peek_string()) {
+		if (time(nullptr) > endtime) {
+			// No message received in time
+			conn_->close();
+			conn_.reset();
+			return;
+		}
+	}
+	std::string game_name;
+	conn_->receive(&game_name);
+   	if (game_name != name) {
+		conn_->close();
+		conn_.reset();
+		return;
+   	}
+}
+
+void NetHostProxy::receive_commands() {
+	if (!conn_->is_connected()) {
+		return;
+	}
+
+	// Receive all available commands
+	RelayCommand cmd;
+	conn_->peek_reset();
+	if (!conn_->peek_cmd(&cmd)) {
+		// No command to receive
+		return;
+	}
+	switch (cmd) {
+		case RelayCommand::kDisconnect:
+			if (conn_->peek_string()) {
+				// Command is completely in the buffer, handle it
+				conn_->receive(&cmd);
+				std::string reason;
+				conn_->receive(&reason);
+				conn_->close();
+				// Set all clients to offline
+				for (auto& entry : clients_) {
+					entry.second.state_ = Client::State::kDisconnected;
+				}
+			}
+			break;
+		case RelayCommand::kConnectClient:
+			if (conn_->peek_uint8_t()) {
+				conn_->receive(&cmd);
+				uint8_t id;
+				conn_->receive(&id);
+				// Kind of hacky, but create a new Client object inplace.
+				// insert() and emplace() do not work since they call the (deleted) copy constructor
+				// (operator[] returns the object when it exists, otherwise a new one is created)
+				assert(clients_.count(id) == 0);
+                clients_[id];
+                assert(clients_.count(id) == 1);
+			}
+			break;
+		case RelayCommand::kDisconnectClient:
+			if (conn_->peek_uint8_t()) {
+				conn_->receive(&cmd);
+				uint8_t id;
+				conn_->receive(&id);
+				assert(clients_.count(id));
+				clients_.at(id).state_ = Client::State::kDisconnected;
+			}
+			break;
+		case RelayCommand::kFromClient:
+			if (conn_->peek_uint8_t() && conn_->peek_recvpacket()) {
+				conn_->receive(&cmd);
+				uint8_t id;
+				conn_->receive(&id);
+				RecvPacket packet;
+				conn_->receive(&packet);
+				assert(clients_.count(id));
+				clients_.at(id).received_.push(std::move(packet));
+			}
+			break;
+		case RelayCommand::kPing:
+			conn_->receive(&cmd);
+			// Reply with a pong
+			conn_->send(RelayCommand::kPong);
+			break;
+		default:
+			// Other commands should not be possible.
+			// Then is either something wrong with the protocol or there is an implementation mistake
+			log("Received command code %i from relay server, do not know what to do with it\n",
+					static_cast<uint8_t>(cmd));
+			NEVER_HERE();
+	}
+}

=== added file 'src/network/nethostproxy.h'
--- src/network/nethostproxy.h	1970-01-01 00:00:00 +0000
+++ src/network/nethostproxy.h	2017-10-17 20:59:13 +0000
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2008-2017 by the Widelands Development Team
+ *
+ * This program 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_NETWORK_NETHOSTPROXY_H
+#define WL_NETWORK_NETHOSTPROXY_H
+
+#include <map>
+#include <memory>
+
+#include "network/nethost_interface.h"
+#include "network/netrelayconnection.h"
+
+/**
+ * NetHostProxy manages is a NetHostInterface implementation communicating to
+ * the game clients about a relay server.
+ */
+class NetHostProxy : public NetHostInterface {
+public:
+
+	/**
+	 * Tries to connect to the relay at the given address.
+	 * \param address The address to connect to.
+	 * \param name The name of the game.
+	 * \param password The password for connecting as host.
+	 * \return A pointer to a ready \c NetHostProxy object or a nullptr if the connection failed.
+	 */
+	static std::unique_ptr<NetHostProxy> connect(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password);
+
+	/**
+	 * Closes the server.
+	 */
+	~NetHostProxy();
+
+	// Inherited from NetHostInterface
+	bool is_connected(ConnectionId id) const override;
+	void close(ConnectionId id) override;
+	bool try_accept(ConnectionId* new_id) override;
+	bool try_receive(ConnectionId id, RecvPacket* packet) override;
+	void send(ConnectionId id, const SendPacket& packet) override;
+	void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override;
+
+private:
+
+	/**
+	 * Tries to connect to the relay at the given address.
+	 * If it fails, is_connected() will return \c false.
+	 * \param addresses Two possible addresses to connect to.
+	 * \param name The name of the game.
+	 * \param password The password for connecting as host.
+	 */
+	explicit NetHostProxy(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password);
+
+	void receive_commands();
+
+	std::unique_ptr<NetRelayConnection> conn_;
+
+	/// A list of clients which want to connect.
+	std::queue<ConnectionId> accept_;
+
+	/// The clients connected through the relay
+	struct Client {
+		/// The state of the client
+		enum class State {
+			/// The relay introduced the client but try_accept() hasn't been called for it yet
+			kConnecting,
+			/// A normally connected client
+			kConnected,
+			/// The relay told us that the client disconnected but there are still packages in the buffer
+			kDisconnected
+		};
+
+		Client()
+			: state_(State::kConnecting), received_() {
+		}
+
+		// deleted since RecvPacket does not offer a copy constructor
+		Client(const Client& other) = delete;
+
+		/// The current connection state
+		State state_;
+		/// The packages that have been received
+		std::queue<RecvPacket> received_;
+	};
+	/// The connected clients
+	std::map<ConnectionId, Client> clients_;
+};
+
+#endif  // end of include guard: WL_NETWORK_NETHOSTPROXY_H

=== added file 'src/network/netrelayconnection.cc'
--- src/network/netrelayconnection.cc	1970-01-01 00:00:00 +0000
+++ src/network/netrelayconnection.cc	2017-10-17 20:59:13 +0000
@@ -0,0 +1,299 @@
+#include "network/netrelayconnection.h"
+
+#include <memory>
+
+#include "base/log.h"
+
+// Not so great: Quite some duplicated code between this class and NetClient.
+
+std::unique_ptr<NetRelayConnection> NetRelayConnection::connect(const NetAddress& host) {
+
+	std::unique_ptr<NetRelayConnection> ptr(new NetRelayConnection(host));
+	if (ptr->is_connected()) {
+		return ptr;
+	} else {
+		ptr.reset();
+		return ptr;
+	}
+}
+
+NetRelayConnection::~NetRelayConnection() {
+	if (is_connected()) {
+		close();
+	}
+}
+
+bool NetRelayConnection::is_connected() const {
+	return socket_.is_open();
+}
+
+void NetRelayConnection::close() {
+	if (!is_connected()) {
+		return;
+	}
+	boost::system::error_code ec;
+	boost::asio::ip::tcp::endpoint remote = socket_.remote_endpoint(ec);
+	if (!ec) {
+		log("[NetRelayConnection] Closing network socket connected to %s:%i.\n",
+		    remote.address().to_string().c_str(), remote.port());
+	} else {
+		log("[NetRelayConnection] Closing network socket.\n");
+	}
+	socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
+	socket_.close(ec);
+}
+
+bool NetRelayConnection::peek_string() {
+	try_network_receive();
+
+	if (buffer_.size() < peek_pointer_ + 1) {
+		return false;
+	}
+
+	// A string goes until the next \0 and might have a length of 0
+	for (size_t i = peek_pointer_; i < buffer_.size(); ++i) {
+		if (buffer_[i] == '\0') {
+			peek_pointer_ = i + 1;
+			return true;
+		}
+	}
+	return false;
+}
+
+void NetRelayConnection::receive(std::string *str) {
+	// Try to receive something from the network.
+	try_network_receive();
+
+	// Reset the peek pointer
+	peek_reset();
+	#ifdef NDEBUG
+	// Check if we can read a complete string
+	assert(peek_string());
+	peek_reset();
+	#endif // NDEBUG
+
+	// Read the string
+	str->clear();
+	// No range check needed, peek_string() takes care of that
+	while (buffer_.front() != '\0') {
+		str->push_back(buffer_.front());
+		buffer_.pop_front();
+	}
+	// Pop the \0
+	buffer_.pop_front();
+}
+
+bool NetRelayConnection::peek_cmd(RelayCommand *cmd) {
+	try_network_receive();
+
+	if (buffer_.size() > peek_pointer_) {
+		if (cmd != nullptr) {
+			*cmd = static_cast<RelayCommand>(buffer_[peek_pointer_]);
+		}
+		peek_pointer_++;
+		return true;
+	} else {
+		return false;
+	}
+}
+
+void NetRelayConnection::receive(RelayCommand *out) {
+	try_network_receive();
+
+	peek_reset();
+	assert(!buffer_.empty());
+
+	uint8_t i;
+	receive(&i);
+	*out = static_cast<RelayCommand>(i);
+}
+
+bool NetRelayConnection::peek_uint8_t() {
+	try_network_receive();
+
+	// If there is any byte available, we can read an uint8
+	if (buffer_.size() > peek_pointer_) {
+		peek_pointer_++;
+		return true;
+	} else {
+		return false;
+	}
+}
+
+void NetRelayConnection::receive(uint8_t *out) {
+	try_network_receive();
+
+	peek_reset();
+	assert(!buffer_.empty());
+
+	*out = buffer_.front();
+	buffer_.pop_front();
+}
+
+bool NetRelayConnection::peek_recvpacket() {
+	try_network_receive();
+
+	if (buffer_.size() < peek_pointer_ + 2) {
+		// Not even enough space for the size
+		return false;
+	}
+
+	// RecvPackets have their size coded in their first two bytes
+	const uint16_t size = buffer_[peek_pointer_ + 0] << 8 | buffer_[peek_pointer_ + 1];
+	assert(size >= 2);
+
+	if (buffer_.size() >= peek_pointer_ + size) {
+		peek_pointer_ += size;
+		return true;
+	} else {
+		return false;
+	}
+}
+
+void NetRelayConnection::receive(RecvPacket *packet) {
+	try_network_receive();
+
+	peek_reset();
+	#ifdef NDEBUG
+	assert(peek_recvpacket());
+	peek_reset();
+	#endif // NDEBUG
+
+	// Read the packet (adapted copy from Deserializer)
+	const uint16_t size = buffer_[0] << 8 | buffer_[1];
+	assert(size >= 2);
+	assert(buffer_.size() >= size);
+
+	packet->buffer.clear();
+	packet->buffer.insert(packet->buffer.end(), buffer_.begin() + 2, buffer_.begin() + size);
+	packet->index_ = 0;
+
+	buffer_.erase(buffer_.begin(), buffer_.begin() + size);
+}
+
+bool NetRelayConnection::try_network_receive() {
+	if (!is_connected()) {
+		return false;
+	}
+
+	unsigned char buffer[kNetworkBufferSize];
+	boost::system::error_code ec;
+	size_t length = socket_.read_some(boost::asio::buffer(buffer, kNetworkBufferSize), ec);
+	if (!ec) {
+		assert(length > 0);
+		assert(length <= kNetworkBufferSize);
+		// Has read something
+		for (size_t i = 0; i < length; ++i) {
+			buffer_.push_back(buffer[i]);
+		}
+	}
+
+	if (ec && ec != boost::asio::error::would_block) {
+		// Connection closed or some error, close the socket
+		log("[NetRelayConnection] Error when trying to receive some data: %s.\n", ec.message().c_str());
+		close();
+		return false;
+	}
+	return true;
+}
+
+void NetRelayConnection::peek_reset() {
+	peek_pointer_ = 0;
+}
+
+void NetRelayConnection::send(const RelayCommand data) {
+	send(static_cast<uint8_t>(data));
+}
+
+void NetRelayConnection::send(const uint8_t data) {
+	if (!is_connected()) {
+		return;
+	}
+
+	uint8_t buf[1];
+	buf[0] = data;
+
+	boost::system::error_code ec;
+#ifdef NDEBUG
+	boost::asio::write(socket_, boost::asio::buffer(buf, 1), ec);
+#else
+	size_t written =
+	   boost::asio::write(socket_, boost::asio::buffer(buf, 1), ec);
+#endif
+
+	// TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold
+	// If it doesn't, set the socket to blocking before writing
+	// If it does, remove this comment after build 20
+	assert(ec != boost::asio::error::would_block);
+	assert(written == 1 || ec);
+	if (ec) {
+		log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
+		close();
+	}
+}
+
+void NetRelayConnection::send(const std::string& str) {
+	if (!is_connected()) {
+		return;
+	}
+
+	// Append \0
+	std::vector<boost::asio::const_buffer> buffers;
+	buffers.push_back(boost::asio::buffer(str));
+	buffers.push_back(boost::asio::buffer("\0", 1));
+
+	boost::system::error_code ec;
+#ifdef NDEBUG
+	boost::asio::write(socket_, buffers, ec);
+#else
+	size_t written =
+	   boost::asio::write(socket_, buffers, ec);
+#endif
+
+	assert(ec != boost::asio::error::would_block);
+	assert(written == str.length() + 1 || ec);
+	if (ec) {
+		log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
+		close();
+	}
+}
+
+void NetRelayConnection::send(const SendPacket& packet) {
+	if (!is_connected()) {
+		return;
+	}
+
+	boost::system::error_code ec;
+#ifdef NDEBUG
+	boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
+#else
+	size_t written =
+	   boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
+#endif
+
+	assert(ec != boost::asio::error::would_block);
+	assert(written == packet.get_size() || ec);
+	if (ec) {
+		log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
+		close();
+	}
+}
+
+NetRelayConnection::NetRelayConnection(const NetAddress& host)
+   : io_service_(), socket_(io_service_), buffer_(), peek_pointer_(0) {
+
+	assert(host.is_valid());
+	const boost::asio::ip::tcp::endpoint destination(host.ip, host.port);
+
+	log("[NetRelayConnection]: Trying to connect to %s:%u ... ", host.ip.to_string().c_str(), host.port);
+	boost::system::error_code ec;
+	socket_.connect(destination, ec);
+	if (!ec && is_connected()) {
+		log("success.\n");
+		socket_.non_blocking(true);
+	} else {
+		log("failed.\n");
+		socket_.close();
+		assert(!is_connected());
+	}
+}

=== added file 'src/network/netrelayconnection.h'
--- src/network/netrelayconnection.h	1970-01-01 00:00:00 +0000
+++ src/network/netrelayconnection.h	2017-10-17 20:59:13 +0000
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2008-2017 by the Widelands Development Team
+ *
+ * This program 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_NETWORK_NETRELAYCONNECTION_H
+#define WL_NETWORK_NETRELAYCONNECTION_H
+
+#include <memory>
+
+#include "network/network.h"
+#include "network/relay_protocol.h"
+
+/**
+ * A wrapper around a network connection to the netrelay.
+ * Does not contain logic but provides a buffer to read
+ * uint8_t / std::string / SendPacket from the network stream.
+ *
+ * Additionally, the peek_* methods allow to check the contents of the buffer. They will not remove any bytes,
+ * but will check whether the required value can be read. After checking, an internal peek-pointer will be
+ * incremented. The next call of a peek_* method will then start reading after the previous value.
+ * Calling peek_reset() will restore the pointer to the start of the buffer. Only a successful peek will
+ * move the pointer.
+ * Note that a successful peek does not mean that the received bytes really are of the requested type, it only
+ * means they could be interpreted that way. Whether the type matches is in the responsibility of the caller.
+ * The idea of this methods is that the caller can check whether a complete relay command can be received
+ * before starting to remove data from the buffer. Otherwise, the caller would have to maintain an own buffer.
+ */
+class NetRelayConnection {
+public:
+	/**
+	 * Tries to establish a connection to the given relay.
+	 * \param host The host to connect to.
+	 * \return A pointer to a connected \c NetRelayConnection object or a \c nullptr.
+	 */
+	static std::unique_ptr<NetRelayConnection> connect(const NetAddress& host);
+
+	/**
+	 * Closes the connection.
+	 * If you want to send a goodbye-message to the relay, do so before freeing the object.
+	 */
+	~NetRelayConnection();
+
+	/**
+	 * Returns whether the relay is connected.
+	 * \return \c true if the connection is open, \c false otherwise.
+	 */
+	bool is_connected() const;
+
+	/**
+	 * Closes the connection.
+	 * If you want to send a goodbye-message to the relay, do so before calling this.
+	 */
+	void close();
+
+	/**
+	 * Checks whether a relay command can be read from the buffer.
+	 * This method does not modify the buffer contents but increases the peek-pointer.
+	 * \param cmd The command that will be returned next. It will not be removed from the input queue!
+	 *            Can be nullptr.
+	 * \return \c True if the value can be read, \c false if not enough data has been received.
+	 */
+	bool peek_cmd(RelayCommand *cmd = nullptr);
+
+	/**
+	 * Checks whether an uint8_t can be read from the buffer.
+	 * This method does not modify the buffer contents but increases the peek-pointer.
+	 * \return \c True if the value can be read, \c false if not enough data has been received.
+	 */
+	bool peek_uint8_t();
+
+	/**
+	 * Checks whether a std::string can be read from the buffer.
+	 * This method does not modify the buffer contents but increases the peek-pointer.
+	 * \return \c True if the value can be read, \c false if not enough data has been received.
+	 */
+	bool peek_string();
+
+	/**
+	 * Checks whether a RecvPacket can be read from the buffer.
+	 * This method does not modify the buffer contents but increases the peek-pointer.
+	 * \return \c True if the value can be read, \c false if not enough data has been received.
+	 */
+	bool peek_recvpacket();
+
+	/**
+	 * Resets the peek pointer to the beginning of the buffer.
+	 */
+	void peek_reset();
+
+	/**
+	 * Receive a command.
+	 * \warning Calling this method is only safe when peek_cmd() returned \c true.
+	 *          Otherwise the behavior of this method is undefined.
+	 *
+	 * Calling this methods resets the peek-pointer.
+	 * \param out The variable to write the value to.
+	 */
+	void receive(RelayCommand *out);
+
+	/**
+	 * Receive an uint8_t.
+	 * \warning Calling this method is only safe when peek_uint8_t() returned \c true.
+	 *          Otherwise the behavior of this method is undefined.
+	 *
+	 * Calling this methods resets the peek-pointer.
+	 * \param out The variable to write the value to.
+	 */
+	void receive(uint8_t *out);
+
+	/**
+	 * Receive a string.
+	 * \warning Calling this method is only safe when peek_string() returned \c true.
+	 *          Otherwise the behavior of this method is undefined.
+	 *
+	 * Calling this methods resets the peek-pointer.
+	 * \param out The variable to write the value to.
+	 */
+	void receive(std::string *out);
+
+	/**
+	 * Receive a RecvPacket.
+	 * \warning Calling this method is only safe when peek_recvpacket() returned \c true.
+	 *          Otherwise the behavior of this method is undefined.
+	 *
+	 * Calling this methods resets the peek-pointer.
+	 * \param out The variable to write the value to.
+	 */
+	void receive(RecvPacket *out);
+
+	/**
+	 * Sends a relay command.
+	 * Calling this on a closed connection will silently fail.
+	 * \param data The data to send.
+	 */
+	void send(RelayCommand data);
+
+	/**
+	 * Sends an uint8_t.
+	 * Calling this on a closed connection will silently fail.
+	 * \param data The data to send.
+	 */
+	void send(uint8_t data);
+
+	/**
+	 * Sends a string.
+	 * Calling this on a closed connection will silently fail.
+	 * \param data The data to send.
+	 */
+	void send(const std::string& data);
+
+	/**
+	 * Sends a packet.
+	 * Calling this on a closed connection will silently fail.
+	 * \param data The data to send.
+	 */
+	void send(const SendPacket& data);
+
+private:
+	/**
+	 * Tries to establish a connection to the given host.
+	 * If the connection attempt failed, is_connected() will return \c false.
+	 * \param host The host to connect to.
+	 */
+	explicit NetRelayConnection(const NetAddress& host);
+
+	/**
+	 * Reads data from network.
+	 * \return \c False if an error occurred.
+	 */
+	bool try_network_receive();
+
+	/// An io_service needed by boost.asio. Primarily needed for asynchronous operations.
+	boost::asio::io_service io_service_;
+
+	/// The socket that connects us to the relay.
+	boost::asio::ip::tcp::socket socket_;
+
+	/// Buffer for arriving data. We need to store it until we have enough to return the required type.
+	std::deque<unsigned char> buffer_;
+
+	/// The position of the next peek.
+	size_t peek_pointer_;
+};
+
+#endif  // end of include guard: WL_NETWORK_NETRELAYCONNECTION_H

=== modified file 'src/network/network.cc'
--- src/network/network.cc	2017-07-05 19:21:57 +0000
+++ src/network/network.cc	2017-10-17 20:59:13 +0000
@@ -171,6 +171,8 @@
 	if (buffer.empty()) {
 		buffer.push_back(0);  //  this will finally be the length of the packet
 		buffer.push_back(0);
+		// Attention! These bytes are also used by the network relay protocol.
+		// So if they are removed the protocol has to be updated
 	}
 
 	for (size_t idx = 0; idx < size; ++idx)
@@ -200,6 +202,17 @@
 
 /*** class RecvPacket ***/
 
+RecvPacket::RecvPacket(RecvPacket&& other) {
+	index_ = other.index_;
+	buffer.swap(other.buffer);
+}
+
+RecvPacket& RecvPacket::operator=(RecvPacket&& other) {
+	index_ = other.index_;
+	buffer.swap(other.buffer);
+	return *this;
+}
+
 size_t RecvPacket::data(void* const packet_data, size_t const bufsize) {
 	if (index_ + bufsize > buffer.size())
 		throw wexception("Packet too short");

=== modified file 'src/network/network.h'
--- src/network/network.h	2017-08-09 17:55:34 +0000
+++ src/network/network.h	2017-10-17 20:59:13 +0000
@@ -166,11 +166,15 @@
  */
 struct RecvPacket : public StreamRead {
 public:
+	RecvPacket() = default;
+	RecvPacket(RecvPacket&& other);
+	RecvPacket& operator=(RecvPacket&& other);
 	size_t data(void* data, size_t bufsize) override;
 	bool end_of_file() const override;
 
 private:
 	friend class Deserializer;
+	friend class NetRelayConnection;
 	std::vector<uint8_t> buffer;
 	size_t index_ = 0U;
 };

=== added file 'src/network/relay_protocol.h'
--- src/network/relay_protocol.h	1970-01-01 00:00:00 +0000
+++ src/network/relay_protocol.h	2017-10-17 20:59:13 +0000
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2012-2017 by the Widelands Development Team
+ *
+ * This program 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_NETWORK_RELAY_PROTOCOL_H
+#define WL_NETWORK_RELAY_PROTOCOL_H
+
+/**
+ * Before the internet gaming relay was added, (a) NetHost and (multiple) NetClient established a direct
+ * connection and exchanged data packets created and processed by the GameHost/GameClient. With the
+ * introduction of the relay this changes: GameHost/GameClient still create and process the data packets.
+ * For LAN games, they still pass them to the NetHost/NetClient.
+ * For internet games, the traffic is relayed by the metaserver. GameHost/GameClient pass their packets to
+ * the new classes NetHostProxy/NetClientProxy. Those no longer have a direct connection but are both
+ * connected to the relay on the metaserver. When they want to exchange messages, they send their packets
+ * to the relay which forwards them to the intended recipient.
+ * The relay only transport the packets, it does not run any game logic. The idea of this is that the
+ * relay runs on an globally reachable computer (i.e. the one of the metaserver) and simplifies
+ * connectivity for the users (i.e. no more port forwarding required).
+ *
+ * Below are the command codes used in the relay protocol. They are used on the connection
+ * between NetHostProxy/NetClientProxy and relay.
+ *
+ * An example of a typical session:
+ * 1) A user wants to start an internet game. The Widelands instance tells the metaserver about it.
+ * 2) A relay instance is started by the metaserver. On startup of the relay, the nonce of the player is set
+ *    as password for the game-host position in the new game. Additionally, the name of the hosted game
+ *    is stored. The IP address of the relay is send to the Widelands instance by the metaserver.
+ * 3) The Widelands instance of the user connects to the relay.
+ * 4) Now there is a reachable relay/game running somewhere. The open game can be listed in the lobby.
+ * 5) Clients get the address and port of the relay by the metaserver and can connect to the game.
+ *    When enough clients have connected, the GameHost can start the game.
+ * 6) When a client wants to send a packet (e.g. user input) to the GameHost, its GameClient packs the packet,
+ *    passes it to the local NetClientProxy which sends it to the relay. The relay relays the packet to the
+ *    NetHostProxy which passes it to the GameHost.
+ * 7) When the GameHost wants to send a packet to one or all clients, it also packs it and passes it to its
+ *    NetHostProxy which sends it to the relay, where it is forwarded to the client(s).
+ */
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * CONSTANTS
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/**
+ * The current version of the network protocol.
+ * Protocol versions must match on all systems.
+ * Used versions:
+ * 1: Initial version between build 19 and build 20
+ */
+constexpr uint8_t kRelayProtocolVersion = 1;
+
+/**
+ * The commands used by the relay.
+ *
+ * If a command is followed by parameters, they are listed in the description.
+ * Most of these commands do not require any reaction by the receiver.
+ * Exceptions are described at the respective commands.
+ *
+ * Parameter types:
+ *
+ * Strings are NULL-terminated.
+ *
+ * Transmitting data packets:
+ *
+ * The main work of the relay consists of passing unstructured data packets between GameHost
+ * and GameClients. They send SendPacket's over the network and expect to receive RecvPacket's
+ * (see network.h). Those packets basically consists of an array of uint8_t with the first two bytes
+ * coding the length of the packet.
+ * When in the following documentation the \c packet datatype is listed as payload data, such a packet
+ * is meant. Since they are already containing their own length, no explicit length field or separator
+ * is required to pack them. When sending they are just written out. When receiving, the Deserializer
+ * has to read the length first and read the appropriate amount of bytes afterwards.
+ */
+/**
+ * This protocol is a binary protocol (as in: not human readable). The receiver reads only the first byte
+ * of the received data to determine which command has been send. Based on that, it knows whether to expect
+ * a string / an uint8 / a SendPacket. Based on this, it can read until NULL / one byte / as much
+ * as the next two bytes specify. When all parameters have been read the next command starts
+ * without any separator.
+ */
+// If anyone removes a command: Please leave a comment which command with which value was removed
+enum class RelayCommand : uint8_t {
+	// Value 0 should not be used
+
+	/**
+	 * Commands send by/to all participants.
+	 */
+	/// \{
+	/**
+	 * Sent by the NetHostProxy or NetClientProxy on connect to the relay.
+	 * Has the following payload:
+	 * \li unsigned_8: Protocol version.
+	 * \li string:     Game name.
+	 * \li string:     For the host: Password that was set on start of the relay.
+	 *                 For clients/observers: String "client".
+	 *
+	 * Is answered by kWelcome or kDisconnect (if a parameter is wrong/unknown).
+	 */
+	kHello = 1,
+
+	/**
+	 * Send by the relay to answer a kHello command.
+	 * Confirms the successful connection with the relay. In the case of a connecting NetClientProxy,
+	 * this does not mean that it can participate on the game. This decision is up to the GameHost as
+	 * soon as it learns about the new client.
+	 * Payload:
+	 * \li unsigned_8: The protocol version. Should be the same as the one send in the kHello.
+	 * \li string:     The name of the hosted game as shown in the internet lobby.
+	 *                 The client might want to check this.
+	 *
+	 * This command and its parameters are not really necessary. But it might be nice to have at least some
+	 * confirmation that we have a connection to a (and the right) relay and not to some other server.
+	 */
+	kWelcome = 2,
+
+	/**
+	 * Can be sent by any participant.
+	 * Might be the result of a protocol violation, an invalid password on connect of the NetHostProxy
+	 * or a regular disconnect (e.g. the game is over).
+	 * After sending or receiving this command, the TCP connection should be closed.
+	 * \note When the game host sends its kDisconnect message, the relay will shut down.
+	 * Payload:
+	 * \li string: An error code describing the reason of the disconnect. Valid values:
+	 *             NORMAL: Regular disconnect (game has ended, client leaves, ...);
+	 *             RELAY_SHUTDOWN: Relay shuts down for some reason;
+	 *             PROTOCOL_VIOLATION: Some protocol error (unknown command, invalid parameters, ...);
+	 *             WRONG_VERSION: The version in the kHello packet is not supported;
+	 *             GAME_UNKNOWN: Game name provided in kHello packet is unknown;
+	 *             NO_HOST: No host is connected to the relay yet;
+	 *             INVALID_CLIENT: Host tried to send a message to a non-existing client
+	 */
+	kDisconnect = 3,
+	/// \}
+
+	/**
+	 * Communication between relay and NetHostProxy.
+	 */
+	/// \{
+
+	/**
+	 * Send by the relay to the NetHostProxy to inform that a client established a connection to the relay.
+	 * Payload:
+	 * \li unsigned_8: An id to represent the new client.
+	 */
+	kConnectClient = 11,
+
+	/**
+	 * If send by the NetHostProxy, tells the relay to close the connection to a client.
+	 * If send by the relay, informs the NetHostProxy that the connection to a client has been lost.
+	 * Payload:
+	 * \li unsigned_8: The id of the client.
+	 */
+	kDisconnectClient = 12,
+
+	/**
+	 * The NetHostProxy sends a message to a connected client over the relay.
+	 * Payload:
+	 * \li NULL terminated list of unsigned_8: The ids of the clients.
+	 * \li packet: The SendPacket to relay.
+	 */
+	kToClients = 13,
+
+	/**
+	 * The relay transmits a packet from a client to the NetHostProxy.
+	 * Payload:
+	 * \li unsigned_8: The id of the client.
+	 * \li packet: The SendPacket to relay.
+	 */
+	kFromClient = 14,
+
+	/**
+	 * The relay sends this message to check for the presence of the NetHostProxy.
+	 * Any message is acceptable as response.
+	 */
+	kPing = 15,
+
+	/**
+	 * The NetHostProxy replies with this message to a kPing when it has nothing
+	 * else to talk about.
+	 */
+	kPong = 16,
+	/// \}
+
+	/**
+	 * Communication between relay and NetHostProxy.
+	 */
+	/// \{
+
+	/**
+	 * The NetClientProxy sends a message to the NetHostProxy over the relay.
+	 * Direct communication between clients is not supported.
+	 * Payload:
+	 * \li packet: The SendPacket to relay.
+	 */
+	kToHost = 21,
+
+	/**
+	 * The relay transmits a packet from a NetHostProxy to the NetClientProxy.
+	 * Payload:
+	 * \li packet: The SendPacket to relay.
+	 */
+	kFromHost = 22
+	/// \}
+};
+
+#endif  // end of include guard: WL_NETWORK_RELAY_PROTOCOL_H

=== modified file 'src/ui_fsmenu/internet_lobby.cc'
--- src/ui_fsmenu/internet_lobby.cc	2017-06-20 19:55:32 +0000
+++ src/ui_fsmenu/internet_lobby.cc	2017-10-17 20:59:13 +0000
@@ -349,30 +349,40 @@
 	}
 }
 
+bool FullscreenMenuInternetLobby::wait_for_ip() {
+	// Wait until the metaserver provided us with an IP address
+	uint32_t const secs = time(nullptr);
+	while (!InternetGaming::ref().ips().first.is_valid()) {
+		InternetGaming::ref().handle_metaserver_communication();
+		// give some time for the answer + for a relogin, if a problem occurs.
+		if ((INTERNET_GAMING_TIMEOUT * 5 / 3) < time(nullptr) - secs) {
+			// Show a popup warning message
+			const std::string warning(
+			   _("Widelands was unable to get the IP address of the server in time. "
+				 "There seems to be a network problem, either on your side or on the side "
+				 "of the server.\n"));
+			UI::WLMessageBox mmb(this, _("Connection timed out"), warning,
+								 UI::WLMessageBox::MBoxType::kOk, UI::Align::kLeft);
+			mmb.run<UI::Panel::Returncodes>();
+			InternetGaming::ref().set_error();
+			return false;
+		}
+	}
+	return true;
+}
+
 /// called when the 'join game' button was clicked
 void FullscreenMenuInternetLobby::clicked_joingame() {
 	if (opengames_list_.has_selection()) {
 		InternetGaming::ref().join_game(opengames_list_.get_selected().name);
 
-		uint32_t const secs = time(nullptr);
-		while (!InternetGaming::ref().ips().first.is_valid()) {
-			InternetGaming::ref().handle_metaserver_communication();
-			// give some time for the answer + for a relogin, if a problem occurs.
-			if ((INTERNET_GAMING_TIMEOUT * 5 / 3) < time(nullptr) - secs) {
-				// Show a popup warning message
-				const std::string warning(
-				   _("Widelands was unable to get the IP address of the server in time.\n"
-				     "There seems to be a network problem, either on your side or on the side\n"
-				     "of the server.\n"));
-				UI::WLMessageBox mmb(this, _("Connection timed out"), warning,
-				                     UI::WLMessageBox::MBoxType::kOk, UI::Align::kLeft);
-				mmb.run<UI::Panel::Returncodes>();
-				return InternetGaming::ref().set_error();
-			}
+		if (!wait_for_ip()) {
+			return;
 		}
 		const std::pair<NetAddress, NetAddress>& ips = InternetGaming::ref().ips();
 
-		GameClient netgame(ips, InternetGaming::ref().get_local_clientname(), true);
+		GameClient netgame(ips, InternetGaming::ref().get_local_clientname(),
+							true, opengames_list_.get_selected().name);
 		netgame.run();
 	} else
 		throw wexception("No server selected! That should not happen!");
@@ -396,6 +406,16 @@
 
 	// Start the game
 	try {
+
+		// Tell the metaserver about it
+		InternetGaming::ref().open_game();
+
+		// Wait for his response with the IPs of the relay server
+		if (!wait_for_ip()) {
+			return;
+		}
+
+		// Start our relay host
 		GameHost netgame(InternetGaming::ref().get_local_clientname(), true);
 		netgame.run();
 	} catch (...) {

=== modified file 'src/ui_fsmenu/internet_lobby.h'
--- src/ui_fsmenu/internet_lobby.h	2017-01-25 18:55:59 +0000
+++ src/ui_fsmenu/internet_lobby.h	2017-10-17 20:59:13 +0000
@@ -75,6 +75,7 @@
 	void server_doubleclicked();
 
 	void change_servername();
+	bool wait_for_ip();
 	void clicked_joingame();
 	void clicked_hostgame();
 


Follow ups