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"
_______________________________________________ Mailing list: https://launchpad.net/~widelands-dev Post to : widelands-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~widelands-dev More help : https://help.launchpad.net/ListHelp