31 changed files with 1128 additions and 245 deletions
@ -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() |
||||
@ -0,0 +1,14 @@
|
||||
#include "dvlnet/base_protocol.h" |
||||
|
||||
#include <algorithm> |
||||
#include <SDL.h> |
||||
|
||||
#ifdef USE_SDL1 |
||||
#include "sdl2_to_1_2_backports.h" |
||||
#endif |
||||
|
||||
namespace devilution { |
||||
namespace net { |
||||
|
||||
} // namespace net
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,293 @@
|
||||
#pragma once |
||||
|
||||
#include <string> |
||||
#include <set> |
||||
#include <map> |
||||
#include <memory> |
||||
|
||||
#include "dvlnet/packet.h" |
||||
#include "dvlnet/base.h" |
||||
|
||||
namespace devilution { |
||||
namespace net { |
||||
|
||||
template<class P> |
||||
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<std::string> get_gamelist(); |
||||
|
||||
virtual ~base_protocol() = default; |
||||
|
||||
private: |
||||
P proto; |
||||
typedef typename P::endpoint endpoint; |
||||
|
||||
endpoint firstpeer; |
||||
std::string gamename; |
||||
std::map<std::string, endpoint> game_list; |
||||
std::array<endpoint, MAX_PLRS> 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<class P> |
||||
plr_t base_protocol<P>::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<class P> |
||||
bool base_protocol<P>::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<class P> |
||||
void base_protocol<P>::disconnect_net(plr_t plr) |
||||
{ |
||||
proto.disconnect(peers[plr]); |
||||
peers[plr] = endpoint(); |
||||
} |
||||
|
||||
template<class P> |
||||
bool base_protocol<P>::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<class P> |
||||
void base_protocol<P>::send_info_request() |
||||
{ |
||||
auto pkt = pktfty->make_packet<PT_INFO_REQUEST>(PLR_BROADCAST, |
||||
PLR_MASTER); |
||||
proto.send_oob_mc(pkt->data()); |
||||
} |
||||
|
||||
template<class P> |
||||
void base_protocol<P>::wait_join() |
||||
{ |
||||
randombytes_buf(reinterpret_cast<unsigned char *>(&cookie_self), |
||||
sizeof(cookie_t)); |
||||
auto pkt = pktfty->make_packet<PT_JOIN_REQUEST>(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<class P> |
||||
int base_protocol<P>::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<class P> |
||||
int base_protocol<P>::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<class P> |
||||
void base_protocol<P>::poll() |
||||
{ |
||||
recv(); |
||||
} |
||||
|
||||
template<class P> |
||||
void base_protocol<P>::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<class P> |
||||
void base_protocol<P>::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<class P> |
||||
void base_protocol<P>::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<PT_CONNECT>(PLR_MASTER, PLR_BROADCAST, j, peers[j].serialize()); |
||||
proto.send(sender, infopkt->data()); |
||||
break; |
||||
} |
||||
} |
||||
auto reply = pktfty->make_packet<PT_JOIN_ACCEPT>(plr_self, PLR_BROADCAST, |
||||
pkt.cookie(), i, |
||||
game_init_info); |
||||
proto.send(sender, reply->data()); |
||||
} |
||||
|
||||
template<class P> |
||||
void base_protocol<P>::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<class P> |
||||
void base_protocol<P>::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<PT_INFO_REPLY>(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<class P> |
||||
std::vector<std::string> base_protocol<P>::get_gamelist() |
||||
{ |
||||
recv(); |
||||
std::vector<std::string> ret; |
||||
for (auto& s : game_list) { |
||||
ret.push_back(s.first); |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
template<class P> |
||||
bool base_protocol<P>::SNetLeaveGame(int type) |
||||
{ |
||||
auto ret = base::SNetLeaveGame(type); |
||||
recv(); |
||||
return ret; |
||||
} |
||||
|
||||
template<class P> |
||||
std::string base_protocol<P>::make_default_gamename() |
||||
{ |
||||
return proto.make_default_gamename(); |
||||
} |
||||
|
||||
} // namespace net
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,304 @@
|
||||
#include "dvlnet/protocol_zt.h" |
||||
|
||||
#include <random> |
||||
|
||||
#include <SDL.h> |
||||
|
||||
#ifdef USE_SDL1 |
||||
#include "sdl2_to_1_2_backports.h" |
||||
#else |
||||
#include "sdl2_backports.h" |
||||
#endif |
||||
|
||||
#include <lwip/sockets.h> |
||||
#include <lwip/tcpip.h> |
||||
#include <lwip/mld6.h> |
||||
#include <lwip/igmp.h> |
||||
|
||||
#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<const unsigned char*>(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<int> 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
|
||||
@ -0,0 +1,99 @@
|
||||
#pragma once |
||||
|
||||
#include <string> |
||||
#include <set> |
||||
#include <atomic> |
||||
#include <deque> |
||||
#include <map> |
||||
#include <exception> |
||||
#include <array> |
||||
#include <algorithm> |
||||
|
||||
#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<unsigned char, 16> addr = {}; |
||||
|
||||
explicit operator bool() const |
||||
{ |
||||
auto empty = std::array<unsigned char, 16>{}; |
||||
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<buffer_t> send_queue; |
||||
frame_queue recv_queue; |
||||
}; |
||||
|
||||
std::deque<std::pair<endpoint, buffer_t>> oob_recv_queue; |
||||
|
||||
std::map<endpoint, peer_state> 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
|
||||
@ -1,171 +0,0 @@
|
||||
#include "dvlnet/udp_p2p.h" |
||||
#include "options.h" |
||||
|
||||
#include <SDL.h> |
||||
|
||||
#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<unsigned char *>(&cookie_self), |
||||
sizeof(cookie_t)); |
||||
auto pkt = pktfty->make_packet<PT_JOIN_REQUEST>(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::endpoint> udp_p2p::dests_for_addr(plr_t dest, endpoint sender) |
||||
{ |
||||
auto ret = std::set<endpoint>(); |
||||
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<PT_JOIN_ACCEPT>(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
|
||||
@ -1,45 +0,0 @@
|
||||
#pragma once |
||||
|
||||
#include <string> |
||||
#include <set> |
||||
#include <asio/ts/buffer.hpp> |
||||
#include <asio/ts/internet.hpp> |
||||
#include <asio/ts/io_context.hpp> |
||||
#include <asio/ts/net.hpp> |
||||
|
||||
#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<endpoint> connection_requests_pending; |
||||
std::array<endpoint, MAX_PLRS> 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<endpoint> dests_for_addr(plr_t dest, endpoint sender); |
||||
void recv_decrypted(packet &pkt, endpoint sender); |
||||
}; |
||||
|
||||
} // namespace net
|
||||
} // namespace dvl
|
||||
@ -0,0 +1,40 @@
|
||||
#include "dvlnet/zerotier_lwip.h" |
||||
|
||||
#include <lwip/sockets.h> |
||||
#include <lwip/tcpip.h> |
||||
#include <lwip/mld6.h> |
||||
#include <lwip/igmp.h> |
||||
|
||||
#include <SDL.h> |
||||
|
||||
#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(); |
||||
} |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,8 @@
|
||||
namespace devilution { |
||||
namespace net { |
||||
|
||||
void print_ip6_addr(void* x); |
||||
void zt_ip6setup(); |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@
|
||||
#include "dvlnet/zerotier_native.h" |
||||
|
||||
#include <atomic> |
||||
#include <SDL.h> |
||||
|
||||
#ifdef USE_SDL1 |
||||
#include "sdl2_to_1_2_backports.h" |
||||
#else |
||||
#include "sdl2_backports.h" |
||||
#endif |
||||
|
||||
#include <cstdlib> |
||||
#include <ZeroTierSockets.h> |
||||
|
||||
#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); |
||||
} |
||||
|
||||
} |
||||
} |
||||
@ -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}; |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,5 @@
|
||||
#pragma once |
||||
|
||||
void DvlNet_SendInfoRequest(); |
||||
std::vector<std::string> DvlNet_GetGamelist(); |
||||
void DvlNet_SetPassword(std::string pw); |
||||
Loading…
Reference in new issue