diff --git a/include/opendht/callbacks.h b/include/opendht/callbacks.h index 604a164014908e47ef186b38917448eb2fa72079..7dcfb65af0fbf9f2c36a5937c2e50b9a3908296d 100644 --- a/include/opendht/callbacks.h +++ b/include/opendht/callbacks.h @@ -103,6 +103,9 @@ struct OPENDHT_PUBLIC Config { /** Makes the DHT responsible to maintain its stored values. Consumes more ressources. */ bool maintain_storage; + + /** If set, the dht will load its state from this file on start and save its state in this file on shutdown */ + std::string persist_path; }; /** diff --git a/include/opendht/dht.h b/include/opendht/dht.h index e604aa05114e98a125a2b5c985285741029c3143..71cdd090e59cbdc481fba4043f45ab02d4bd7658 100644 --- a/include/opendht/dht.h +++ b/include/opendht/dht.h @@ -266,6 +266,9 @@ public: std::vector<ValuesExport> exportValues() const; void importValues(const std::vector<ValuesExport>&); + void saveState(const std::string& path) const; + void loadState(const std::string& path); + NodeStats getNodesStats(sa_family_t af) const; std::string getStorageLog() const; @@ -359,13 +362,6 @@ private: // registred types TypeStore types; - // are we a bootstrap node ? - // note: Any running node can be used as a bootstrap node. - // Only nodes running only as bootstrap nodes should - // be put in bootstrap mode. - const bool is_bootstrap {false}; - const bool maintain_storage {false}; - // the stuff RoutingTable buckets4 {}; RoutingTable buckets6 {}; @@ -400,6 +396,15 @@ private: std::mt19937_64 rd {crypto::getSeededRandomEngine<std::mt19937_64>()}; + std::string persistPath; + + // are we a bootstrap node ? + // note: Any running node can be used as a bootstrap node. + // Only nodes running only as bootstrap nodes should + // be put in bootstrap mode. + const bool is_bootstrap {false}; + const bool maintain_storage {false}; + void rotateSecrets(); Blob makeToken(const SockAddr&, bool old) const; diff --git a/include/opendht/dhtrunner.h b/include/opendht/dhtrunner.h index e74391313d69d2cde7cddaf161e3558439bd2f49..5f685799485cfe49e2dc04bcaa6a7f7d44d41f6d 100644 --- a/include/opendht/dhtrunner.h +++ b/include/opendht/dhtrunner.h @@ -354,7 +354,8 @@ public: /*.node_id = */{}, /*.network = */network, /*.is_bootstrap = */false, - /*.maintain_storage*/false + /*.maintain_storage = */false, + /*.persist_path = */{} }, /*.id = */identity }, diff --git a/src/dht.cpp b/src/dht.cpp index d0ceda3429b16c485587cad5ddfd7c17bc3b9a03..58804df31f0f664632fc175149a8e46eeeb4b2d1 100644 --- a/src/dht.cpp +++ b/src/dht.cpp @@ -29,6 +29,7 @@ #include <algorithm> #include <random> #include <sstream> +#include <fstream> namespace dht { @@ -54,6 +55,9 @@ Dht::getStatus(sa_family_t af) const void Dht::shutdown(ShutdownCallback cb) { + if (not persistPath.empty()) + saveState(persistPath); + if (not maintain_storage) { if (cb) cb(); return; @@ -1722,9 +1726,7 @@ Dht::~Dht() Dht::Dht() : store(), network_engine(DHT_LOG, scheduler) {} Dht::Dht(const int& s, const int& s6, Config config) - : myid(config.node_id ? config.node_id : InfoHash::getRandom()), - is_bootstrap(config.is_bootstrap), - maintain_storage(config.maintain_storage), store(), store_quota(), + : myid(config.node_id ? config.node_id : InfoHash::getRandom()), store(), store_quota(), network_engine(myid, config.network, s, s6, DHT_LOG, scheduler, std::bind(&Dht::onError, this, _1, _2), std::bind(&Dht::onNewNode, this, _1, _2), @@ -1734,7 +1736,10 @@ Dht::Dht(const int& s, const int& s6, Config config) std::bind(&Dht::onGetValues, this, _1, _2, _3, _4), std::bind(&Dht::onListen, this, _1, _2, _3, _4, _5), std::bind(&Dht::onAnnounce, this, _1, _2, _3, _4, _5), - std::bind(&Dht::onRefresh, this, _1, _2, _3, _4)) + std::bind(&Dht::onRefresh, this, _1, _2, _3, _4)), + persistPath(config.persist_path), + is_bootstrap(config.is_bootstrap), + maintain_storage(config.maintain_storage) { scheduler.syncTime(); if (s < 0 && s6 < 0) @@ -1765,8 +1770,10 @@ Dht::Dht(const int& s, const int& s6, Config config) expire(); DHT_LOG.d("DHT initialised with node ID %s", myid.toString().c_str()); -} + if (not persistPath.empty()) + loadState(persistPath); +} bool Dht::neighbourhoodMaintenance(RoutingTable& list) @@ -2283,7 +2290,7 @@ Dht::onListen(Sp<Node> node, const InfoHash& hash, const Blob& token, size_t soc } void -Dht::onListenDone(const Sp<Node>& node, net::RequestAnswer& answer, Sp<Search>& sr) +Dht::onListenDone(const Sp<Node>& /* node */, net::RequestAnswer& /* answer */, Sp<Search>& sr) { // DHT_LOG.d(sr->id, node->id, "[search %s] [node %s] got listen confirmation", // sr->id.toString().c_str(), node->toString().c_str(), answer.values.size()); @@ -2424,4 +2431,52 @@ Dht::onAnnounceDone(const Sp<Node>& node, net::RequestAnswer& answer, Sp<Search> sr->checkAnnounced(answer.vid); } + +void +Dht::saveState(const std::string& path) const +{ + std::ofstream file(path); + msgpack::pack(file, exportNodes()); + msgpack::pack(file, exportValues()); +} + +void +Dht::loadState(const std::string& path) +{ + DHT_LOG.d("Importing state from %s", path.c_str()); + try { + // Import nodes from binary file + msgpack::unpacker pac; + { + // Read whole file + std::ifstream file(path, std::ios::binary|std::ios::ate); + if (!file.is_open()) { + return; + } + auto size = file.tellg(); + file.seekg (0, std::ios::beg); + pac.reserve_buffer(size); + file.read (pac.buffer(), size); + pac.buffer_consumed(size); + } + // Import nodes + msgpack::object_handle oh; + if (pac.next(oh)) { + { + auto imported_nodes = oh.get().as<std::vector<NodeExport>>(); + DHT_LOG.d("Importing %zu nodes", imported_nodes.size()); + for (const auto& node : imported_nodes) + insertNode(node); + } + if (pac.next(oh)) { + auto imported_values = oh.get().as<std::vector<ValuesExport>>(); + DHT_LOG.d("Importing %zu values", imported_values.size()); + importValues(imported_values); + } + } + } catch (const std::exception& e) { + DHT_LOG.w("Error importing state from %s: %s", path.c_str(), e.what()); + } +} + } diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp index ebab15377ae846107b665c37b1d094a9a43b824a..7befc75f870ffe5f6e1f170b186e699b2958e095 100644 --- a/tools/dhtnode.cpp +++ b/tools/dhtnode.cpp @@ -500,6 +500,7 @@ main(int argc, char **argv) dht::DhtRunner::Config config {}; config.dht_config.node_config.network = params.network; config.dht_config.node_config.maintain_storage = false; + config.dht_config.node_config.persist_path = params.persist_path; config.dht_config.id = crt; config.threaded = true; config.proxy_server = params.proxyclient; diff --git a/tools/tools_common.h b/tools/tools_common.h index 92c2af5d8ceee703d6dc6640b46fa2e4459a9e02..bdbab8a8f568948d8601b269b5e3764adda73766 100644 --- a/tools/tools_common.h +++ b/tools/tools_common.h @@ -103,6 +103,7 @@ struct dht_params { std::string proxyclient {}; std::string pushserver {}; std::string devicekey {}; + std::string persist_path {}; }; static const constexpr struct option long_options[] = { @@ -114,6 +115,7 @@ static const constexpr struct option long_options[] = { {"verbose", no_argument , nullptr, 'v'}, {"daemonize", no_argument , nullptr, 'd'}, {"service", no_argument , nullptr, 's'}, + {"persist", required_argument, nullptr, 'f'}, {"logfile", required_argument, nullptr, 'l'}, {"syslog", no_argument , nullptr, 'L'}, {"proxyserver",required_argument, nullptr, 'S'}, @@ -127,7 +129,7 @@ dht_params parseArgs(int argc, char **argv) { dht_params params; int opt; - while ((opt = getopt_long(argc, argv, "hidsvp:n:b:l:", long_options, nullptr)) != -1) { + while ((opt = getopt_long(argc, argv, "hidsvp:n:b:f:l:", long_options, nullptr)) != -1) { switch (opt) { case 'p': { int port_arg = atoi(optarg); @@ -154,6 +156,9 @@ parseArgs(int argc, char **argv) { case 'D': params.devicekey = optarg; break; + case 'f': + params.persist_path = optarg; + break; case 'n': params.network = strtoul(optarg, nullptr, 0); break;