Revert "[DHT] Implement DHT"

This commit is contained in:
momo5502 2018-10-09 10:53:15 +02:00
parent 537cbe12f0
commit b2c94a7be3
23 changed files with 795 additions and 576 deletions

2
deps/dht vendored

@ -1 +1 @@
Subproject commit 1a489272ec72e5d571b63e3448063690d6d8656c
Subproject commit 54ee76af630f56cb97646bc770b88bfb592fd328

View File

@ -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());

View File

@ -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"

View File

@ -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<Network::Address> DHT::Nodes;
std::map<std::basic_string<uint8_t>, Utils::Slot<void(std::vector<Network::Address>)>> DHT::Handlers;
void DHT::Insert(std::string data, Utils::Slot<void(std::vector<Network::Address>)> callback)
{
unsigned char hash[20];
DHT::Hash(data, hash, sizeof(hash));
DHT::InsertHash(hash, callback);
}
void DHT::InsertHash(char* hash, Utils::Slot<void(std::vector<Network::Address>)> callback)
{
DHT::InsertHash(reinterpret_cast<unsigned char*>(hash), callback);
}
void DHT::InsertHash(unsigned char* hash, Utils::Slot<void(std::vector<Network::Address>)> callback)
{
std::basic_string<uint8_t> 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<uint8_t> hashStr(info_hash, 20);
auto handler = DHT::Handlers.find(hashStr);
if (handler != DHT::Handlers.end())
{
std::vector<Network::Address> addresses;
const unsigned char* bytes = reinterpret_cast<const unsigned char*>(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<const unsigned short*>(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<sockaddr_in>(nodes);
DHT::Id* ids = allocator.allocateArray<DHT::Id>(nodes);
int num6 = 0;
dht_get_nodes(addresses, reinterpret_cast<unsigned char*>(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<const unsigned char*>(id.data()), reinterpret_cast<sockaddr*>(LPSTR(address.data())), INT(address.size()));
}
}
}
void DHT::Add(Network::Address addr)
{
std::lock_guard<std::mutex> _(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<std::mutex> _(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<bool>() ? 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<sockaddr*>(&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<Utils::Time::Interval> 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<sockaddr*>(&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<unsigned char*>(idData.data()), reinterpret_cast<unsigned char*>("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<Network::Address> addresses)
{
std::lock_guard<std::mutex> _(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();
}
}

View File

@ -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<void(std::vector<Network::Address>)> callback);
static void InsertHash(char* hash, Utils::Slot<void(std::vector<Network::Address>)> callback);
static void InsertHash(unsigned char* hash, Utils::Slot<void(std::vector<Network::Address>)> 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<Network::Address> Nodes;
static std::map<std::basic_string<uint8_t>, Utils::Slot<void(std::vector<Network::Address>)>> 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);
};
}

View File

@ -7,26 +7,6 @@ namespace Components
std::thread Discovery::Thread;
std::string Discovery::Challenge;
std::mutex Discovery::Mutex;
std::vector<Network::Address> Discovery::LocalServers;
std::vector<Network::Address> Discovery::GetLocalServers()
{
std::lock_guard<std::mutex> _(Discovery::Mutex);
return Discovery::LocalServers;
}
void Discovery::InsertServer(Network::Address server)
{
if (!server.isLocal()) return;
std::lock_guard<std::mutex> _(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<std::mutex> _(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<Utils::Time::Interval> 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())
{

View File

@ -12,17 +12,10 @@ namespace Components
static void Perform();
static std::vector<Network::Address> GetLocalServers();
private:
static bool IsTerminating;
static bool IsPerforming;
static std::thread Thread;
static std::string Challenge;
static std::mutex Mutex;
static std::vector<Network::Address> LocalServers;
static void InsertServer(Network::Address server);
};
}

View File

@ -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());
}

View File

@ -33,7 +33,7 @@ namespace Components
{
Utils::Hook::Call<void()>(0x49F0B0)(); // Com_ClientPacketEvent
//Session::RunFrame();
DHT::RunFrame();
Node::RunFrame();
ServerList::Frame();
std::this_thread::sleep_for(10ms);

View File

@ -18,7 +18,6 @@ namespace Components
Address(sockaddr_in* addr) : Address(reinterpret_cast<sockaddr*>(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;

View File

@ -0,0 +1,355 @@
#include "STDInclude.hpp"
namespace Components
{
std::recursive_mutex Node::Mutex;
std::vector<Node::Entry> 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<sockaddr*>(const_cast<char*>(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<sockaddr*>(const_cast<char*>(addr.data())));
}
}
}
void Node::StoreNodes(bool force)
{
if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get<bool>()) 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<char*>(&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<std::recursive_mutex> _(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<bool>()) 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<int>(1000.0f / Dvar::Var("net_serverFrames").get<int>());
if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return;
frameLimit.update();
std::lock_guard<std::recursive_mutex> _(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<int>() && i->requiresRequest())
{
++sentRequests;
i->sendRequest();
}
++i;
}
}
void Node::Synchronize()
{
std::lock_guard<std::recursive_mutex> _(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<std::recursive_mutex> _(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<sockaddr*>(const_cast<char*>(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<std::recursive_mutex> _(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<char*>(&addr), sizeof(addr));
}
}
Session::Send(address, "nodeListResponse", list.SerializeAsString());
}
unsigned short Node::GetPort()
{
if (Dvar::Var("net_natFix").get<bool>()) return 0;
return Network::GetPort();
}
Node::Node()
{
if (ZoneBuilder::IsEnabled()) return;
Dvar::Register<bool>("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<std::recursive_mutex> _(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<std::recursive_mutex> _(Node::Mutex);
Node::StoreNodes(true);
Node::Nodes.clear();
}
}

View File

@ -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<Utils::Time::Point> lastRequest;
std::optional<Utils::Time::Point> 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<Entry> 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();
};
}

View File

@ -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<int>()));
info.set("sv_running", (Dvar::Var("sv_running").get<bool>() ? "1" : "0"));
if (Dedicated::IsEnabled())
{
std::vector<Network::Address> servers = Discovery::GetLocalServers();
std::vector<unsigned short> 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<std::string> 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"));
});

View File

@ -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<std::recursive_mutex> _(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<std::recursive_mutex> _(ServerList::RefreshContainer.mutex);

View File

@ -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();

View File

@ -0,0 +1,282 @@
#include "STDInclude.hpp"
namespace Components
{
bool Session::Terminate;
std::thread Session::Thread;
std::recursive_mutex Session::Mutex;
std::unordered_map<Network::Address, Session::Frame> Session::Sessions;
std::unordered_map<Network::Address, std::queue<std::shared_ptr<Session::Packet>>> Session::PacketQueue;
Utils::Cryptography::ECC::Key Session::SignatureKey;
std::map<std::string, Utils::Slot<Network::Callback>> Session::PacketHandlers;
std::queue<std::pair<Network::Address, std::string>> 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<std::recursive_mutex> _(Session::Mutex);
auto queue = Session::PacketQueue.find(target);
if (queue == Session::PacketQueue.end())
{
Session::PacketQueue[target] = std::queue<std::shared_ptr<Session::Packet>>();
queue = Session::PacketQueue.find(target);
if (queue == Session::PacketQueue.end()) Logger::Error("Failed to enqueue session packet!\n");
}
std::shared_ptr<Session::Packet> packet = std::make_shared<Session::Packet>();
packet->command = command;
packet->data = data;
packet->tries = 0;
queue->second.push(packet);
#endif
}
void Session::Handle(std::string packet, Utils::Slot<Network::Callback> callback)
{
#ifdef DISABLE_SESSION
Network::Handle(packet, callback);
#else
std::lock_guard<std::recursive_mutex> _(Session::Mutex);
Session::PacketHandlers[packet] = callback;
#endif
}
void Session::RunFrame()
{
std::lock_guard<std::recursive_mutex> _(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<Session::Packet> 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<std::recursive_mutex> 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<Session::Packet> 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<std::recursive_mutex> _(Session::Mutex);
Session::Sessions[address] = frame;
Network::SendCommand(address, "sessionAck", frame.challenge);
});
Network::Handle("sessionAck", [](Network::Address address, std::string data)
{
std::lock_guard<std::recursive_mutex> _(Session::Mutex);
Session::SignatureQueue.push({ address, data });
});
Network::Handle("sessionFin", [](Network::Address address, std::string data)
{
std::lock_guard<std::recursive_mutex> _(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<std::recursive_mutex> _(Session::Mutex);
Session::PacketHandlers.clear();
Session::PacketQueue.clear();
Session::SignatureQueue = std::queue<std::pair<Network::Address, std::string>>();
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;
}
}

View File

@ -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<Utils::Time::Point> 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<Network::Callback> callback);
private:
static bool Terminate;
static std::thread Thread;
static std::recursive_mutex Mutex;
static std::unordered_map<Network::Address, Frame> Sessions;
static std::unordered_map<Network::Address, std::queue<std::shared_ptr<Packet>>> PacketQueue;
static Utils::Cryptography::ECC::Key SignatureKey;
static std::map<std::string, Utils::Slot<Network::Callback>> PacketHandlers;
static std::queue<std::pair<Network::Address, std::string>> SignatureQueue;
static void RunFrame();
static void HandleSignatures();
};
}

View File

@ -1,14 +0,0 @@
syntax = "proto3";
package Proto.Network;
message Node
{
bytes id = 1;
bytes address = 2;
}
message Nodes
{
repeated Node nodes = 1;
}

16
src/Proto/node.proto Normal file
View File

@ -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;
}

11
src/Proto/session.proto Normal file
View File

@ -0,0 +1,11 @@
syntax = "proto3";
package Proto.Session;
message Packet
{
bytes signature = 1;
bytes publicKey = 2;
bytes command = 3;
bytes data = 4;
}

View File

@ -81,7 +81,6 @@ template <size_t S> class Sizer { };
#include <json11.hpp>
#include <tomcrypt.h>
#include <udis86.h>
#include <dht.h>
#ifdef max
#undef max
@ -103,9 +102,10 @@ template <size_t S> 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"

View File

@ -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<char>(size);
Rand::GetRandomBytes(data, size);
return std::string(data, size);
}
void Rand::GetRandomBytes(void* data, size_t size)
{
char* dataPtr = reinterpret_cast<char*>(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

View File

@ -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;
};