diff --git a/deps/dht b/deps/dht index 1a489272..54ee76af 160000 --- a/deps/dht +++ b/deps/dht @@ -1 +1 @@ -Subproject commit 1a489272ec72e5d571b63e3448063690d6d8656c +Subproject commit 54ee76af630f56cb97646bc770b88bfb592fd328 diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 234bb1c5..10f487eb 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -33,7 +33,6 @@ 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()); @@ -41,6 +40,7 @@ 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,6 +63,7 @@ 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 72cdf9da..3c9a5699 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -86,13 +86,14 @@ namespace Components #include "Modules/Network.hpp" #include "Modules/Theatre.hpp" #include "Modules/QuickPatch.hpp" -#include "Modules/DHT.hpp" +#include "Modules/Node.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 deleted file mode 100644 index 3fa5784a..00000000 --- a/src/Components/Modules/DHT.cpp +++ /dev/null @@ -1,372 +0,0 @@ -#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 deleted file mode 100644 index 315e8a66..00000000 --- a/src/Components/Modules/DHT.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#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 d16118aa..833b7103 100644 --- a/src/Components/Modules/Discovery.cpp +++ b/src/Components/Modules/Discovery.cpp @@ -7,26 +7,6 @@ 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; @@ -47,9 +27,6 @@ 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"); @@ -69,23 +46,6 @@ 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; @@ -98,8 +58,6 @@ 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) @@ -119,7 +77,6 @@ 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 24dfc22e..655122bf 100644 --- a/src/Components/Modules/Discovery.hpp +++ b/src/Components/Modules/Discovery.hpp @@ -12,17 +12,10 @@ 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 ccd74024..448e3ed7 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) { - DHT::Add(entry->server); + Node::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 21821db9..4f0edcb5 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(); - DHT::RunFrame(); + Node::RunFrame(); ServerList::Frame(); std::this_thread::sleep_for(10ms); diff --git a/src/Components/Modules/Network.hpp b/src/Components/Modules/Network.hpp index b6beda23..4a2919b2 100644 --- a/src/Components/Modules/Network.hpp +++ b/src/Components/Modules/Network.hpp @@ -18,7 +18,6 @@ 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 new file mode 100644 index 00000000..f033d885 --- /dev/null +++ b/src/Components/Modules/Node.cpp @@ -0,0 +1,355 @@ +#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 new file mode 100644 index 00000000..ac6ce353 --- /dev/null +++ b/src/Components/Modules/Node.hpp @@ -0,0 +1,64 @@ +#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 3453a514..2779c286 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) { - DHT::Add(target); + Node::Add(target); Party::Container.valid = true; Party::Container.awaitingPlaylist = false; @@ -346,27 +346,6 @@ 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()) { @@ -530,23 +509,6 @@ 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 f9cad1d0..fe56129a 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 - DHT::Search(); + Node::Synchronize(); #endif } else if (ServerList::IsFavouriteList()) @@ -394,22 +394,6 @@ 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 260409e4..4b246ce8 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -37,7 +37,6 @@ 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 new file mode 100644 index 00000000..a2e0852e --- /dev/null +++ b/src/Components/Modules/Session.cpp @@ -0,0 +1,282 @@ +#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 new file mode 100644 index 00000000..fd28e894 --- /dev/null +++ b/src/Components/Modules/Session.hpp @@ -0,0 +1,56 @@ +#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 deleted file mode 100644 index a81302e2..00000000 --- a/src/Proto/network.proto +++ /dev/null @@ -1,14 +0,0 @@ -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 new file mode 100644 index 00000000..c26b8165 --- /dev/null +++ b/src/Proto/node.proto @@ -0,0 +1,16 @@ +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 new file mode 100644 index 00000000..8b786969 --- /dev/null +++ b/src/Proto/session.proto @@ -0,0 +1,11 @@ +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 d44062dd..b6182f9f 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -81,7 +81,6 @@ template class Sizer { }; #include #include #include -#include #ifdef max #undef max @@ -103,9 +102,10 @@ template class Sizer { }; #endif // Protobuf -#include "proto/network.pb.h" +#include "proto/session.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 e4ff5c1f..9bc4443b 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -40,31 +40,6 @@ 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 2b8be037..05b5b007 100644 --- a/src/Utils/Cryptography.hpp +++ b/src/Utils/Cryptography.hpp @@ -140,9 +140,6 @@ 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; };