librats 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/binding.gyp +1 -0
- package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
- package/native-src/CMakeLists.txt +360 -0
- package/native-src/LICENSE +21 -0
- package/native-src/src/bencode.cpp +485 -0
- package/native-src/src/bencode.h +145 -0
- package/native-src/src/bittorrent.cpp +3682 -0
- package/native-src/src/bittorrent.h +731 -0
- package/native-src/src/dht.cpp +2342 -0
- package/native-src/src/dht.h +501 -0
- package/native-src/src/encrypted_socket.cpp +817 -0
- package/native-src/src/encrypted_socket.h +239 -0
- package/native-src/src/file_transfer.cpp +1808 -0
- package/native-src/src/file_transfer.h +567 -0
- package/native-src/src/fs.cpp +639 -0
- package/native-src/src/fs.h +108 -0
- package/native-src/src/gossipsub.cpp +1137 -0
- package/native-src/src/gossipsub.h +403 -0
- package/native-src/src/ice.cpp +1386 -0
- package/native-src/src/ice.h +328 -0
- package/native-src/src/json.hpp +25526 -0
- package/native-src/src/krpc.cpp +558 -0
- package/native-src/src/krpc.h +145 -0
- package/native-src/src/librats.cpp +2715 -0
- package/native-src/src/librats.h +1729 -0
- package/native-src/src/librats_bittorrent.cpp +167 -0
- package/native-src/src/librats_c.cpp +1317 -0
- package/native-src/src/librats_c.h +237 -0
- package/native-src/src/librats_encryption.cpp +123 -0
- package/native-src/src/librats_file_transfer.cpp +226 -0
- package/native-src/src/librats_gossipsub.cpp +293 -0
- package/native-src/src/librats_ice.cpp +515 -0
- package/native-src/src/librats_logging.cpp +158 -0
- package/native-src/src/librats_mdns.cpp +171 -0
- package/native-src/src/librats_nat.cpp +571 -0
- package/native-src/src/librats_persistence.cpp +815 -0
- package/native-src/src/logger.h +412 -0
- package/native-src/src/mdns.cpp +1178 -0
- package/native-src/src/mdns.h +253 -0
- package/native-src/src/network_utils.cpp +598 -0
- package/native-src/src/network_utils.h +162 -0
- package/native-src/src/noise.cpp +981 -0
- package/native-src/src/noise.h +227 -0
- package/native-src/src/os.cpp +371 -0
- package/native-src/src/os.h +40 -0
- package/native-src/src/rats_export.h +17 -0
- package/native-src/src/sha1.cpp +163 -0
- package/native-src/src/sha1.h +42 -0
- package/native-src/src/socket.cpp +1376 -0
- package/native-src/src/socket.h +309 -0
- package/native-src/src/stun.cpp +484 -0
- package/native-src/src/stun.h +349 -0
- package/native-src/src/threadmanager.cpp +105 -0
- package/native-src/src/threadmanager.h +53 -0
- package/native-src/src/tracker.cpp +1110 -0
- package/native-src/src/tracker.h +268 -0
- package/native-src/src/version.cpp +24 -0
- package/native-src/src/version.h.in +45 -0
- package/native-src/version.rc.in +31 -0
- package/package.json +2 -8
- package/scripts/build-librats.js +59 -12
- package/scripts/prepare-package.js +133 -37
|
@@ -0,0 +1,2715 @@
|
|
|
1
|
+
#include "librats.h"
|
|
2
|
+
#include "gossipsub.h"
|
|
3
|
+
#include "sha1.h"
|
|
4
|
+
#include "os.h"
|
|
5
|
+
#include "network_utils.h"
|
|
6
|
+
#include "fs.h"
|
|
7
|
+
#include "json.hpp" // nlohmann::json
|
|
8
|
+
#include "version.h"
|
|
9
|
+
#include <iostream>
|
|
10
|
+
#include <algorithm>
|
|
11
|
+
#include <chrono>
|
|
12
|
+
#include <memory>
|
|
13
|
+
#include <random>
|
|
14
|
+
#include <sstream>
|
|
15
|
+
#include <iomanip>
|
|
16
|
+
#include <stdexcept>
|
|
17
|
+
|
|
18
|
+
#ifdef TESTING
|
|
19
|
+
#define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", "[pointer: " << this << "] " << message)
|
|
20
|
+
#define LOG_CLIENT_INFO(message) LOG_INFO("client", "[pointer: " << this << "] " << message)
|
|
21
|
+
#define LOG_CLIENT_WARN(message) LOG_WARN("client", "[pointer: " << this << "] " << message)
|
|
22
|
+
#define LOG_CLIENT_ERROR(message) LOG_ERROR("client", "[pointer: " << this << "] " << message)
|
|
23
|
+
|
|
24
|
+
#define LOG_SERVER_DEBUG(message) LOG_DEBUG("server", "[pointer: " << this << "] " << message)
|
|
25
|
+
#define LOG_SERVER_INFO(message) LOG_INFO("server", "[pointer: " << this << "] " << message)
|
|
26
|
+
#define LOG_SERVER_WARN(message) LOG_WARN("server", "[pointer: " << this << "] " << message)
|
|
27
|
+
#define LOG_SERVER_ERROR(message) LOG_ERROR("server", "[pointer: " << this << "] " << message)
|
|
28
|
+
#else
|
|
29
|
+
#define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", message)
|
|
30
|
+
#define LOG_CLIENT_INFO(message) LOG_INFO("client", message)
|
|
31
|
+
#define LOG_CLIENT_WARN(message) LOG_WARN("client", message)
|
|
32
|
+
#define LOG_CLIENT_ERROR(message) LOG_ERROR("client", message)
|
|
33
|
+
|
|
34
|
+
#define LOG_SERVER_DEBUG(message) LOG_DEBUG("server", message)
|
|
35
|
+
#define LOG_SERVER_INFO(message) LOG_INFO("server", message)
|
|
36
|
+
#define LOG_SERVER_WARN(message) LOG_WARN("server", message)
|
|
37
|
+
#define LOG_SERVER_ERROR(message) LOG_ERROR("server", message)
|
|
38
|
+
#endif
|
|
39
|
+
|
|
40
|
+
namespace librats {
|
|
41
|
+
|
|
42
|
+
// Configuration file constants
|
|
43
|
+
const std::string RatsClient::CONFIG_FILE_NAME = "config.json";
|
|
44
|
+
const std::string RatsClient::PEERS_FILE_NAME = "peers.rats";
|
|
45
|
+
const std::string RatsClient::PEERS_EVER_FILE_NAME = "peers_ever.rats";
|
|
46
|
+
|
|
47
|
+
// =========================================================================
|
|
48
|
+
// Constructor and Destructor
|
|
49
|
+
// =========================================================================
|
|
50
|
+
|
|
51
|
+
RatsClient::RatsClient(int listen_port, int max_peers, const NatTraversalConfig& nat_config, const std::string& bind_address)
|
|
52
|
+
: listen_port_(listen_port),
|
|
53
|
+
bind_address_(bind_address),
|
|
54
|
+
max_peers_(max_peers),
|
|
55
|
+
nat_config_(nat_config),
|
|
56
|
+
server_socket_(INVALID_SOCKET_VALUE),
|
|
57
|
+
running_(false),
|
|
58
|
+
auto_discovery_running_(false),
|
|
59
|
+
encryption_enabled_(false),
|
|
60
|
+
detected_nat_type_(NatType::UNKNOWN),
|
|
61
|
+
data_directory_("."),
|
|
62
|
+
custom_protocol_name_("rats"),
|
|
63
|
+
custom_protocol_version_("1.0") {
|
|
64
|
+
// Initialize STUN client
|
|
65
|
+
stun_client_ = std::make_unique<StunClient>();
|
|
66
|
+
|
|
67
|
+
// Initialize NAT detector for advanced NAT characteristics detection
|
|
68
|
+
nat_detector_ = std::make_unique<AdvancedNatDetector>();
|
|
69
|
+
|
|
70
|
+
// Initialize ICE agent if enabled
|
|
71
|
+
initialize_ice_agent();
|
|
72
|
+
|
|
73
|
+
// Generate encryption key
|
|
74
|
+
static_encryption_key_ = encrypted_communication::generate_node_key();
|
|
75
|
+
|
|
76
|
+
// Load configuration (this will generate peer ID if needed)
|
|
77
|
+
load_configuration();
|
|
78
|
+
|
|
79
|
+
// Initialize modules
|
|
80
|
+
initialize_modules();
|
|
81
|
+
|
|
82
|
+
// Initialize NAT traversal
|
|
83
|
+
initialize_nat_traversal();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
RatsClient::~RatsClient() {
|
|
87
|
+
stop();
|
|
88
|
+
// Destroy modules
|
|
89
|
+
destroy_modules();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// =========================================================================
|
|
93
|
+
// Modules Initialization and Destruction
|
|
94
|
+
// =========================================================================
|
|
95
|
+
|
|
96
|
+
void RatsClient::initialize_modules() {
|
|
97
|
+
// Initialize GossipSub
|
|
98
|
+
if (!gossipsub_) {
|
|
99
|
+
LOG_CLIENT_INFO("Initializing GossipSub");
|
|
100
|
+
gossipsub_ = std::make_unique<GossipSub>(*this);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Initialize File Transfer Manager
|
|
104
|
+
if (!file_transfer_manager_) {
|
|
105
|
+
LOG_CLIENT_INFO("Initializing File Transfer Manager");
|
|
106
|
+
file_transfer_manager_ = std::make_unique<FileTransferManager>(*this);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void RatsClient::destroy_modules() {
|
|
111
|
+
if (gossipsub_) {
|
|
112
|
+
LOG_CLIENT_INFO("Destroying GossipSub");
|
|
113
|
+
gossipsub_.reset();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (file_transfer_manager_) {
|
|
117
|
+
LOG_CLIENT_INFO("Destroying File Transfer Manager");
|
|
118
|
+
file_transfer_manager_.reset();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// =========================================================================
|
|
123
|
+
// Core Lifecycle Management
|
|
124
|
+
// =========================================================================
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
bool RatsClient::start() {
|
|
128
|
+
if (running_.load()) {
|
|
129
|
+
LOG_CLIENT_WARN("RatsClient is already running");
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
LOG_CLIENT_INFO("Starting RatsClient on port " << listen_port_ <<
|
|
134
|
+
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
135
|
+
|
|
136
|
+
// Print system information for debugging and log analysis
|
|
137
|
+
SystemInfo sys_info = get_system_info();
|
|
138
|
+
LOG_CLIENT_INFO("=== System Information ===");
|
|
139
|
+
LOG_CLIENT_INFO("OS: " << sys_info.os_name << " " << sys_info.os_version);
|
|
140
|
+
LOG_CLIENT_INFO("Architecture: " << sys_info.architecture);
|
|
141
|
+
LOG_CLIENT_INFO("Hostname: " << sys_info.hostname);
|
|
142
|
+
LOG_CLIENT_INFO("CPU: " << sys_info.cpu_model);
|
|
143
|
+
LOG_CLIENT_INFO("CPU Cores: " << sys_info.cpu_cores << " physical, " << sys_info.cpu_logical_cores << " logical");
|
|
144
|
+
LOG_CLIENT_INFO("Memory: " << sys_info.total_memory_mb << " MB total, " << sys_info.available_memory_mb << " MB available");
|
|
145
|
+
LOG_CLIENT_INFO("===========================");
|
|
146
|
+
|
|
147
|
+
// Initialize socket library first (required for all socket operations)
|
|
148
|
+
init_socket_library();
|
|
149
|
+
|
|
150
|
+
// Initialize encryption
|
|
151
|
+
if (!initialize_encryption(encryption_enabled_)) {
|
|
152
|
+
LOG_CLIENT_ERROR("Failed to initialize encryption");
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Initialize local interface addresses for connection blocking
|
|
157
|
+
initialize_local_addresses();
|
|
158
|
+
|
|
159
|
+
// Discover public IP address via STUN and add to ignore list
|
|
160
|
+
if (!discover_and_ignore_public_ip()) {
|
|
161
|
+
LOG_CLIENT_WARN("Failed to discover public IP via STUN - continuing without it");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Create dual-stack server socket (supports both IPv4 and IPv6)
|
|
165
|
+
server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
|
|
166
|
+
if (!is_valid_socket(server_socket_)) {
|
|
167
|
+
LOG_CLIENT_ERROR("Failed to create dual-stack server socket on port " << listen_port_ <<
|
|
168
|
+
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Update listen_port_ with actual bound port if ephemeral port was requested
|
|
173
|
+
if (listen_port_ == 0) {
|
|
174
|
+
listen_port_ = get_ephemeral_port(server_socket_);
|
|
175
|
+
if (listen_port_ == 0) {
|
|
176
|
+
LOG_CLIENT_WARN("Failed to get actual bound port - using port 0");
|
|
177
|
+
} else {
|
|
178
|
+
LOG_CLIENT_INFO("Server bound to ephemeral port " << listen_port_);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
running_.store(true);
|
|
183
|
+
|
|
184
|
+
// Start server thread
|
|
185
|
+
server_thread_ = std::thread(&RatsClient::server_loop, this);
|
|
186
|
+
|
|
187
|
+
// Start management thread
|
|
188
|
+
management_thread_ = std::thread(&RatsClient::management_loop, this);
|
|
189
|
+
|
|
190
|
+
// Start GossipSub
|
|
191
|
+
if (gossipsub_ && !gossipsub_->start()) {
|
|
192
|
+
LOG_CLIENT_WARN("Failed to start GossipSub - continuing without it");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
LOG_CLIENT_INFO("RatsClient started successfully on port " << listen_port_);
|
|
196
|
+
|
|
197
|
+
// Attempt to reconnect to saved peers
|
|
198
|
+
add_managed_thread(std::thread([this]() {
|
|
199
|
+
// Give the server some time to fully initialize
|
|
200
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
201
|
+
int reconnect_attempts = load_and_reconnect_peers();
|
|
202
|
+
if (reconnect_attempts > 0) {
|
|
203
|
+
LOG_CLIENT_INFO("Attempted to reconnect to " << reconnect_attempts << " saved peers");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Also attempt to reconnect to historical peers if not at peer limit
|
|
207
|
+
if (!is_peer_limit_reached()) {
|
|
208
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Give current peers time to connect
|
|
209
|
+
int historical_attempts = load_and_reconnect_historical_peers();
|
|
210
|
+
if (historical_attempts > 0) {
|
|
211
|
+
LOG_CLIENT_INFO("Attempted to reconnect to " << historical_attempts << " historical peers");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}), "peer-reconnection");
|
|
215
|
+
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
void RatsClient::stop() {
|
|
220
|
+
if (!running_.load()) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
LOG_CLIENT_INFO("Stopping RatsClient");
|
|
225
|
+
|
|
226
|
+
// Stop GossipSub (can broadcast stop message)
|
|
227
|
+
if (gossipsub_) {
|
|
228
|
+
gossipsub_->stop();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
// Trigger immediate shutdown of all background threads
|
|
233
|
+
shutdown_all_threads();
|
|
234
|
+
|
|
235
|
+
// Stop DHT discovery (this will also stop automatic discovery)
|
|
236
|
+
stop_dht_discovery();
|
|
237
|
+
|
|
238
|
+
// Stop mDNS discovery
|
|
239
|
+
stop_mdns_discovery();
|
|
240
|
+
|
|
241
|
+
// Cleanup ICE resources
|
|
242
|
+
cleanup_ice_resources();
|
|
243
|
+
|
|
244
|
+
// Close server socket to break accept loop
|
|
245
|
+
if (is_valid_socket(server_socket_)) {
|
|
246
|
+
close_socket(server_socket_, true);
|
|
247
|
+
server_socket_ = INVALID_SOCKET_VALUE;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Close all peer connections
|
|
251
|
+
{
|
|
252
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
253
|
+
LOG_CLIENT_INFO("Closing " << peers_.size() << " peer connections");
|
|
254
|
+
for (const auto& pair : peers_) {
|
|
255
|
+
const RatsPeer& peer = pair.second;
|
|
256
|
+
close_socket(peer.socket, true);
|
|
257
|
+
}
|
|
258
|
+
peers_.clear();
|
|
259
|
+
socket_to_peer_id_.clear();
|
|
260
|
+
address_to_peer_id_.clear();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
// Wait for server thread to finish
|
|
266
|
+
if (server_thread_.joinable()) {
|
|
267
|
+
LOG_CLIENT_DEBUG("Waiting for server thread to finish");
|
|
268
|
+
server_thread_.join();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Wait for management thread to finish
|
|
272
|
+
if (management_thread_.joinable()) {
|
|
273
|
+
LOG_CLIENT_DEBUG("Waiting for management thread to finish");
|
|
274
|
+
management_thread_.join();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Join all managed threads for graceful cleanup
|
|
278
|
+
join_all_active_threads();
|
|
279
|
+
|
|
280
|
+
cleanup_socket_library();
|
|
281
|
+
|
|
282
|
+
// Save configuration before stopping
|
|
283
|
+
save_configuration();
|
|
284
|
+
|
|
285
|
+
LOG_CLIENT_INFO("RatsClient stopped successfully");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
void RatsClient::shutdown_all_threads() {
|
|
289
|
+
LOG_CLIENT_INFO("Initiating shutdown of all background threads");
|
|
290
|
+
|
|
291
|
+
// Signal all threads to stop
|
|
292
|
+
running_.store(false);
|
|
293
|
+
|
|
294
|
+
// Call parent class to handle thread management shutdown
|
|
295
|
+
ThreadManager::shutdown_all_threads();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
bool RatsClient::is_running() const {
|
|
299
|
+
return running_.load();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// =========================================================================
|
|
303
|
+
// Utility Methods
|
|
304
|
+
// =========================================================================
|
|
305
|
+
|
|
306
|
+
int RatsClient::get_listen_port() const {
|
|
307
|
+
return listen_port_;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
std::string RatsClient::get_bind_address() const {
|
|
311
|
+
return bind_address_;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// =========================================================================
|
|
315
|
+
// Managment loops
|
|
316
|
+
// =========================================================================
|
|
317
|
+
|
|
318
|
+
void RatsClient::server_loop() {
|
|
319
|
+
LOG_SERVER_INFO("Server loop started");
|
|
320
|
+
|
|
321
|
+
while (running_.load()) {
|
|
322
|
+
socket_t client_socket = accept_client(server_socket_);
|
|
323
|
+
if (!is_valid_socket(client_socket)) {
|
|
324
|
+
if (running_.load()) {
|
|
325
|
+
LOG_SERVER_ERROR("Failed to accept client connection");
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Get peer address information
|
|
331
|
+
std::string peer_address = get_peer_address(client_socket);
|
|
332
|
+
if (peer_address.empty()) {
|
|
333
|
+
LOG_SERVER_ERROR("Failed to get peer address for incoming connection");
|
|
334
|
+
close_socket(client_socket);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Parse IP and port from peer_address
|
|
339
|
+
std::string ip;
|
|
340
|
+
int port = 0;
|
|
341
|
+
if (!parse_address_string(peer_address, ip, port)) {
|
|
342
|
+
LOG_SERVER_ERROR("Failed to parse peer address from incoming connection: " << peer_address);
|
|
343
|
+
close_socket(client_socket);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
std::string normalized_peer_address = normalize_peer_address(ip, port);
|
|
348
|
+
|
|
349
|
+
// Check if peer limit is reached
|
|
350
|
+
if (is_peer_limit_reached()) {
|
|
351
|
+
LOG_SERVER_INFO("Peer limit reached (" << max_peers_ << "), rejecting connection from " << normalized_peer_address);
|
|
352
|
+
close_socket(client_socket);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check if we're already connected to this peer
|
|
357
|
+
if (is_already_connected_to_address(normalized_peer_address)) {
|
|
358
|
+
LOG_SERVER_INFO("Already connected to peer " << normalized_peer_address << ", rejecting duplicate connection");
|
|
359
|
+
close_socket(client_socket);
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Initialize encryption for incoming connection
|
|
364
|
+
if (is_encryption_enabled()) {
|
|
365
|
+
if (!encrypted_communication::initialize_incoming_connection(client_socket)) {
|
|
366
|
+
LOG_SERVER_ERROR("Failed to initialize encryption for incoming connection from " << normalized_peer_address);
|
|
367
|
+
close_socket(client_socket);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Generate unique hash ID for this incoming client
|
|
373
|
+
std::string connection_info = "incoming_from_" + peer_address;
|
|
374
|
+
std::string peer_hash_id = generate_peer_hash_id(client_socket, connection_info); // Temporary hash ID (real hash ID will be set after handshake)
|
|
375
|
+
|
|
376
|
+
// Create RatsPeer object for incoming connection
|
|
377
|
+
{
|
|
378
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
379
|
+
RatsPeer new_peer(peer_hash_id, ip, port, client_socket, normalized_peer_address, false); // false = incoming connection
|
|
380
|
+
new_peer.encryption_enabled = is_encryption_enabled();
|
|
381
|
+
add_peer_unlocked(new_peer);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Start a thread to handle this client
|
|
385
|
+
LOG_SERVER_DEBUG("Starting thread for client " << peer_hash_id << " from " << peer_address);
|
|
386
|
+
add_managed_thread(std::thread(&RatsClient::handle_client, this, client_socket, peer_hash_id),
|
|
387
|
+
"client-handler-" + peer_hash_id.substr(0, 8));
|
|
388
|
+
|
|
389
|
+
// Note: Connection callback will be called after handshake completion in handle_client
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
LOG_SERVER_INFO("Server loop ended");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
void RatsClient::management_loop() {
|
|
396
|
+
LOG_CLIENT_INFO("Management loop started");
|
|
397
|
+
|
|
398
|
+
while (running_.load()) {
|
|
399
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
400
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(30), [this] { return !running_.load(); })) {
|
|
401
|
+
break; // Exit if shutdown requested
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Periodically cleanup finished threads
|
|
405
|
+
try {
|
|
406
|
+
cleanup_finished_threads();
|
|
407
|
+
LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
|
|
408
|
+
} catch (const std::exception& e) {
|
|
409
|
+
LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
LOG_CLIENT_INFO("Management loop ended");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
void RatsClient::handle_client(socket_t client_socket, const std::string& peer_hash_id) {
|
|
418
|
+
LOG_CLIENT_INFO("Started handling client: " << peer_hash_id);
|
|
419
|
+
|
|
420
|
+
bool handshake_completed = false;
|
|
421
|
+
bool noise_handshake_completed = false;
|
|
422
|
+
auto last_timeout_check = std::chrono::steady_clock::now();
|
|
423
|
+
|
|
424
|
+
// Check if encryption is enabled for this connection
|
|
425
|
+
bool encryption_enabled = is_encryption_enabled();
|
|
426
|
+
|
|
427
|
+
while (running_.load()) {
|
|
428
|
+
std::string data;
|
|
429
|
+
|
|
430
|
+
// Handle encryption handshake first if enabled
|
|
431
|
+
LOG_CLIENT_DEBUG("Checking handshake state: encryption_enabled=" << encryption_enabled << ", noise_handshake_completed=" << noise_handshake_completed);
|
|
432
|
+
if (encryption_enabled && !noise_handshake_completed) {
|
|
433
|
+
LOG_CLIENT_DEBUG("Entering encryption handshake handling for socket " << client_socket);
|
|
434
|
+
// Check if noise handshake is completed
|
|
435
|
+
if (encrypted_communication::is_handshake_completed(client_socket)) {
|
|
436
|
+
noise_handshake_completed = true;
|
|
437
|
+
LOG_CLIENT_INFO("Noise handshake completed for peer " << peer_hash_id);
|
|
438
|
+
// Update peer state
|
|
439
|
+
{
|
|
440
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
441
|
+
auto it = socket_to_peer_id_.find(client_socket);
|
|
442
|
+
if (it != socket_to_peer_id_.end()) {
|
|
443
|
+
auto peer_it = peers_.find(it->second);
|
|
444
|
+
if (peer_it != peers_.end()) {
|
|
445
|
+
peer_it->second.noise_handshake_completed = true;
|
|
446
|
+
// For outgoing connections, send application handshake after noise handshake completes
|
|
447
|
+
if (peer_it->second.is_outgoing) {
|
|
448
|
+
if (!send_handshake_unlocked(client_socket, get_our_peer_id())) {
|
|
449
|
+
LOG_CLIENT_ERROR("Failed to send application handshake after noise completion for peer " << peer_hash_id);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
// For responders and ongoing handshake processing, try to continue the handshake
|
|
458
|
+
LOG_CLIENT_DEBUG("Attempting to process handshake for socket " << client_socket);
|
|
459
|
+
if (!encrypted_communication::perform_handshake(client_socket)) {
|
|
460
|
+
LOG_CLIENT_ERROR("Encryption handshake failed for peer " << peer_hash_id);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
// Continue to check if handshake completed
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Receive data (encrypted or unencrypted based on settings)
|
|
469
|
+
LOG_CLIENT_DEBUG("Receiving data: encryption_enabled=" << encryption_enabled << ", noise_handshake_completed=" << noise_handshake_completed);
|
|
470
|
+
if (encryption_enabled && noise_handshake_completed) {
|
|
471
|
+
LOG_CLIENT_DEBUG("Receiving encrypted data from socket " << client_socket);
|
|
472
|
+
auto binary_data = encrypted_communication::receive_tcp_data_encrypted(client_socket);
|
|
473
|
+
data = std::string(binary_data.begin(), binary_data.end());
|
|
474
|
+
} else if (encryption_enabled && !noise_handshake_completed) {
|
|
475
|
+
LOG_CLIENT_DEBUG("Processing handshake for socket " << client_socket);
|
|
476
|
+
// During handshake, let the encrypted socket handle data reception and processing
|
|
477
|
+
// The perform_handshake function will internally call receive_tcp_data and process handshake messages
|
|
478
|
+
if (!encrypted_communication::perform_handshake(client_socket)) {
|
|
479
|
+
LOG_CLIENT_ERROR("Encryption handshake failed for peer " << peer_hash_id);
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
// Continue to check if handshake completed
|
|
483
|
+
continue;
|
|
484
|
+
} else {
|
|
485
|
+
LOG_CLIENT_DEBUG("Receiving unencrypted data from socket " << client_socket);
|
|
486
|
+
// Always use framed message reception for reliable large message handling
|
|
487
|
+
data = receive_tcp_string_framed(client_socket);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (data.empty()) {
|
|
491
|
+
// Check if this is a timeout or actual connection close
|
|
492
|
+
if (!handshake_completed) {
|
|
493
|
+
// Check for handshake timeout one more time before giving up
|
|
494
|
+
auto now = std::chrono::steady_clock::now();
|
|
495
|
+
if (now - last_timeout_check >= std::chrono::seconds(1)) {
|
|
496
|
+
check_handshake_timeouts();
|
|
497
|
+
last_timeout_check = now;
|
|
498
|
+
|
|
499
|
+
// Check if handshake has failed due to timeout
|
|
500
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
501
|
+
auto it = socket_to_peer_id_.find(client_socket);
|
|
502
|
+
if (it != socket_to_peer_id_.end()) {
|
|
503
|
+
auto peer_it = peers_.find(it->second);
|
|
504
|
+
if (peer_it != peers_.end() && peer_it->second.is_handshake_failed()) {
|
|
505
|
+
LOG_CLIENT_ERROR("Handshake failed for peer " << peer_hash_id);
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
break; // Connection closed or error
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
LOG_CLIENT_DEBUG("Received data from " << peer_hash_id << ": " << data.substr(0, 50) << (data.length() > 50 ? "..." : ""));
|
|
515
|
+
|
|
516
|
+
// Check for handshake timeout and failure
|
|
517
|
+
if (!handshake_completed) {
|
|
518
|
+
bool should_exit = false;
|
|
519
|
+
|
|
520
|
+
{
|
|
521
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
522
|
+
auto it = socket_to_peer_id_.find(client_socket);
|
|
523
|
+
if (it != socket_to_peer_id_.end()) {
|
|
524
|
+
auto peer_it = peers_.find(it->second);
|
|
525
|
+
if (peer_it != peers_.end()) {
|
|
526
|
+
const RatsPeer& peer = peer_it->second;
|
|
527
|
+
if (peer.is_handshake_failed()) {
|
|
528
|
+
LOG_CLIENT_ERROR("Handshake failed for peer " << peer_hash_id);
|
|
529
|
+
should_exit = true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (should_exit) {
|
|
536
|
+
break; // Exit loop to disconnect
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Check for handshake timeout
|
|
540
|
+
auto now = std::chrono::steady_clock::now();
|
|
541
|
+
if (now - last_timeout_check >= std::chrono::seconds(1)) {
|
|
542
|
+
check_handshake_timeouts();
|
|
543
|
+
last_timeout_check = now;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Handle handshake messages
|
|
548
|
+
if (is_handshake_message(data)) {
|
|
549
|
+
if (!handle_handshake_message(client_socket, peer_hash_id, data)) {
|
|
550
|
+
LOG_CLIENT_ERROR("Failed to handle handshake message from " << peer_hash_id);
|
|
551
|
+
break; // Exit loop to disconnect
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Check if handshake just completed and trigger notifications
|
|
555
|
+
if (!handshake_completed) {
|
|
556
|
+
RatsPeer peer_copy;
|
|
557
|
+
bool should_notify_connection = false;
|
|
558
|
+
bool should_broadcast_exchange = false;
|
|
559
|
+
|
|
560
|
+
{
|
|
561
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
562
|
+
auto it = socket_to_peer_id_.find(client_socket);
|
|
563
|
+
if (it != socket_to_peer_id_.end()) {
|
|
564
|
+
auto peer_it = peers_.find(it->second);
|
|
565
|
+
if (peer_it != peers_.end()) {
|
|
566
|
+
const RatsPeer& peer = peer_it->second;
|
|
567
|
+
if (peer.is_handshake_completed()) {
|
|
568
|
+
handshake_completed = true;
|
|
569
|
+
should_notify_connection = true;
|
|
570
|
+
should_broadcast_exchange = true;
|
|
571
|
+
peer_copy = peer; // Copy peer data
|
|
572
|
+
|
|
573
|
+
LOG_CLIENT_INFO("Handshake completed for peer " << peer_hash_id << " (peer_id: " << peer.peer_id << ")");
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Call callbacks and methods outside of mutex to avoid deadlock
|
|
580
|
+
if (should_notify_connection) {
|
|
581
|
+
if (connection_callback_) {
|
|
582
|
+
connection_callback_(client_socket, peer_copy.peer_id);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (gossipsub_) {
|
|
586
|
+
gossipsub_->handle_peer_connected(peer_copy.peer_id);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Broadcast peer exchange message to other peers
|
|
590
|
+
broadcast_peer_exchange_message(peer_copy);
|
|
591
|
+
|
|
592
|
+
// Send peers request to the newly connected peer to discover more peers
|
|
593
|
+
if (peer_copy.is_outgoing) {
|
|
594
|
+
send_peers_request(client_socket, peer_copy.peer_id);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Initiate NAT traversal information exchange
|
|
598
|
+
send_nat_info_to_peer(client_socket, peer_copy.peer_id);
|
|
599
|
+
|
|
600
|
+
// If ICE is enabled and this is an outgoing connection, initiate ICE coordination
|
|
601
|
+
if (ice_agent_ && ice_agent_->is_running() && peer_copy.is_outgoing) {
|
|
602
|
+
if (should_initiate_ice_coordination(peer_copy.peer_id)) {
|
|
603
|
+
mark_ice_coordination_in_progress(peer_copy.peer_id);
|
|
604
|
+
|
|
605
|
+
// Start ICE coordination in background thread with proper exception handling
|
|
606
|
+
add_managed_thread(std::thread([this, peer_id = peer_copy.peer_id, host = peer_copy.ip, port = peer_copy.port]() {
|
|
607
|
+
try {
|
|
608
|
+
// Use conditional variable for responsive shutdown
|
|
609
|
+
{
|
|
610
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
611
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !running_.load(); })) {
|
|
612
|
+
// Clean up tracking before exit
|
|
613
|
+
cleanup_ice_coordination_for_peer(peer_id);
|
|
614
|
+
return; // Exit if shutdown requested
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Initiate ICE coordination
|
|
619
|
+
initiate_ice_with_peer(peer_id, host, port);
|
|
620
|
+
|
|
621
|
+
} catch (const std::exception& e) {
|
|
622
|
+
LOG_CLIENT_ERROR("Exception in ICE coordination thread for peer " << peer_id << ": " << e.what());
|
|
623
|
+
} catch (...) {
|
|
624
|
+
LOG_CLIENT_ERROR("Unknown exception in ICE coordination thread for peer " << peer_id);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Clean up tracking when done
|
|
628
|
+
cleanup_ice_coordination_for_peer(peer_id);
|
|
629
|
+
}), "ice-coordination-" + peer_copy.peer_id.substr(0, 8));
|
|
630
|
+
} else {
|
|
631
|
+
LOG_CLIENT_DEBUG("ICE coordination already in progress for peer " << peer_copy.peer_id << " - skipping duplicate attempt");
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Save configuration after a new peer connects to keep peer list current
|
|
636
|
+
if (running_.load()) {
|
|
637
|
+
add_managed_thread(std::thread([this]() {
|
|
638
|
+
if (running_.load()) {
|
|
639
|
+
save_configuration();
|
|
640
|
+
}
|
|
641
|
+
}), "config-save");
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
continue; // Don't process handshake messages as regular data
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Only process regular data after handshake is completed (and noise handshake if encryption is enabled)
|
|
650
|
+
if (handshake_completed && (!encryption_enabled || noise_handshake_completed)) {
|
|
651
|
+
// Convert string data to binary for header parsing
|
|
652
|
+
std::vector<uint8_t> received_data(data.begin(), data.end());
|
|
653
|
+
|
|
654
|
+
// Try to parse message header first
|
|
655
|
+
MessageHeader header;
|
|
656
|
+
std::vector<uint8_t> payload;
|
|
657
|
+
if (parse_message_with_header(received_data, header, payload)) {
|
|
658
|
+
// Message has valid header - call appropriate callback based on type
|
|
659
|
+
std::string peer_id = get_peer_id(client_socket);
|
|
660
|
+
|
|
661
|
+
switch (header.type) {
|
|
662
|
+
case MessageDataType::BINARY: {
|
|
663
|
+
LOG_CLIENT_DEBUG("Received BINARY message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
664
|
+
// Try to handle as file transfer data first
|
|
665
|
+
bool handled = false;
|
|
666
|
+
if (file_transfer_manager_) {
|
|
667
|
+
handled = file_transfer_manager_->handle_binary_data(peer_id, payload);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// If not a file transfer chunk, call user's binary callback
|
|
671
|
+
if (!handled && binary_data_callback_) {
|
|
672
|
+
binary_data_callback_(client_socket, peer_id, payload);
|
|
673
|
+
}
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
case MessageDataType::STRING:
|
|
678
|
+
LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
679
|
+
if (string_data_callback_) {
|
|
680
|
+
std::string string_data(payload.begin(), payload.end());
|
|
681
|
+
string_data_callback_(client_socket, peer_id, string_data);
|
|
682
|
+
}
|
|
683
|
+
break;
|
|
684
|
+
|
|
685
|
+
case MessageDataType::JSON: {
|
|
686
|
+
LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
687
|
+
// Parse JSON payload
|
|
688
|
+
std::string json_string(payload.begin(), payload.end());
|
|
689
|
+
nlohmann::json json_msg;
|
|
690
|
+
if (parse_json_message(json_string, json_msg)) {
|
|
691
|
+
// Check if it's a rats protocol message
|
|
692
|
+
if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
|
|
693
|
+
handle_rats_message(client_socket, peer_id, json_msg);
|
|
694
|
+
} else {
|
|
695
|
+
// Regular JSON data - call JSON callback
|
|
696
|
+
if (json_data_callback_) {
|
|
697
|
+
json_data_callback_(client_socket, peer_id, json_msg);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id);
|
|
702
|
+
}
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
default:
|
|
707
|
+
LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
// No header found
|
|
712
|
+
LOG_CLIENT_WARN("No header found in message from " << peer_hash_id);
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
LOG_CLIENT_WARN("Received non-handshake data from " << peer_hash_id << " before handshake completion - ignoring");
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Get current peer ID before cleanup for disconnect callback
|
|
720
|
+
std::string current_peer_id = get_peer_id(client_socket);
|
|
721
|
+
|
|
722
|
+
// Clean up
|
|
723
|
+
remove_peer(client_socket);
|
|
724
|
+
|
|
725
|
+
// Clean up encryption state
|
|
726
|
+
if (encryption_enabled) {
|
|
727
|
+
encrypted_communication::cleanup_socket(client_socket);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
close_socket(client_socket);
|
|
731
|
+
|
|
732
|
+
// Notify disconnect callback only if handshake was completed
|
|
733
|
+
if (handshake_completed && disconnect_callback_) {
|
|
734
|
+
disconnect_callback_(client_socket, current_peer_id);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (handshake_completed && gossipsub_) {
|
|
738
|
+
gossipsub_->handle_peer_disconnected(current_peer_id);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Save configuration after a validated peer disconnects to update the saved peer list
|
|
742
|
+
if (handshake_completed && running_.load()) {
|
|
743
|
+
// Save configuration in a separate thread to avoid blocking
|
|
744
|
+
add_managed_thread(std::thread([this]() {
|
|
745
|
+
if (running_.load()) {
|
|
746
|
+
save_configuration();
|
|
747
|
+
}
|
|
748
|
+
}), "config-save-disconnect");
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
LOG_CLIENT_INFO("Client disconnected: " << peer_hash_id);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Handshake protocol implementation
|
|
755
|
+
std::string RatsClient::create_handshake_message(const std::string& message_type, const std::string& our_peer_id) const {
|
|
756
|
+
auto now = std::chrono::high_resolution_clock::now();
|
|
757
|
+
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
|
758
|
+
|
|
759
|
+
// Use nlohmann::json for proper JSON serialization
|
|
760
|
+
nlohmann::json handshake_msg;
|
|
761
|
+
{
|
|
762
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
763
|
+
handshake_msg["protocol"] = custom_protocol_name_;
|
|
764
|
+
handshake_msg["version"] = custom_protocol_version_;
|
|
765
|
+
}
|
|
766
|
+
handshake_msg["peer_id"] = our_peer_id;
|
|
767
|
+
handshake_msg["message_type"] = message_type;
|
|
768
|
+
handshake_msg["timestamp"] = timestamp;
|
|
769
|
+
|
|
770
|
+
return handshake_msg.dump();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
bool RatsClient::parse_handshake_message(const std::string& message, HandshakeMessage& out_msg) const {
|
|
774
|
+
try {
|
|
775
|
+
// Use nlohmann::json for proper JSON parsing
|
|
776
|
+
nlohmann::json json_msg = nlohmann::json::parse(message);
|
|
777
|
+
|
|
778
|
+
// Clear the output structure
|
|
779
|
+
out_msg = HandshakeMessage{};
|
|
780
|
+
|
|
781
|
+
// Extract fields using nlohmann::json
|
|
782
|
+
out_msg.protocol = json_msg.value("protocol", "");
|
|
783
|
+
out_msg.version = json_msg.value("version", "");
|
|
784
|
+
out_msg.peer_id = json_msg.value("peer_id", "");
|
|
785
|
+
out_msg.message_type = json_msg.value("message_type", "");
|
|
786
|
+
// Tolerate missing timestamp to avoid hard dependency on remote system clock
|
|
787
|
+
out_msg.timestamp = json_msg.value("timestamp", static_cast<int64_t>(0));
|
|
788
|
+
|
|
789
|
+
return true;
|
|
790
|
+
|
|
791
|
+
} catch (const nlohmann::json::exception& e) {
|
|
792
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message: " << e.what());
|
|
793
|
+
return false;
|
|
794
|
+
} catch (const std::exception& e) {
|
|
795
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message: " << e.what());
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
|
|
801
|
+
std::string expected_protocol;
|
|
802
|
+
std::string expected_version;
|
|
803
|
+
{
|
|
804
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
805
|
+
expected_protocol = custom_protocol_name_;
|
|
806
|
+
expected_version = custom_protocol_version_;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Validate protocol
|
|
810
|
+
if (msg.protocol != expected_protocol) {
|
|
811
|
+
LOG_CLIENT_WARN("Invalid handshake protocol: " << msg.protocol << " (expected: " << expected_protocol << ")");
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Validate version (for now, only accept exact version match)
|
|
816
|
+
if (msg.version != expected_version) {
|
|
817
|
+
LOG_CLIENT_WARN("Unsupported protocol version: " << msg.version << " (expected: " << expected_version << ")");
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Validate message type
|
|
822
|
+
if (msg.message_type != "handshake") {
|
|
823
|
+
LOG_CLIENT_WARN("Invalid handshake message type: " << msg.message_type);
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Validate peer_id (must not be empty)
|
|
828
|
+
if (msg.peer_id.empty()) {
|
|
829
|
+
LOG_CLIENT_WARN("Empty peer_id in handshake message");
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Soft-validate timestamp to avoid rejecting valid peers due to clock skew
|
|
834
|
+
if (msg.timestamp == 0) {
|
|
835
|
+
LOG_CLIENT_WARN("Handshake missing timestamp; accepting to avoid clock-skew rejection");
|
|
836
|
+
} else {
|
|
837
|
+
auto now = std::chrono::high_resolution_clock::now();
|
|
838
|
+
auto current_timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
|
839
|
+
int64_t time_diff = std::abs(current_timestamp - msg.timestamp);
|
|
840
|
+
const int64_t allowed_skew_ms = 10LL * 60LL * 1000LL; // 10 minutes tolerance
|
|
841
|
+
if (time_diff > allowed_skew_ms) {
|
|
842
|
+
LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << allowed_skew_ms << "ms; accepting to be tolerant of clock skew");
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
bool RatsClient::is_handshake_message(const std::string& message) const {
|
|
850
|
+
try {
|
|
851
|
+
std::string json_to_parse = message;
|
|
852
|
+
|
|
853
|
+
// Check if message has our message header (starts with "RATS" magic)
|
|
854
|
+
std::vector<uint8_t> message_data(message.begin(), message.end());
|
|
855
|
+
MessageHeader header;
|
|
856
|
+
std::vector<uint8_t> payload;
|
|
857
|
+
|
|
858
|
+
if (parse_message_with_header(message_data, header, payload)) {
|
|
859
|
+
// Message has valid header - extract the JSON payload
|
|
860
|
+
if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
|
|
861
|
+
json_to_parse = std::string(payload.begin(), payload.end());
|
|
862
|
+
} else {
|
|
863
|
+
// Handshake messages should be string/JSON type
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
} else {
|
|
867
|
+
// Message has no header
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Parse the JSON message
|
|
872
|
+
nlohmann::json json_msg = nlohmann::json::parse(json_to_parse);
|
|
873
|
+
std::string expected_protocol;
|
|
874
|
+
{
|
|
875
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
876
|
+
expected_protocol = custom_protocol_name_;
|
|
877
|
+
}
|
|
878
|
+
return json_msg.value("protocol", "") == expected_protocol &&
|
|
879
|
+
json_msg.value("message_type", "") == "handshake";
|
|
880
|
+
} catch (const std::exception&) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Add this private helper function before send_handshake
|
|
886
|
+
bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our_peer_id) {
|
|
887
|
+
std::string handshake_msg = create_handshake_message("handshake", our_peer_id);
|
|
888
|
+
LOG_CLIENT_DEBUG("Sending handshake to socket " << socket << ": " << handshake_msg);
|
|
889
|
+
|
|
890
|
+
if (!send_string_to_peer(socket, handshake_msg)) {
|
|
891
|
+
LOG_CLIENT_ERROR("Failed to send handshake to socket " << socket);
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Update peer state (assumes peers_mutex_ is already locked)
|
|
896
|
+
auto it = socket_to_peer_id_.find(socket);
|
|
897
|
+
if (it != socket_to_peer_id_.end()) {
|
|
898
|
+
auto peer_it = peers_.find(it->second);
|
|
899
|
+
if (peer_it != peers_.end()) {
|
|
900
|
+
peer_it->second.handshake_state = RatsPeer::HandshakeState::SENT;
|
|
901
|
+
peer_it->second.handshake_start_time = std::chrono::steady_clock::now();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return true;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
bool RatsClient::send_handshake(socket_t socket, const std::string& our_peer_id) {
|
|
909
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
910
|
+
return send_handshake_unlocked(socket, our_peer_id);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::string& message) {
|
|
914
|
+
// Extract JSON payload from message header if present
|
|
915
|
+
std::string json_to_parse = message;
|
|
916
|
+
std::vector<uint8_t> message_data(message.begin(), message.end());
|
|
917
|
+
MessageHeader header;
|
|
918
|
+
std::vector<uint8_t> payload;
|
|
919
|
+
|
|
920
|
+
if (parse_message_with_header(message_data, header, payload)) {
|
|
921
|
+
// Message has valid header - extract the JSON payload
|
|
922
|
+
if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
|
|
923
|
+
json_to_parse = std::string(payload.begin(), payload.end());
|
|
924
|
+
} else {
|
|
925
|
+
LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
} else {
|
|
929
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message header from " << peer_hash_id);
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
HandshakeMessage handshake_msg;
|
|
934
|
+
if (!parse_handshake_message(json_to_parse, handshake_msg)) {
|
|
935
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message from " << peer_hash_id);
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (!validate_handshake_message(handshake_msg)) {
|
|
940
|
+
LOG_CLIENT_ERROR("Invalid handshake message from " << peer_hash_id);
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
if (handshake_msg.peer_id == get_our_peer_id()) {
|
|
945
|
+
LOG_CLIENT_INFO("Received handshake from ourselves, ignoring");
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
LOG_CLIENT_INFO("Received valid handshake from " << peer_hash_id
|
|
950
|
+
<< " (peer_id: " << handshake_msg.peer_id << ")");
|
|
951
|
+
|
|
952
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
953
|
+
auto it = socket_to_peer_id_.find(socket);
|
|
954
|
+
if (it == socket_to_peer_id_.end()) {
|
|
955
|
+
LOG_CLIENT_ERROR("Socket " << socket << " not found in peer mapping");
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
auto peer_it = peers_.find(it->second);
|
|
960
|
+
if (peer_it == peers_.end()) {
|
|
961
|
+
LOG_CLIENT_ERROR("Peer " << peer_hash_id << " not found in peers");
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (peers_.find(handshake_msg.peer_id) != peers_.end()) {
|
|
966
|
+
LOG_CLIENT_INFO("Peer " << handshake_msg.peer_id << " already connected, closing duplicate connection");
|
|
967
|
+
// This is a duplicate connection - the existing connection should remain stable
|
|
968
|
+
// Return false to close this duplicate connection, but this is expected behavior
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Store old peer ID for mapping updates
|
|
973
|
+
std::string old_peer_id = peer_it->second.peer_id;
|
|
974
|
+
|
|
975
|
+
// Update peer mappings with new peer_id if it changed
|
|
976
|
+
if (old_peer_id != handshake_msg.peer_id) {
|
|
977
|
+
// Create a copy of the peer object before erasing it.
|
|
978
|
+
RatsPeer peer_copy = peer_it->second;
|
|
979
|
+
|
|
980
|
+
// Erase the old entry from the main peers map.
|
|
981
|
+
peers_.erase(peer_it);
|
|
982
|
+
|
|
983
|
+
// Update the peer_id within the copied object.
|
|
984
|
+
peer_copy.peer_id = handshake_msg.peer_id;
|
|
985
|
+
|
|
986
|
+
// Insert the updated peer object back into the maps with the new peer_id.
|
|
987
|
+
peers_[peer_copy.peer_id] = peer_copy;
|
|
988
|
+
socket_to_peer_id_[socket] = peer_copy.peer_id;
|
|
989
|
+
address_to_peer_id_[peer_copy.normalized_address] = peer_copy.peer_id;
|
|
990
|
+
|
|
991
|
+
// Find the iterator for the newly inserted peer.
|
|
992
|
+
peer_it = peers_.find(peer_copy.peer_id);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
RatsPeer& peer = peer_it->second;
|
|
996
|
+
|
|
997
|
+
// Store remote peer information
|
|
998
|
+
peer.version = handshake_msg.version;
|
|
999
|
+
|
|
1000
|
+
// Simplified handshake logic - just one message type
|
|
1001
|
+
if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
|
|
1002
|
+
// This is an incoming handshake - send our handshake back
|
|
1003
|
+
if (send_handshake_unlocked(socket, get_our_peer_id())) {
|
|
1004
|
+
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1005
|
+
log_handshake_completion_unlocked(peer);
|
|
1006
|
+
|
|
1007
|
+
// Append to historical peers file after successful connection
|
|
1008
|
+
append_peer_to_historical_file(peer);
|
|
1009
|
+
|
|
1010
|
+
return true;
|
|
1011
|
+
} else {
|
|
1012
|
+
peer.handshake_state = RatsPeer::HandshakeState::FAILED;
|
|
1013
|
+
LOG_CLIENT_ERROR("Failed to send handshake response to " << peer_hash_id);
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
} else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
|
|
1017
|
+
// This is a response to our handshake
|
|
1018
|
+
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1019
|
+
log_handshake_completion_unlocked(peer);
|
|
1020
|
+
|
|
1021
|
+
// Append to historical peers file after successful connection
|
|
1022
|
+
append_peer_to_historical_file(peer);
|
|
1023
|
+
|
|
1024
|
+
return true;
|
|
1025
|
+
} else {
|
|
1026
|
+
LOG_CLIENT_WARN("Received handshake from " << peer_hash_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
void RatsClient::check_handshake_timeouts() {
|
|
1032
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1033
|
+
auto now = std::chrono::steady_clock::now();
|
|
1034
|
+
|
|
1035
|
+
std::vector<std::string> peers_to_remove;
|
|
1036
|
+
|
|
1037
|
+
for (auto& pair : peers_) {
|
|
1038
|
+
RatsPeer& peer = pair.second;
|
|
1039
|
+
|
|
1040
|
+
if (peer.handshake_state != RatsPeer::HandshakeState::COMPLETED &&
|
|
1041
|
+
peer.handshake_state != RatsPeer::HandshakeState::FAILED) {
|
|
1042
|
+
|
|
1043
|
+
auto handshake_duration = std::chrono::duration_cast<std::chrono::seconds>(now - peer.handshake_start_time);
|
|
1044
|
+
|
|
1045
|
+
if (handshake_duration.count() > HANDSHAKE_TIMEOUT_SECONDS) {
|
|
1046
|
+
LOG_CLIENT_WARN("Handshake timeout for peer " << peer.peer_id << " after " << handshake_duration.count() << " seconds");
|
|
1047
|
+
peer.handshake_state = RatsPeer::HandshakeState::FAILED;
|
|
1048
|
+
peers_to_remove.push_back(peer.peer_id);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Remove timed out peers
|
|
1054
|
+
for (const auto& peer_id : peers_to_remove) {
|
|
1055
|
+
auto peer_it = peers_.find(peer_id);
|
|
1056
|
+
if (peer_it != peers_.end()) {
|
|
1057
|
+
socket_t socket = peer_it->second.socket;
|
|
1058
|
+
LOG_CLIENT_INFO("Disconnecting peer " << peer_id << " due to handshake timeout");
|
|
1059
|
+
|
|
1060
|
+
// Clean up peer data
|
|
1061
|
+
remove_peer_by_id_unlocked(peer_id);
|
|
1062
|
+
close_socket(socket);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// =========================================================================
|
|
1068
|
+
// Connection Management
|
|
1069
|
+
// =========================================================================
|
|
1070
|
+
|
|
1071
|
+
bool RatsClient::connect_to_peer(const std::string& host, int port, ConnectionStrategy strategy) {
|
|
1072
|
+
if (!running_.load()) {
|
|
1073
|
+
LOG_CLIENT_ERROR("RatsClient is not running");
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
LOG_CLIENT_INFO("Connecting to peer " << host << ":" << port << " using strategy: " << static_cast<int>(strategy));
|
|
1078
|
+
|
|
1079
|
+
// Create connection attempt result to track the attempt
|
|
1080
|
+
ConnectionAttemptResult result;
|
|
1081
|
+
result.local_nat_type = detect_nat_type();
|
|
1082
|
+
result.remote_nat_type = NatType::UNKNOWN;
|
|
1083
|
+
|
|
1084
|
+
auto start_time = std::chrono::high_resolution_clock::now();
|
|
1085
|
+
bool success = false;
|
|
1086
|
+
|
|
1087
|
+
// Execute connection strategy
|
|
1088
|
+
switch (strategy) {
|
|
1089
|
+
case ConnectionStrategy::DIRECT_ONLY:
|
|
1090
|
+
success = attempt_direct_connection(host, port, result);
|
|
1091
|
+
result.method = "direct";
|
|
1092
|
+
break;
|
|
1093
|
+
|
|
1094
|
+
case ConnectionStrategy::STUN_ASSISTED:
|
|
1095
|
+
success = attempt_stun_assisted_connection(host, port, result);
|
|
1096
|
+
result.method = "stun";
|
|
1097
|
+
break;
|
|
1098
|
+
|
|
1099
|
+
case ConnectionStrategy::ICE_FULL:
|
|
1100
|
+
success = attempt_ice_connection(host, port, result);
|
|
1101
|
+
result.method = "ice";
|
|
1102
|
+
break;
|
|
1103
|
+
|
|
1104
|
+
case ConnectionStrategy::TURN_RELAY:
|
|
1105
|
+
success = attempt_turn_relay_connection(host, port, result);
|
|
1106
|
+
result.method = "turn";
|
|
1107
|
+
break;
|
|
1108
|
+
|
|
1109
|
+
case ConnectionStrategy::AUTO_ADAPTIVE:
|
|
1110
|
+
// Select best strategy based on NAT type
|
|
1111
|
+
std::string best_strategy = select_best_connection_strategy(host, port);
|
|
1112
|
+
result.method = best_strategy;
|
|
1113
|
+
|
|
1114
|
+
if (best_strategy == "direct") {
|
|
1115
|
+
success = attempt_direct_connection(host, port, result);
|
|
1116
|
+
} else if (best_strategy == "stun") {
|
|
1117
|
+
success = attempt_stun_assisted_connection(host, port, result);
|
|
1118
|
+
} else if (best_strategy == "ice") {
|
|
1119
|
+
success = attempt_ice_connection(host, port, result);
|
|
1120
|
+
} else if (best_strategy == "turn") {
|
|
1121
|
+
success = attempt_turn_relay_connection(host, port, result);
|
|
1122
|
+
} else {
|
|
1123
|
+
success = attempt_hole_punch_connection(host, port, result);
|
|
1124
|
+
}
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
auto end_time = std::chrono::high_resolution_clock::now();
|
|
1129
|
+
result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
|
1130
|
+
result.success = success;
|
|
1131
|
+
|
|
1132
|
+
// Log connection attempt result
|
|
1133
|
+
LOG_CLIENT_INFO("Connection attempt to " << host << ":" << port << " via " << result.method
|
|
1134
|
+
<< " completed: " << (success ? "SUCCESS" : "FAILED")
|
|
1135
|
+
<< " in " << result.duration.count() << "ms");
|
|
1136
|
+
|
|
1137
|
+
if (!result.error_message.empty()) {
|
|
1138
|
+
LOG_CLIENT_DEBUG("Error details: " << result.error_message);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Update connection statistics
|
|
1142
|
+
std::string peer_address = normalize_peer_address(host, port);
|
|
1143
|
+
update_connection_statistics(peer_address, result);
|
|
1144
|
+
|
|
1145
|
+
// Call advanced connection callback if set
|
|
1146
|
+
if (success && advanced_connection_callback_) {
|
|
1147
|
+
// Find the socket for this connection to pass to callback
|
|
1148
|
+
socket_t connected_socket = INVALID_SOCKET_VALUE;
|
|
1149
|
+
std::string peer_id;
|
|
1150
|
+
|
|
1151
|
+
{
|
|
1152
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1153
|
+
auto addr_it = address_to_peer_id_.find(peer_address);
|
|
1154
|
+
if (addr_it != address_to_peer_id_.end()) {
|
|
1155
|
+
auto peer_it = peers_.find(addr_it->second);
|
|
1156
|
+
if (peer_it != peers_.end()) {
|
|
1157
|
+
connected_socket = peer_it->second.socket;
|
|
1158
|
+
peer_id = peer_it->second.peer_id;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (is_valid_socket(connected_socket)) {
|
|
1164
|
+
advanced_connection_callback_(connected_socket, peer_id, result);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
return success;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
void RatsClient::disconnect_peer(socket_t socket) {
|
|
1172
|
+
remove_peer(socket);
|
|
1173
|
+
|
|
1174
|
+
// Clean up encryption state
|
|
1175
|
+
if (is_encryption_enabled()) {
|
|
1176
|
+
encrypted_communication::cleanup_socket(socket);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
close_socket(socket);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
void RatsClient::disconnect_peer_by_id(const std::string& peer_id) {
|
|
1183
|
+
socket_t socket = get_peer_socket_by_id(peer_id);
|
|
1184
|
+
if (is_valid_socket(socket)) {
|
|
1185
|
+
disconnect_peer(socket);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Helper methods for peer management
|
|
1190
|
+
void RatsClient::add_peer(const RatsPeer& peer) {
|
|
1191
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1192
|
+
add_peer_unlocked(peer);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
|
|
1196
|
+
// Assumes peers_mutex_ is already locked
|
|
1197
|
+
peers_[peer.peer_id] = peer;
|
|
1198
|
+
socket_to_peer_id_[peer.socket] = peer.peer_id;
|
|
1199
|
+
address_to_peer_id_[peer.normalized_address] = peer.peer_id;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
void RatsClient::remove_peer(socket_t socket) {
|
|
1203
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1204
|
+
auto it = socket_to_peer_id_.find(socket);
|
|
1205
|
+
if (it != socket_to_peer_id_.end()) {
|
|
1206
|
+
remove_peer_by_id_unlocked(it->second);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
void RatsClient::remove_peer_by_id(const std::string& peer_id) {
|
|
1211
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1212
|
+
remove_peer_by_id_unlocked(peer_id);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
1216
|
+
// Assumes peers_mutex_ is already locked
|
|
1217
|
+
|
|
1218
|
+
// Make a copy of peer_id to avoid use-after-free if the reference points to memory that gets freed
|
|
1219
|
+
std::string peer_id_copy = peer_id;
|
|
1220
|
+
|
|
1221
|
+
auto it = peers_.find(peer_id_copy);
|
|
1222
|
+
if (it != peers_.end()) {
|
|
1223
|
+
// Copy the values we need before erasing to avoid use-after-free
|
|
1224
|
+
socket_t peer_socket = it->second.socket;
|
|
1225
|
+
std::string peer_normalized_address = it->second.normalized_address;
|
|
1226
|
+
|
|
1227
|
+
socket_to_peer_id_.erase(peer_socket);
|
|
1228
|
+
address_to_peer_id_.erase(peer_normalized_address);
|
|
1229
|
+
peers_.erase(it);
|
|
1230
|
+
|
|
1231
|
+
// Clean up socket-specific mutex
|
|
1232
|
+
cleanup_socket_send_mutex(peer_socket);
|
|
1233
|
+
|
|
1234
|
+
// Clean up ICE coordination tracking for this peer
|
|
1235
|
+
cleanup_ice_coordination_for_peer(peer_id_copy);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
bool RatsClient::is_already_connected_to_address(const std::string& normalized_address) const {
|
|
1240
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1241
|
+
return address_to_peer_id_.find(normalized_address) != address_to_peer_id_.end();
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
1245
|
+
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1246
|
+
|
|
1247
|
+
// Check if already in the list
|
|
1248
|
+
if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), ip_address) == local_interface_addresses_.end()) {
|
|
1249
|
+
local_interface_addresses_.push_back(ip_address);
|
|
1250
|
+
LOG_CLIENT_INFO("Added " << ip_address << " to ignore list");
|
|
1251
|
+
} else {
|
|
1252
|
+
LOG_CLIENT_DEBUG("IP address " << ip_address << " already in ignore list");
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
// Local interface address blocking methods
|
|
1258
|
+
void RatsClient::initialize_local_addresses() {
|
|
1259
|
+
LOG_CLIENT_INFO("Initializing local interface addresses for connection blocking");
|
|
1260
|
+
|
|
1261
|
+
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1262
|
+
|
|
1263
|
+
// Get all local interface addresses using network_utils
|
|
1264
|
+
local_interface_addresses_ = network_utils::get_local_interface_addresses();
|
|
1265
|
+
|
|
1266
|
+
// Add common localhost addresses if not already present
|
|
1267
|
+
std::vector<std::string> localhost_addrs = {"127.0.0.1", "::1", "0.0.0.0", "::"};
|
|
1268
|
+
for (const auto& addr : localhost_addrs) {
|
|
1269
|
+
if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
|
|
1270
|
+
local_interface_addresses_.push_back(addr);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
LOG_CLIENT_INFO("Found " << local_interface_addresses_.size() << " local addresses to block:");
|
|
1275
|
+
for (const auto& addr : local_interface_addresses_) {
|
|
1276
|
+
LOG_CLIENT_INFO(" - " << addr);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
void RatsClient::refresh_local_addresses() {
|
|
1281
|
+
LOG_CLIENT_DEBUG("Refreshing local interface addresses");
|
|
1282
|
+
|
|
1283
|
+
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1284
|
+
|
|
1285
|
+
// Clear old addresses and get fresh ones
|
|
1286
|
+
local_interface_addresses_.clear();
|
|
1287
|
+
local_interface_addresses_ = network_utils::get_local_interface_addresses();
|
|
1288
|
+
|
|
1289
|
+
// Add common localhost addresses if not already present
|
|
1290
|
+
std::vector<std::string> localhost_addrs = {"127.0.0.1", "::1", "0.0.0.0", "::"};
|
|
1291
|
+
for (const auto& addr : localhost_addrs) {
|
|
1292
|
+
if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
|
|
1293
|
+
local_interface_addresses_.push_back(addr);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
LOG_CLIENT_DEBUG("Refreshed " << local_interface_addresses_.size() << " local addresses");
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
bool RatsClient::is_blocked_address(const std::string& ip_address) const {
|
|
1301
|
+
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1302
|
+
|
|
1303
|
+
// Check against our stored local addresses
|
|
1304
|
+
for (const auto& local_addr : local_interface_addresses_) {
|
|
1305
|
+
if (local_addr == ip_address) {
|
|
1306
|
+
return true;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
bool RatsClient::should_ignore_peer(const std::string& ip, int port) const {
|
|
1314
|
+
// Always block connections to ourselves (same port)
|
|
1315
|
+
if (port == listen_port_) {
|
|
1316
|
+
if (ip == "127.0.0.1" || ip == "::1" || ip == "localhost" || ip == "0.0.0.0" || ip == "::") {
|
|
1317
|
+
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - localhost with same port");
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// For localhost addresses on different ports, allow the connection (for testing)
|
|
1323
|
+
if (ip == "127.0.0.1" || ip == "::1" || ip == "localhost") {
|
|
1324
|
+
LOG_CLIENT_DEBUG("Allowing localhost peer " << ip << ":" << port << " on different port");
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// Check if the IP is a non-localhost local interface address
|
|
1329
|
+
if (is_blocked_address(ip)) {
|
|
1330
|
+
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - matches local interface address");
|
|
1331
|
+
return true;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
return false;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// =========================================================================
|
|
1338
|
+
// Data Transmission Methods
|
|
1339
|
+
// =========================================================================
|
|
1340
|
+
|
|
1341
|
+
// Helper method to create a message with header
|
|
1342
|
+
std::vector<uint8_t> RatsClient::create_message_with_header(const std::vector<uint8_t>& payload, MessageDataType type) {
|
|
1343
|
+
MessageHeader header(type);
|
|
1344
|
+
std::vector<uint8_t> header_bytes = header.serialize();
|
|
1345
|
+
|
|
1346
|
+
// Combine header + payload
|
|
1347
|
+
std::vector<uint8_t> message;
|
|
1348
|
+
message.reserve(header_bytes.size() + payload.size());
|
|
1349
|
+
message.insert(message.end(), header_bytes.begin(), header_bytes.end());
|
|
1350
|
+
message.insert(message.end(), payload.begin(), payload.end());
|
|
1351
|
+
|
|
1352
|
+
return message;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Helper method to parse message header and extract payload
|
|
1356
|
+
bool RatsClient::parse_message_with_header(const std::vector<uint8_t>& message, MessageHeader& header, std::vector<uint8_t>& payload) const {
|
|
1357
|
+
// Check if message is large enough to contain header
|
|
1358
|
+
if (message.size() < MessageHeader::HEADER_SIZE) {
|
|
1359
|
+
LOG_CLIENT_DEBUG("Message too small to contain header: " << message.size() << " bytes");
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Extract header bytes
|
|
1364
|
+
std::vector<uint8_t> header_bytes(message.begin(), message.begin() + MessageHeader::HEADER_SIZE);
|
|
1365
|
+
|
|
1366
|
+
// Parse header
|
|
1367
|
+
if (!MessageHeader::deserialize(header_bytes, header)) {
|
|
1368
|
+
LOG_CLIENT_DEBUG("Failed to parse message header - invalid magic number or format");
|
|
1369
|
+
return false;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// Validate header
|
|
1373
|
+
if (!header.is_valid_type()) {
|
|
1374
|
+
LOG_CLIENT_WARN("Invalid message data type: " << static_cast<int>(header.type));
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// Extract payload
|
|
1379
|
+
payload.assign(message.begin() + MessageHeader::HEADER_SIZE, message.end());
|
|
1380
|
+
|
|
1381
|
+
LOG_CLIENT_DEBUG("Parsed message header: type=" << static_cast<int>(header.type) << ", payload_size=" << payload.size());
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
1386
|
+
if (!running_.load()) {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Get socket-specific mutex for thread-safe sending
|
|
1391
|
+
// Prevent framed messages corruption (like two-times sending the number of bytes instead number of bytes + message)
|
|
1392
|
+
auto socket_mutex = get_socket_send_mutex(socket);
|
|
1393
|
+
std::lock_guard<std::mutex> send_lock(*socket_mutex);
|
|
1394
|
+
|
|
1395
|
+
// Create message with specified header type
|
|
1396
|
+
std::vector<uint8_t> message_with_header = create_message_with_header(data, message_type);
|
|
1397
|
+
|
|
1398
|
+
// Use encrypted communication if encryption is enabled
|
|
1399
|
+
if (is_encryption_enabled()) {
|
|
1400
|
+
// Check if handshake is completed before sending data
|
|
1401
|
+
if (!encrypted_communication::is_handshake_completed(socket)) {
|
|
1402
|
+
std::string type_name = (message_type == MessageDataType::BINARY) ? "binary" :
|
|
1403
|
+
(message_type == MessageDataType::STRING) ? "string" : "JSON";
|
|
1404
|
+
LOG_CLIENT_WARN("Cannot send " << type_name << " data to socket " << socket << " - encryption handshake not completed");
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
int sent = encrypted_communication::send_tcp_data_encrypted(socket, message_with_header);
|
|
1409
|
+
return sent > 0;
|
|
1410
|
+
} else {
|
|
1411
|
+
// Use framed messages for reliable large message handling
|
|
1412
|
+
int sent = send_tcp_message_framed(socket, message_with_header);
|
|
1413
|
+
return sent > 0;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
bool RatsClient::send_string_to_peer(socket_t socket, const std::string& data) {
|
|
1418
|
+
// Convert string to binary and use the primary send_binary_to_peer method
|
|
1419
|
+
std::vector<uint8_t> binary_data(data.begin(), data.end());
|
|
1420
|
+
return send_binary_to_peer(socket, binary_data, MessageDataType::STRING);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
bool RatsClient::send_json_to_peer(socket_t socket, const nlohmann::json& data) {
|
|
1424
|
+
try {
|
|
1425
|
+
// Serialize JSON and convert to binary, then use the primary send_binary_to_peer method
|
|
1426
|
+
std::string json_string = data.dump();
|
|
1427
|
+
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1428
|
+
return send_binary_to_peer(socket, binary_data, MessageDataType::JSON);
|
|
1429
|
+
} catch (const nlohmann::json::exception& e) {
|
|
1430
|
+
LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
bool RatsClient::send_binary_to_peer_id(const std::string& peer_hash_id, const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
1436
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1437
|
+
auto it = peers_.find(peer_hash_id);
|
|
1438
|
+
if (it == peers_.end() || !it->second.is_handshake_completed()) {
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return send_binary_to_peer(it->second.socket, data, message_type);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
bool RatsClient::send_string_to_peer_id(const std::string& peer_hash_id, const std::string& data) {
|
|
1446
|
+
// Convert string to binary and use primary binary method with STRING type
|
|
1447
|
+
std::vector<uint8_t> binary_data(data.begin(), data.end());
|
|
1448
|
+
return send_binary_to_peer_id(peer_hash_id, binary_data, MessageDataType::STRING);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlohmann::json& data) {
|
|
1452
|
+
try {
|
|
1453
|
+
// Serialize JSON and convert to binary, then use primary binary method with JSON type
|
|
1454
|
+
std::string json_string = data.dump();
|
|
1455
|
+
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1456
|
+
return send_binary_to_peer_id(peer_hash_id, binary_data, MessageDataType::JSON);
|
|
1457
|
+
} catch (const nlohmann::json::exception& e) {
|
|
1458
|
+
LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
|
|
1464
|
+
try {
|
|
1465
|
+
// Serialize JSON and convert to binary, then use primary binary method with JSON type
|
|
1466
|
+
std::string json_string = data.dump();
|
|
1467
|
+
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1468
|
+
return broadcast_binary_to_peers(binary_data, MessageDataType::JSON);
|
|
1469
|
+
} catch (const nlohmann::json::exception& e) {
|
|
1470
|
+
LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
|
|
1471
|
+
return 0;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
int RatsClient::broadcast_binary_to_peers(const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
1476
|
+
if (!running_.load()) {
|
|
1477
|
+
return 0;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
int sent_count = 0;
|
|
1481
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1482
|
+
|
|
1483
|
+
for (const auto& pair : peers_) {
|
|
1484
|
+
const RatsPeer& peer = pair.second;
|
|
1485
|
+
// Only send to peers that have completed handshake
|
|
1486
|
+
if (peer.is_handshake_completed()) {
|
|
1487
|
+
if (send_binary_to_peer(peer.socket, data, message_type)) {
|
|
1488
|
+
sent_count++;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
return sent_count;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
int RatsClient::broadcast_string_to_peers(const std::string& data) {
|
|
1497
|
+
// Convert string to binary and use primary binary method with STRING type
|
|
1498
|
+
std::vector<uint8_t> binary_data(data.begin(), data.end());
|
|
1499
|
+
return broadcast_binary_to_peers(binary_data, MessageDataType::STRING);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
bool RatsClient::parse_json_message(const std::string& message, nlohmann::json& out_json) {
|
|
1503
|
+
try {
|
|
1504
|
+
out_json = nlohmann::json::parse(message);
|
|
1505
|
+
return true;
|
|
1506
|
+
} catch (const nlohmann::json::exception& e) {
|
|
1507
|
+
LOG_CLIENT_ERROR("Failed to parse JSON message: " << e.what());
|
|
1508
|
+
return false;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// Helpers
|
|
1513
|
+
|
|
1514
|
+
// Per-socket synchronization helpers
|
|
1515
|
+
std::shared_ptr<std::mutex> RatsClient::get_socket_send_mutex(socket_t socket) {
|
|
1516
|
+
std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
|
|
1517
|
+
auto it = socket_send_mutexes_.find(socket);
|
|
1518
|
+
if (it == socket_send_mutexes_.end()) {
|
|
1519
|
+
// Create new mutex for this socket
|
|
1520
|
+
socket_send_mutexes_[socket] = std::make_shared<std::mutex>();
|
|
1521
|
+
return socket_send_mutexes_[socket];
|
|
1522
|
+
}
|
|
1523
|
+
return it->second;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
void RatsClient::cleanup_socket_send_mutex(socket_t socket) {
|
|
1527
|
+
std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
|
|
1528
|
+
socket_send_mutexes_.erase(socket);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// =========================================================================
|
|
1532
|
+
// Peer Information and Management
|
|
1533
|
+
// =========================================================================
|
|
1534
|
+
|
|
1535
|
+
std::string RatsClient::get_our_peer_id() const {
|
|
1536
|
+
return our_peer_id_;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
int RatsClient::get_peer_count_unlocked() const {
|
|
1540
|
+
// Assumes peers_mutex_ is already locked
|
|
1541
|
+
int count = 0;
|
|
1542
|
+
for (const auto& pair : peers_) {
|
|
1543
|
+
if (pair.second.is_handshake_completed()) {
|
|
1544
|
+
count++;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return count;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
int RatsClient::get_peer_count() const {
|
|
1551
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1552
|
+
return get_peer_count_unlocked();
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
std::string RatsClient::get_peer_id(socket_t socket) const {
|
|
1556
|
+
const RatsPeer* peer = get_peer_by_socket(socket);
|
|
1557
|
+
return peer ? peer->peer_id : "";
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
socket_t RatsClient::get_peer_socket_by_id(const std::string& peer_id) const {
|
|
1561
|
+
const RatsPeer* peer = get_peer_by_id(peer_id);
|
|
1562
|
+
return peer ? peer->socket : INVALID_SOCKET_VALUE;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
std::vector<RatsPeer> RatsClient::get_all_peers() const {
|
|
1566
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1567
|
+
std::vector<RatsPeer> result;
|
|
1568
|
+
result.reserve(peers_.size());
|
|
1569
|
+
|
|
1570
|
+
for (const auto& pair : peers_) {
|
|
1571
|
+
result.push_back(pair.second);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
return result;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
std::vector<RatsPeer> RatsClient::get_validated_peers() const {
|
|
1578
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1579
|
+
std::vector<RatsPeer> result;
|
|
1580
|
+
|
|
1581
|
+
for (const auto& pair : peers_) {
|
|
1582
|
+
if (pair.second.is_handshake_completed()) {
|
|
1583
|
+
result.push_back(pair.second);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
return result;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::string& exclude_peer_id) const {
|
|
1592
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1593
|
+
|
|
1594
|
+
std::vector<RatsPeer> all_validated_peers;
|
|
1595
|
+
|
|
1596
|
+
// Get all validated peers excluding the specified peer
|
|
1597
|
+
for (const auto& pair : peers_) {
|
|
1598
|
+
const RatsPeer& peer = pair.second;
|
|
1599
|
+
if (peer.is_handshake_completed() && peer.peer_id != exclude_peer_id) {
|
|
1600
|
+
all_validated_peers.push_back(peer);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// If we have fewer peers than requested, return all
|
|
1605
|
+
if (all_validated_peers.size() <= static_cast<size_t>(max_count)) {
|
|
1606
|
+
return all_validated_peers;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// Randomly select peers
|
|
1610
|
+
std::vector<RatsPeer> selected_peers;
|
|
1611
|
+
std::random_device rd;
|
|
1612
|
+
std::mt19937 gen(rd());
|
|
1613
|
+
|
|
1614
|
+
// Use random sampling to select peers
|
|
1615
|
+
std::sample(all_validated_peers.begin(), all_validated_peers.end(),
|
|
1616
|
+
std::back_inserter(selected_peers), max_count, gen);
|
|
1617
|
+
|
|
1618
|
+
return selected_peers;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const RatsPeer* RatsClient::get_peer_by_id(const std::string& peer_id) const {
|
|
1622
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1623
|
+
auto it = peers_.find(peer_id);
|
|
1624
|
+
return (it != peers_.end()) ? &it->second : nullptr;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
const RatsPeer* RatsClient::get_peer_by_socket(socket_t socket) const {
|
|
1628
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1629
|
+
auto it = socket_to_peer_id_.find(socket);
|
|
1630
|
+
if (it != socket_to_peer_id_.end()) {
|
|
1631
|
+
auto peer_it = peers_.find(it->second);
|
|
1632
|
+
return (peer_it != peers_.end()) ? &peer_it->second : nullptr;
|
|
1633
|
+
}
|
|
1634
|
+
return nullptr;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
|
|
1638
|
+
// Peer limit management methods
|
|
1639
|
+
int RatsClient::get_max_peers() const {
|
|
1640
|
+
return max_peers_;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
void RatsClient::set_max_peers(int max_peers) {
|
|
1644
|
+
max_peers_ = max_peers;
|
|
1645
|
+
LOG_CLIENT_INFO("Maximum peers set to " << max_peers_);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
bool RatsClient::is_peer_limit_reached() const {
|
|
1649
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1650
|
+
// Connected peers only enforcement (exclude handshake peers)
|
|
1651
|
+
int connected_peers = get_peer_count_unlocked();
|
|
1652
|
+
if (connected_peers >= max_peers_) {
|
|
1653
|
+
return true;
|
|
1654
|
+
}
|
|
1655
|
+
return false;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
std::string RatsClient::generate_peer_hash_id(socket_t socket, const std::string& connection_info) {
|
|
1659
|
+
// Generate unique hash ID using timestamp, socket, connection info, and random component
|
|
1660
|
+
auto now = std::chrono::high_resolution_clock::now();
|
|
1661
|
+
auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
|
|
1662
|
+
|
|
1663
|
+
// Create a random component
|
|
1664
|
+
std::random_device rd;
|
|
1665
|
+
std::mt19937 gen(rd());
|
|
1666
|
+
std::uniform_int_distribution<> dis(0, 255);
|
|
1667
|
+
|
|
1668
|
+
// Build hash string
|
|
1669
|
+
std::ostringstream hash_stream;
|
|
1670
|
+
hash_stream << std::hex << timestamp << "_" << socket << "_";
|
|
1671
|
+
|
|
1672
|
+
// Add connection info hash
|
|
1673
|
+
std::hash<std::string> hasher;
|
|
1674
|
+
hash_stream << hasher(connection_info) << "_";
|
|
1675
|
+
|
|
1676
|
+
// Add random component
|
|
1677
|
+
for (int i = 0; i < 8; ++i) {
|
|
1678
|
+
hash_stream << std::setfill('0') << std::setw(2) << dis(gen);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
return hash_stream.str();
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
std::string RatsClient::normalize_peer_address(const std::string& ip, int port) const {
|
|
1685
|
+
// Normalize IPv6 addresses and create consistent format
|
|
1686
|
+
std::string normalized_ip = ip;
|
|
1687
|
+
|
|
1688
|
+
// Remove brackets from IPv6 addresses if present
|
|
1689
|
+
if (!normalized_ip.empty() && normalized_ip.front() == '[' && normalized_ip.back() == ']') {
|
|
1690
|
+
normalized_ip = normalized_ip.substr(1, normalized_ip.length() - 2);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// Handle localhost variations
|
|
1694
|
+
if (normalized_ip == "localhost" || normalized_ip == "::1") {
|
|
1695
|
+
normalized_ip = "127.0.0.1";
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// For IPv6 addresses, add brackets for consistency
|
|
1699
|
+
if (normalized_ip.find(':') != std::string::npos && normalized_ip.find('.') == std::string::npos) {
|
|
1700
|
+
// This is likely an IPv6 address (contains colons but no dots)
|
|
1701
|
+
return "[" + normalized_ip + "]:" + std::to_string(port);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
return normalized_ip + ":" + std::to_string(port);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// =========================================================================
|
|
1708
|
+
// Callback Registration
|
|
1709
|
+
// ========================================================================
|
|
1710
|
+
|
|
1711
|
+
void RatsClient::set_connection_callback(ConnectionCallback callback) {
|
|
1712
|
+
connection_callback_ = callback;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
void RatsClient::set_binary_data_callback(BinaryDataCallback callback) {
|
|
1716
|
+
binary_data_callback_ = callback;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
void RatsClient::set_string_data_callback(StringDataCallback callback) {
|
|
1720
|
+
string_data_callback_ = callback;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
void RatsClient::set_json_data_callback(JsonDataCallback callback) {
|
|
1724
|
+
json_data_callback_ = callback;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
void RatsClient::set_disconnect_callback(DisconnectCallback callback) {
|
|
1728
|
+
disconnect_callback_ = callback;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// NAT Traversal and ICE Callback Registration
|
|
1732
|
+
void RatsClient::set_advanced_connection_callback(AdvancedConnectionCallback callback) {
|
|
1733
|
+
advanced_connection_callback_ = callback;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
void RatsClient::set_nat_traversal_progress_callback(NatTraversalProgressCallback callback) {
|
|
1737
|
+
nat_progress_callback_ = callback;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
void RatsClient::set_ice_candidate_callback(IceCandidateDiscoveredCallback callback) {
|
|
1741
|
+
ice_candidate_callback_ = callback;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// =========================================================================
|
|
1745
|
+
// Peer Discovery Methods
|
|
1746
|
+
// =========================================================================
|
|
1747
|
+
|
|
1748
|
+
bool RatsClient::start_dht_discovery(int dht_port) {
|
|
1749
|
+
if (dht_client_ && dht_client_->is_running()) {
|
|
1750
|
+
LOG_CLIENT_WARN("DHT discovery is already running");
|
|
1751
|
+
return true;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
LOG_CLIENT_INFO("Starting DHT discovery on port " << dht_port <<
|
|
1755
|
+
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
1756
|
+
|
|
1757
|
+
dht_client_ = std::make_unique<DhtClient>(dht_port, bind_address_, data_directory_);
|
|
1758
|
+
if (!dht_client_->start()) {
|
|
1759
|
+
LOG_CLIENT_ERROR("Failed to start DHT client");
|
|
1760
|
+
dht_client_.reset();
|
|
1761
|
+
return false;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// Bootstrap with default nodes
|
|
1765
|
+
auto bootstrap_nodes = DhtClient::get_default_bootstrap_nodes();
|
|
1766
|
+
if (!dht_client_->bootstrap(bootstrap_nodes)) {
|
|
1767
|
+
LOG_CLIENT_WARN("Failed to bootstrap DHT");
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// Start automatic peer discovery
|
|
1771
|
+
start_automatic_peer_discovery();
|
|
1772
|
+
|
|
1773
|
+
LOG_CLIENT_INFO("DHT discovery started successfully");
|
|
1774
|
+
return true;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
void RatsClient::stop_dht_discovery() {
|
|
1778
|
+
if (!dht_client_) {
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
LOG_CLIENT_INFO("Stopping DHT discovery");
|
|
1783
|
+
|
|
1784
|
+
// Stop automatic peer discovery
|
|
1785
|
+
stop_automatic_peer_discovery();
|
|
1786
|
+
|
|
1787
|
+
dht_client_->stop();
|
|
1788
|
+
dht_client_.reset();
|
|
1789
|
+
LOG_CLIENT_INFO("DHT discovery stopped");
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::function<void(const std::vector<std::string>&)> callback) {
|
|
1793
|
+
if (!dht_client_ || !dht_client_->is_running()) {
|
|
1794
|
+
LOG_CLIENT_ERROR("DHT client not running");
|
|
1795
|
+
return false;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
if (content_hash.length() != 40) { // 160-bit hash as hex string
|
|
1799
|
+
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
LOG_CLIENT_INFO("Finding peers for content hash: " << content_hash);
|
|
1804
|
+
|
|
1805
|
+
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
1806
|
+
|
|
1807
|
+
return dht_client_->find_peers(info_hash, [this, callback](const std::vector<Peer>& peers, const InfoHash& info_hash) {
|
|
1808
|
+
// Convert Peer to string addresses for callback
|
|
1809
|
+
std::vector<std::string> peer_addresses;
|
|
1810
|
+
for (const auto& peer : peers) {
|
|
1811
|
+
peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
if (callback) {
|
|
1815
|
+
callback(peer_addresses);
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port) {
|
|
1821
|
+
if (!dht_client_ || !dht_client_->is_running()) {
|
|
1822
|
+
LOG_CLIENT_ERROR("DHT client not running");
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
if (content_hash.length() != 40) { // 160-bit hash as hex string
|
|
1827
|
+
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
|
|
1828
|
+
return false;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
if (port == 0) {
|
|
1832
|
+
port = listen_port_;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port);
|
|
1836
|
+
|
|
1837
|
+
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
1838
|
+
return dht_client_->announce_peer(info_hash, port);
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
bool RatsClient::is_dht_running() const {
|
|
1842
|
+
return dht_client_ && dht_client_->is_running();
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
size_t RatsClient::get_dht_routing_table_size() const {
|
|
1846
|
+
if (!dht_client_) {
|
|
1847
|
+
return 0;
|
|
1848
|
+
}
|
|
1849
|
+
return dht_client_->get_routing_table_size();
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
void RatsClient::handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash) {
|
|
1853
|
+
LOG_CLIENT_INFO("DHT discovered " << peers.size() << " peers for info hash: " << node_id_to_hex(info_hash));
|
|
1854
|
+
|
|
1855
|
+
// Auto-connect to discovered peers (optional behavior)
|
|
1856
|
+
for (const auto& peer : peers) {
|
|
1857
|
+
// Check if this peer should be ignored (local interface)
|
|
1858
|
+
if (should_ignore_peer(peer.ip, peer.port)) {
|
|
1859
|
+
LOG_CLIENT_DEBUG("Ignoring discovered peer " << peer.ip << ":" << peer.port << " - local interface address");
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// Check if we're already connected to this peer
|
|
1864
|
+
std::string normalized_peer_address = normalize_peer_address(peer.ip, peer.port);
|
|
1865
|
+
bool already_connected = is_already_connected_to_address(normalized_peer_address);
|
|
1866
|
+
|
|
1867
|
+
if (!already_connected) {
|
|
1868
|
+
// Check if peer limit is reached
|
|
1869
|
+
if (is_peer_limit_reached()) {
|
|
1870
|
+
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to DHT discovered peer " << peer.ip << ":" << peer.port);
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
LOG_CLIENT_DEBUG("Attempting to connect to discovered peer: " << peer.ip << ":" << peer.port);
|
|
1875
|
+
|
|
1876
|
+
// Try to connect to the peer (non-blocking)
|
|
1877
|
+
std::thread([this, peer]() {
|
|
1878
|
+
if (connect_to_peer(peer.ip, peer.port)) {
|
|
1879
|
+
LOG_CLIENT_INFO("Successfully connected to DHT discovered peer: " << peer.ip << ":" << peer.port);
|
|
1880
|
+
} else {
|
|
1881
|
+
LOG_CLIENT_DEBUG("Failed to connect to DHT discovered peer: " << peer.ip << ":" << peer.port);
|
|
1882
|
+
}
|
|
1883
|
+
}).detach();
|
|
1884
|
+
} else {
|
|
1885
|
+
LOG_CLIENT_DEBUG("Already connected to discovered peer: " << normalized_peer_address);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
void RatsClient::start_automatic_peer_discovery() {
|
|
1891
|
+
if (auto_discovery_running_.load()) {
|
|
1892
|
+
LOG_CLIENT_WARN("Automatic peer discovery is already running");
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
LOG_CLIENT_INFO("Starting automatic rats peer discovery");
|
|
1897
|
+
auto_discovery_running_.store(true);
|
|
1898
|
+
auto_discovery_thread_ = std::thread(&RatsClient::automatic_discovery_loop, this);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
void RatsClient::stop_automatic_peer_discovery() {
|
|
1902
|
+
if (!auto_discovery_running_.load()) {
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
LOG_CLIENT_INFO("Stopping automatic peer discovery");
|
|
1907
|
+
auto_discovery_running_.store(false);
|
|
1908
|
+
|
|
1909
|
+
if (auto_discovery_thread_.joinable()) {
|
|
1910
|
+
auto_discovery_thread_.join();
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
LOG_CLIENT_INFO("Automatic peer discovery stopped");
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
bool RatsClient::is_automatic_discovery_running() const {
|
|
1917
|
+
return auto_discovery_running_.load();
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// This function will be removed - implementation moved to end of file
|
|
1921
|
+
|
|
1922
|
+
void RatsClient::automatic_discovery_loop() {
|
|
1923
|
+
LOG_CLIENT_INFO("Automatic peer discovery loop started");
|
|
1924
|
+
|
|
1925
|
+
// Initial delay to let DHT bootstrap
|
|
1926
|
+
{
|
|
1927
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
1928
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(5), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
1929
|
+
LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// Search immediately
|
|
1935
|
+
search_rats_peers();
|
|
1936
|
+
|
|
1937
|
+
{
|
|
1938
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
1939
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(10), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
1940
|
+
LOG_CLIENT_INFO("Automatic peer discovery loop stopped during search delay");
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// Announce immediately
|
|
1946
|
+
announce_rats_peer();
|
|
1947
|
+
|
|
1948
|
+
auto last_announce = std::chrono::steady_clock::now();
|
|
1949
|
+
auto last_search = std::chrono::steady_clock::now();
|
|
1950
|
+
|
|
1951
|
+
while (auto_discovery_running_.load()) {
|
|
1952
|
+
auto now = std::chrono::steady_clock::now();
|
|
1953
|
+
|
|
1954
|
+
if (get_peer_count() == 0) {
|
|
1955
|
+
// No peers: aggressive search and announce
|
|
1956
|
+
if (now - last_search >= std::chrono::seconds(5)) {
|
|
1957
|
+
search_rats_peers();
|
|
1958
|
+
last_search = now;
|
|
1959
|
+
}
|
|
1960
|
+
if (now - last_announce >= std::chrono::seconds(20)) {
|
|
1961
|
+
announce_rats_peer();
|
|
1962
|
+
last_announce = now;
|
|
1963
|
+
}
|
|
1964
|
+
} else {
|
|
1965
|
+
// Peers connected: less aggressive, similar to original logic
|
|
1966
|
+
if (now - last_search >= std::chrono::minutes(5)) {
|
|
1967
|
+
search_rats_peers();
|
|
1968
|
+
last_search = now;
|
|
1969
|
+
}
|
|
1970
|
+
if (now - last_announce >= std::chrono::minutes(10)) {
|
|
1971
|
+
announce_rats_peer();
|
|
1972
|
+
last_announce = now;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
// Use conditional variable for responsive shutdown
|
|
1977
|
+
{
|
|
1978
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
1979
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
LOG_CLIENT_INFO("Automatic peer discovery loop stopped");
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
void RatsClient::announce_rats_peer() {
|
|
1989
|
+
if (!dht_client_ || !dht_client_->is_running()) {
|
|
1990
|
+
LOG_CLIENT_WARN("DHT client not running, cannot announce peer");
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
std::string discovery_hash = get_discovery_hash();
|
|
1995
|
+
LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
|
|
1996
|
+
|
|
1997
|
+
if (announce_for_hash(discovery_hash, listen_port_)) {
|
|
1998
|
+
LOG_CLIENT_DEBUG("Successfully announced peer for discovery");
|
|
1999
|
+
} else {
|
|
2000
|
+
LOG_CLIENT_WARN("Failed to announce peer for discovery");
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
void RatsClient::search_rats_peers() {
|
|
2005
|
+
if (!dht_client_ || !dht_client_->is_running()) {
|
|
2006
|
+
LOG_CLIENT_WARN("DHT client not running, cannot search for peers");
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
std::string discovery_hash = get_discovery_hash();
|
|
2011
|
+
LOG_CLIENT_INFO("Searching for peers using discovery hash: " << discovery_hash);
|
|
2012
|
+
|
|
2013
|
+
InfoHash info_hash = hex_to_node_id(discovery_hash);
|
|
2014
|
+
|
|
2015
|
+
if (dht_client_->is_search_active(info_hash)) {
|
|
2016
|
+
LOG_CLIENT_WARN("Search already in progress for info hash " << discovery_hash);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
find_peers_by_hash(discovery_hash, [this, info_hash](const std::vector<std::string>& peer_addresses) {
|
|
2021
|
+
LOG_CLIENT_INFO("Found " << peer_addresses.size() << " peers through DHT discovery");
|
|
2022
|
+
|
|
2023
|
+
// Convert peer addresses back to Peer objects for handle_dht_peer_discovery()
|
|
2024
|
+
std::vector<Peer> peers;
|
|
2025
|
+
for (const auto& peer_address : peer_addresses) {
|
|
2026
|
+
std::string ip;
|
|
2027
|
+
int port;
|
|
2028
|
+
if (parse_address_string(peer_address, ip, port)) {
|
|
2029
|
+
peers.push_back(Peer(ip, port));
|
|
2030
|
+
LOG_CLIENT_DEBUG("Discovered peer: " << peer_address);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// Auto-connect to discovered RATS peers
|
|
2035
|
+
handle_dht_peer_discovery(peers, info_hash);
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
std::string RatsClient::get_discovery_hash() const {
|
|
2040
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
2041
|
+
// Generate discovery hash based on current protocol configuration
|
|
2042
|
+
std::string discovery_string = custom_protocol_name_ + "_peer_discovery_v" + custom_protocol_version_;
|
|
2043
|
+
return SHA1::hash(discovery_string);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
std::string RatsClient::get_rats_peer_discovery_hash() {
|
|
2047
|
+
// Well-known hash for rats peer discovery
|
|
2048
|
+
// Compute SHA1 hash of "rats_peer_discovery_v1.0"
|
|
2049
|
+
return SHA1::hash("rats_peer_discovery_v1.0");
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
|
|
2054
|
+
// =========================================================================
|
|
2055
|
+
// Protocol Configuration
|
|
2056
|
+
// =========================================================================
|
|
2057
|
+
|
|
2058
|
+
void RatsClient::set_protocol_name(const std::string& protocol_name) {
|
|
2059
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
2060
|
+
custom_protocol_name_ = protocol_name;
|
|
2061
|
+
LOG_CLIENT_INFO("Protocol name set to: " << protocol_name);
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
void RatsClient::set_protocol_version(const std::string& protocol_version) {
|
|
2065
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
2066
|
+
custom_protocol_version_ = protocol_version;
|
|
2067
|
+
LOG_CLIENT_INFO("Protocol version set to: " << protocol_version);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
std::string RatsClient::get_protocol_name() const {
|
|
2071
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
2072
|
+
return custom_protocol_name_;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
std::string RatsClient::get_protocol_version() const {
|
|
2076
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
2077
|
+
return custom_protocol_version_;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// =========================================================================
|
|
2081
|
+
// Message Exchange API
|
|
2082
|
+
// =========================================================================
|
|
2083
|
+
|
|
2084
|
+
|
|
2085
|
+
void RatsClient::on(const std::string& message_type, MessageCallback callback) {
|
|
2086
|
+
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2087
|
+
message_handlers_[message_type].emplace_back(callback, false); // false = not once
|
|
2088
|
+
LOG_CLIENT_INFO("Registered persistent handler for message type: " << message_type << " (total handlers: " << message_handlers_[message_type].size() << ")");
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
void RatsClient::once(const std::string& message_type, MessageCallback callback) {
|
|
2092
|
+
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2093
|
+
message_handlers_[message_type].emplace_back(callback, true); // true = once
|
|
2094
|
+
LOG_CLIENT_DEBUG("Registered one-time handler for message type: " << message_type);
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
void RatsClient::off(const std::string& message_type) {
|
|
2098
|
+
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2099
|
+
auto it = message_handlers_.find(message_type);
|
|
2100
|
+
if (it != message_handlers_.end()) {
|
|
2101
|
+
size_t removed_count = it->second.size();
|
|
2102
|
+
message_handlers_.erase(it);
|
|
2103
|
+
LOG_CLIENT_DEBUG("Removed " << removed_count << " handlers for message type: " << message_type);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
void RatsClient::send(const std::string& message_type, const nlohmann::json& data, SendCallback callback) {
|
|
2108
|
+
if (!running_.load()) {
|
|
2109
|
+
LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - client is not running");
|
|
2110
|
+
if (callback) {
|
|
2111
|
+
callback(false, "Client is not running");
|
|
2112
|
+
}
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
LOG_CLIENT_INFO("Sending broadcast message type '" << message_type << "' with data: " << data.dump());
|
|
2117
|
+
|
|
2118
|
+
// Create rats message
|
|
2119
|
+
nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
|
|
2120
|
+
|
|
2121
|
+
// Broadcast to all validated peers
|
|
2122
|
+
int sent_count = broadcast_rats_message_to_validated_peers(message);
|
|
2123
|
+
|
|
2124
|
+
LOG_CLIENT_INFO("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
|
|
2125
|
+
|
|
2126
|
+
if (callback) {
|
|
2127
|
+
if (sent_count > 0) {
|
|
2128
|
+
callback(true, "");
|
|
2129
|
+
} else {
|
|
2130
|
+
LOG_CLIENT_WARN("No peers to send message to");
|
|
2131
|
+
callback(false, "No peers to send message to");
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
void RatsClient::send(const std::string& peer_id, const std::string& message_type, const nlohmann::json& data, SendCallback callback) {
|
|
2137
|
+
if (!running_.load()) {
|
|
2138
|
+
LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' to peer " << peer_id << " - client is not running");
|
|
2139
|
+
if (callback) {
|
|
2140
|
+
callback(false, "Client is not running");
|
|
2141
|
+
}
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
LOG_CLIENT_INFO("Sending targeted message type '" << message_type << "' to peer " << peer_id << " with data: " << data.dump());
|
|
2146
|
+
|
|
2147
|
+
// Create rats message
|
|
2148
|
+
nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
|
|
2149
|
+
|
|
2150
|
+
// Send to specific peer
|
|
2151
|
+
socket_t target_socket = INVALID_SOCKET_VALUE;
|
|
2152
|
+
bool peer_found = false;
|
|
2153
|
+
bool handshake_completed = false;
|
|
2154
|
+
|
|
2155
|
+
{
|
|
2156
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2157
|
+
auto it = peers_.find(peer_id);
|
|
2158
|
+
if (it != peers_.end()) {
|
|
2159
|
+
peer_found = true;
|
|
2160
|
+
handshake_completed = it->second.is_handshake_completed();
|
|
2161
|
+
if (handshake_completed) {
|
|
2162
|
+
target_socket = it->second.socket;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
if (!peer_found) {
|
|
2168
|
+
LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - peer not found: " << peer_id);
|
|
2169
|
+
if (callback) {
|
|
2170
|
+
callback(false, "Peer not found: " + peer_id);
|
|
2171
|
+
}
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
if (!handshake_completed) {
|
|
2176
|
+
LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - peer handshake not completed: " << peer_id);
|
|
2177
|
+
if (callback) {
|
|
2178
|
+
callback(false, "Peer handshake not completed: " + peer_id);
|
|
2179
|
+
}
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
bool success = send_json_to_peer(target_socket, message);
|
|
2184
|
+
|
|
2185
|
+
LOG_CLIENT_INFO("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
|
|
2186
|
+
|
|
2187
|
+
if (callback) {
|
|
2188
|
+
if (success) {
|
|
2189
|
+
callback(true, "");
|
|
2190
|
+
} else {
|
|
2191
|
+
callback(false, "Failed to send message to peer: " + peer_id);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// Message exchange system helpers
|
|
2197
|
+
void RatsClient::call_message_handlers(const std::string& message_type, const std::string& peer_id, const nlohmann::json& data) {
|
|
2198
|
+
std::vector<MessageHandler> handlers_to_call;
|
|
2199
|
+
std::vector<MessageHandler> remaining_handlers;
|
|
2200
|
+
|
|
2201
|
+
LOG_CLIENT_INFO("Calling message handlers for type '" << message_type << "' from peer " << peer_id << " with data: " << data.dump());
|
|
2202
|
+
|
|
2203
|
+
// Get handlers to call and identify once handlers
|
|
2204
|
+
{
|
|
2205
|
+
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2206
|
+
auto it = message_handlers_.find(message_type);
|
|
2207
|
+
if (it != message_handlers_.end()) {
|
|
2208
|
+
handlers_to_call = it->second; // Copy handlers
|
|
2209
|
+
|
|
2210
|
+
// Keep only non-once handlers for the remaining list
|
|
2211
|
+
for (const auto& handler : it->second) {
|
|
2212
|
+
if (!handler.is_once) {
|
|
2213
|
+
remaining_handlers.push_back(handler);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
// Update the handlers list (removes once handlers)
|
|
2218
|
+
it->second = remaining_handlers;
|
|
2219
|
+
} else {
|
|
2220
|
+
LOG_CLIENT_WARN("No handlers registered for message type '" << message_type << "'");
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
LOG_CLIENT_INFO("Found " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
|
|
2225
|
+
|
|
2226
|
+
// Call handlers outside of mutex to avoid deadlock
|
|
2227
|
+
for (const auto& handler : handlers_to_call) {
|
|
2228
|
+
try {
|
|
2229
|
+
LOG_CLIENT_INFO("Calling handler for message type '" << message_type << "'");
|
|
2230
|
+
handler.callback(peer_id, data);
|
|
2231
|
+
LOG_CLIENT_INFO("Handler for message type '" << message_type << "' completed successfully");
|
|
2232
|
+
} catch (const std::exception& e) {
|
|
2233
|
+
LOG_CLIENT_ERROR("Exception in message handler for type '" << message_type << "': " << e.what());
|
|
2234
|
+
} catch (...) {
|
|
2235
|
+
LOG_CLIENT_ERROR("Unknown exception in message handler for type '" << message_type << "'");
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
if (!handlers_to_call.empty()) {
|
|
2240
|
+
LOG_CLIENT_INFO("Called " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
void RatsClient::remove_once_handlers(const std::string& message_type) {
|
|
2245
|
+
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2246
|
+
auto it = message_handlers_.find(message_type);
|
|
2247
|
+
if (it != message_handlers_.end()) {
|
|
2248
|
+
auto& handlers = it->second;
|
|
2249
|
+
auto new_end = std::remove_if(handlers.begin(), handlers.end(),
|
|
2250
|
+
[](const MessageHandler& handler) { return handler.is_once; });
|
|
2251
|
+
handlers.erase(new_end, handlers.end());
|
|
2252
|
+
|
|
2253
|
+
// Remove the entire entry if no handlers remain
|
|
2254
|
+
if (handlers.empty()) {
|
|
2255
|
+
message_handlers_.erase(it);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// =========================================================================
|
|
2261
|
+
// Rats messages protocol / Message handling system
|
|
2262
|
+
// =========================================================================
|
|
2263
|
+
|
|
2264
|
+
nlohmann::json RatsClient::create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id) {
|
|
2265
|
+
nlohmann::json message;
|
|
2266
|
+
message["rats_protocol"] = true;
|
|
2267
|
+
message["type"] = type;
|
|
2268
|
+
message["payload"] = payload;
|
|
2269
|
+
message["sender_peer_id"] = sender_peer_id;
|
|
2270
|
+
message["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
2271
|
+
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
|
2272
|
+
|
|
2273
|
+
return message;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& message) {
|
|
2277
|
+
try {
|
|
2278
|
+
std::string message_type = message.value("type", "");
|
|
2279
|
+
nlohmann::json payload = message.value("payload", nlohmann::json::object());
|
|
2280
|
+
std::string sender_peer_id = message.value("sender_peer_id", "");
|
|
2281
|
+
|
|
2282
|
+
LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_hash_id);
|
|
2283
|
+
|
|
2284
|
+
// Call registered message handlers for all message types (including custom ones)
|
|
2285
|
+
call_message_handlers(message_type, sender_peer_id.empty() ? peer_hash_id : sender_peer_id, payload);
|
|
2286
|
+
|
|
2287
|
+
// Handle built-in message types for internal functionality
|
|
2288
|
+
if (message_type == "peer") {
|
|
2289
|
+
handle_peer_exchange_message(socket, peer_hash_id, payload);
|
|
2290
|
+
}
|
|
2291
|
+
else if (message_type == "peers_request") {
|
|
2292
|
+
handle_peers_request_message(socket, peer_hash_id, payload);
|
|
2293
|
+
}
|
|
2294
|
+
else if (message_type == "peers_response") {
|
|
2295
|
+
handle_peers_response_message(socket, peer_hash_id, payload);
|
|
2296
|
+
}
|
|
2297
|
+
// ICE coordination messages
|
|
2298
|
+
else if (message_type == "ice_offer") {
|
|
2299
|
+
handle_ice_offer_message(socket, peer_hash_id, payload);
|
|
2300
|
+
}
|
|
2301
|
+
else if (message_type == "ice_answer") {
|
|
2302
|
+
handle_ice_answer_message(socket, peer_hash_id, payload);
|
|
2303
|
+
}
|
|
2304
|
+
else if (message_type == "ice_candidate") {
|
|
2305
|
+
handle_ice_candidate_message(socket, peer_hash_id, payload);
|
|
2306
|
+
}
|
|
2307
|
+
// NAT traversal coordination messages
|
|
2308
|
+
else if (message_type == "hole_punch_coordination") {
|
|
2309
|
+
handle_hole_punch_coordination_message(socket, peer_hash_id, payload);
|
|
2310
|
+
}
|
|
2311
|
+
else if (message_type == "nat_info_exchange") {
|
|
2312
|
+
handle_nat_info_exchange_message(socket, peer_hash_id, payload);
|
|
2313
|
+
}
|
|
2314
|
+
// Custom message types are now handled by registered handlers above
|
|
2315
|
+
// No need for else clause - all message types are valid if they have registered handlers
|
|
2316
|
+
|
|
2317
|
+
} catch (const nlohmann::json::exception& e) {
|
|
2318
|
+
LOG_CLIENT_ERROR("Failed to handle rats message: " << e.what());
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
|
|
2323
|
+
try {
|
|
2324
|
+
std::string peer_ip = payload.value("ip", "");
|
|
2325
|
+
int peer_port = payload.value("port", 0);
|
|
2326
|
+
std::string peer_id = payload.value("peer_id", "");
|
|
2327
|
+
|
|
2328
|
+
if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
|
|
2329
|
+
LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_hash_id);
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
LOG_CLIENT_INFO("Received peer exchange: " << peer_ip << ":" << peer_port << " (peer_id: " << peer_id << ")");
|
|
2334
|
+
|
|
2335
|
+
// Check if we should ignore this peer (local interface)
|
|
2336
|
+
if (should_ignore_peer(peer_ip, peer_port)) {
|
|
2337
|
+
LOG_CLIENT_DEBUG("Ignoring exchanged peer " << peer_ip << ":" << peer_port << " - local interface address");
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// Check if we're already connected to this peer
|
|
2342
|
+
std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
|
|
2343
|
+
if (is_already_connected_to_address(normalized_peer_address)) {
|
|
2344
|
+
LOG_CLIENT_DEBUG("Already connected to exchanged peer " << normalized_peer_address);
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
// Check if peer limit is reached
|
|
2349
|
+
if (is_peer_limit_reached()) {
|
|
2350
|
+
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to exchanged peer " << peer_ip << ":" << peer_port);
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
// Try to connect to the exchanged peer (non-blocking)
|
|
2355
|
+
add_managed_thread(std::thread([this, peer_ip, peer_port, peer_id]() {
|
|
2356
|
+
if (connect_to_peer(peer_ip, peer_port)) {
|
|
2357
|
+
LOG_CLIENT_INFO("Successfully connected to exchanged peer: " << peer_ip << ":" << peer_port);
|
|
2358
|
+
} else {
|
|
2359
|
+
LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << peer_ip << ":" << peer_port);
|
|
2360
|
+
}
|
|
2361
|
+
}), "peer-exchange-connect-" + peer_id.substr(0, 8));
|
|
2362
|
+
|
|
2363
|
+
} catch (const nlohmann::json::exception& e) {
|
|
2364
|
+
LOG_CLIENT_ERROR("Failed to handle peer exchange message: " << e.what());
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// General broadcasting functions
|
|
2369
|
+
int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id) {
|
|
2370
|
+
int sent_count = 0;
|
|
2371
|
+
{
|
|
2372
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2373
|
+
for (const auto& pair : peers_) {
|
|
2374
|
+
const RatsPeer& peer = pair.second;
|
|
2375
|
+
// Don't send to excluded peer
|
|
2376
|
+
if (!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) {
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
if (send_json_to_peer(peer.socket, message)) {
|
|
2381
|
+
sent_count++;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
return sent_count;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
int RatsClient::broadcast_rats_message_to_validated_peers(const nlohmann::json& message, const std::string& exclude_peer_id) {
|
|
2389
|
+
int sent_count = 0;
|
|
2390
|
+
{
|
|
2391
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2392
|
+
for (const auto& pair : peers_) {
|
|
2393
|
+
const RatsPeer& peer = pair.second;
|
|
2394
|
+
// Don't send to excluded peer and only send to peers with completed handshake
|
|
2395
|
+
if ((!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) ||
|
|
2396
|
+
!peer.is_handshake_completed()) {
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
if (send_json_to_peer(peer.socket, message)) {
|
|
2401
|
+
sent_count++;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
return sent_count;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
// Specific message creation functions
|
|
2409
|
+
nlohmann::json RatsClient::create_peer_exchange_message(const RatsPeer& peer) {
|
|
2410
|
+
// Create peer exchange payload
|
|
2411
|
+
nlohmann::json payload;
|
|
2412
|
+
payload["ip"] = peer.ip;
|
|
2413
|
+
payload["port"] = peer.port;
|
|
2414
|
+
payload["peer_id"] = peer.peer_id;
|
|
2415
|
+
payload["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
|
|
2416
|
+
|
|
2417
|
+
// Create rats message
|
|
2418
|
+
return create_rats_message("peer", payload, peer.peer_id);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
|
|
2422
|
+
// Don't broadcast exchange messages for ourselves
|
|
2423
|
+
if (new_peer.peer_id.empty()) {
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// Create peer exchange message
|
|
2428
|
+
nlohmann::json message = create_peer_exchange_message(new_peer);
|
|
2429
|
+
|
|
2430
|
+
// Broadcast to all validated peers except the new peer
|
|
2431
|
+
int sent_count = broadcast_rats_message_to_validated_peers(message, new_peer.peer_id);
|
|
2432
|
+
|
|
2433
|
+
LOG_CLIENT_INFO("Broadcasted peer exchange message for " << new_peer.ip << ":" << new_peer.port
|
|
2434
|
+
<< " to " << sent_count << " peers");
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
// Peers request/response system implementation
|
|
2438
|
+
nlohmann::json RatsClient::create_peers_request_message(const std::string& sender_peer_id) {
|
|
2439
|
+
nlohmann::json payload;
|
|
2440
|
+
payload["max_peers"] = 5; // Request up to 5 peers
|
|
2441
|
+
payload["requester_info"] = {
|
|
2442
|
+
{"listen_port", listen_port_},
|
|
2443
|
+
{"peer_count", get_peer_count()}
|
|
2444
|
+
};
|
|
2445
|
+
|
|
2446
|
+
return create_rats_message("peers_request", payload, sender_peer_id);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
nlohmann::json RatsClient::create_peers_response_message(const std::vector<RatsPeer>& peers, const std::string& sender_peer_id) {
|
|
2450
|
+
nlohmann::json payload;
|
|
2451
|
+
nlohmann::json peers_array = nlohmann::json::array();
|
|
2452
|
+
|
|
2453
|
+
for (const auto& peer : peers) {
|
|
2454
|
+
nlohmann::json peer_info;
|
|
2455
|
+
peer_info["ip"] = peer.ip;
|
|
2456
|
+
peer_info["port"] = peer.port;
|
|
2457
|
+
peer_info["peer_id"] = peer.peer_id;
|
|
2458
|
+
peer_info["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
|
|
2459
|
+
peers_array.push_back(peer_info);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
payload["peers"] = peers_array;
|
|
2463
|
+
payload["total_peers"] = get_peer_count();
|
|
2464
|
+
|
|
2465
|
+
return create_rats_message("peers_response", payload, sender_peer_id);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
|
|
2469
|
+
void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
|
|
2470
|
+
try {
|
|
2471
|
+
int max_peers = payload.value("max_peers", 5);
|
|
2472
|
+
|
|
2473
|
+
LOG_CLIENT_INFO("Received peers request from " << peer_hash_id << " for up to " << max_peers << " peers");
|
|
2474
|
+
|
|
2475
|
+
// Get random peers excluding the requester
|
|
2476
|
+
std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_hash_id);
|
|
2477
|
+
|
|
2478
|
+
LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_hash_id);
|
|
2479
|
+
|
|
2480
|
+
// Create and send peers response
|
|
2481
|
+
nlohmann::json response_message = create_peers_response_message(random_peers, peer_hash_id);
|
|
2482
|
+
|
|
2483
|
+
if (!send_json_to_peer(socket, response_message)) {
|
|
2484
|
+
LOG_CLIENT_ERROR("Failed to send peers response to " << peer_hash_id);
|
|
2485
|
+
} else {
|
|
2486
|
+
LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_hash_id);
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
} catch (const nlohmann::json::exception& e) {
|
|
2490
|
+
LOG_CLIENT_ERROR("Failed to handle peers request message: " << e.what());
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
|
|
2495
|
+
try {
|
|
2496
|
+
nlohmann::json peers_array = payload.value("peers", nlohmann::json::array());
|
|
2497
|
+
int total_peers = payload.value("total_peers", 0);
|
|
2498
|
+
|
|
2499
|
+
LOG_CLIENT_INFO("Received peers response from " << peer_hash_id << " with " << peers_array.size()
|
|
2500
|
+
<< " peers (total: " << total_peers << ")");
|
|
2501
|
+
|
|
2502
|
+
// Process each peer in the response
|
|
2503
|
+
for (const auto& peer_info : peers_array) {
|
|
2504
|
+
std::string peer_ip = peer_info.value("ip", "");
|
|
2505
|
+
int peer_port = peer_info.value("port", 0);
|
|
2506
|
+
std::string peer_id = peer_info.value("peer_id", "");
|
|
2507
|
+
|
|
2508
|
+
if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
|
|
2509
|
+
LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_hash_id);
|
|
2510
|
+
continue;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
LOG_CLIENT_DEBUG("Processing peer from response: " << peer_ip << ":" << peer_port << " (peer_id: " << peer_id << ")");
|
|
2514
|
+
|
|
2515
|
+
// Check if we should ignore this peer (local interface)
|
|
2516
|
+
if (should_ignore_peer(peer_ip, peer_port)) {
|
|
2517
|
+
LOG_CLIENT_DEBUG("Ignoring peer from response " << peer_ip << ":" << peer_port << " - local interface address");
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// Check if we're already connected to this peer
|
|
2522
|
+
std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
|
|
2523
|
+
if (is_already_connected_to_address(normalized_peer_address)) {
|
|
2524
|
+
LOG_CLIENT_DEBUG("Already connected to peer from response " << normalized_peer_address);
|
|
2525
|
+
continue;
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// Check if peer limit is reached
|
|
2529
|
+
if (is_peer_limit_reached()) {
|
|
2530
|
+
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to peer from response " << peer_ip << ":" << peer_port);
|
|
2531
|
+
continue;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// Try to connect to the peer (non-blocking)
|
|
2535
|
+
LOG_CLIENT_INFO("Attempting to connect to peer from response: " << peer_ip << ":" << peer_port);
|
|
2536
|
+
add_managed_thread(std::thread([this, peer_ip, peer_port, peer_id]() {
|
|
2537
|
+
if (connect_to_peer(peer_ip, peer_port)) {
|
|
2538
|
+
LOG_CLIENT_INFO("Successfully connected to peer from response: " << peer_ip << ":" << peer_port);
|
|
2539
|
+
} else {
|
|
2540
|
+
LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << peer_ip << ":" << peer_port);
|
|
2541
|
+
}
|
|
2542
|
+
}), "peer-response-connect-" + peer_id.substr(0, 8));
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
} catch (const nlohmann::json::exception& e) {
|
|
2546
|
+
LOG_CLIENT_ERROR("Failed to handle peers response message: " << e.what());
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
void RatsClient::send_peers_request(socket_t socket, const std::string& our_peer_id) {
|
|
2551
|
+
nlohmann::json request_message = create_peers_request_message(our_peer_id);
|
|
2552
|
+
|
|
2553
|
+
if (send_json_to_peer(socket, request_message)) {
|
|
2554
|
+
LOG_CLIENT_INFO("Sent peers request to socket " << socket);
|
|
2555
|
+
} else {
|
|
2556
|
+
LOG_CLIENT_ERROR("Failed to send peers request to socket " << socket);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// =========================================================================
|
|
2561
|
+
// Statistics and Information
|
|
2562
|
+
// =========================================================================
|
|
2563
|
+
|
|
2564
|
+
nlohmann::json RatsClient::get_connection_statistics() const {
|
|
2565
|
+
nlohmann::json stats;
|
|
2566
|
+
|
|
2567
|
+
{
|
|
2568
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2569
|
+
stats["total_peers"] = peers_.size();
|
|
2570
|
+
stats["validated_peers"] = get_peer_count_unlocked();
|
|
2571
|
+
stats["max_peers"] = max_peers_;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
stats["running"] = is_running();
|
|
2575
|
+
stats["listen_port"] = listen_port_;
|
|
2576
|
+
stats["our_peer_id"] = get_our_peer_id();
|
|
2577
|
+
stats["encryption_enabled"] = is_encryption_enabled();
|
|
2578
|
+
stats["public_ip"] = get_public_ip();
|
|
2579
|
+
|
|
2580
|
+
// DHT statistics
|
|
2581
|
+
if (dht_client_ && dht_client_->is_running()) {
|
|
2582
|
+
stats["dht_running"] = true;
|
|
2583
|
+
stats["dht_routing_table_size"] = get_dht_routing_table_size();
|
|
2584
|
+
} else {
|
|
2585
|
+
stats["dht_running"] = false;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// mDNS statistics
|
|
2589
|
+
stats["mdns_running"] = is_mdns_running();
|
|
2590
|
+
|
|
2591
|
+
return stats;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
|
|
2595
|
+
// =========================================================================
|
|
2596
|
+
// Helper functions
|
|
2597
|
+
// =========================================================================
|
|
2598
|
+
|
|
2599
|
+
std::unique_ptr<RatsClient> create_rats_client(int listen_port) {
|
|
2600
|
+
auto client = std::make_unique<RatsClient>(listen_port, 10); // Default 10 max peers
|
|
2601
|
+
if (!client->start()) {
|
|
2602
|
+
return nullptr;
|
|
2603
|
+
}
|
|
2604
|
+
return client;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
// Version query functions
|
|
2608
|
+
const char* rats_get_library_version_string() {
|
|
2609
|
+
return librats::version::STRING;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
void rats_get_library_version(int* major, int* minor, int* patch, int* build) {
|
|
2613
|
+
if (major) *major = librats::version::MAJOR;
|
|
2614
|
+
if (minor) *minor = librats::version::MINOR;
|
|
2615
|
+
if (patch) *patch = librats::version::PATCH;
|
|
2616
|
+
if (build) *build = librats::version::BUILD;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
const char* rats_get_library_git_describe() {
|
|
2620
|
+
return librats::version::GIT_DESCRIBE;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
uint32_t rats_get_library_abi() {
|
|
2624
|
+
// ABI policy: MAJOR bumps on breaking changes; MINOR for additive; PATCH ignored in ABI id
|
|
2625
|
+
return (static_cast<uint32_t>(librats::version::MAJOR) << 16) |
|
|
2626
|
+
(static_cast<uint32_t>(librats::version::MINOR) << 8) |
|
|
2627
|
+
(static_cast<uint32_t>(librats::version::PATCH));
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
bool RatsClient::parse_address_string(const std::string& address_str, std::string& out_ip, int& out_port) {
|
|
2631
|
+
if (address_str.empty()) {
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
size_t colon_pos;
|
|
2636
|
+
if (address_str.front() == '[') {
|
|
2637
|
+
// IPv6 format: [ip]:port
|
|
2638
|
+
size_t bracket_end = address_str.find(']');
|
|
2639
|
+
if (bracket_end == std::string::npos || bracket_end < 2) { // Must be at least [a]
|
|
2640
|
+
return false;
|
|
2641
|
+
}
|
|
2642
|
+
out_ip = address_str.substr(1, bracket_end - 1);
|
|
2643
|
+
colon_pos = address_str.find(':', bracket_end);
|
|
2644
|
+
} else {
|
|
2645
|
+
// IPv4 or IPv6 without brackets
|
|
2646
|
+
colon_pos = address_str.find_last_of(':');
|
|
2647
|
+
if (colon_pos == std::string::npos || colon_pos == 0) {
|
|
2648
|
+
return false;
|
|
2649
|
+
}
|
|
2650
|
+
out_ip = address_str.substr(0, colon_pos);
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
if (colon_pos == std::string::npos || colon_pos + 1 >= address_str.length()) {
|
|
2654
|
+
return false;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
try {
|
|
2658
|
+
out_port = std::stoi(address_str.substr(colon_pos + 1));
|
|
2659
|
+
} catch (const std::exception&) {
|
|
2660
|
+
return false;
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
return !out_ip.empty() && out_port > 0 && out_port <= 65535;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
std::string get_box_separator() {
|
|
2667
|
+
return supports_unicode() ?
|
|
2668
|
+
"════════════════════════════════════════════════════════════════════" :
|
|
2669
|
+
"=====================================================================";
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
std::string get_box_vertical() {
|
|
2673
|
+
return supports_unicode() ? "│" : "|";
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
std::string get_checkmark() {
|
|
2677
|
+
return supports_unicode() ? "✓" : "[*]";
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
void RatsClient::log_handshake_completion_unlocked(const RatsPeer& peer) {
|
|
2681
|
+
// Calculate connection duration
|
|
2682
|
+
auto now = std::chrono::steady_clock::now();
|
|
2683
|
+
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - peer.connected_at);
|
|
2684
|
+
|
|
2685
|
+
// Get current peer count (assumes peers_mutex_ is already locked)
|
|
2686
|
+
int current_peer_count = get_peer_count_unlocked();
|
|
2687
|
+
|
|
2688
|
+
// Create visually appealing log output
|
|
2689
|
+
std::string connection_type = peer.is_outgoing ? "OUTGOING" : "INCOMING";
|
|
2690
|
+
std::string separator = get_box_separator();
|
|
2691
|
+
std::string vertical = get_box_vertical();
|
|
2692
|
+
std::string checkmark = get_checkmark();
|
|
2693
|
+
|
|
2694
|
+
LOG_CLIENT_INFO("");
|
|
2695
|
+
LOG_CLIENT_INFO(separator);
|
|
2696
|
+
LOG_CLIENT_INFO(checkmark << " HANDSHAKE COMPLETED - NEW PEER CONNECTED");
|
|
2697
|
+
LOG_CLIENT_INFO(separator);
|
|
2698
|
+
LOG_CLIENT_INFO(vertical << " Peer ID : " << peer.peer_id);
|
|
2699
|
+
LOG_CLIENT_INFO(vertical << " Address : " << peer.ip << ":" << peer.port);
|
|
2700
|
+
LOG_CLIENT_INFO(vertical << " Connection : " << connection_type);
|
|
2701
|
+
LOG_CLIENT_INFO(vertical << " Protocol Ver. : " << peer.version);
|
|
2702
|
+
LOG_CLIENT_INFO(vertical << " Socket : " << peer.socket);
|
|
2703
|
+
LOG_CLIENT_INFO(vertical << " Duration : " << duration.count() << "ms");
|
|
2704
|
+
LOG_CLIENT_INFO(vertical << " Network Peers : " << current_peer_count << "/" << max_peers_);
|
|
2705
|
+
|
|
2706
|
+
LOG_CLIENT_INFO(separator);
|
|
2707
|
+
LOG_CLIENT_INFO("");
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
void RatsClient::log_handshake_completion(const RatsPeer& peer) {
|
|
2711
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2712
|
+
log_handshake_completion_unlocked(peer);
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
} // namespace librats
|