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