From 867dd96a73898de12e81e1d9dc4fef5cb2f53123 Mon Sep 17 00:00:00 2001 From: Xadhoom Date: Wed, 7 Apr 2021 12:59:51 +0000 Subject: [PATCH] Add ZeroTier support --- 3rdParty/libzt/CMakeLists.txt | 49 +++++ CMake/mingwcc.cmake | 2 + CMake/mingwcc64.cmake | 2 + CMakeLists.txt | 10 +- SourceX/DiabloUI/selconn.cpp | 8 +- SourceX/dvlnet/abstract_net.cpp | 9 +- SourceX/dvlnet/abstract_net.h | 17 +- SourceX/dvlnet/base.cpp | 9 + SourceX/dvlnet/base.h | 6 +- SourceX/dvlnet/base_protocol.cpp | 14 ++ SourceX/dvlnet/base_protocol.h | 293 +++++++++++++++++++++++++++ SourceX/dvlnet/cdwrap.h | 17 +- SourceX/dvlnet/frame_queue.h | 11 +- SourceX/dvlnet/loopback.cpp | 11 +- SourceX/dvlnet/loopback.h | 3 +- SourceX/dvlnet/packet.cpp | 6 +- SourceX/dvlnet/packet.h | 46 ++++- SourceX/dvlnet/protocol_zt.cpp | 304 +++++++++++++++++++++++++++++ SourceX/dvlnet/protocol_zt.h | 99 ++++++++++ SourceX/dvlnet/tcp_client.cpp | 5 + SourceX/dvlnet/tcp_client.h | 3 +- SourceX/dvlnet/tcp_server.h | 2 +- SourceX/dvlnet/udp_p2p.cpp | 171 ---------------- SourceX/dvlnet/udp_p2p.h | 45 ----- SourceX/dvlnet/zerotier_lwip.cpp | 40 ++++ SourceX/dvlnet/zerotier_lwip.h | 8 + SourceX/dvlnet/zerotier_native.cpp | 71 +++++++ SourceX/dvlnet/zerotier_native.h | 17 ++ SourceX/storm/storm_dvlnet.h | 5 + SourceX/storm/storm_net.cpp | 88 ++++++++- enums.h | 2 +- 31 files changed, 1128 insertions(+), 245 deletions(-) create mode 100644 3rdParty/libzt/CMakeLists.txt create mode 100644 SourceX/dvlnet/base_protocol.cpp create mode 100644 SourceX/dvlnet/base_protocol.h create mode 100644 SourceX/dvlnet/protocol_zt.cpp create mode 100644 SourceX/dvlnet/protocol_zt.h delete mode 100644 SourceX/dvlnet/udp_p2p.cpp delete mode 100644 SourceX/dvlnet/udp_p2p.h create mode 100644 SourceX/dvlnet/zerotier_lwip.cpp create mode 100644 SourceX/dvlnet/zerotier_lwip.h create mode 100644 SourceX/dvlnet/zerotier_native.cpp create mode 100644 SourceX/dvlnet/zerotier_native.h create mode 100644 SourceX/storm/storm_dvlnet.h diff --git a/3rdParty/libzt/CMakeLists.txt b/3rdParty/libzt/CMakeLists.txt new file mode 100644 index 000000000..d9081a4ae --- /dev/null +++ b/3rdParty/libzt/CMakeLists.txt @@ -0,0 +1,49 @@ +include(FetchContent_MakeAvailableExcludeFromAll) + +include(FetchContent) +FetchContent_Declare(libzt + GIT_REPOSITORY https://github.com/diasurgical/libzt.git + GIT_TAG 97a405529d64d9589e9fc785d6d1d2c597f92478) +FetchContent_MakeAvailableExcludeFromAll(libzt) + +# External library, ignore all warnings +target_compile_options(zto_obj PRIVATE -fpermissive -w) +target_compile_options(libnatpmp_obj PRIVATE -w) +target_compile_options(libzt_obj PRIVATE -fpermissive -w) +target_compile_options(lwip_obj PRIVATE -w) +target_compile_options(miniupnpc_obj PRIVATE -w) +target_compile_options(zt-static PRIVATE -fpermissive -w) + +target_include_directories(zt-static INTERFACE + "${libzt_SOURCE_DIR}/include" + "${libzt_SOURCE_DIR}/src" + "${libzt_SOURCE_DIR}/ext/lwip/src/include") + +if(WIN32) + target_include_directories(zt-static INTERFACE + "${libzt_SOURCE_DIR}/ext/lwip-contrib/ports/win32/include") +else() + target_include_directories(zt-static INTERFACE + "${libzt_SOURCE_DIR}/ext/lwip-contrib/ports/unix/port/include") +endif() + +if(MINGW_CROSS) + option(MINGW_STDTHREADS_GENERATE_STDHEADERS "" ON) + + FetchContent_Declare(mingw-std-threads + GIT_REPOSITORY https://github.com/meganz/mingw-std-threads + GIT_TAG bee085c0a6cb32c59f0b55c7bba976fe6dcfca7f) + FetchContent_MakeAvailableExcludeFromAll(mingw-std-threads) + + target_compile_definitions(libnatpmp_obj PRIVATE -D_WIN32_WINNT=0x601 -DSTATICLIB) + target_compile_definitions(zto_obj PRIVATE -D_WIN32_WINNT=0x601 -DZT_SALSA20_SSE=0) + target_compile_definitions(libzt_obj PRIVATE -D_WIN32_WINNT=0x601) + target_link_libraries(libzt_obj PRIVATE mingw_stdthreads) + target_link_libraries(zt-static PUBLIC iphlpapi shlwapi wsock32 ws2_32 wininet mingw_stdthreads) + target_include_directories(zt-static INTERFACE "${libzt_SOURCE_DIR}/include/mingw-fixes") +endif() + +if(MSVC) + target_compile_definitions(libnatpmp_obj PRIVATE -DSTATICLIB) + target_link_libraries(zt-static PUBLIC iphlpapi shlwapi wsock32 ws2_32 wininet) +endif() diff --git a/CMake/mingwcc.cmake b/CMake/mingwcc.cmake index d56c385a7..ed24d4ece 100644 --- a/CMake/mingwcc.cmake +++ b/CMake/mingwcc.cmake @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.10) +SET(MINGW_CROSS TRUE) + SET(CROSS_PREFIX "/usr" CACHE STRING "crosstool-NG prefix") SET(CMAKE_SYSTEM_NAME Windows) diff --git a/CMake/mingwcc64.cmake b/CMake/mingwcc64.cmake index 657aaddd6..dfe8115c6 100644 --- a/CMake/mingwcc64.cmake +++ b/CMake/mingwcc64.cmake @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.10) +SET(MINGW_CROSS TRUE) + SET(CROSS_PREFIX "/usr" CACHE STRING "crosstool-NG prefix") SET(CMAKE_SYSTEM_NAME Windows) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1491a656..313d3d071 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -343,7 +343,10 @@ if(NOT NONET) list(APPEND devilutionx_SRCS SourceX/dvlnet/tcp_client.cpp SourceX/dvlnet/tcp_server.cpp - SourceX/dvlnet/udp_p2p.cpp) + SourceX/dvlnet/protocol_zt.cpp + SourceX/dvlnet/base_protocol.cpp + SourceX/dvlnet/zerotier_native.cpp + SourceX/dvlnet/zerotier_lwip.cpp) endif() set(BIN_TARGET devilutionx) @@ -888,3 +891,8 @@ if(CPACK) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) include(CPack) endif() + +if(NOT NONET) + add_subdirectory(3rdParty/libzt) + target_link_libraries(devilutionx PRIVATE zt-static) +endif() diff --git a/SourceX/DiabloUI/selconn.cpp b/SourceX/DiabloUI/selconn.cpp index ca3f3f19a..2b213f261 100644 --- a/SourceX/DiabloUI/selconn.cpp +++ b/SourceX/DiabloUI/selconn.cpp @@ -26,10 +26,8 @@ void selconn_Load() LoadBackgroundArt("ui_art\\selconn.pcx"); #ifndef NONET + vecConnItems.push_back(new UiListItem("Zerotier", SELCONN_ZT)); vecConnItems.push_back(new UiListItem("Client-Server (TCP)", SELCONN_TCP)); -#ifdef BUGGY - vecConnItems.push_back(new UiListItem("Peer-to-Peer (UDP)", SELCONN_UDP)); -#endif #endif vecConnItems.push_back(new UiListItem("Loopback", SELCONN_LOOPBACK)); @@ -103,8 +101,8 @@ void selconn_Focus(int value) strncpy(selconn_Description, "All computers must be connected to a TCP-compatible network.", sizeof(selconn_Description) - 1); players = MAX_PLRS; break; - case SELCONN_UDP: - strncpy(selconn_Description, "All computers must be connected to a UDP-compatible network.", sizeof(selconn_Description) - 1); + case SELCONN_ZT: + strncpy(selconn_Description, "All computers must be connected to the internet.", sizeof(selconn_Description) - 1); players = MAX_PLRS; break; case SELCONN_LOOPBACK: diff --git a/SourceX/dvlnet/abstract_net.cpp b/SourceX/dvlnet/abstract_net.cpp index 094b84bb8..d182295b4 100644 --- a/SourceX/dvlnet/abstract_net.cpp +++ b/SourceX/dvlnet/abstract_net.cpp @@ -4,7 +4,8 @@ #ifndef NONET #include "dvlnet/cdwrap.h" #include "dvlnet/tcp_client.h" -#include "dvlnet/udp_p2p.h" +#include "dvlnet/base_protocol.h" +#include "dvlnet/protocol_zt.h" #endif #include "dvlnet/loopback.h" @@ -19,10 +20,8 @@ std::unique_ptr abstract_net::make_net(provider_t provider) switch (provider) { case SELCONN_TCP: return std::unique_ptr(new cdwrap); -#ifdef BUGGY - case SELCONN_UDP: - return std::unique_ptr(new cdwrap); -#endif + case SELCONN_ZT: + return std::unique_ptr(new cdwrap>); case SELCONN_LOOPBACK: return std::unique_ptr(new loopback); default: diff --git a/SourceX/dvlnet/abstract_net.h b/SourceX/dvlnet/abstract_net.h index 8b0593424..8035892c5 100644 --- a/SourceX/dvlnet/abstract_net.h +++ b/SourceX/dvlnet/abstract_net.h @@ -49,8 +49,23 @@ public: virtual void setup_gameinfo(buffer_t info) = 0; virtual ~abstract_net() = default; + virtual std::string make_default_gamename() = 0; + + virtual void setup_password(std::string passwd) + { + } + + virtual void send_info_request() + { + } + + virtual std::vector get_gamelist() + { + return std::vector(); + } + static std::unique_ptr make_net(provider_t provider); }; } // namespace net -} // namespace dvl +} // namespace devilution diff --git a/SourceX/dvlnet/base.cpp b/SourceX/dvlnet/base.cpp index e7196b167..08cce081a 100644 --- a/SourceX/dvlnet/base.cpp +++ b/SourceX/dvlnet/base.cpp @@ -24,6 +24,10 @@ void base::run_event_handler(_SNETEVENT &ev) } } +void base::disconnect_net(plr_t plr) +{ +} + void base::handle_accept(packet &pkt) { if (plr_self != PLR_BROADCAST) { @@ -34,7 +38,11 @@ void base::handle_accept(packet &pkt) connected_table[plr_self] = true; } if (game_init_info != pkt.info()) { + if(pkt.info().size() != sizeof(GameData)) { + ABORT(); + } // we joined and did not create + game_init_info = pkt.info(); _SNETEVENT ev; ev.eventid = EVENT_TYPE_PLAYER_CREATE_GAME; ev.playerid = plr_self; @@ -83,6 +91,7 @@ void base::recv_local(packet &pkt) ev.databytes = sizeof(leaveinfo_t); run_event_handler(ev); connected_table[pkt.newplr()] = false; + disconnect_net(pkt.newplr()); clear_msg(pkt.newplr()); turn_queue[pkt.newplr()].clear(); } diff --git a/SourceX/dvlnet/base.h b/SourceX/dvlnet/base.h index fb12b4b44..d25555652 100644 --- a/SourceX/dvlnet/base.h +++ b/SourceX/dvlnet/base.h @@ -35,9 +35,12 @@ public: virtual void poll() = 0; virtual void send(packet &pkt) = 0; + virtual void disconnect_net(plr_t plr); void setup_gameinfo(buffer_t info); + virtual void setup_password(std::string pw); + virtual ~base() = default; protected: @@ -70,7 +73,6 @@ protected: std::unique_ptr pktfty; - void setup_password(std::string pw); void handle_accept(packet &pkt); void recv_local(packet &pkt); void run_event_handler(_SNETEVENT &ev); @@ -81,4 +83,4 @@ private: }; } // namespace net -} // namespace dvl +} // namespace devilution diff --git a/SourceX/dvlnet/base_protocol.cpp b/SourceX/dvlnet/base_protocol.cpp new file mode 100644 index 000000000..443e647c1 --- /dev/null +++ b/SourceX/dvlnet/base_protocol.cpp @@ -0,0 +1,14 @@ +#include "dvlnet/base_protocol.h" + +#include +#include + +#ifdef USE_SDL1 +#include "sdl2_to_1_2_backports.h" +#endif + +namespace devilution { +namespace net { + +} // namespace net +} // namespace devilution diff --git a/SourceX/dvlnet/base_protocol.h b/SourceX/dvlnet/base_protocol.h new file mode 100644 index 000000000..7c271df50 --- /dev/null +++ b/SourceX/dvlnet/base_protocol.h @@ -0,0 +1,293 @@ +#pragma once + +#include +#include +#include +#include + +#include "dvlnet/packet.h" +#include "dvlnet/base.h" + +namespace devilution { +namespace net { + +template +class base_protocol : public base { +public: + virtual int create(std::string addrstr, std::string passwd); + virtual int join(std::string addrstr, std::string passwd); + virtual void poll(); + virtual void send(packet &pkt); + virtual void disconnect_net(plr_t plr); + + virtual bool SNetLeaveGame(int type); + + virtual std::string make_default_gamename(); + virtual void send_info_request(); + virtual std::vector get_gamelist(); + + virtual ~base_protocol() = default; + +private: + P proto; + typedef typename P::endpoint endpoint; + + endpoint firstpeer; + std::string gamename; + std::map game_list; + std::array peers; + + plr_t get_master(); + void recv(); + void handle_join_request(packet &pkt, endpoint sender); + void recv_decrypted(packet &pkt, endpoint sender); + void recv_ingame(packet &pkt, endpoint sender); + + bool wait_network(); + bool wait_firstpeer(); + void wait_join(); +}; + +template +plr_t base_protocol

::get_master() +{ + plr_t ret = plr_self; + for (plr_t i = 0; i < MAX_PLRS; ++i) + if(peers[i]) + ret = std::min(ret, i); + return ret; +} + +template +bool base_protocol

::wait_network() +{ + // wait for ZeroTier for 5 seconds + for (auto i = 0; i < 500; ++i) { + if (proto.network_online()) + break; + SDL_Delay(10); + } + return proto.network_online(); +} + +template +void base_protocol

::disconnect_net(plr_t plr) +{ + proto.disconnect(peers[plr]); + peers[plr] = endpoint(); +} + +template +bool base_protocol

::wait_firstpeer() +{ + // wait for peer for 5 seconds + for (auto i = 0; i < 500; ++i) { + if (game_list.count(gamename)) { + firstpeer = game_list[gamename]; + break; + } + send_info_request(); + recv(); + SDL_Delay(10); + } + return (bool)firstpeer; +} + +template +void base_protocol

::send_info_request() +{ + auto pkt = pktfty->make_packet(PLR_BROADCAST, + PLR_MASTER); + proto.send_oob_mc(pkt->data()); +} + +template +void base_protocol

::wait_join() +{ + randombytes_buf(reinterpret_cast(&cookie_self), + sizeof(cookie_t)); + auto pkt = pktfty->make_packet(PLR_BROADCAST, + PLR_MASTER, cookie_self, game_init_info); + proto.send(firstpeer, pkt->data()); + for (auto i = 0; i < 500; ++i) { + recv(); + if (plr_self != PLR_BROADCAST) + break; // join successful + SDL_Delay(10); + } +} + +template +int base_protocol

::create(std::string addrstr, std::string passwd) +{ + setup_password(passwd); + gamename = addrstr; + + if(wait_network()) { + plr_self = 0; + connected_table[plr_self] = true; + } + + return (plr_self == PLR_BROADCAST ? MAX_PLRS : plr_self); +} + +template +int base_protocol

::join(std::string addrstr, std::string passwd) +{ + //addrstr = "fd80:56c2:e21c:0:199:931d:b14:c4d2"; + setup_password(passwd); + gamename = addrstr; + if(wait_network()) + if(wait_firstpeer()) + wait_join(); + return (plr_self == PLR_BROADCAST ? MAX_PLRS : plr_self); +} + +template +void base_protocol

::poll() +{ + recv(); +} + +template +void base_protocol

::send(packet &pkt) +{ + if(pkt.dest() < MAX_PLRS) { + if(pkt.dest() == myplr) + return; + if(peers[pkt.dest()]) + proto.send(peers[pkt.dest()], pkt.data()); + } else if(pkt.dest() == PLR_BROADCAST) { + for (auto &peer : peers) + if(peer) + proto.send(peer, pkt.data()); + } else if(pkt.dest() == PLR_MASTER) { + throw dvlnet_exception(); + } else { + throw dvlnet_exception(); + } +} + +template +void base_protocol

::recv() +{ + try { + buffer_t pkt_buf; + endpoint sender; + while (proto.recv(sender, pkt_buf)) { // read until kernel buffer is empty? + try { + auto pkt = pktfty->make_packet(pkt_buf); + recv_decrypted(*pkt, sender); + } catch (packet_exception &e) { + // drop packet + proto.disconnect(sender); + SDL_Log(e.what()); + } + } + } catch (std::exception &e) { + SDL_Log(e.what()); + return; + } +} + +template +void base_protocol

::handle_join_request(packet &pkt, endpoint sender) +{ + plr_t i; + for (i = 0; i < MAX_PLRS; ++i) { + if (i != plr_self && !peers[i]) { + peers[i] = sender; + break; + } + } + if(i >= MAX_PLRS) { + //already full + return; + } + for (plr_t j = 0; j < MAX_PLRS; ++j) { + if ((j != plr_self) && (j != i) && peers[j]) { + auto infopkt = pktfty->make_packet(PLR_MASTER, PLR_BROADCAST, j, peers[j].serialize()); + proto.send(sender, infopkt->data()); + break; + } + } + auto reply = pktfty->make_packet(plr_self, PLR_BROADCAST, + pkt.cookie(), i, + game_init_info); + proto.send(sender, reply->data()); +} + +template +void base_protocol

::recv_decrypted(packet &pkt, endpoint sender) +{ + if (pkt.src() == PLR_BROADCAST && pkt.dest() == PLR_MASTER && pkt.type() == PT_INFO_REPLY) { + std::string pname; + pname.resize(pkt.info().size()); + std::memcpy(&pname[0], pkt.info().data(), pkt.info().size()); + game_list[pname] = sender; + return; + } + recv_ingame(pkt, sender); +} + +template +void base_protocol

::recv_ingame(packet &pkt, endpoint sender) +{ + if (pkt.src() == PLR_BROADCAST && pkt.dest() == PLR_MASTER) { + if(pkt.type() == PT_JOIN_REQUEST) { + handle_join_request(pkt, sender); + } else if(pkt.type() == PT_INFO_REQUEST) { + if((plr_self != PLR_BROADCAST) && (get_master() == plr_self)) { + buffer_t buf; + buf.resize(gamename.size()); + std::memcpy(buf.data(), &gamename[0], gamename.size()); + auto reply = pktfty->make_packet(PLR_BROADCAST, + PLR_MASTER, + buf); + proto.send_oob(sender, reply->data()); + } + } + return; + } else if (pkt.src() == PLR_MASTER && pkt.type() == PT_CONNECT) { + // addrinfo packets + connected_table[pkt.newplr()] = true; + peers[pkt.newplr()].unserialize(pkt.info()); + return; + } else if (pkt.src() >= MAX_PLRS) { + // normal packets + ABORT(); + } + connected_table[pkt.src()] = true; + peers[pkt.src()] = sender; + if (pkt.dest() != plr_self && pkt.dest() != PLR_BROADCAST) + return; //packet not for us, drop + recv_local(pkt); +} + +template +std::vector base_protocol

::get_gamelist() +{ + recv(); + std::vector ret; + for (auto& s : game_list) { + ret.push_back(s.first); + } + return ret; +} + +template +bool base_protocol

::SNetLeaveGame(int type) +{ + auto ret = base::SNetLeaveGame(type); + recv(); + return ret; +} + +template +std::string base_protocol

::make_default_gamename() +{ + return proto.make_default_gamename(); +} + +} // namespace net +} // namespace devilution diff --git a/SourceX/dvlnet/cdwrap.h b/SourceX/dvlnet/cdwrap.h index a7069084b..a3498655e 100644 --- a/SourceX/dvlnet/cdwrap.h +++ b/SourceX/dvlnet/cdwrap.h @@ -41,10 +41,18 @@ public: virtual bool SNetGetOwnerTurnsWaiting(DWORD *turns); virtual bool SNetGetTurnsInTransit(DWORD *turns); virtual void setup_gameinfo(buffer_t info); + virtual std::string make_default_gamename(); + cdwrap(); virtual ~cdwrap() = default; }; +template +cdwrap::cdwrap() +{ + reset(); +} + template void cdwrap::reset() { @@ -65,6 +73,7 @@ int cdwrap::create(std::string addrstr, std::string passwd) template int cdwrap::join(std::string addrstr, std::string passwd) { + game_init_info = buffer_t(); reset(); return dvlnet_wrap->join(addrstr, passwd); } @@ -151,5 +160,11 @@ bool cdwrap::SNetGetTurnsInTransit(DWORD *turns) return dvlnet_wrap->SNetGetTurnsInTransit(turns); } +template +std::string cdwrap::make_default_gamename() +{ + return dvlnet_wrap->make_default_gamename(); +} + } // namespace net -} // namespace dvl +} // namespace devilution diff --git a/SourceX/dvlnet/frame_queue.h b/SourceX/dvlnet/frame_queue.h index 38c993b78..16ee8a41c 100644 --- a/SourceX/dvlnet/frame_queue.h +++ b/SourceX/dvlnet/frame_queue.h @@ -1,13 +1,16 @@ #pragma once #include - -#include "dvlnet/abstract_net.h" +#include +#include +#include namespace devilution { namespace net { -class frame_queue_exception : public dvlnet_exception { +typedef std::vector buffer_t; + +class frame_queue_exception : public std::exception { public: const char *what() const throw() override { @@ -38,4 +41,4 @@ public: }; } // namespace net -} // namespace dvl +} // namespace devilution diff --git a/SourceX/dvlnet/loopback.cpp b/SourceX/dvlnet/loopback.cpp index 4b33f6a07..e21ae025f 100644 --- a/SourceX/dvlnet/loopback.cpp +++ b/SourceX/dvlnet/loopback.cpp @@ -38,13 +38,15 @@ bool loopback::SNetSendMessage(int dest, void *data, unsigned int size) bool loopback::SNetReceiveTurns(char **data, unsigned int *size, DWORD *status) { - // todo: check that this is safe + for (auto i = 0; i < MAX_PLRS; ++i) { + size[i] = 0; + data[i] = nullptr; + } return true; } bool loopback::SNetSendTurn(char *data, unsigned int size) { - // todo: check that this is safe return true; } @@ -105,5 +107,10 @@ bool loopback::SNetGetTurnsInTransit(DWORD *turns) return true; } +std::string loopback::make_default_gamename() +{ + return std::string("loopback"); +} + } // namespace net } // namespace devilution diff --git a/SourceX/dvlnet/loopback.h b/SourceX/dvlnet/loopback.h index bd8e7a2a4..d7f4da92d 100644 --- a/SourceX/dvlnet/loopback.h +++ b/SourceX/dvlnet/loopback.h @@ -38,7 +38,8 @@ public: virtual bool SNetGetOwnerTurnsWaiting(DWORD *turns); virtual bool SNetGetTurnsInTransit(DWORD *turns); virtual void setup_gameinfo(buffer_t info); + virtual std::string make_default_gamename(); }; } // namespace net -} // namespace dvl +} // namespace devilution diff --git a/SourceX/dvlnet/packet.cpp b/SourceX/dvlnet/packet.cpp index 029855b09..97bf0a910 100644 --- a/SourceX/dvlnet/packet.cpp +++ b/SourceX/dvlnet/packet.cpp @@ -22,6 +22,10 @@ const char *packet_type_to_string(uint8_t packet_type) return "PT_CONNECT"; case PT_DISCONNECT: return "PT_DISCONNECT"; + case PT_INFO_REQUEST: + return "PT_INFO_REQUEST"; + case PT_INFO_REPLY: + return "PT_INFO_REPLY"; default: return NULL; } @@ -124,7 +128,7 @@ const buffer_t &packet::info() { if (!have_decrypted) ABORT(); - CheckPacketTypeOneOf({ PT_JOIN_REQUEST, PT_JOIN_ACCEPT }, m_type); + CheckPacketTypeOneOf({ PT_JOIN_REQUEST, PT_JOIN_ACCEPT, PT_CONNECT, PT_INFO_REPLY }, m_type); return m_info; } diff --git a/SourceX/dvlnet/packet.h b/SourceX/dvlnet/packet.h index ccaf07c39..6f1d4071e 100644 --- a/SourceX/dvlnet/packet.h +++ b/SourceX/dvlnet/packet.h @@ -21,6 +21,8 @@ enum packet_type : uint8_t { PT_JOIN_ACCEPT = 0x12, PT_CONNECT = 0x13, PT_DISCONNECT = 0x14, + PT_INFO_REQUEST = 0x21, + PT_INFO_REPLY = 0x22 }; // Returns NULL for an invalid packet type. @@ -152,11 +154,17 @@ void packet_proc

::process_data() break; case PT_CONNECT: self.process_element(m_newplr); + self.process_element(m_info); break; case PT_DISCONNECT: self.process_element(m_newplr); self.process_element(m_leaveinfo); break; + case PT_INFO_REPLY: + self.process_element(m_info); + break; + case PT_INFO_REQUEST: + break; } } @@ -176,6 +184,29 @@ void packet_in::process_element(T &x) decrypted_buffer.begin() + sizeof(T)); } +template <> +inline void packet_out::create(plr_t s, plr_t d) +{ + if (have_encrypted || have_decrypted) + ABORT(); + have_decrypted = true; + m_type = PT_INFO_REQUEST; + m_src = s; + m_dest = d; +} + +template <> +inline void packet_out::create(plr_t s, plr_t d, buffer_t i) +{ + if (have_encrypted || have_decrypted) + ABORT(); + have_decrypted = true; + m_type = PT_INFO_REPLY; + m_src = s; + m_dest = d; + m_info = std::move(i); +} + template <> inline void packet_out::create(plr_t s, plr_t d, buffer_t m) { @@ -229,6 +260,19 @@ inline void packet_out::create(plr_t s, plr_t d, cookie_t c, m_info = i; } +template <> +inline void packet_out::create(plr_t s, plr_t d, plr_t n, buffer_t i) +{ + if (have_encrypted || have_decrypted) + ABORT(); + have_decrypted = true; + m_type = PT_CONNECT; + m_src = s; + m_dest = d; + m_newplr = n; + m_info = i; +} + template <> inline void packet_out::create(plr_t s, plr_t d, plr_t n) { @@ -308,4 +352,4 @@ std::unique_ptr packet_factory::make_packet(Args... args) } } // namespace net -} // namespace dvl +} // namespace devilution diff --git a/SourceX/dvlnet/protocol_zt.cpp b/SourceX/dvlnet/protocol_zt.cpp new file mode 100644 index 000000000..ceb01a096 --- /dev/null +++ b/SourceX/dvlnet/protocol_zt.cpp @@ -0,0 +1,304 @@ +#include "dvlnet/protocol_zt.h" + +#include + +#include + +#ifdef USE_SDL1 +#include "sdl2_to_1_2_backports.h" +#else +#include "sdl2_backports.h" +#endif + +#include +#include +#include +#include + +#include "dvlnet/zerotier_native.h" + +namespace devilution { +namespace net { + +protocol_zt::protocol_zt() +{ + zerotier_network_start(); +} + +void protocol_zt::set_nonblock(int fd) +{ + static_assert(O_NONBLOCK == 1, "O_NONBLOCK == 1 not satisfied"); + auto mode = lwip_fcntl(fd, F_GETFL, 0); + mode |= O_NONBLOCK; + lwip_fcntl(fd, F_SETFL, mode); +} + +void protocol_zt::set_nodelay(int fd) +{ + const int yes = 1; + lwip_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&yes, sizeof(yes)); +} + +void protocol_zt::set_reuseaddr(int fd) +{ + const int yes = 1; + lwip_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)); +} + +bool protocol_zt::network_online() +{ + if(!zerotier_network_ready()) + return false; + + struct sockaddr_in6 in6{}; + in6.sin6_port = htons(default_port); + in6.sin6_family = AF_INET6; + in6.sin6_addr = in6addr_any; + + if(fd_udp == -1) { + fd_udp = lwip_socket(AF_INET6, SOCK_DGRAM, 0); + set_reuseaddr(fd_udp); + auto ret = lwip_bind(fd_udp, (struct sockaddr *)&in6, sizeof(in6)); + if(ret < 0) { + SDL_Log("lwip, (udp) bind: %s\n", strerror(errno)); + throw protocol_exception(); + } + set_nonblock(fd_udp); + } + if(fd_tcp == -1) { + fd_tcp = lwip_socket(AF_INET6, SOCK_STREAM, 0); + set_reuseaddr(fd_tcp); + auto r1 = lwip_bind(fd_tcp, (struct sockaddr *)&in6, sizeof(in6)); + if(r1 < 0) { + SDL_Log("lwip, (tcp) bind: %s\n", strerror(errno)); + throw protocol_exception(); + } + auto r2 = lwip_listen(fd_tcp, 10); + if(r2 < 0) { + SDL_Log("lwip, listen: %s\n", strerror(errno)); + throw protocol_exception(); + } + set_nonblock(fd_tcp); + set_nodelay(fd_tcp); + } + return true; +} + +bool protocol_zt::send(const endpoint& peer, const buffer_t& data) +{ + peer_list[peer].send_queue.push_back(frame_queue::make_frame(data)); + return true; +} + +bool protocol_zt::send_oob(const endpoint& peer, const buffer_t& data) +{ + struct sockaddr_in6 in6{}; + in6.sin6_port = htons(default_port); + in6.sin6_family = AF_INET6; + std::copy(peer.addr.begin(), peer.addr.end(), in6.sin6_addr.s6_addr); + lwip_sendto(fd_udp, data.data(), data.size(), 0, (const struct sockaddr *)&in6, sizeof(in6)); + return true; +} + +bool protocol_zt::send_oob_mc(const buffer_t& data) +{ + endpoint mc; + std::copy(dvl_multicast_addr, dvl_multicast_addr+16, mc.addr.begin()); + return send_oob(mc, data); +} + +bool protocol_zt::send_queued_peer(const endpoint& peer) +{ + if(peer_list[peer].fd == -1) { + peer_list[peer].fd = lwip_socket(AF_INET6, SOCK_STREAM, 0); + set_nodelay(peer_list[peer].fd); + set_nonblock(peer_list[peer].fd); + struct sockaddr_in6 in6{}; + in6.sin6_port = htons(default_port); + in6.sin6_family = AF_INET6; + std::copy(peer.addr.begin(), peer.addr.end(), in6.sin6_addr.s6_addr); + lwip_connect(peer_list[peer].fd, (const struct sockaddr *)&in6, sizeof(in6)); + } + while(!peer_list[peer].send_queue.empty()) { + auto len = peer_list[peer].send_queue.front().size(); + auto r = lwip_send(peer_list[peer].fd, peer_list[peer].send_queue.front().data(), len, 0); + if(r < 0) { + // handle error + return false; + } else if (decltype(len)(r) < len) { + // partial send + auto it = peer_list[peer].send_queue.front().begin(); + peer_list[peer].send_queue.front().erase(it, it+r); + return true; + } else if (decltype(len)(r) == len) { + peer_list[peer].send_queue.pop_front(); + } else { + throw protocol_exception(); + } + } + return true; +} + +bool protocol_zt::recv_peer(const endpoint& peer) +{ + unsigned char buf[PKTBUF_LEN]; + while(true) { + auto len = lwip_recv(peer_list[peer].fd, buf, sizeof(buf), 0); + if(len >= 0) { + peer_list[peer].recv_queue.write(buffer_t(buf, buf+len)); + } else { + if(errno == EAGAIN || errno == EWOULDBLOCK) { + return true; + } else { + return false; + } + } + } +} + +bool protocol_zt::send_queued_all() +{ + for(auto& peer : peer_list) { + if(!send_queued_peer(peer.first)) { + // disconnect this peer + } + } + return true; +} + +bool protocol_zt::recv_from_peers() +{ + for(auto& peer : peer_list) { + if(peer.second.fd != -1) { + if(!recv_peer(peer.first)) { + // error, disconnect? + } + } + } + return true; +} + +bool protocol_zt::recv_from_udp() +{ + unsigned char buf[PKTBUF_LEN]; + struct sockaddr_in6 in6{}; + socklen_t addrlen = sizeof(in6); + auto len = lwip_recvfrom(fd_udp, buf, sizeof(buf), 0, (struct sockaddr *)&in6, &addrlen); + if(len < 0) + return false; + buffer_t data(buf, buf+len); + endpoint ep; + std::copy(in6.sin6_addr.s6_addr, in6.sin6_addr.s6_addr+16, ep.addr.begin()); + oob_recv_queue.push_back(std::make_pair(std::move(ep), std::move(data))); + return true; +} + +bool protocol_zt::accept_all() +{ + struct sockaddr_in6 in6{}; + socklen_t addrlen = sizeof(in6); + while(true) { + auto newfd = lwip_accept(fd_tcp, (struct sockaddr *)&in6, &addrlen); + if(newfd < 0) + break; + endpoint ep; + std::copy(in6.sin6_addr.s6_addr, in6.sin6_addr.s6_addr+16, ep.addr.begin()); + if(peer_list[ep].fd != -1) { + SDL_Log("protocol_zt::accept_all: WARNING: overwriting connection\n"); + lwip_close(peer_list[ep].fd); + } + set_nonblock(newfd); + set_nodelay(newfd); + peer_list[ep].fd = newfd; + } + return true; +} + +bool protocol_zt::recv(endpoint& peer, buffer_t& data) +{ + accept_all(); + send_queued_all(); + recv_from_peers(); + recv_from_udp(); + + if(!oob_recv_queue.empty()) { + peer = oob_recv_queue.front().first; + data = oob_recv_queue.front().second; + oob_recv_queue.pop_front(); + return true; + } + + for(auto& p : peer_list) { + if(p.second.recv_queue.packet_ready()) { + peer = p.first; + data = p.second.recv_queue.read_packet(); + return true; + } + } + return false; +} + +void protocol_zt::disconnect(const endpoint& peer) +{ + if(peer_list.count(peer)) { + if(peer_list[peer].fd != -1) { + if(lwip_close(peer_list[peer].fd) < 0) { + SDL_Log("lwip_close: %s\n", strerror(errno)); + } + } + peer_list.erase(peer); + } +} + +void protocol_zt::close_all() +{ + if(fd_tcp != -1) { + lwip_close(fd_tcp); + fd_tcp = -1; + } + if(fd_udp != -1) { + lwip_close(fd_udp); + fd_udp = -1; + } + for(auto& i : peer_list) { + if(i.second.fd != -1) + lwip_close(i.second.fd); + } + peer_list.clear(); +} + +protocol_zt::~protocol_zt() +{ + close_all(); +} + +void protocol_zt::endpoint::from_string(const std::string& str) +{ + ip_addr_t a; + if(!ipaddr_aton(str.c_str(), &a)) + return; + if(!IP_IS_V6_VAL(a)) + return; + const unsigned char* r = reinterpret_cast(a.u_addr.ip6.addr); + std::copy(r, r+16, addr.begin()); +} + +uint64_t protocol_zt::current_ms() +{ + return 0; +} + +std::string protocol_zt::make_default_gamename() +{ + std::string ret; + std::string allowed_chars = "abcdefghkopqrstuvwxyz"; + std::random_device rd; + std::uniform_int_distribution dist(0, allowed_chars.size() - 1); + for(int i = 0; i < 5; ++i) { + ret += allowed_chars.at(dist(rd)); + } + return ret; +} + +} // namespace net +} // namespace devilution diff --git a/SourceX/dvlnet/protocol_zt.h b/SourceX/dvlnet/protocol_zt.h new file mode 100644 index 000000000..d6d3ecaa8 --- /dev/null +++ b/SourceX/dvlnet/protocol_zt.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dvlnet/frame_queue.h" + +namespace devilution { +namespace net { + +class protocol_exception : public std::exception { + public: + const char *what() const throw() override + { + return "Protocol error"; + } +}; + +class protocol_zt { +public: + class endpoint { + public: + std::array addr = {}; + + explicit operator bool() const + { + auto empty = std::array{}; + return (addr != empty); + } + + bool operator<(const endpoint& rhs) const + { + return addr < rhs.addr; + } + + buffer_t serialize() const + { + return buffer_t(addr.begin(), addr.end()); + } + + void unserialize(const buffer_t& buf) + { + if(buf.size() != 16) + throw protocol_exception(); + std::copy(buf.begin(), buf.end(), addr.begin()); + } + + void from_string(const std::string& str); + }; + + protocol_zt(); + ~protocol_zt(); + void disconnect(const endpoint& peer); + bool send(const endpoint& peer, const buffer_t& data); + bool send_oob(const endpoint& peer, const buffer_t& data); + bool send_oob_mc(const buffer_t& data); + bool recv(endpoint& peer, buffer_t& data); + bool network_online(); + std::string make_default_gamename(); + +private: + static constexpr uint32_t PKTBUF_LEN = 65536; + static constexpr uint16_t default_port = 6112; + + struct peer_state { + int fd = -1; + std::deque send_queue; + frame_queue recv_queue; + }; + + std::deque> oob_recv_queue; + + std::map peer_list; + int fd_tcp = -1; + int fd_udp = -1; + + uint64_t current_ms(); + void close_all(); + + void set_nonblock(int fd); + void set_nodelay(int fd); + void set_reuseaddr(int fd); + + bool send_queued_peer(const endpoint& peer); + bool recv_peer(const endpoint& peer); + bool send_queued_all(); + bool recv_from_peers(); + bool recv_from_udp(); + bool accept_all(); +}; + +} // namespace net +} // namespace devilution diff --git a/SourceX/dvlnet/tcp_client.cpp b/SourceX/dvlnet/tcp_client.cpp index b4b48e41f..30702f1af 100644 --- a/SourceX/dvlnet/tcp_client.cpp +++ b/SourceX/dvlnet/tcp_client.cpp @@ -127,6 +127,11 @@ bool tcp_client::SNetLeaveGame(int type) return ret; } +std::string tcp_client::make_default_gamename() +{ + return std::string(sgOptions.Network.szBindAddress); +} + tcp_client::~tcp_client() { } diff --git a/SourceX/dvlnet/tcp_client.h b/SourceX/dvlnet/tcp_client.h index 7382fbfdc..1863cd1ef 100644 --- a/SourceX/dvlnet/tcp_client.h +++ b/SourceX/dvlnet/tcp_client.h @@ -27,6 +27,7 @@ public: virtual ~tcp_client(); + virtual std::string make_default_gamename(); private: frame_queue recv_queue; buffer_t recv_buffer = buffer_t(frame_queue::max_frame_size); @@ -42,4 +43,4 @@ private: }; } // namespace net -} // namespace dvl +} // namespace devilution diff --git a/SourceX/dvlnet/tcp_server.h b/SourceX/dvlnet/tcp_server.h index 8617a0d3e..b9a355860 100644 --- a/SourceX/dvlnet/tcp_server.h +++ b/SourceX/dvlnet/tcp_server.h @@ -76,4 +76,4 @@ private: }; } //namespace net -} //namespace dvl +} //namespace devilution diff --git a/SourceX/dvlnet/udp_p2p.cpp b/SourceX/dvlnet/udp_p2p.cpp deleted file mode 100644 index 257e2dc80..000000000 --- a/SourceX/dvlnet/udp_p2p.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "dvlnet/udp_p2p.h" -#include "options.h" - -#include - -#ifdef USE_SDL1 -#include "sdl2_to_1_2_backports.h" -#endif - -namespace devilution { -namespace net { - -const udp_p2p::endpoint udp_p2p::none; - -int udp_p2p::create(std::string addrstr, std::string passwd) -{ - sock = asio::ip::udp::socket(io_context); // to be removed later - setup_password(passwd); - auto ipaddr = asio::ip::make_address(addrstr); - if (ipaddr.is_v4()) - sock.open(asio::ip::udp::v4()); - else if (ipaddr.is_v6()) - sock.open(asio::ip::udp::v6()); - sock.non_blocking(true); - - try { - sock.bind(endpoint(ipaddr, sgOptions.Network.nPort)); - } catch (std::exception &e) { - SDL_SetError(e.what()); - return -1; - } - plr_self = 0; - return plr_self; -} - -int udp_p2p::join(std::string addrstr, std::string passwd) -{ - sock = asio::ip::udp::socket(io_context); // to be removed later - setup_password(passwd); - auto ipaddr = asio::ip::make_address(addrstr); - if (ipaddr.is_v4()) - sock.open(asio::ip::udp::v4()); - else if (ipaddr.is_v6()) - sock.open(asio::ip::udp::v6()); - sock.non_blocking(true); - endpoint themaster(ipaddr, sgOptions.Network.nPort); - sock.connect(themaster); - master = themaster; - { // hack: try to join for 5 seconds - randombytes_buf(reinterpret_cast(&cookie_self), - sizeof(cookie_t)); - auto pkt = pktfty->make_packet(PLR_BROADCAST, - PLR_MASTER, cookie_self, - game_init_info); - send(*pkt); - for (auto i = 0; i < 5; ++i) { - recv(); - if (plr_self != PLR_BROADCAST) - break; // join successful - SDL_Delay(1000); - } - } - return (plr_self == PLR_BROADCAST ? MAX_PLRS : plr_self); -} - -void udp_p2p::poll() -{ - recv(); -} - -void udp_p2p::send(packet &pkt) -{ - send_internal(pkt, none); -} - -void udp_p2p::recv() -{ - try { - while (1) { // read until kernel buffer is empty? - try { - endpoint sender; - buffer_t pkt_buf(packet_factory::max_packet_size); - size_t pkt_len; - pkt_len = sock.receive_from(asio::buffer(pkt_buf), sender); - pkt_buf.resize(pkt_len); - auto pkt = pktfty->make_packet(pkt_buf); - recv_decrypted(*pkt, sender); - } catch (packet_exception &e) { - SDL_Log(e.what()); - // drop packet - } - } - } catch (std::exception &e) { - SDL_Log(e.what()); - return; - } -} - -void udp_p2p::send_internal(packet &pkt, endpoint sender) -{ - for (auto &dest : dests_for_addr(pkt.dest(), sender)) { - sock.send_to(asio::buffer(pkt.data()), dest); - } -} - -std::set udp_p2p::dests_for_addr(plr_t dest, endpoint sender) -{ - auto ret = std::set(); - if (dest == plr_self) - return ret; - - if (dest < MAX_PLRS) { - if (connected_table[dest]) - ret.insert(nexthop_table[dest]); - } else if (dest == PLR_BROADCAST) { - for (auto i = 0; i < MAX_PLRS; ++i) - if (i != plr_self && connected_table[i]) - ret.insert(nexthop_table[i]); - ret.insert(connection_requests_pending.begin(), - connection_requests_pending.end()); - } else if (dest == PLR_MASTER) { - if (master != none) - ret.insert(master); - } - ret.erase(sender); - return ret; -} - -void udp_p2p::handle_join_request(packet &pkt, endpoint sender) -{ - plr_t i; - for (i = 0; i < MAX_PLRS; ++i) { - if (i != plr_self && nexthop_table[i] == none) { - nexthop_table[i] = sender; - break; - } - } - auto reply = pktfty->make_packet(plr_self, PLR_BROADCAST, - pkt.cookie(), i, - game_init_info); - send(*reply); -} - -void udp_p2p::recv_decrypted(packet &pkt, endpoint sender) -{ - // 1. route - send_internal(pkt, sender); - // 2. handle local - if (pkt.src() == PLR_BROADCAST && pkt.dest() == PLR_MASTER) { - connection_requests_pending.insert(sender); - if (master == none) { - handle_join_request(pkt, sender); - } - } - // normal packets - if (pkt.src() >= MAX_PLRS) - return; //drop packet - if (connected_table[pkt.src()]) { //WRONG?!? - if (sender != nexthop_table[pkt.src()]) - return; //rpfilter fail: drop packet - } else { - nexthop_table[pkt.src()] = sender; // new connection: accept - } - connected_table[pkt.src()] = true; - if (pkt.dest() != plr_self && pkt.dest() != PLR_BROADCAST) - return; //packet not for us, drop - recv_local(pkt); -} - -} // namespace net -} // namespace devilution diff --git a/SourceX/dvlnet/udp_p2p.h b/SourceX/dvlnet/udp_p2p.h deleted file mode 100644 index 93d96c275..000000000 --- a/SourceX/dvlnet/udp_p2p.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "dvlnet/packet.h" -#include "dvlnet/base.h" - -namespace devilution { -namespace net { - -class udp_p2p : public base { -public: - virtual int create(std::string addrstr, std::string passwd); - virtual int join(std::string addrstr, std::string passwd); - virtual void poll(); - virtual void send(packet &pkt); - -private: - typedef asio::ip::udp::endpoint endpoint; - static const endpoint none; - - static constexpr int ACTIVE = 60; - - asio::io_context io_context; - endpoint master; - - std::set connection_requests_pending; - std::array nexthop_table; - - asio::ip::udp::socket sock = asio::ip::udp::socket(io_context); - - void recv(); - void handle_join_request(packet &pkt, endpoint sender); - void send_internal(packet &pkt, endpoint sender = none); - std::set dests_for_addr(plr_t dest, endpoint sender); - void recv_decrypted(packet &pkt, endpoint sender); -}; - -} // namespace net -} // namespace dvl diff --git a/SourceX/dvlnet/zerotier_lwip.cpp b/SourceX/dvlnet/zerotier_lwip.cpp new file mode 100644 index 000000000..811d5df50 --- /dev/null +++ b/SourceX/dvlnet/zerotier_lwip.cpp @@ -0,0 +1,40 @@ +#include "dvlnet/zerotier_lwip.h" + +#include +#include +#include +#include + +#include + +#ifdef USE_SDL1 +#include "sdl2_to_1_2_backports.h" +#else +#include "sdl2_backports.h" +#endif + +#include "dvlnet/zerotier_native.h" + +namespace devilution { +namespace net { + +void print_ip6_addr(void* x) +{ + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 *in = (struct sockaddr_in6*)x; + lwip_inet_ntop(AF_INET6, &(in->sin6_addr), ipstr, INET6_ADDRSTRLEN); + SDL_Log("ZeroTier: ZTS_EVENT_ADDR_NEW_IP6, addr=%s\n", ipstr); +} + +void zt_ip6setup() +{ + ip6_addr_t mcaddr; + memcpy(mcaddr.addr, dvl_multicast_addr, 16); + mcaddr.zone = 0; + LOCK_TCPIP_CORE(); + mld6_joingroup(IP6_ADDR_ANY6, &mcaddr); + UNLOCK_TCPIP_CORE(); +} + +} +} diff --git a/SourceX/dvlnet/zerotier_lwip.h b/SourceX/dvlnet/zerotier_lwip.h new file mode 100644 index 000000000..540fa5ded --- /dev/null +++ b/SourceX/dvlnet/zerotier_lwip.h @@ -0,0 +1,8 @@ +namespace devilution { +namespace net { + +void print_ip6_addr(void* x); +void zt_ip6setup(); + +} +} diff --git a/SourceX/dvlnet/zerotier_native.cpp b/SourceX/dvlnet/zerotier_native.cpp new file mode 100644 index 000000000..bc8da769b --- /dev/null +++ b/SourceX/dvlnet/zerotier_native.cpp @@ -0,0 +1,71 @@ +#include "dvlnet/zerotier_native.h" + +#include +#include + +#ifdef USE_SDL1 +#include "sdl2_to_1_2_backports.h" +#else +#include "sdl2_backports.h" +#endif + +#include +#include + +#include "paths.h" + +#include "dvlnet/zerotier_lwip.h" + +namespace devilution { +namespace net { + +//static constexpr uint64_t zt_earth = 0x8056c2e21c000001; +static constexpr uint64_t zt_network = 0xaf78bf943649eb12; + +static std::atomic_bool zt_network_ready(false); +static std::atomic_bool zt_node_online(false); +static std::atomic_bool zt_started(false); +static std::atomic_bool zt_joined(false); + +static void callback(struct zts_callback_msg *msg) +{ + //printf("callback %i\n", msg->eventCode); + if(msg->eventCode == ZTS_EVENT_NODE_ONLINE) { + SDL_Log("ZeroTier: ZTS_EVENT_NODE_ONLINE, nodeId=%llx\n", (unsigned long long)msg->node->address); + zt_node_online = true; + if(!zt_joined) { + zts_join(zt_network); + zt_joined = true; + } + } else if(msg->eventCode == ZTS_EVENT_NODE_OFFLINE) { + SDL_Log("ZeroTier: ZTS_EVENT_NODE_OFFLINE\n"); + zt_node_online = false; + } else if(msg->eventCode == ZTS_EVENT_NETWORK_READY_IP6) { + SDL_Log("ZeroTier: ZTS_EVENT_NETWORK_READY_IP6, networkId=%llx\n", (unsigned long long)msg->network->nwid); + zt_ip6setup(); + zt_network_ready = true; + } else if(msg->eventCode == ZTS_EVENT_ADDR_ADDED_IP6) { + print_ip6_addr(&(msg->addr->addr)); + } +} + +bool zerotier_network_ready() +{ + return zt_network_ready && zt_node_online; +} + +void zerotier_network_stop() +{ + zts_stop(); +} + +void zerotier_network_start() +{ + if(zt_started) + return; + zts_start(GetPrefPath().c_str(), (void(*)(void*))callback, 0); + std::atexit(zerotier_network_stop); +} + +} +} diff --git a/SourceX/dvlnet/zerotier_native.h b/SourceX/dvlnet/zerotier_native.h new file mode 100644 index 000000000..06512d5d2 --- /dev/null +++ b/SourceX/dvlnet/zerotier_native.h @@ -0,0 +1,17 @@ +#pragma once + +namespace devilution { +namespace net { + +bool zerotier_network_ready(); +void zerotier_network_start(); +void zerotier_network_stop(); + +// NOTE: We have patched our libzt to have the corresponding multicast +// MAC hardcoded, since libzt is still missing the proper handling. +const unsigned char dvl_multicast_addr[16] = + {0xff, 0x0e, 0xa8, 0xa9, 0xb6, 0x11, 0x58, 0xce, + 0x04, 0x12, 0xfd, 0x73, 0x37, 0x86, 0x6f, 0xb7}; + +} +} diff --git a/SourceX/storm/storm_dvlnet.h b/SourceX/storm/storm_dvlnet.h new file mode 100644 index 000000000..f7534a194 --- /dev/null +++ b/SourceX/storm/storm_dvlnet.h @@ -0,0 +1,5 @@ +#pragma once + +void DvlNet_SendInfoRequest(); +std::vector DvlNet_GetGamelist(); +void DvlNet_SetPassword(std::string pw); diff --git a/SourceX/storm/storm_net.cpp b/SourceX/storm/storm_net.cpp index 1179373c7..056b4bbaa 100644 --- a/SourceX/storm/storm_net.cpp +++ b/SourceX/storm/storm_net.cpp @@ -1,9 +1,14 @@ #include +#ifndef NONET +#include +#include +#endif #include "all.h" #include "options.h" #include "stubs.h" #include "dvlnet/abstract_net.h" +#include "storm/storm_dvlnet.h" namespace devilution { @@ -11,8 +16,15 @@ static std::unique_ptr dvlnet_inst; static char gpszGameName[128] = {}; static char gpszGamePassword[128] = {}; +#ifndef NONET +static std::mutex storm_net_mutex; +#endif + BOOL SNetReceiveMessage(int *senderplayerid, char **data, int *databytes) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif if (!dvlnet_inst->SNetReceiveMessage(senderplayerid, data, databytes)) { SErrSetLastError(STORM_ERROR_NO_MESSAGES_WAITING); return false; @@ -22,12 +34,18 @@ BOOL SNetReceiveMessage(int *senderplayerid, char **data, int *databytes) BOOL SNetSendMessage(int playerID, void *data, unsigned int databytes) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return dvlnet_inst->SNetSendMessage(playerID, data, databytes); } BOOL SNetReceiveTurns(int a1, int arraysize, char **arraydata, unsigned int *arraydatabytes, DWORD *arrayplayerstatus) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif if (a1 != 0) UNIMPLEMENTED(); if (arraysize != MAX_PLRS) @@ -41,36 +59,57 @@ BOOL SNetReceiveTurns(int a1, int arraysize, char **arraydata, unsigned int *arr BOOL SNetSendTurn(char *data, unsigned int databytes) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return dvlnet_inst->SNetSendTurn(data, databytes); } int SNetGetProviderCaps(struct _SNETCAPS *caps) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return dvlnet_inst->SNetGetProviderCaps(caps); } bool SNetUnregisterEventHandler(event_type evtype, SEVTHANDLER func) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return dvlnet_inst->SNetUnregisterEventHandler(evtype, func); } bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return dvlnet_inst->SNetRegisterEventHandler(evtype, func); } BOOL SNetDestroy() { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return true; } BOOL SNetDropPlayer(int playerid, DWORD flags) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return dvlnet_inst->SNetDropPlayer(playerid, flags); } BOOL SNetGetGameInfo(int type, void *dst, unsigned int length) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif switch (type) { case GAMEINFO_NAME: strncpy((char *)dst, gpszGameName, length); @@ -85,6 +124,9 @@ BOOL SNetGetGameInfo(int type, void *dst, unsigned int length) BOOL SNetLeaveGame(int type) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif if (dvlnet_inst == NULL) return true; return dvlnet_inst->SNetLeaveGame(type); @@ -96,6 +138,9 @@ BOOL SNetLeaveGame(int type) */ int SNetInitializeProvider(Uint32 provider, struct GameData *gameData) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif dvlnet_inst = net::abstract_net::make_net(provider); return mainmenu_select_hero_dialog(gameData); } @@ -107,20 +152,32 @@ BOOL SNetCreateGame(const char *pszGameName, const char *pszGamePassword, const DWORD dwGameType, char *GameTemplateData, int GameTemplateSize, int playerCount, const char *creatorName, const char *a11, int *playerID) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif if (GameTemplateSize != sizeof(GameData)) ABORT(); net::buffer_t game_init_info(GameTemplateData, GameTemplateData + GameTemplateSize); dvlnet_inst->setup_gameinfo(std::move(game_init_info)); - strncpy(gpszGameName, sgOptions.Network.szBindAddress, sizeof(gpszGameName) - 1); + std::string default_name; + if(!pszGameName) { + default_name = dvlnet_inst->make_default_gamename(); + pszGameName = default_name.c_str(); + } + + strncpy(gpszGameName, pszGameName, sizeof(gpszGameName) - 1); if (pszGamePassword) strncpy(gpszGamePassword, pszGamePassword, sizeof(gpszGamePassword) - 1); - *playerID = dvlnet_inst->create(sgOptions.Network.szBindAddress, pszGamePassword); + *playerID = dvlnet_inst->create(pszGameName, pszGamePassword); return *playerID != -1; } BOOL SNetJoinGame(int id, char *pszGameName, char *pszGamePassword, char *playerName, char *userStats, int *playerID) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif if (pszGameName) strncpy(gpszGameName, pszGameName, sizeof(gpszGameName) - 1); if (pszGamePassword) @@ -134,11 +191,17 @@ BOOL SNetJoinGame(int id, char *pszGameName, char *pszGamePassword, char *player */ BOOL SNetGetOwnerTurnsWaiting(DWORD *turns) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return dvlnet_inst->SNetGetOwnerTurnsWaiting(turns); } BOOL SNetGetTurnsInTransit(DWORD *turns) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return dvlnet_inst->SNetGetTurnsInTransit(turns); } @@ -147,6 +210,9 @@ BOOL SNetGetTurnsInTransit(DWORD *turns) */ BOOLEAN SNetSetBasePlayer(int) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif return true; } @@ -155,7 +221,25 @@ BOOLEAN SNetSetBasePlayer(int) */ BOOL SNetPerformUpgrade(DWORD *upgradestatus) { +#ifndef NONET + std::lock_guard lg(storm_net_mutex); +#endif UNIMPLEMENTED(); } +void DvlNet_SendInfoRequest() +{ + dvlnet_inst->send_info_request(); +} + +std::vector DvlNet_GetGamelist() +{ + return dvlnet_inst->get_gamelist(); +} + +void DvlNet_SetPassword(std::string pw) +{ + dvlnet_inst->setup_password(pw); +} + } // namespace devilution diff --git a/enums.h b/enums.h index ce6b42935..a367a325e 100644 --- a/enums.h +++ b/enums.h @@ -2338,8 +2338,8 @@ typedef enum dlrg_flag { } dlrg_flag; typedef enum conn_type { + SELCONN_ZT, SELCONN_TCP, - SELCONN_UDP, SELCONN_LOOPBACK, } conn_type;