blob: 7ed1fc44b9eec0b8c6c9effc8f610a0ba5890e81 [file] [log] [blame]
Amna38768302023-08-21 11:51:56 -04001/*
Amna2f3539b2023-09-18 13:59:22 -04002 * Copyright (C) 2023 Savoir-faire Linux Inc.
Amna38768302023-08-21 11:51:56 -04003 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17#include "dnc.h"
18#include "certstore.h"
19#include "connectionmanager.h"
20#include "fileutils.h"
21#include "../common.h"
22
23#include <opendht/log.h>
24#include <opendht/crypto.h>
25#include <asio.hpp>
26
27#include <fcntl.h>
28#include <unistd.h>
29
Amna38768302023-08-21 11:51:56 -040030#include <chrono>
31#include <string>
32#include <string_view>
33#include <filesystem>
34#include <memory>
35
36namespace dhtnet {
37std::pair<std::string, std::string>
38Dnc::parseName(const std::string_view name)
39{
40 // Find the position of the first ':' character after "nc//"
41 size_t ip_add_start = name.find("nc//") + 6; // Adding 5 to skip "nc//"
42 size_t colonPos = name.find(':', ip_add_start);
43
44 if (colonPos == std::string::npos) {
45 // Return an empty pair if ':' is not found
46 return std::make_pair("", "");
47 }
48
49 std::string ip_add(name.substr(ip_add_start, colonPos - ip_add_start));
50 std::string port(name.substr(colonPos + 1));
51
52 return std::make_pair(ip_add, port);
53}
54
Amna38768302023-08-21 11:51:56 -040055// Build a server
Amnac75ffe92024-02-08 17:23:29 -050056Dnc::Dnc(dht::crypto::Identity identity,
Amna2f3539b2023-09-18 13:59:22 -040057 const std::string& bootstrap,
58 const std::string& turn_host,
59 const std::string& turn_user,
60 const std::string& turn_pass,
Amna4325f0f2024-01-22 16:11:00 -050061 const std::string& turn_realm,
Amna69996232024-07-23 14:04:44 -040062 const bool anonymous,
Amna2ee14f02024-07-24 15:15:55 -040063 const bool verbose,
Amna45db7762024-07-24 18:33:48 -040064 const std::map<std::string, std::vector<int>> authorized_services,
65 const bool enable_upnp)
Amna69996232024-07-23 14:04:44 -040066 :logger(verbose ? dht::log::getStdLogger() : nullptr),
67 ioContext(std::make_shared<asio::io_context>()),
Amna0e5f0762024-05-06 15:40:14 -040068 iceFactory(std::make_shared<IceTransportFactory>(logger))
Amna38768302023-08-21 11:51:56 -040069{
Amna5170f762024-08-16 11:49:56 -040070
Amna0e5f0762024-05-06 15:40:14 -040071 certStore = std::make_shared<tls::CertificateStore>(cachePath()/"certStore", logger);
72 trustStore = std::make_shared<tls::TrustStore>(*certStore);
73
Amna4325f0f2024-01-22 16:11:00 -050074 auto ca = identity.second->issuer;
75 trustStore->setCertificateStatus(ca->getId().toString(), tls::TrustStore::PermissionStatus::ALLOWED);
76
Amnac75ffe92024-02-08 17:23:29 -050077 auto config = connectionManagerConfig(identity,
Amna2f3539b2023-09-18 13:59:22 -040078 bootstrap,
79 logger,
80 certStore,
81 ioContext,
82 iceFactory,
83 turn_host,
84 turn_user,
85 turn_pass,
Amna45db7762024-07-24 18:33:48 -040086 turn_realm,
87 enable_upnp);
Amna38768302023-08-21 11:51:56 -040088 // create a connection manager
Adrien Béraudc1cac452023-08-22 20:32:36 -040089 connectionManager = std::make_unique<ConnectionManager>(std::move(config));
Amna38768302023-08-21 11:51:56 -040090
91 connectionManager->onDhtConnected(identity.first->getPublicKey());
Amna4325f0f2024-01-22 16:11:00 -050092 connectionManager->onICERequest([this, identity, anonymous](const DeviceId& deviceId) {
93 auto cert = certStore->getCertificate(deviceId.toString());
94 return trustStore->isAllowed(*cert, anonymous);
Amna38768302023-08-21 11:51:56 -040095 });
96
97 std::mutex mtx;
Adrien Béraud024c46f2024-03-02 23:53:18 -050098 std::unique_lock lk {mtx};
Amna38768302023-08-21 11:51:56 -040099
100 connectionManager->onChannelRequest(
Amna2ee14f02024-07-24 15:15:55 -0400101 [authorized_services, this](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& name) {
Amna38768302023-08-21 11:51:56 -0400102 // handle channel request
Amna2ee14f02024-07-24 15:15:55 -0400103 if (authorized_services.empty()) {
104 // Accept all connections if no authorized services are provided
105 return true;
106 }
107 // parse channel name to get the ip address and port: nc://<ip>:<port>
108 auto parsedName = parseName(name);
109 const std::string &ip = parsedName.first;
110 int port = 0;
111 try {
112 port = std::stoi(parsedName.second);
113 }
114 catch (std::exception const &err) {
115 fmt::print(stderr, "Rejecting connection: port '{}' is not a valid number", parsedName.second);
116 return false;
117 }
118
119 // Check if the IP is authorized
120 auto it = authorized_services.find(ip);
121 if (it == authorized_services.end()) {
122 // Reject the connection if the ip is not authorized
Amnac03c4b52024-07-24 17:57:25 -0400123 Log("Rejecting connection to {}:{}", ip, port);
Amna2ee14f02024-07-24 15:15:55 -0400124 return false;
125 }
126
127 // Check if the port is authorized
128 const auto &ports = it->second;
129 if (std::find(ports.begin(), ports.end(), port) == ports.end()) {
130 // Reject the connection if the port is not authorized
Amnac03c4b52024-07-24 17:57:25 -0400131 Log("Rejecting connection to {}:{}", ip, port);
Amna2ee14f02024-07-24 15:15:55 -0400132 return false;
133 }
Amnac03c4b52024-07-24 17:57:25 -0400134 Log("Accepting connection to {}:{}", ip, port);
Amna38768302023-08-21 11:51:56 -0400135 return true;
136 });
137
138 connectionManager->onConnectionReady([&](const DeviceId&,
139 const std::string& name,
140 std::shared_ptr<ChannelSocket> mtlxSocket) {
141 if (name.empty()) {
142 // Handle the empty input case here
143 return;
144 }
145 try {
146 auto parsedName = parseName(name);
Amnac03c4b52024-07-24 17:57:25 -0400147 Log("Connecting to {}:{}", parsedName.first, parsedName.second);
Adrien Béraudecde63f2023-08-26 18:11:21 -0400148
Amna38768302023-08-21 11:51:56 -0400149 asio::ip::tcp::resolver resolver(*ioContext);
150 asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(parsedName.first,
151 parsedName.second);
152
153 // Create a TCP socket
154 auto socket = std::make_shared<asio::ip::tcp::socket>(*ioContext);
Amna2f3539b2023-09-18 13:59:22 -0400155 socket->open(asio::ip::tcp::v4());
156 socket->set_option(asio::socket_base::keep_alive(true));
Amna38768302023-08-21 11:51:56 -0400157 asio::async_connect(
158 *socket,
159 endpoints,
160 [this, socket, mtlxSocket](const std::error_code& error,
Amna2f3539b2023-09-18 13:59:22 -0400161 const asio::ip::tcp::endpoint& ep) {
Amna38768302023-08-21 11:51:56 -0400162 if (!error) {
Amnac03c4b52024-07-24 17:57:25 -0400163 Log("Connected!\n");
Amna38768302023-08-21 11:51:56 -0400164 mtlxSocket->setOnRecv([socket, this](const uint8_t* data, size_t size) {
165 auto data_copy = std::make_shared<std::vector<uint8_t>>(data,
166 data + size);
167 asio::async_write(*socket,
168 asio::buffer(*data_copy),
169 [data_copy, this](const std::error_code& error,
170 std::size_t bytesWritten) {
171 if (error) {
Amnac03c4b52024-07-24 17:57:25 -0400172 Log("Write error: {}\n", error.message());
Amna38768302023-08-21 11:51:56 -0400173 }
Amna69996232024-07-23 14:04:44 -0400174
Amna38768302023-08-21 11:51:56 -0400175 });
176 return size;
177 });
178 // Create a buffer to read data into
Amna2f3539b2023-09-18 13:59:22 -0400179 auto buffer = std::make_shared<std::vector<uint8_t>>(BUFFER_SIZE);
Amna38768302023-08-21 11:51:56 -0400180 readFromPipe(mtlxSocket, socket, buffer);
181 } else {
Amnac03c4b52024-07-24 17:57:25 -0400182 Log("Connection error: {}\n", error.message());
Adrien Béraudecde63f2023-08-26 18:11:21 -0400183 mtlxSocket->shutdown();
Amna38768302023-08-21 11:51:56 -0400184 }
185 });
186
187 } catch (std::exception& e) {
Amnac03c4b52024-07-24 17:57:25 -0400188 Log("Exception: {}\n", e.what());
Amna38768302023-08-21 11:51:56 -0400189 }
190 });
191}
192// Build a client
Amnac75ffe92024-02-08 17:23:29 -0500193Dnc::Dnc(dht::crypto::Identity identity,
Adrien Béraudecde63f2023-08-26 18:11:21 -0400194 const std::string& bootstrap,
Amna38768302023-08-21 11:51:56 -0400195 dht::InfoHash peer_id,
Amna2f3539b2023-09-18 13:59:22 -0400196 const std::string& remote_host,
197 int remote_port,
198 const std::string& turn_host,
199 const std::string& turn_user,
200 const std::string& turn_pass,
Amna69996232024-07-23 14:04:44 -0400201 const std::string& turn_realm,
Amna45db7762024-07-24 18:33:48 -0400202 const bool verbose,
203 const bool enable_upnp)
204 : Dnc(identity, bootstrap,turn_host,turn_user,turn_pass, turn_realm, true, verbose, {}, enable_upnp)
Amna38768302023-08-21 11:51:56 -0400205{
206 std::condition_variable cv;
Adrien Béraudecde63f2023-08-26 18:11:21 -0400207 auto name = fmt::format("nc://{:s}:{:d}", remote_host, remote_port);
Amnac5ce2692024-08-05 12:18:40 -0400208 Log("Requesting socket: {}\n", name.c_str());
Amna2f3539b2023-09-18 13:59:22 -0400209 connectionManager->connectDevice(
210 peer_id, name, [&](std::shared_ptr<ChannelSocket> socket, const dht::InfoHash&) {
211 if (socket) {
212 socket->setOnRecv([this, socket](const uint8_t* data, size_t size) {
213 std::cout.write((const char*) data, size);
214 std::cout.flush();
215 return size;
216 });
217 // Create a buffer to read data into
218 auto buffer = std::make_shared<std::vector<uint8_t>>(BUFFER_SIZE);
Amna38768302023-08-21 11:51:56 -0400219
Amna2f3539b2023-09-18 13:59:22 -0400220 // Create a shared_ptr to the stream_descriptor
221 auto stdinPipe = std::make_shared<asio::posix::stream_descriptor>(*ioContext,
222 ::dup(
223 STDIN_FILENO));
224 readFromPipe(socket, stdinPipe, buffer);
Amna38768302023-08-21 11:51:56 -0400225
Amna2f3539b2023-09-18 13:59:22 -0400226 socket->onShutdown([this]() {
Amnac03c4b52024-07-24 17:57:25 -0400227 Log("Exit program\n");
Amna2f3539b2023-09-18 13:59:22 -0400228 ioContext->stop();
229 });
230 }
231 });
Amna38768302023-08-21 11:51:56 -0400232
Amna2f3539b2023-09-18 13:59:22 -0400233 connectionManager->onConnectionReady(
234 [&](const DeviceId&, const std::string& name, std::shared_ptr<ChannelSocket> mtlxSocket) {
Amnac03c4b52024-07-24 17:57:25 -0400235 Log("Connected!\n");
Amna2f3539b2023-09-18 13:59:22 -0400236 });
Amna38768302023-08-21 11:51:56 -0400237}
238
239void
240Dnc::run()
241{
Amna14d54f22024-08-29 16:17:38 -0400242 auto work = asio::make_work_guard(*ioContext);
Amna38768302023-08-21 11:51:56 -0400243 ioContext->run();
244}
245
Amna38768302023-08-21 11:51:56 -0400246Dnc::~Dnc()
247{
248 ioContext->stop();
Amna38768302023-08-21 11:51:56 -0400249}
250} // namespace dhtnet