You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

236 lines
6.0 KiB

#include "dvlnet/zerotier_native.h"
#include <atomic>
#ifdef USE_SDL3
#include <SDL3/SDL_timer.h>
#else
#include <SDL.h>
#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
#else
#include "utils/sdl2_backports.h"
#endif
#endif
#include <ankerl/unordered_dense.h>
#if defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR)
#include "utils/stdcompat/filesystem.hpp"
#ifdef DVL_HAS_FILESYSTEM
#define DVL_ZT_SYMLINK
#endif
#endif
#ifdef DVL_ZT_SYMLINK
#include <shlobj.h>
#ifdef PACKET_ENCRYPTION
#include <sodium.h>
#endif
#include "utils/str_cat.hpp"
#include "utils/utf8.hpp"
#endif
#include <ZeroTierSockets.h>
#include <cstdlib>
#include "utils/algorithm/container.hpp"
#include "utils/log.hpp"
#include "utils/paths.h"
#include "dvlnet/zerotier_lwip.h"
namespace devilution {
namespace net {
namespace {
// static constexpr uint64_t zt_earth = 0x8056c2e21c000001;
constexpr uint64_t ZtNetwork = 0xa84ac5c10a7ebb5f;
std::atomic_bool zt_network_ready(false);
std::atomic_bool zt_node_online(false);
std::atomic_bool zt_joined(false);
std::atomic_uint zt_peers_ready(0);
ankerl::unordered_dense::map<uint64_t, zts_event_t> ztPeerEvents;
#ifdef DVL_ZT_SYMLINK
bool HasMultiByteChars(std::string_view path)
{
return c_any_of(path, IsTrailUtf8CodeUnit);
}
#ifdef PACKET_ENCRYPTION
std::string ComputeAlternateFolderName(std::string_view path)
{
const size_t hashSize = crypto_generichash_BYTES;
unsigned char hash[hashSize];
const int status = crypto_generichash(hash, hashSize,
reinterpret_cast<const unsigned char *>(path.data()), path.size(),
nullptr, 0);
if (status != 0)
return {};
char buf[hashSize * 2];
for (size_t i = 0; i < hashSize; ++i) {
BufCopy(&buf[i * 2], AsHexPad2(hash[i]));
}
return std::string(buf, hashSize * 2);
}
#else
std::string ComputeAlternateFolderName(std::string_view path)
{
return {};
}
#endif
std::string ToZTCompliantPath(std::string_view configPath)
{
if (!HasMultiByteChars(configPath))
return std::string(configPath);
char commonAppDataPath[MAX_PATH];
if (!SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_COMMON_APPDATA, NULL, 0, commonAppDataPath))) {
LogVerbose("Failed to retrieve common application data path");
return std::string(configPath);
}
std::error_code err;
std::string alternateConfigPath = StrCat(commonAppDataPath, "\\diasurgical\\devilution");
std::filesystem::create_directories(alternateConfigPath, err);
if (err) {
LogVerbose("Failed to create directories in ZT-compliant config path");
return std::string(configPath);
}
std::string alternateFolderName = ComputeAlternateFolderName(configPath);
if (alternateFolderName == "") {
LogVerbose("Failed to hash config path for ZT");
return std::string(configPath);
}
std::string symlinkPath = StrCat(alternateConfigPath, "\\", alternateFolderName);
bool symlinkExists = std::filesystem::exists(
std::u8string_view(reinterpret_cast<const char8_t *>(symlinkPath.data()), symlinkPath.size()), err);
if (err) {
LogVerbose("Failed to determine if symlink for ZT-compliant config path exists");
return std::string(configPath);
}
if (!symlinkExists) {
std::filesystem::create_directory_symlink(
std::u8string_view(reinterpret_cast<const char8_t *>(configPath.data()), configPath.size()),
std::u8string_view(reinterpret_cast<const char8_t *>(symlinkPath.data()), symlinkPath.size()),
err);
if (err) {
LogVerbose("Failed to create symlink for ZT-compliant config path");
return std::string(configPath);
}
}
return StrCat(symlinkPath, "\\");
}
#endif
void Callback(void *ptr)
{
zts_event_msg_t *msg = reinterpret_cast<zts_event_msg_t *>(ptr);
switch (msg->event_code) {
case ZTS_EVENT_NODE_ONLINE:
Log("ZeroTier: ZTS_EVENT_NODE_ONLINE, nodeId={:x}", (unsigned long long)msg->node->node_id);
zt_node_online = true;
if (!zt_joined) {
zts_net_join(ZtNetwork);
zt_joined = true;
}
break;
case ZTS_EVENT_NODE_OFFLINE:
Log("ZeroTier: ZTS_EVENT_NODE_OFFLINE");
zt_node_online = false;
break;
case ZTS_EVENT_NETWORK_READY_IP6:
Log("ZeroTier: ZTS_EVENT_NETWORK_READY_IP6, networkId={:x}", (unsigned long long)msg->network->net_id);
zt_ip6setup();
zt_network_ready = true;
zt_peers_ready = SDL_GetTicks();
break;
case ZTS_EVENT_ADDR_ADDED_IP6:
print_ip6_addr(&(msg->addr->addr));
break;
case ZTS_EVENT_PEER_DIRECT:
case ZTS_EVENT_PEER_RELAY:
ztPeerEvents[msg->peer->peer_id] = static_cast<zts_event_t>(msg->event_code);
if (!zerotier_peers_ready())
zt_peers_ready = SDL_GetTicks();
break;
case ZTS_EVENT_PEER_PATH_DEAD:
ztPeerEvents.erase(msg->peer->peer_id);
break;
}
}
} // namespace
bool zerotier_network_ready()
{
return zt_network_ready && zt_node_online;
}
bool zerotier_peers_ready()
{
return SDL_GetTicks() - zt_peers_ready >= 5000;
}
void zerotier_network_start()
{
std::string configPath = paths::ConfigPath();
#ifdef DVL_ZT_SYMLINK
configPath = ToZTCompliantPath(configPath);
#endif
std::string ztpath = configPath + "zerotier";
zts_init_from_storage(ztpath.c_str());
zts_init_set_event_handler(&Callback);
zts_node_start();
}
bool zerotier_is_relayed(uint64_t mac)
{
bool isRelayed = true;
if (zts_core_lock_obtain() != ZTS_ERR_OK)
return isRelayed;
zts_peer_info_t peerInfo;
if (zts_core_query_peer_info(ZtNetwork, mac, &peerInfo) == ZTS_ERR_OK) {
auto peerEvent = ztPeerEvents.find(peerInfo.peer_id);
if (peerEvent != ztPeerEvents.end())
isRelayed = (peerEvent->second == ZTS_EVENT_PEER_RELAY);
}
zts_core_lock_release();
return isRelayed;
}
int zerotier_latency(uint64_t mac)
{
int latency = -1;
if (zts_core_lock_obtain() != ZTS_ERR_OK)
return latency;
zts_peer_info_t peerInfo;
if (zts_core_query_peer_info(ZtNetwork, mac, &peerInfo) == ZTS_ERR_OK)
latency = peerInfo.latency;
zts_core_lock_release();
return latency;
}
} // namespace net
} // namespace devilution