widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #10212
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
Notabilis has proposed merging lp:~widelands-dev/widelands/net-boost-asio 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-boost-asio/+merge/324364
Replaced the SDL-Net network code with Boost.Asio. This removes SDL-Net as a required library, unfortunately we now require boost-system.
Also added support for IPv6. As of now, this only works for games in the local network, since the metaserver host does not support IPv6 at the moment.
Warning: This branch is not yet ready for merge. I removed the sdl-net library but I don't know / can't test how to add the boost requirements to the build process for Windows and MacOS. For Linux I only had to add the option to link with boost-system in CMakeList.txt. It would be great if someone could test the other operating systems and, if needed, add the required linking for them. The commit where I removed the sdl-net references is:
http://bazaar.launchpad.net/~widelands-dev/widelands/net-boost-asio/revision/8365
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/net-boost-asio into lp:widelands.
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2017-02-28 08:31:53 +0000
+++ CMakeLists.txt 2017-05-20 19:06:48 +0000
@@ -50,7 +50,8 @@
COMPONENTS
unit_test_framework
regex
- REQUIRED)
+ REQUIRED
+ system)
find_package (PythonInterp REQUIRED)
@@ -60,7 +61,6 @@
find_package(SDL2 REQUIRED)
find_package(SDL2_image REQUIRED)
find_package(SDL2_mixer REQUIRED)
-find_package(SDL2_net REQUIRED)
find_package(SDL2_ttf REQUIRED)
find_package(ZLIB REQUIRED)
find_package(ICU REQUIRED)
=== modified file 'CREDITS'
--- CREDITS 2014-10-13 15:04:50 +0000
+++ CREDITS 2017-05-20 19:06:48 +0000
@@ -10,7 +10,6 @@
libSDL2
libSDL2_mixer
- libSDL2_net
libSDL2_image
libSDL2_ttf
* All files from SDL2-Project
=== modified file 'appveyor.yml'
--- appveyor.yml 2016-10-06 20:34:46 +0000
+++ appveyor.yml 2017-05-20 19:06:48 +0000
@@ -19,7 +19,7 @@
- cmd: "bash --login -c \"pacman -Su --noconfirm\""
- cmd: "bash --login -c \"pacman -Su --noconfirm\""
# Installed required libs
- - cmd: "bash --login -c \"pacman --noconfirm -S mingw-w64-%MINGWSUFFIX%-ninja mingw-w64-%MINGWSUFFIX%-boost mingw-w64-%MINGWSUFFIX%-SDL2_net mingw-w64-%MINGWSUFFIX%-SDL2_ttf mingw-w64-%MINGWSUFFIX%-SDL2_mixer mingw-w64-%MINGWSUFFIX%-SDL2_image mingw-w64-%MINGWSUFFIX%-glbinding\""
+ - cmd: "bash --login -c \"pacman --noconfirm -S mingw-w64-%MINGWSUFFIX%-ninja mingw-w64-%MINGWSUFFIX%-boost mingw-w64-%MINGWSUFFIX%-SDL2_ttf mingw-w64-%MINGWSUFFIX%-SDL2_mixer mingw-w64-%MINGWSUFFIX%-SDL2_image mingw-w64-%MINGWSUFFIX%-glbinding\""
shallow_clone: true
=== removed file 'cmake/Modules/FindSDL2_net.cmake'
--- cmake/Modules/FindSDL2_net.cmake 2014-10-13 15:04:50 +0000
+++ cmake/Modules/FindSDL2_net.cmake 1970-01-01 00:00:00 +0000
@@ -1,88 +0,0 @@
-# - Locate SDL2_net library
-# This module defines:
-# SDL2_NET_LIBRARIES, the name of the library to link against
-# SDL2_NET_INCLUDE_DIRS, where to find the headers
-# SDL2_NET_FOUND, if false, do not try to link against
-# SDL2_NET_VERSION_STRING - human-readable string containing the version of SDL2_net
-#
-# For backward compatiblity the following variables are also set:
-# SDL2NET_LIBRARY (same value as SDL2_NET_LIBRARIES)
-# SDL2NET_INCLUDE_DIR (same value as SDL2_NET_INCLUDE_DIRS)
-# SDL2NET_FOUND (same value as SDL2_NET_FOUND)
-#
-# $SDL2DIR is an environment variable that would
-# correspond to the ./configure --prefix=$SDL2DIR
-# used in building SDL2.
-#
-# Created by Eric Wing. This was influenced by the FindSDL2.cmake
-# module, but with modifications to recognize OS X frameworks and
-# additional Unix paths (FreeBSD, etc).
-
-#=============================================================================
-# Copyright 2005-2009 Kitware, Inc.
-# Copyright 2012 Benjamin Eikel
-#
-# Distributed under the OSI-approved BSD License (the "License");
-# see accompanying file Copyright.txt for details.
-#
-# This software is distributed WITHOUT ANY WARRANTY; without even the
-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# See the License for more information.
-#=============================================================================
-# (To distribute this file outside of CMake, substitute the full
-# License text for the above reference.)
-
-if(NOT SDL2_NET_INCLUDE_DIR AND SDL2NET_INCLUDE_DIR)
- set(SDL2_NET_INCLUDE_DIR ${SDL2NET_INCLUDE_DIR} CACHE PATH "directory cache
-entry initialized from old variable name")
-endif()
-find_path(SDL2_NET_INCLUDE_DIR SDL_net.h
- HINTS
- ENV SDL2NETDIR
- ENV SDL2DIR
- PATH_SUFFIXES include/SDL2 include
-)
-
-if(NOT SDL2_NET_LIBRARY AND SDL2NET_LIBRARY)
- set(SDL2_NET_LIBRARY ${SDL2NET_LIBRARY} CACHE FILEPATH "file cache entry
-initialized from old variable name")
-endif()
-find_library(SDL2_NET_LIBRARY
- NAMES SDL2_net
- HINTS
- ENV SDL2NETDIR
- ENV SDL2DIR
- PATH_SUFFIXES lib
-)
-
-if(SDL2_NET_INCLUDE_DIR AND EXISTS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h")
- file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_NET_MAJOR_VERSION[ \t]+[0-9]+$")
- file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_NET_MINOR_VERSION[ \t]+[0-9]+$")
- file(STRINGS "${SDL2_NET_INCLUDE_DIR}/SDL_net.h" SDL_NET_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_NET_PATCHLEVEL[ \t]+[0-9]+$")
- string(REGEX REPLACE "^#define[ \t]+SDL_NET_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_MAJOR "${SDL_NET_VERSION_MAJOR_LINE}")
- string(REGEX REPLACE "^#define[ \t]+SDL_NET_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_MINOR "${SDL_NET_VERSION_MINOR_LINE}")
- string(REGEX REPLACE "^#define[ \t]+SDL_NET_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL_NET_VERSION_PATCH "${SDL_NET_VERSION_PATCH_LINE}")
- set(SDL2_NET_VERSION_STRING ${SDL_NET_VERSION_MAJOR}.${SDL_NET_VERSION_MINOR}.${SDL_NET_VERSION_PATCH})
- unset(SDL_NET_VERSION_MAJOR_LINE)
- unset(SDL_NET_VERSION_MINOR_LINE)
- unset(SDL_NET_VERSION_PATCH_LINE)
- unset(SDL_NET_VERSION_MAJOR)
- unset(SDL_NET_VERSION_MINOR)
- unset(SDL_NET_VERSION_PATCH)
-endif()
-
-set(SDL2_NET_LIBRARIES ${SDL2_NET_LIBRARY})
-set(SDL2_NET_INCLUDE_DIRS ${SDL2_NET_INCLUDE_DIR})
-
-# include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
-
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_net
- REQUIRED_VARS SDL2_NET_LIBRARIES SDL2_NET_INCLUDE_DIRS
- VERSION_VAR SDL2_NET_VERSION_STRING)
-
-# for backward compatiblity
-set(SDL2NET_LIBRARY ${SDL2_NET_LIBRARIES})
-set(SDL2NET_INCLUDE_DIR ${SDL2_NET_INCLUDE_DIRS})
-set(SDL2NET_FOUND ${SDL2_NET_FOUND})
-
-mark_as_advanced(SDL2_NET_LIBRARY SDL2_NET_INCLUDE_DIR)
=== modified file 'cmake/WlFunctions.cmake'
--- cmake/WlFunctions.cmake 2016-02-06 16:17:23 +0000
+++ cmake/WlFunctions.cmake 2017-05-20 19:06:48 +0000
@@ -12,7 +12,6 @@
USES_SDL2
USES_SDL2_IMAGE
USES_SDL2_MIXER
- USES_SDL2_NET
USES_SDL2_TTF
USES_ZLIB
USES_ICU
@@ -127,11 +126,6 @@
target_link_libraries(${NAME} ${SDL2MIXER_LIBRARY})
endif()
- if(ARG_USES_SDL2_NET)
- wl_include_system_directories(${NAME} ${SDL2NET_INCLUDE_DIR})
- target_link_libraries(${NAME} ${SDL2NET_LIBRARY})
- endif()
-
if(ARG_USES_SDL2_IMAGE)
wl_include_system_directories(${NAME} ${SDL2IMAGE_INCLUDE_DIR})
target_link_libraries(${NAME} ${SDL2IMAGE_LIBRARY})
=== modified file 'src/network/CMakeLists.txt'
--- src/network/CMakeLists.txt 2017-05-07 20:27:21 +0000
+++ src/network/CMakeLists.txt 2017-05-20 19:06:48 +0000
@@ -23,8 +23,6 @@
network_player_settings_backend.cc
network_player_settings_backend.h
network_protocol.h
- network_system.h
- USES_SDL2_NET
DEPENDS
ai
base_exceptions
=== modified file 'src/network/gameclient.cc'
--- src/network/gameclient.cc 2017-05-11 10:45:44 +0000
+++ src/network/gameclient.cc 2017-05-20 19:06:48 +0000
@@ -42,7 +42,6 @@
#include "network/internet_gaming.h"
#include "network/network_gaming_messages.h"
#include "network/network_protocol.h"
-#include "network/network_system.h"
#include "scripting/lua_interface.h"
#include "scripting/lua_table.h"
#include "ui_basic/messagebox.h"
@@ -89,17 +88,13 @@
std::vector<ChatMessage> chatmessages;
};
-GameClient::GameClient(const std::string& host,
- const uint16_t port,
- const std::string& playername,
- bool internet)
+GameClient::GameClient(const NetAddress& host, const std::string& playername, bool internet)
: d(new GameClientImpl), internet_(internet) {
- d->net = NetClient::connect(host, port);
+ d->net = NetClient::connect(host);
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.\n"
- "Either no Widelands server was running at the supposed port or\n"
+ _("Widelands could not establish a connection to the given address. "
+ "Either no Widelands server was running at the supposed port or "
"the server shut down as you tried to connect."));
}
=== modified file 'src/network/gameclient.h'
--- src/network/gameclient.h 2017-05-11 10:45:44 +0000
+++ src/network/gameclient.h 2017-05-20 19:06:48 +0000
@@ -36,13 +36,11 @@
* launch, as well as dealing with the actual network protocol.
*/
struct GameClient : public GameController,
- public GameSettingsProvider,
- private SyncCallback,
- public ChatProvider {
- GameClient(const std::string& host,
- const uint16_t port,
- const std::string& playername,
- bool internet = false);
+ public GameSettingsProvider,
+ private SyncCallback,
+ public ChatProvider {
+ GameClient(const NetAddress& host, const std::string& playername, bool internet = false);
+
virtual ~GameClient();
void run();
=== modified file 'src/network/gamehost.cc'
--- src/network/gamehost.cc 2017-05-11 10:45:44 +0000
+++ src/network/gamehost.cc 2017-05-20 19:06:48 +0000
@@ -54,7 +54,6 @@
#include "network/network_lan_promotion.h"
#include "network/network_player_settings_backend.h"
#include "network/network_protocol.h"
-#include "network/network_system.h"
#include "profile/profile.h"
#include "scripting/lua_interface.h"
#include "ui_basic/progresswindow.h"
=== modified file 'src/network/internet_gaming.cc'
--- src/network/internet_gaming.cc 2017-05-16 18:29:06 +0000
+++ src/network/internet_gaming.cc 2017-05-20 19:06:48 +0000
@@ -95,7 +95,10 @@
void InternetGaming::initialize_connection() {
// First of all try to connect to the metaserver
log("InternetGaming: Connecting to the metaserver.\n");
- net = NetClient::connect(meta_, port_);
+ NetAddress addr;
+ net.reset();
+ if (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"
=== modified file 'src/network/internet_gaming.h'
--- src/network/internet_gaming.h 2017-05-09 19:17:12 +0000
+++ src/network/internet_gaming.h 2017-05-20 19:06:48 +0000
@@ -170,7 +170,7 @@
std::string pwd_;
bool reg_;
std::string meta_;
- uint32_t port_;
+ uint16_t port_;
/// local clients name and rights
std::string clientname_;
=== modified file 'src/network/netclient.cc'
--- src/network/netclient.cc 2017-05-11 10:45:44 +0000
+++ src/network/netclient.cc 2017-05-20 19:06:48 +0000
@@ -4,8 +4,9 @@
#include "base/log.h"
-std::unique_ptr<NetClient> NetClient::connect(const std::string& ip_address, const uint16_t port) {
- std::unique_ptr<NetClient> ptr(new NetClient(ip_address, port));
+std::unique_ptr<NetClient> NetClient::connect(const NetAddress& host) {
+
+ std::unique_ptr<NetClient> ptr(new NetClient(host));
if (ptr->is_connected()) {
return ptr;
} else {
@@ -17,20 +18,18 @@
NetClient::~NetClient() {
if (is_connected())
close();
- if (sockset_ != nullptr)
- SDLNet_FreeSocketSet(sockset_);
}
bool NetClient::is_connected() const {
- return sock_ != nullptr;
+ return socket_.is_open();
}
void NetClient::close() {
if (!is_connected())
return;
- SDLNet_TCP_DelSocket(sockset_, sock_);
- SDLNet_TCP_Close(sock_);
- sock_ = nullptr;
+ boost::system::error_code ec;
+ socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
+ socket_.close(ec);
}
bool NetClient::try_receive(RecvPacket* packet) {
@@ -38,41 +37,53 @@
return false;
uint8_t buffer[512];
- while (SDLNet_CheckSockets(sockset_, 0) > 0) {
-
- const int32_t bytes = SDLNet_TCP_Recv(sock_, buffer, sizeof(buffer));
- if (bytes <= 0) {
- // Error while receiving
- close();
- return false;
- }
-
- deserializer_.read_data(buffer, bytes);
+ boost::system::error_code ec;
+ size_t length = socket_.read_some(boost::asio::buffer(buffer, 512), ec);
+ if (!ec) {
+ assert(length > 0);
+ assert(length <= 512);
+ // Has read something
+ deserializer_.read_data(buffer, length);
+ }
+
+ if (ec && ec != boost::asio::error::would_block) {
+ // Connection closed or some error, close the socket
+ close();
+ return false;
}
// Get one packet from the deserializer
return deserializer_.write_packet(packet);
}
void NetClient::send(const SendPacket& packet) {
- if (is_connected()) {
- SDLNet_TCP_Send(sock_, packet.get_data(), packet.get_size());
+ if (!is_connected())
+ return;
+
+ boost::system::error_code ec;
+ size_t written = boost::asio::write(socket_,
+ boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
+ // 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
+ assert(ec != boost::asio::error::would_block);
+ assert(written == packet.get_size() || ec);
+ if (ec) {
+ close();
}
}
-NetClient::NetClient(const std::string& ip_address, const uint16_t port)
- : sock_(nullptr), sockset_(nullptr), deserializer_() {
-
- IPaddress addr;
- if (SDLNet_ResolveHost(&addr, ip_address.c_str(), port) != 0) {
- log("[Client]: Failed to resolve host address %s:%u.\n", ip_address.c_str(), port);
- return;
- }
- log("[Client]: Trying to connect to %s:%u ... ", ip_address.c_str(), port);
- sock_ = SDLNet_TCP_Open(&addr);
- if (is_connected()) {
+NetClient::NetClient(const NetAddress& host)
+ : io_service_(), socket_(io_service_), deserializer_() {
+
+ boost::system::error_code ec;
+ const boost::asio::ip::address address = boost::asio::ip::address::from_string(host.ip, ec);
+ assert(!ec);
+ const boost::asio::ip::tcp::endpoint destination(address, host.port);
+
+ log("[Client]: Trying to connect to %s:%u ... ", host.ip.c_str(), host.port);
+ socket_.connect(destination, ec);
+ if (!ec && is_connected()) {
log("success\n");
- sockset_ = SDLNet_AllocSocketSet(1);
- SDLNet_TCP_AddSocket(sockset_, sock_);
+ socket_.non_blocking(true);
} else {
log("failed\n");
}
=== modified file 'src/network/netclient.h'
--- src/network/netclient.h 2017-05-11 10:45:44 +0000
+++ src/network/netclient.h 2017-05-20 19:06:48 +0000
@@ -22,23 +22,25 @@
#include <memory>
-#include <SDL_net.h>
+#include <boost/asio.hpp>
#include "network/network.h"
/**
* NetClient manages the network connection for a network game in which this computer
* participates as a client.
+ * 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 {
public:
+
/**
* Tries to establish a connection to the given host.
- * @param ip_address A hostname or an IPv4 address as string.
- * @param port The port to connect to.
- * @return A pointer to a connected \c NetClient object or a nullptr if the connection failed.
+ * \param host The host to connect to.
+ * \return A pointer to a connected \c NetClient object or an invalid pointer if the connection failed.
*/
- static std::unique_ptr<NetClient> connect(const std::string& ip_address, const uint16_t port);
+ static std::unique_ptr<NetClient> connect(const NetAddress& host);
/**
* Closes the connection.
@@ -48,7 +50,7 @@
/**
* Returns whether the client is connected.
- * @return \c true if the connection is open, \c false otherwise.
+ * \return \c true if the connection is open, \c false otherwise.
*/
bool is_connected() const;
@@ -60,30 +62,35 @@
/**
* 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.
+ * \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);
+ bool try_receive(RecvPacket *packet);
/**
* Sends a packet.
* Calling this on a closed connection will silently fail.
- * @param packet The packet to send.
+ * \param packet The packet to send.
*/
- void send(const SendPacket& packet);
+ void send(const SendPacket& packet);
private:
- NetClient(const std::string& ip_address, const uint16_t port);
-
- /// The socket that connects us to the host
- TCPsocket sock_;
-
- /// Socket set used for selection
- SDLNet_SocketSet sockset_;
-
- /// Deserializer acts as a buffer for packets (reassembly/splitting up)
+ /**
+ * 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.
+ */
+ NetClient(const NetAddress& host);
+
+ /// An io_service needed by boost.asio. Primary needed for asynchronous operations.
+ boost::asio::io_service io_service_;
+
+ /// The socket that connects us to the host.
+ boost::asio::ip::tcp::socket socket_;
+
+ /// Deserializer acts as a buffer for packets (splitting stream to packets)
Deserializer deserializer_;
};
=== modified file 'src/network/nethost.cc'
--- src/network/nethost.cc 2017-05-11 10:45:44 +0000
+++ src/network/nethost.cc 2017-05-20 19:06:48 +0000
@@ -4,7 +4,7 @@
#include "base/log.h"
-NetHost::Client::Client(TCPsocket sock) : socket(sock), deserializer() {
+NetHost::Client::Client(boost::asio::ip::tcp::socket&& sock) : socket(std::move(sock)), deserializer() {
}
std::unique_ptr<NetHost> NetHost::listen(const uint16_t port) {
@@ -22,11 +22,10 @@
while (!clients_.empty()) {
close(clients_.begin()->first);
}
- SDLNet_FreeSocketSet(sockset_);
}
bool NetHost::is_listening() const {
- return svrsock_ != nullptr;
+ return acceptor_v4_.is_open() || acceptor_v6_.is_open();
}
bool NetHost::is_connected(const ConnectionId id) const {
@@ -34,11 +33,16 @@
}
void NetHost::stop_listening() {
- if (!is_listening())
- return;
- SDLNet_TCP_DelSocket(sockset_, svrsock_);
- SDLNet_TCP_Close(svrsock_);
- svrsock_ = nullptr;
+ boost::system::error_code ec;
+ if (acceptor_v4_.is_open()) {
+ log("[NetHost]: Closing a listening IPv4 socket\n");
+ acceptor_v4_.close(ec);
+ }
+ if (acceptor_v6_.is_open()) {
+ log("[NetHost]: Closing a listening IPv6 socket\n");
+ acceptor_v6_.close(ec);
+ }
+ // Ignore errors
}
void NetHost::close(const ConnectionId id) {
@@ -47,68 +51,136 @@
// Not connected anyway
return;
}
- SDLNet_TCP_DelSocket(sockset_, iter_client->second.socket);
- SDLNet_TCP_Close(iter_client->second.socket);
+ boost::system::error_code ec;
+ iter_client->second.socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
+ iter_client->second.socket.close(ec);
clients_.erase(iter_client);
}
bool NetHost::try_accept(ConnectionId* new_id) {
if (!is_listening())
return false;
+ boost::system::error_code ec;
+ boost::asio::ip::tcp::socket socket(io_service_);
+ if (acceptor_v4_.is_open()) {
+ acceptor_v4_.accept(socket, ec);
+ if (ec == boost::asio::error::would_block) {
+ // No client wants to connect
+ // New socket don't need to be closed since it isn't open yet
+ } else if (ec) {
+ // Some other error, close the acceptor
+ acceptor_v4_.close(ec);
+ } else {
+ log("[NetHost]: Accepting IPv4 connection from %s\n",
+ socket.remote_endpoint().address().to_string().c_str());
+ }
+ }
+ if (acceptor_v6_.is_open() && !socket.is_open()) {
+ // IPv4 did not get a connection
+ acceptor_v6_.accept(socket, ec);
+ if (ec == boost::asio::error::would_block) {
+ ;
+ } else if (ec) {
+ acceptor_v6_.close(ec);
+ } else {
+ log("[NetHost]: Accepting IPv6 connection from %s\n",
+ socket.remote_endpoint().address().to_string().c_str());
+ }
+ }
- TCPsocket sock = SDLNet_TCP_Accept(svrsock_);
- // No client wants to connect
- if (sock == nullptr)
+ if (!socket.is_open()) {
+ // No new connection
return false;
- SDLNet_TCP_AddSocket(sockset_, sock);
+ }
+
+ socket.non_blocking(true);
+
ConnectionId id = next_id_++;
assert(id > 0);
assert(clients_.count(id) == 0);
- clients_.insert(std::make_pair(id, Client(sock)));
+ clients_.insert(std::make_pair(id, Client(std::move(socket))));
assert(clients_.count(id) == 1);
*new_id = id;
return true;
}
bool NetHost::try_receive(const ConnectionId id, RecvPacket* packet) {
-
// Always read all available data into buffers
uint8_t buffer[512];
- while (SDLNet_CheckSockets(sockset_, 0) > 0) {
- for (auto& e : clients_) {
- if (SDLNet_SocketReady(e.second.socket)) {
- const int32_t bytes = SDLNet_TCP_Recv(e.second.socket, buffer, sizeof(buffer));
- if (bytes <= 0) {
- // Error while receiving
- close(e.first);
- // We have to run the for-loop again since we modified the map
- break;
- }
- e.second.deserializer.read_data(buffer, bytes);
- }
+ boost::system::error_code ec;
+ for (auto it = clients_.begin(); it != clients_.end(); ) {
+ size_t length = it->second.socket.read_some(boost::asio::buffer(buffer, 512), ec);
+ if (ec == boost::asio::error::would_block) {
+ // Nothing to read
+ assert(length == 0);
+ ++it;
+ continue;
+ } else if (ec) {
+ assert(length == 0);
+ // Connection closed or some error, close the socket
+ // close() will remove the client from the map so we have to increment the iterator first
+ ConnectionId id_to_remove = it->first;
+ ++it;
+ close(id_to_remove);
+ continue;
}
+ assert(length > 0);
+ assert(length <= 512);
+ // Read something
+ it->second.deserializer.read_data(buffer, length);
+ ++it;
}
// Now check whether there is data for the requested client
if (!is_connected(id))
return false;
- // Get one packet from the deserializer
+ // Try to get one packet from the deserializer
return clients_.at(id).deserializer.write_packet(packet);
}
void NetHost::send(const ConnectionId id, const SendPacket& packet) {
+ boost::system::error_code ec;
if (is_connected(id)) {
- SDLNet_TCP_Send(clients_.at(id).socket, packet.get_data(), packet.get_size());
- }
-}
-
-NetHost::NetHost(const uint16_t port) : svrsock_(nullptr), sockset_(nullptr), next_id_(1) {
-
- IPaddress myaddr;
- SDLNet_ResolveHost(&myaddr, nullptr, port);
- svrsock_ = SDLNet_TCP_Open(&myaddr);
- // Maximal 16 sockets! This mean we can have at most 15 clients_ in our game (+ metaserver)
- sockset_ = SDLNet_AllocSocketSet(16);
+ size_t written = boost::asio::write(clients_.at(id).socket,
+ boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
+ // 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
+ assert(ec != boost::asio::error::would_block);
+ assert(written == packet.get_size() || ec);
+ if (ec) {
+ close(id);
+ }
+ }
+}
+
+NetHost::NetHost(const uint16_t port)
+ : clients_(), next_id_(1), io_service_(), acceptor_v4_(io_service_), acceptor_v6_(io_service_) {
+
+ if (open_acceptor(&acceptor_v4_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))) {
+ log("[NetHost]: Opening a listening IPv4 socket on port %u\n", port);
+ }
+ if (open_acceptor(&acceptor_v6_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), port))) {
+ log("[NetHost]: Opening a listening IPv6 socket on port %u\n", port);
+ }
+}
+
+bool NetHost::open_acceptor(boost::asio::ip::tcp::acceptor *acceptor,
+ const boost::asio::ip::tcp::endpoint& endpoint) {
+ try {
+ acceptor->open(endpoint.protocol());
+ acceptor->non_blocking(true);
+ const boost::asio::socket_base::reuse_address option_reuse(true);
+ acceptor->set_option(option_reuse);
+ if (endpoint.protocol() == boost::asio::ip::tcp::v6()) {
+ const boost::asio::ip::v6_only option_v6only(true);
+ acceptor->set_option(option_v6only);
+ }
+ acceptor->bind(endpoint);
+ acceptor->listen(boost::asio::socket_base::max_connections);
+ return true;
+ } catch (const boost::system::system_error&) {
+ return false;
+ }
}
=== modified file 'src/network/nethost.h'
--- src/network/nethost.h 2017-05-11 10:45:44 +0000
+++ src/network/nethost.h 2017-05-20 19:06:48 +0000
@@ -23,22 +23,24 @@
#include <map>
#include <memory>
-#include <SDL_net.h>
+#include <boost/asio.hpp>
#include "network/network.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.
*/
class NetHost {
public:
+ /// IDs used to enumerate the clients.
using ConnectionId = uint32_t;
/**
* Tries to listen on the given port.
- * @param port The port to listen on.
- * @return A pointer to a listening \c NetHost object or a nullptr if the connection failed.
+ * \param port The port to listen on.
+ * \return A pointer to a listening \c NetHost object or a nullptr if the connection failed.
*/
static std::unique_ptr<NetHost> listen(const uint16_t port);
@@ -49,14 +51,14 @@
/**
* Returns whether the server is started and is listening.
- * @return \c true if the server is listening, \c false otherwise.
+ * \return \c true if the server is listening, \c false otherwise.
*/
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.
+ * \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;
@@ -67,14 +69,14 @@
/**
* Closes the connection to the given client.
- * @param id The id of the client to close the connection to.
+ * \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.
+ * \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.
@@ -83,9 +85,9 @@
/**
* 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.
+ * \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.
*/
@@ -94,26 +96,49 @@
/**
* 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.
+ * \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.
+ */
NetHost(const uint16_t port);
- class Client {
- public:
- Client(TCPsocket sock);
-
- TCPsocket socket;
+ bool open_acceptor(boost::asio::ip::tcp::acceptor *acceptor,
+ const boost::asio::ip::tcp::endpoint& endpoint);
+
+ /**
+ * Helper structure to store variables about a connected client.
+ */
+ struct Client {
+ /**
+ * Initializes the structure with the given socket.
+ * \param sock The socket to listen on. The socket is moved by this
+ * constructor so the given socket is no longer valid.
+ */
+ Client(boost::asio::ip::tcp::socket&& sock);
+
+ /// The socket to send/receive with.
+ boost::asio::ip::tcp::socket socket;
+ /// The deserializer to feed the received data to. It will transform it into data packets.
Deserializer deserializer;
};
- TCPsocket svrsock_;
- SDLNet_SocketSet sockset_;
+ /// A map of connected clients.
std::map<NetHost::ConnectionId, Client> clients_;
+ /// The next client id that will be used
NetHost::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.
+ boost::asio::ip::tcp::acceptor acceptor_v4_;
+ /// The acceptor we get IPv6 connection requests to.
+ boost::asio::ip::tcp::acceptor acceptor_v6_;
};
#endif // end of include guard: WL_NETWORK_NETHOST_H
=== modified file 'src/network/network.cc'
--- src/network/network.cc 2017-05-14 14:40:24 +0000
+++ src/network/network.cc 2017-05-20 19:06:48 +0000
@@ -19,8 +19,44 @@
#include "network/network.h"
+#include <SDL.h>
+#include <boost/asio.hpp>
+
#include "base/log.h"
+
+namespace {
+
+bool do_resolve(const boost::asio::ip::tcp& protocol, NetAddress *addr, const std::string& hostname, uint16_t port) {
+ assert(addr != nullptr);
+ try {
+ boost::asio::io_service io_service;
+ boost::asio::ip::tcp::resolver resolver(io_service);
+ boost::asio::ip::tcp::resolver::query query(protocol, hostname, boost::lexical_cast<std::string>(port));
+ boost::asio::ip::tcp::resolver::iterator iter = resolver.resolve(query);
+ if (iter == boost::asio::ip::tcp::resolver::iterator()) {
+ // Resolution failed
+ return false;
+ }
+ addr->ip = iter->endpoint().address().to_string();
+ addr->port = port;
+ return true;
+ } catch (const boost::system::system_error&) {
+ // Resolution failed
+ return false;
+ }
+}
+
+}
+
+bool NetAddress::resolve_to_v4(NetAddress *addr, const std::string& hostname, uint16_t port) {
+ return do_resolve(boost::asio::ip::tcp::v4(), addr, hostname, port);
+}
+
+bool NetAddress::resolve_to_v6(NetAddress *addr, const std::string& hostname, uint16_t port) {
+ return do_resolve(boost::asio::ip::tcp::v6(), addr, hostname, port);
+}
+
CmdNetCheckSync::CmdNetCheckSync(uint32_t const dt, SyncCallback* const cb)
: Command(dt), callback_(cb) {
}
=== modified file 'src/network/network.h'
--- src/network/network.h 2017-05-11 16:13:34 +0000
+++ src/network/network.h 2017-05-20 19:06:48 +0000
@@ -24,7 +24,6 @@
#include <string>
#include <vector>
-#include <SDL_net.h>
#include <boost/lexical_cast.hpp>
#include "base/wexception.h"
@@ -36,6 +35,35 @@
class Deserializer;
class FileRead;
+/**
+ * Simple structure to hold the IP address and port of a server.
+ * This structure should not contain a hostname.
+ */
+struct NetAddress {
+ /**
+ * Tries to resolve the given hostname to an IPv4 address.
+ * \param[out] addr An NetAddress structure to write the result to,
+ * if resolution succeeds.
+ * \param hostname The name of the host.
+ * \param port The port on the host.
+ * \return \c True if the resolution succeeded, \c false otherwise.
+ */
+ static bool resolve_to_v4(NetAddress *addr, const std::string& hostname, uint16_t port);
+
+ /**
+ * Tries to resolve the given hostname to an IPv6 address.
+ * \param[out] addr An NetAddress structure to write the result to,
+ * if resolution succeeds.
+ * \param hostname The name of the host.
+ * \param port The port on the host.
+ * \return \c True if the resolution succeeded, \c false otherwise.
+ */
+ static bool resolve_to_v6(NetAddress *addr, const std::string& hostname, uint16_t port);
+
+ std::string ip;
+ uint16_t port;
+};
+
struct SyncCallback {
virtual ~SyncCallback() {
}
=== modified file 'src/network/network_lan_promotion.cc'
--- src/network/network_lan_promotion.cc 2017-01-25 18:55:59 +0000
+++ src/network/network_lan_promotion.cc 2017-05-20 19:06:48 +0000
@@ -19,116 +19,293 @@
#include "network/network_lan_promotion.h"
-#include <cstdio>
-#include <cstring>
+#ifndef _WIN32
+#include <ifaddrs.h>
+#endif
+#include "base/i18n.h"
#include "base/log.h"
-#include "base/macros.h"
+#include "base/warning.h"
#include "build_info.h"
#include "network/constants.h"
+namespace {
+
+ static const char *ip_versions[] = {"IPv4", "IPv6"};
+
+ /**
+ * Returns the matching string for the given IP address.
+ * \param addr The address object to get the IP version for.
+ * \return A pointer to a constant string naming the IP version.
+ */
+ const char* get_ip_version_string(const boost::asio::ip::address& addr) {
+ assert(!addr.is_unspecified());
+ if (addr.is_v4()) {
+ return ip_versions[0];
+ } else {
+ assert(addr.is_v6());
+ return ip_versions[1];
+ }
+ }
+
+ /**
+ * Returns the matching string for the given IP address.
+ * \param version A whatever object to get the IP version for.
+ * \return A pointer to a constant string naming the IP version.
+ */
+ const char* get_ip_version_string(const boost::asio::ip::udp& version) {
+ if (version == boost::asio::ip::udp::v4()) {
+ return ip_versions[0];
+ } else {
+ assert(version == boost::asio::ip::udp::v6());
+ return ip_versions[1];
+ }
+ }
+}
+
/*** class LanBase ***/
-
-LanBase::LanBase() {
-
- sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); // open the socket
-
- int32_t opt = 1;
- // the cast to char* is because microsoft wants it that way
- setsockopt(sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<char*>(&opt), sizeof(opt));
+LanBase::LanBase(uint16_t port)
+ : io_service(), socket_v4(io_service), socket_v6(io_service) {
#ifndef _WIN32
-
- // get a list of all local broadcast addresses
- struct if_nameindex* ifnames = if_nameindex();
- struct ifreq ifr;
-
- for (int32_t i = 0; ifnames[i].if_index; ++i) {
- strncpy(ifr.ifr_name, ifnames[i].if_name, IFNAMSIZ);
-
- DIAG_OFF("-Wold-style-cast")
- if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0)
- continue;
-
- if (!(ifr.ifr_flags & IFF_BROADCAST))
- continue;
-
- if (ioctl(sock, SIOCGIFBRDADDR, &ifr) < 0)
- continue;
- DIAG_ON("-Wold-style-cast")
-
- broadcast_addresses.push_back(
- reinterpret_cast<sockaddr_in*>(&ifr.ifr_broadaddr)->sin_addr.s_addr);
- }
-
- if_freenameindex(ifnames);
+ // Iterate over all interfaces. If they support IPv4, store the broadcast-address
+ // of the interface and try to start the socket. If they support IPv6, just start
+ // the socket. There is one fixed broadcast-address for IPv6 (well, actually multicast)
+
+ // Adapted example out of "man getifaddrs"
+ // Admittedly: I don't like this part. But boost is not able to iterate over
+ // the local IPs at this time. If they ever add it, replace this code
+ struct ifaddrs *ifaddr, *ifa;
+ int family, s, n;
+ char host[NI_MAXHOST];
+ if (getifaddrs(&ifaddr) == -1) {
+ perror("getifaddrs");
+ exit(EXIT_FAILURE);
+ }
+ for (ifa = ifaddr, n = 0; ifa != nullptr; ifa = ifa->ifa_next, n++) {
+ if (ifa->ifa_addr == nullptr)
+ continue;
+ family = ifa->ifa_addr->sa_family;
+ if (family == AF_INET && (ifa->ifa_flags & IFF_BROADCAST)) {
+ s = getnameinfo(ifa->ifa_ifu.ifu_broadaddr, sizeof(struct sockaddr_in),
+ host, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);
+ if (s == 0) {
+ start_socket(&socket_v4, boost::asio::ip::udp::v4(), port);
+ broadcast_addresses_v4.insert(host);
+ }
+ } else if (family == AF_INET6 && (ifa->ifa_flags & IFF_BROADCAST)) {
+ start_socket(&socket_v6, boost::asio::ip::udp::v6(), port);
+ // Nothing to insert here. There is only one "broadcast" address for IPv6 (I think)
+ }
+ }
+ freeifaddrs(ifaddr);
#else
// As Microsoft does not seem to support if_nameindex, we just broadcast to
// INADDR_BROADCAST.
- broadcast_addresses.push_back(INADDR_BROADCAST);
+ broadcast_addresses_v4.insert("255.255.255.255");
#endif
+
+ // Okay, needed this for development. But might be useful to debug network problems
+ for (const std::string& ip : broadcast_addresses_v4)
+ log("[LAN] Will broadcast to %s\n", ip.c_str());
+
+ if (!is_open()) {
+ // Hm, not good. Just try to open them and hope for the best
+ start_socket(&socket_v4, boost::asio::ip::udp::v4(), port);
+ start_socket(&socket_v6, boost::asio::ip::udp::v6(), port);
+ }
+
+ if (!is_open()) {
+ // Still not open? Go back to main menu.
+ report_network_error();
+ }
}
LanBase::~LanBase() {
- closesocket(sock);
-}
-
-void LanBase::bind(uint16_t port) {
- sockaddr_in addr;
-
- DIAG_OFF("-Wold-style-cast")
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = INADDR_ANY;
- addr.sin_port = htons(port);
- DIAG_ON("-Wold-style-cast")
-
- ::bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
+ close_socket(&socket_v4);
+ close_socket(&socket_v6);
}
bool LanBase::avail() {
- fd_set fds;
- timeval tv;
-
- DIAG_OFF("-Wold-style-cast")
- FD_ZERO(&fds);
- FD_SET(sock, &fds);
- DIAG_ON("-Wold-style-cast")
-
- tv.tv_sec = 0;
- tv.tv_usec = 0;
-
- return select(sock + 1, &fds, nullptr, nullptr, &tv) == 1;
-}
-
-ssize_t LanBase::receive(void* const buf, size_t const len, sockaddr_in* const addr) {
- socklen_t addrlen = sizeof(sockaddr_in);
- return recvfrom(
- sock, static_cast<DATATYPE*>(buf), len, 0, reinterpret_cast<sockaddr*>(addr), &addrlen);
-}
-
-void LanBase::send(void const* const buf, size_t const len, sockaddr_in const* const addr) {
- sendto(sock, static_cast<const DATATYPE*>(buf), len, 0, reinterpret_cast<const sockaddr*>(addr),
- sizeof(sockaddr_in));
-}
-
-void LanBase::broadcast(void const* const buf, size_t const len, uint16_t const port) {
- for (const in_addr_t& temp_address : broadcast_addresses) {
- sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = temp_address;
- DIAG_OFF("-Wold-style-cast")
- addr.sin_port = htons(port);
- DIAG_ON("-Wold-style-cast")
-
- sendto(sock, static_cast<const DATATYPE*>(buf), len, 0,
- reinterpret_cast<const sockaddr*>(&addr), sizeof(addr));
+ boost::system::error_code ec;
+ bool available_v4 = (socket_v4.is_open() && socket_v4.available(ec) > 0);
+ if (ec) {
+ close_socket(&socket_v4);
+ available_v4 = false;
+ }
+ bool available_v6 = (socket_v6.is_open() && socket_v6.available(ec) > 0);
+ if (ec) {
+ close_socket(&socket_v6);
+ available_v4 = false;
+ }
+ return available_v4 || available_v6;
+}
+
+bool LanBase::is_open() {
+ return socket_v4.is_open() || socket_v6.is_open();
+}
+
+ssize_t LanBase::receive(void* const buf, size_t const len, NetAddress *addr) {
+ assert(buf != nullptr);
+ assert(addr != nullptr);
+ boost::asio::ip::udp::endpoint sender_endpoint;
+ size_t recv_len;
+ if (socket_v4.is_open()) {
+ try {
+ if (socket_v4.available() > 0) {
+ recv_len = socket_v4.receive_from(boost::asio::buffer(buf, len), sender_endpoint);
+ *addr = NetAddress{sender_endpoint.address().to_string(), sender_endpoint.port()};
+ assert(recv_len <= len);
+ return recv_len;
+ }
+ } catch (const boost::system::system_error&) {
+ // Some network error. Close the socket
+ close_socket(&socket_v4);
+ }
+ }
+ // We only reach this point if there was nothing to receive for IPv4
+ if (socket_v6.is_open()) {
+ try {
+ if (socket_v6.available() > 0) {
+ recv_len = socket_v6.receive_from(boost::asio::buffer(buf, len), sender_endpoint);
+ *addr = NetAddress{sender_endpoint.address().to_string(), sender_endpoint.port()};
+ assert(recv_len <= len);
+ return recv_len;
+ }
+ } catch (const boost::system::system_error&) {
+ close_socket(&socket_v6);
+ }
+ }
+ // Nothing to receive at all. So lonely here...
+ return 0;
+}
+
+bool LanBase::send(void const* const buf, size_t const len, const NetAddress& addr) {
+ boost::system::error_code ec;
+ const boost::asio::ip::address address = boost::asio::ip::address::from_string(addr.ip, ec);
+ // If this assert failed, then there is some bug in the code. NetAddress should only be filled
+ // with valid IP addresses (e.g. no hostnames)
+ assert(!ec);
+ boost::asio::ip::udp::endpoint destination(address, addr.port);
+ boost::asio::ip::udp::socket *socket = nullptr;
+ if (destination.address().is_v4()) {
+ socket = &socket_v4;
+ } else if (destination.address().is_v6()) {
+ socket = &socket_v6;
+ } else {
+ NEVER_HERE();
+ }
+ assert(socket != nullptr);
+ if (!socket->is_open()) {
+ // I think this shouldn't happen normally. It might happen, though, if we receive
+ // a broadcast and learn the IP, then our sockets goes down, then we try to send
+ log("[LAN] Error: trying to send to an %s address but socket is not open",
+ get_ip_version_string(address));
+ return false;
+ }
+ socket->send_to(boost::asio::buffer(buf, len), destination, 0, ec);
+ if (ec) {
+ close_socket(socket);
+ return false;
+ }
+ return true;
+}
+
+bool LanBase::broadcast(void const* const buf, size_t const len, uint16_t const port) {
+ boost::system::error_code ec;
+ bool error = false;
+ if (socket_v4.is_open()) {
+ for (const std::string& address : broadcast_addresses_v4) {
+ boost::asio::ip::udp::endpoint destination(boost::asio::ip::address::from_string(address), port);
+ socket_v4.send_to(boost::asio::buffer(buf, len), destination, 0, ec);
+ if (ec) {
+ close_socket(&socket_v4);
+ error = true;
+ break;
+ }
+ }
+ }
+ if (socket_v6.is_open()) {
+ boost::asio::ip::udp::endpoint destination(boost::asio::ip::address::from_string("ff02::1"), port);
+ socket_v6.send_to(boost::asio::buffer(buf, len), destination, 0, ec);
+ if (ec) {
+ close_socket(&socket_v6);
+ error = true;
+ }
+ }
+ return !error;
+}
+
+void LanBase::start_socket(boost::asio::ip::udp::socket *socket, boost::asio::ip::udp version, uint16_t port) {
+
+ if (socket->is_open())
+ return;
+
+ boost::system::error_code ec;
+ // Try to open the socket
+ socket->open(version, ec);
+ if (ec) {
+ log("[LAN] Failed to start an %s socket: %s\n",
+ get_ip_version_string(version), ec.message().c_str());
+ return;
+ }
+
+ const boost::asio::socket_base::broadcast option_broadcast(true);
+ socket->set_option(option_broadcast, ec);
+ if (ec) {
+ log("[LAN] Error setting options for %s socket, closing socket: %s\n",
+ get_ip_version_string(version), ec.message().c_str());
+ // Retrieve the error code to avoid throwing but ignore it
+ close_socket(socket);
+ return;
+ }
+
+ const boost::asio::socket_base::reuse_address option_reuse(true);
+ socket->set_option(option_reuse, ec);
+ // This one isn't really needed so ignore the error
+
+
+ if (version == boost::asio::ip::udp::v6()) {
+ const boost::asio::ip::v6_only option_v6only(true);
+ socket->set_option(option_v6only, ec);
+ // This one might not be needed, ignore the error and see whether we fail on bind()
+ }
+
+ socket->bind(boost::asio::ip::udp::endpoint(version, port), ec);
+ if (ec) {
+ log("[LAN] Error binding %s socket to UDP port %d, closing socket: %s\n",
+ get_ip_version_string(version), port, ec.message().c_str());
+ close_socket(socket);
+ return;
+ }
+
+ log("[LAN] Started an %s socket on UDP port %d\n", get_ip_version_string(version), port);
+}
+
+void LanBase::report_network_error() {
+ // No socket open? Sorry, but we can't continue this way
+ throw WLWarning(_("Failed to use the local network!"),
+ _("Widelands was unable to use the local network. "
+ "Maybe some other process is already running a server on port %d, %d or %d "
+ "or your network setup is broken."),
+ WIDELANDS_LAN_DISCOVERY_PORT, WIDELANDS_LAN_PROMOTION_PORT, WIDELANDS_PORT);
+}
+
+void LanBase::close_socket(boost::asio::ip::udp::socket *socket) {
+ boost::system::error_code ec;
+ if (socket->is_open()) {
+ const boost::asio::ip::udp::endpoint& endpoint = socket->local_endpoint(ec);
+ if (!ec)
+ log("[LAN] Closing an %s socket.\n", get_ip_version_string(endpoint.protocol()));
+ socket->shutdown(boost::asio::ip::udp::socket::shutdown_both, ec);
+ socket->close(ec);
}
}
/*** class LanGamePromoter ***/
-LanGamePromoter::LanGamePromoter() {
- bind(WIDELANDS_LAN_PROMOTION_PORT);
+LanGamePromoter::LanGamePromoter()
+ : LanBase(WIDELANDS_LAN_PROMOTION_PORT) {
needupdate = true;
@@ -140,12 +317,13 @@
strncpy(gameinfo.gameversion, build_id().c_str(), sizeof(gameinfo.gameversion));
- gethostname(gameinfo.hostname, sizeof(gameinfo.hostname));
+ strncpy(gameinfo.hostname, boost::asio::ip::host_name().c_str(), sizeof(gameinfo.hostname));
}
LanGamePromoter::~LanGamePromoter() {
gameinfo.state = LAN_GAME_CLOSED;
+ // Don't care about errors at this point
broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT);
}
@@ -153,20 +331,23 @@
if (needupdate) {
needupdate = false;
- broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT);
+ if (!broadcast(&gameinfo, sizeof(gameinfo), WIDELANDS_LAN_DISCOVERY_PORT))
+ report_network_error();
}
while (avail()) {
char magic[8];
- sockaddr_in addr;
+ NetAddress addr;
if (receive(magic, 8, &addr) < 8)
continue;
- log("Received %s packet\n", magic);
+ log("Received %s packet from %s\n", magic, addr.ip.c_str());
- if (!strncmp(magic, "QUERY", 6) && magic[6] == LAN_PROMOTION_PROTOCOL_VERSION)
- send(&gameinfo, sizeof(gameinfo), &addr);
+ if (!strncmp(magic, "QUERY", 6) && magic[6] == LAN_PROMOTION_PROTOCOL_VERSION) {
+ if (!send(&gameinfo, sizeof(gameinfo), addr))
+ report_network_error();
+ }
}
}
@@ -178,8 +359,8 @@
/*** class LanGameFinder ***/
-LanGameFinder::LanGameFinder() : callback(nullptr) {
- bind(WIDELANDS_LAN_DISCOVERY_PORT);
+LanGameFinder::LanGameFinder()
+ : LanBase(WIDELANDS_LAN_DISCOVERY_PORT), callback(nullptr) {
reset();
}
@@ -192,18 +373,19 @@
strncpy(magic, "QUERY", 8);
magic[6] = LAN_PROMOTION_PROTOCOL_VERSION;
- broadcast(magic, 8, WIDELANDS_LAN_PROMOTION_PORT);
+ if (!broadcast(magic, 8, WIDELANDS_LAN_PROMOTION_PORT))
+ report_network_error();
}
void LanGameFinder::run() {
while (avail()) {
NetGameInfo info;
- sockaddr_in addr;
+ NetAddress addr;
if (receive(&info, sizeof(info), &addr) < static_cast<int32_t>(sizeof(info)))
continue;
- log("Received %s packet\n", info.magic);
+ log("Received %s packet from %s\n", info.magic, addr.ip.c_str());
if (strncmp(info.magic, "GAME", 6))
continue;
@@ -226,8 +408,8 @@
if (!was_in_list) {
opengames.push_back(new NetOpenGame);
DIAG_OFF("-Wold-style-cast")
- opengames.back()->address = addr.sin_addr.s_addr;
- opengames.back()->port = htons(WIDELANDS_PORT);
+ addr.port = WIDELANDS_PORT;
+ opengames.back()->address = addr;
DIAG_ON("-Wold-style-cast")
opengames.back()->info = info;
callback(GameOpened, opengames.back(), userdata);
=== modified file 'src/network/network_lan_promotion.h'
--- src/network/network_lan_promotion.h 2017-05-07 20:27:21 +0000
+++ src/network/network_lan_promotion.h 2017-05-20 19:06:48 +0000
@@ -20,22 +20,17 @@
#ifndef WL_NETWORK_NETWORK_LAN_PROMOTION_H
#define WL_NETWORK_NETWORK_LAN_PROMOTION_H
+#include <boost/asio.hpp>
#include <list>
-
-#ifndef _WIN32
-#include <sys/socket.h>
-#endif
-#include <sys/types.h>
-
-#include "network/network_system.h"
+#include <set>
+
+#include "network/network.h"
#define LAN_PROMOTION_PROTOCOL_VERSION 1
#define LAN_GAME_CLOSED 0
#define LAN_GAME_OPEN 1
-// TODO(Notabilis): Update file for IPv6
-
struct NetGameInfo {
char magic[6];
uint8_t version;
@@ -47,31 +42,111 @@
};
struct NetOpenGame {
- in_addr_t address;
- in_port_t port;
+ NetAddress address;
NetGameInfo info;
};
+/**
+ * Base class for UDP networking.
+ * This class is used by derived classes to find open games on the
+ * local network and to announce a just opened game on the local network.
+ * This class tries to create sockets for IPv4 and IPv6.
+ */
struct LanBase {
protected:
- LanBase();
+ /**
+ * Tries to start a socket on the given port.
+ * Sockets for IPv4 and IPv6 are started.
+ * When both fail, report_network_error() is called.
+ * \param port The port to listen on.
+ */
+ LanBase(uint16_t port);
+
+ /**
+ * Destructor.
+ */
~LanBase();
- void bind(uint16_t);
-
+ /**
+ * Returns whether data is available to be read.
+ * \return \c True when receive() will return data, \c false otherwise.
+ */
bool avail();
- ssize_t receive(void*, size_t, sockaddr_in*);
-
- void send(void const*, size_t, sockaddr_in const*);
- void broadcast(void const*, size_t, uint16_t);
+ /**
+ * Returns whether at least one of the sockets is open.
+ * If this returns \c false, you probably have a problem.
+ * \return \c True when a socket is ready, \c false otherwise.
+ */
+ bool is_open();
+
+ /**
+ * Tries to receive some data.
+ * \param[out] buf The buffer to read data into.
+ * \param len The length of the buffer.
+ * \param[out] addr The address we received data from. Since UDP is a connection-less
+ * protocol, each receive() might receive data from another address.
+ * \return How many bytes have been written to \c buf. If 0 is returned there either was no data
+ * available (check before with avail()) or there was some error (check with is_open())
+ */
+ ssize_t receive(void *buf, size_t len, NetAddress *addr);
+
+ /**
+ * Sends data to a specified address.
+ * \param buf The data to send.
+ * \param len The length of the buffer.
+ * \param addr The address to send to.
+ */
+ bool send(void const *buf, size_t len, const NetAddress& addr);
+
+ /**
+ * Broadcast some data in the local network.
+ * \param buf The data to send.
+ * \param len The length of the buffer.
+ * \param port The port to send to. No address is required.
+ */
+ bool broadcast(void const* buf, size_t len, uint16_t port);
+
+ /**
+ * Throws a WLWarning exception to jump back to the main menu.
+ * Calling this on network errors is in the responsibility of derived classes.
+ * (Most of the time, aborting makes sense when an error occurred. But e.g. in
+ * the destructor simply ignoring the error is okay.)
+ */
+ void report_network_error();
private:
- int32_t sock;
-
- std::list<in_addr_t> broadcast_addresses;
+
+ /**
+ * Opens a listening UDP socket.
+ * \param[out] The socket to open. The object has to be created but the socket not opened before.
+ * If it already has been opened before, nothing will be done.
+ * \param version Whether a IPv4 or IPv6 socket should be opened.
+ * \param port The port to listen on.
+ */
+ void start_socket(boost::asio::ip::udp::socket *socket, boost::asio::ip::udp version, uint16_t port);
+
+ /**
+ * Closes the given socket.
+ * Does nothing if the socket already has been closed.
+ * \param socket The socket to close.
+ */
+ void close_socket(boost::asio::ip::udp::socket *socket);
+
+ /// No idea what this does. I think it is only really used when asynchronous operations are done.
+ boost::asio::io_service io_service;
+ /// The socket for IPv4.
+ boost::asio::ip::udp::socket socket_v4;
+ /// The socket for IPv6.
+ boost::asio::ip::udp::socket socket_v6;
+ /// The found broadcast addresses for IPv4.
+ /// No addresses for v6, there is only one fixed address
+ std::set<std::string> broadcast_addresses_v4;
};
+/**
+ * Used to promote opened games locally.
+ */
struct LanGamePromoter : public LanBase {
LanGamePromoter();
~LanGamePromoter();
@@ -85,6 +160,9 @@
bool needupdate;
};
+/**
+ * Used to listen for open games while in the LAN-screen.
+ */
struct LanGameFinder : LanBase {
enum { GameOpened, GameClosed, GameUpdated };
=== removed file 'src/network/network_system.h'
--- src/network/network_system.h 2017-01-25 18:55:59 +0000
+++ src/network/network_system.h 1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2004-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_NETWORK_SYSTEM_H
-#define WL_NETWORK_NETWORK_SYSTEM_H
-
-#include <stdint.h>
-#ifndef _WIN32
-// These includes work on Linux and should be fine on any other Unix-alike.
-// If not, this is the right place to conditionally include what is needed.
-#include <net/if.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-// be compatible to microsoft
-#define closesocket close
-#define DATATYPE void
-
-#else
-
-// This is the header to include according to the documentation
-// at msdn.microsoft.com
-#include <winsock2.h>
-
-#define DATATYPE char
-// microsoft doesn't have these
-using in_port_t = uint16_t;
-using in_addr_t = uint32_t;
-
-#ifndef s_addr
-#define s_addr S_addr
-#endif
-
-// This is no typedef on purpose
-#define socklen_t int32_t
-
-#endif
-
-#endif // end of include guard: WL_NETWORK_NETWORK_SYSTEM_H
=== modified file 'src/ui_fsmenu/internet_lobby.cc'
--- src/ui_fsmenu/internet_lobby.cc 2017-05-11 06:31:35 +0000
+++ src/ui_fsmenu/internet_lobby.cc 2017-05-20 19:06:48 +0000
@@ -373,15 +373,8 @@
}
std::string ip = InternetGaming::ref().ip();
- // TODO(Notabilis): Change this for IPv6
- // convert IPv6 addresses returned by the metaserver to IPv4 addresses.
- // At the moment SDL_net does not support IPv6 anyways.
- if (!ip.compare(0, 7, "::ffff:")) {
- ip = ip.substr(7);
- log("InternetGaming: cut IPv6 address: %s\n", ip.c_str());
- }
-
- GameClient netgame(ip, WIDELANDS_PORT, InternetGaming::ref().get_local_clientname(), true);
+ GameClient netgame(NetAddress{ip, WIDELANDS_PORT},
+ InternetGaming::ref().get_local_clientname(), true);
netgame.run();
} else
throw wexception("No server selected! That should not happen!");
=== modified file 'src/ui_fsmenu/netsetup_lan.cc'
--- src/ui_fsmenu/netsetup_lan.cc 2017-02-27 13:45:46 +0000
+++ src/ui_fsmenu/netsetup_lan.cc 2017-05-20 19:06:48 +0000
@@ -135,7 +135,7 @@
discovery.run();
}
-bool FullscreenMenuNetSetupLAN::get_host_address(uint32_t& addr, uint16_t& port) {
+bool FullscreenMenuNetSetupLAN::get_host_address(NetAddress *addr) {
const std::string& host = hostname.text();
const uint32_t opengames_size = opengames.size();
@@ -143,20 +143,17 @@
const NetOpenGame& game = *opengames[i];
if (!strcmp(game.info.hostname, host.c_str())) {
- addr = game.address;
- port = game.port;
+ *addr = game.address;
return true;
}
}
- if (hostent* const he = gethostbyname(host.c_str())) {
- addr = (reinterpret_cast<in_addr*>(he->h_addr_list[0]))->s_addr;
- DIAG_OFF("-Wold-style-cast")
- port = htons(WIDELANDS_PORT);
- DIAG_ON("-Wold-style-cast")
- return true;
- } else
- return false;
+ // The user probably entered a hostname on his own. Try to resolve it
+ if (NetAddress::resolve_to_v6(addr, host, WIDELANDS_PORT))
+ return true;
+ if (NetAddress::resolve_to_v4(addr, host, WIDELANDS_PORT))
+ return true;
+ return false;
}
const std::string& FullscreenMenuNetSetupLAN::get_playername() {
=== modified file 'src/ui_fsmenu/netsetup_lan.h'
--- src/ui_fsmenu/netsetup_lan.h 2017-05-07 20:27:21 +0000
+++ src/ui_fsmenu/netsetup_lan.h 2017-05-20 19:06:48 +0000
@@ -34,8 +34,6 @@
struct NetOpenGame;
struct NetGameInfo;
-// TODO(Notabilis): Update for IPv6
-
class FullscreenMenuNetSetupLAN : public FullscreenMenuBase {
public:
FullscreenMenuNetSetupLAN();
@@ -43,12 +41,10 @@
void think() override;
/**
- * \param[out] addr filled in with the IP address of the chosen server
- * \param[out] port filled in with the port of the chosen server
- * \return \c true if a valid server has been chosen. If \c false is
- * returned, the values of \p addr and \p port are undefined.
+ * \param[out] addr filled in with the host name or IP address and port of the chosen server
+ * \return \c True if the address is valid, \c false otherwise. In that case \c addr is not modified
*/
- bool get_host_address(uint32_t& addr, uint16_t& port);
+ bool get_host_address(NetAddress *addr);
/**
* \return the name chosen by the player
=== modified file 'src/wlapplication.cc'
--- src/wlapplication.cc 2017-05-13 13:14:29 +0000
+++ src/wlapplication.cc 2017-05-20 19:06:48 +0000
@@ -350,9 +350,6 @@
// This might grab the input.
refresh_graphics();
- if (SDLNet_Init() == -1)
- throw wexception("SDLNet_Init failed: %s\n", SDLNet_GetError());
-
// seed random number generator used for random tribe selection
std::srand(time(nullptr));
@@ -378,8 +375,6 @@
delete UI::g_fh1;
UI::g_fh1 = nullptr;
- SDLNet_Quit();
-
TTF_Quit(); // TODO(unknown): not here
assert(g_fs);
@@ -1164,10 +1159,6 @@
FullscreenMenuNetSetupLAN ns;
menu_result = ns.run<FullscreenMenuBase::MenuTarget>();
std::string playername = ns.get_playername();
- // TODO(Notabilis): This has to be updated for IPv6
- uint32_t addr;
- uint16_t port;
- bool const host_address = ns.get_host_address(addr, port);
switch (menu_result) {
case FullscreenMenuBase::MenuTarget::kHostgame: {
@@ -1176,18 +1167,16 @@
break;
}
case FullscreenMenuBase::MenuTarget::kJoingame: {
- if (!host_address)
- throw WLWarning(
- "Invalid Address", "%s", "The address of the game server is invalid");
+ NetAddress addr;
+ if (!ns.get_host_address(&addr)) {
+ UI::WLMessageBox mmb(&ns, _("Invalid address"),
+ _("The entered hostname or address is invalid and can't be connected to."),
+ UI::WLMessageBox::MBoxType::kOk);
+ mmb.run<UI::Panel::Returncodes>();
+ break;
+ }
- // TODO(Notabilis): Make this prettier. I am aware that this is quite ugly but it should
- // work
- // for now and will be removed shortly when we switch to boost.asio
- char ip_str[] = {"255.255.255.255"};
- sprintf(ip_str, "%d.%d.%d.%d", (addr & 0x000000ff), (addr & 0x0000ff00) >> 8,
- (addr & 0x00ff0000) >> 16, (addr & 0xff000000) >> 24);
- port = (port >> 8) | ((port & 0xFF) << 8);
- GameClient netgame(ip_str, port, playername);
+ GameClient netgame(addr, playername);
netgame.run();
break;
}
=== modified file 'utils/macos/build_app.sh'
--- utils/macos/build_app.sh 2016-11-30 00:18:17 +0000
+++ utils/macos/build_app.sh 2017-05-20 19:06:48 +0000
@@ -109,7 +109,6 @@
export SDL2IMAGEDIR="$(brew --prefix sdl2_image)"
export SDL2MIXERDIR="$(brew --prefix sdl2_mixer)"
export SDL2TTFDIR="$(brew --prefix sdl2_ttf)"
- export SDL2NETDIR="$(brew --prefix sdl2_net)"
export BOOST_ROOT="$(brew --prefix boost)"
export ICU_ROOT="$(brew --prefix icu4c)"
=== modified file 'utils/win32/innosetup/Widelands.iss'
--- utils/win32/innosetup/Widelands.iss 2017-02-27 08:52:41 +0000
+++ utils/win32/innosetup/Widelands.iss 2017-05-20 19:06:48 +0000
@@ -131,7 +131,6 @@
Source: {#DLLFolder}\SDL2.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
Source: {#DLLFolder}\SDL2_image.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
Source: {#DLLFolder}\libSDL2_mixer-2-0-0.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
-Source: {#DLLFolder}\SDL2_net.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
Source: {#DLLFolder}\SDL2_ttf.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
Source: {#DLLFolder}\zlib1.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
Source: {#DLLFolder}\libFLAC-8.dll; DestDir: {app}; Flags: ignoreversion; Components: "Widelands"
Follow ups
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: noreply, 2017-06-10
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-06-10
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-06-10
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-06-09
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-06-08
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-06-07
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-06-07
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-06-07
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-06-07
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-06-06
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-06-05
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-06-05
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-06-04
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-06-04
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-06-04
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-06-04
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: bunnybot, 2017-06-04
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: SirVer, 2017-06-04
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Tino, 2017-06-03
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-06-03
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-06-03
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-06-01
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-06-01
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Tino, 2017-06-01
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-06-01
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: bunnybot, 2017-06-01
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-31
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-30
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-05-30
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-30
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-05-30
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-29
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-29
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: bunnybot, 2017-05-28
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-28
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-28
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-28
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-27
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-05-27
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-27
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-27
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-27
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: bunnybot, 2017-05-27
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-26
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-26
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-26
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-26
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-05-25
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-25
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-25
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-25
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-25
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Klaus Halfmann, 2017-05-25
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-24
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: bunnybot, 2017-05-24
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: bunnybot, 2017-05-22
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: Notabilis, 2017-05-21
-
Re: [Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: GunChleoc, 2017-05-21
-
[Merge] lp:~widelands-dev/widelands/net-boost-asio into lp:widelands
From: bunnybot, 2017-05-20