From 387683077a5ca4cf1ba73914f46cb1590acf66d1 Mon Sep 17 00:00:00 2001
From: Amna <amna.snene@savoirfairelinux.com>
Date: Mon, 21 Aug 2023 11:51:56 -0400
Subject: [PATCH] tools: add dnc (distributed netcat) tool

Change-Id: I85a4845a75f4aa46509a7a94ecace821745118e0
---
 CMakeLists.txt     |  12 +++
 tools/common.cpp   | 147 +++++++++++++++++++++++++++++++
 tools/common.h     |  44 ++++++++++
 tools/dnc/dnc.cpp  | 210 +++++++++++++++++++++++++++++++++++++++++++++
 tools/dnc/dnc.h    |  61 +++++++++++++
 tools/dnc/main.cpp | 150 ++++++++++++++++++++++++++++++++
 6 files changed, 624 insertions(+)
 create mode 100644 tools/common.cpp
 create mode 100644 tools/common.h
 create mode 100644 tools/dnc/dnc.cpp
 create mode 100644 tools/dnc/dnc.h
 create mode 100644 tools/dnc/main.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index dad3553..077c4d4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,8 @@ project(dhtnet
     LANGUAGES CXX
     DESCRIPTION "A C++ library for NAT traversal and secure communication")
 
+option(BUILD_TOOLS "Build dnc" ON)
+
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 include(CTest)
@@ -135,6 +137,16 @@ install(TARGETS dhtnet)
 install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dhtnet)
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dhtnet.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
 
+if (BUILD_TOOLS)
+    add_executable(dnc
+        tools/dnc/main.cpp
+        tools/dnc/dnc.cpp
+        tools/common.cpp)
+    target_link_libraries(dnc PRIVATE dhtnet fmt::fmt)
+    target_include_directories(dnc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
+    install(TARGETS dnc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+endif()
+
 if (BUILD_TESTING AND NOT MSVC)
     pkg_search_module(Cppunit REQUIRED IMPORTED_TARGET cppunit)
     add_executable(tests_certstore tests/certstore.cpp)
diff --git a/tools/common.cpp b/tools/common.cpp
new file mode 100644
index 0000000..101fbb9
--- /dev/null
+++ b/tools/common.cpp
@@ -0,0 +1,147 @@
+/*
+ *  Copyright (C) 2004-2023 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 "certstore.h"
+#include <opendht/crypto.h>
+#include "connectionmanager.h"
+#include "common.h"
+#include "fileutils.h"
+#include "ice_transport.h"
+
+#include <iostream>
+#include <string>
+#include <filesystem>
+#include <unistd.h>
+#include <fcntl.h>
+#include <asio.hpp>
+
+namespace dhtnet {
+
+std::shared_ptr<asio::posix::stream_descriptor> stdinDescriptor;
+
+dht::crypto::Identity
+loadIdentity(bool isServer)
+{
+    std::string idDir = std::string(getenv("HOME")) + "/.dhtnetTools";
+    if (isServer){
+
+        if (!std::filesystem::exists(idDir)) {
+            std::filesystem::create_directory(idDir);
+        }
+
+        try {
+            std::filesystem::directory_iterator endIter;
+            for (std::filesystem::directory_iterator iter(idDir); iter != endIter; ++iter) {
+                if (iter->path().extension() == ".pem") {
+                    auto privateKey = std::make_unique<dht::crypto::PrivateKey>(
+                        fileutils::loadFile(std::filesystem::path(iter->path())));
+                    // Generate certificate
+                    auto certificate = std::make_unique<dht::crypto::Certificate>(
+                        dht::crypto::Certificate::generate(*privateKey, "dhtnc"));
+                    // return
+                    return dht::crypto::Identity(std::move(privateKey), std::move(certificate));
+                }
+            }
+        } catch (const std::exception& e) {
+            fmt::print(stderr, "Error loadind key from .dhtnetTools: {}\n", e.what());
+        }
+    }
+    auto ca = dht::crypto::generateIdentity("ca");
+    auto id = dht::crypto::generateIdentity("dhtnc", ca);
+    idDir += "/id";
+    if (isServer)
+        dht::crypto::saveIdentity(id, idDir);
+    return id;
+}
+
+std::unique_ptr<ConnectionManager::Config>
+connectionManagerConfig(dht::crypto::Identity identity,
+                  const std::string& bootstrap_ip_add,
+                  const std::string& bootstrap_port,
+                  std::shared_ptr<Logger> logger,
+                  tls::CertificateStore& certStore,
+                  std::shared_ptr<asio::io_context> ioContext,
+                  IceTransportFactory& iceFactory)
+{
+    std::filesystem::create_directories(std::string(getenv("HOME")) + "/.dhtnetTools/certstore");
+
+    // DHT node creation: To make a connection manager at first a DHT node should be created
+
+    dht::DhtRunner::Config dhtConfig;
+    dhtConfig.dht_config.id = identity;
+    dhtConfig.threaded = true;
+    dhtConfig.peer_discovery = false;
+    dhtConfig.peer_publish = false;
+    dht::DhtRunner::Context dhtContext;
+    dhtContext.identityAnnouncedCb = [logger](bool ok) {
+        if (logger)
+            logger->debug("Identity announced {}\n", ok);
+    };
+    dhtContext.certificateStore = [&](const dht::InfoHash& pk_id) {
+        std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
+        if (auto cert = certStore.getCertificate(pk_id.toString()))
+            ret.emplace_back(std::move(cert));
+        return ret;
+    };
+    auto runner = std::make_shared<dht::DhtRunner>();
+    runner->run(dhtConfig, std::move(dhtContext));
+    runner->bootstrap(bootstrap_ip_add, bootstrap_port);
+
+    // DHT node creation end:
+    // ConnectionManager creation:
+    auto config = std::make_unique<ConnectionManager::Config>();
+    config->dht = runner;
+    config->id = identity;
+    config->ioContext = ioContext;
+    config->certStore = &certStore;
+    config->factory = &iceFactory;
+    config->cachePath = std::string(getenv("HOME")) + "/.dhtnetTools";
+
+    return std::move(config);
+}
+template <typename T>
+void readFromPipe(std::shared_ptr<ChannelSocket> socket, T input, Buffer buffer)
+{
+    asio::async_read(*input,
+                     asio::buffer(*buffer),
+                     asio::transfer_at_least(1),
+                     [socket, input, buffer](const asio::error_code& error, size_t bytesRead) {
+                         if (!error) {
+                             // Process the data received in the buffer
+                             std::error_code ec;
+                             // print the data to stdout
+                             socket->write(buffer->data(), bytesRead, ec);
+                             if (!ec) {
+                                 // Continue reading more data
+                                 readFromPipe(socket, input, buffer);
+                             } else {
+                                fmt::print(stderr, "Error writing to socket: {}\n", ec.message());
+                                // logger->error("Error writing to socket: {}", ec.message());
+                             }
+                         } else if(error != asio::error::eof) {
+                            fmt::print(stderr, "Error reading from stdin: {}\n", error.message());
+                            //  logger->error("Error reading from stdin: {}", error.message());
+                         }
+                     });
+}
+
+template void readFromPipe(std::shared_ptr<ChannelSocket> socket, std::shared_ptr<asio::posix::stream_descriptor> input, Buffer buffer);
+template void readFromPipe(std::shared_ptr<ChannelSocket> socket, std::shared_ptr<asio::ip::tcp::socket> input, Buffer buffer);
+
+
+
+
+} // namespace dhtnet
\ No newline at end of file
diff --git a/tools/common.h b/tools/common.h
new file mode 100644
index 0000000..19b7771
--- /dev/null
+++ b/tools/common.h
@@ -0,0 +1,44 @@
+/*
+ *  Copyright (C) 2004-2023 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 <opendht/crypto.h>
+
+
+namespace dhtnet {
+
+using Buffer = std::shared_ptr<std::vector<uint8_t>>;
+
+/**
+ * Attempt to retrieve the identity from the .ssh directory, and if none is found, generate a new
+ * certification.
+ * @return dht::crypto::Identity
+ */
+
+dht::crypto::Identity loadIdentity(bool isServer);
+// add certstore to the config
+std::unique_ptr<ConnectionManager::Config> connectionManagerConfig(dht::crypto::Identity identity,
+                                                      const std::string& bootstrap_ip_add,
+                                                      const std::string& bootstrap_port,
+                                                      std::shared_ptr<Logger> logger,
+                                                      tls::CertificateStore& certStore,
+                                                      std::shared_ptr<asio::io_context> ioContext,
+                                                      IceTransportFactory& iceFactory);
+// add ioContext to readFromStdin
+
+template <typename T>
+void readFromPipe(std::shared_ptr<ChannelSocket> socket, T input, Buffer buffer);
+
+} // namespace dhtnet
\ No newline at end of file
diff --git a/tools/dnc/dnc.cpp b/tools/dnc/dnc.cpp
new file mode 100644
index 0000000..dddc7b1
--- /dev/null
+++ b/tools/dnc/dnc.cpp
@@ -0,0 +1,210 @@
+/*
+ *  Copyright (C) 2004-2023 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 "dnc.h"
+#include "certstore.h"
+#include "connectionmanager.h"
+#include "fileutils.h"
+#include "../common.h"
+
+#include <opendht/log.h>
+#include <opendht/crypto.h>
+#include <asio.hpp>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <chrono>
+#include <string>
+#include <string_view>
+#include <filesystem>
+#include <memory>
+
+namespace dhtnet {
+std::pair<std::string, std::string>
+Dnc::parseName(const std::string_view name)
+{
+    // Find the position of the first ':' character after "nc//"
+    size_t ip_add_start = name.find("nc//") + 6; // Adding 5 to skip "nc//"
+    size_t colonPos = name.find(':', ip_add_start);
+
+    if (colonPos == std::string::npos) {
+        // Return an empty pair if ':' is not found
+        return std::make_pair("", "");
+    }
+
+    std::string ip_add(name.substr(ip_add_start, colonPos - ip_add_start));
+    std::string port(name.substr(colonPos + 1));
+
+    return std::make_pair(ip_add, port);
+}
+
+
+// Build a server
+Dnc::Dnc(dht::crypto::Identity identity,
+         const std::string& bootstrap_ip_add,
+         const std::string& bootstrap_port)
+    : logger(dht::log::getStdLogger())
+    , certStore(std::string(getenv("HOME")) + "/.dhtnetTools/certstore", logger)
+
+{
+    ioContext = std::make_shared<asio::io_context>();
+    ioContextRunner = std::thread([context = ioContext, logger = logger] {
+        try {
+            auto work = asio::make_work_guard(*context);
+            context->run();
+        } catch (const std::exception& ex) {
+            if (logger)
+                logger->error("Error in ioContextRunner: {}", ex.what());
+        }
+    });
+
+    auto config = connectionManagerConfig(identity, bootstrap_ip_add, bootstrap_port, logger, certStore, ioContext, iceFactory);
+    // create a connection manager
+    connectionManager = std::make_unique<ConnectionManager>(move(config));
+
+    connectionManager->onDhtConnected(identity.first->getPublicKey());
+    connectionManager->onICERequest([this](const dht::Hash<32>&) { // handle ICE request
+        if (logger)
+            logger->debug("ICE request received");
+        return true;
+    });
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+
+    connectionManager->onChannelRequest(
+        [&](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& name) {
+            // handle channel request
+            if (logger)
+                logger->debug("Channel request received");
+            return true;
+        });
+
+    connectionManager->onConnectionReady([&](const DeviceId&,
+                                             const std::string& name,
+                                             std::shared_ptr<ChannelSocket> mtlxSocket) {
+        if (name.empty()) {
+            // Handle the empty input case here
+            return;
+        }
+        try {
+            auto parsedName = parseName(name);
+            asio::ip::tcp::resolver resolver(*ioContext);
+            asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(parsedName.first,
+                                                                               parsedName.second);
+
+            // Create a TCP socket
+            auto socket = std::make_shared<asio::ip::tcp::socket>(*ioContext);
+            asio::async_connect(
+                *socket,
+                endpoints,
+                [this, socket, mtlxSocket](const std::error_code& error,
+                                                const asio::ip::tcp::endpoint& ep) {
+                    if (!error) {
+                        if (logger)
+                            logger->debug("Connected!");
+                        mtlxSocket->setOnRecv([socket, this](const uint8_t* data, size_t size) {
+                            auto data_copy = std::make_shared<std::vector<uint8_t>>(data,
+                                                                                    data + size);
+                            asio::async_write(*socket,
+                                              asio::buffer(*data_copy),
+                                              [data_copy, this](const std::error_code& error,
+                                                                std::size_t bytesWritten) {
+                                                  if (error) {
+                                                      if (logger)
+                                                          logger->error("Write error: {}",
+                                                                        error.message());
+                                                  }
+                                              });
+                            return size;
+                        });
+                        // Create a buffer to read data into
+                        auto buffer = std::make_shared<std::vector<uint8_t>>(65536);
+
+                        readFromPipe(mtlxSocket, socket, buffer);
+                    } else {
+                        if (logger)
+                            logger->error("Connection error: {}", error.message());
+                    }
+                });
+
+        } catch (std::exception& e) {
+            if (logger)
+                logger->error("Exception: {}", e.what());
+        }
+    });
+}
+// Build a client
+Dnc::Dnc(dht::crypto::Identity identity,
+         const std::string& bootstrap_ip_add,
+         const std::string& bootstrap_port,
+         dht::InfoHash peer_id,
+         int port,
+         const std::string& ip_add)
+    : Dnc(identity, bootstrap_ip_add, bootstrap_port)
+{
+    std::condition_variable cv;
+    auto name = fmt::format("nc://{:s}:{:d}", ip_add, port);
+    connectionManager->connectDevice(peer_id,
+                                     name,
+                                     [&](std::shared_ptr<ChannelSocket> socket,
+                                         const dht::InfoHash&) {
+                                         if (socket) {
+                                            socket->setOnRecv(
+                                                [this, socket](const uint8_t* data, size_t size) {
+                                                    std::cout.write((const char*) data, size);
+                                                    std::cout.flush();
+                                                    return size;
+                                                });
+                                            // Create a buffer to read data into
+                                            auto buffer = std::make_shared<std::vector<uint8_t>>(65536);
+
+                                            // Create a shared_ptr to the stream_descriptor
+                                            auto stdinPipe = std::make_shared<asio::posix::stream_descriptor>(*ioContext,
+                                                                                                                ::dup(STDIN_FILENO));
+                                            readFromPipe(socket, stdinPipe, buffer);
+
+                                            socket->onShutdown([this]() {
+                                                if (logger)
+                                                    logger->error("Exit program");
+                                                std::exit(EXIT_FAILURE);
+                                            });
+                                         }
+                                     });
+
+    connectionManager->onConnectionReady([&](const DeviceId&,
+                                             const std::string& name,
+                                             std::shared_ptr<ChannelSocket> mtlxSocket) {
+        if (logger)
+            logger->debug("Connected!");
+    });
+}
+
+void
+Dnc::run()
+{
+    ioContext->run();
+}
+
+
+Dnc::~Dnc()
+{
+    ioContext->stop();
+    ioContextRunner.join();
+}
+} // namespace dhtnet
diff --git a/tools/dnc/dnc.h b/tools/dnc/dnc.h
new file mode 100644
index 0000000..0c8c7c1
--- /dev/null
+++ b/tools/dnc/dnc.h
@@ -0,0 +1,61 @@
+/*
+ *  Copyright (C) 2004-2023 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 "ice_transport_factory.h"
+#include "certstore.h"
+
+#include <asio.hpp>
+
+namespace dhtnet {
+/**
+ * Attempt to retrieve the identity from the .ssh directory, and if none is found, generate a new
+ * certification.
+ * @return dht::crypto::Identity
+ */
+
+class Dnc
+{
+public:
+    // Build a server
+    Dnc(dht::crypto::Identity identity,
+        const std::string& bootstrap_ip_add,
+        const std::string& bootstrap_port);
+    // Build a client
+    Dnc(dht::crypto::Identity identity,
+        const std::string& bootstrap_ip_add,
+        const std::string& bootstrap_port,
+        dht::InfoHash peer_id,
+        int port,
+        const std::string& ip_add);
+    ~Dnc();
+    void run();
+
+private:
+    std::unique_ptr<ConnectionManager> connectionManager;
+    std::shared_ptr<Logger> logger;
+    tls::CertificateStore certStore;
+    IceTransportFactory iceFactory;
+    std::shared_ptr<asio::io_context> ioContext;
+    std::thread ioContextRunner;
+
+    std::shared_ptr<asio::posix::stream_descriptor> stdinDescriptor;
+
+    std::pair<std::string, std::string> parseName(const std::string_view name);
+};
+
+} // namespace dhtnet
diff --git a/tools/dnc/main.cpp b/tools/dnc/main.cpp
new file mode 100644
index 0000000..e0eeeeb
--- /dev/null
+++ b/tools/dnc/main.cpp
@@ -0,0 +1,150 @@
+/*
+ *  Copyright (C) 2004-2023 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 "dnc.h"
+#include "common.h"
+#include <string>
+#include <vector>
+#include <iostream>
+#include <unistd.h>
+#include <getopt.h>
+
+#include <netinet/in.h>
+
+struct dhtnc_params
+{
+    bool help {false};
+    bool version {false};
+    bool listen {false};
+    std::string ip_add {};
+    std::string bootstrap_ip {};
+    std::string bootstrap_port {};
+    in_port_t port {};
+    dht::InfoHash peer_id {};
+};
+
+static const constexpr struct option long_options[]
+    = {{"help", no_argument, nullptr, 'h'},
+       {"version", no_argument, nullptr, 'v'},
+       {"port", required_argument, nullptr, 'p'},
+       {"ip", required_argument, nullptr, 'i'},
+       {"listen", no_argument, nullptr, 'l'},
+       {"bootstrap_ip", required_argument, nullptr, 'b'},
+       {"bootstrap_port", required_argument, nullptr, 'P'},
+       {nullptr, 0, nullptr, 0}};
+
+dhtnc_params
+parse_args(int argc, char** argv)
+{
+    dhtnc_params params;
+    int opt;
+    while ((opt = getopt_long(argc, argv, "hvp:i:", long_options, nullptr)) != -1) {
+        switch (opt) {
+        case 'h':
+            params.help = true;
+            break;
+        case 'v':
+            params.version = true;
+            break;
+        case 'p':
+            params.port = std::stoi(optarg);
+            break;
+        case 'i':
+            params.ip_add = optarg;
+            break;
+        case 'l':
+            params.listen = true;
+            break;
+        case 'b':
+            params.bootstrap_ip = optarg;
+            break;
+        case 'P':
+            params.bootstrap_port = optarg;
+            break;
+        default:
+            std::cerr << "Invalid option" << std::endl;
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    // If not listening, the peer_id argument is required
+    if (!params.listen) {
+        if (optind < argc) {
+            params.peer_id = dht::InfoHash(argv[optind]);
+            optind++; // Move to the next argument
+        } else {
+            std::cerr << "Error: Missing peer_id argument.\n";
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    // default values
+    if (params.port == 0)
+        params.port = 22;
+    if (params.ip_add.empty())
+        params.ip_add = "127.0.0.1";
+    if (params.bootstrap_ip.empty())
+        params.bootstrap_ip = "bootstrap.jami.net";
+    if (params.bootstrap_port.empty())
+        params.bootstrap_port = "4222";
+    return params;
+}
+
+static void
+setSipLogLevel()
+{
+    char* envvar = getenv("SIPLOGLEVEL");
+
+    int level = 0;
+
+    if (envvar != nullptr) {
+        level = std::stoi(envvar);
+
+        // From 0 (min) to 6 (max)
+        level = std::max(0, std::min(level, 6));
+    }
+
+    pj_log_set_level(level);
+    pj_log_set_log_func([](int level, const char* data, int /*len*/) {
+    });
+}
+
+int
+main(int argc, char** argv)
+{
+    setSipLogLevel();
+    auto params = parse_args(argc, argv);
+
+    std::unique_ptr<dhtnet::Dnc> dhtnc;
+    if (params.listen) {
+        auto identity = dhtnet::loadIdentity(true);
+        // create dnc instance
+        dhtnc = std::make_unique<dhtnet::Dnc>(identity, params.bootstrap_ip, params.bootstrap_port);
+        fmt::print("DhtNC 1.1\n");
+        fmt::print("Loaded identity: {}\n", identity.second->getId());
+    } else {
+        auto identity = dhtnet::loadIdentity(false);
+        dhtnc = std::make_unique<dhtnet::Dnc>(identity,
+                                              params.bootstrap_ip,
+                                              params.bootstrap_port,
+                                              params.peer_id,
+                                              params.port,
+                                              params.ip_add);
+        fmt::print("DhtNC 1.0\n");
+        fmt::print("Loaded identity: {}\n", identity.second->getId());
+    }
+    dhtnc->run();
+}
-- 
GitLab