← Back to team overview

widelands-dev team mailing list archive

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

 

Notabilis has proposed merging lp:~widelands-dev/widelands/net-internetgaming-ipv6 into lp:widelands.

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-internetgaming-ipv6/+merge/326027

Updated the metaserver connection to support IPv6. Clients register both their IPv4 and IPv6 addresses with the metaserver now, allowing other clients to join their games with both protocols.

Warning: The related branch for the metaserver has to be merged first. IPv6 on the metaserver host is not necessarily required, it should work fine only using IPv4 (the IPv4 part, that is. IPv6 over the metaserver will not work). But the metaserver software has to be updated or clients with this branch are no longer able to connect. The related branch is:
https://code.launchpad.net/~widelands-dev/widelands-metaserver/ipv6

For testing, I should be able to set up a private metaserver with IPv4 and IPv6. However, since I only have changing IP addresses, I would have to set this up shortly before the test.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/net-internetgaming-ipv6 into lp:widelands.
=== modified file 'src/network/gameclient.cc'
--- src/network/gameclient.cc	2017-06-15 15:45:01 +0000
+++ src/network/gameclient.cc	2017-06-20 20:42:27 +0000
@@ -88,9 +88,14 @@
 	std::vector<ChatMessage> chatmessages;
 };
 
-GameClient::GameClient(const NetAddress& host, const std::string& playername, bool internet)
+GameClient::GameClient(const std::pair<NetAddress, NetAddress>& host, const std::string& playername, bool internet)
    : d(new GameClientImpl), internet_(internet) {
-	d->net = NetClient::connect(host);
+
+	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 (!d->net || !d->net->is_connected()) {
 		throw WLWarning(_("Could not establish connection to host"),
 		                _("Widelands could not establish a connection to the given address. "

=== modified file 'src/network/gameclient.h'
--- src/network/gameclient.h	2017-06-15 15:45:01 +0000
+++ src/network/gameclient.h	2017-06-20 20:42:27 +0000
@@ -40,7 +40,7 @@
                     public GameSettingsProvider,
                     private SyncCallback,
                     public ChatProvider {
-	GameClient(const NetAddress& host, const std::string& playername, bool internet = false);
+	GameClient(const std::pair<NetAddress, NetAddress>& host, const std::string& playername, bool internet = false);
 
 	virtual ~GameClient();
 

=== modified file 'src/network/internet_gaming.cc'
--- src/network/internet_gaming.cc	2017-06-04 16:01:13 +0000
+++ src/network/internet_gaming.cc	2017-06-20 20:42:27 +0000
@@ -19,6 +19,8 @@
 
 #include "network/internet_gaming.h"
 
+#include <memory>
+
 #include <boost/format.hpp>
 #include <boost/lexical_cast.hpp>
 
@@ -28,6 +30,7 @@
 #include "base/warning.h"
 #include "io/fileread.h"
 #include "io/filesystem/layered_filesystem.h"
+#include "network/constants.h"
 #include "network/internet_gaming_messages.h"
 
 /// Private constructor by purpose: NEVER call directly. Always call InternetGaming::ref(), this
@@ -39,7 +42,7 @@
      reg_(false),
      port_(INTERNET_GAMING_PORT),
      clientrights_(INTERNET_CLIENT_UNREGISTERED),
-     gameip_(""),
+     gameips_(),
      clientupdateonmetaserver_(true),
      gameupdateonmetaserver_(true),
      clientupdate_(false),
@@ -66,7 +69,7 @@
 	clientname_ = "";
 	clientrights_ = INTERNET_CLIENT_UNREGISTERED;
 	gamename_ = "";
-	gameip_ = "";
+	gameips_ = std::make_pair(NetAddress(), NetAddress());
 	clientupdateonmetaserver_ = true;
 	gameupdateonmetaserver_ = true;
 	clientupdate_ = false;
@@ -97,13 +100,18 @@
 	log("InternetGaming: Connecting to the metaserver.\n");
 	NetAddress addr;
 	net.reset();
-	if (NetAddress::resolve_to_v4(&addr, meta_, port_))
-		net = NetClient::connect(addr);
-	if (!net || !net->is_connected())
+	if (NetAddress::resolve_to_v6(&addr, meta_, port_)) {
+		net = NetClient::connect(addr);
+	}
+	if ((!net || !net->is_connected()) && NetAddress::resolve_to_v4(&addr, meta_, port_)) {
+		net = NetClient::connect(addr);
+	}
+	if (!net || !net->is_connected()) {
 		throw WLWarning(_("Could not establish connection to host"),
 		                _("Widelands could not establish a connection to the given address.\n"
 		                  "Either there was no metaserver running at the supposed port or\n"
 		                  "your network setup is broken."));
+	}
 
 	// Of course not 100% true, but we just care about an answer at all, so we reset this tracker
 	lastping_ = time(nullptr);
@@ -122,6 +130,16 @@
 	meta_ = meta;
 	port_ = port;
 
+	// If we are not connecting to a registered account, create a random value
+	// to send as password. Used so the metaserver can match our IPv4 and IPv6 connections
+	if (!reg_) {
+		// Admittedly this is a pretty stupid generator. But it should be fine for us
+        static const char random_chars[] = "0123456789ABCDEF";
+		pwd_ = "";
+		while (pwd_.length() < 8)
+			pwd_.push_back(random_chars[rand() % (sizeof(random_chars) - 1)]);
+	}
+
 	initialize_connection();
 
 	// If we are here, a connection was established and we can send our login package through the
@@ -132,9 +150,8 @@
 	s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));
 	s.string(nick);
 	s.string(build_id());
-	s.string(bool2str(reg));
-	if (reg)
-		s.string(pwd);
+	s.string(bool2str(reg_));
+	s.string(pwd_);
 	net->send(s);
 
 	// Now let's see, whether the metaserver is answering
@@ -149,6 +166,9 @@
 				format_and_add_chat(
 				   "", "", true, _("For hosting a game, please take a look at the notes at:"));
 				format_and_add_chat("", "", true, "http://wl.widelands.org/wiki/InternetGaming";);
+
+				// Try to establish a second connection to tell the metaserver about our IPv4 address
+				create_second_connection();
 				return true;
 			} else if (error())
 				return false;
@@ -176,8 +196,7 @@
 	s.string(clientname_);
 	s.string(build_id());
 	s.string(bool2str(reg_));
-	if (reg_)
-		s.string(pwd_);
+	s.string(pwd_);
 	net->send(s);
 
 	// Now let's see, whether the metaserver is answering
@@ -200,6 +219,8 @@
 		return false;
 	}
 
+	create_second_connection();
+
 	// Client is reconnected, so let's try resend the timeouted command.
 	if (waitcmd_ == IGPCMD_GAME_CONNECT)
 		join_game(gamename_);
@@ -334,6 +355,40 @@
 	}
 }
 
+void InternetGaming::create_second_connection() {
+	// TODO(Notabilis): This method could probably be executed by a separate thread. Do we want to do so?
+	// Of course, we would have to be carefully that the main thread might call disconnect while doing so
+	NetAddress addr;
+	net->get_remote_address(&addr);
+	if (!addr.is_ipv6()) {
+		// Primary connection already is IPv4, abort
+		return;
+	}
+
+	if (!NetAddress::resolve_to_v4(&addr, meta_, port_)) {
+		// Could not get the IPv4 address of the metaserver? Strange :-/
+		return;
+	}
+
+	std::unique_ptr<NetClient> tmpNet = NetClient::connect(addr);
+
+	if (!tmpNet || !tmpNet->is_connected()) {
+		// Connecting by IPv4 doesn't work? Well, nothing to do then
+		return;
+	}
+
+	// Okay, we have a connection. Send the login message and terminate the connection
+	SendPacket s;
+	s.string(IGPCMD_TELL_IP);
+	s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));
+	s.string(clientname_);
+	s.string(pwd_);
+	tmpNet->send(s);
+
+	// Close the connection
+	tmpNet->close();
+}
+
 /// Handle one packet received from the metaserver.
 void InternetGaming::handle_packet(RecvPacket& packet) {
 	std::string cmd = packet.string();
@@ -549,8 +604,12 @@
 			assert(waitcmd_ == IGPCMD_GAME_CONNECT);
 			waitcmd_ = "";
 			// save the received ip, so the client cann connect to the game
-			gameip_ = packet.string();
-			log("InternetGaming: Received ip of the game to join: %s.\n", gameip_.c_str());
+			NetAddress::parse_ip(&gameips_.first, packet.string(), WIDELANDS_PORT);
+			if (packet.string() == "1") {
+				NetAddress::parse_ip(&gameips_.second, packet.string(), WIDELANDS_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());
 		}
 
 		else if (cmd == IGPCMD_GAME_START) {
@@ -598,10 +657,10 @@
 	}
 }
 
-/// \returns the ip of the game the client is on or wants to join (or the client is hosting)
-///          or 0, if no ip available.
-const std::string& InternetGaming::ip() {
-	return gameip_;
+/// \returns Up to two NetAdress with ips of the game the client is on or wants to join
+///          (or the client is hosting) or invalid addresses, if no ip available.
+const std::pair<NetAddress, NetAddress>& InternetGaming::ips() {
+	return gameips_;
 }
 
 /// called by a client to join the game \arg gamename
@@ -666,7 +725,7 @@
 	s.string(IGPCMD_GAME_DISCONNECT);
 	net->send(s);
 
-	gameip_ = "";
+	gameips_ = std::make_pair(NetAddress(), NetAddress());
 	state_ = LOBBY;
 
 	log("InternetGaming: Client announced the disconnect from the game %s.\n", gamename_.c_str());

=== modified file 'src/network/internet_gaming.h'
--- src/network/internet_gaming.h	2017-06-03 05:27:36 +0000
+++ src/network/internet_gaming.h	2017-06-20 20:42:27 +0000
@@ -82,7 +82,7 @@
 	void handle_metaserver_communication();
 
 	// Game specific functions
-	const std::string& ip();
+	const std::pair<NetAddress, NetAddress>& ips();
 	void join_game(const std::string& gamename);
 	void open_game();
 	void set_game_playing();
@@ -146,6 +146,20 @@
 private:
 	InternetGaming();
 
+	/**
+	 * Temporarily creates a second connection to the metaserver.
+	 * If the primary connection is an IPv6 connection, we also try
+	 * an IPv4 connection to tell the metaserver our IP.
+	 * This way, when we host a game later on, the metaserver
+	 * knows how to reach us for both protocol versions.
+	 * The established connection does a login, then the connection is
+	 * immediately closed.
+	 *
+	 * If the primary connection already is IPv4, this method
+	 * does nothing.
+	 */
+	void create_second_connection();
+
 	void handle_packet(RecvPacket& packet);
 	void handle_failed_read();
 
@@ -173,7 +187,7 @@
 
 	/// information of the clients game
 	std::string gamename_;
-	std::string gameip_;
+	std::pair<NetAddress, NetAddress> gameips_;
 
 	/// Metaserver information
 	bool clientupdateonmetaserver_;

=== modified file 'src/network/internet_gaming_protocol.h'
--- src/network/internet_gaming_protocol.h	2017-02-10 15:37:44 +0000
+++ src/network/internet_gaming_protocol.h	2017-06-20 20:42:27 +0000
@@ -30,7 +30,7 @@
  * The current version of the in-game network protocol. Client and metaserver
  * protocol versions must match.
  */
-#define INTERNET_GAMING_PROTOCOL_VERSION 0
+#define INTERNET_GAMING_PROTOCOL_VERSION 1
 
 /**
  * The default timeout time after which the client tries to resend a package or even finally closes
@@ -127,7 +127,8 @@
  * \li string:    client name
  * \li string:    build_id of the client
  * \li string:    whether the client wants to login in to a registered account (0 = false, 1 = true)
- * \li string:    password in clear text - only valid if previous was 1
+ * \li string:    for registered accounts: password in clear text
+ *                for unregistered users a random nonce to recognized the matching IPv4 and IPv6 connections
  *
  * If the metaserver accepts, it replies with a LOGIN command with the following payload:
  * \li string:    client name (might be different to the previously chosen one, if the client did
@@ -162,7 +163,8 @@
  * \li string:    client name - the one the metaserver replied at the first login
  * \li string:    build_id of the client
  * \li string:    whether the client wants to login in to a registered account ("false", "true")
- * \li string:    password in clear text - only valid if previous was 1
+ * \li string:    for registered accounts: password in clear text
+ *                for unregistered users a random nonce to recognized the matching IPv4 and IPv6 connections
  *
  * If the metaserver accepts, it replies with a RELOGIN command without any payload.
  *
@@ -175,6 +177,22 @@
 static const std::string IGPCMD_RELOGIN = "RELOGIN";
 
 /**
+ * Tells the metaserver about a secondary IP address.
+ *
+ * Assuming the client already has a connection over IPv6 and tries to establish a secondary
+ * connection over IPv4, this is the only message send.
+ * It should be send as soon as a connection is established, immediately followed by closing
+ * the connection. No answer from the server should be expected.
+ *
+ * Is sent by the client, with the following payload:
+ * \li string:    protocol version
+ * \li string:    client name - the one the metaserver replied at the first login
+ * \li string:    for registered accounts: password in clear text
+ *                for unregistered users the random nonce used on login
+ */
+static const std::string IGPCMD_TELL_IP = "TELL_IP";
+
+/**
  * This command is sent by the metaserver if something went wrong.
  * At least the following payload:
  * \li string:    IGPCMD code of the message that lead to the ERROR message or ERROR
@@ -322,7 +340,9 @@
  *       client finally closes the connection.
  *
  * Sent by the metaserver to acknowledge the connection request and to submit the ip of the game
- * \li string:    ip of the game.
+ * \li string:    primary ip of the game.
+ * \li string:    whether a secondary ip for the game follows (0 = false, 1 = true)
+ * \li string:    secondary ip of the game - only valid if previous was 1
  * \note as soon as this message is sent, the metaserver will list the client as connected to the
  * game.
  */

=== modified file 'src/network/netclient.cc'
--- src/network/netclient.cc	2017-06-10 16:36:29 +0000
+++ src/network/netclient.cc	2017-06-20 20:42:27 +0000
@@ -20,6 +20,15 @@
 		close();
 }
 
+bool NetClient::get_remote_address(NetAddress *addr) const {
+	if (!is_connected())
+		return false;
+	boost::asio::ip::tcp::endpoint remote = socket_.remote_endpoint();
+	addr->ip = remote.address();
+	addr->port = remote.port();
+	return true;
+}
+
 bool NetClient::is_connected() const {
 	return socket_.is_open();
 }
@@ -95,5 +104,7 @@
 		socket_.non_blocking(true);
 	} else {
 		log("failed.\n");
+		socket_.close();
+		assert(!is_connected());
 	}
 }

=== modified file 'src/network/netclient.h'
--- src/network/netclient.h	2017-06-10 16:36:29 +0000
+++ src/network/netclient.h	2017-06-20 20:42:27 +0000
@@ -46,6 +46,14 @@
 	~NetClient();
 
 	/**
+	 * Returns the ip and port of the remote host we are connected to.
+	 * \param addr A pointer to a NetAddress structure to write the address to.
+	 * \return \c True when \c addr has successfully been written to. \c False otherwise,
+	 *   should only happen when the client is not connected.
+	 */
+	bool get_remote_address(NetAddress *addr) const;
+
+	/**
 	 * Returns whether the client is connected.
 	 * \return \c true if the connection is open, \c false otherwise.
 	 */

=== modified file 'src/ui_fsmenu/internet_lobby.cc'
--- src/ui_fsmenu/internet_lobby.cc	2017-06-04 16:21:45 +0000
+++ src/ui_fsmenu/internet_lobby.cc	2017-06-20 20:42:27 +0000
@@ -27,7 +27,6 @@
 #include "base/macros.h"
 #include "graphic/graphic.h"
 #include "graphic/text_constants.h"
-#include "network/constants.h"
 #include "network/gameclient.h"
 #include "network/gamehost.h"
 #include "network/internet_gaming.h"
@@ -356,7 +355,7 @@
 		InternetGaming::ref().join_game(opengames_list_.get_selected().name);
 
 		uint32_t const secs = time(nullptr);
-		while (InternetGaming::ref().ip().size() < 1) {
+		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) {
@@ -371,12 +370,9 @@
 				return InternetGaming::ref().set_error();
 			}
 		}
-		std::string ip = InternetGaming::ref().ip();
+		const std::pair<NetAddress, NetAddress>& ips = InternetGaming::ref().ips();
 
-		NetAddress addr;
-		NetAddress::parse_ip(&addr, ip, WIDELANDS_PORT);
-		assert(addr.is_valid());
-		GameClient netgame(addr, InternetGaming::ref().get_local_clientname(), true);
+		GameClient netgame(ips, InternetGaming::ref().get_local_clientname(), true);
 		netgame.run();
 	} else
 		throw wexception("No server selected! That should not happen!");

=== modified file 'src/wlapplication.cc'
--- src/wlapplication.cc	2017-06-10 16:36:29 +0000
+++ src/wlapplication.cc	2017-06-20 20:42:27 +0000
@@ -1177,7 +1177,7 @@
 					break;
 				}
 
-				GameClient netgame(addr, playername);
+				GameClient netgame(std::make_pair(addr, NetAddress()), playername);
 				netgame.run();
 				break;
 			}


Follow ups