librats 0.7.1 → 0.8.0
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/native-src/CMakeLists.txt +15 -2
- package/native-src/src/bt_client.cpp +18 -5
- package/native-src/src/bt_client.h +3 -1
- package/native-src/src/bt_network.cpp +297 -184
- package/native-src/src/bt_network.h +25 -5
- package/native-src/src/bt_peer_connection.cpp +11 -0
- package/native-src/src/bt_peer_connection.h +6 -0
- package/native-src/src/bt_torrent.cpp +17 -6
- package/native-src/src/bt_torrent.h +1 -0
- package/native-src/src/bt_types.h +287 -20
- package/native-src/src/dht.cpp +26 -8
- package/native-src/src/dht.h +1 -1
- package/native-src/src/ice.cpp +1 -1
- package/native-src/src/io_poller.cpp +910 -0
- package/native-src/src/io_poller.h +138 -0
- package/native-src/src/krpc.cpp +8 -1
- package/native-src/src/krpc.h +3 -2
- package/native-src/src/librats.cpp +824 -1213
- package/native-src/src/librats.h +167 -54
- package/native-src/src/librats_bittorrent.cpp +4 -7
- package/native-src/src/librats_c.cpp +13 -1
- package/native-src/src/librats_c.h +2 -0
- package/native-src/src/librats_discovery.cpp +309 -0
- package/native-src/src/librats_encryption.cpp +130 -283
- package/native-src/src/librats_file_transfer.cpp +2 -6
- package/native-src/src/librats_gossipsub.cpp +1 -5
- package/native-src/src/librats_ice.cpp +1 -1
- package/native-src/src/librats_log_macros.h +36 -0
- package/native-src/src/librats_logging.cpp +10 -7
- package/native-src/src/librats_mdns.cpp +1 -11
- package/native-src/src/librats_persistence.cpp +1 -11
- package/native-src/src/librats_reconnection.cpp +2 -13
- package/native-src/src/librats_statistic.cpp +97 -0
- package/native-src/src/logger.h +40 -4
- package/native-src/src/network_utils.cpp +175 -373
- package/native-src/src/network_utils.h +2 -113
- package/native-src/src/socket.cpp +485 -883
- package/native-src/src/socket.h +36 -130
- package/native-src/src/stun.cpp +3 -5
- package/native-src/src/tracker.cpp +3 -2
- package/native-src/src/turn.cpp +4 -6
- package/package.json +1 -1
|
@@ -1,42 +1,15 @@
|
|
|
1
1
|
#include "librats.h"
|
|
2
|
-
#include "gossipsub.h"
|
|
3
|
-
#include "sha1.h"
|
|
4
2
|
#include "os.h"
|
|
5
3
|
#include "network_utils.h"
|
|
6
|
-
#include "fs.h"
|
|
7
|
-
#include "json.hpp" // nlohmann::json
|
|
8
4
|
#include "version.h"
|
|
9
|
-
#include <iostream>
|
|
10
5
|
#include <algorithm>
|
|
11
|
-
#include <
|
|
12
|
-
#include <memory>
|
|
6
|
+
#include <array>
|
|
13
7
|
#include <random>
|
|
14
8
|
#include <sstream>
|
|
15
9
|
#include <iomanip>
|
|
16
|
-
#include <stdexcept>
|
|
17
10
|
#include <string_view>
|
|
18
11
|
|
|
19
|
-
#
|
|
20
|
-
#define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", "[pointer: " << this << "] " << message)
|
|
21
|
-
#define LOG_CLIENT_INFO(message) LOG_INFO("client", "[pointer: " << this << "] " << message)
|
|
22
|
-
#define LOG_CLIENT_WARN(message) LOG_WARN("client", "[pointer: " << this << "] " << message)
|
|
23
|
-
#define LOG_CLIENT_ERROR(message) LOG_ERROR("client", "[pointer: " << this << "] " << message)
|
|
24
|
-
|
|
25
|
-
#define LOG_SERVER_DEBUG(message) LOG_DEBUG("server", "[pointer: " << this << "] " << message)
|
|
26
|
-
#define LOG_SERVER_INFO(message) LOG_INFO("server", "[pointer: " << this << "] " << message)
|
|
27
|
-
#define LOG_SERVER_WARN(message) LOG_WARN("server", "[pointer: " << this << "] " << message)
|
|
28
|
-
#define LOG_SERVER_ERROR(message) LOG_ERROR("server", "[pointer: " << this << "] " << message)
|
|
29
|
-
#else
|
|
30
|
-
#define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", message)
|
|
31
|
-
#define LOG_CLIENT_INFO(message) LOG_INFO("client", message)
|
|
32
|
-
#define LOG_CLIENT_WARN(message) LOG_WARN("client", message)
|
|
33
|
-
#define LOG_CLIENT_ERROR(message) LOG_ERROR("client", message)
|
|
34
|
-
|
|
35
|
-
#define LOG_SERVER_DEBUG(message) LOG_DEBUG("server", message)
|
|
36
|
-
#define LOG_SERVER_INFO(message) LOG_INFO("server", message)
|
|
37
|
-
#define LOG_SERVER_WARN(message) LOG_WARN("server", message)
|
|
38
|
-
#define LOG_SERVER_ERROR(message) LOG_ERROR("server", message)
|
|
39
|
-
#endif
|
|
12
|
+
#include "librats_log_macros.h"
|
|
40
13
|
|
|
41
14
|
namespace librats {
|
|
42
15
|
|
|
@@ -112,7 +85,6 @@ void RatsClient::destroy_modules() {
|
|
|
112
85
|
// Core Lifecycle Management
|
|
113
86
|
// =========================================================================
|
|
114
87
|
|
|
115
|
-
|
|
116
88
|
bool RatsClient::start() {
|
|
117
89
|
if (running_.load()) {
|
|
118
90
|
LOG_CLIENT_WARN("RatsClient is already running");
|
|
@@ -146,16 +118,27 @@ bool RatsClient::start() {
|
|
|
146
118
|
initialize_local_addresses();
|
|
147
119
|
|
|
148
120
|
// Create dual-stack server socket (supports both IPv4 and IPv6)
|
|
149
|
-
|
|
121
|
+
server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
|
|
122
|
+
// Fallback to free port
|
|
123
|
+
if (!is_valid_socket(server_socket_) && listen_port_ > 0) {
|
|
124
|
+
// Requested port is not available, try ephemeral port as fallback
|
|
125
|
+
int original_port = listen_port_;
|
|
126
|
+
LOG_CLIENT_WARN("TCP port " << original_port << " is not available, falling back to ephemeral port");
|
|
127
|
+
server_socket_ = create_tcp_server(0, 5, bind_address_);
|
|
128
|
+
if (is_valid_socket(server_socket_)) {
|
|
129
|
+
listen_port_ = get_bound_port(server_socket_);
|
|
130
|
+
LOG_CLIENT_INFO("Fell back from port " << original_port << " to ephemeral port " << listen_port_);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
150
133
|
if (!is_valid_socket(server_socket_)) {
|
|
151
|
-
LOG_CLIENT_ERROR("Failed to create
|
|
134
|
+
LOG_CLIENT_ERROR("Failed to create server socket on port " << listen_port_ <<
|
|
152
135
|
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
153
136
|
return false;
|
|
154
137
|
}
|
|
155
138
|
|
|
156
139
|
// Update listen_port_ with actual bound port if ephemeral port was requested
|
|
157
140
|
if (listen_port_ == 0) {
|
|
158
|
-
listen_port_ =
|
|
141
|
+
listen_port_ = get_bound_port(server_socket_);
|
|
159
142
|
if (listen_port_ == 0) {
|
|
160
143
|
LOG_CLIENT_WARN("Failed to get actual bound port - using port 0");
|
|
161
144
|
} else {
|
|
@@ -163,12 +146,20 @@ bool RatsClient::start() {
|
|
|
163
146
|
}
|
|
164
147
|
}
|
|
165
148
|
|
|
149
|
+
// Set server socket to non-blocking for the IO poller
|
|
150
|
+
set_socket_nonblocking(server_socket_);
|
|
151
|
+
|
|
152
|
+
// Create platform-optimal IO poller and register server socket
|
|
153
|
+
poller_ = IOPoller::create();
|
|
154
|
+
poller_->add(server_socket_, PollIn);
|
|
155
|
+
LOG_CLIENT_INFO("IO poller backend: " << poller_->name());
|
|
156
|
+
|
|
166
157
|
running_.store(true);
|
|
167
158
|
|
|
168
|
-
// Start
|
|
169
|
-
|
|
159
|
+
// Start IO thread (single-threaded event loop for all sockets)
|
|
160
|
+
io_thread_ = std::thread(&RatsClient::io_loop, this);
|
|
170
161
|
|
|
171
|
-
// Start management thread
|
|
162
|
+
// Start management thread (handshake timeouts, reconnection, thread cleanup)
|
|
172
163
|
management_thread_ = std::thread(&RatsClient::management_loop, this);
|
|
173
164
|
|
|
174
165
|
// Start GossipSub
|
|
@@ -181,7 +172,7 @@ bool RatsClient::start() {
|
|
|
181
172
|
// Attempt to reconnect to saved peers
|
|
182
173
|
add_managed_thread(std::thread([this]() {
|
|
183
174
|
// Give the server some time to fully initialize
|
|
184
|
-
std::this_thread::sleep_for(std::chrono::milliseconds(
|
|
175
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(PEER_RECONNECT_DELAY_MS));
|
|
185
176
|
int reconnect_attempts = load_and_reconnect_peers();
|
|
186
177
|
if (reconnect_attempts > 0) {
|
|
187
178
|
LOG_CLIENT_INFO("Attempted to reconnect to " << reconnect_attempts << " saved peers");
|
|
@@ -189,7 +180,7 @@ bool RatsClient::start() {
|
|
|
189
180
|
|
|
190
181
|
// Also attempt to reconnect to historical peers if not at peer limit
|
|
191
182
|
if (!is_peer_limit_reached()) {
|
|
192
|
-
std::this_thread::sleep_for(std::chrono::milliseconds(
|
|
183
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(HISTORICAL_RECONNECT_DELAY_MS));
|
|
193
184
|
int historical_attempts = load_and_reconnect_historical_peers();
|
|
194
185
|
if (historical_attempts > 0) {
|
|
195
186
|
LOG_CLIENT_INFO("Attempted to reconnect to " << historical_attempts << " historical peers");
|
|
@@ -211,8 +202,7 @@ void RatsClient::stop() {
|
|
|
211
202
|
if (gossipsub_) {
|
|
212
203
|
gossipsub_->stop();
|
|
213
204
|
}
|
|
214
|
-
|
|
215
|
-
|
|
205
|
+
|
|
216
206
|
// Trigger immediate shutdown of all background threads
|
|
217
207
|
shutdown_all_threads();
|
|
218
208
|
|
|
@@ -235,25 +225,25 @@ void RatsClient::stop() {
|
|
|
235
225
|
manual_disconnect_peers_.clear();
|
|
236
226
|
}
|
|
237
227
|
|
|
238
|
-
// Close all peer connections
|
|
228
|
+
// Close all peer connections and remove from poller
|
|
239
229
|
{
|
|
240
230
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
241
231
|
LOG_CLIENT_INFO("Closing " << peers_.size() << " peer connections");
|
|
242
232
|
for (const auto& pair : peers_) {
|
|
243
233
|
const RatsPeer& peer = pair.second;
|
|
234
|
+
if (poller_) poller_->remove(peer.socket);
|
|
244
235
|
close_socket(peer.socket, true);
|
|
245
236
|
}
|
|
246
237
|
peers_.clear();
|
|
247
238
|
socket_to_peer_id_.clear();
|
|
248
239
|
address_to_peer_id_.clear();
|
|
240
|
+
validated_peer_count_.store(0, std::memory_order_relaxed);
|
|
249
241
|
}
|
|
250
242
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
LOG_CLIENT_DEBUG("Waiting for server thread to finish");
|
|
256
|
-
server_thread_.join();
|
|
243
|
+
// Wait for IO thread to finish
|
|
244
|
+
if (io_thread_.joinable()) {
|
|
245
|
+
LOG_CLIENT_DEBUG("Waiting for IO thread to finish");
|
|
246
|
+
io_thread_.join();
|
|
257
247
|
}
|
|
258
248
|
|
|
259
249
|
// Wait for management thread to finish
|
|
@@ -262,11 +252,14 @@ void RatsClient::stop() {
|
|
|
262
252
|
management_thread_.join();
|
|
263
253
|
}
|
|
264
254
|
|
|
255
|
+
// Destroy poller after threads have stopped
|
|
256
|
+
poller_.reset();
|
|
257
|
+
|
|
265
258
|
// Join all managed threads for graceful cleanup
|
|
266
259
|
join_all_active_threads();
|
|
267
260
|
|
|
268
261
|
cleanup_socket_library();
|
|
269
|
-
|
|
262
|
+
|
|
270
263
|
// Save configuration before stopping
|
|
271
264
|
save_configuration();
|
|
272
265
|
|
|
@@ -278,7 +271,7 @@ void RatsClient::shutdown_all_threads() {
|
|
|
278
271
|
|
|
279
272
|
// Signal all threads to stop
|
|
280
273
|
running_.store(false);
|
|
281
|
-
|
|
274
|
+
|
|
282
275
|
// Call parent class to handle thread management shutdown
|
|
283
276
|
ThreadManager::shutdown_all_threads();
|
|
284
277
|
}
|
|
@@ -300,456 +293,564 @@ std::string RatsClient::get_bind_address() const {
|
|
|
300
293
|
}
|
|
301
294
|
|
|
302
295
|
// =========================================================================
|
|
303
|
-
//
|
|
296
|
+
// Async I/O – single-threaded event loop
|
|
304
297
|
// =========================================================================
|
|
305
298
|
|
|
306
|
-
void RatsClient::
|
|
307
|
-
|
|
299
|
+
void RatsClient::io_loop() {
|
|
300
|
+
LOG_CLIENT_INFO("IO loop started (backend: " << poller_->name() << ")");
|
|
301
|
+
|
|
302
|
+
static constexpr int MAX_EVENTS = 256;
|
|
303
|
+
PollResult results[MAX_EVENTS];
|
|
308
304
|
|
|
309
305
|
while (running_.load()) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
306
|
+
int n = poller_->wait(results, MAX_EVENTS, IO_POLL_TIMEOUT_MS);
|
|
307
|
+
|
|
308
|
+
if (n < 0) {
|
|
309
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Collect sockets to disconnect (defer to avoid iterator issues)
|
|
314
|
+
std::vector<socket_t> to_disconnect;
|
|
315
|
+
|
|
316
|
+
for (int i = 0; i < n; ++i) {
|
|
317
|
+
socket_t fd = results[i].fd;
|
|
318
|
+
uint32_t events = results[i].events;
|
|
319
|
+
|
|
320
|
+
// Server socket – accept incoming connections
|
|
321
|
+
if (fd == server_socket_) {
|
|
322
|
+
if (events & PollIn) accept_incoming();
|
|
323
|
+
continue;
|
|
314
324
|
}
|
|
315
|
-
|
|
325
|
+
|
|
326
|
+
bool should_close = false;
|
|
327
|
+
|
|
328
|
+
if (events & (PollErr | PollHup)) {
|
|
329
|
+
should_close = true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!should_close && (events & PollIn)) {
|
|
333
|
+
should_close = handle_readable(fd);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!should_close && (events & PollOut)) {
|
|
337
|
+
should_close = handle_writable(fd);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (should_close) {
|
|
341
|
+
to_disconnect.push_back(fd);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Handle disconnections outside the event loop
|
|
346
|
+
for (socket_t fd : to_disconnect) {
|
|
347
|
+
handle_disconnect(fd);
|
|
316
348
|
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
LOG_CLIENT_INFO("IO loop ended");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
// accept_incoming – non-blocking accept of new TCP connections
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
void RatsClient::accept_incoming() {
|
|
358
|
+
// Accept as many pending connections as possible (level-triggered)
|
|
359
|
+
while (true) {
|
|
360
|
+
socket_t client = accept_client(server_socket_);
|
|
361
|
+
if (!is_valid_socket(client)) break;
|
|
317
362
|
|
|
318
|
-
|
|
319
|
-
std::string peer_address = get_peer_address(client_socket);
|
|
363
|
+
std::string peer_address = get_peer_address(client);
|
|
320
364
|
if (peer_address.empty()) {
|
|
321
|
-
|
|
322
|
-
close_socket(client_socket);
|
|
365
|
+
close_socket(client);
|
|
323
366
|
continue;
|
|
324
367
|
}
|
|
325
368
|
|
|
326
|
-
// Parse IP and port from peer_address
|
|
327
369
|
std::string ip;
|
|
328
370
|
int port = 0;
|
|
329
371
|
if (!parse_address_string(peer_address, ip, port)) {
|
|
330
|
-
|
|
331
|
-
close_socket(client_socket);
|
|
372
|
+
close_socket(client);
|
|
332
373
|
continue;
|
|
333
374
|
}
|
|
334
375
|
|
|
335
|
-
std::string
|
|
376
|
+
std::string normalized = normalize_peer_address(ip, port);
|
|
336
377
|
|
|
337
|
-
// Check if peer limit is reached
|
|
338
378
|
if (is_peer_limit_reached()) {
|
|
339
|
-
LOG_SERVER_INFO("Peer limit reached
|
|
340
|
-
close_socket(
|
|
379
|
+
LOG_SERVER_INFO("Peer limit reached, rejecting " << normalized);
|
|
380
|
+
close_socket(client);
|
|
341
381
|
continue;
|
|
342
382
|
}
|
|
343
383
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
close_socket(client_socket);
|
|
384
|
+
if (is_already_connected_to_address(normalized)) {
|
|
385
|
+
LOG_SERVER_DEBUG("Duplicate connection from " << normalized);
|
|
386
|
+
close_socket(client);
|
|
348
387
|
continue;
|
|
349
388
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// Generate unique hash ID for this incoming client
|
|
353
|
-
std::string connection_info = "incoming_from_" + peer_address;
|
|
354
|
-
std::string peer_hash_id = generate_peer_hash_id(client_socket, connection_info); // Temporary hash ID (real hash ID will be set after handshake)
|
|
355
389
|
|
|
356
|
-
//
|
|
390
|
+
// Make the new socket non-blocking and register with poller
|
|
391
|
+
set_socket_nonblocking(client);
|
|
392
|
+
|
|
393
|
+
std::string initial_id = generate_temporary_peer_id(client, "incoming_from_" + peer_address);
|
|
394
|
+
|
|
357
395
|
{
|
|
358
396
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
359
|
-
RatsPeer new_peer(
|
|
397
|
+
RatsPeer new_peer(initial_id, ip, port, client, normalized, false);
|
|
360
398
|
new_peer.encryption_enabled = is_encryption_enabled();
|
|
361
399
|
add_peer_unlocked(new_peer);
|
|
362
400
|
}
|
|
363
401
|
|
|
364
|
-
|
|
365
|
-
LOG_SERVER_DEBUG("Starting thread for client " << peer_hash_id << " from " << peer_address);
|
|
366
|
-
add_managed_thread(std::thread(&RatsClient::handle_client, this, client_socket, peer_hash_id),
|
|
367
|
-
"client-handler-" + peer_hash_id.substr(0, 8));
|
|
402
|
+
poller_add(client, PollIn);
|
|
368
403
|
|
|
369
|
-
|
|
404
|
+
LOG_SERVER_INFO("Accepted incoming connection from " << normalized << " (id: " << initial_id.substr(0, 8) << "…)");
|
|
370
405
|
}
|
|
371
|
-
|
|
372
|
-
LOG_SERVER_INFO("Server loop ended");
|
|
373
406
|
}
|
|
374
407
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Process reconnection queue
|
|
391
|
-
try {
|
|
392
|
-
process_reconnect_queue();
|
|
393
|
-
} catch (const std::exception& e) {
|
|
394
|
-
LOG_CLIENT_ERROR("Exception during reconnect queue processing: " << e.what());
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Periodically cleanup finished threads (every 30 seconds)
|
|
398
|
-
auto now = std::chrono::steady_clock::now();
|
|
399
|
-
if (now - last_thread_cleanup >= thread_cleanup_interval) {
|
|
400
|
-
try {
|
|
401
|
-
cleanup_finished_threads();
|
|
402
|
-
LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
|
|
403
|
-
} catch (const std::exception& e) {
|
|
404
|
-
LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
|
|
405
|
-
}
|
|
406
|
-
last_thread_cleanup = now;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
LOG_CLIENT_INFO("Management loop ended");
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
void RatsClient::handle_client(socket_t client_socket, const std::string& peer_hash_id) {
|
|
415
|
-
LOG_CLIENT_INFO("Started handling client: " << peer_hash_id);
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
// handle_readable – drain kernel buffer, parse length-prefixed frames
|
|
410
|
+
// Returns true if the peer should be disconnected.
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
bool RatsClient::handle_readable(socket_t socket) {
|
|
413
|
+
// ── Phase 1: drain data & extract complete frames under lock ──────────
|
|
414
|
+
struct PendingFrame {
|
|
415
|
+
std::vector<uint8_t> data;
|
|
416
|
+
RatsPeer::HandshakeState state;
|
|
417
|
+
std::string peer_id;
|
|
418
|
+
bool noise_encrypted;
|
|
419
|
+
std::shared_ptr<rats::NoiseCipherState> recv_cipher;
|
|
420
|
+
};
|
|
416
421
|
|
|
417
|
-
|
|
418
|
-
bool
|
|
419
|
-
bool
|
|
420
|
-
|
|
421
|
-
bool is_outgoing = false;
|
|
422
|
-
auto last_timeout_check = std::chrono::steady_clock::now();
|
|
422
|
+
std::vector<PendingFrame> frames;
|
|
423
|
+
bool peer_closed = false;
|
|
424
|
+
bool need_post_handshake = false;
|
|
425
|
+
RatsPeer peer_copy_for_post_handshake;
|
|
423
426
|
|
|
424
427
|
{
|
|
425
428
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
426
|
-
auto
|
|
427
|
-
if (
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
430
|
+
if (peer_it == peers_.end()) return true;
|
|
431
|
+
|
|
432
|
+
RatsPeer& peer = peer_it->second;
|
|
433
|
+
auto& recv_buf = peer.io_.recv_buffer;
|
|
434
|
+
|
|
435
|
+
// Non-blocking recv loop
|
|
436
|
+
while (true) {
|
|
437
|
+
recv_buf.ensure_space(16384);
|
|
438
|
+
int bytes = ::recv(socket,
|
|
439
|
+
reinterpret_cast<char*>(recv_buf.write_ptr()),
|
|
440
|
+
static_cast<int>(recv_buf.write_space()), 0);
|
|
441
|
+
|
|
442
|
+
if (bytes > 0) {
|
|
443
|
+
recv_buf.received(static_cast<size_t>(bytes));
|
|
444
|
+
continue;
|
|
431
445
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
close_socket(client_socket);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// ===== MAIN LOOP =====
|
|
447
|
-
while (running_.load()) {
|
|
448
|
-
|
|
449
|
-
// ----- 1. RECEIVE DATA -----
|
|
450
|
-
LOG_CLIENT_DEBUG("Receiving data from socket " << client_socket);
|
|
451
|
-
std::vector<uint8_t> received_bytes = receive_tcp_message_framed(client_socket);
|
|
452
|
-
|
|
453
|
-
if (received_bytes.empty()) {
|
|
454
|
-
break; // Connection closed or error
|
|
446
|
+
if (bytes == 0) { peer_closed = true; break; }
|
|
447
|
+
|
|
448
|
+
#ifdef _WIN32
|
|
449
|
+
if (WSAGetLastError() == WSAEWOULDBLOCK) break;
|
|
450
|
+
#else
|
|
451
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
|
|
452
|
+
#endif
|
|
453
|
+
peer_closed = true;
|
|
454
|
+
break;
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
-
|
|
458
|
-
// Use vector directly to avoid unnecessary string conversions
|
|
459
|
-
std::vector<uint8_t> data;
|
|
457
|
+
if (peer_closed && recv_buf.empty()) return true;
|
|
460
458
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
459
|
+
// Parse length-prefixed frames: [4-byte network-order length][payload]
|
|
460
|
+
while (recv_buf.size() >= 4) {
|
|
461
|
+
uint32_t net_len;
|
|
462
|
+
memcpy(&net_len, recv_buf.data(), 4);
|
|
463
|
+
uint32_t msg_len = ntohl(net_len);
|
|
464
464
|
|
|
465
|
-
{
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (sock_it != socket_to_peer_id_.end()) {
|
|
469
|
-
auto peer_it = peers_.find(sock_it->second);
|
|
470
|
-
if (peer_it != peers_.end() && peer_it->second.is_noise_encrypted()) {
|
|
471
|
-
recv_cipher = peer_it->second.recv_cipher.get();
|
|
472
|
-
current_peer_id = peer_it->second.peer_id;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
465
|
+
if (msg_len > MAX_FRAME_SIZE) {
|
|
466
|
+
LOG_CLIENT_ERROR("Frame too large (" << msg_len << " bytes) from " << peer.peer_id);
|
|
467
|
+
return true;
|
|
475
468
|
}
|
|
469
|
+
if (recv_buf.size() < 4 + msg_len) break; // incomplete frame
|
|
476
470
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
std::vector<uint8_t> plaintext(received_bytes.size());
|
|
484
|
-
size_t pt_len = recv_cipher->decrypt_with_ad(
|
|
485
|
-
nullptr, 0,
|
|
486
|
-
received_bytes.data(), received_bytes.size(),
|
|
487
|
-
plaintext.data()
|
|
488
|
-
);
|
|
471
|
+
// Handle Noise handshake messages inline (fast crypto, no callbacks)
|
|
472
|
+
if (peer.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
|
|
473
|
+
if (!handle_noise_frame(peer)) return true;
|
|
474
|
+
recv_buf.consume(4 + msg_len);
|
|
489
475
|
|
|
490
|
-
if
|
|
491
|
-
|
|
492
|
-
|
|
476
|
+
// Check if Noise just completed
|
|
477
|
+
if (peer.handshake_state == RatsPeer::HandshakeState::COMPLETED) {
|
|
478
|
+
need_post_handshake = true;
|
|
479
|
+
peer_copy_for_post_handshake = peer;
|
|
493
480
|
}
|
|
494
|
-
|
|
495
|
-
plaintext.resize(pt_len);
|
|
496
|
-
data = std::move(plaintext);
|
|
497
|
-
LOG_CLIENT_DEBUG("Decrypted message from " << current_peer_id << " (" << pt_len << " bytes)");
|
|
498
|
-
} else {
|
|
499
|
-
data = std::move(received_bytes);
|
|
500
|
-
}
|
|
501
|
-
} else {
|
|
502
|
-
data = std::move(received_bytes);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Log first 50 bytes for debugging
|
|
506
|
-
size_t log_len = (std::min)(data.size(), static_cast<size_t>(50));
|
|
507
|
-
LOG_CLIENT_DEBUG("Received data from " << peer_hash_id << ": " << std::string(data.begin(), data.begin() + log_len) << (data.size() > 50 ? "..." : ""));
|
|
508
|
-
|
|
509
|
-
// ----- 3. CONNECTION STATE CHECK (during handshake phase only) -----
|
|
510
|
-
if (!handshake_completed) {
|
|
511
|
-
auto now = std::chrono::steady_clock::now();
|
|
512
|
-
if (now - last_timeout_check >= std::chrono::seconds(1)) {
|
|
513
|
-
check_handshake_timeouts();
|
|
514
|
-
last_timeout_check = now;
|
|
481
|
+
continue;
|
|
515
482
|
}
|
|
516
483
|
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (peer_it != peers_.end() && peer_it->second.is_handshake_failed()) {
|
|
525
|
-
handshake_failed = true;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
484
|
+
// Snapshot state for out-of-lock processing
|
|
485
|
+
PendingFrame pf;
|
|
486
|
+
pf.data.assign(recv_buf.data() + 4, recv_buf.data() + 4 + msg_len);
|
|
487
|
+
pf.state = peer.handshake_state;
|
|
488
|
+
pf.peer_id = peer.peer_id;
|
|
489
|
+
pf.noise_encrypted = peer.is_noise_encrypted();
|
|
490
|
+
if (pf.noise_encrypted) pf.recv_cipher = peer.recv_cipher;
|
|
529
491
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
492
|
+
frames.push_back(std::move(pf));
|
|
493
|
+
recv_buf.consume(4 + msg_len);
|
|
534
494
|
}
|
|
535
495
|
|
|
536
|
-
//
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
496
|
+
// Compact if >32KB wasted at front
|
|
497
|
+
if (recv_buf.front_waste() > 32768) recv_buf.normalize();
|
|
498
|
+
}
|
|
499
|
+
// ── peers_mutex_ released ────────────────────────────────────────────
|
|
500
|
+
|
|
501
|
+
// Deferred post-handshake completion (includes callbacks – must be outside mutex)
|
|
502
|
+
if (need_post_handshake) {
|
|
503
|
+
handle_post_handshake_completion(socket, peer_copy_for_post_handshake);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ── Phase 2: process frames outside lock ─────────────────────────────
|
|
507
|
+
for (auto& pf : frames) {
|
|
508
|
+
// Handshake phase – RATS JSON handshake messages
|
|
509
|
+
if (pf.state != RatsPeer::HandshakeState::COMPLETED) {
|
|
510
|
+
if (is_handshake_message(pf.data)) {
|
|
511
|
+
if (!handle_handshake_message(socket, pf.peer_id, pf.data)) {
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
550
514
|
|
|
515
|
+
// Check if handshake just completed and handle Noise / post-handshake
|
|
516
|
+
bool do_post_handshake = false;
|
|
517
|
+
RatsPeer post_hs_copy;
|
|
551
518
|
{
|
|
552
519
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
553
|
-
auto
|
|
554
|
-
if (
|
|
555
|
-
|
|
556
|
-
if (peer_it != peers_.end()) {
|
|
557
|
-
// Check if rats handshake completed (either COMPLETED or NOISE_PENDING)
|
|
558
|
-
if (peer_it->second.is_handshake_completed()) {
|
|
559
|
-
rats_handshake_done = true;
|
|
560
|
-
peer_copy = peer_it->second;
|
|
561
|
-
} else if (peer_it->second.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
|
|
562
|
-
rats_handshake_done = true;
|
|
563
|
-
needs_noise_handshake = true;
|
|
564
|
-
peer_copy = peer_it->second;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// ----- POST-HANDSHAKE ACTIONS -----
|
|
571
|
-
if (rats_handshake_done) {
|
|
572
|
-
LOG_CLIENT_INFO("Rats handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
|
|
573
|
-
|
|
574
|
-
// Remove from reconnection queue if present (successful connection)
|
|
575
|
-
remove_from_reconnect_queue(peer_copy.peer_id);
|
|
576
|
-
|
|
577
|
-
// Noise encryption handshake - only if BOTH sides support encryption
|
|
578
|
-
// peer_copy.encryption_enabled is already negotiated in handle_handshake_message()
|
|
579
|
-
if (needs_noise_handshake) {
|
|
580
|
-
LOG_CLIENT_INFO("Starting Noise handshake for peer " << peer_copy.peer_id);
|
|
581
|
-
if (perform_noise_handshake(client_socket, peer_copy.peer_id, peer_copy.is_outgoing)) {
|
|
582
|
-
noise_handshake_done = true;
|
|
583
|
-
LOG_CLIENT_INFO("Noise handshake successful for peer " << peer_copy.peer_id);
|
|
584
|
-
|
|
585
|
-
// Update state to COMPLETED after successful Noise handshake
|
|
586
|
-
{
|
|
587
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
588
|
-
auto sock_it = socket_to_peer_id_.find(client_socket);
|
|
589
|
-
if (sock_it != socket_to_peer_id_.end()) {
|
|
590
|
-
auto peer_it = peers_.find(sock_it->second);
|
|
591
|
-
if (peer_it != peers_.end()) {
|
|
592
|
-
peer_it->second.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
593
|
-
log_handshake_completion_unlocked(peer_it->second);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
} else {
|
|
598
|
-
LOG_CLIENT_ERROR("Noise handshake failed for peer " << peer_copy.peer_id);
|
|
599
|
-
// Connection will be closed due to failed Noise handshake
|
|
600
|
-
break;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
handshake_completed = true;
|
|
520
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
521
|
+
if (peer_it == peers_.end()) return true;
|
|
522
|
+
RatsPeer& peer = peer_it->second;
|
|
605
523
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
// GossipSub notification
|
|
612
|
-
if (gossipsub_) {
|
|
613
|
-
gossipsub_->handle_peer_connected(peer_copy.peer_id);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
#ifdef RATS_STORAGE
|
|
617
|
-
// Storage manager notification
|
|
618
|
-
if (storage_manager_) {
|
|
619
|
-
storage_manager_->on_peer_connected(peer_copy.peer_id);
|
|
620
|
-
}
|
|
621
|
-
#endif
|
|
622
|
-
|
|
623
|
-
// Peer exchange broadcast
|
|
624
|
-
broadcast_peer_exchange_message(peer_copy);
|
|
625
|
-
|
|
626
|
-
// Request peers from newly connected peer (outgoing only)
|
|
627
|
-
if (peer_copy.is_outgoing) {
|
|
628
|
-
send_peers_request(client_socket, peer_copy.peer_id);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Save configuration
|
|
632
|
-
if (running_.load()) {
|
|
633
|
-
add_managed_thread(std::thread([this]() {
|
|
634
|
-
if (running_.load()) {
|
|
635
|
-
save_configuration();
|
|
636
|
-
}
|
|
637
|
-
}), "config-save");
|
|
524
|
+
if (peer.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
|
|
525
|
+
start_noise_handshake_async(peer);
|
|
526
|
+
} else if (peer.handshake_state == RatsPeer::HandshakeState::COMPLETED) {
|
|
527
|
+
post_hs_copy = peer;
|
|
528
|
+
do_post_handshake = true;
|
|
638
529
|
}
|
|
639
530
|
}
|
|
531
|
+
// peers_mutex_ released – safe to invoke callbacks
|
|
532
|
+
if (do_post_handshake) {
|
|
533
|
+
handle_post_handshake_completion(socket, post_hs_copy);
|
|
534
|
+
}
|
|
535
|
+
} else {
|
|
536
|
+
LOG_CLIENT_WARN("Non-handshake data from " << pf.peer_id << " before handshake – ignoring");
|
|
640
537
|
}
|
|
641
|
-
|
|
642
538
|
continue;
|
|
643
539
|
}
|
|
644
540
|
|
|
645
|
-
//
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
541
|
+
// Data phase – decrypt if needed, then dispatch
|
|
542
|
+
std::vector<uint8_t> plaintext;
|
|
543
|
+
if (pf.noise_encrypted && pf.recv_cipher) {
|
|
544
|
+
if (pf.data.size() < rats::NOISE_TAG_SIZE) {
|
|
545
|
+
LOG_CLIENT_ERROR("Encrypted frame too small from " << pf.peer_id);
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
plaintext.resize(pf.data.size());
|
|
549
|
+
size_t pt_len = pf.recv_cipher->decrypt_with_ad(
|
|
550
|
+
nullptr, 0, pf.data.data(), pf.data.size(), plaintext.data());
|
|
551
|
+
if (pt_len == 0) {
|
|
552
|
+
LOG_CLIENT_ERROR("Decryption failed from " << pf.peer_id);
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
plaintext.resize(pt_len);
|
|
556
|
+
} else {
|
|
557
|
+
plaintext = std::move(pf.data);
|
|
649
558
|
}
|
|
650
559
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
560
|
+
process_message(socket, plaintext, pf.peer_id);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return peer_closed;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ---------------------------------------------------------------------------
|
|
567
|
+
// handle_writable – flush the peer's send buffer to the kernel
|
|
568
|
+
// Returns true if the peer should be disconnected.
|
|
569
|
+
// ---------------------------------------------------------------------------
|
|
570
|
+
bool RatsClient::handle_writable(socket_t socket) {
|
|
571
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
572
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
573
|
+
if (peer_it == peers_.end()) return true;
|
|
574
|
+
|
|
575
|
+
auto& send_buf = peer_it->second.io_.send_buffer;
|
|
576
|
+
|
|
577
|
+
while (!send_buf.empty()) {
|
|
578
|
+
int bytes = ::send(socket,
|
|
579
|
+
reinterpret_cast<const char*>(send_buf.front_data()),
|
|
580
|
+
static_cast<int>(send_buf.front_size()),
|
|
581
|
+
#ifdef _WIN32
|
|
582
|
+
0
|
|
583
|
+
#else
|
|
584
|
+
MSG_NOSIGNAL
|
|
585
|
+
#endif
|
|
586
|
+
);
|
|
654
587
|
|
|
655
|
-
if (
|
|
656
|
-
|
|
588
|
+
if (bytes > 0) {
|
|
589
|
+
send_buf.pop_front(static_cast<size_t>(bytes));
|
|
657
590
|
continue;
|
|
658
591
|
}
|
|
659
592
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
if (!handled && binary_data_callback_) {
|
|
670
|
-
binary_data_callback_(client_socket, peer_id, payload);
|
|
671
|
-
}
|
|
672
|
-
break;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
case MessageDataType::STRING: {
|
|
676
|
-
LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
677
|
-
if (string_data_callback_) {
|
|
678
|
-
std::string string_data(payload.begin(), payload.end());
|
|
679
|
-
string_data_callback_(client_socket, peer_id, string_data);
|
|
680
|
-
}
|
|
681
|
-
break;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
case MessageDataType::JSON: {
|
|
685
|
-
LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
686
|
-
std::string json_string(payload.begin(), payload.end());
|
|
687
|
-
nlohmann::json json_msg;
|
|
688
|
-
if (parse_json_message(json_string, json_msg)) {
|
|
689
|
-
if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
|
|
690
|
-
handle_rats_message(client_socket, peer_id, json_msg);
|
|
691
|
-
} else if (json_data_callback_) {
|
|
692
|
-
json_data_callback_(client_socket, peer_id, json_msg);
|
|
693
|
-
}
|
|
694
|
-
} else {
|
|
695
|
-
LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id);
|
|
696
|
-
}
|
|
697
|
-
break;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
default:
|
|
701
|
-
LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
|
|
702
|
-
break;
|
|
593
|
+
if (bytes < 0) {
|
|
594
|
+
#ifdef _WIN32
|
|
595
|
+
if (WSAGetLastError() == WSAEWOULDBLOCK) break;
|
|
596
|
+
#else
|
|
597
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
|
|
598
|
+
#endif
|
|
599
|
+
LOG_CLIENT_ERROR("Send error on socket " << socket);
|
|
600
|
+
return true;
|
|
703
601
|
}
|
|
704
602
|
|
|
705
|
-
|
|
603
|
+
// bytes == 0 — shouldn't happen on a stream socket
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
706
606
|
|
|
707
|
-
//
|
|
708
|
-
|
|
607
|
+
// If buffer fully flushed, stop watching for PollOut
|
|
608
|
+
if (send_buf.empty()) {
|
|
609
|
+
std::lock_guard<std::mutex> io_lock(io_mutex_);
|
|
610
|
+
if (poller_) poller_->modify(socket, PollIn);
|
|
611
|
+
}
|
|
709
612
|
|
|
710
|
-
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ---------------------------------------------------------------------------
|
|
617
|
+
// handle_disconnect – clean up peer on error / hangup / close
|
|
618
|
+
// ---------------------------------------------------------------------------
|
|
619
|
+
void RatsClient::handle_disconnect(socket_t socket) {
|
|
620
|
+
// Gather info before removing
|
|
621
|
+
std::string peer_id;
|
|
622
|
+
bool was_validated = false;
|
|
711
623
|
RatsPeer peer_copy_for_reconnect;
|
|
712
624
|
bool should_schedule_reconnect = false;
|
|
713
625
|
|
|
714
|
-
|
|
626
|
+
{
|
|
715
627
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
716
|
-
auto
|
|
717
|
-
if (
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
628
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
629
|
+
if (peer_it == peers_.end()) {
|
|
630
|
+
// Already removed
|
|
631
|
+
poller_remove(socket);
|
|
632
|
+
close_socket(socket);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
peer_id = peer_it->second.peer_id;
|
|
636
|
+
was_validated = peer_it->second.is_handshake_completed();
|
|
637
|
+
if (was_validated) {
|
|
638
|
+
peer_copy_for_reconnect = peer_it->second;
|
|
639
|
+
should_schedule_reconnect = true;
|
|
723
640
|
}
|
|
724
641
|
}
|
|
725
642
|
|
|
726
|
-
|
|
727
|
-
|
|
643
|
+
// Remove from poller, peers map, close socket
|
|
644
|
+
poller_remove(socket);
|
|
645
|
+
remove_peer(socket);
|
|
646
|
+
close_socket(socket);
|
|
728
647
|
|
|
729
|
-
if (
|
|
648
|
+
if (was_validated) {
|
|
730
649
|
if (disconnect_callback_) {
|
|
731
|
-
disconnect_callback_(
|
|
650
|
+
disconnect_callback_(socket, peer_id);
|
|
732
651
|
}
|
|
733
|
-
|
|
734
652
|
if (gossipsub_) {
|
|
735
|
-
gossipsub_->handle_peer_disconnected(
|
|
653
|
+
gossipsub_->handle_peer_disconnected(peer_id);
|
|
736
654
|
}
|
|
737
|
-
|
|
738
|
-
// Schedule reconnection if we have valid peer info
|
|
739
655
|
if (should_schedule_reconnect && running_.load()) {
|
|
740
656
|
schedule_reconnect(peer_copy_for_reconnect);
|
|
741
657
|
}
|
|
742
|
-
|
|
743
658
|
if (running_.load()) {
|
|
744
659
|
add_managed_thread(std::thread([this]() {
|
|
745
|
-
if (running_.load())
|
|
746
|
-
save_configuration();
|
|
747
|
-
}
|
|
660
|
+
if (running_.load()) save_configuration();
|
|
748
661
|
}), "config-save-disconnect");
|
|
749
662
|
}
|
|
750
663
|
}
|
|
751
664
|
|
|
752
|
-
LOG_CLIENT_INFO("
|
|
665
|
+
LOG_CLIENT_INFO("Peer disconnected: " << peer_id);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
// Poller registration helpers (thread-safe via io_mutex_)
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
void RatsClient::poller_add(socket_t fd, uint32_t events) {
|
|
672
|
+
std::lock_guard<std::mutex> lock(io_mutex_);
|
|
673
|
+
if (poller_) poller_->add(fd, events);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
void RatsClient::poller_modify(socket_t fd, uint32_t events) {
|
|
677
|
+
std::lock_guard<std::mutex> lock(io_mutex_);
|
|
678
|
+
if (poller_) poller_->modify(fd, events);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
void RatsClient::poller_remove(socket_t fd) {
|
|
682
|
+
std::lock_guard<std::mutex> lock(io_mutex_);
|
|
683
|
+
if (poller_) poller_->remove(fd);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ---------------------------------------------------------------------------
|
|
687
|
+
// enqueue_message – build a length-prefixed frame and append to send buffer
|
|
688
|
+
// ---------------------------------------------------------------------------
|
|
689
|
+
bool RatsClient::enqueue_message(socket_t socket, const std::vector<uint8_t>& data) {
|
|
690
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
691
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
692
|
+
if (peer_it == peers_.end()) return false;
|
|
693
|
+
return enqueue_message_unlocked(peer_it->second, data);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
bool RatsClient::enqueue_message_unlocked(RatsPeer& peer, const std::vector<uint8_t>& data) {
|
|
697
|
+
// Build length-prefixed frame: [4-byte network-order length][payload]
|
|
698
|
+
uint32_t net_len = htonl(static_cast<uint32_t>(data.size()));
|
|
699
|
+
|
|
700
|
+
std::vector<uint8_t> frame;
|
|
701
|
+
frame.reserve(4 + data.size());
|
|
702
|
+
frame.insert(frame.end(),
|
|
703
|
+
reinterpret_cast<const uint8_t*>(&net_len),
|
|
704
|
+
reinterpret_cast<const uint8_t*>(&net_len) + 4);
|
|
705
|
+
frame.insert(frame.end(), data.begin(), data.end());
|
|
706
|
+
|
|
707
|
+
peer.io_.send_buffer.append(std::move(frame));
|
|
708
|
+
|
|
709
|
+
// Arm PollOut so io_loop flushes the buffer
|
|
710
|
+
{
|
|
711
|
+
std::lock_guard<std::mutex> io_lock(io_mutex_);
|
|
712
|
+
if (poller_) poller_->modify(peer.socket, PollIn | PollOut);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
void RatsClient::management_loop() {
|
|
719
|
+
LOG_CLIENT_INFO("Management loop started");
|
|
720
|
+
|
|
721
|
+
auto last_thread_cleanup = std::chrono::steady_clock::now();
|
|
722
|
+
const auto thread_cleanup_interval = std::chrono::seconds(THREAD_CLEANUP_INTERVAL_SECONDS);
|
|
723
|
+
|
|
724
|
+
while (running_.load()) {
|
|
725
|
+
// Wait for interval or until shutdown (for responsive reconnection processing)
|
|
726
|
+
{
|
|
727
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
728
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(MANAGEMENT_LOOP_INTERVAL_SECONDS), [this] { return !running_.load(); })) {
|
|
729
|
+
break; // Exit if shutdown requested
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Check handshake timeouts (centralized, runs once for all peers)
|
|
734
|
+
try {
|
|
735
|
+
check_handshake_timeouts();
|
|
736
|
+
} catch (const std::exception& e) {
|
|
737
|
+
LOG_CLIENT_ERROR("Exception during handshake timeout check: " << e.what());
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Process reconnection queue
|
|
741
|
+
try {
|
|
742
|
+
process_reconnect_queue();
|
|
743
|
+
} catch (const std::exception& e) {
|
|
744
|
+
LOG_CLIENT_ERROR("Exception during reconnect queue processing: " << e.what());
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Periodically cleanup finished threads (every 30 seconds)
|
|
748
|
+
auto now = std::chrono::steady_clock::now();
|
|
749
|
+
if (now - last_thread_cleanup >= thread_cleanup_interval) {
|
|
750
|
+
try {
|
|
751
|
+
cleanup_finished_threads();
|
|
752
|
+
LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
|
|
753
|
+
} catch (const std::exception& e) {
|
|
754
|
+
LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
|
|
755
|
+
}
|
|
756
|
+
last_thread_cleanup = now;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
LOG_CLIENT_INFO("Management loop ended");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
void RatsClient::handle_post_handshake_completion(socket_t socket, const RatsPeer& peer_copy) {
|
|
764
|
+
// Remove from reconnection queue (successful connection)
|
|
765
|
+
remove_from_reconnect_queue(peer_copy.peer_id);
|
|
766
|
+
|
|
767
|
+
// Connection callback
|
|
768
|
+
if (connection_callback_) {
|
|
769
|
+
connection_callback_(socket, peer_copy.peer_id);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// GossipSub notification
|
|
773
|
+
if (gossipsub_) {
|
|
774
|
+
gossipsub_->handle_peer_connected(peer_copy.peer_id);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
#ifdef RATS_STORAGE
|
|
778
|
+
// Storage manager notification
|
|
779
|
+
if (storage_manager_) {
|
|
780
|
+
storage_manager_->on_peer_connected(peer_copy.peer_id);
|
|
781
|
+
}
|
|
782
|
+
#endif
|
|
783
|
+
|
|
784
|
+
// Peer exchange broadcast
|
|
785
|
+
broadcast_peer_exchange_message(peer_copy);
|
|
786
|
+
|
|
787
|
+
// Request peers from newly connected peer (outgoing only)
|
|
788
|
+
if (peer_copy.is_outgoing) {
|
|
789
|
+
send_peers_request(socket, peer_copy.peer_id);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Save configuration
|
|
793
|
+
if (running_.load()) {
|
|
794
|
+
add_managed_thread(std::thread([this]() {
|
|
795
|
+
if (running_.load()) {
|
|
796
|
+
save_configuration();
|
|
797
|
+
}
|
|
798
|
+
}), "config-save");
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
void RatsClient::process_message(socket_t socket, const std::vector<uint8_t>& data, const std::string& initial_peer_id) {
|
|
803
|
+
MessageHeader header;
|
|
804
|
+
std::vector<uint8_t> payload;
|
|
805
|
+
|
|
806
|
+
if (!parse_message_with_header(data, header, payload)) {
|
|
807
|
+
LOG_CLIENT_WARN("No header found in message from " << initial_peer_id);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
std::string peer_id = get_peer_id(socket);
|
|
812
|
+
|
|
813
|
+
switch (header.type) {
|
|
814
|
+
case MessageDataType::BINARY: {
|
|
815
|
+
LOG_CLIENT_DEBUG("Received BINARY message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
816
|
+
bool handled = false;
|
|
817
|
+
if (file_transfer_manager_) {
|
|
818
|
+
handled = file_transfer_manager_->handle_binary_data(peer_id, payload);
|
|
819
|
+
}
|
|
820
|
+
if (!handled && binary_data_callback_) {
|
|
821
|
+
binary_data_callback_(socket, peer_id, payload);
|
|
822
|
+
}
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
case MessageDataType::STRING: {
|
|
827
|
+
LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
828
|
+
if (string_data_callback_) {
|
|
829
|
+
std::string string_data(payload.begin(), payload.end());
|
|
830
|
+
string_data_callback_(socket, peer_id, string_data);
|
|
831
|
+
}
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
case MessageDataType::JSON: {
|
|
836
|
+
LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
837
|
+
try {
|
|
838
|
+
nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
|
|
839
|
+
if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
|
|
840
|
+
handle_rats_message(socket, peer_id, json_msg);
|
|
841
|
+
} else if (json_data_callback_) {
|
|
842
|
+
json_data_callback_(socket, peer_id, json_msg);
|
|
843
|
+
}
|
|
844
|
+
} catch (const nlohmann::json::exception& e) {
|
|
845
|
+
LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id << ": " << e.what());
|
|
846
|
+
}
|
|
847
|
+
break;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
default:
|
|
851
|
+
LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
753
854
|
}
|
|
754
855
|
|
|
755
856
|
// Handshake protocol implementation
|
|
@@ -844,9 +945,8 @@ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
|
|
|
844
945
|
auto now = std::chrono::high_resolution_clock::now();
|
|
845
946
|
auto current_timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
|
846
947
|
int64_t time_diff = std::abs(current_timestamp - msg.timestamp);
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << allowed_skew_ms << "ms; accepting to be tolerant of clock skew");
|
|
948
|
+
if (time_diff > TIMESTAMP_SKEW_TOLERANCE_MS) {
|
|
949
|
+
LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << TIMESTAMP_SKEW_TOLERANCE_MS << "ms; accepting to be tolerant of clock skew");
|
|
850
950
|
}
|
|
851
951
|
}
|
|
852
952
|
|
|
@@ -882,52 +982,32 @@ bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
|
|
|
882
982
|
}
|
|
883
983
|
}
|
|
884
984
|
|
|
885
|
-
|
|
886
|
-
bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our_peer_id) {
|
|
985
|
+
bool RatsClient::send_handshake_unlocked(RatsPeer& peer, const std::string& our_peer_id) {
|
|
887
986
|
std::string handshake_msg = create_handshake_message("handshake", our_peer_id);
|
|
888
|
-
LOG_CLIENT_DEBUG("Sending handshake to
|
|
987
|
+
LOG_CLIENT_DEBUG("Sending handshake to " << peer.peer_id << ": " << handshake_msg);
|
|
889
988
|
|
|
890
|
-
//
|
|
891
|
-
// (which would cause deadlock by trying to lock peers_mutex_ again).
|
|
892
|
-
// Handshakes are always unencrypted since they happen before noise handshake.
|
|
989
|
+
// Handshakes are always unencrypted – enqueue into the peer's send buffer
|
|
893
990
|
std::vector<uint8_t> binary_data(handshake_msg.begin(), handshake_msg.end());
|
|
894
991
|
std::vector<uint8_t> message_with_header = create_message_with_header(binary_data, MessageDataType::STRING);
|
|
895
992
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
std::lock_guard<std::mutex> send_lock(*socket_mutex);
|
|
899
|
-
|
|
900
|
-
int sent = send_tcp_message_framed(socket, message_with_header);
|
|
901
|
-
if (sent <= 0) {
|
|
902
|
-
LOG_CLIENT_ERROR("Failed to send handshake to socket " << socket);
|
|
993
|
+
if (!enqueue_message_unlocked(peer, message_with_header)) {
|
|
994
|
+
LOG_CLIENT_ERROR("Failed to enqueue handshake for " << peer.peer_id);
|
|
903
995
|
return false;
|
|
904
996
|
}
|
|
905
997
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
if (it != socket_to_peer_id_.end()) {
|
|
909
|
-
auto peer_it = peers_.find(it->second);
|
|
910
|
-
if (peer_it != peers_.end()) {
|
|
911
|
-
peer_it->second.handshake_state = RatsPeer::HandshakeState::SENT;
|
|
912
|
-
peer_it->second.handshake_start_time = std::chrono::steady_clock::now();
|
|
913
|
-
}
|
|
914
|
-
}
|
|
998
|
+
peer.handshake_state = RatsPeer::HandshakeState::SENT;
|
|
999
|
+
peer.handshake_start_time = std::chrono::steady_clock::now();
|
|
915
1000
|
|
|
916
1001
|
return true;
|
|
917
1002
|
}
|
|
918
1003
|
|
|
919
|
-
bool RatsClient::
|
|
920
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
921
|
-
return send_handshake_unlocked(socket, our_peer_id);
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::vector<uint8_t>& data) {
|
|
1004
|
+
bool RatsClient::handle_handshake_message(socket_t socket, const std::string& initial_peer_id, const std::vector<uint8_t>& data) {
|
|
925
1005
|
// Extract JSON payload from message header
|
|
926
1006
|
MessageHeader header;
|
|
927
1007
|
std::vector<uint8_t> payload;
|
|
928
1008
|
|
|
929
1009
|
if (!parse_message_with_header(data, header, payload)) {
|
|
930
|
-
LOG_CLIENT_ERROR("Failed to parse handshake message header from " <<
|
|
1010
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message header from " << initial_peer_id);
|
|
931
1011
|
return false;
|
|
932
1012
|
}
|
|
933
1013
|
|
|
@@ -940,12 +1020,12 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
940
1020
|
// Parse handshake message directly from payload (no string conversion)
|
|
941
1021
|
HandshakeMessage handshake_msg;
|
|
942
1022
|
if (!parse_handshake_message(payload, handshake_msg)) {
|
|
943
|
-
LOG_CLIENT_ERROR("Failed to parse handshake message from " <<
|
|
1023
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message from " << initial_peer_id);
|
|
944
1024
|
return false;
|
|
945
1025
|
}
|
|
946
1026
|
|
|
947
1027
|
if (!validate_handshake_message(handshake_msg)) {
|
|
948
|
-
LOG_CLIENT_ERROR("Invalid handshake message from " <<
|
|
1028
|
+
LOG_CLIENT_ERROR("Invalid handshake message from " << initial_peer_id);
|
|
949
1029
|
return false;
|
|
950
1030
|
}
|
|
951
1031
|
|
|
@@ -954,19 +1034,13 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
954
1034
|
return false;
|
|
955
1035
|
}
|
|
956
1036
|
|
|
957
|
-
LOG_CLIENT_INFO("Received valid handshake from " <<
|
|
1037
|
+
LOG_CLIENT_INFO("Received valid handshake from " << initial_peer_id
|
|
958
1038
|
<< " (peer_id: " << handshake_msg.peer_id << ")");
|
|
959
1039
|
|
|
960
1040
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
961
|
-
auto
|
|
962
|
-
if (it == socket_to_peer_id_.end()) {
|
|
963
|
-
LOG_CLIENT_ERROR("Socket " << socket << " not found in peer mapping");
|
|
964
|
-
return false;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
auto peer_it = peers_.find(it->second);
|
|
1041
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
968
1042
|
if (peer_it == peers_.end()) {
|
|
969
|
-
LOG_CLIENT_ERROR("Peer " <<
|
|
1043
|
+
LOG_CLIENT_ERROR("Peer " << initial_peer_id << " not found for socket " << socket);
|
|
970
1044
|
return false;
|
|
971
1045
|
}
|
|
972
1046
|
|
|
@@ -982,22 +1056,18 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
982
1056
|
|
|
983
1057
|
// Update peer mappings with new peer_id if it changed
|
|
984
1058
|
if (old_peer_id != handshake_msg.peer_id) {
|
|
985
|
-
//
|
|
986
|
-
RatsPeer
|
|
987
|
-
|
|
988
|
-
// Erase the old entry from the main peers map.
|
|
1059
|
+
// Move the peer (preserves io_ context with send/recv buffers)
|
|
1060
|
+
RatsPeer peer_moved = std::move(peer_it->second);
|
|
989
1061
|
peers_.erase(peer_it);
|
|
990
1062
|
|
|
991
|
-
|
|
992
|
-
peer_copy.peer_id = handshake_msg.peer_id;
|
|
1063
|
+
peer_moved.peer_id = handshake_msg.peer_id;
|
|
993
1064
|
|
|
994
|
-
//
|
|
995
|
-
|
|
996
|
-
socket_to_peer_id_[socket] =
|
|
997
|
-
address_to_peer_id_[
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
peer_it = peers_.find(peer_copy.peer_id);
|
|
1065
|
+
// Use emplace to avoid extra copy/move
|
|
1066
|
+
auto [new_it, ok] = peers_.emplace(peer_moved.peer_id, std::move(peer_moved));
|
|
1067
|
+
socket_to_peer_id_[socket] = new_it->second.peer_id;
|
|
1068
|
+
address_to_peer_id_[new_it->second.normalized_address] = new_it->second.peer_id;
|
|
1069
|
+
|
|
1070
|
+
peer_it = new_it;
|
|
1001
1071
|
}
|
|
1002
1072
|
|
|
1003
1073
|
RatsPeer& peer = peer_it->second;
|
|
@@ -1034,14 +1104,15 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1034
1104
|
// Simplified handshake logic - just one message type
|
|
1035
1105
|
if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
|
|
1036
1106
|
// This is an incoming handshake - send our handshake back
|
|
1037
|
-
if (send_handshake_unlocked(
|
|
1107
|
+
if (send_handshake_unlocked(peer, get_our_peer_id())) {
|
|
1038
1108
|
// If encryption is enabled, we need to do Noise handshake first
|
|
1039
1109
|
// Set NOISE_PENDING to prevent other threads from sending messages
|
|
1040
1110
|
if (peer.encryption_enabled) {
|
|
1041
1111
|
peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
|
|
1042
|
-
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " <<
|
|
1112
|
+
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
|
|
1043
1113
|
} else {
|
|
1044
1114
|
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1115
|
+
validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
|
|
1045
1116
|
log_handshake_completion_unlocked(peer);
|
|
1046
1117
|
}
|
|
1047
1118
|
|
|
@@ -1051,7 +1122,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1051
1122
|
return true;
|
|
1052
1123
|
} else {
|
|
1053
1124
|
peer.handshake_state = RatsPeer::HandshakeState::FAILED;
|
|
1054
|
-
LOG_CLIENT_ERROR("Failed to send handshake response to " <<
|
|
1125
|
+
LOG_CLIENT_ERROR("Failed to send handshake response to " << initial_peer_id);
|
|
1055
1126
|
return false;
|
|
1056
1127
|
}
|
|
1057
1128
|
} else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
|
|
@@ -1060,9 +1131,10 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1060
1131
|
// Set NOISE_PENDING to prevent other threads from sending messages
|
|
1061
1132
|
if (peer.encryption_enabled) {
|
|
1062
1133
|
peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
|
|
1063
|
-
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " <<
|
|
1134
|
+
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
|
|
1064
1135
|
} else {
|
|
1065
1136
|
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1137
|
+
validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
|
|
1066
1138
|
log_handshake_completion_unlocked(peer);
|
|
1067
1139
|
}
|
|
1068
1140
|
|
|
@@ -1071,7 +1143,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1071
1143
|
|
|
1072
1144
|
return true;
|
|
1073
1145
|
} else {
|
|
1074
|
-
LOG_CLIENT_WARN("Received handshake from " <<
|
|
1146
|
+
LOG_CLIENT_WARN("Received handshake from " << initial_peer_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
|
|
1075
1147
|
return false;
|
|
1076
1148
|
}
|
|
1077
1149
|
}
|
|
@@ -1105,8 +1177,8 @@ void RatsClient::check_handshake_timeouts() {
|
|
|
1105
1177
|
socket_t socket = peer_it->second.socket;
|
|
1106
1178
|
LOG_CLIENT_INFO("Disconnecting peer " << peer_id << " due to handshake timeout");
|
|
1107
1179
|
|
|
1108
|
-
// Clean up peer data
|
|
1109
1180
|
remove_peer_by_id_unlocked(peer_id);
|
|
1181
|
+
poller_remove(socket);
|
|
1110
1182
|
close_socket(socket);
|
|
1111
1183
|
}
|
|
1112
1184
|
}
|
|
@@ -1143,70 +1215,108 @@ bool RatsClient::connect_to_peer(const std::string& host, int port) {
|
|
|
1143
1215
|
return false;
|
|
1144
1216
|
}
|
|
1145
1217
|
|
|
1146
|
-
// Create TCP connection with timeout
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
std::
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1218
|
+
// Create TCP connection with timeout (blocking connect is done on a managed thread
|
|
1219
|
+
// to avoid blocking the caller, then the connected socket is handed to the IO loop).
|
|
1220
|
+
add_managed_thread(std::thread([this, host, port, normalized_address]() {
|
|
1221
|
+
socket_t client_socket = create_tcp_client(host, port, TCP_CONNECT_TIMEOUT_MS);
|
|
1222
|
+
if (!is_valid_socket(client_socket)) {
|
|
1223
|
+
LOG_CLIENT_DEBUG("Failed to connect to " << host << ":" << port);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
LOG_CLIENT_INFO("TCP connected to " << host << ":" << port);
|
|
1228
|
+
|
|
1229
|
+
// Switch to non-blocking for the IO poller
|
|
1230
|
+
set_socket_nonblocking(client_socket);
|
|
1231
|
+
|
|
1232
|
+
std::string initial_peer_id = generate_temporary_peer_id(client_socket, normalized_address);
|
|
1233
|
+
|
|
1234
|
+
{
|
|
1235
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1236
|
+
|
|
1237
|
+
// Re-check peer limit and duplicate (could have changed while connecting)
|
|
1238
|
+
// NOTE: Use unlocked variant — peers_mutex_ is already held!
|
|
1239
|
+
if (get_peer_count_unlocked() >= max_peers_) {
|
|
1240
|
+
LOG_CLIENT_DEBUG("connect_to_peer: peer limit reached after TCP connect, aborting fd=" << client_socket);
|
|
1241
|
+
close_socket(client_socket);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
if (address_to_peer_id_.find(normalized_address) != address_to_peer_id_.end()) {
|
|
1245
|
+
LOG_CLIENT_DEBUG("connect_to_peer: duplicate address " << normalized_address << " after TCP connect, aborting fd=" << client_socket);
|
|
1246
|
+
close_socket(client_socket);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
RatsPeer new_peer(initial_peer_id, host, static_cast<uint16_t>(port),
|
|
1251
|
+
client_socket, normalized_address, true);
|
|
1252
|
+
new_peer.encryption_enabled = is_encryption_enabled();
|
|
1253
|
+
add_peer_unlocked(new_peer);
|
|
1254
|
+
|
|
1255
|
+
// Send initial handshake (enqueued into send buffer)
|
|
1256
|
+
auto peer_it = peers_.find(initial_peer_id);
|
|
1257
|
+
if (peer_it != peers_.end()) {
|
|
1258
|
+
if (!send_handshake_unlocked(peer_it->second, get_our_peer_id())) {
|
|
1259
|
+
LOG_CLIENT_ERROR("Failed to enqueue handshake for outgoing connection to " << host << ":" << port);
|
|
1260
|
+
remove_peer_by_id_unlocked(initial_peer_id);
|
|
1261
|
+
close_socket(client_socket);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// Register with poller – PollIn for reads, PollOut to flush the queued handshake
|
|
1268
|
+
poller_add(client_socket, PollIn | PollOut);
|
|
1269
|
+
|
|
1270
|
+
LOG_CLIENT_INFO("Outgoing connection to " << host << ":" << port << " registered with IO poller");
|
|
1271
|
+
}), "connect-" + host + ":" + std::to_string(port));
|
|
1170
1272
|
|
|
1171
1273
|
return true;
|
|
1172
1274
|
}
|
|
1173
1275
|
|
|
1276
|
+
void RatsClient::mark_manual_disconnect(const std::string& peer_id) {
|
|
1277
|
+
std::lock_guard<std::mutex> lock(reconnect_mutex_);
|
|
1278
|
+
manual_disconnect_peers_.insert(peer_id);
|
|
1279
|
+
reconnect_queue_.erase(peer_id);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1174
1282
|
void RatsClient::disconnect_peer(socket_t socket) {
|
|
1175
|
-
// Mark as manually disconnected to prevent auto-reconnection
|
|
1176
1283
|
std::string peer_id = get_peer_id(socket);
|
|
1177
1284
|
if (!peer_id.empty()) {
|
|
1178
|
-
|
|
1179
|
-
manual_disconnect_peers_.insert(peer_id);
|
|
1180
|
-
// Also remove from reconnection queue if present
|
|
1181
|
-
reconnect_queue_.erase(peer_id);
|
|
1285
|
+
mark_manual_disconnect(peer_id);
|
|
1182
1286
|
}
|
|
1183
|
-
|
|
1287
|
+
poller_remove(socket);
|
|
1184
1288
|
remove_peer(socket);
|
|
1185
1289
|
close_socket(socket);
|
|
1186
1290
|
}
|
|
1187
1291
|
|
|
1188
1292
|
void RatsClient::disconnect_peer_by_id(const std::string& peer_id) {
|
|
1189
|
-
|
|
1190
|
-
{
|
|
1191
|
-
std::lock_guard<std::mutex> lock(reconnect_mutex_);
|
|
1192
|
-
manual_disconnect_peers_.insert(peer_id);
|
|
1193
|
-
// Also remove from reconnection queue if present
|
|
1194
|
-
reconnect_queue_.erase(peer_id);
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1293
|
+
mark_manual_disconnect(peer_id);
|
|
1197
1294
|
socket_t socket = get_peer_socket_by_id(peer_id);
|
|
1198
1295
|
if (is_valid_socket(socket)) {
|
|
1296
|
+
poller_remove(socket);
|
|
1199
1297
|
remove_peer(socket);
|
|
1200
1298
|
close_socket(socket);
|
|
1201
1299
|
}
|
|
1202
1300
|
}
|
|
1203
1301
|
|
|
1204
|
-
//
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1302
|
+
// Peer lookup helpers (assumes peers_mutex_ is already locked)
|
|
1303
|
+
std::unordered_map<std::string, RatsPeer>::iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) {
|
|
1304
|
+
auto sock_it = socket_to_peer_id_.find(socket);
|
|
1305
|
+
if (sock_it != socket_to_peer_id_.end()) {
|
|
1306
|
+
return peers_.find(sock_it->second);
|
|
1307
|
+
}
|
|
1308
|
+
return peers_.end();
|
|
1208
1309
|
}
|
|
1209
1310
|
|
|
1311
|
+
std::unordered_map<std::string, RatsPeer>::const_iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) const {
|
|
1312
|
+
auto sock_it = socket_to_peer_id_.find(socket);
|
|
1313
|
+
if (sock_it != socket_to_peer_id_.end()) {
|
|
1314
|
+
return peers_.find(sock_it->second);
|
|
1315
|
+
}
|
|
1316
|
+
return peers_.end();
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Helper methods for peer management
|
|
1210
1320
|
void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
|
|
1211
1321
|
// Assumes peers_mutex_ is already locked
|
|
1212
1322
|
peers_[peer.peer_id] = peer;
|
|
@@ -1216,17 +1326,12 @@ void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
|
|
|
1216
1326
|
|
|
1217
1327
|
void RatsClient::remove_peer(socket_t socket) {
|
|
1218
1328
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1219
|
-
auto
|
|
1220
|
-
if (
|
|
1221
|
-
remove_peer_by_id_unlocked(
|
|
1329
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1330
|
+
if (peer_it != peers_.end()) {
|
|
1331
|
+
remove_peer_by_id_unlocked(peer_it->second.peer_id);
|
|
1222
1332
|
}
|
|
1223
1333
|
}
|
|
1224
1334
|
|
|
1225
|
-
void RatsClient::remove_peer_by_id(const std::string& peer_id) {
|
|
1226
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1227
|
-
remove_peer_by_id_unlocked(peer_id);
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
1335
|
void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
1231
1336
|
// Assumes peers_mutex_ is already locked
|
|
1232
1337
|
|
|
@@ -1235,6 +1340,11 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
|
1235
1340
|
|
|
1236
1341
|
auto it = peers_.find(peer_id_copy);
|
|
1237
1342
|
if (it != peers_.end()) {
|
|
1343
|
+
// Decrement validated peer count if this peer had completed handshake
|
|
1344
|
+
if (it->second.is_handshake_completed()) {
|
|
1345
|
+
validated_peer_count_.fetch_sub(1, std::memory_order_relaxed);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1238
1348
|
// Copy the values we need before erasing to avoid use-after-free
|
|
1239
1349
|
socket_t peer_socket = it->second.socket;
|
|
1240
1350
|
std::string peer_normalized_address = it->second.normalized_address;
|
|
@@ -1242,9 +1352,6 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
|
1242
1352
|
socket_to_peer_id_.erase(peer_socket);
|
|
1243
1353
|
address_to_peer_id_.erase(peer_normalized_address);
|
|
1244
1354
|
peers_.erase(it);
|
|
1245
|
-
|
|
1246
|
-
// Clean up socket-specific mutex
|
|
1247
|
-
cleanup_socket_send_mutex(peer_socket);
|
|
1248
1355
|
}
|
|
1249
1356
|
}
|
|
1250
1357
|
|
|
@@ -1256,9 +1363,8 @@ bool RatsClient::is_already_connected_to_address(const std::string& normalized_a
|
|
|
1256
1363
|
void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
1257
1364
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1258
1365
|
|
|
1259
|
-
|
|
1260
|
-
if (
|
|
1261
|
-
local_interface_addresses_.push_back(ip_address);
|
|
1366
|
+
auto [it, inserted] = local_interface_addresses_.insert(ip_address);
|
|
1367
|
+
if (inserted) {
|
|
1262
1368
|
LOG_CLIENT_INFO("Added " << ip_address << " to ignore list");
|
|
1263
1369
|
} else {
|
|
1264
1370
|
LOG_CLIENT_DEBUG("IP address " << ip_address << " already in ignore list");
|
|
@@ -1266,7 +1372,7 @@ void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
|
1266
1372
|
}
|
|
1267
1373
|
|
|
1268
1374
|
//common localhost addresses
|
|
1269
|
-
static constexpr std::array<std::string_view,
|
|
1375
|
+
static constexpr std::array<std::string_view,5> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::", "localhost"};
|
|
1270
1376
|
|
|
1271
1377
|
// Local interface address blocking methods
|
|
1272
1378
|
void RatsClient::initialize_local_addresses() {
|
|
@@ -1275,13 +1381,12 @@ void RatsClient::initialize_local_addresses() {
|
|
|
1275
1381
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1276
1382
|
|
|
1277
1383
|
// Get all local interface addresses using network_utils
|
|
1278
|
-
|
|
1384
|
+
auto addrs = network_utils::get_local_interface_addresses();
|
|
1385
|
+
local_interface_addresses_.insert(addrs.begin(), addrs.end());
|
|
1279
1386
|
|
|
1280
|
-
// Add common localhost addresses
|
|
1387
|
+
// Add common localhost addresses
|
|
1281
1388
|
for (const auto& addr : localhost_addrs) {
|
|
1282
|
-
|
|
1283
|
-
local_interface_addresses_.emplace_back(addr);
|
|
1284
|
-
}
|
|
1389
|
+
local_interface_addresses_.emplace(std::string(addr));
|
|
1285
1390
|
}
|
|
1286
1391
|
|
|
1287
1392
|
LOG_CLIENT_INFO("Found " << local_interface_addresses_.size() << " local addresses to block:");
|
|
@@ -1290,36 +1395,47 @@ void RatsClient::initialize_local_addresses() {
|
|
|
1290
1395
|
}
|
|
1291
1396
|
}
|
|
1292
1397
|
|
|
1293
|
-
|
|
1294
1398
|
bool RatsClient::is_blocked_address(const std::string& ip_address) const {
|
|
1295
1399
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1400
|
+
return local_interface_addresses_.count(ip_address) > 0;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
bool RatsClient::can_connect_to_peer(const std::string& ip, int port) const {
|
|
1404
|
+
if (should_ignore_peer(ip, port)) {
|
|
1405
|
+
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - blocked address");
|
|
1406
|
+
return false;
|
|
1407
|
+
}
|
|
1296
1408
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
}
|
|
1409
|
+
std::string normalized_address = normalize_peer_address(ip, port);
|
|
1410
|
+
if (is_already_connected_to_address(normalized_address)) {
|
|
1411
|
+
LOG_CLIENT_DEBUG("Already connected to " << normalized_address);
|
|
1412
|
+
return false;
|
|
1302
1413
|
}
|
|
1303
1414
|
|
|
1304
|
-
|
|
1415
|
+
if (is_peer_limit_reached()) {
|
|
1416
|
+
LOG_CLIENT_DEBUG("Peer limit reached, cannot connect to " << ip << ":" << port);
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
return true;
|
|
1305
1421
|
}
|
|
1306
1422
|
|
|
1307
1423
|
bool RatsClient::should_ignore_peer(const std::string& ip, int port) const {
|
|
1308
|
-
//
|
|
1309
|
-
|
|
1310
|
-
|
|
1424
|
+
// Check if this is a well-known localhost address
|
|
1425
|
+
bool is_localhost = std::find(localhost_addrs.begin(), localhost_addrs.end(), ip) != localhost_addrs.end();
|
|
1426
|
+
|
|
1427
|
+
if (is_localhost) {
|
|
1428
|
+
// Block self-connections (same port on localhost)
|
|
1429
|
+
if (port == listen_port_) {
|
|
1311
1430
|
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - localhost with same port");
|
|
1312
1431
|
return true;
|
|
1313
1432
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
// For localhost addresses on different ports, allow the connection (for testing)
|
|
1317
|
-
if (ip == "127.0.0.1" || ip == "::1" || ip == "localhost") {
|
|
1433
|
+
// Allow localhost on different ports (for testing)
|
|
1318
1434
|
LOG_CLIENT_DEBUG("Allowing localhost peer " << ip << ":" << port << " on different port");
|
|
1319
1435
|
return false;
|
|
1320
1436
|
}
|
|
1321
1437
|
|
|
1322
|
-
//
|
|
1438
|
+
// Block non-localhost local interface addresses
|
|
1323
1439
|
if (is_blocked_address(ip)) {
|
|
1324
1440
|
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - matches local interface address");
|
|
1325
1441
|
return true;
|
|
@@ -1372,30 +1488,25 @@ bool RatsClient::parse_message_with_header(const std::vector<uint8_t>& message,
|
|
|
1372
1488
|
// Extract payload
|
|
1373
1489
|
payload.assign(message.begin() + MessageHeader::HEADER_SIZE, message.end());
|
|
1374
1490
|
|
|
1375
|
-
LOG_CLIENT_DEBUG("Parsed message header: type=" << static_cast<int>(header.type) << ", payload_size=" << payload.size());
|
|
1376
1491
|
return true;
|
|
1377
1492
|
}
|
|
1378
1493
|
|
|
1379
|
-
//
|
|
1380
|
-
//
|
|
1494
|
+
// Async send – enqueues header + (optionally encrypted) payload into the peer's
|
|
1495
|
+
// ChainedSendBuffer. Does NOT require peers_mutex_; caller passes cached peer data.
|
|
1496
|
+
// The shared_ptr keeps the cipher alive even if the peer is removed concurrently.
|
|
1381
1497
|
bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector<uint8_t>& data,
|
|
1382
1498
|
MessageDataType message_type,
|
|
1383
|
-
rats::NoiseCipherState
|
|
1499
|
+
std::shared_ptr<rats::NoiseCipherState> send_cipher,
|
|
1384
1500
|
const std::string& peer_id_for_logging) {
|
|
1385
1501
|
if (!running_.load()) {
|
|
1386
1502
|
return false;
|
|
1387
1503
|
}
|
|
1388
1504
|
|
|
1389
|
-
// Get socket-specific mutex for thread-safe sending
|
|
1390
|
-
// Prevent framed messages corruption (like two-times sending the number of bytes instead number of bytes + message)
|
|
1391
|
-
auto socket_mutex = get_socket_send_mutex(socket);
|
|
1392
|
-
std::lock_guard<std::mutex> send_lock(*socket_mutex);
|
|
1393
|
-
|
|
1394
1505
|
// Create message with specified header type
|
|
1395
1506
|
std::vector<uint8_t> message_with_header = create_message_with_header(data, message_type);
|
|
1396
1507
|
|
|
1397
1508
|
if (send_cipher) {
|
|
1398
|
-
// Encrypt the message before
|
|
1509
|
+
// Encrypt the message before enqueuing
|
|
1399
1510
|
std::vector<uint8_t> ciphertext(message_with_header.size() + rats::NOISE_TAG_SIZE);
|
|
1400
1511
|
size_t ct_len = send_cipher->encrypt_with_ad(
|
|
1401
1512
|
nullptr, 0,
|
|
@@ -1409,16 +1520,13 @@ bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector
|
|
|
1409
1520
|
}
|
|
1410
1521
|
|
|
1411
1522
|
ciphertext.resize(ct_len);
|
|
1412
|
-
LOG_CLIENT_DEBUG("
|
|
1523
|
+
LOG_CLIENT_DEBUG("Enqueuing encrypted message for " << peer_id_for_logging << " (" << ct_len << " bytes)");
|
|
1413
1524
|
|
|
1414
|
-
|
|
1415
|
-
int sent = send_tcp_message_framed(socket, ciphertext);
|
|
1416
|
-
return sent > 0;
|
|
1525
|
+
return enqueue_message(socket, ciphertext);
|
|
1417
1526
|
}
|
|
1418
1527
|
|
|
1419
|
-
// Unencrypted path
|
|
1420
|
-
|
|
1421
|
-
return sent > 0;
|
|
1528
|
+
// Unencrypted path
|
|
1529
|
+
return enqueue_message(socket, message_with_header);
|
|
1422
1530
|
}
|
|
1423
1531
|
|
|
1424
1532
|
bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
@@ -1426,25 +1534,22 @@ bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>
|
|
|
1426
1534
|
return false;
|
|
1427
1535
|
}
|
|
1428
1536
|
|
|
1429
|
-
// Cache peer
|
|
1537
|
+
// Cache peer data under lock, then release lock before sending
|
|
1430
1538
|
std::string peer_id;
|
|
1431
|
-
rats::NoiseCipherState
|
|
1539
|
+
std::shared_ptr<rats::NoiseCipherState> send_cipher;
|
|
1432
1540
|
|
|
1433
1541
|
{
|
|
1434
1542
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1435
|
-
auto
|
|
1436
|
-
if (
|
|
1437
|
-
|
|
1438
|
-
if (peer_it
|
|
1439
|
-
|
|
1440
|
-
if (peer_it->second.is_noise_encrypted()) {
|
|
1441
|
-
send_cipher = peer_it->second.send_cipher.get();
|
|
1442
|
-
}
|
|
1543
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1544
|
+
if (peer_it != peers_.end()) {
|
|
1545
|
+
peer_id = peer_it->second.peer_id;
|
|
1546
|
+
if (peer_it->second.is_noise_encrypted()) {
|
|
1547
|
+
send_cipher = peer_it->second.send_cipher; // shared_ptr copy keeps cipher alive
|
|
1443
1548
|
}
|
|
1444
1549
|
}
|
|
1445
1550
|
}
|
|
1446
1551
|
|
|
1447
|
-
//
|
|
1552
|
+
// peers_mutex_ released -- safe to do potentially slow TCP send
|
|
1448
1553
|
return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
|
|
1449
1554
|
}
|
|
1450
1555
|
|
|
@@ -1454,43 +1559,50 @@ bool RatsClient::send_string_to_peer(socket_t socket, const std::string& data) {
|
|
|
1454
1559
|
return send_binary_to_peer(socket, binary_data, MessageDataType::STRING);
|
|
1455
1560
|
}
|
|
1456
1561
|
|
|
1562
|
+
std::vector<uint8_t> RatsClient::json_to_binary(const nlohmann::json& data) {
|
|
1563
|
+
std::string s = data.dump();
|
|
1564
|
+
return {s.begin(), s.end()};
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1457
1567
|
bool RatsClient::send_json_to_peer(socket_t socket, const nlohmann::json& data) {
|
|
1458
1568
|
try {
|
|
1459
|
-
|
|
1460
|
-
std::string json_string = data.dump();
|
|
1461
|
-
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1462
|
-
return send_binary_to_peer(socket, binary_data, MessageDataType::JSON);
|
|
1569
|
+
return send_binary_to_peer(socket, json_to_binary(data), MessageDataType::JSON);
|
|
1463
1570
|
} catch (const nlohmann::json::exception& e) {
|
|
1464
1571
|
LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
|
|
1465
1572
|
return false;
|
|
1466
1573
|
}
|
|
1467
1574
|
}
|
|
1468
1575
|
|
|
1469
|
-
bool RatsClient::send_binary_to_peer_id(const std::string&
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1576
|
+
bool RatsClient::send_binary_to_peer_id(const std::string& peer_id, const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
1577
|
+
// Cache peer data under lock, then release before sending
|
|
1578
|
+
socket_t socket;
|
|
1579
|
+
std::shared_ptr<rats::NoiseCipherState> send_cipher;
|
|
1580
|
+
|
|
1581
|
+
{
|
|
1582
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1583
|
+
auto it = peers_.find(peer_id);
|
|
1584
|
+
if (it == peers_.end() || !it->second.is_handshake_completed()) {
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
socket = it->second.socket;
|
|
1588
|
+
if (it->second.is_noise_encrypted()) {
|
|
1589
|
+
send_cipher = it->second.send_cipher; // shared_ptr copy keeps cipher alive
|
|
1590
|
+
}
|
|
1474
1591
|
}
|
|
1475
1592
|
|
|
1476
|
-
//
|
|
1477
|
-
|
|
1478
|
-
rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
|
|
1479
|
-
return send_binary_to_peer_unlocked(peer.socket, data, message_type, send_cipher, peer.peer_id);
|
|
1593
|
+
// peers_mutex_ released -- safe to do potentially slow TCP send
|
|
1594
|
+
return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
|
|
1480
1595
|
}
|
|
1481
1596
|
|
|
1482
|
-
bool RatsClient::send_string_to_peer_id(const std::string&
|
|
1597
|
+
bool RatsClient::send_string_to_peer_id(const std::string& peer_id, const std::string& data) {
|
|
1483
1598
|
// Convert string to binary and use primary binary method with STRING type
|
|
1484
1599
|
std::vector<uint8_t> binary_data(data.begin(), data.end());
|
|
1485
|
-
return send_binary_to_peer_id(
|
|
1600
|
+
return send_binary_to_peer_id(peer_id, binary_data, MessageDataType::STRING);
|
|
1486
1601
|
}
|
|
1487
1602
|
|
|
1488
|
-
bool RatsClient::send_json_to_peer_id(const std::string&
|
|
1603
|
+
bool RatsClient::send_json_to_peer_id(const std::string& peer_id, const nlohmann::json& data) {
|
|
1489
1604
|
try {
|
|
1490
|
-
|
|
1491
|
-
std::string json_string = data.dump();
|
|
1492
|
-
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1493
|
-
return send_binary_to_peer_id(peer_hash_id, binary_data, MessageDataType::JSON);
|
|
1605
|
+
return send_binary_to_peer_id(peer_id, json_to_binary(data), MessageDataType::JSON);
|
|
1494
1606
|
} catch (const nlohmann::json::exception& e) {
|
|
1495
1607
|
LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
|
|
1496
1608
|
return false;
|
|
@@ -1499,10 +1611,7 @@ bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlo
|
|
|
1499
1611
|
|
|
1500
1612
|
int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
|
|
1501
1613
|
try {
|
|
1502
|
-
|
|
1503
|
-
std::string json_string = data.dump();
|
|
1504
|
-
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1505
|
-
return broadcast_binary_to_peers(binary_data, MessageDataType::JSON);
|
|
1614
|
+
return broadcast_binary_to_peers(json_to_binary(data), MessageDataType::JSON);
|
|
1506
1615
|
} catch (const nlohmann::json::exception& e) {
|
|
1507
1616
|
LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
|
|
1508
1617
|
return 0;
|
|
@@ -1512,23 +1621,27 @@ int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
|
|
|
1512
1621
|
int RatsClient::broadcast_binary_to_peers(const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
1513
1622
|
if (!running_.load()) {
|
|
1514
1623
|
return 0;
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
int sent_count = 0;
|
|
1518
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1624
|
+
}
|
|
1519
1625
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
if (
|
|
1527
|
-
|
|
1626
|
+
// Collect targets under lock, then enqueue outside
|
|
1627
|
+
std::vector<PeerSendTarget> targets;
|
|
1628
|
+
{
|
|
1629
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1630
|
+
targets.reserve(peers_.size());
|
|
1631
|
+
for (const auto& [id, peer] : peers_) {
|
|
1632
|
+
if (peer.is_handshake_completed()) {
|
|
1633
|
+
targets.push_back({peer.socket, peer.peer_id,
|
|
1634
|
+
peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
|
|
1528
1635
|
}
|
|
1529
1636
|
}
|
|
1530
1637
|
}
|
|
1531
1638
|
|
|
1639
|
+
int sent_count = 0;
|
|
1640
|
+
for (const auto& t : targets) {
|
|
1641
|
+
if (send_binary_to_peer_unlocked(t.socket, data, message_type, t.send_cipher, t.peer_id)) {
|
|
1642
|
+
sent_count++;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1532
1645
|
return sent_count;
|
|
1533
1646
|
}
|
|
1534
1647
|
|
|
@@ -1538,35 +1651,6 @@ int RatsClient::broadcast_string_to_peers(const std::string& data) {
|
|
|
1538
1651
|
return broadcast_binary_to_peers(binary_data, MessageDataType::STRING);
|
|
1539
1652
|
}
|
|
1540
1653
|
|
|
1541
|
-
bool RatsClient::parse_json_message(const std::string& message, nlohmann::json& out_json) {
|
|
1542
|
-
try {
|
|
1543
|
-
out_json = nlohmann::json::parse(message);
|
|
1544
|
-
return true;
|
|
1545
|
-
} catch (const nlohmann::json::exception& e) {
|
|
1546
|
-
LOG_CLIENT_ERROR("Failed to parse JSON message: " << e.what());
|
|
1547
|
-
return false;
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
// Helpers
|
|
1552
|
-
|
|
1553
|
-
// Per-socket synchronization helpers
|
|
1554
|
-
std::shared_ptr<std::mutex> RatsClient::get_socket_send_mutex(socket_t socket) {
|
|
1555
|
-
std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
|
|
1556
|
-
auto it = socket_send_mutexes_.find(socket);
|
|
1557
|
-
if (it == socket_send_mutexes_.end()) {
|
|
1558
|
-
// Create new mutex for this socket
|
|
1559
|
-
socket_send_mutexes_[socket] = std::make_shared<std::mutex>();
|
|
1560
|
-
return socket_send_mutexes_[socket];
|
|
1561
|
-
}
|
|
1562
|
-
return it->second;
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
void RatsClient::cleanup_socket_send_mutex(socket_t socket) {
|
|
1566
|
-
std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
|
|
1567
|
-
socket_send_mutexes_.erase(socket);
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
1654
|
// =========================================================================
|
|
1571
1655
|
// Peer Information and Management
|
|
1572
1656
|
// =========================================================================
|
|
@@ -1576,32 +1660,18 @@ std::string RatsClient::get_our_peer_id() const {
|
|
|
1576
1660
|
}
|
|
1577
1661
|
|
|
1578
1662
|
int RatsClient::get_peer_count_unlocked() const {
|
|
1579
|
-
//
|
|
1580
|
-
|
|
1581
|
-
for (const auto& pair : peers_) {
|
|
1582
|
-
if (pair.second.is_handshake_completed()) {
|
|
1583
|
-
count++;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
return count;
|
|
1663
|
+
// Returns the cached validated peer count (O(1) instead of O(N) scan)
|
|
1664
|
+
return validated_peer_count_.load(std::memory_order_relaxed);
|
|
1587
1665
|
}
|
|
1588
1666
|
|
|
1589
1667
|
int RatsClient::get_peer_count() const {
|
|
1590
|
-
std::
|
|
1591
|
-
return get_peer_count_unlocked();
|
|
1668
|
+
return validated_peer_count_.load(std::memory_order_relaxed);
|
|
1592
1669
|
}
|
|
1593
1670
|
|
|
1594
1671
|
std::string RatsClient::get_peer_id(socket_t socket) const {
|
|
1595
|
-
// Atomic operation - lock once and return copy to avoid race condition
|
|
1596
1672
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1597
|
-
auto
|
|
1598
|
-
|
|
1599
|
-
auto peer_it = peers_.find(it->second);
|
|
1600
|
-
if (peer_it != peers_.end()) {
|
|
1601
|
-
return peer_it->second.peer_id;
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
return "";
|
|
1673
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1674
|
+
return (peer_it != peers_.end()) ? peer_it->second.peer_id : "";
|
|
1605
1675
|
}
|
|
1606
1676
|
|
|
1607
1677
|
socket_t RatsClient::get_peer_socket_by_id(const std::string& peer_id) const {
|
|
@@ -1639,7 +1709,6 @@ std::vector<RatsPeer> RatsClient::get_validated_peers() const {
|
|
|
1639
1709
|
return result;
|
|
1640
1710
|
}
|
|
1641
1711
|
|
|
1642
|
-
|
|
1643
1712
|
std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::string& exclude_peer_id) const {
|
|
1644
1713
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1645
1714
|
|
|
@@ -1670,23 +1739,24 @@ std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::str
|
|
|
1670
1739
|
return selected_peers;
|
|
1671
1740
|
}
|
|
1672
1741
|
|
|
1673
|
-
|
|
1742
|
+
std::optional<RatsPeer> RatsClient::get_peer_by_id(const std::string& peer_id) const {
|
|
1674
1743
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1675
1744
|
auto it = peers_.find(peer_id);
|
|
1676
|
-
|
|
1745
|
+
if (it != peers_.end()) {
|
|
1746
|
+
return it->second;
|
|
1747
|
+
}
|
|
1748
|
+
return std::nullopt;
|
|
1677
1749
|
}
|
|
1678
1750
|
|
|
1679
|
-
|
|
1751
|
+
std::optional<RatsPeer> RatsClient::get_peer_by_socket(socket_t socket) const {
|
|
1680
1752
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1681
|
-
auto
|
|
1682
|
-
if (
|
|
1683
|
-
|
|
1684
|
-
return (peer_it != peers_.end()) ? &peer_it->second : nullptr;
|
|
1753
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1754
|
+
if (peer_it != peers_.end()) {
|
|
1755
|
+
return peer_it->second;
|
|
1685
1756
|
}
|
|
1686
|
-
return
|
|
1757
|
+
return std::nullopt;
|
|
1687
1758
|
}
|
|
1688
1759
|
|
|
1689
|
-
|
|
1690
1760
|
// Peer limit management methods
|
|
1691
1761
|
int RatsClient::get_max_peers() const {
|
|
1692
1762
|
return max_peers_;
|
|
@@ -1699,15 +1769,10 @@ void RatsClient::set_max_peers(int max_peers) {
|
|
|
1699
1769
|
|
|
1700
1770
|
bool RatsClient::is_peer_limit_reached() const {
|
|
1701
1771
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1702
|
-
|
|
1703
|
-
int connected_peers = get_peer_count_unlocked();
|
|
1704
|
-
if (connected_peers >= max_peers_) {
|
|
1705
|
-
return true;
|
|
1706
|
-
}
|
|
1707
|
-
return false;
|
|
1772
|
+
return get_peer_count_unlocked() >= max_peers_;
|
|
1708
1773
|
}
|
|
1709
1774
|
|
|
1710
|
-
std::string RatsClient::
|
|
1775
|
+
std::string RatsClient::generate_temporary_peer_id(socket_t socket, const std::string& connection_info) {
|
|
1711
1776
|
// Generate unique hash ID using timestamp, socket, connection info, and random component
|
|
1712
1777
|
auto now = std::chrono::high_resolution_clock::now();
|
|
1713
1778
|
auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
|
|
@@ -1758,7 +1823,7 @@ std::string RatsClient::normalize_peer_address(const std::string& ip, int port)
|
|
|
1758
1823
|
|
|
1759
1824
|
// =========================================================================
|
|
1760
1825
|
// Callback Registration
|
|
1761
|
-
//
|
|
1826
|
+
// =========================================================================
|
|
1762
1827
|
|
|
1763
1828
|
void RatsClient::set_connection_callback(ConnectionCallback callback) {
|
|
1764
1829
|
connection_callback_ = callback;
|
|
@@ -1780,298 +1845,6 @@ void RatsClient::set_disconnect_callback(DisconnectCallback callback) {
|
|
|
1780
1845
|
disconnect_callback_ = callback;
|
|
1781
1846
|
}
|
|
1782
1847
|
|
|
1783
|
-
// =========================================================================
|
|
1784
|
-
// Peer Discovery Methods
|
|
1785
|
-
// =========================================================================
|
|
1786
|
-
|
|
1787
|
-
bool RatsClient::start_dht_discovery(int dht_port) {
|
|
1788
|
-
if (dht_client_ && dht_client_->is_running()) {
|
|
1789
|
-
LOG_CLIENT_WARN("DHT discovery is already running");
|
|
1790
|
-
return true;
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
LOG_CLIENT_INFO("Starting DHT discovery on port " << dht_port <<
|
|
1794
|
-
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
1795
|
-
|
|
1796
|
-
dht_client_ = std::make_unique<DhtClient>(dht_port, bind_address_, data_directory_);
|
|
1797
|
-
if (!dht_client_->start()) {
|
|
1798
|
-
LOG_CLIENT_ERROR("Failed to start DHT client");
|
|
1799
|
-
dht_client_.reset();
|
|
1800
|
-
return false;
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
// Bootstrap with default nodes
|
|
1804
|
-
auto bootstrap_nodes = DhtClient::get_default_bootstrap_nodes();
|
|
1805
|
-
if (!dht_client_->bootstrap(bootstrap_nodes)) {
|
|
1806
|
-
LOG_CLIENT_WARN("Failed to bootstrap DHT");
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
// Start automatic peer discovery
|
|
1810
|
-
start_automatic_peer_discovery();
|
|
1811
|
-
|
|
1812
|
-
LOG_CLIENT_INFO("DHT discovery started successfully");
|
|
1813
|
-
return true;
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
void RatsClient::stop_dht_discovery() {
|
|
1817
|
-
if (!dht_client_) {
|
|
1818
|
-
return;
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
LOG_CLIENT_INFO("Stopping DHT discovery");
|
|
1822
|
-
|
|
1823
|
-
// Stop automatic peer discovery
|
|
1824
|
-
stop_automatic_peer_discovery();
|
|
1825
|
-
|
|
1826
|
-
dht_client_->stop();
|
|
1827
|
-
dht_client_.reset();
|
|
1828
|
-
LOG_CLIENT_INFO("DHT discovery stopped");
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::function<void(const std::vector<std::string>&)> callback) {
|
|
1832
|
-
if (!dht_client_ || !dht_client_->is_running()) {
|
|
1833
|
-
LOG_CLIENT_ERROR("DHT client not running");
|
|
1834
|
-
return false;
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
if (content_hash.length() != 40) { // 160-bit hash as hex string
|
|
1838
|
-
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
|
|
1839
|
-
return false;
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
LOG_CLIENT_INFO("Finding peers for content hash: " << content_hash);
|
|
1843
|
-
|
|
1844
|
-
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
1845
|
-
|
|
1846
|
-
return dht_client_->find_peers(info_hash, [this, callback](const std::vector<Peer>& peers, const InfoHash& info_hash) {
|
|
1847
|
-
// Convert Peer to string addresses for callback
|
|
1848
|
-
std::vector<std::string> peer_addresses;
|
|
1849
|
-
for (const auto& peer : peers) {
|
|
1850
|
-
peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
if (callback) {
|
|
1854
|
-
callback(peer_addresses);
|
|
1855
|
-
}
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port,
|
|
1860
|
-
std::function<void(const std::vector<std::string>&)> callback) {
|
|
1861
|
-
if (!dht_client_ || !dht_client_->is_running()) {
|
|
1862
|
-
LOG_CLIENT_ERROR("DHT client not running");
|
|
1863
|
-
return false;
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
if (content_hash.length() != 40) { // 160-bit hash as hex string
|
|
1867
|
-
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
|
|
1868
|
-
return false;
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
if (port == 0) {
|
|
1872
|
-
port = listen_port_;
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
|
|
1876
|
-
<< (callback ? " with peer callback" : ""));
|
|
1877
|
-
|
|
1878
|
-
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
1879
|
-
|
|
1880
|
-
// Create wrapper callback that converts Peer to string addresses (if callback provided)
|
|
1881
|
-
PeerDiscoveryCallback peer_callback = nullptr;
|
|
1882
|
-
if (callback) {
|
|
1883
|
-
peer_callback = [callback](const std::vector<Peer>& peers, const InfoHash& hash) {
|
|
1884
|
-
std::vector<std::string> peer_addresses;
|
|
1885
|
-
peer_addresses.reserve(peers.size());
|
|
1886
|
-
for (const auto& peer : peers) {
|
|
1887
|
-
peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
|
|
1888
|
-
}
|
|
1889
|
-
callback(peer_addresses);
|
|
1890
|
-
};
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
return dht_client_->announce_peer(info_hash, port, peer_callback);
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
bool RatsClient::is_dht_running() const {
|
|
1897
|
-
return dht_client_ && dht_client_->is_running();
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
size_t RatsClient::get_dht_routing_table_size() const {
|
|
1901
|
-
if (!dht_client_) {
|
|
1902
|
-
return 0;
|
|
1903
|
-
}
|
|
1904
|
-
return dht_client_->get_routing_table_size();
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
void RatsClient::handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash) {
|
|
1908
|
-
LOG_CLIENT_INFO("DHT discovered " << peers.size() << " peers for info hash: " << node_id_to_hex(info_hash));
|
|
1909
|
-
|
|
1910
|
-
// Auto-connect to discovered peers (optional behavior)
|
|
1911
|
-
for (const auto& peer : peers) {
|
|
1912
|
-
// Check if this peer should be ignored (local interface)
|
|
1913
|
-
if (should_ignore_peer(peer.ip, peer.port)) {
|
|
1914
|
-
LOG_CLIENT_DEBUG("Ignoring discovered peer " << peer.ip << ":" << peer.port << " - local interface address");
|
|
1915
|
-
continue;
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
// Check if we're already connected to this peer
|
|
1919
|
-
std::string normalized_peer_address = normalize_peer_address(peer.ip, peer.port);
|
|
1920
|
-
bool already_connected = is_already_connected_to_address(normalized_peer_address);
|
|
1921
|
-
|
|
1922
|
-
if (!already_connected) {
|
|
1923
|
-
// Check if peer limit is reached
|
|
1924
|
-
if (is_peer_limit_reached()) {
|
|
1925
|
-
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to DHT discovered peer " << peer.ip << ":" << peer.port);
|
|
1926
|
-
continue;
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
LOG_CLIENT_DEBUG("Attempting to connect to discovered peer: " << peer.ip << ":" << peer.port);
|
|
1930
|
-
|
|
1931
|
-
// Try to connect to the peer (non-blocking)
|
|
1932
|
-
std::thread([this, peer]() {
|
|
1933
|
-
if (connect_to_peer(peer.ip, peer.port)) {
|
|
1934
|
-
LOG_CLIENT_INFO("Successfully connected to DHT discovered peer: " << peer.ip << ":" << peer.port);
|
|
1935
|
-
} else {
|
|
1936
|
-
LOG_CLIENT_DEBUG("Failed to connect to DHT discovered peer: " << peer.ip << ":" << peer.port);
|
|
1937
|
-
}
|
|
1938
|
-
}).detach();
|
|
1939
|
-
} else {
|
|
1940
|
-
LOG_CLIENT_DEBUG("Already connected to discovered peer: " << normalized_peer_address);
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
void RatsClient::start_automatic_peer_discovery() {
|
|
1946
|
-
if (auto_discovery_running_.load()) {
|
|
1947
|
-
LOG_CLIENT_WARN("Automatic peer discovery is already running");
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
LOG_CLIENT_INFO("Starting automatic rats peer discovery");
|
|
1952
|
-
auto_discovery_running_.store(true);
|
|
1953
|
-
auto_discovery_thread_ = std::thread(&RatsClient::automatic_discovery_loop, this);
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
void RatsClient::stop_automatic_peer_discovery() {
|
|
1957
|
-
if (!auto_discovery_running_.load()) {
|
|
1958
|
-
return;
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
LOG_CLIENT_INFO("Stopping automatic peer discovery");
|
|
1962
|
-
auto_discovery_running_.store(false);
|
|
1963
|
-
|
|
1964
|
-
if (auto_discovery_thread_.joinable()) {
|
|
1965
|
-
auto_discovery_thread_.join();
|
|
1966
|
-
}
|
|
1967
|
-
|
|
1968
|
-
LOG_CLIENT_INFO("Automatic peer discovery stopped");
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
bool RatsClient::is_automatic_discovery_running() const {
|
|
1972
|
-
return auto_discovery_running_.load();
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
void RatsClient::automatic_discovery_loop() {
|
|
1976
|
-
LOG_CLIENT_INFO("Automatic peer discovery loop started");
|
|
1977
|
-
|
|
1978
|
-
// Initial delay to let DHT bootstrap
|
|
1979
|
-
{
|
|
1980
|
-
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
1981
|
-
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(5), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
1982
|
-
LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
|
|
1983
|
-
return;
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
// Announce immediately - this also discovers peers during traversal
|
|
1988
|
-
announce_rats_peer();
|
|
1989
|
-
|
|
1990
|
-
auto last_announce = std::chrono::steady_clock::now();
|
|
1991
|
-
|
|
1992
|
-
while (auto_discovery_running_.load()) {
|
|
1993
|
-
auto now = std::chrono::steady_clock::now();
|
|
1994
|
-
|
|
1995
|
-
// Announce combines both announcing our presence and discovering peers
|
|
1996
|
-
// Adjust frequency based on whether we have peers
|
|
1997
|
-
auto interval = (get_peer_count() == 0)
|
|
1998
|
-
? std::chrono::seconds(15) // Aggressive when no peers
|
|
1999
|
-
: std::chrono::minutes(10); // Less aggressive when connected
|
|
2000
|
-
|
|
2001
|
-
if (now - last_announce >= interval) {
|
|
2002
|
-
announce_rats_peer();
|
|
2003
|
-
last_announce = now;
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
// Use conditional variable for responsive shutdown
|
|
2007
|
-
{
|
|
2008
|
-
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
2009
|
-
if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
2010
|
-
break;
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
LOG_CLIENT_INFO("Automatic peer discovery loop stopped");
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
void RatsClient::announce_rats_peer() {
|
|
2019
|
-
if (!dht_client_ || !dht_client_->is_running()) {
|
|
2020
|
-
LOG_CLIENT_WARN("DHT client not running, cannot announce peer");
|
|
2021
|
-
return;
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
std::string discovery_hash = get_discovery_hash();
|
|
2025
|
-
LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
|
|
2026
|
-
|
|
2027
|
-
InfoHash info_hash = hex_to_node_id(discovery_hash);
|
|
2028
|
-
|
|
2029
|
-
if (dht_client_->is_announce_active(info_hash)) {
|
|
2030
|
-
LOG_CLIENT_WARN("Announce already in progress for info hash: " << node_id_to_hex(info_hash));
|
|
2031
|
-
return;
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
// Use announce with callback - combines announce and find_peers in one traversal
|
|
2035
|
-
// Peers discovered during traversal will be returned through the callback
|
|
2036
|
-
if (announce_for_hash(discovery_hash, listen_port_, [this, info_hash](const std::vector<std::string>& peer_addresses) {
|
|
2037
|
-
LOG_CLIENT_INFO("Announce discovered " << peer_addresses.size() << " peers during traversal");
|
|
2038
|
-
|
|
2039
|
-
// Convert peer addresses to Peer objects for handle_dht_peer_discovery()
|
|
2040
|
-
std::vector<Peer> peers;
|
|
2041
|
-
peers.reserve(peer_addresses.size());
|
|
2042
|
-
for (const auto& peer_address : peer_addresses) {
|
|
2043
|
-
std::string ip;
|
|
2044
|
-
int port;
|
|
2045
|
-
if (parse_address_string(peer_address, ip, port)) {
|
|
2046
|
-
peers.push_back(Peer(ip, port));
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
// Auto-connect to discovered peers
|
|
2051
|
-
if (!peers.empty()) {
|
|
2052
|
-
handle_dht_peer_discovery(peers, info_hash);
|
|
2053
|
-
}
|
|
2054
|
-
})) {
|
|
2055
|
-
LOG_CLIENT_DEBUG("Successfully started announce with peer discovery for discovery hash");
|
|
2056
|
-
} else {
|
|
2057
|
-
LOG_CLIENT_WARN("Failed to announce peer for discovery");
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
std::string RatsClient::get_discovery_hash() const {
|
|
2063
|
-
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
2064
|
-
// Generate discovery hash based on current protocol configuration
|
|
2065
|
-
std::string discovery_string = custom_protocol_name_ + "_peer_discovery_v" + custom_protocol_version_;
|
|
2066
|
-
return SHA1::hash(discovery_string);
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
std::string RatsClient::get_rats_peer_discovery_hash() {
|
|
2070
|
-
// Well-known hash for rats peer discovery
|
|
2071
|
-
// Compute SHA1 hash of "rats_peer_discovery_v1.0"
|
|
2072
|
-
return SHA1::hash("rats_peer_discovery_v1.0");
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
1848
|
// =========================================================================
|
|
2076
1849
|
// Protocol Configuration
|
|
2077
1850
|
// =========================================================================
|
|
@@ -2102,11 +1875,10 @@ std::string RatsClient::get_protocol_version() const {
|
|
|
2102
1875
|
// Message Exchange API
|
|
2103
1876
|
// =========================================================================
|
|
2104
1877
|
|
|
2105
|
-
|
|
2106
1878
|
void RatsClient::on(const std::string& message_type, MessageCallback callback) {
|
|
2107
1879
|
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2108
1880
|
message_handlers_[message_type].emplace_back(callback, false); // false = not once
|
|
2109
|
-
|
|
1881
|
+
LOG_CLIENT_DEBUG("Registered handler for message type: " << message_type);
|
|
2110
1882
|
}
|
|
2111
1883
|
|
|
2112
1884
|
void RatsClient::once(const std::string& message_type, MessageCallback callback) {
|
|
@@ -2134,15 +1906,15 @@ void RatsClient::send(const std::string& message_type, const nlohmann::json& dat
|
|
|
2134
1906
|
return;
|
|
2135
1907
|
}
|
|
2136
1908
|
|
|
2137
|
-
|
|
1909
|
+
LOG_CLIENT_DEBUG("Sending broadcast message type '" << message_type << "'");
|
|
2138
1910
|
|
|
2139
1911
|
// Create rats message
|
|
2140
1912
|
nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
|
|
2141
1913
|
|
|
2142
1914
|
// Broadcast to all validated peers
|
|
2143
|
-
int sent_count =
|
|
1915
|
+
int sent_count = broadcast_rats_message(message);
|
|
2144
1916
|
|
|
2145
|
-
|
|
1917
|
+
LOG_CLIENT_DEBUG("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
|
|
2146
1918
|
|
|
2147
1919
|
if (callback) {
|
|
2148
1920
|
if (sent_count > 0) {
|
|
@@ -2163,7 +1935,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2163
1935
|
return;
|
|
2164
1936
|
}
|
|
2165
1937
|
|
|
2166
|
-
|
|
1938
|
+
LOG_CLIENT_DEBUG("Sending targeted message type '" << message_type << "' to peer " << peer_id);
|
|
2167
1939
|
|
|
2168
1940
|
// Create rats message
|
|
2169
1941
|
nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
|
|
@@ -2203,7 +1975,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2203
1975
|
|
|
2204
1976
|
bool success = send_json_to_peer(target_socket, message);
|
|
2205
1977
|
|
|
2206
|
-
|
|
1978
|
+
LOG_CLIENT_DEBUG("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
|
|
2207
1979
|
|
|
2208
1980
|
if (callback) {
|
|
2209
1981
|
if (success) {
|
|
@@ -2217,54 +1989,41 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2217
1989
|
// Message exchange system helpers
|
|
2218
1990
|
void RatsClient::call_message_handlers(const std::string& message_type, const std::string& peer_id, const nlohmann::json& data) {
|
|
2219
1991
|
std::vector<MessageHandler> handlers_to_call;
|
|
2220
|
-
std::vector<MessageHandler> remaining_handlers;
|
|
2221
1992
|
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
// Get handlers to call and identify once handlers
|
|
1993
|
+
// Get handlers to call and remove once handlers atomically
|
|
2225
1994
|
{
|
|
2226
1995
|
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2227
1996
|
auto it = message_handlers_.find(message_type);
|
|
2228
|
-
if (it
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
// Keep only non-once handlers for the remaining list
|
|
2232
|
-
for (const auto& handler : it->second) {
|
|
2233
|
-
if (!handler.is_once) {
|
|
2234
|
-
remaining_handlers.push_back(handler);
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
// Update the handlers list (removes once handlers)
|
|
2239
|
-
it->second = remaining_handlers;
|
|
2240
|
-
} else {
|
|
2241
|
-
LOG_CLIENT_WARN("No handlers registered for message type '" << message_type << "'");
|
|
1997
|
+
if (it == message_handlers_.end()) {
|
|
1998
|
+
LOG_CLIENT_DEBUG("No handlers registered for message type '" << message_type << "'");
|
|
1999
|
+
return;
|
|
2242
2000
|
}
|
|
2001
|
+
|
|
2002
|
+
handlers_to_call = it->second;
|
|
2003
|
+
|
|
2004
|
+
// Remove once handlers using erase-remove idiom
|
|
2005
|
+
it->second.erase(
|
|
2006
|
+
std::remove_if(it->second.begin(), it->second.end(),
|
|
2007
|
+
[](const MessageHandler& h) { return h.is_once; }),
|
|
2008
|
+
it->second.end());
|
|
2243
2009
|
}
|
|
2244
2010
|
|
|
2245
|
-
|
|
2011
|
+
LOG_CLIENT_DEBUG("Calling " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
|
|
2246
2012
|
|
|
2247
2013
|
// Call handlers outside of mutex to avoid deadlock
|
|
2248
2014
|
for (const auto& handler : handlers_to_call) {
|
|
2249
2015
|
try {
|
|
2250
|
-
LOG_CLIENT_INFO("Calling handler for message type '" << message_type << "'");
|
|
2251
2016
|
handler.callback(peer_id, data);
|
|
2252
|
-
LOG_CLIENT_INFO("Handler for message type '" << message_type << "' completed successfully");
|
|
2253
2017
|
} catch (const std::exception& e) {
|
|
2254
2018
|
LOG_CLIENT_ERROR("Exception in message handler for type '" << message_type << "': " << e.what());
|
|
2255
2019
|
} catch (...) {
|
|
2256
2020
|
LOG_CLIENT_ERROR("Unknown exception in message handler for type '" << message_type << "'");
|
|
2257
2021
|
}
|
|
2258
2022
|
}
|
|
2259
|
-
|
|
2260
|
-
if (!handlers_to_call.empty()) {
|
|
2261
|
-
LOG_CLIENT_INFO("Called " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
|
|
2262
|
-
}
|
|
2263
2023
|
}
|
|
2264
2024
|
|
|
2265
|
-
|
|
2266
2025
|
// =========================================================================
|
|
2267
|
-
// Rats
|
|
2026
|
+
// Rats Protocol Message Handling
|
|
2268
2027
|
// =========================================================================
|
|
2269
2028
|
|
|
2270
2029
|
nlohmann::json RatsClient::create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id) {
|
|
@@ -2279,26 +2038,26 @@ nlohmann::json RatsClient::create_rats_message(const std::string& type, const nl
|
|
|
2279
2038
|
return message;
|
|
2280
2039
|
}
|
|
2281
2040
|
|
|
2282
|
-
void RatsClient::handle_rats_message(socket_t socket, const std::string&
|
|
2041
|
+
void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_id, const nlohmann::json& message) {
|
|
2283
2042
|
try {
|
|
2284
2043
|
std::string message_type = message.value("type", "");
|
|
2285
2044
|
nlohmann::json payload = message.value("payload", nlohmann::json::object());
|
|
2286
2045
|
std::string sender_peer_id = message.value("sender_peer_id", "");
|
|
2287
2046
|
|
|
2288
|
-
LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " <<
|
|
2047
|
+
LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_id);
|
|
2289
2048
|
|
|
2290
2049
|
// Call registered message handlers for all message types (including custom ones)
|
|
2291
|
-
call_message_handlers(message_type, sender_peer_id.empty() ?
|
|
2050
|
+
call_message_handlers(message_type, sender_peer_id.empty() ? peer_id : sender_peer_id, payload);
|
|
2292
2051
|
|
|
2293
2052
|
// Handle built-in message types for internal functionality
|
|
2294
2053
|
if (message_type == "peer") {
|
|
2295
|
-
handle_peer_exchange_message(socket,
|
|
2054
|
+
handle_peer_exchange_message(socket, peer_id, payload);
|
|
2296
2055
|
}
|
|
2297
2056
|
else if (message_type == "peers_request") {
|
|
2298
|
-
handle_peers_request_message(socket,
|
|
2057
|
+
handle_peers_request_message(socket, peer_id, payload);
|
|
2299
2058
|
}
|
|
2300
2059
|
else if (message_type == "peers_response") {
|
|
2301
|
-
handle_peers_response_message(socket,
|
|
2060
|
+
handle_peers_response_message(socket, peer_id, payload);
|
|
2302
2061
|
}
|
|
2303
2062
|
// Custom message types are now handled by registered handlers above
|
|
2304
2063
|
// No need for else clause - all message types are valid if they have registered handlers
|
|
@@ -2308,54 +2067,39 @@ void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_ha
|
|
|
2308
2067
|
}
|
|
2309
2068
|
}
|
|
2310
2069
|
|
|
2311
|
-
void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string&
|
|
2070
|
+
void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
|
|
2312
2071
|
try {
|
|
2313
|
-
std::string
|
|
2314
|
-
int
|
|
2315
|
-
std::string
|
|
2316
|
-
|
|
2317
|
-
if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
|
|
2318
|
-
LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_hash_id);
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
LOG_CLIENT_INFO("Received peer exchange: " << peer_ip << ":" << peer_port << " (peer_id: " << peer_id << ")");
|
|
2072
|
+
std::string exchanged_ip = payload.value("ip", "");
|
|
2073
|
+
int exchanged_port = payload.value("port", 0);
|
|
2074
|
+
std::string exchanged_peer_id = payload.value("peer_id", "");
|
|
2323
2075
|
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
LOG_CLIENT_DEBUG("Ignoring exchanged peer " << peer_ip << ":" << peer_port << " - local interface address");
|
|
2076
|
+
if (exchanged_ip.empty() || exchanged_port <= 0 || exchanged_peer_id.empty()) {
|
|
2077
|
+
LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_id);
|
|
2327
2078
|
return;
|
|
2328
2079
|
}
|
|
2329
2080
|
|
|
2330
|
-
|
|
2331
|
-
std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
|
|
2332
|
-
if (is_already_connected_to_address(normalized_peer_address)) {
|
|
2333
|
-
LOG_CLIENT_DEBUG("Already connected to exchanged peer " << normalized_peer_address);
|
|
2334
|
-
return;
|
|
2335
|
-
}
|
|
2081
|
+
LOG_CLIENT_INFO("Received peer exchange: " << exchanged_ip << ":" << exchanged_port << " (peer_id: " << exchanged_peer_id << ")");
|
|
2336
2082
|
|
|
2337
|
-
|
|
2338
|
-
if (is_peer_limit_reached()) {
|
|
2339
|
-
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to exchanged peer " << peer_ip << ":" << peer_port);
|
|
2083
|
+
if (!can_connect_to_peer(exchanged_ip, exchanged_port)) {
|
|
2340
2084
|
return;
|
|
2341
2085
|
}
|
|
2342
2086
|
|
|
2343
2087
|
// Try to connect to the exchanged peer (non-blocking)
|
|
2344
|
-
add_managed_thread(std::thread([this,
|
|
2345
|
-
if (connect_to_peer(
|
|
2346
|
-
LOG_CLIENT_INFO("Successfully connected to exchanged peer: " <<
|
|
2088
|
+
add_managed_thread(std::thread([this, exchanged_ip, exchanged_port, exchanged_peer_id]() {
|
|
2089
|
+
if (connect_to_peer(exchanged_ip, exchanged_port)) {
|
|
2090
|
+
LOG_CLIENT_INFO("Successfully connected to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
|
|
2347
2091
|
} else {
|
|
2348
|
-
LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " <<
|
|
2092
|
+
LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
|
|
2349
2093
|
}
|
|
2350
|
-
}), "peer-exchange-connect-" +
|
|
2094
|
+
}), "peer-exchange-connect-" + exchanged_peer_id.substr(0, 8));
|
|
2351
2095
|
|
|
2352
2096
|
} catch (const nlohmann::json::exception& e) {
|
|
2353
2097
|
LOG_CLIENT_ERROR("Failed to handle peer exchange message: " << e.what());
|
|
2354
2098
|
}
|
|
2355
2099
|
}
|
|
2356
2100
|
|
|
2357
|
-
// General broadcasting
|
|
2358
|
-
int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id) {
|
|
2101
|
+
// General broadcasting function
|
|
2102
|
+
int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id, bool validated_only) {
|
|
2359
2103
|
// Serialize JSON once before iterating
|
|
2360
2104
|
std::string json_string;
|
|
2361
2105
|
try {
|
|
@@ -2366,53 +2110,27 @@ int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std:
|
|
|
2366
2110
|
}
|
|
2367
2111
|
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
2368
2112
|
|
|
2369
|
-
|
|
2113
|
+
// Collect targets under lock, then enqueue outside
|
|
2114
|
+
std::vector<PeerSendTarget> targets;
|
|
2370
2115
|
{
|
|
2371
2116
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
// Don't send to excluded peer
|
|
2117
|
+
targets.reserve(peers_.size());
|
|
2118
|
+
for (const auto& [id, peer] : peers_) {
|
|
2375
2119
|
if (!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) {
|
|
2376
2120
|
continue;
|
|
2377
2121
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
|
|
2381
|
-
if (send_binary_to_peer_unlocked(peer.socket, binary_data, MessageDataType::JSON, send_cipher, peer.peer_id)) {
|
|
2382
|
-
sent_count++;
|
|
2122
|
+
if (validated_only && !peer.is_handshake_completed()) {
|
|
2123
|
+
continue;
|
|
2383
2124
|
}
|
|
2125
|
+
targets.push_back({peer.socket, peer.peer_id,
|
|
2126
|
+
peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
|
|
2384
2127
|
}
|
|
2385
2128
|
}
|
|
2386
|
-
return sent_count;
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
int RatsClient::broadcast_rats_message_to_validated_peers(const nlohmann::json& message, const std::string& exclude_peer_id) {
|
|
2390
|
-
// Serialize JSON once before iterating
|
|
2391
|
-
std::string json_string;
|
|
2392
|
-
try {
|
|
2393
|
-
json_string = message.dump();
|
|
2394
|
-
} catch (const nlohmann::json::exception& e) {
|
|
2395
|
-
LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
|
|
2396
|
-
return 0;
|
|
2397
|
-
}
|
|
2398
|
-
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
2399
2129
|
|
|
2400
2130
|
int sent_count = 0;
|
|
2401
|
-
{
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
const RatsPeer& peer = pair.second;
|
|
2405
|
-
// Don't send to excluded peer and only send to peers with completed handshake
|
|
2406
|
-
if ((!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) ||
|
|
2407
|
-
!peer.is_handshake_completed()) {
|
|
2408
|
-
continue;
|
|
2409
|
-
}
|
|
2410
|
-
|
|
2411
|
-
// Use unlocked version since we already hold peers_mutex_
|
|
2412
|
-
rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
|
|
2413
|
-
if (send_binary_to_peer_unlocked(peer.socket, binary_data, MessageDataType::JSON, send_cipher, peer.peer_id)) {
|
|
2414
|
-
sent_count++;
|
|
2415
|
-
}
|
|
2131
|
+
for (const auto& t : targets) {
|
|
2132
|
+
if (send_binary_to_peer_unlocked(t.socket, binary_data, MessageDataType::JSON, t.send_cipher, t.peer_id)) {
|
|
2133
|
+
sent_count++;
|
|
2416
2134
|
}
|
|
2417
2135
|
}
|
|
2418
2136
|
return sent_count;
|
|
@@ -2441,7 +2159,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
|
|
|
2441
2159
|
nlohmann::json message = create_peer_exchange_message(new_peer);
|
|
2442
2160
|
|
|
2443
2161
|
// Broadcast to all validated peers except the new peer
|
|
2444
|
-
int sent_count =
|
|
2162
|
+
int sent_count = broadcast_rats_message(message, new_peer.peer_id);
|
|
2445
2163
|
|
|
2446
2164
|
LOG_CLIENT_INFO("Broadcasted peer exchange message for " << new_peer.ip << ":" << new_peer.port
|
|
2447
2165
|
<< " to " << sent_count << " peers");
|
|
@@ -2450,7 +2168,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
|
|
|
2450
2168
|
// Peers request/response system implementation
|
|
2451
2169
|
nlohmann::json RatsClient::create_peers_request_message(const std::string& sender_peer_id) {
|
|
2452
2170
|
nlohmann::json payload;
|
|
2453
|
-
payload["max_peers"] =
|
|
2171
|
+
payload["max_peers"] = MAX_PEERS_REQUEST_COUNT;
|
|
2454
2172
|
payload["requester_info"] = {
|
|
2455
2173
|
{"listen_port", listen_port_},
|
|
2456
2174
|
{"peer_count", get_peer_count()}
|
|
@@ -2464,39 +2182,38 @@ nlohmann::json RatsClient::create_peers_response_message(const std::vector<RatsP
|
|
|
2464
2182
|
nlohmann::json peers_array = nlohmann::json::array();
|
|
2465
2183
|
|
|
2466
2184
|
for (const auto& peer : peers) {
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2185
|
+
peers_array.push_back({
|
|
2186
|
+
{"ip", peer.ip},
|
|
2187
|
+
{"port", peer.port},
|
|
2188
|
+
{"peer_id", peer.peer_id},
|
|
2189
|
+
{"connection_type", peer.is_outgoing ? "outgoing" : "incoming"}
|
|
2190
|
+
});
|
|
2473
2191
|
}
|
|
2474
2192
|
|
|
2475
|
-
payload["peers"] = peers_array;
|
|
2193
|
+
payload["peers"] = std::move(peers_array);
|
|
2476
2194
|
payload["total_peers"] = get_peer_count();
|
|
2477
2195
|
|
|
2478
2196
|
return create_rats_message("peers_response", payload, sender_peer_id);
|
|
2479
2197
|
}
|
|
2480
2198
|
|
|
2481
|
-
|
|
2482
|
-
void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
|
|
2199
|
+
void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
|
|
2483
2200
|
try {
|
|
2484
|
-
int max_peers = payload.value("max_peers",
|
|
2201
|
+
int max_peers = payload.value("max_peers", MAX_PEERS_REQUEST_COUNT);
|
|
2485
2202
|
|
|
2486
|
-
LOG_CLIENT_INFO("Received peers request from " <<
|
|
2203
|
+
LOG_CLIENT_INFO("Received peers request from " << peer_id << " for up to " << max_peers << " peers");
|
|
2487
2204
|
|
|
2488
2205
|
// Get random peers excluding the requester
|
|
2489
|
-
std::vector<RatsPeer> random_peers = get_random_peers(max_peers,
|
|
2206
|
+
std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_id);
|
|
2490
2207
|
|
|
2491
|
-
LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " <<
|
|
2208
|
+
LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_id);
|
|
2492
2209
|
|
|
2493
2210
|
// Create and send peers response
|
|
2494
|
-
nlohmann::json response_message = create_peers_response_message(random_peers,
|
|
2211
|
+
nlohmann::json response_message = create_peers_response_message(random_peers, peer_id);
|
|
2495
2212
|
|
|
2496
2213
|
if (!send_json_to_peer(socket, response_message)) {
|
|
2497
|
-
LOG_CLIENT_ERROR("Failed to send peers response to " <<
|
|
2214
|
+
LOG_CLIENT_ERROR("Failed to send peers response to " << peer_id);
|
|
2498
2215
|
} else {
|
|
2499
|
-
LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " <<
|
|
2216
|
+
LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_id);
|
|
2500
2217
|
}
|
|
2501
2218
|
|
|
2502
2219
|
} catch (const nlohmann::json::exception& e) {
|
|
@@ -2504,55 +2221,39 @@ void RatsClient::handle_peers_request_message(socket_t socket, const std::string
|
|
|
2504
2221
|
}
|
|
2505
2222
|
}
|
|
2506
2223
|
|
|
2507
|
-
void RatsClient::handle_peers_response_message(socket_t socket, const std::string&
|
|
2224
|
+
void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
|
|
2508
2225
|
try {
|
|
2509
2226
|
nlohmann::json peers_array = payload.value("peers", nlohmann::json::array());
|
|
2510
2227
|
int total_peers = payload.value("total_peers", 0);
|
|
2511
2228
|
|
|
2512
|
-
LOG_CLIENT_INFO("Received peers response from " <<
|
|
2229
|
+
LOG_CLIENT_INFO("Received peers response from " << peer_id << " with " << peers_array.size()
|
|
2513
2230
|
<< " peers (total: " << total_peers << ")");
|
|
2514
2231
|
|
|
2515
2232
|
// Process each peer in the response
|
|
2516
2233
|
for (const auto& peer_info : peers_array) {
|
|
2517
|
-
std::string
|
|
2518
|
-
int
|
|
2519
|
-
std::string
|
|
2520
|
-
|
|
2521
|
-
if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
|
|
2522
|
-
LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_hash_id);
|
|
2523
|
-
continue;
|
|
2524
|
-
}
|
|
2234
|
+
std::string resp_ip = peer_info.value("ip", "");
|
|
2235
|
+
int resp_port = peer_info.value("port", 0);
|
|
2236
|
+
std::string resp_peer_id = peer_info.value("peer_id", "");
|
|
2525
2237
|
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
// Check if we should ignore this peer (local interface)
|
|
2529
|
-
if (should_ignore_peer(peer_ip, peer_port)) {
|
|
2530
|
-
LOG_CLIENT_DEBUG("Ignoring peer from response " << peer_ip << ":" << peer_port << " - local interface address");
|
|
2238
|
+
if (resp_ip.empty() || resp_port <= 0 || resp_peer_id.empty()) {
|
|
2239
|
+
LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_id);
|
|
2531
2240
|
continue;
|
|
2532
2241
|
}
|
|
2533
2242
|
|
|
2534
|
-
|
|
2535
|
-
std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
|
|
2536
|
-
if (is_already_connected_to_address(normalized_peer_address)) {
|
|
2537
|
-
LOG_CLIENT_DEBUG("Already connected to peer from response " << normalized_peer_address);
|
|
2538
|
-
continue;
|
|
2539
|
-
}
|
|
2243
|
+
LOG_CLIENT_DEBUG("Processing peer from response: " << resp_ip << ":" << resp_port << " (peer_id: " << resp_peer_id << ")");
|
|
2540
2244
|
|
|
2541
|
-
|
|
2542
|
-
if (is_peer_limit_reached()) {
|
|
2543
|
-
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to peer from response " << peer_ip << ":" << peer_port);
|
|
2245
|
+
if (!can_connect_to_peer(resp_ip, resp_port)) {
|
|
2544
2246
|
continue;
|
|
2545
2247
|
}
|
|
2546
2248
|
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
LOG_CLIENT_INFO("Successfully connected to peer from response: " << peer_ip << ":" << peer_port);
|
|
2249
|
+
LOG_CLIENT_DEBUG("Attempting to connect to peer from response: " << resp_ip << ":" << resp_port);
|
|
2250
|
+
add_managed_thread(std::thread([this, resp_ip, resp_port, resp_peer_id]() {
|
|
2251
|
+
if (connect_to_peer(resp_ip, resp_port)) {
|
|
2252
|
+
LOG_CLIENT_INFO("Successfully connected to peer from response: " << resp_ip << ":" << resp_port);
|
|
2552
2253
|
} else {
|
|
2553
|
-
LOG_CLIENT_DEBUG("Failed to connect to peer from response: " <<
|
|
2254
|
+
LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << resp_ip << ":" << resp_port);
|
|
2554
2255
|
}
|
|
2555
|
-
}), "peer-response-connect-" +
|
|
2256
|
+
}), "peer-response-connect-" + resp_peer_id.substr(0, 8));
|
|
2556
2257
|
}
|
|
2557
2258
|
|
|
2558
2259
|
} catch (const nlohmann::json::exception& e) {
|
|
@@ -2571,49 +2272,7 @@ void RatsClient::send_peers_request(socket_t socket, const std::string& our_peer
|
|
|
2571
2272
|
}
|
|
2572
2273
|
|
|
2573
2274
|
// =========================================================================
|
|
2574
|
-
//
|
|
2575
|
-
// =========================================================================
|
|
2576
|
-
|
|
2577
|
-
nlohmann::json RatsClient::get_connection_statistics() const {
|
|
2578
|
-
nlohmann::json stats;
|
|
2579
|
-
|
|
2580
|
-
{
|
|
2581
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2582
|
-
stats["total_peers"] = peers_.size();
|
|
2583
|
-
stats["validated_peers"] = get_peer_count_unlocked();
|
|
2584
|
-
stats["max_peers"] = max_peers_;
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
stats["running"] = is_running();
|
|
2588
|
-
stats["listen_port"] = listen_port_;
|
|
2589
|
-
stats["our_peer_id"] = get_our_peer_id();
|
|
2590
|
-
stats["encryption_enabled"] = is_encryption_enabled();
|
|
2591
|
-
|
|
2592
|
-
// DHT statistics
|
|
2593
|
-
if (dht_client_ && dht_client_->is_running()) {
|
|
2594
|
-
stats["dht_running"] = true;
|
|
2595
|
-
stats["dht_routing_table_size"] = get_dht_routing_table_size();
|
|
2596
|
-
} else {
|
|
2597
|
-
stats["dht_running"] = false;
|
|
2598
|
-
}
|
|
2599
|
-
|
|
2600
|
-
// mDNS statistics
|
|
2601
|
-
stats["mdns_running"] = is_mdns_running();
|
|
2602
|
-
|
|
2603
|
-
// Reconnection statistics
|
|
2604
|
-
{
|
|
2605
|
-
std::lock_guard<std::mutex> lock(reconnect_mutex_);
|
|
2606
|
-
stats["reconnect_enabled"] = reconnect_config_.enabled;
|
|
2607
|
-
stats["reconnect_queue_size"] = reconnect_queue_.size();
|
|
2608
|
-
stats["reconnect_max_attempts"] = reconnect_config_.max_attempts;
|
|
2609
|
-
}
|
|
2610
|
-
|
|
2611
|
-
return stats;
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
// =========================================================================
|
|
2616
|
-
// Helper functions
|
|
2275
|
+
// Helper Functions
|
|
2617
2276
|
// =========================================================================
|
|
2618
2277
|
|
|
2619
2278
|
std::unique_ptr<RatsClient> create_rats_client(int listen_port) {
|
|
@@ -2683,52 +2342,4 @@ bool RatsClient::parse_address_string(const std::string& address_str, std::strin
|
|
|
2683
2342
|
return !out_ip.empty() && out_port > 0 && out_port <= 65535;
|
|
2684
2343
|
}
|
|
2685
2344
|
|
|
2686
|
-
// Cached formatting helpers - computed once on first use
|
|
2687
|
-
static const std::string& get_box_separator() {
|
|
2688
|
-
static const std::string separator = supports_unicode() ?
|
|
2689
|
-
"════════════════════════════════════════════════════════════════════" :
|
|
2690
|
-
"=====================================================================";
|
|
2691
|
-
return separator;
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
static const std::string& get_box_vertical() {
|
|
2695
|
-
static const std::string vertical = supports_unicode() ? "│" : "|";
|
|
2696
|
-
return vertical;
|
|
2697
|
-
}
|
|
2698
|
-
|
|
2699
|
-
static const std::string& get_checkmark() {
|
|
2700
|
-
static const std::string checkmark = supports_unicode() ? "✓" : "[*]";
|
|
2701
|
-
return checkmark;
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
void RatsClient::log_handshake_completion_unlocked(const RatsPeer& peer) {
|
|
2705
|
-
// Calculate connection duration
|
|
2706
|
-
auto now = std::chrono::steady_clock::now();
|
|
2707
|
-
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - peer.connected_at);
|
|
2708
|
-
|
|
2709
|
-
// Get current peer count (assumes peers_mutex_ is already locked)
|
|
2710
|
-
int current_peer_count = get_peer_count_unlocked();
|
|
2711
|
-
|
|
2712
|
-
// Create visually appealing log output
|
|
2713
|
-
std::string connection_type = peer.is_outgoing ? "OUTGOING" : "INCOMING";
|
|
2714
|
-
const std::string& separator = get_box_separator();
|
|
2715
|
-
const std::string& vertical = get_box_vertical();
|
|
2716
|
-
const std::string& checkmark = get_checkmark();
|
|
2717
|
-
|
|
2718
|
-
LOG_CLIENT_INFO("");
|
|
2719
|
-
LOG_CLIENT_INFO(separator);
|
|
2720
|
-
LOG_CLIENT_INFO(checkmark << " HANDSHAKE COMPLETED - NEW PEER CONNECTED");
|
|
2721
|
-
LOG_CLIENT_INFO(separator);
|
|
2722
|
-
LOG_CLIENT_INFO(vertical << " Peer ID : " << peer.peer_id);
|
|
2723
|
-
LOG_CLIENT_INFO(vertical << " Address : " << peer.ip << ":" << peer.port);
|
|
2724
|
-
LOG_CLIENT_INFO(vertical << " Connection : " << connection_type);
|
|
2725
|
-
LOG_CLIENT_INFO(vertical << " Protocol Ver. : " << peer.version);
|
|
2726
|
-
LOG_CLIENT_INFO(vertical << " Socket : " << peer.socket);
|
|
2727
|
-
LOG_CLIENT_INFO(vertical << " Duration : " << duration.count() << "ms");
|
|
2728
|
-
LOG_CLIENT_INFO(vertical << " Network Peers : " << current_peer_count << "/" << max_peers_);
|
|
2729
|
-
|
|
2730
|
-
LOG_CLIENT_INFO(separator);
|
|
2731
|
-
LOG_CLIENT_INFO("");
|
|
2732
|
-
}
|
|
2733
|
-
|
|
2734
2345
|
} // namespace librats
|