librats 0.7.2 → 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 +10 -2
- package/native-src/src/bt_network.cpp +296 -183
- package/native-src/src/bt_network.h +25 -5
- package/native-src/src/io_poller.cpp +910 -0
- package/native-src/src/io_poller.h +138 -0
- package/native-src/src/librats.cpp +809 -1236
- package/native-src/src/librats.h +146 -54
- package/native-src/src/librats_bittorrent.cpp +1 -5
- package/native-src/src/librats_c.cpp +1 -1
- 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 +1 -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/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");
|
|
@@ -174,12 +146,20 @@ bool RatsClient::start() {
|
|
|
174
146
|
}
|
|
175
147
|
}
|
|
176
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
|
+
|
|
177
157
|
running_.store(true);
|
|
178
158
|
|
|
179
|
-
// Start
|
|
180
|
-
|
|
159
|
+
// Start IO thread (single-threaded event loop for all sockets)
|
|
160
|
+
io_thread_ = std::thread(&RatsClient::io_loop, this);
|
|
181
161
|
|
|
182
|
-
// Start management thread
|
|
162
|
+
// Start management thread (handshake timeouts, reconnection, thread cleanup)
|
|
183
163
|
management_thread_ = std::thread(&RatsClient::management_loop, this);
|
|
184
164
|
|
|
185
165
|
// Start GossipSub
|
|
@@ -192,7 +172,7 @@ bool RatsClient::start() {
|
|
|
192
172
|
// Attempt to reconnect to saved peers
|
|
193
173
|
add_managed_thread(std::thread([this]() {
|
|
194
174
|
// Give the server some time to fully initialize
|
|
195
|
-
std::this_thread::sleep_for(std::chrono::milliseconds(
|
|
175
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(PEER_RECONNECT_DELAY_MS));
|
|
196
176
|
int reconnect_attempts = load_and_reconnect_peers();
|
|
197
177
|
if (reconnect_attempts > 0) {
|
|
198
178
|
LOG_CLIENT_INFO("Attempted to reconnect to " << reconnect_attempts << " saved peers");
|
|
@@ -200,7 +180,7 @@ bool RatsClient::start() {
|
|
|
200
180
|
|
|
201
181
|
// Also attempt to reconnect to historical peers if not at peer limit
|
|
202
182
|
if (!is_peer_limit_reached()) {
|
|
203
|
-
std::this_thread::sleep_for(std::chrono::milliseconds(
|
|
183
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(HISTORICAL_RECONNECT_DELAY_MS));
|
|
204
184
|
int historical_attempts = load_and_reconnect_historical_peers();
|
|
205
185
|
if (historical_attempts > 0) {
|
|
206
186
|
LOG_CLIENT_INFO("Attempted to reconnect to " << historical_attempts << " historical peers");
|
|
@@ -222,8 +202,7 @@ void RatsClient::stop() {
|
|
|
222
202
|
if (gossipsub_) {
|
|
223
203
|
gossipsub_->stop();
|
|
224
204
|
}
|
|
225
|
-
|
|
226
|
-
|
|
205
|
+
|
|
227
206
|
// Trigger immediate shutdown of all background threads
|
|
228
207
|
shutdown_all_threads();
|
|
229
208
|
|
|
@@ -246,25 +225,25 @@ void RatsClient::stop() {
|
|
|
246
225
|
manual_disconnect_peers_.clear();
|
|
247
226
|
}
|
|
248
227
|
|
|
249
|
-
// Close all peer connections
|
|
228
|
+
// Close all peer connections and remove from poller
|
|
250
229
|
{
|
|
251
230
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
252
231
|
LOG_CLIENT_INFO("Closing " << peers_.size() << " peer connections");
|
|
253
232
|
for (const auto& pair : peers_) {
|
|
254
233
|
const RatsPeer& peer = pair.second;
|
|
234
|
+
if (poller_) poller_->remove(peer.socket);
|
|
255
235
|
close_socket(peer.socket, true);
|
|
256
236
|
}
|
|
257
237
|
peers_.clear();
|
|
258
238
|
socket_to_peer_id_.clear();
|
|
259
239
|
address_to_peer_id_.clear();
|
|
240
|
+
validated_peer_count_.store(0, std::memory_order_relaxed);
|
|
260
241
|
}
|
|
261
242
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
LOG_CLIENT_DEBUG("Waiting for server thread to finish");
|
|
267
|
-
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();
|
|
268
247
|
}
|
|
269
248
|
|
|
270
249
|
// Wait for management thread to finish
|
|
@@ -273,11 +252,14 @@ void RatsClient::stop() {
|
|
|
273
252
|
management_thread_.join();
|
|
274
253
|
}
|
|
275
254
|
|
|
255
|
+
// Destroy poller after threads have stopped
|
|
256
|
+
poller_.reset();
|
|
257
|
+
|
|
276
258
|
// Join all managed threads for graceful cleanup
|
|
277
259
|
join_all_active_threads();
|
|
278
260
|
|
|
279
261
|
cleanup_socket_library();
|
|
280
|
-
|
|
262
|
+
|
|
281
263
|
// Save configuration before stopping
|
|
282
264
|
save_configuration();
|
|
283
265
|
|
|
@@ -289,7 +271,7 @@ void RatsClient::shutdown_all_threads() {
|
|
|
289
271
|
|
|
290
272
|
// Signal all threads to stop
|
|
291
273
|
running_.store(false);
|
|
292
|
-
|
|
274
|
+
|
|
293
275
|
// Call parent class to handle thread management shutdown
|
|
294
276
|
ThreadManager::shutdown_all_threads();
|
|
295
277
|
}
|
|
@@ -311,456 +293,564 @@ std::string RatsClient::get_bind_address() const {
|
|
|
311
293
|
}
|
|
312
294
|
|
|
313
295
|
// =========================================================================
|
|
314
|
-
//
|
|
296
|
+
// Async I/O – single-threaded event loop
|
|
315
297
|
// =========================================================================
|
|
316
298
|
|
|
317
|
-
void RatsClient::
|
|
318
|
-
|
|
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];
|
|
319
304
|
|
|
320
305
|
while (running_.load()) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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;
|
|
324
|
+
}
|
|
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);
|
|
325
342
|
}
|
|
326
|
-
break;
|
|
327
343
|
}
|
|
328
344
|
|
|
329
|
-
//
|
|
330
|
-
|
|
345
|
+
// Handle disconnections outside the event loop
|
|
346
|
+
for (socket_t fd : to_disconnect) {
|
|
347
|
+
handle_disconnect(fd);
|
|
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;
|
|
362
|
+
|
|
363
|
+
std::string peer_address = get_peer_address(client);
|
|
331
364
|
if (peer_address.empty()) {
|
|
332
|
-
|
|
333
|
-
close_socket(client_socket);
|
|
365
|
+
close_socket(client);
|
|
334
366
|
continue;
|
|
335
367
|
}
|
|
336
368
|
|
|
337
|
-
// Parse IP and port from peer_address
|
|
338
369
|
std::string ip;
|
|
339
370
|
int port = 0;
|
|
340
371
|
if (!parse_address_string(peer_address, ip, port)) {
|
|
341
|
-
|
|
342
|
-
close_socket(client_socket);
|
|
372
|
+
close_socket(client);
|
|
343
373
|
continue;
|
|
344
374
|
}
|
|
345
375
|
|
|
346
|
-
std::string
|
|
376
|
+
std::string normalized = normalize_peer_address(ip, port);
|
|
347
377
|
|
|
348
|
-
// Check if peer limit is reached
|
|
349
378
|
if (is_peer_limit_reached()) {
|
|
350
|
-
LOG_SERVER_INFO("Peer limit reached
|
|
351
|
-
close_socket(
|
|
379
|
+
LOG_SERVER_INFO("Peer limit reached, rejecting " << normalized);
|
|
380
|
+
close_socket(client);
|
|
352
381
|
continue;
|
|
353
382
|
}
|
|
354
383
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
close_socket(client_socket);
|
|
384
|
+
if (is_already_connected_to_address(normalized)) {
|
|
385
|
+
LOG_SERVER_DEBUG("Duplicate connection from " << normalized);
|
|
386
|
+
close_socket(client);
|
|
359
387
|
continue;
|
|
360
388
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// Generate unique hash ID for this incoming client
|
|
364
|
-
std::string connection_info = "incoming_from_" + peer_address;
|
|
365
|
-
std::string peer_hash_id = generate_peer_hash_id(client_socket, connection_info); // Temporary hash ID (real hash ID will be set after handshake)
|
|
366
389
|
|
|
367
|
-
//
|
|
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
|
+
|
|
368
395
|
{
|
|
369
396
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
370
|
-
RatsPeer new_peer(
|
|
397
|
+
RatsPeer new_peer(initial_id, ip, port, client, normalized, false);
|
|
371
398
|
new_peer.encryption_enabled = is_encryption_enabled();
|
|
372
399
|
add_peer_unlocked(new_peer);
|
|
373
400
|
}
|
|
374
401
|
|
|
375
|
-
|
|
376
|
-
LOG_SERVER_DEBUG("Starting thread for client " << peer_hash_id << " from " << peer_address);
|
|
377
|
-
add_managed_thread(std::thread(&RatsClient::handle_client, this, client_socket, peer_hash_id),
|
|
378
|
-
"client-handler-" + peer_hash_id.substr(0, 8));
|
|
379
|
-
|
|
380
|
-
// Note: Connection callback will be called after handshake completion in handle_client
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
LOG_SERVER_INFO("Server loop ended");
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
void RatsClient::management_loop() {
|
|
387
|
-
LOG_CLIENT_INFO("Management loop started");
|
|
388
|
-
|
|
389
|
-
auto last_thread_cleanup = std::chrono::steady_clock::now();
|
|
390
|
-
const auto thread_cleanup_interval = std::chrono::seconds(30);
|
|
391
|
-
|
|
392
|
-
while (running_.load()) {
|
|
393
|
-
// Wait for 2 seconds or until shutdown (for responsive reconnection processing)
|
|
394
|
-
{
|
|
395
|
-
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
396
|
-
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(2), [this] { return !running_.load(); })) {
|
|
397
|
-
break; // Exit if shutdown requested
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Process reconnection queue
|
|
402
|
-
try {
|
|
403
|
-
process_reconnect_queue();
|
|
404
|
-
} catch (const std::exception& e) {
|
|
405
|
-
LOG_CLIENT_ERROR("Exception during reconnect queue processing: " << e.what());
|
|
406
|
-
}
|
|
402
|
+
poller_add(client, PollIn);
|
|
407
403
|
|
|
408
|
-
|
|
409
|
-
auto now = std::chrono::steady_clock::now();
|
|
410
|
-
if (now - last_thread_cleanup >= thread_cleanup_interval) {
|
|
411
|
-
try {
|
|
412
|
-
cleanup_finished_threads();
|
|
413
|
-
LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
|
|
414
|
-
} catch (const std::exception& e) {
|
|
415
|
-
LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
|
|
416
|
-
}
|
|
417
|
-
last_thread_cleanup = now;
|
|
418
|
-
}
|
|
404
|
+
LOG_SERVER_INFO("Accepted incoming connection from " << normalized << " (id: " << initial_id.substr(0, 8) << "…)");
|
|
419
405
|
}
|
|
420
|
-
|
|
421
|
-
LOG_CLIENT_INFO("Management loop ended");
|
|
422
406
|
}
|
|
423
407
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
+
};
|
|
427
421
|
|
|
428
|
-
|
|
429
|
-
bool
|
|
430
|
-
bool
|
|
431
|
-
|
|
432
|
-
bool is_outgoing = false;
|
|
433
|
-
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;
|
|
434
426
|
|
|
435
427
|
{
|
|
436
428
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
437
|
-
auto
|
|
438
|
-
if (
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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;
|
|
442
445
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
close_socket(client_socket);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// ===== MAIN LOOP =====
|
|
458
|
-
while (running_.load()) {
|
|
459
|
-
|
|
460
|
-
// ----- 1. RECEIVE DATA -----
|
|
461
|
-
LOG_CLIENT_DEBUG("Receiving data from socket " << client_socket);
|
|
462
|
-
std::vector<uint8_t> received_bytes = receive_tcp_message(client_socket);
|
|
463
|
-
|
|
464
|
-
if (received_bytes.empty()) {
|
|
465
|
-
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;
|
|
466
455
|
}
|
|
467
456
|
|
|
468
|
-
|
|
469
|
-
// Use vector directly to avoid unnecessary string conversions
|
|
470
|
-
std::vector<uint8_t> data;
|
|
457
|
+
if (peer_closed && recv_buf.empty()) return true;
|
|
471
458
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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);
|
|
475
464
|
|
|
476
|
-
{
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (sock_it != socket_to_peer_id_.end()) {
|
|
480
|
-
auto peer_it = peers_.find(sock_it->second);
|
|
481
|
-
if (peer_it != peers_.end() && peer_it->second.is_noise_encrypted()) {
|
|
482
|
-
recv_cipher = peer_it->second.recv_cipher.get();
|
|
483
|
-
current_peer_id = peer_it->second.peer_id;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
465
|
+
if (msg_len > MAX_FRAME_SIZE) {
|
|
466
|
+
LOG_CLIENT_ERROR("Frame too large (" << msg_len << " bytes) from " << peer.peer_id);
|
|
467
|
+
return true;
|
|
486
468
|
}
|
|
469
|
+
if (recv_buf.size() < 4 + msg_len) break; // incomplete frame
|
|
487
470
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
std::vector<uint8_t> plaintext(received_bytes.size());
|
|
495
|
-
size_t pt_len = recv_cipher->decrypt_with_ad(
|
|
496
|
-
nullptr, 0,
|
|
497
|
-
received_bytes.data(), received_bytes.size(),
|
|
498
|
-
plaintext.data()
|
|
499
|
-
);
|
|
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);
|
|
500
475
|
|
|
501
|
-
if
|
|
502
|
-
|
|
503
|
-
|
|
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;
|
|
504
480
|
}
|
|
505
|
-
|
|
506
|
-
plaintext.resize(pt_len);
|
|
507
|
-
data = std::move(plaintext);
|
|
508
|
-
LOG_CLIENT_DEBUG("Decrypted message from " << current_peer_id << " (" << pt_len << " bytes)");
|
|
509
|
-
} else {
|
|
510
|
-
data = std::move(received_bytes);
|
|
511
|
-
}
|
|
512
|
-
} else {
|
|
513
|
-
data = std::move(received_bytes);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Log first 50 bytes for debugging
|
|
517
|
-
size_t log_len = (std::min)(data.size(), static_cast<size_t>(50));
|
|
518
|
-
LOG_CLIENT_DEBUG("Received data from " << peer_hash_id << ": " << std::string(data.begin(), data.begin() + log_len) << (data.size() > 50 ? "..." : ""));
|
|
519
|
-
|
|
520
|
-
// ----- 3. CONNECTION STATE CHECK (during handshake phase only) -----
|
|
521
|
-
if (!handshake_completed) {
|
|
522
|
-
auto now = std::chrono::steady_clock::now();
|
|
523
|
-
if (now - last_timeout_check >= std::chrono::seconds(1)) {
|
|
524
|
-
check_handshake_timeouts();
|
|
525
|
-
last_timeout_check = now;
|
|
481
|
+
continue;
|
|
526
482
|
}
|
|
527
483
|
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (peer_it != peers_.end() && peer_it->second.is_handshake_failed()) {
|
|
536
|
-
handshake_failed = true;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
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;
|
|
540
491
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
break;
|
|
544
|
-
}
|
|
492
|
+
frames.push_back(std::move(pf));
|
|
493
|
+
recv_buf.consume(4 + msg_len);
|
|
545
494
|
}
|
|
546
495
|
|
|
547
|
-
//
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
+
}
|
|
561
514
|
|
|
515
|
+
// Check if handshake just completed and handle Noise / post-handshake
|
|
516
|
+
bool do_post_handshake = false;
|
|
517
|
+
RatsPeer post_hs_copy;
|
|
562
518
|
{
|
|
563
519
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
564
|
-
auto
|
|
565
|
-
if (
|
|
566
|
-
|
|
567
|
-
if (peer_it != peers_.end()) {
|
|
568
|
-
// Check if rats handshake completed (either COMPLETED or NOISE_PENDING)
|
|
569
|
-
if (peer_it->second.is_handshake_completed()) {
|
|
570
|
-
rats_handshake_done = true;
|
|
571
|
-
peer_copy = peer_it->second;
|
|
572
|
-
} else if (peer_it->second.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
|
|
573
|
-
rats_handshake_done = true;
|
|
574
|
-
needs_noise_handshake = true;
|
|
575
|
-
peer_copy = peer_it->second;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// ----- POST-HANDSHAKE ACTIONS -----
|
|
582
|
-
if (rats_handshake_done) {
|
|
583
|
-
LOG_CLIENT_INFO("Rats handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
|
|
520
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
521
|
+
if (peer_it == peers_.end()) return true;
|
|
522
|
+
RatsPeer& peer = peer_it->second;
|
|
584
523
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
if (needs_noise_handshake) {
|
|
591
|
-
LOG_CLIENT_INFO("Starting Noise handshake for peer " << peer_copy.peer_id);
|
|
592
|
-
if (perform_noise_handshake(client_socket, peer_copy.peer_id, peer_copy.is_outgoing)) {
|
|
593
|
-
noise_handshake_done = true;
|
|
594
|
-
LOG_CLIENT_INFO("Noise handshake successful for peer " << peer_copy.peer_id);
|
|
595
|
-
|
|
596
|
-
// Update state to COMPLETED after successful Noise handshake
|
|
597
|
-
{
|
|
598
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
599
|
-
auto sock_it = socket_to_peer_id_.find(client_socket);
|
|
600
|
-
if (sock_it != socket_to_peer_id_.end()) {
|
|
601
|
-
auto peer_it = peers_.find(sock_it->second);
|
|
602
|
-
if (peer_it != peers_.end()) {
|
|
603
|
-
peer_it->second.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
604
|
-
log_handshake_completion_unlocked(peer_it->second);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
} else {
|
|
609
|
-
LOG_CLIENT_ERROR("Noise handshake failed for peer " << peer_copy.peer_id);
|
|
610
|
-
// Connection will be closed due to failed Noise handshake
|
|
611
|
-
break;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
handshake_completed = true;
|
|
616
|
-
|
|
617
|
-
// Connection callback
|
|
618
|
-
if (connection_callback_) {
|
|
619
|
-
connection_callback_(client_socket, peer_copy.peer_id);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// GossipSub notification
|
|
623
|
-
if (gossipsub_) {
|
|
624
|
-
gossipsub_->handle_peer_connected(peer_copy.peer_id);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
#ifdef RATS_STORAGE
|
|
628
|
-
// Storage manager notification
|
|
629
|
-
if (storage_manager_) {
|
|
630
|
-
storage_manager_->on_peer_connected(peer_copy.peer_id);
|
|
631
|
-
}
|
|
632
|
-
#endif
|
|
633
|
-
|
|
634
|
-
// Peer exchange broadcast
|
|
635
|
-
broadcast_peer_exchange_message(peer_copy);
|
|
636
|
-
|
|
637
|
-
// Request peers from newly connected peer (outgoing only)
|
|
638
|
-
if (peer_copy.is_outgoing) {
|
|
639
|
-
send_peers_request(client_socket, peer_copy.peer_id);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Save configuration
|
|
643
|
-
if (running_.load()) {
|
|
644
|
-
add_managed_thread(std::thread([this]() {
|
|
645
|
-
if (running_.load()) {
|
|
646
|
-
save_configuration();
|
|
647
|
-
}
|
|
648
|
-
}), "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;
|
|
649
529
|
}
|
|
650
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");
|
|
651
537
|
}
|
|
652
|
-
|
|
653
538
|
continue;
|
|
654
539
|
}
|
|
655
540
|
|
|
656
|
-
//
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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);
|
|
660
558
|
}
|
|
661
559
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
+
);
|
|
665
587
|
|
|
666
|
-
if (
|
|
667
|
-
|
|
588
|
+
if (bytes > 0) {
|
|
589
|
+
send_buf.pop_front(static_cast<size_t>(bytes));
|
|
668
590
|
continue;
|
|
669
591
|
}
|
|
670
592
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
if (!handled && binary_data_callback_) {
|
|
681
|
-
binary_data_callback_(client_socket, peer_id, payload);
|
|
682
|
-
}
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
case MessageDataType::STRING: {
|
|
687
|
-
LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
688
|
-
if (string_data_callback_) {
|
|
689
|
-
std::string string_data(payload.begin(), payload.end());
|
|
690
|
-
string_data_callback_(client_socket, peer_id, string_data);
|
|
691
|
-
}
|
|
692
|
-
break;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
case MessageDataType::JSON: {
|
|
696
|
-
LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
697
|
-
std::string json_string(payload.begin(), payload.end());
|
|
698
|
-
nlohmann::json json_msg;
|
|
699
|
-
if (parse_json_message(json_string, json_msg)) {
|
|
700
|
-
if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
|
|
701
|
-
handle_rats_message(client_socket, peer_id, json_msg);
|
|
702
|
-
} else if (json_data_callback_) {
|
|
703
|
-
json_data_callback_(client_socket, peer_id, json_msg);
|
|
704
|
-
}
|
|
705
|
-
} else {
|
|
706
|
-
LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id);
|
|
707
|
-
}
|
|
708
|
-
break;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
default:
|
|
712
|
-
LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
|
|
713
|
-
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;
|
|
714
601
|
}
|
|
715
602
|
|
|
716
|
-
|
|
603
|
+
// bytes == 0 — shouldn't happen on a stream socket
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
717
606
|
|
|
718
|
-
//
|
|
719
|
-
|
|
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
|
+
}
|
|
720
612
|
|
|
721
|
-
|
|
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;
|
|
722
623
|
RatsPeer peer_copy_for_reconnect;
|
|
723
624
|
bool should_schedule_reconnect = false;
|
|
724
625
|
|
|
725
|
-
|
|
626
|
+
{
|
|
726
627
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
727
|
-
auto
|
|
728
|
-
if (
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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;
|
|
734
640
|
}
|
|
735
641
|
}
|
|
736
642
|
|
|
737
|
-
|
|
738
|
-
|
|
643
|
+
// Remove from poller, peers map, close socket
|
|
644
|
+
poller_remove(socket);
|
|
645
|
+
remove_peer(socket);
|
|
646
|
+
close_socket(socket);
|
|
739
647
|
|
|
740
|
-
if (
|
|
648
|
+
if (was_validated) {
|
|
741
649
|
if (disconnect_callback_) {
|
|
742
|
-
disconnect_callback_(
|
|
650
|
+
disconnect_callback_(socket, peer_id);
|
|
743
651
|
}
|
|
744
|
-
|
|
745
652
|
if (gossipsub_) {
|
|
746
|
-
gossipsub_->handle_peer_disconnected(
|
|
653
|
+
gossipsub_->handle_peer_disconnected(peer_id);
|
|
747
654
|
}
|
|
748
|
-
|
|
749
|
-
// Schedule reconnection if we have valid peer info
|
|
750
655
|
if (should_schedule_reconnect && running_.load()) {
|
|
751
656
|
schedule_reconnect(peer_copy_for_reconnect);
|
|
752
657
|
}
|
|
753
|
-
|
|
754
658
|
if (running_.load()) {
|
|
755
659
|
add_managed_thread(std::thread([this]() {
|
|
756
|
-
if (running_.load())
|
|
757
|
-
save_configuration();
|
|
758
|
-
}
|
|
660
|
+
if (running_.load()) save_configuration();
|
|
759
661
|
}), "config-save-disconnect");
|
|
760
662
|
}
|
|
761
663
|
}
|
|
762
664
|
|
|
763
|
-
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
|
+
}
|
|
764
854
|
}
|
|
765
855
|
|
|
766
856
|
// Handshake protocol implementation
|
|
@@ -855,9 +945,8 @@ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
|
|
|
855
945
|
auto now = std::chrono::high_resolution_clock::now();
|
|
856
946
|
auto current_timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
|
857
947
|
int64_t time_diff = std::abs(current_timestamp - msg.timestamp);
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
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");
|
|
861
950
|
}
|
|
862
951
|
}
|
|
863
952
|
|
|
@@ -893,52 +982,32 @@ bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
|
|
|
893
982
|
}
|
|
894
983
|
}
|
|
895
984
|
|
|
896
|
-
|
|
897
|
-
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) {
|
|
898
986
|
std::string handshake_msg = create_handshake_message("handshake", our_peer_id);
|
|
899
|
-
LOG_CLIENT_DEBUG("Sending handshake to
|
|
987
|
+
LOG_CLIENT_DEBUG("Sending handshake to " << peer.peer_id << ": " << handshake_msg);
|
|
900
988
|
|
|
901
|
-
//
|
|
902
|
-
// (which would cause deadlock by trying to lock peers_mutex_ again).
|
|
903
|
-
// Handshakes are always unencrypted since they happen before noise handshake.
|
|
989
|
+
// Handshakes are always unencrypted – enqueue into the peer's send buffer
|
|
904
990
|
std::vector<uint8_t> binary_data(handshake_msg.begin(), handshake_msg.end());
|
|
905
991
|
std::vector<uint8_t> message_with_header = create_message_with_header(binary_data, MessageDataType::STRING);
|
|
906
992
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
std::lock_guard<std::mutex> send_lock(*socket_mutex);
|
|
910
|
-
|
|
911
|
-
int sent = send_tcp_message(socket, message_with_header);
|
|
912
|
-
if (sent <= 0) {
|
|
913
|
-
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);
|
|
914
995
|
return false;
|
|
915
996
|
}
|
|
916
997
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
if (it != socket_to_peer_id_.end()) {
|
|
920
|
-
auto peer_it = peers_.find(it->second);
|
|
921
|
-
if (peer_it != peers_.end()) {
|
|
922
|
-
peer_it->second.handshake_state = RatsPeer::HandshakeState::SENT;
|
|
923
|
-
peer_it->second.handshake_start_time = std::chrono::steady_clock::now();
|
|
924
|
-
}
|
|
925
|
-
}
|
|
998
|
+
peer.handshake_state = RatsPeer::HandshakeState::SENT;
|
|
999
|
+
peer.handshake_start_time = std::chrono::steady_clock::now();
|
|
926
1000
|
|
|
927
1001
|
return true;
|
|
928
1002
|
}
|
|
929
1003
|
|
|
930
|
-
bool RatsClient::
|
|
931
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
932
|
-
return send_handshake_unlocked(socket, our_peer_id);
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
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) {
|
|
936
1005
|
// Extract JSON payload from message header
|
|
937
1006
|
MessageHeader header;
|
|
938
1007
|
std::vector<uint8_t> payload;
|
|
939
1008
|
|
|
940
1009
|
if (!parse_message_with_header(data, header, payload)) {
|
|
941
|
-
LOG_CLIENT_ERROR("Failed to parse handshake message header from " <<
|
|
1010
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message header from " << initial_peer_id);
|
|
942
1011
|
return false;
|
|
943
1012
|
}
|
|
944
1013
|
|
|
@@ -951,12 +1020,12 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
951
1020
|
// Parse handshake message directly from payload (no string conversion)
|
|
952
1021
|
HandshakeMessage handshake_msg;
|
|
953
1022
|
if (!parse_handshake_message(payload, handshake_msg)) {
|
|
954
|
-
LOG_CLIENT_ERROR("Failed to parse handshake message from " <<
|
|
1023
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message from " << initial_peer_id);
|
|
955
1024
|
return false;
|
|
956
1025
|
}
|
|
957
1026
|
|
|
958
1027
|
if (!validate_handshake_message(handshake_msg)) {
|
|
959
|
-
LOG_CLIENT_ERROR("Invalid handshake message from " <<
|
|
1028
|
+
LOG_CLIENT_ERROR("Invalid handshake message from " << initial_peer_id);
|
|
960
1029
|
return false;
|
|
961
1030
|
}
|
|
962
1031
|
|
|
@@ -965,19 +1034,13 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
965
1034
|
return false;
|
|
966
1035
|
}
|
|
967
1036
|
|
|
968
|
-
LOG_CLIENT_INFO("Received valid handshake from " <<
|
|
1037
|
+
LOG_CLIENT_INFO("Received valid handshake from " << initial_peer_id
|
|
969
1038
|
<< " (peer_id: " << handshake_msg.peer_id << ")");
|
|
970
1039
|
|
|
971
1040
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
972
|
-
auto
|
|
973
|
-
if (it == socket_to_peer_id_.end()) {
|
|
974
|
-
LOG_CLIENT_ERROR("Socket " << socket << " not found in peer mapping");
|
|
975
|
-
return false;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
auto peer_it = peers_.find(it->second);
|
|
1041
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
979
1042
|
if (peer_it == peers_.end()) {
|
|
980
|
-
LOG_CLIENT_ERROR("Peer " <<
|
|
1043
|
+
LOG_CLIENT_ERROR("Peer " << initial_peer_id << " not found for socket " << socket);
|
|
981
1044
|
return false;
|
|
982
1045
|
}
|
|
983
1046
|
|
|
@@ -993,22 +1056,18 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
993
1056
|
|
|
994
1057
|
// Update peer mappings with new peer_id if it changed
|
|
995
1058
|
if (old_peer_id != handshake_msg.peer_id) {
|
|
996
|
-
//
|
|
997
|
-
RatsPeer
|
|
998
|
-
|
|
999
|
-
// 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);
|
|
1000
1061
|
peers_.erase(peer_it);
|
|
1001
1062
|
|
|
1002
|
-
|
|
1003
|
-
peer_copy.peer_id = handshake_msg.peer_id;
|
|
1063
|
+
peer_moved.peer_id = handshake_msg.peer_id;
|
|
1004
1064
|
|
|
1005
|
-
//
|
|
1006
|
-
|
|
1007
|
-
socket_to_peer_id_[socket] =
|
|
1008
|
-
address_to_peer_id_[
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
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;
|
|
1012
1071
|
}
|
|
1013
1072
|
|
|
1014
1073
|
RatsPeer& peer = peer_it->second;
|
|
@@ -1045,14 +1104,15 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1045
1104
|
// Simplified handshake logic - just one message type
|
|
1046
1105
|
if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
|
|
1047
1106
|
// This is an incoming handshake - send our handshake back
|
|
1048
|
-
if (send_handshake_unlocked(
|
|
1107
|
+
if (send_handshake_unlocked(peer, get_our_peer_id())) {
|
|
1049
1108
|
// If encryption is enabled, we need to do Noise handshake first
|
|
1050
1109
|
// Set NOISE_PENDING to prevent other threads from sending messages
|
|
1051
1110
|
if (peer.encryption_enabled) {
|
|
1052
1111
|
peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
|
|
1053
|
-
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);
|
|
1054
1113
|
} else {
|
|
1055
1114
|
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1115
|
+
validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
|
|
1056
1116
|
log_handshake_completion_unlocked(peer);
|
|
1057
1117
|
}
|
|
1058
1118
|
|
|
@@ -1062,7 +1122,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1062
1122
|
return true;
|
|
1063
1123
|
} else {
|
|
1064
1124
|
peer.handshake_state = RatsPeer::HandshakeState::FAILED;
|
|
1065
|
-
LOG_CLIENT_ERROR("Failed to send handshake response to " <<
|
|
1125
|
+
LOG_CLIENT_ERROR("Failed to send handshake response to " << initial_peer_id);
|
|
1066
1126
|
return false;
|
|
1067
1127
|
}
|
|
1068
1128
|
} else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
|
|
@@ -1071,9 +1131,10 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1071
1131
|
// Set NOISE_PENDING to prevent other threads from sending messages
|
|
1072
1132
|
if (peer.encryption_enabled) {
|
|
1073
1133
|
peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
|
|
1074
|
-
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);
|
|
1075
1135
|
} else {
|
|
1076
1136
|
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1137
|
+
validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
|
|
1077
1138
|
log_handshake_completion_unlocked(peer);
|
|
1078
1139
|
}
|
|
1079
1140
|
|
|
@@ -1082,7 +1143,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1082
1143
|
|
|
1083
1144
|
return true;
|
|
1084
1145
|
} else {
|
|
1085
|
-
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));
|
|
1086
1147
|
return false;
|
|
1087
1148
|
}
|
|
1088
1149
|
}
|
|
@@ -1116,8 +1177,8 @@ void RatsClient::check_handshake_timeouts() {
|
|
|
1116
1177
|
socket_t socket = peer_it->second.socket;
|
|
1117
1178
|
LOG_CLIENT_INFO("Disconnecting peer " << peer_id << " due to handshake timeout");
|
|
1118
1179
|
|
|
1119
|
-
// Clean up peer data
|
|
1120
1180
|
remove_peer_by_id_unlocked(peer_id);
|
|
1181
|
+
poller_remove(socket);
|
|
1121
1182
|
close_socket(socket);
|
|
1122
1183
|
}
|
|
1123
1184
|
}
|
|
@@ -1154,70 +1215,108 @@ bool RatsClient::connect_to_peer(const std::string& host, int port) {
|
|
|
1154
1215
|
return false;
|
|
1155
1216
|
}
|
|
1156
1217
|
|
|
1157
|
-
// Create TCP connection with timeout
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
std::
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
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));
|
|
1181
1272
|
|
|
1182
1273
|
return true;
|
|
1183
1274
|
}
|
|
1184
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
|
+
|
|
1185
1282
|
void RatsClient::disconnect_peer(socket_t socket) {
|
|
1186
|
-
// Mark as manually disconnected to prevent auto-reconnection
|
|
1187
1283
|
std::string peer_id = get_peer_id(socket);
|
|
1188
1284
|
if (!peer_id.empty()) {
|
|
1189
|
-
|
|
1190
|
-
manual_disconnect_peers_.insert(peer_id);
|
|
1191
|
-
// Also remove from reconnection queue if present
|
|
1192
|
-
reconnect_queue_.erase(peer_id);
|
|
1285
|
+
mark_manual_disconnect(peer_id);
|
|
1193
1286
|
}
|
|
1194
|
-
|
|
1287
|
+
poller_remove(socket);
|
|
1195
1288
|
remove_peer(socket);
|
|
1196
1289
|
close_socket(socket);
|
|
1197
1290
|
}
|
|
1198
1291
|
|
|
1199
1292
|
void RatsClient::disconnect_peer_by_id(const std::string& peer_id) {
|
|
1200
|
-
|
|
1201
|
-
{
|
|
1202
|
-
std::lock_guard<std::mutex> lock(reconnect_mutex_);
|
|
1203
|
-
manual_disconnect_peers_.insert(peer_id);
|
|
1204
|
-
// Also remove from reconnection queue if present
|
|
1205
|
-
reconnect_queue_.erase(peer_id);
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1293
|
+
mark_manual_disconnect(peer_id);
|
|
1208
1294
|
socket_t socket = get_peer_socket_by_id(peer_id);
|
|
1209
1295
|
if (is_valid_socket(socket)) {
|
|
1296
|
+
poller_remove(socket);
|
|
1210
1297
|
remove_peer(socket);
|
|
1211
1298
|
close_socket(socket);
|
|
1212
1299
|
}
|
|
1213
1300
|
}
|
|
1214
1301
|
|
|
1215
|
-
//
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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();
|
|
1309
|
+
}
|
|
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();
|
|
1219
1317
|
}
|
|
1220
1318
|
|
|
1319
|
+
// Helper methods for peer management
|
|
1221
1320
|
void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
|
|
1222
1321
|
// Assumes peers_mutex_ is already locked
|
|
1223
1322
|
peers_[peer.peer_id] = peer;
|
|
@@ -1227,17 +1326,12 @@ void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
|
|
|
1227
1326
|
|
|
1228
1327
|
void RatsClient::remove_peer(socket_t socket) {
|
|
1229
1328
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1230
|
-
auto
|
|
1231
|
-
if (
|
|
1232
|
-
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);
|
|
1233
1332
|
}
|
|
1234
1333
|
}
|
|
1235
1334
|
|
|
1236
|
-
void RatsClient::remove_peer_by_id(const std::string& peer_id) {
|
|
1237
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1238
|
-
remove_peer_by_id_unlocked(peer_id);
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
1335
|
void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
1242
1336
|
// Assumes peers_mutex_ is already locked
|
|
1243
1337
|
|
|
@@ -1246,6 +1340,11 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
|
1246
1340
|
|
|
1247
1341
|
auto it = peers_.find(peer_id_copy);
|
|
1248
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
|
+
|
|
1249
1348
|
// Copy the values we need before erasing to avoid use-after-free
|
|
1250
1349
|
socket_t peer_socket = it->second.socket;
|
|
1251
1350
|
std::string peer_normalized_address = it->second.normalized_address;
|
|
@@ -1253,9 +1352,6 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
|
1253
1352
|
socket_to_peer_id_.erase(peer_socket);
|
|
1254
1353
|
address_to_peer_id_.erase(peer_normalized_address);
|
|
1255
1354
|
peers_.erase(it);
|
|
1256
|
-
|
|
1257
|
-
// Clean up socket-specific mutex
|
|
1258
|
-
cleanup_socket_send_mutex(peer_socket);
|
|
1259
1355
|
}
|
|
1260
1356
|
}
|
|
1261
1357
|
|
|
@@ -1267,9 +1363,8 @@ bool RatsClient::is_already_connected_to_address(const std::string& normalized_a
|
|
|
1267
1363
|
void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
1268
1364
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1269
1365
|
|
|
1270
|
-
|
|
1271
|
-
if (
|
|
1272
|
-
local_interface_addresses_.push_back(ip_address);
|
|
1366
|
+
auto [it, inserted] = local_interface_addresses_.insert(ip_address);
|
|
1367
|
+
if (inserted) {
|
|
1273
1368
|
LOG_CLIENT_INFO("Added " << ip_address << " to ignore list");
|
|
1274
1369
|
} else {
|
|
1275
1370
|
LOG_CLIENT_DEBUG("IP address " << ip_address << " already in ignore list");
|
|
@@ -1277,7 +1372,7 @@ void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
|
1277
1372
|
}
|
|
1278
1373
|
|
|
1279
1374
|
//common localhost addresses
|
|
1280
|
-
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"};
|
|
1281
1376
|
|
|
1282
1377
|
// Local interface address blocking methods
|
|
1283
1378
|
void RatsClient::initialize_local_addresses() {
|
|
@@ -1286,13 +1381,12 @@ void RatsClient::initialize_local_addresses() {
|
|
|
1286
1381
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1287
1382
|
|
|
1288
1383
|
// Get all local interface addresses using network_utils
|
|
1289
|
-
|
|
1384
|
+
auto addrs = network_utils::get_local_interface_addresses();
|
|
1385
|
+
local_interface_addresses_.insert(addrs.begin(), addrs.end());
|
|
1290
1386
|
|
|
1291
|
-
// Add common localhost addresses
|
|
1387
|
+
// Add common localhost addresses
|
|
1292
1388
|
for (const auto& addr : localhost_addrs) {
|
|
1293
|
-
|
|
1294
|
-
local_interface_addresses_.emplace_back(addr);
|
|
1295
|
-
}
|
|
1389
|
+
local_interface_addresses_.emplace(std::string(addr));
|
|
1296
1390
|
}
|
|
1297
1391
|
|
|
1298
1392
|
LOG_CLIENT_INFO("Found " << local_interface_addresses_.size() << " local addresses to block:");
|
|
@@ -1301,36 +1395,47 @@ void RatsClient::initialize_local_addresses() {
|
|
|
1301
1395
|
}
|
|
1302
1396
|
}
|
|
1303
1397
|
|
|
1304
|
-
|
|
1305
1398
|
bool RatsClient::is_blocked_address(const std::string& ip_address) const {
|
|
1306
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
|
+
}
|
|
1307
1408
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
}
|
|
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;
|
|
1313
1413
|
}
|
|
1314
1414
|
|
|
1315
|
-
|
|
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;
|
|
1316
1421
|
}
|
|
1317
1422
|
|
|
1318
1423
|
bool RatsClient::should_ignore_peer(const std::string& ip, int port) const {
|
|
1319
|
-
//
|
|
1320
|
-
|
|
1321
|
-
|
|
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_) {
|
|
1322
1430
|
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - localhost with same port");
|
|
1323
1431
|
return true;
|
|
1324
1432
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
// For localhost addresses on different ports, allow the connection (for testing)
|
|
1328
|
-
if (ip == "127.0.0.1" || ip == "::1" || ip == "localhost") {
|
|
1433
|
+
// Allow localhost on different ports (for testing)
|
|
1329
1434
|
LOG_CLIENT_DEBUG("Allowing localhost peer " << ip << ":" << port << " on different port");
|
|
1330
1435
|
return false;
|
|
1331
1436
|
}
|
|
1332
1437
|
|
|
1333
|
-
//
|
|
1438
|
+
// Block non-localhost local interface addresses
|
|
1334
1439
|
if (is_blocked_address(ip)) {
|
|
1335
1440
|
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - matches local interface address");
|
|
1336
1441
|
return true;
|
|
@@ -1383,30 +1488,25 @@ bool RatsClient::parse_message_with_header(const std::vector<uint8_t>& message,
|
|
|
1383
1488
|
// Extract payload
|
|
1384
1489
|
payload.assign(message.begin() + MessageHeader::HEADER_SIZE, message.end());
|
|
1385
1490
|
|
|
1386
|
-
LOG_CLIENT_DEBUG("Parsed message header: type=" << static_cast<int>(header.type) << ", payload_size=" << payload.size());
|
|
1387
1491
|
return true;
|
|
1388
1492
|
}
|
|
1389
1493
|
|
|
1390
|
-
//
|
|
1391
|
-
//
|
|
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.
|
|
1392
1497
|
bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector<uint8_t>& data,
|
|
1393
1498
|
MessageDataType message_type,
|
|
1394
|
-
rats::NoiseCipherState
|
|
1499
|
+
std::shared_ptr<rats::NoiseCipherState> send_cipher,
|
|
1395
1500
|
const std::string& peer_id_for_logging) {
|
|
1396
1501
|
if (!running_.load()) {
|
|
1397
1502
|
return false;
|
|
1398
1503
|
}
|
|
1399
1504
|
|
|
1400
|
-
// Get socket-specific mutex for thread-safe sending
|
|
1401
|
-
// Prevent framed messages corruption (like two-times sending the number of bytes instead number of bytes + message)
|
|
1402
|
-
auto socket_mutex = get_socket_send_mutex(socket);
|
|
1403
|
-
std::lock_guard<std::mutex> send_lock(*socket_mutex);
|
|
1404
|
-
|
|
1405
1505
|
// Create message with specified header type
|
|
1406
1506
|
std::vector<uint8_t> message_with_header = create_message_with_header(data, message_type);
|
|
1407
1507
|
|
|
1408
1508
|
if (send_cipher) {
|
|
1409
|
-
// Encrypt the message before
|
|
1509
|
+
// Encrypt the message before enqueuing
|
|
1410
1510
|
std::vector<uint8_t> ciphertext(message_with_header.size() + rats::NOISE_TAG_SIZE);
|
|
1411
1511
|
size_t ct_len = send_cipher->encrypt_with_ad(
|
|
1412
1512
|
nullptr, 0,
|
|
@@ -1420,16 +1520,13 @@ bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector
|
|
|
1420
1520
|
}
|
|
1421
1521
|
|
|
1422
1522
|
ciphertext.resize(ct_len);
|
|
1423
|
-
LOG_CLIENT_DEBUG("
|
|
1523
|
+
LOG_CLIENT_DEBUG("Enqueuing encrypted message for " << peer_id_for_logging << " (" << ct_len << " bytes)");
|
|
1424
1524
|
|
|
1425
|
-
|
|
1426
|
-
int sent = send_tcp_message(socket, ciphertext);
|
|
1427
|
-
return sent > 0;
|
|
1525
|
+
return enqueue_message(socket, ciphertext);
|
|
1428
1526
|
}
|
|
1429
1527
|
|
|
1430
|
-
// Unencrypted path
|
|
1431
|
-
|
|
1432
|
-
return sent > 0;
|
|
1528
|
+
// Unencrypted path
|
|
1529
|
+
return enqueue_message(socket, message_with_header);
|
|
1433
1530
|
}
|
|
1434
1531
|
|
|
1435
1532
|
bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
@@ -1437,25 +1534,22 @@ bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>
|
|
|
1437
1534
|
return false;
|
|
1438
1535
|
}
|
|
1439
1536
|
|
|
1440
|
-
// Cache peer
|
|
1537
|
+
// Cache peer data under lock, then release lock before sending
|
|
1441
1538
|
std::string peer_id;
|
|
1442
|
-
rats::NoiseCipherState
|
|
1539
|
+
std::shared_ptr<rats::NoiseCipherState> send_cipher;
|
|
1443
1540
|
|
|
1444
1541
|
{
|
|
1445
1542
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1446
|
-
auto
|
|
1447
|
-
if (
|
|
1448
|
-
|
|
1449
|
-
if (peer_it
|
|
1450
|
-
|
|
1451
|
-
if (peer_it->second.is_noise_encrypted()) {
|
|
1452
|
-
send_cipher = peer_it->second.send_cipher.get();
|
|
1453
|
-
}
|
|
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
|
|
1454
1548
|
}
|
|
1455
1549
|
}
|
|
1456
1550
|
}
|
|
1457
1551
|
|
|
1458
|
-
//
|
|
1552
|
+
// peers_mutex_ released -- safe to do potentially slow TCP send
|
|
1459
1553
|
return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
|
|
1460
1554
|
}
|
|
1461
1555
|
|
|
@@ -1465,43 +1559,50 @@ bool RatsClient::send_string_to_peer(socket_t socket, const std::string& data) {
|
|
|
1465
1559
|
return send_binary_to_peer(socket, binary_data, MessageDataType::STRING);
|
|
1466
1560
|
}
|
|
1467
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
|
+
|
|
1468
1567
|
bool RatsClient::send_json_to_peer(socket_t socket, const nlohmann::json& data) {
|
|
1469
1568
|
try {
|
|
1470
|
-
|
|
1471
|
-
std::string json_string = data.dump();
|
|
1472
|
-
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1473
|
-
return send_binary_to_peer(socket, binary_data, MessageDataType::JSON);
|
|
1569
|
+
return send_binary_to_peer(socket, json_to_binary(data), MessageDataType::JSON);
|
|
1474
1570
|
} catch (const nlohmann::json::exception& e) {
|
|
1475
1571
|
LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
|
|
1476
1572
|
return false;
|
|
1477
1573
|
}
|
|
1478
1574
|
}
|
|
1479
1575
|
|
|
1480
|
-
bool RatsClient::send_binary_to_peer_id(const std::string&
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
+
}
|
|
1485
1591
|
}
|
|
1486
1592
|
|
|
1487
|
-
//
|
|
1488
|
-
|
|
1489
|
-
rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
|
|
1490
|
-
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);
|
|
1491
1595
|
}
|
|
1492
1596
|
|
|
1493
|
-
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) {
|
|
1494
1598
|
// Convert string to binary and use primary binary method with STRING type
|
|
1495
1599
|
std::vector<uint8_t> binary_data(data.begin(), data.end());
|
|
1496
|
-
return send_binary_to_peer_id(
|
|
1600
|
+
return send_binary_to_peer_id(peer_id, binary_data, MessageDataType::STRING);
|
|
1497
1601
|
}
|
|
1498
1602
|
|
|
1499
|
-
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) {
|
|
1500
1604
|
try {
|
|
1501
|
-
|
|
1502
|
-
std::string json_string = data.dump();
|
|
1503
|
-
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1504
|
-
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);
|
|
1505
1606
|
} catch (const nlohmann::json::exception& e) {
|
|
1506
1607
|
LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
|
|
1507
1608
|
return false;
|
|
@@ -1510,10 +1611,7 @@ bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlo
|
|
|
1510
1611
|
|
|
1511
1612
|
int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
|
|
1512
1613
|
try {
|
|
1513
|
-
|
|
1514
|
-
std::string json_string = data.dump();
|
|
1515
|
-
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
1516
|
-
return broadcast_binary_to_peers(binary_data, MessageDataType::JSON);
|
|
1614
|
+
return broadcast_binary_to_peers(json_to_binary(data), MessageDataType::JSON);
|
|
1517
1615
|
} catch (const nlohmann::json::exception& e) {
|
|
1518
1616
|
LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
|
|
1519
1617
|
return 0;
|
|
@@ -1525,21 +1623,25 @@ int RatsClient::broadcast_binary_to_peers(const std::vector<uint8_t>& data, Mess
|
|
|
1525
1623
|
return 0;
|
|
1526
1624
|
}
|
|
1527
1625
|
|
|
1528
|
-
|
|
1529
|
-
std::
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
if (send_binary_to_peer_unlocked(peer.socket, data, message_type, send_cipher, peer.peer_id)) {
|
|
1538
|
-
sent_count++;
|
|
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});
|
|
1539
1635
|
}
|
|
1540
1636
|
}
|
|
1541
1637
|
}
|
|
1542
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
|
+
}
|
|
1543
1645
|
return sent_count;
|
|
1544
1646
|
}
|
|
1545
1647
|
|
|
@@ -1549,35 +1651,6 @@ int RatsClient::broadcast_string_to_peers(const std::string& data) {
|
|
|
1549
1651
|
return broadcast_binary_to_peers(binary_data, MessageDataType::STRING);
|
|
1550
1652
|
}
|
|
1551
1653
|
|
|
1552
|
-
bool RatsClient::parse_json_message(const std::string& message, nlohmann::json& out_json) {
|
|
1553
|
-
try {
|
|
1554
|
-
out_json = nlohmann::json::parse(message);
|
|
1555
|
-
return true;
|
|
1556
|
-
} catch (const nlohmann::json::exception& e) {
|
|
1557
|
-
LOG_CLIENT_ERROR("Failed to parse JSON message: " << e.what());
|
|
1558
|
-
return false;
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
// Helpers
|
|
1563
|
-
|
|
1564
|
-
// Per-socket synchronization helpers
|
|
1565
|
-
std::shared_ptr<std::mutex> RatsClient::get_socket_send_mutex(socket_t socket) {
|
|
1566
|
-
std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
|
|
1567
|
-
auto it = socket_send_mutexes_.find(socket);
|
|
1568
|
-
if (it == socket_send_mutexes_.end()) {
|
|
1569
|
-
// Create new mutex for this socket
|
|
1570
|
-
socket_send_mutexes_[socket] = std::make_shared<std::mutex>();
|
|
1571
|
-
return socket_send_mutexes_[socket];
|
|
1572
|
-
}
|
|
1573
|
-
return it->second;
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
void RatsClient::cleanup_socket_send_mutex(socket_t socket) {
|
|
1577
|
-
std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
|
|
1578
|
-
socket_send_mutexes_.erase(socket);
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
1654
|
// =========================================================================
|
|
1582
1655
|
// Peer Information and Management
|
|
1583
1656
|
// =========================================================================
|
|
@@ -1587,32 +1660,18 @@ std::string RatsClient::get_our_peer_id() const {
|
|
|
1587
1660
|
}
|
|
1588
1661
|
|
|
1589
1662
|
int RatsClient::get_peer_count_unlocked() const {
|
|
1590
|
-
//
|
|
1591
|
-
|
|
1592
|
-
for (const auto& pair : peers_) {
|
|
1593
|
-
if (pair.second.is_handshake_completed()) {
|
|
1594
|
-
count++;
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
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);
|
|
1598
1665
|
}
|
|
1599
1666
|
|
|
1600
1667
|
int RatsClient::get_peer_count() const {
|
|
1601
|
-
std::
|
|
1602
|
-
return get_peer_count_unlocked();
|
|
1668
|
+
return validated_peer_count_.load(std::memory_order_relaxed);
|
|
1603
1669
|
}
|
|
1604
1670
|
|
|
1605
1671
|
std::string RatsClient::get_peer_id(socket_t socket) const {
|
|
1606
|
-
// Atomic operation - lock once and return copy to avoid race condition
|
|
1607
1672
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1608
|
-
auto
|
|
1609
|
-
|
|
1610
|
-
auto peer_it = peers_.find(it->second);
|
|
1611
|
-
if (peer_it != peers_.end()) {
|
|
1612
|
-
return peer_it->second.peer_id;
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
return "";
|
|
1673
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1674
|
+
return (peer_it != peers_.end()) ? peer_it->second.peer_id : "";
|
|
1616
1675
|
}
|
|
1617
1676
|
|
|
1618
1677
|
socket_t RatsClient::get_peer_socket_by_id(const std::string& peer_id) const {
|
|
@@ -1650,7 +1709,6 @@ std::vector<RatsPeer> RatsClient::get_validated_peers() const {
|
|
|
1650
1709
|
return result;
|
|
1651
1710
|
}
|
|
1652
1711
|
|
|
1653
|
-
|
|
1654
1712
|
std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::string& exclude_peer_id) const {
|
|
1655
1713
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1656
1714
|
|
|
@@ -1681,23 +1739,24 @@ std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::str
|
|
|
1681
1739
|
return selected_peers;
|
|
1682
1740
|
}
|
|
1683
1741
|
|
|
1684
|
-
|
|
1742
|
+
std::optional<RatsPeer> RatsClient::get_peer_by_id(const std::string& peer_id) const {
|
|
1685
1743
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1686
1744
|
auto it = peers_.find(peer_id);
|
|
1687
|
-
|
|
1745
|
+
if (it != peers_.end()) {
|
|
1746
|
+
return it->second;
|
|
1747
|
+
}
|
|
1748
|
+
return std::nullopt;
|
|
1688
1749
|
}
|
|
1689
1750
|
|
|
1690
|
-
|
|
1751
|
+
std::optional<RatsPeer> RatsClient::get_peer_by_socket(socket_t socket) const {
|
|
1691
1752
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1692
|
-
auto
|
|
1693
|
-
if (
|
|
1694
|
-
|
|
1695
|
-
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;
|
|
1696
1756
|
}
|
|
1697
|
-
return
|
|
1757
|
+
return std::nullopt;
|
|
1698
1758
|
}
|
|
1699
1759
|
|
|
1700
|
-
|
|
1701
1760
|
// Peer limit management methods
|
|
1702
1761
|
int RatsClient::get_max_peers() const {
|
|
1703
1762
|
return max_peers_;
|
|
@@ -1710,15 +1769,10 @@ void RatsClient::set_max_peers(int max_peers) {
|
|
|
1710
1769
|
|
|
1711
1770
|
bool RatsClient::is_peer_limit_reached() const {
|
|
1712
1771
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1713
|
-
|
|
1714
|
-
int connected_peers = get_peer_count_unlocked();
|
|
1715
|
-
if (connected_peers >= max_peers_) {
|
|
1716
|
-
return true;
|
|
1717
|
-
}
|
|
1718
|
-
return false;
|
|
1772
|
+
return get_peer_count_unlocked() >= max_peers_;
|
|
1719
1773
|
}
|
|
1720
1774
|
|
|
1721
|
-
std::string RatsClient::
|
|
1775
|
+
std::string RatsClient::generate_temporary_peer_id(socket_t socket, const std::string& connection_info) {
|
|
1722
1776
|
// Generate unique hash ID using timestamp, socket, connection info, and random component
|
|
1723
1777
|
auto now = std::chrono::high_resolution_clock::now();
|
|
1724
1778
|
auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
|
|
@@ -1769,7 +1823,7 @@ std::string RatsClient::normalize_peer_address(const std::string& ip, int port)
|
|
|
1769
1823
|
|
|
1770
1824
|
// =========================================================================
|
|
1771
1825
|
// Callback Registration
|
|
1772
|
-
//
|
|
1826
|
+
// =========================================================================
|
|
1773
1827
|
|
|
1774
1828
|
void RatsClient::set_connection_callback(ConnectionCallback callback) {
|
|
1775
1829
|
connection_callback_ = callback;
|
|
@@ -1791,325 +1845,6 @@ void RatsClient::set_disconnect_callback(DisconnectCallback callback) {
|
|
|
1791
1845
|
disconnect_callback_ = callback;
|
|
1792
1846
|
}
|
|
1793
1847
|
|
|
1794
|
-
// =========================================================================
|
|
1795
|
-
// Peer Discovery Methods
|
|
1796
|
-
// =========================================================================
|
|
1797
|
-
|
|
1798
|
-
bool RatsClient::start_dht_discovery(int dht_port) {
|
|
1799
|
-
if (dht_client_ && dht_client_->is_running()) {
|
|
1800
|
-
LOG_CLIENT_WARN("DHT discovery is already running");
|
|
1801
|
-
return true;
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
LOG_CLIENT_INFO("Starting DHT discovery on port " << dht_port <<
|
|
1805
|
-
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
1806
|
-
|
|
1807
|
-
dht_client_ = std::make_unique<DhtClient>(dht_port, bind_address_, data_directory_);
|
|
1808
|
-
if (!dht_client_->start()) {
|
|
1809
|
-
LOG_CLIENT_ERROR("Failed to start DHT client");
|
|
1810
|
-
dht_client_.reset();
|
|
1811
|
-
return false;
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
// Bootstrap with default nodes
|
|
1815
|
-
auto bootstrap_nodes = DhtClient::get_default_bootstrap_nodes();
|
|
1816
|
-
if (!dht_client_->bootstrap(bootstrap_nodes)) {
|
|
1817
|
-
LOG_CLIENT_WARN("Failed to bootstrap DHT");
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
// Start automatic peer discovery
|
|
1821
|
-
start_automatic_peer_discovery();
|
|
1822
|
-
|
|
1823
|
-
LOG_CLIENT_INFO("DHT discovery started successfully");
|
|
1824
|
-
return true;
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
void RatsClient::stop_dht_discovery() {
|
|
1828
|
-
if (!dht_client_) {
|
|
1829
|
-
return;
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
LOG_CLIENT_INFO("Stopping DHT discovery");
|
|
1833
|
-
|
|
1834
|
-
// Stop automatic peer discovery
|
|
1835
|
-
stop_automatic_peer_discovery();
|
|
1836
|
-
|
|
1837
|
-
dht_client_->stop();
|
|
1838
|
-
dht_client_.reset();
|
|
1839
|
-
LOG_CLIENT_INFO("DHT discovery stopped");
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::function<void(const std::vector<std::string>&)> callback) {
|
|
1843
|
-
if (!dht_client_ || !dht_client_->is_running()) {
|
|
1844
|
-
LOG_CLIENT_ERROR("DHT client not running");
|
|
1845
|
-
return false;
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
if (content_hash.length() != 40) { // 160-bit hash as hex string
|
|
1849
|
-
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
|
|
1850
|
-
return false;
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
LOG_CLIENT_INFO("Finding peers for content hash: " << content_hash);
|
|
1854
|
-
|
|
1855
|
-
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
1856
|
-
|
|
1857
|
-
return dht_client_->find_peers(info_hash, [this, callback](const std::vector<Peer>& peers, const InfoHash& info_hash) {
|
|
1858
|
-
// Convert Peer to string addresses for callback
|
|
1859
|
-
std::vector<std::string> peer_addresses;
|
|
1860
|
-
for (const auto& peer : peers) {
|
|
1861
|
-
peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
if (callback) {
|
|
1865
|
-
callback(peer_addresses);
|
|
1866
|
-
}
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port,
|
|
1871
|
-
std::function<void(const std::vector<std::string>&)> callback) {
|
|
1872
|
-
if (!dht_client_ || !dht_client_->is_running()) {
|
|
1873
|
-
LOG_CLIENT_ERROR("DHT client not running");
|
|
1874
|
-
return false;
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
if (content_hash.length() != 40) { // 160-bit hash as hex string
|
|
1878
|
-
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
|
|
1879
|
-
return false;
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
if (port == 0) {
|
|
1883
|
-
port = listen_port_;
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
|
|
1887
|
-
<< (callback ? " with peer callback" : ""));
|
|
1888
|
-
|
|
1889
|
-
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
1890
|
-
|
|
1891
|
-
// Create wrapper callback that converts Peer to string addresses (if callback provided)
|
|
1892
|
-
PeerDiscoveryCallback peer_callback = nullptr;
|
|
1893
|
-
if (callback) {
|
|
1894
|
-
peer_callback = [callback](const std::vector<Peer>& peers, const InfoHash& hash) {
|
|
1895
|
-
std::vector<std::string> peer_addresses;
|
|
1896
|
-
peer_addresses.reserve(peers.size());
|
|
1897
|
-
for (const auto& peer : peers) {
|
|
1898
|
-
peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
|
|
1899
|
-
}
|
|
1900
|
-
callback(peer_addresses);
|
|
1901
|
-
};
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
return dht_client_->announce_peer(info_hash, port, peer_callback);
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
bool RatsClient::is_dht_running() const {
|
|
1908
|
-
return dht_client_ && dht_client_->is_running();
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
size_t RatsClient::get_dht_routing_table_size() const {
|
|
1912
|
-
if (!dht_client_) {
|
|
1913
|
-
return 0;
|
|
1914
|
-
}
|
|
1915
|
-
return dht_client_->get_routing_table_size();
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
void RatsClient::handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash) {
|
|
1919
|
-
LOG_CLIENT_INFO("DHT discovered " << peers.size() << " peers for info hash: " << node_id_to_hex(info_hash));
|
|
1920
|
-
|
|
1921
|
-
// Auto-connect to discovered peers (optional behavior)
|
|
1922
|
-
for (const auto& peer : peers) {
|
|
1923
|
-
// Check if this peer should be ignored (local interface)
|
|
1924
|
-
if (should_ignore_peer(peer.ip, peer.port)) {
|
|
1925
|
-
LOG_CLIENT_DEBUG("Ignoring discovered peer " << peer.ip << ":" << peer.port << " - local interface address");
|
|
1926
|
-
continue;
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// Check if we're already connected to this peer
|
|
1930
|
-
std::string normalized_peer_address = normalize_peer_address(peer.ip, peer.port);
|
|
1931
|
-
bool already_connected = is_already_connected_to_address(normalized_peer_address);
|
|
1932
|
-
|
|
1933
|
-
if (!already_connected) {
|
|
1934
|
-
// Check if peer limit is reached
|
|
1935
|
-
if (is_peer_limit_reached()) {
|
|
1936
|
-
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to DHT discovered peer " << peer.ip << ":" << peer.port);
|
|
1937
|
-
continue;
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
LOG_CLIENT_DEBUG("Attempting to connect to discovered peer: " << peer.ip << ":" << peer.port);
|
|
1941
|
-
|
|
1942
|
-
// Try to connect to the peer (non-blocking)
|
|
1943
|
-
std::thread([this, peer]() {
|
|
1944
|
-
if (connect_to_peer(peer.ip, peer.port)) {
|
|
1945
|
-
LOG_CLIENT_INFO("Successfully connected to DHT discovered peer: " << peer.ip << ":" << peer.port);
|
|
1946
|
-
} else {
|
|
1947
|
-
LOG_CLIENT_DEBUG("Failed to connect to DHT discovered peer: " << peer.ip << ":" << peer.port);
|
|
1948
|
-
}
|
|
1949
|
-
}).detach();
|
|
1950
|
-
} else {
|
|
1951
|
-
LOG_CLIENT_DEBUG("Already connected to discovered peer: " << normalized_peer_address);
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
void RatsClient::start_automatic_peer_discovery() {
|
|
1957
|
-
if (auto_discovery_running_.load()) {
|
|
1958
|
-
LOG_CLIENT_WARN("Automatic peer discovery is already running");
|
|
1959
|
-
return;
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
LOG_CLIENT_INFO("Starting automatic rats peer discovery");
|
|
1963
|
-
auto_discovery_running_.store(true);
|
|
1964
|
-
auto_discovery_thread_ = std::thread(&RatsClient::automatic_discovery_loop, this);
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
void RatsClient::stop_automatic_peer_discovery() {
|
|
1968
|
-
if (!auto_discovery_running_.load()) {
|
|
1969
|
-
return;
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
LOG_CLIENT_INFO("Stopping automatic peer discovery");
|
|
1973
|
-
auto_discovery_running_.store(false);
|
|
1974
|
-
|
|
1975
|
-
if (auto_discovery_thread_.joinable()) {
|
|
1976
|
-
auto_discovery_thread_.join();
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
LOG_CLIENT_INFO("Automatic peer discovery stopped");
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
bool RatsClient::is_automatic_discovery_running() const {
|
|
1983
|
-
return auto_discovery_running_.load();
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
std::chrono::seconds RatsClient::calculate_discovery_interval() const {
|
|
1987
|
-
int peer_count = get_peer_count();
|
|
1988
|
-
|
|
1989
|
-
// No peers - aggressive discovery
|
|
1990
|
-
if (peer_count == 0) {
|
|
1991
|
-
return std::chrono::seconds(15);
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
// Calculate fill ratio
|
|
1995
|
-
float fill_ratio = static_cast<float>(peer_count) / static_cast<float>(max_peers_);
|
|
1996
|
-
|
|
1997
|
-
// Graduated intervals based on fill ratio
|
|
1998
|
-
if (fill_ratio < 0.25f) {
|
|
1999
|
-
// Less than 25% full - still fairly aggressive
|
|
2000
|
-
return std::chrono::seconds(60); // 1 minute
|
|
2001
|
-
} else if (fill_ratio < 0.50f) {
|
|
2002
|
-
// 25-50% full - moderate
|
|
2003
|
-
return std::chrono::seconds(180); // 3 minutes
|
|
2004
|
-
} else if (fill_ratio < 0.75f) {
|
|
2005
|
-
// 50-75% full - relaxed
|
|
2006
|
-
return std::chrono::seconds(600); // 10 minutes
|
|
2007
|
-
} else {
|
|
2008
|
-
// 75-100% full - very relaxed (mostly just re-announcing)
|
|
2009
|
-
return std::chrono::seconds(1800); // 30 minutes
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
void RatsClient::automatic_discovery_loop() {
|
|
2014
|
-
LOG_CLIENT_INFO("Automatic peer discovery loop started");
|
|
2015
|
-
|
|
2016
|
-
// Initial delay to let DHT bootstrap
|
|
2017
|
-
{
|
|
2018
|
-
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
2019
|
-
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(5), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
2020
|
-
LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
|
|
2021
|
-
return;
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
// Announce immediately - this also discovers peers during traversal
|
|
2026
|
-
announce_rats_peer();
|
|
2027
|
-
|
|
2028
|
-
auto last_announce = std::chrono::steady_clock::now();
|
|
2029
|
-
|
|
2030
|
-
while (auto_discovery_running_.load()) {
|
|
2031
|
-
auto now = std::chrono::steady_clock::now();
|
|
2032
|
-
|
|
2033
|
-
// Announce combines both announcing our presence and discovering peers
|
|
2034
|
-
// Interval scales based on peer count: aggressive when empty, relaxed when nearly full
|
|
2035
|
-
auto interval = calculate_discovery_interval();
|
|
2036
|
-
|
|
2037
|
-
if (now - last_announce >= interval) {
|
|
2038
|
-
LOG_CLIENT_DEBUG("Discovery interval: " << interval.count() << "s (peers: "
|
|
2039
|
-
<< get_peer_count() << "/" << max_peers_ << ")");
|
|
2040
|
-
announce_rats_peer();
|
|
2041
|
-
last_announce = now;
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
// Use conditional variable for responsive shutdown
|
|
2045
|
-
{
|
|
2046
|
-
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
2047
|
-
if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
2048
|
-
break;
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
LOG_CLIENT_INFO("Automatic peer discovery loop stopped");
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
void RatsClient::announce_rats_peer() {
|
|
2057
|
-
if (!dht_client_ || !dht_client_->is_running()) {
|
|
2058
|
-
LOG_CLIENT_WARN("DHT client not running, cannot announce peer");
|
|
2059
|
-
return;
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
std::string discovery_hash = get_discovery_hash();
|
|
2063
|
-
LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
|
|
2064
|
-
|
|
2065
|
-
InfoHash info_hash = hex_to_node_id(discovery_hash);
|
|
2066
|
-
|
|
2067
|
-
if (dht_client_->is_announce_active(info_hash)) {
|
|
2068
|
-
LOG_CLIENT_WARN("Announce already in progress for info hash: " << node_id_to_hex(info_hash));
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
// Use announce with callback - combines announce and find_peers in one traversal
|
|
2073
|
-
// Peers discovered during traversal will be returned through the callback
|
|
2074
|
-
if (announce_for_hash(discovery_hash, listen_port_, [this, info_hash](const std::vector<std::string>& peer_addresses) {
|
|
2075
|
-
LOG_CLIENT_INFO("Announce discovered " << peer_addresses.size() << " peers during traversal");
|
|
2076
|
-
|
|
2077
|
-
// Convert peer addresses to Peer objects for handle_dht_peer_discovery()
|
|
2078
|
-
std::vector<Peer> peers;
|
|
2079
|
-
peers.reserve(peer_addresses.size());
|
|
2080
|
-
for (const auto& peer_address : peer_addresses) {
|
|
2081
|
-
std::string ip;
|
|
2082
|
-
int port;
|
|
2083
|
-
if (parse_address_string(peer_address, ip, port)) {
|
|
2084
|
-
peers.push_back(Peer(ip, port));
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
// Auto-connect to discovered peers
|
|
2089
|
-
if (!peers.empty()) {
|
|
2090
|
-
handle_dht_peer_discovery(peers, info_hash);
|
|
2091
|
-
}
|
|
2092
|
-
})) {
|
|
2093
|
-
LOG_CLIENT_DEBUG("Successfully started announce with peer discovery for discovery hash");
|
|
2094
|
-
} else {
|
|
2095
|
-
LOG_CLIENT_WARN("Failed to announce peer for discovery");
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
std::string RatsClient::get_discovery_hash() const {
|
|
2101
|
-
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
2102
|
-
// Generate discovery hash based on current protocol configuration
|
|
2103
|
-
std::string discovery_string = custom_protocol_name_ + "_peer_discovery_v" + custom_protocol_version_;
|
|
2104
|
-
return SHA1::hash(discovery_string);
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
std::string RatsClient::get_rats_peer_discovery_hash() {
|
|
2108
|
-
// Well-known hash for rats peer discovery
|
|
2109
|
-
// Compute SHA1 hash of "rats_peer_discovery_v1.0"
|
|
2110
|
-
return SHA1::hash("rats_peer_discovery_v1.0");
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
1848
|
// =========================================================================
|
|
2114
1849
|
// Protocol Configuration
|
|
2115
1850
|
// =========================================================================
|
|
@@ -2140,11 +1875,10 @@ std::string RatsClient::get_protocol_version() const {
|
|
|
2140
1875
|
// Message Exchange API
|
|
2141
1876
|
// =========================================================================
|
|
2142
1877
|
|
|
2143
|
-
|
|
2144
1878
|
void RatsClient::on(const std::string& message_type, MessageCallback callback) {
|
|
2145
1879
|
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2146
1880
|
message_handlers_[message_type].emplace_back(callback, false); // false = not once
|
|
2147
|
-
|
|
1881
|
+
LOG_CLIENT_DEBUG("Registered handler for message type: " << message_type);
|
|
2148
1882
|
}
|
|
2149
1883
|
|
|
2150
1884
|
void RatsClient::once(const std::string& message_type, MessageCallback callback) {
|
|
@@ -2172,15 +1906,15 @@ void RatsClient::send(const std::string& message_type, const nlohmann::json& dat
|
|
|
2172
1906
|
return;
|
|
2173
1907
|
}
|
|
2174
1908
|
|
|
2175
|
-
|
|
1909
|
+
LOG_CLIENT_DEBUG("Sending broadcast message type '" << message_type << "'");
|
|
2176
1910
|
|
|
2177
1911
|
// Create rats message
|
|
2178
1912
|
nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
|
|
2179
1913
|
|
|
2180
1914
|
// Broadcast to all validated peers
|
|
2181
|
-
int sent_count =
|
|
1915
|
+
int sent_count = broadcast_rats_message(message);
|
|
2182
1916
|
|
|
2183
|
-
|
|
1917
|
+
LOG_CLIENT_DEBUG("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
|
|
2184
1918
|
|
|
2185
1919
|
if (callback) {
|
|
2186
1920
|
if (sent_count > 0) {
|
|
@@ -2201,7 +1935,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2201
1935
|
return;
|
|
2202
1936
|
}
|
|
2203
1937
|
|
|
2204
|
-
|
|
1938
|
+
LOG_CLIENT_DEBUG("Sending targeted message type '" << message_type << "' to peer " << peer_id);
|
|
2205
1939
|
|
|
2206
1940
|
// Create rats message
|
|
2207
1941
|
nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
|
|
@@ -2241,7 +1975,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2241
1975
|
|
|
2242
1976
|
bool success = send_json_to_peer(target_socket, message);
|
|
2243
1977
|
|
|
2244
|
-
|
|
1978
|
+
LOG_CLIENT_DEBUG("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
|
|
2245
1979
|
|
|
2246
1980
|
if (callback) {
|
|
2247
1981
|
if (success) {
|
|
@@ -2255,54 +1989,41 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2255
1989
|
// Message exchange system helpers
|
|
2256
1990
|
void RatsClient::call_message_handlers(const std::string& message_type, const std::string& peer_id, const nlohmann::json& data) {
|
|
2257
1991
|
std::vector<MessageHandler> handlers_to_call;
|
|
2258
|
-
std::vector<MessageHandler> remaining_handlers;
|
|
2259
1992
|
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
// Get handlers to call and identify once handlers
|
|
1993
|
+
// Get handlers to call and remove once handlers atomically
|
|
2263
1994
|
{
|
|
2264
1995
|
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2265
1996
|
auto it = message_handlers_.find(message_type);
|
|
2266
|
-
if (it
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
// Keep only non-once handlers for the remaining list
|
|
2270
|
-
for (const auto& handler : it->second) {
|
|
2271
|
-
if (!handler.is_once) {
|
|
2272
|
-
remaining_handlers.push_back(handler);
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
// Update the handlers list (removes once handlers)
|
|
2277
|
-
it->second = remaining_handlers;
|
|
2278
|
-
} else {
|
|
2279
|
-
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;
|
|
2280
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());
|
|
2281
2009
|
}
|
|
2282
2010
|
|
|
2283
|
-
|
|
2011
|
+
LOG_CLIENT_DEBUG("Calling " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
|
|
2284
2012
|
|
|
2285
2013
|
// Call handlers outside of mutex to avoid deadlock
|
|
2286
2014
|
for (const auto& handler : handlers_to_call) {
|
|
2287
2015
|
try {
|
|
2288
|
-
LOG_CLIENT_INFO("Calling handler for message type '" << message_type << "'");
|
|
2289
2016
|
handler.callback(peer_id, data);
|
|
2290
|
-
LOG_CLIENT_INFO("Handler for message type '" << message_type << "' completed successfully");
|
|
2291
2017
|
} catch (const std::exception& e) {
|
|
2292
2018
|
LOG_CLIENT_ERROR("Exception in message handler for type '" << message_type << "': " << e.what());
|
|
2293
2019
|
} catch (...) {
|
|
2294
2020
|
LOG_CLIENT_ERROR("Unknown exception in message handler for type '" << message_type << "'");
|
|
2295
2021
|
}
|
|
2296
2022
|
}
|
|
2297
|
-
|
|
2298
|
-
if (!handlers_to_call.empty()) {
|
|
2299
|
-
LOG_CLIENT_INFO("Called " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
|
|
2300
|
-
}
|
|
2301
2023
|
}
|
|
2302
2024
|
|
|
2303
|
-
|
|
2304
2025
|
// =========================================================================
|
|
2305
|
-
// Rats
|
|
2026
|
+
// Rats Protocol Message Handling
|
|
2306
2027
|
// =========================================================================
|
|
2307
2028
|
|
|
2308
2029
|
nlohmann::json RatsClient::create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id) {
|
|
@@ -2317,26 +2038,26 @@ nlohmann::json RatsClient::create_rats_message(const std::string& type, const nl
|
|
|
2317
2038
|
return message;
|
|
2318
2039
|
}
|
|
2319
2040
|
|
|
2320
|
-
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) {
|
|
2321
2042
|
try {
|
|
2322
2043
|
std::string message_type = message.value("type", "");
|
|
2323
2044
|
nlohmann::json payload = message.value("payload", nlohmann::json::object());
|
|
2324
2045
|
std::string sender_peer_id = message.value("sender_peer_id", "");
|
|
2325
2046
|
|
|
2326
|
-
LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " <<
|
|
2047
|
+
LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_id);
|
|
2327
2048
|
|
|
2328
2049
|
// Call registered message handlers for all message types (including custom ones)
|
|
2329
|
-
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);
|
|
2330
2051
|
|
|
2331
2052
|
// Handle built-in message types for internal functionality
|
|
2332
2053
|
if (message_type == "peer") {
|
|
2333
|
-
handle_peer_exchange_message(socket,
|
|
2054
|
+
handle_peer_exchange_message(socket, peer_id, payload);
|
|
2334
2055
|
}
|
|
2335
2056
|
else if (message_type == "peers_request") {
|
|
2336
|
-
handle_peers_request_message(socket,
|
|
2057
|
+
handle_peers_request_message(socket, peer_id, payload);
|
|
2337
2058
|
}
|
|
2338
2059
|
else if (message_type == "peers_response") {
|
|
2339
|
-
handle_peers_response_message(socket,
|
|
2060
|
+
handle_peers_response_message(socket, peer_id, payload);
|
|
2340
2061
|
}
|
|
2341
2062
|
// Custom message types are now handled by registered handlers above
|
|
2342
2063
|
// No need for else clause - all message types are valid if they have registered handlers
|
|
@@ -2346,54 +2067,39 @@ void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_ha
|
|
|
2346
2067
|
}
|
|
2347
2068
|
}
|
|
2348
2069
|
|
|
2349
|
-
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) {
|
|
2350
2071
|
try {
|
|
2351
|
-
std::string
|
|
2352
|
-
int
|
|
2353
|
-
std::string
|
|
2354
|
-
|
|
2355
|
-
if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
|
|
2356
|
-
LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_hash_id);
|
|
2357
|
-
return;
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
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", "");
|
|
2361
2075
|
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
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);
|
|
2365
2078
|
return;
|
|
2366
2079
|
}
|
|
2367
2080
|
|
|
2368
|
-
|
|
2369
|
-
std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
|
|
2370
|
-
if (is_already_connected_to_address(normalized_peer_address)) {
|
|
2371
|
-
LOG_CLIENT_DEBUG("Already connected to exchanged peer " << normalized_peer_address);
|
|
2372
|
-
return;
|
|
2373
|
-
}
|
|
2081
|
+
LOG_CLIENT_INFO("Received peer exchange: " << exchanged_ip << ":" << exchanged_port << " (peer_id: " << exchanged_peer_id << ")");
|
|
2374
2082
|
|
|
2375
|
-
|
|
2376
|
-
if (is_peer_limit_reached()) {
|
|
2377
|
-
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)) {
|
|
2378
2084
|
return;
|
|
2379
2085
|
}
|
|
2380
2086
|
|
|
2381
2087
|
// Try to connect to the exchanged peer (non-blocking)
|
|
2382
|
-
add_managed_thread(std::thread([this,
|
|
2383
|
-
if (connect_to_peer(
|
|
2384
|
-
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);
|
|
2385
2091
|
} else {
|
|
2386
|
-
LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " <<
|
|
2092
|
+
LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
|
|
2387
2093
|
}
|
|
2388
|
-
}), "peer-exchange-connect-" +
|
|
2094
|
+
}), "peer-exchange-connect-" + exchanged_peer_id.substr(0, 8));
|
|
2389
2095
|
|
|
2390
2096
|
} catch (const nlohmann::json::exception& e) {
|
|
2391
2097
|
LOG_CLIENT_ERROR("Failed to handle peer exchange message: " << e.what());
|
|
2392
2098
|
}
|
|
2393
2099
|
}
|
|
2394
2100
|
|
|
2395
|
-
// General broadcasting
|
|
2396
|
-
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) {
|
|
2397
2103
|
// Serialize JSON once before iterating
|
|
2398
2104
|
std::string json_string;
|
|
2399
2105
|
try {
|
|
@@ -2404,53 +2110,27 @@ int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std:
|
|
|
2404
2110
|
}
|
|
2405
2111
|
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
2406
2112
|
|
|
2407
|
-
|
|
2113
|
+
// Collect targets under lock, then enqueue outside
|
|
2114
|
+
std::vector<PeerSendTarget> targets;
|
|
2408
2115
|
{
|
|
2409
2116
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
// Don't send to excluded peer
|
|
2117
|
+
targets.reserve(peers_.size());
|
|
2118
|
+
for (const auto& [id, peer] : peers_) {
|
|
2413
2119
|
if (!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) {
|
|
2414
2120
|
continue;
|
|
2415
2121
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
|
|
2419
|
-
if (send_binary_to_peer_unlocked(peer.socket, binary_data, MessageDataType::JSON, send_cipher, peer.peer_id)) {
|
|
2420
|
-
sent_count++;
|
|
2122
|
+
if (validated_only && !peer.is_handshake_completed()) {
|
|
2123
|
+
continue;
|
|
2421
2124
|
}
|
|
2125
|
+
targets.push_back({peer.socket, peer.peer_id,
|
|
2126
|
+
peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
|
|
2422
2127
|
}
|
|
2423
2128
|
}
|
|
2424
|
-
return sent_count;
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
int RatsClient::broadcast_rats_message_to_validated_peers(const nlohmann::json& message, const std::string& exclude_peer_id) {
|
|
2428
|
-
// Serialize JSON once before iterating
|
|
2429
|
-
std::string json_string;
|
|
2430
|
-
try {
|
|
2431
|
-
json_string = message.dump();
|
|
2432
|
-
} catch (const nlohmann::json::exception& e) {
|
|
2433
|
-
LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
|
|
2434
|
-
return 0;
|
|
2435
|
-
}
|
|
2436
|
-
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
2437
2129
|
|
|
2438
2130
|
int sent_count = 0;
|
|
2439
|
-
{
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
const RatsPeer& peer = pair.second;
|
|
2443
|
-
// Don't send to excluded peer and only send to peers with completed handshake
|
|
2444
|
-
if ((!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) ||
|
|
2445
|
-
!peer.is_handshake_completed()) {
|
|
2446
|
-
continue;
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2449
|
-
// Use unlocked version since we already hold peers_mutex_
|
|
2450
|
-
rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
|
|
2451
|
-
if (send_binary_to_peer_unlocked(peer.socket, binary_data, MessageDataType::JSON, send_cipher, peer.peer_id)) {
|
|
2452
|
-
sent_count++;
|
|
2453
|
-
}
|
|
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++;
|
|
2454
2134
|
}
|
|
2455
2135
|
}
|
|
2456
2136
|
return sent_count;
|
|
@@ -2479,7 +2159,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
|
|
|
2479
2159
|
nlohmann::json message = create_peer_exchange_message(new_peer);
|
|
2480
2160
|
|
|
2481
2161
|
// Broadcast to all validated peers except the new peer
|
|
2482
|
-
int sent_count =
|
|
2162
|
+
int sent_count = broadcast_rats_message(message, new_peer.peer_id);
|
|
2483
2163
|
|
|
2484
2164
|
LOG_CLIENT_INFO("Broadcasted peer exchange message for " << new_peer.ip << ":" << new_peer.port
|
|
2485
2165
|
<< " to " << sent_count << " peers");
|
|
@@ -2488,7 +2168,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
|
|
|
2488
2168
|
// Peers request/response system implementation
|
|
2489
2169
|
nlohmann::json RatsClient::create_peers_request_message(const std::string& sender_peer_id) {
|
|
2490
2170
|
nlohmann::json payload;
|
|
2491
|
-
payload["max_peers"] =
|
|
2171
|
+
payload["max_peers"] = MAX_PEERS_REQUEST_COUNT;
|
|
2492
2172
|
payload["requester_info"] = {
|
|
2493
2173
|
{"listen_port", listen_port_},
|
|
2494
2174
|
{"peer_count", get_peer_count()}
|
|
@@ -2502,39 +2182,38 @@ nlohmann::json RatsClient::create_peers_response_message(const std::vector<RatsP
|
|
|
2502
2182
|
nlohmann::json peers_array = nlohmann::json::array();
|
|
2503
2183
|
|
|
2504
2184
|
for (const auto& peer : peers) {
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
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
|
+
});
|
|
2511
2191
|
}
|
|
2512
2192
|
|
|
2513
|
-
payload["peers"] = peers_array;
|
|
2193
|
+
payload["peers"] = std::move(peers_array);
|
|
2514
2194
|
payload["total_peers"] = get_peer_count();
|
|
2515
2195
|
|
|
2516
2196
|
return create_rats_message("peers_response", payload, sender_peer_id);
|
|
2517
2197
|
}
|
|
2518
2198
|
|
|
2519
|
-
|
|
2520
|
-
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) {
|
|
2521
2200
|
try {
|
|
2522
|
-
int max_peers = payload.value("max_peers",
|
|
2201
|
+
int max_peers = payload.value("max_peers", MAX_PEERS_REQUEST_COUNT);
|
|
2523
2202
|
|
|
2524
|
-
LOG_CLIENT_INFO("Received peers request from " <<
|
|
2203
|
+
LOG_CLIENT_INFO("Received peers request from " << peer_id << " for up to " << max_peers << " peers");
|
|
2525
2204
|
|
|
2526
2205
|
// Get random peers excluding the requester
|
|
2527
|
-
std::vector<RatsPeer> random_peers = get_random_peers(max_peers,
|
|
2206
|
+
std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_id);
|
|
2528
2207
|
|
|
2529
|
-
LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " <<
|
|
2208
|
+
LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_id);
|
|
2530
2209
|
|
|
2531
2210
|
// Create and send peers response
|
|
2532
|
-
nlohmann::json response_message = create_peers_response_message(random_peers,
|
|
2211
|
+
nlohmann::json response_message = create_peers_response_message(random_peers, peer_id);
|
|
2533
2212
|
|
|
2534
2213
|
if (!send_json_to_peer(socket, response_message)) {
|
|
2535
|
-
LOG_CLIENT_ERROR("Failed to send peers response to " <<
|
|
2214
|
+
LOG_CLIENT_ERROR("Failed to send peers response to " << peer_id);
|
|
2536
2215
|
} else {
|
|
2537
|
-
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);
|
|
2538
2217
|
}
|
|
2539
2218
|
|
|
2540
2219
|
} catch (const nlohmann::json::exception& e) {
|
|
@@ -2542,55 +2221,39 @@ void RatsClient::handle_peers_request_message(socket_t socket, const std::string
|
|
|
2542
2221
|
}
|
|
2543
2222
|
}
|
|
2544
2223
|
|
|
2545
|
-
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) {
|
|
2546
2225
|
try {
|
|
2547
2226
|
nlohmann::json peers_array = payload.value("peers", nlohmann::json::array());
|
|
2548
2227
|
int total_peers = payload.value("total_peers", 0);
|
|
2549
2228
|
|
|
2550
|
-
LOG_CLIENT_INFO("Received peers response from " <<
|
|
2229
|
+
LOG_CLIENT_INFO("Received peers response from " << peer_id << " with " << peers_array.size()
|
|
2551
2230
|
<< " peers (total: " << total_peers << ")");
|
|
2552
2231
|
|
|
2553
2232
|
// Process each peer in the response
|
|
2554
2233
|
for (const auto& peer_info : peers_array) {
|
|
2555
|
-
std::string
|
|
2556
|
-
int
|
|
2557
|
-
std::string
|
|
2558
|
-
|
|
2559
|
-
if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
|
|
2560
|
-
LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_hash_id);
|
|
2561
|
-
continue;
|
|
2562
|
-
}
|
|
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", "");
|
|
2563
2237
|
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
// Check if we should ignore this peer (local interface)
|
|
2567
|
-
if (should_ignore_peer(peer_ip, peer_port)) {
|
|
2568
|
-
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);
|
|
2569
2240
|
continue;
|
|
2570
2241
|
}
|
|
2571
2242
|
|
|
2572
|
-
|
|
2573
|
-
std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
|
|
2574
|
-
if (is_already_connected_to_address(normalized_peer_address)) {
|
|
2575
|
-
LOG_CLIENT_DEBUG("Already connected to peer from response " << normalized_peer_address);
|
|
2576
|
-
continue;
|
|
2577
|
-
}
|
|
2243
|
+
LOG_CLIENT_DEBUG("Processing peer from response: " << resp_ip << ":" << resp_port << " (peer_id: " << resp_peer_id << ")");
|
|
2578
2244
|
|
|
2579
|
-
|
|
2580
|
-
if (is_peer_limit_reached()) {
|
|
2581
|
-
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)) {
|
|
2582
2246
|
continue;
|
|
2583
2247
|
}
|
|
2584
2248
|
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
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);
|
|
2590
2253
|
} else {
|
|
2591
|
-
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);
|
|
2592
2255
|
}
|
|
2593
|
-
}), "peer-response-connect-" +
|
|
2256
|
+
}), "peer-response-connect-" + resp_peer_id.substr(0, 8));
|
|
2594
2257
|
}
|
|
2595
2258
|
|
|
2596
2259
|
} catch (const nlohmann::json::exception& e) {
|
|
@@ -2609,49 +2272,7 @@ void RatsClient::send_peers_request(socket_t socket, const std::string& our_peer
|
|
|
2609
2272
|
}
|
|
2610
2273
|
|
|
2611
2274
|
// =========================================================================
|
|
2612
|
-
//
|
|
2613
|
-
// =========================================================================
|
|
2614
|
-
|
|
2615
|
-
nlohmann::json RatsClient::get_connection_statistics() const {
|
|
2616
|
-
nlohmann::json stats;
|
|
2617
|
-
|
|
2618
|
-
{
|
|
2619
|
-
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2620
|
-
stats["total_peers"] = peers_.size();
|
|
2621
|
-
stats["validated_peers"] = get_peer_count_unlocked();
|
|
2622
|
-
stats["max_peers"] = max_peers_;
|
|
2623
|
-
}
|
|
2624
|
-
|
|
2625
|
-
stats["running"] = is_running();
|
|
2626
|
-
stats["listen_port"] = listen_port_;
|
|
2627
|
-
stats["our_peer_id"] = get_our_peer_id();
|
|
2628
|
-
stats["encryption_enabled"] = is_encryption_enabled();
|
|
2629
|
-
|
|
2630
|
-
// DHT statistics
|
|
2631
|
-
if (dht_client_ && dht_client_->is_running()) {
|
|
2632
|
-
stats["dht_running"] = true;
|
|
2633
|
-
stats["dht_routing_table_size"] = get_dht_routing_table_size();
|
|
2634
|
-
} else {
|
|
2635
|
-
stats["dht_running"] = false;
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
// mDNS statistics
|
|
2639
|
-
stats["mdns_running"] = is_mdns_running();
|
|
2640
|
-
|
|
2641
|
-
// Reconnection statistics
|
|
2642
|
-
{
|
|
2643
|
-
std::lock_guard<std::mutex> lock(reconnect_mutex_);
|
|
2644
|
-
stats["reconnect_enabled"] = reconnect_config_.enabled;
|
|
2645
|
-
stats["reconnect_queue_size"] = reconnect_queue_.size();
|
|
2646
|
-
stats["reconnect_max_attempts"] = reconnect_config_.max_attempts;
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
return stats;
|
|
2650
|
-
}
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
// =========================================================================
|
|
2654
|
-
// Helper functions
|
|
2275
|
+
// Helper Functions
|
|
2655
2276
|
// =========================================================================
|
|
2656
2277
|
|
|
2657
2278
|
std::unique_ptr<RatsClient> create_rats_client(int listen_port) {
|
|
@@ -2721,52 +2342,4 @@ bool RatsClient::parse_address_string(const std::string& address_str, std::strin
|
|
|
2721
2342
|
return !out_ip.empty() && out_port > 0 && out_port <= 65535;
|
|
2722
2343
|
}
|
|
2723
2344
|
|
|
2724
|
-
// Cached formatting helpers - computed once on first use
|
|
2725
|
-
static const std::string& get_box_separator() {
|
|
2726
|
-
static const std::string separator = supports_unicode() ?
|
|
2727
|
-
"════════════════════════════════════════════════════════════════════" :
|
|
2728
|
-
"=====================================================================";
|
|
2729
|
-
return separator;
|
|
2730
|
-
}
|
|
2731
|
-
|
|
2732
|
-
static const std::string& get_box_vertical() {
|
|
2733
|
-
static const std::string vertical = supports_unicode() ? "│" : "|";
|
|
2734
|
-
return vertical;
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
static const std::string& get_checkmark() {
|
|
2738
|
-
static const std::string checkmark = supports_unicode() ? "✓" : "[*]";
|
|
2739
|
-
return checkmark;
|
|
2740
|
-
}
|
|
2741
|
-
|
|
2742
|
-
void RatsClient::log_handshake_completion_unlocked(const RatsPeer& peer) {
|
|
2743
|
-
// Calculate connection duration
|
|
2744
|
-
auto now = std::chrono::steady_clock::now();
|
|
2745
|
-
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - peer.connected_at);
|
|
2746
|
-
|
|
2747
|
-
// Get current peer count (assumes peers_mutex_ is already locked)
|
|
2748
|
-
int current_peer_count = get_peer_count_unlocked();
|
|
2749
|
-
|
|
2750
|
-
// Create visually appealing log output
|
|
2751
|
-
std::string connection_type = peer.is_outgoing ? "OUTGOING" : "INCOMING";
|
|
2752
|
-
const std::string& separator = get_box_separator();
|
|
2753
|
-
const std::string& vertical = get_box_vertical();
|
|
2754
|
-
const std::string& checkmark = get_checkmark();
|
|
2755
|
-
|
|
2756
|
-
LOG_CLIENT_INFO("");
|
|
2757
|
-
LOG_CLIENT_INFO(separator);
|
|
2758
|
-
LOG_CLIENT_INFO(checkmark << " HANDSHAKE COMPLETED - NEW PEER CONNECTED");
|
|
2759
|
-
LOG_CLIENT_INFO(separator);
|
|
2760
|
-
LOG_CLIENT_INFO(vertical << " Peer ID : " << peer.peer_id);
|
|
2761
|
-
LOG_CLIENT_INFO(vertical << " Address : " << peer.ip << ":" << peer.port);
|
|
2762
|
-
LOG_CLIENT_INFO(vertical << " Connection : " << connection_type);
|
|
2763
|
-
LOG_CLIENT_INFO(vertical << " Protocol Ver. : " << peer.version);
|
|
2764
|
-
LOG_CLIENT_INFO(vertical << " Socket : " << peer.socket);
|
|
2765
|
-
LOG_CLIENT_INFO(vertical << " Duration : " << duration.count() << "ms");
|
|
2766
|
-
LOG_CLIENT_INFO(vertical << " Network Peers : " << current_peer_count << "/" << max_peers_);
|
|
2767
|
-
|
|
2768
|
-
LOG_CLIENT_INFO(separator);
|
|
2769
|
-
LOG_CLIENT_INFO("");
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
2345
|
} // namespace librats
|