tools: add peerDiscovery tool
This tool is designed to test peer discovery behavior under different connectivity scenarios.
Change-Id: I0fa1165ff1e1d08558a5a0b128d043d61d7edfa8
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 55bd01c..2f93419 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -346,6 +346,12 @@
target_include_directories(dhtnet-crtmgr PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
install(TARGETS dhtnet-crtmgr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+ add_executable(peerDiscovery
+ tools/peerdiscovery/peerDiscovery.cpp)
+ target_link_libraries(peerDiscovery PRIVATE dhtnet fmt::fmt readline)
+ target_include_directories(peerDiscovery PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools/peerdiscovery)
+ install(TARGETS peerDiscovery RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
install(FILES
tools/dnc/dnc.1
tools/dsh/dsh.1
@@ -390,4 +396,5 @@
#add_executable(tests_stringutils tests/testString_utils.cpp)
#target_link_libraries(tests_stringutils PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit)
#add_test(NAME tests_stringutils COMMAND tests_stringutils)
+
endif()
\ No newline at end of file
diff --git a/tools/peerdiscovery/peerDiscovery.cpp b/tools/peerdiscovery/peerDiscovery.cpp
new file mode 100644
index 0000000..a9590ca
--- /dev/null
+++ b/tools/peerdiscovery/peerDiscovery.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 Savoir-faire Linux Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "connectionmanager.h"
+#include "multiplexed_socket.h"
+#include "certstore.h"
+#include "string_utils.h"
+
+#include <opendht/log.h>
+#include <opendht/utils.h>
+#include <opendht/thread_pool.h>
+
+#include <asio/executor_work_guard.hpp>
+#include <asio/io_context.hpp>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include <condition_variable>
+#include<fmt/chrono.h>
+
+namespace dhtnet {
+using namespace std::literals::chrono_literals;
+using clock = std::chrono::high_resolution_clock;
+
+
+struct ConnectionHandler
+{
+ dht::crypto::Identity id;
+ std::shared_ptr<Logger> logger;
+ std::shared_ptr<tls::CertificateStore> certStore;
+ std::shared_ptr<dht::DhtRunner> dht;
+ std::shared_ptr<ConnectionManager> connectionManager;
+ std::shared_ptr<asio::io_context> ioContext;
+ std::shared_ptr<std::thread> ioContextRunner;
+};
+
+std::unique_ptr<ConnectionHandler>
+setupHandler(const std::string& name,
+ std::shared_ptr<asio::io_context> ioContext,
+ std::shared_ptr<std::thread> ioContextRunner,
+ std::shared_ptr<IceTransportFactory> factory,
+ std::shared_ptr<Logger> logger)
+{
+ auto h = std::make_unique<ConnectionHandler>();
+ auto ca = dht::crypto::generateIdentity("ca");
+ h->id = dht::crypto::generateIdentity(name, ca);
+ h->logger = logger;
+ h->certStore = std::make_shared<tls::CertificateStore>(name, h->logger);
+ h->ioContext = std::make_shared<asio::io_context>();
+ h->ioContext = ioContext;
+ h->ioContextRunner = ioContextRunner;
+
+ dht::DhtRunner::Config dhtConfig;
+ dhtConfig.dht_config.id = h->id;
+ dhtConfig.threaded = true;
+ dhtConfig.peer_discovery = true;
+ dhtConfig.peer_publish = true;
+
+ dht::DhtRunner::Context dhtContext;
+
+ dhtContext.identityAnnouncedCb = [](bool ok) {
+ fmt::print("{} Identity announced {}\n", clock::now().time_since_epoch().count(), ok);
+ };
+
+ dhtContext.publicAddressChangedCb = [](std::vector<dht::SockAddr> addr) {
+ if (addr.size() != 0)
+ fmt::print("{} Public address changed\n", clock::now().time_since_epoch().count());
+ };
+
+ dhtContext.statusChangedCallback = [](dht::NodeStatus status4, dht::NodeStatus status6) {
+ fmt::print("{} Connectivity changed: IPv4: {}, IPv6: {}\n", clock::now().time_since_epoch().count(), dht::statusToStr(status4), dht::statusToStr(status6));
+ };
+
+
+ dhtContext.certificateStore = [c = h->certStore](const dht::InfoHash& pk_id) {
+ std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
+ if (auto cert = c->getCertificate(pk_id.toString()))
+ ret.emplace_back(std::move(cert));
+ return ret;
+ };
+ // dhtContext.logger = h->logger;
+
+ h->dht = std::make_shared<dht::DhtRunner>();
+ h->dht->run(dhtConfig, std::move(dhtContext));
+
+ auto config = std::make_shared<ConnectionManager::Config>();
+ config->dht = h->dht;
+ config->id = h->id;
+ config->ioContext = h->ioContext;
+ config->factory = factory;
+ config->logger = logger;
+ config->certStore = h->certStore;
+ config->cachePath = std::filesystem::current_path() / "temp";
+
+ h->connectionManager = std::make_shared<ConnectionManager>(config);
+ h->connectionManager->onICERequest([](const DeviceId&) { return true; });
+ fmt::print("Identity:{}\n", h->id.second->getId());
+ return h;
+}
+
+void
+print_help()
+{
+ fmt::print("Commands:\n");
+ fmt::print(" help, h, ? - print this help\n");
+ fmt::print(" quit, exit, q, x - exit the program\n");
+ fmt::print(" connect <peer_id> - connect to a peer\n");
+ fmt::print(" cc - connectivity changed\n");
+}
+
+} // namespace dhtnet
+
+static void
+setSipLogLevel()
+{
+ int level = 0;
+ if (char* envvar = getenv("SIPLOGLEVEL")) {
+ // From 0 (min) to 6 (max)
+ level = std::clamp(std::stoi(envvar), 0, 6);
+ }
+
+ pj_log_set_level(level);
+ pj_log_set_log_func([](int level, const char* data, int /*len*/) {});
+}
+
+using namespace std::literals::chrono_literals;
+int
+main(int argc, char** argv)
+{
+ setSipLogLevel();
+ std::shared_ptr<dhtnet::Logger> logger; // = dht::log::getStdLogger();
+ auto factory = std::make_shared<dhtnet::IceTransportFactory>(logger);
+ auto ioContext = std::make_shared<asio::io_context>();
+ auto ioContextRunner = std::make_shared<std::thread>([context = ioContext]() {
+ try {
+ auto work = asio::make_work_guard(*context);
+ context->run();
+ } catch (const std::exception& ex) {
+ fmt::print(stderr, "Exception: {}\n", ex.what());
+ }
+ });
+
+ // Create a new DHTNet node
+ auto dhtnet = setupHandler("DHT", ioContext, ioContextRunner, factory, logger);
+
+ dhtnet->connectionManager->onDhtConnected(dhtnet->id.first->getPublicKey());
+
+ // Set up a handler for incoming channel requests
+ dhtnet->connectionManager->onChannelRequest(
+ [](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& name) {
+ fmt::print("Channel request received: {}\n", name);
+ return true;
+ });
+
+ while (true) {
+ char* l = readline("> ");
+ if (not l)
+ break;
+ std::string_view line {l};
+ if (line.empty())
+ continue;
+ add_history(l);
+ auto args = dhtnet::split_string(line, ' ');
+ auto command = args[0];
+ if (command == "quit" || command == "exit" || command == "q" || command == "x")
+ break;
+ else if (command == "help" || command == "h" || command == "?") {
+ dhtnet::print_help();
+ } else if (command == "connect") {
+ if (args.size() < 2) {
+ fmt::print("Usage: connect <peer_id>\n");
+ continue;
+ }
+ std::condition_variable cv;
+ std::mutex mtx;
+ std::unique_lock lock {mtx};
+
+ bool ret = false;
+ dht::InfoHash peer_id(args[1]);
+ dhtnet->connectionManager
+ ->connectDevice(peer_id,
+ "channelName",
+ [&](std::shared_ptr<dhtnet::ChannelSocket> socket,
+ const dht::InfoHash&) {
+ if (socket) {
+ ret = true;
+ cv.notify_one();
+ }
+ });
+ if (cv.wait_for(lock, 10s, [&] { return ret; })) {
+ fmt::print("Connected to {}\n", peer_id);
+ } else {
+ fmt::print("Failed to connect to {}\n", peer_id);
+ }
+ } else if (command == "cc") {
+ dhtnet->dht->connectivityChanged();
+ } else {
+ fmt::print("Unknown command: {}\n", command);
+ }
+ }
+ fmt::print("Stopping…\n");
+
+ ioContext->stop();
+ ioContextRunner->join();
+}