From 76b8e299a0c5c8298b36fba2e49eae61f50c6224 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 2 Sep 2017 18:16:56 +0200 Subject: [PATCH] [DHT] Implement DHT --- deps/dht | 2 +- src/Components/Loader.cpp | 3 +- src/Components/Loader.hpp | 3 +- src/Components/Modules/DHT.cpp | 372 ++++++++++++++++++++++++++ src/Components/Modules/DHT.hpp | 47 ++++ src/Components/Modules/Discovery.cpp | 43 +++ src/Components/Modules/Discovery.hpp | 7 + src/Components/Modules/Friends.cpp | 2 +- src/Components/Modules/Monitor.cpp | 2 +- src/Components/Modules/Network.hpp | 1 + src/Components/Modules/Node.cpp | 355 ------------------------ src/Components/Modules/Node.hpp | 64 ----- src/Components/Modules/Party.cpp | 40 ++- src/Components/Modules/ServerList.cpp | 18 +- src/Components/Modules/ServerList.hpp | 1 + src/Components/Modules/Session.cpp | 282 ------------------- src/Components/Modules/Session.hpp | 56 ---- src/Proto/network.proto | 14 + src/Proto/node.proto | 16 -- src/Proto/session.proto | 11 - src/STDInclude.hpp | 4 +- src/Utils/Cryptography.cpp | 25 ++ src/Utils/Cryptography.hpp | 3 + 23 files changed, 576 insertions(+), 795 deletions(-) create mode 100644 src/Components/Modules/DHT.cpp create mode 100644 src/Components/Modules/DHT.hpp delete mode 100644 src/Components/Modules/Node.cpp delete mode 100644 src/Components/Modules/Node.hpp delete mode 100644 src/Components/Modules/Session.cpp delete mode 100644 src/Components/Modules/Session.hpp create mode 100644 src/Proto/network.proto delete mode 100644 src/Proto/node.proto delete mode 100644 src/Proto/session.proto diff --git a/deps/dht b/deps/dht index 54ee76af..1a489272 160000 --- a/deps/dht +++ b/deps/dht @@ -1 +1 @@ -Subproject commit 54ee76af630f56cb97646bc770b88bfb592fd328 +Subproject commit 1a489272ec72e5d571b63e3448063690d6d8656c diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 6000beba..0a5994cf 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -33,6 +33,7 @@ namespace Components Loader::Register(new Singleton()); Loader::Register(new Exception()); // install our exception handler as early as posssible to get better debug dumps from startup crashes + Loader::Register(new DHT()); Loader::Register(new Auth()); Loader::Register(new Bans()); Loader::Register(new Bots()); @@ -40,7 +41,6 @@ namespace Components Loader::Register(new Lean()); Loader::Register(new Maps()); Loader::Register(new News()); - Loader::Register(new Node()); Loader::Register(new RCon()); Loader::Register(new Stats()); Loader::Register(new Menus()); @@ -63,7 +63,6 @@ namespace Components Loader::Register(new ModList()); Loader::Register(new Monitor()); Loader::Register(new Network()); - Loader::Register(new Session()); Loader::Register(new Theatre()); //Loader::Register(new ClanTags()); Loader::Register(new Download()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 3c9a5699..72cdf9da 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -86,14 +86,13 @@ namespace Components #include "Modules/Network.hpp" #include "Modules/Theatre.hpp" #include "Modules/QuickPatch.hpp" -#include "Modules/Node.hpp" +#include "Modules/DHT.hpp" #include "Modules/RCon.hpp" #include "Modules/Party.hpp" // Destroys the order, but requires network classes :D #include "Modules/IW4MVM.hpp" #include "Modules/Logger.hpp" #include "Modules/Friends.hpp" #include "Modules/IPCPipe.hpp" -#include "Modules/Session.hpp" #include "Modules/ClanTags.hpp" #include "Modules/Download.hpp" #include "Modules/Playlist.hpp" diff --git a/src/Components/Modules/DHT.cpp b/src/Components/Modules/DHT.cpp new file mode 100644 index 00000000..3fa5784a --- /dev/null +++ b/src/Components/Modules/DHT.cpp @@ -0,0 +1,372 @@ +#include "STDInclude.hpp" + +int dht_random_bytes(void *buf, size_t size) +{ + Utils::Cryptography::Rand::GetRandomBytes(buf, size); + return INT(size); +} + +void dht_hash(void *hash_return, int hash_size, const void *v1, int len1, const void *v2, int len2, const void *v3, int len3) +{ + std::string data; + data.append(LPSTR(v1), len1); + data.append(LPSTR(v2), len2); + data.append(LPSTR(v3), len3); + + Components::DHT::Hash(data, hash_return, size_t(hash_size)); +} + +int dht_blacklisted(const struct sockaddr* /*sa*/, int /*salen*/) +{ + return 0; +} + +extern "C" int dht_gettimeofday(struct timeval *tp, struct timezone* /*tzp*/) +{ + static const unsigned __int64 epoch = 116444736000000000ULL; + + SYSTEMTIME systemTime; + GetSystemTime(&systemTime); + + FILETIME fileTime; + SystemTimeToFileTime(&systemTime, &fileTime); + + ULARGE_INTEGER ularge; + ularge.LowPart = fileTime.dwLowDateTime; + ularge.HighPart = fileTime.dwHighDateTime; + + tp->tv_sec = LONG((ularge.QuadPart - epoch) / 10000000L); + tp->tv_usec = LONG(systemTime.wMilliseconds * 1000); + + return 0; +} + +namespace Components +{ + SOCKET DHT::Socket; + std::mutex DHT::Mutex; + std::vector DHT::Nodes; + std::map, Utils::Slot)>> DHT::Handlers; + + void DHT::Insert(std::string data, Utils::Slot)> callback) + { + unsigned char hash[20]; + DHT::Hash(data, hash, sizeof(hash)); + DHT::InsertHash(hash, callback); + } + + void DHT::InsertHash(char* hash, Utils::Slot)> callback) + { + DHT::InsertHash(reinterpret_cast(hash), callback); + } + + void DHT::InsertHash(unsigned char* hash, Utils::Slot)> callback) + { + std::basic_string hashStr(hash, 20); + DHT::Handlers[hashStr] = callback; + } + + void DHT::Hash(std::string data, void* out, size_t size) + { + ZeroMemory(out, size); + + for (size_t i = 0; i < size; ++i) + { + std::string hashData = data; + hashData.append(LPSTR(out), size); + + std::string hash = Utils::Cryptography::SHA512::Compute(hashData); + std::memmove(LPSTR(out) + i, hash.data(), std::min(size - i, hash.size())); + } + } + + void DHT::Callback(void* /*closure*/, int event, const unsigned char* info_hash, const void* data, size_t data_len) + { + if (event == DHT_EVENT_VALUES) + { + std::basic_string hashStr(info_hash, 20); + auto handler = DHT::Handlers.find(hashStr); + if (handler != DHT::Handlers.end()) + { + std::vector addresses; + + const unsigned char* bytes = reinterpret_cast(data); + while ((data_len - (LPSTR(bytes) - LPSTR(data))) >= 6) + { + unsigned char ip[4]; + ip[0] = *bytes++; + ip[1] = *bytes++; + ip[2] = *bytes++; + ip[3] = *bytes++; + + unsigned short port = ntohs(*reinterpret_cast(bytes)); + bytes += 2; + + Network::Address address(Utils::String::VA("%u.%u.%u.%u:%hu", ip[0], ip[1], ip[2], ip[3], port)); + addresses.push_back(address); + } + + handler->second(addresses); + } + } + else if (event == DHT_EVENT_SEARCH_DONE) + { + Logger::Print("Search done!\n"); + } + } + + void DHT::OnData(std::string data, Network::Address address) + { + time_t tosleep = 0; + sockaddr addr = address.getSockAddr(); + dht_periodic(data.data(), data.size(), &addr, sizeof(addr), &tosleep, DHT::Callback, NULL); + } + + void DHT::StoreNodes(bool force) + { + static Utils::Time::Interval interval; + if (!force && !interval.elapsed(1min)) return; + interval.update(); + + Utils::Memory::Allocator allocator; + int nodes = dht_nodes(AF_INET, nullptr, nullptr, nullptr, nullptr) + 10; + + sockaddr_in* addresses = allocator.allocateArray(nodes); + DHT::Id* ids = allocator.allocateArray(nodes); + + int num6 = 0; + dht_get_nodes(addresses, reinterpret_cast(ids), &nodes, nullptr, nullptr, &num6); + + // We could be offline, don't overwrite anything + if (nodes <= 0) return; + + Proto::Network::Nodes protoNodes; + + for (int i = 0; i < nodes; ++i) + { + Proto::Network::Node* node = protoNodes.add_nodes(); + + node->mutable_id()->clear(); + node->mutable_id()->append(LPSTR(ids + i), sizeof(*ids)); + + node->mutable_address()->clear(); + node->mutable_address()->append(LPSTR(addresses + i), sizeof(*addresses)); + } + + Utils::IO::WriteFile(Utils::String::VA("players/dht/%hu.nodes", Network::GetPort()), protoNodes.SerializeAsString()); + } + + void DHT::LoadNodes() + { + std::string file = Utils::String::VA("players/dht/%hu.nodes", Network::GetPort()); + if (!Utils::IO::FileExists(file)) return; + + auto data = Utils::IO::ReadFile(file); + + Proto::Network::Nodes nodes; + if (!nodes.ParseFromString(data)) return; + + for (auto& node : nodes.nodes()) + { + const std::string& id = node.id(); + const std::string& address = node.address(); + + if (id.size() == 20) + { + dht_insert_node(reinterpret_cast(id.data()), reinterpret_cast(LPSTR(address.data())), INT(address.size())); + } + } + } + + void DHT::Add(Network::Address addr) + { + std::lock_guard _(DHT::Mutex); + + if (std::find(DHT::Nodes.begin(), DHT::Nodes.end(), addr) == DHT::Nodes.end()) + { + DHT::Nodes.push_back(addr); + } + + if (ServerList::IsOnlineList()) + { + ServerList::InsertRequest(addr); + } + } + + void DHT::Search() + { + // Serverlist specific + { + std::lock_guard _(DHT::Mutex); + for (auto& address : DHT::Nodes) + { + if (ServerList::IsOnlineList()) + { + ServerList::InsertRequest(address); + } + } + } + + for (auto& hash : DHT::Handlers) + { + dht_search(hash.first.data(), (Dedicated::IsEnabled() ? (Dvar::Var("sv_lanOnly").get() ? 0 : Network::GetPort()) : 0), AF_INET, DHT::Callback, nullptr); + } + } + + void DHT::SocketFrame() + { + static char buffer[0x2001]; + + sockaddr_in addr; + int addrLen = sizeof(addr); + + while (true) + { + int len = recvfrom(DHT::Socket, buffer, sizeof buffer, 0, reinterpret_cast(&addr), &addrLen); + if (len <= 0) break; + + Network::Address address(&addr); + + std::string data(buffer, len); + DHT::OnData(data, address); + } + } + + void DHT::RunFrame() + { + DHT::SocketFrame(); + + time_t tosleep = 0; + dht_periodic(NULL, 0, NULL, 0, &tosleep, DHT::Callback, NULL); + DHT::StoreNodes(false); + + static std::optional interval; + if (!interval.has_value() || interval->elapsed(2min)) + { + if (!interval.has_value()) interval.emplace(); + interval->update(); + + DHT::Search(); + } + } + + void DHT::Bootstrap(Network::Address node) + { + sockaddr addr = node.getSockAddr(); + dht_ping_node(&addr, sizeof(addr)); + } + + void DHT::InitSocket() + { + WSAData data; + WSAStartup(MAKEWORD(2, 2), &data); + + DHT::Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + int broadcastOn = 1; + setsockopt(DHT::Socket, SOL_SOCKET, SO_BROADCAST, LPSTR(&broadcastOn), sizeof(broadcastOn)); + + unsigned long nonBlocking = 1; + ioctlsocket(DHT::Socket, FIONBIO, &nonBlocking); + + sockaddr_in addr; + ZeroMemory(&addr, sizeof(addr)); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + + unsigned short port = DHT_PORT; + while (port < DHT_PORT + DHT_RANGE) + { + addr.sin_port = htons(port++); + if (bind(DHT::Socket, reinterpret_cast(&addr), sizeof(addr)) != SOCKET_ERROR) + { + break; + } + } + } + + DHT::DHT() + { +// #ifdef DEBUG +// AllocConsole(); +// AttachConsole(GetCurrentProcessId()); +// +// freopen("conin$", "r", stdin); +// freopen("conout$", "w", stdout); +// freopen("conout$", "w", stderr); +// dht_debug = stdout; +// #endif + + Network::OnStart([]() + { + std::string idData; + + std::string file = Utils::String::VA("players/dht/%hu.id", Network::GetPort()); + if (Utils::IO::FileExists(file)) + { + idData = Utils::IO::ReadFile(file); + } + + if (idData.size() != 20) + { + idData = Utils::Cryptography::Rand::GetRandomBytes(20); + Utils::IO::WriteFile(file, idData); + } + + DHT::InitSocket(); + dht_init(INT(DHT::Socket), -1, reinterpret_cast(idData.data()), reinterpret_cast("JC\0\0")); + + DHT::LoadNodes(); + Scheduler::OnFrame(DHT::RunFrame); + + DHT::Bootstrap("router.bittorrent.com:6881"); + DHT::Bootstrap("router.utorrent.com:6881"); + DHT::Bootstrap("dht.transmissionbt.com:6881"); + //DHT::Bootstrap("router.bitcomet.com:6881"); // Blacklisted + DHT::Bootstrap("dht.aelitis.com:6881"); + }); + + DHT::Insert("IW4x", [](std::vector addresses) + { + std::lock_guard _(DHT::Mutex); + + for (auto& address : addresses) + { + if (std::find(DHT::Nodes.begin(), DHT::Nodes.end(), address) == DHT::Nodes.end()) + { + DHT::Nodes.push_back(address); + } + } + + for (auto& address : addresses) + { + if (ServerList::IsOnlineList()) + { + ServerList::InsertRequest(address); + } + + Logger::Print("Received %s\n", address.getCString()); + } + }); + + Command::Add("addnode", [](Command::Params* params) + { + if (params->length() >= 2) + { + DHT::Add(params->get(1)); + } + }); + } + + DHT::~DHT() + { + DHT::Handlers.clear(); + + DHT::StoreNodes(true); + dht_uninit(); + + closesocket(DHT::Socket); + WSACleanup(); + } +} diff --git a/src/Components/Modules/DHT.hpp b/src/Components/Modules/DHT.hpp new file mode 100644 index 00000000..315e8a66 --- /dev/null +++ b/src/Components/Modules/DHT.hpp @@ -0,0 +1,47 @@ +#pragma once + +#define DHT_PORT 9000 +#define DHT_RANGE 100 + +namespace Components +{ + class DHT : public Component + { + public: + typedef unsigned char Id[20]; + + DHT(); + ~DHT(); + + static void OnData(std::string data, Network::Address address); + + static void Insert(std::string data, Utils::Slot)> callback); + + static void InsertHash(char* hash, Utils::Slot)> callback); + static void InsertHash(unsigned char* hash, Utils::Slot)> callback); + + static void RunFrame(); + + static void Search(); + + static void Add(Network::Address addr); + + static void Hash(std::string data, void* out, size_t size); + + private: + static SOCKET Socket; + static std::mutex Mutex; + static std::vector Nodes; + static std::map, Utils::Slot)>> Handlers; + + static void Callback(void *closure, int event, const unsigned char *info_hash, const void *data, size_t data_len); + + static void StoreNodes(bool force); + static void LoadNodes(); + + static void InitSocket(); + static void SocketFrame(); + + static void Bootstrap(Network::Address node); + }; +} diff --git a/src/Components/Modules/Discovery.cpp b/src/Components/Modules/Discovery.cpp index 833b7103..d16118aa 100644 --- a/src/Components/Modules/Discovery.cpp +++ b/src/Components/Modules/Discovery.cpp @@ -7,6 +7,26 @@ namespace Components std::thread Discovery::Thread; std::string Discovery::Challenge; + std::mutex Discovery::Mutex; + std::vector Discovery::LocalServers; + + std::vector Discovery::GetLocalServers() + { + std::lock_guard _(Discovery::Mutex); + return Discovery::LocalServers; + } + + void Discovery::InsertServer(Network::Address server) + { + if (!server.isLocal()) return; + std::lock_guard _(Discovery::Mutex); + + if (std::find(Discovery::LocalServers.begin(), Discovery::LocalServers.end(), server) == Discovery::LocalServers.end()) + { + Discovery::LocalServers.push_back(server); + } + } + void Discovery::Perform() { Discovery::IsPerforming = true; @@ -27,6 +47,9 @@ namespace Components { if (Discovery::IsPerforming) { + std::lock_guard _(Discovery::Mutex); + Discovery::LocalServers.clear(); + int start = Game::Sys_Milliseconds(); Logger::Print("Starting local server discovery...\n"); @@ -46,6 +69,23 @@ namespace Components } }); + if (Dedicated::IsEnabled()) + { + Network::OnStart([]() + { + Scheduler::OnFrame([]() + { + static std::optional interval; + if (!interval.has_value() || interval->elapsed(10min)) + { + if (!interval.has_value()) interval.emplace(Utils::Time::Interval()); + interval->update(); + Discovery::Perform(); + } + }); + }); + } + Network::Handle("discovery", [](Network::Address address, std::string data) { if (address.isSelf()) return; @@ -58,6 +98,8 @@ namespace Components Logger::Print("Received discovery request from %s\n", address.getCString()); Network::SendCommand(address, "discoveryResponse", data); + + Discovery::InsertServer(address); }); Network::Handle("discoveryResponse", [](Network::Address address, std::string data) @@ -77,6 +119,7 @@ namespace Components } Logger::Print("Received discovery response from: %s\n", address.getCString()); + Discovery::InsertServer(address); if (ServerList::IsOfflineList()) { diff --git a/src/Components/Modules/Discovery.hpp b/src/Components/Modules/Discovery.hpp index 655122bf..24dfc22e 100644 --- a/src/Components/Modules/Discovery.hpp +++ b/src/Components/Modules/Discovery.hpp @@ -12,10 +12,17 @@ namespace Components static void Perform(); + static std::vector GetLocalServers(); + private: static bool IsTerminating; static bool IsPerforming; static std::thread Thread; static std::string Challenge; + + static std::mutex Mutex; + static std::vector LocalServers; + + static void InsertServer(Network::Address server); }; } diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index 5f29daa9..79d76467 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -105,7 +105,7 @@ namespace Components if (entry->server.getType() == Game::NA_LOOPBACK || (entry->server.getType() == Game::NA_IP && entry->server.getIP().full == 0x0100007F)) entry->server.setType(Game::NA_BAD); else if (entry->server.getType() != Game::NA_BAD && entry->server != oldAddress) { - Node::Add(entry->server); + DHT::Add(entry->server); Network::SendCommand(entry->server, "getinfo", Utils::Cryptography::Rand::GenerateChallenge()); } diff --git a/src/Components/Modules/Monitor.cpp b/src/Components/Modules/Monitor.cpp index 4f0edcb5..21821db9 100644 --- a/src/Components/Modules/Monitor.cpp +++ b/src/Components/Modules/Monitor.cpp @@ -33,7 +33,7 @@ namespace Components { Utils::Hook::Call(0x49F0B0)(); // Com_ClientPacketEvent //Session::RunFrame(); - Node::RunFrame(); + DHT::RunFrame(); ServerList::Frame(); std::this_thread::sleep_for(10ms); diff --git a/src/Components/Modules/Network.hpp b/src/Components/Modules/Network.hpp index 4a2919b2..b6beda23 100644 --- a/src/Components/Modules/Network.hpp +++ b/src/Components/Modules/Network.hpp @@ -18,6 +18,7 @@ namespace Components Address(sockaddr_in* addr) : Address(reinterpret_cast(addr)) {} Address(Game::netadr_t addr) : address(addr) {} Address(Game::netadr_t* addr) : Address(*addr) {} + Address(const char* addr) : Address(std::string(addr)) {} Address(const Address& obj) : address(obj.address) {}; bool operator!=(const Address &obj) const { return !(*this == obj); }; bool operator==(const Address &obj) const; diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp deleted file mode 100644 index f033d885..00000000 --- a/src/Components/Modules/Node.cpp +++ /dev/null @@ -1,355 +0,0 @@ -#include "STDInclude.hpp" - -namespace Components -{ - std::recursive_mutex Node::Mutex; - std::vector Node::Nodes; - - bool Node::wasIngame; - - bool Node::Entry::isValid() - { - return (this->lastResponse.has_value() && !this->lastResponse->elapsed(NODE_HALFLIFE * 2)); - } - - bool Node::Entry::isDead() - { - if (!this->lastResponse.has_value()) - { - if (this->lastRequest.has_value() && this->lastRequest->elapsed(NODE_HALFLIFE)) - { - return true; - } - } - else if(this->lastResponse->elapsed(NODE_HALFLIFE * 2) && this->lastRequest.has_value() && this->lastRequest->after(*this->lastResponse)) - { - return true; - } - - return false; - } - - bool Node::Entry::requiresRequest() - { - return (!this->isDead() && (!this->lastRequest.has_value() || this->lastRequest->elapsed(NODE_HALFLIFE))); - } - - void Node::Entry::sendRequest() - { - if (!this->lastRequest.has_value()) this->lastRequest.emplace(Utils::Time::Point()); - this->lastRequest->update(); - - Session::Send(this->address, "nodeListRequest"); - Node::SendList(this->address); - NODE_LOG("Sent request to %s\n", this->address.getCString()); - } - - void Node::Entry::reset() - { - // this->lastResponse.reset(); // This would invalidate the node, but maybe we don't want that? - this->lastRequest.reset(); - } - - void Node::LoadNodeRemotePreset() - { - std::string nodes = Utils::Cache::GetFile("/iw4/nodes.txt"); - if (nodes.empty()) return; - - auto nodeList = Utils::String::Explode(nodes, '\n'); - for (auto& node : nodeList) - { - Utils::String::Replace(node, "\r", ""); - node = Utils::String::Trim(node); - Node::Add(node); - } - } - - void Node::LoadNodePreset() - { - Proto::Node::List list; - - if (Monitor::IsEnabled()) - { - std::string nodes = Utils::IO::ReadFile("players/nodes_default.dat"); - if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return; - } - else - { - FileSystem::File defaultNodes("nodes_default.dat"); - if (!defaultNodes.exists() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(defaultNodes.getBuffer()))) return; - } - - for (int i = 0; i < list.nodes_size(); ++i) - { - const std::string& addr = list.nodes(i); - - if (addr.size() == sizeof(sockaddr)) - { - Node::Add(reinterpret_cast(const_cast(addr.data()))); - } - } - } - - void Node::LoadNodes() - { - Proto::Node::List list; - std::string nodes = Utils::IO::ReadFile("players/nodes.dat"); - if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return; - - for (int i = 0; i < list.nodes_size(); ++i) - { - const std::string& addr = list.nodes(i); - - if (addr.size() == sizeof(sockaddr)) - { - Node::Add(reinterpret_cast(const_cast(addr.data()))); - } - } - } - - void Node::StoreNodes(bool force) - { - if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get()) return; - - static Utils::Time::Interval interval; - if (!force && !interval.elapsed(1min)) return; - interval.update(); - - Proto::Node::List list; - - Node::Mutex.lock(); - for (auto& node : Node::Nodes) - { - if (node.isValid()) - { - std::string* str = list.add_nodes(); - - sockaddr addr = node.address.getSockAddr(); - str->append(reinterpret_cast(&addr), sizeof(addr)); - } - } - Node::Mutex.unlock(); - - Utils::IO::WriteFile("players/nodes.dat", Utils::Compression::ZLib::Compress(list.SerializeAsString())); - } - - void Node::Add(Network::Address address) - { -#ifndef DEBUG - if (address.isLocal() || address.isSelf()) return; -#endif - - if (!address.isValid()) return; - - std::lock_guard _(Node::Mutex); - for (auto& session : Node::Nodes) - { - if (session.address == address) return; - } - - Node::Entry node; - node.address = address; - - Node::Nodes.push_back(node); - } - - void Node::RunFrame() - { - if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get()) return; - - if (Game::CL_IsCgameInitialized()) - { - wasIngame = true; - return; // don't run while ingame because it can still cause lag spikes on lower end PCs - } - - if (wasIngame) // our last frame we were ingame and now we aren't so touch all nodes - { - for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();++i) - { - // clearing the last request and response times makes the - // dispatcher think its a new node and will force a refresh - i->lastRequest.reset(); - i->lastResponse.reset(); - } - wasIngame = false; - } - - static Utils::Time::Interval frameLimit; - int interval = static_cast(1000.0f / Dvar::Var("net_serverFrames").get()); - if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return; - frameLimit.update(); - - std::lock_guard _(Node::Mutex); - Dvar::Var queryLimit("net_serverQueryLimit"); - - int sentRequests = 0; - for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();) - { - if (i->isDead()) - { - i = Node::Nodes.erase(i); - continue; - } - else if (sentRequests < queryLimit.get() && i->requiresRequest()) - { - ++sentRequests; - i->sendRequest(); - } - - ++i; - } - } - - void Node::Synchronize() - { - std::lock_guard _(Node::Mutex); - for (auto& node : Node::Nodes) - { - //if (node.isValid()) // Comment out to simulate 'syncnodes' behaviour - { - node.reset(); - } - } - } - - void Node::HandleResponse(Network::Address address, std::string data) - { - Proto::Node::List list; - if (!list.ParseFromString(data)) return; - - NODE_LOG("Received response from %s\n", address.getCString()); - - std::lock_guard _(Node::Mutex); - - for (int i = 0; i < list.nodes_size(); ++i) - { - const std::string& addr = list.nodes(i); - - if (addr.size() == sizeof(sockaddr)) - { - Node::Add(reinterpret_cast(const_cast(addr.data()))); - } - } - - if (list.isnode() && (!list.port() || list.port() == address.getPort())) - { - if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && list.protocol() == PROTOCOL) - { - NODE_LOG("Inserting %s into the serverlist\n", address.getCString()); - ServerList::InsertRequest(address); - } - else - { - NODE_LOG("Dropping serverlist insertion for %s\n", address.getCString()); - } - - for (auto& node : Node::Nodes) - { - if (address == node.address) - { - if (!node.lastResponse.has_value()) node.lastResponse.emplace(Utils::Time::Point()); - node.lastResponse->update(); - - node.data.protocol = list.protocol(); - return; - } - } - - Node::Entry entry; - entry.address = address; - entry.data.protocol = list.protocol(); - entry.lastResponse.emplace(Utils::Time::Point()); - - Node::Nodes.push_back(entry); - } - } - void Node::SendList(Network::Address address) - { - Proto::Node::List list; - list.set_isnode(Dedicated::IsEnabled()); - list.set_protocol(PROTOCOL); - list.set_port(Node::GetPort()); - - std::lock_guard _(Node::Mutex); - - for (auto& node : Node::Nodes) - { - if (node.isValid()) - { - std::string* str = list.add_nodes(); - - sockaddr addr = node.address.getSockAddr(); - str->append(reinterpret_cast(&addr), sizeof(addr)); - } - } - - Session::Send(address, "nodeListResponse", list.SerializeAsString()); - } - - unsigned short Node::GetPort() - { - if (Dvar::Var("net_natFix").get()) return 0; - return Network::GetPort(); - } - - Node::Node() - { - if (ZoneBuilder::IsEnabled()) return; - Dvar::Register("net_natFix", false, 0, "Fix node registration for certain firewalls/routers"); - - Scheduler::OnFrameAsync([]() - { - Node::StoreNodes(false); - }); - - Scheduler::OnFrame(Node::RunFrame); - Session::Handle("nodeListResponse", Node::HandleResponse); - Session::Handle("nodeListRequest", [](Network::Address address, std::string) - { - Node::SendList(address); - }); - - // Load stored nodes - auto loadNodes = []() - { - Node::LoadNodePreset(); - Node::LoadNodes(); - }; - - if (Monitor::IsEnabled()) Network::OnStart(loadNodes); - else Dvar::OnInit(loadNodes); - - Network::OnStart([]() - { - std::thread([]() - { - Node::LoadNodeRemotePreset(); - }).detach(); - }); - - Command::Add("listnodes", [](Command::Params*) - { - Logger::Print("Nodes: %d\n", Node::Nodes.size()); - - std::lock_guard _(Node::Mutex); - for (auto& node : Node::Nodes) - { - Logger::Print("%s\t(%s)\n", node.address.getCString(), node.isValid() ? "Valid" : "Invalid"); - } - }); - - Command::Add("addnode", [](Command::Params* params) - { - if (params->length() < 2) return; - Node::Add({ params->get(1) }); - }); - } - - Node::~Node() - { - std::lock_guard _(Node::Mutex); - Node::StoreNodes(true); - Node::Nodes.clear(); - } -} diff --git a/src/Components/Modules/Node.hpp b/src/Components/Modules/Node.hpp deleted file mode 100644 index ac6ce353..00000000 --- a/src/Components/Modules/Node.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#define NODE_HALFLIFE (3 * 60 * 1000) //3min - -#ifdef NODE_LOG_MESSAGES -#define NODE_LOG(x, ...) Logger::Print(x, __VA_ARGS__) -#else -#define NODE_LOG(x, ...) -#endif - -namespace Components -{ - class Node : public Component - { - public: - class Data - { - public: - uint64_t protocol; - }; - - class Entry - { - public: - Network::Address address; - Data data; - - std::optional lastRequest; - std::optional lastResponse; - - bool isValid(); - bool isDead(); - - bool requiresRequest(); - void sendRequest(); - - void reset(); - }; - - Node(); - ~Node(); - - static void Add(Network::Address address); - static void RunFrame(); - static void Synchronize(); - - static void LoadNodeRemotePreset(); - - private: - static std::recursive_mutex Mutex; - static std::vector Nodes; - static bool wasIngame; - - static void HandleResponse(Network::Address address, std::string data); - - static void SendList(Network::Address address); - - static void LoadNodePreset(); - static void LoadNodes(); - static void StoreNodes(bool force); - - static unsigned short GetPort(); - }; -} diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 2779c286..3453a514 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -24,7 +24,7 @@ namespace Components void Party::Connect(Network::Address target) { - Node::Add(target); + DHT::Add(target); Party::Container.valid = true; Party::Container.awaitingPlaylist = false; @@ -346,6 +346,27 @@ namespace Components info.set("securityLevel", Utils::String::VA("%i", Dvar::Var("sv_securityLevel").get())); info.set("sv_running", (Dvar::Var("sv_running").get() ? "1" : "0")); + if (Dedicated::IsEnabled()) + { + std::vector servers = Discovery::GetLocalServers(); + std::vector ports; + + std::string portString; + for (auto& server : servers) + { + unsigned short port = server.getPort(); + if (std::find(ports.begin(), ports.end(), port) == ports.end()) + { + ports.push_back(port); + + if (!portString.empty()) portString.push_back(','); + portString.append(Utils::String::VA("%hu", port & 0xFFFF)); + } + } + + info.set("siblings", portString); + } + // Ensure mapname is set if (info.get("mapname").empty() || Party::IsInLobby()) { @@ -509,6 +530,23 @@ namespace Components } } + std::string siblings = info.get("siblings"); + if (!siblings.empty() && !address.isLocal()) + { + Network::Address sibling(address); + std::vector ports = Utils::String::Explode(siblings, ','); + + for (auto& port : ports) + { + sibling.setPort(SHORT(atoi(port.data()))); + + if (ServerList::IsOnlineList()) + { + ServerList::InsertRequestIfNotInList(sibling); + } + } + } + ServerList::Insert(address, info); Friends::UpdateServer(address, info.get("hostname"), info.get("mapname")); }); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 0cb5c1d2..0a965715 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -283,7 +283,7 @@ namespace Components Network::SendCommand(ServerList::RefreshContainer.host, "getservers", Utils::String::VA("IW4 %i full empty", PROTOCOL)); //Network::SendCommand(ServerList::RefreshContainer.Host, "getservers", "0 full empty"); #else - Node::Synchronize(); + DHT::Search(); #endif } else if (ServerList::IsFavouriteList()) @@ -394,6 +394,22 @@ namespace Components } } + void ServerList::InsertRequestIfNotInList(Network::Address address) + { + std::lock_guard _(ServerList::RefreshContainer.mutex); + + auto list = ServerList::GetList(); + if (list) + { + for (auto& server : *list) + { + if (server.addr == address) return; + } + } + + ServerList::InsertRequest(address); + } + void ServerList::InsertRequest(Network::Address address) { std::lock_guard _(ServerList::RefreshContainer.mutex); diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 4b246ce8..260409e4 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -37,6 +37,7 @@ namespace Components static void RefreshVisibleList(UIScript::Token); static void UpdateVisibleList(UIScript::Token); static void InsertRequest(Network::Address address); + static void InsertRequestIfNotInList(Network::Address address); static void Insert(Network::Address address, Utils::InfoString info); static ServerInfo* GetCurrentServer(); diff --git a/src/Components/Modules/Session.cpp b/src/Components/Modules/Session.cpp deleted file mode 100644 index a2e0852e..00000000 --- a/src/Components/Modules/Session.cpp +++ /dev/null @@ -1,282 +0,0 @@ -#include "STDInclude.hpp" - -namespace Components -{ - bool Session::Terminate; - std::thread Session::Thread; - - std::recursive_mutex Session::Mutex; - std::unordered_map Session::Sessions; - std::unordered_map>> Session::PacketQueue; - - Utils::Cryptography::ECC::Key Session::SignatureKey; - - std::map> Session::PacketHandlers; - - std::queue> Session::SignatureQueue; - - void Session::Send(Network::Address target, std::string command, std::string data) - { -#ifdef DISABLE_SESSION - class DelayedResend - { - public: - Network::Address target; - std::string command; - std::string data; - }; - - DelayedResend* delayData = new DelayedResend; - delayData->target = target; - delayData->command = command; - delayData->data = data; - - Network::SendCommand(target, command, data); - - Scheduler::OnDelay([delayData]() - { - Network::SendCommand(delayData->target, delayData->command, delayData->data); - delete delayData; - }, 500ms + std::chrono::milliseconds(rand() % 200)); -#else - std::lock_guard _(Session::Mutex); - - auto queue = Session::PacketQueue.find(target); - if (queue == Session::PacketQueue.end()) - { - Session::PacketQueue[target] = std::queue>(); - queue = Session::PacketQueue.find(target); - if (queue == Session::PacketQueue.end()) Logger::Error("Failed to enqueue session packet!\n"); - } - - std::shared_ptr packet = std::make_shared(); - packet->command = command; - packet->data = data; - packet->tries = 0; - - queue->second.push(packet); -#endif - } - - void Session::Handle(std::string packet, Utils::Slot callback) - { -#ifdef DISABLE_SESSION - Network::Handle(packet, callback); -#else - std::lock_guard _(Session::Mutex); - Session::PacketHandlers[packet] = callback; -#endif - } - - void Session::RunFrame() - { - std::lock_guard _(Session::Mutex); - - for (auto queue = Session::PacketQueue.begin(); queue != Session::PacketQueue.end();) - { - if (queue->second.empty()) - { - queue = Session::PacketQueue.erase(queue); - continue; - } - - std::shared_ptr packet = queue->second.front(); - if (!packet->lastTry.has_value() || !packet->tries || (packet->lastTry.has_value() && packet->lastTry->elapsed(SESSION_TIMEOUT))) - { - if (packet->tries <= SESSION_MAX_RETRIES) - { - packet->tries++; - if(!packet->lastTry.has_value()) packet->lastTry.emplace(Utils::Time::Point()); - packet->lastTry->update(); - - Network::SendCommand(queue->first, "sessionSyn"); - } - else - { - queue->second.pop(); - } - } - - ++queue; - } - } - - void Session::HandleSignatures() - { - while (!Session::SignatureQueue.empty()) - { - std::unique_lock lock(Session::Mutex); - auto signature = Session::SignatureQueue.front(); - Session::SignatureQueue.pop(); - - auto queue = Session::PacketQueue.find(signature.first); - if (queue == Session::PacketQueue.end()) continue; - - if (!queue->second.empty()) - { - std::shared_ptr packet = queue->second.front(); - queue->second.pop(); - lock.unlock(); - - Proto::Session::Packet dataPacket; - dataPacket.set_publickey(Session::SignatureKey.getPublicKey()); - dataPacket.set_signature(Utils::Cryptography::ECC::SignMessage(Session::SignatureKey, signature.second)); - dataPacket.set_command(packet->command); - dataPacket.set_data(packet->data); - - Network::SendCommand(signature.first, "sessionFin", dataPacket.SerializeAsString()); - std::this_thread::sleep_for(3ms); - } - } - } - - Session::Session() - { -#ifndef DISABLE_SESSION - Session::SignatureKey = Utils::Cryptography::ECC::GenerateKey(512); - //Scheduler::OnFrame(Session::RunFrame); - - if (!Loader::IsPerformingUnitTests()) - { - Session::Terminate = false; - Session::Thread = std::thread([]() - { - while (!Session::Terminate) - { - Session::RunFrame(); - Session::HandleSignatures(); - std::this_thread::sleep_for(20ms); - } - }); - } - - Network::Handle("sessionSyn", [](Network::Address address, std::string data) - { - Session::Frame frame; - frame.challenge = Utils::Cryptography::Rand::GenerateChallenge(); - - std::lock_guard _(Session::Mutex); - Session::Sessions[address] = frame; - - Network::SendCommand(address, "sessionAck", frame.challenge); - }); - - Network::Handle("sessionAck", [](Network::Address address, std::string data) - { - std::lock_guard _(Session::Mutex); - Session::SignatureQueue.push({ address, data }); - }); - - Network::Handle("sessionFin", [](Network::Address address, std::string data) - { - std::lock_guard _(Session::Mutex); - - auto frame = Session::Sessions.find(address); - if (frame == Session::Sessions.end()) return; - - std::string challenge = frame->second.challenge; - Session::Sessions.erase(frame); - - Proto::Session::Packet dataPacket; - if (!dataPacket.ParseFromString(data)) return; - - Utils::Cryptography::ECC::Key publicKey; - publicKey.set(dataPacket.publickey()); - - if (!Utils::Cryptography::ECC::VerifyMessage(publicKey, challenge, dataPacket.signature())) return; - - auto handler = Session::PacketHandlers.find(dataPacket.command()); - if (handler == Session::PacketHandlers.end()) return; - - handler->second(address, dataPacket.data()); - }); -#endif - } - - Session::~Session() - { - std::lock_guard _(Session::Mutex); - Session::PacketHandlers.clear(); - Session::PacketQueue.clear(); - Session::SignatureQueue = std::queue>(); - - Session::SignatureKey.free(); - } - - void Session::preDestroy() - { - Session::Terminate = true; - if (Session::Thread.joinable()) - { - Session::Thread.join(); - } - } - - bool Session::unitTest() - { - printf("Testing ECDSA key..."); - Utils::Cryptography::ECC::Key key = Utils::Cryptography::ECC::GenerateKey(512); - - if (!key.isValid()) - { - printf("Error\n"); - printf("ECDSA key seems invalid!\n"); - return false; - } - - printf("Success\n"); - printf("Testing 10 valid signatures..."); - - for (int i = 0; i < 10; ++i) - { - std::string message = Utils::Cryptography::Rand::GenerateChallenge(); - std::string signature = Utils::Cryptography::ECC::SignMessage(key, message); - - if (!Utils::Cryptography::ECC::VerifyMessage(key, message, signature)) - { - printf("Error\n"); - printf("Signature for '%s' (%d) was invalid!\n", message.data(), i); - return false; - } - } - - printf("Success\n"); - printf("Testing 10 invalid signatures..."); - - for (int i = 0; i < 10; ++i) - { - std::string message = Utils::Cryptography::Rand::GenerateChallenge(); - std::string signature = Utils::Cryptography::ECC::SignMessage(key, message); - - // Invalidate the message... - ++message[Utils::Cryptography::Rand::GenerateInt() % message.size()]; - - if (Utils::Cryptography::ECC::VerifyMessage(key, message, signature)) - { - printf("Error\n"); - printf("Signature for '%s' (%d) was valid? What the fuck? That is absolutely impossible...\n", message.data(), i); - return false; - } - } - - printf("Success\n"); - printf("Testing ECDSA key import..."); - - std::string pubKey = key.getPublicKey(); - std::string message = Utils::Cryptography::Rand::GenerateChallenge(); - std::string signature = Utils::Cryptography::ECC::SignMessage(key, message); - - Utils::Cryptography::ECC::Key testKey; - testKey.set(pubKey); - - if (!Utils::Cryptography::ECC::VerifyMessage(key, message, signature)) - { - printf("Error\n"); - printf("Verifying signature for message '%s' using imported keys failed!\n", message.data()); - return false; - } - - printf("Success\n"); - return true; - } -} diff --git a/src/Components/Modules/Session.hpp b/src/Components/Modules/Session.hpp deleted file mode 100644 index fd28e894..00000000 --- a/src/Components/Modules/Session.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#define SESSION_TIMEOUT (10 * 1000) //10s -#define SESSION_MAX_RETRIES 3 -#define SESSION_REQUEST_LIMIT 10 - -#define DISABLE_SESSION - -namespace Components -{ - class Session : public Component - { - public: - class Packet - { - public: - std::string command; - std::string data; - - unsigned int tries; - std::optional lastTry; - }; - - class Frame - { - public: - std::string challenge; - Utils::Time::Point creationPoint; - }; - - Session(); - ~Session(); - - bool unitTest() override; - void preDestroy() override; - - static void Send(Network::Address target, std::string command, std::string data = ""); - static void Handle(std::string packet, Utils::Slot callback); - - private: - static bool Terminate; - static std::thread Thread; - static std::recursive_mutex Mutex; - static std::unordered_map Sessions; - static std::unordered_map>> PacketQueue; - - static Utils::Cryptography::ECC::Key SignatureKey; - - static std::map> PacketHandlers; - - static std::queue> SignatureQueue; - - static void RunFrame(); - static void HandleSignatures(); - }; -} diff --git a/src/Proto/network.proto b/src/Proto/network.proto new file mode 100644 index 00000000..a81302e2 --- /dev/null +++ b/src/Proto/network.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package Proto.Network; + +message Node +{ + bytes id = 1; + bytes address = 2; +} + +message Nodes +{ + repeated Node nodes = 1; +} diff --git a/src/Proto/node.proto b/src/Proto/node.proto deleted file mode 100644 index c26b8165..00000000 --- a/src/Proto/node.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package Proto.Node; - -message List -{ - repeated bytes nodes = 1; - - // The port is used to check if a dedi sends data through a redirected port. - // This usually means the port is not forwarded - uint32 port = 2; - - // Additional data - bool isNode = 3; - uint64 protocol = 4; -} diff --git a/src/Proto/session.proto b/src/Proto/session.proto deleted file mode 100644 index 8b786969..00000000 --- a/src/Proto/session.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto3"; - -package Proto.Session; - -message Packet -{ - bytes signature = 1; - bytes publicKey = 2; - bytes command = 3; - bytes data = 4; -} \ No newline at end of file diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 6947ed42..5f5c297a 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -80,6 +80,7 @@ template class Sizer { }; #include #include #include +#include #ifdef max #undef max @@ -90,10 +91,9 @@ template class Sizer { }; #endif // Protobuf -#include "proto/session.pb.h" +#include "proto/network.pb.h" #include "proto/party.pb.h" #include "proto/auth.pb.h" -#include "proto/node.pb.h" #include "proto/rcon.pb.h" #include "proto/ipc.pb.h" #include "proto/friends.pb.h" diff --git a/src/Utils/Cryptography.cpp b/src/Utils/Cryptography.cpp index 9bc4443b..e4ff5c1f 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -40,6 +40,31 @@ namespace Utils rng_make_prng(128, find_prng("fortuna"), &Rand::State, nullptr); } + std::string Rand::GetRandomBytes(size_t size) + { + Utils::Memory::Allocator allocator; + char* data = allocator.allocateArray(size); + + Rand::GetRandomBytes(data, size); + + return std::string(data, size); + } + + void Rand::GetRandomBytes(void* data, size_t size) + { + char* dataPtr = reinterpret_cast(data); + while (size > 0) + { + uint32_t num = Rand::GenerateInt(); + size_t len = std::min(size, sizeof(num)); + + std::memmove(dataPtr, &num, len); + + dataPtr += len; + size -= len; + } + } + #pragma endregion #pragma region ECC diff --git a/src/Utils/Cryptography.hpp b/src/Utils/Cryptography.hpp index 05b5b007..2b8be037 100644 --- a/src/Utils/Cryptography.hpp +++ b/src/Utils/Cryptography.hpp @@ -140,6 +140,9 @@ namespace Utils static uint32_t GenerateInt(); static void Initialize(); + static std::string GetRandomBytes(size_t size); + static void GetRandomBytes(void* data, size_t size); + private: static prng_state State; };