librats 0.7.2 → 0.9.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/README.md +9 -4
- package/lib/index.d.ts +9 -22
- package/native-src/CMakeLists.txt +132 -50
- package/native-src/cmake/ratsConfig.cmake.in +8 -0
- package/native-src/src/bt_network.cpp +296 -183
- package/native-src/src/bt_network.h +25 -5
- package/native-src/src/crypto/sha256.c +1 -1
- package/native-src/src/dht.cpp +297 -47
- package/native-src/src/dht.h +70 -6
- package/native-src/src/file_transfer.cpp +1185 -1578
- package/native-src/src/file_transfer.h +240 -521
- package/native-src/src/io_poller.cpp +917 -0
- package/native-src/src/io_poller.h +138 -0
- package/native-src/src/krpc.cpp +161 -96
- package/native-src/src/krpc.h +18 -4
- package/native-src/src/librats.cpp +812 -1236
- package/native-src/src/librats.h +207 -208
- package/native-src/src/librats_bittorrent.cpp +1 -5
- package/native-src/src/librats_c.cpp +22 -39
- package/native-src/src/librats_discovery.cpp +377 -0
- package/native-src/src/librats_encryption.cpp +130 -283
- package/native-src/src/librats_file_transfer.cpp +27 -109
- package/native-src/src/librats_gossipsub.cpp +1 -5
- package/native-src/src/librats_ice.cpp +5 -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 +105 -0
- package/native-src/src/socket.cpp +15 -3
- package/package.json +1 -1
- package/scripts/build-librats.js +3 -0
- package/scripts/prepare-package.js +10 -0
- package/src/librats_node.cpp +53 -64
|
@@ -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,567 @@ 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);
|
|
654
|
+
}
|
|
655
|
+
if (file_transfer_manager_) {
|
|
656
|
+
file_transfer_manager_->on_peer_disconnected(peer_id);
|
|
747
657
|
}
|
|
748
|
-
|
|
749
|
-
// Schedule reconnection if we have valid peer info
|
|
750
658
|
if (should_schedule_reconnect && running_.load()) {
|
|
751
659
|
schedule_reconnect(peer_copy_for_reconnect);
|
|
752
660
|
}
|
|
753
|
-
|
|
754
661
|
if (running_.load()) {
|
|
755
662
|
add_managed_thread(std::thread([this]() {
|
|
756
|
-
if (running_.load())
|
|
757
|
-
save_configuration();
|
|
758
|
-
}
|
|
663
|
+
if (running_.load()) save_configuration();
|
|
759
664
|
}), "config-save-disconnect");
|
|
760
665
|
}
|
|
761
666
|
}
|
|
762
667
|
|
|
763
|
-
LOG_CLIENT_INFO("
|
|
668
|
+
LOG_CLIENT_INFO("Peer disconnected: " << peer_id);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// ---------------------------------------------------------------------------
|
|
672
|
+
// Poller registration helpers (thread-safe via io_mutex_)
|
|
673
|
+
// ---------------------------------------------------------------------------
|
|
674
|
+
void RatsClient::poller_add(socket_t fd, uint32_t events) {
|
|
675
|
+
std::lock_guard<std::mutex> lock(io_mutex_);
|
|
676
|
+
if (poller_) poller_->add(fd, events);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
void RatsClient::poller_modify(socket_t fd, uint32_t events) {
|
|
680
|
+
std::lock_guard<std::mutex> lock(io_mutex_);
|
|
681
|
+
if (poller_) poller_->modify(fd, events);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
void RatsClient::poller_remove(socket_t fd) {
|
|
685
|
+
std::lock_guard<std::mutex> lock(io_mutex_);
|
|
686
|
+
if (poller_) poller_->remove(fd);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// ---------------------------------------------------------------------------
|
|
690
|
+
// enqueue_message – build a length-prefixed frame and append to send buffer
|
|
691
|
+
// ---------------------------------------------------------------------------
|
|
692
|
+
bool RatsClient::enqueue_message(socket_t socket, const std::vector<uint8_t>& data) {
|
|
693
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
694
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
695
|
+
if (peer_it == peers_.end()) return false;
|
|
696
|
+
return enqueue_message_unlocked(peer_it->second, data);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
bool RatsClient::enqueue_message_unlocked(RatsPeer& peer, const std::vector<uint8_t>& data) {
|
|
700
|
+
// Build length-prefixed frame: [4-byte network-order length][payload]
|
|
701
|
+
uint32_t net_len = htonl(static_cast<uint32_t>(data.size()));
|
|
702
|
+
|
|
703
|
+
std::vector<uint8_t> frame;
|
|
704
|
+
frame.reserve(4 + data.size());
|
|
705
|
+
frame.insert(frame.end(),
|
|
706
|
+
reinterpret_cast<const uint8_t*>(&net_len),
|
|
707
|
+
reinterpret_cast<const uint8_t*>(&net_len) + 4);
|
|
708
|
+
frame.insert(frame.end(), data.begin(), data.end());
|
|
709
|
+
|
|
710
|
+
peer.io_.send_buffer.append(std::move(frame));
|
|
711
|
+
|
|
712
|
+
// Arm PollOut so io_loop flushes the buffer
|
|
713
|
+
{
|
|
714
|
+
std::lock_guard<std::mutex> io_lock(io_mutex_);
|
|
715
|
+
if (poller_) poller_->modify(peer.socket, PollIn | PollOut);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
void RatsClient::management_loop() {
|
|
722
|
+
LOG_CLIENT_INFO("Management loop started");
|
|
723
|
+
|
|
724
|
+
auto last_thread_cleanup = std::chrono::steady_clock::now();
|
|
725
|
+
const auto thread_cleanup_interval = std::chrono::seconds(THREAD_CLEANUP_INTERVAL_SECONDS);
|
|
726
|
+
|
|
727
|
+
while (running_.load()) {
|
|
728
|
+
// Wait for interval or until shutdown (for responsive reconnection processing)
|
|
729
|
+
{
|
|
730
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
731
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(MANAGEMENT_LOOP_INTERVAL_SECONDS), [this] { return !running_.load(); })) {
|
|
732
|
+
break; // Exit if shutdown requested
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Check handshake timeouts (centralized, runs once for all peers)
|
|
737
|
+
try {
|
|
738
|
+
check_handshake_timeouts();
|
|
739
|
+
} catch (const std::exception& e) {
|
|
740
|
+
LOG_CLIENT_ERROR("Exception during handshake timeout check: " << e.what());
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Process reconnection queue
|
|
744
|
+
try {
|
|
745
|
+
process_reconnect_queue();
|
|
746
|
+
} catch (const std::exception& e) {
|
|
747
|
+
LOG_CLIENT_ERROR("Exception during reconnect queue processing: " << e.what());
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Periodically cleanup finished threads (every 30 seconds)
|
|
751
|
+
auto now = std::chrono::steady_clock::now();
|
|
752
|
+
if (now - last_thread_cleanup >= thread_cleanup_interval) {
|
|
753
|
+
try {
|
|
754
|
+
cleanup_finished_threads();
|
|
755
|
+
LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
|
|
756
|
+
} catch (const std::exception& e) {
|
|
757
|
+
LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
|
|
758
|
+
}
|
|
759
|
+
last_thread_cleanup = now;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
LOG_CLIENT_INFO("Management loop ended");
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
void RatsClient::handle_post_handshake_completion(socket_t socket, const RatsPeer& peer_copy) {
|
|
767
|
+
// Remove from reconnection queue (successful connection)
|
|
768
|
+
remove_from_reconnect_queue(peer_copy.peer_id);
|
|
769
|
+
|
|
770
|
+
// Connection callback
|
|
771
|
+
if (connection_callback_) {
|
|
772
|
+
connection_callback_(socket, peer_copy.peer_id);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// GossipSub notification
|
|
776
|
+
if (gossipsub_) {
|
|
777
|
+
gossipsub_->handle_peer_connected(peer_copy.peer_id);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
#ifdef RATS_STORAGE
|
|
781
|
+
// Storage manager notification
|
|
782
|
+
if (storage_manager_) {
|
|
783
|
+
storage_manager_->on_peer_connected(peer_copy.peer_id);
|
|
784
|
+
}
|
|
785
|
+
#endif
|
|
786
|
+
|
|
787
|
+
// Peer exchange broadcast
|
|
788
|
+
broadcast_peer_exchange_message(peer_copy);
|
|
789
|
+
|
|
790
|
+
// Request peers from newly connected peer (outgoing only)
|
|
791
|
+
if (peer_copy.is_outgoing) {
|
|
792
|
+
send_peers_request(socket, peer_copy.peer_id);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Save configuration
|
|
796
|
+
if (running_.load()) {
|
|
797
|
+
add_managed_thread(std::thread([this]() {
|
|
798
|
+
if (running_.load()) {
|
|
799
|
+
save_configuration();
|
|
800
|
+
}
|
|
801
|
+
}), "config-save");
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
void RatsClient::process_message(socket_t socket, const std::vector<uint8_t>& data, const std::string& initial_peer_id) {
|
|
806
|
+
MessageHeader header;
|
|
807
|
+
std::vector<uint8_t> payload;
|
|
808
|
+
|
|
809
|
+
if (!parse_message_with_header(data, header, payload)) {
|
|
810
|
+
LOG_CLIENT_WARN("No header found in message from " << initial_peer_id);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
std::string peer_id = get_peer_id(socket);
|
|
815
|
+
|
|
816
|
+
switch (header.type) {
|
|
817
|
+
case MessageDataType::BINARY: {
|
|
818
|
+
LOG_CLIENT_DEBUG("Received BINARY message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
819
|
+
bool handled = false;
|
|
820
|
+
if (file_transfer_manager_) {
|
|
821
|
+
handled = file_transfer_manager_->handle_binary_data(peer_id, payload);
|
|
822
|
+
}
|
|
823
|
+
if (!handled && binary_data_callback_) {
|
|
824
|
+
binary_data_callback_(socket, peer_id, payload);
|
|
825
|
+
}
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
case MessageDataType::STRING: {
|
|
830
|
+
LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
831
|
+
if (string_data_callback_) {
|
|
832
|
+
std::string string_data(payload.begin(), payload.end());
|
|
833
|
+
string_data_callback_(socket, peer_id, string_data);
|
|
834
|
+
}
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
case MessageDataType::JSON: {
|
|
839
|
+
LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
|
|
840
|
+
try {
|
|
841
|
+
nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
|
|
842
|
+
if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
|
|
843
|
+
handle_rats_message(socket, peer_id, json_msg);
|
|
844
|
+
} else if (json_data_callback_) {
|
|
845
|
+
json_data_callback_(socket, peer_id, json_msg);
|
|
846
|
+
}
|
|
847
|
+
} catch (const nlohmann::json::exception& e) {
|
|
848
|
+
LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id << ": " << e.what());
|
|
849
|
+
}
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
default:
|
|
854
|
+
LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
764
857
|
}
|
|
765
858
|
|
|
766
859
|
// Handshake protocol implementation
|
|
@@ -855,9 +948,8 @@ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
|
|
|
855
948
|
auto now = std::chrono::high_resolution_clock::now();
|
|
856
949
|
auto current_timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
|
857
950
|
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");
|
|
951
|
+
if (time_diff > TIMESTAMP_SKEW_TOLERANCE_MS) {
|
|
952
|
+
LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << TIMESTAMP_SKEW_TOLERANCE_MS << "ms; accepting to be tolerant of clock skew");
|
|
861
953
|
}
|
|
862
954
|
}
|
|
863
955
|
|
|
@@ -893,52 +985,32 @@ bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
|
|
|
893
985
|
}
|
|
894
986
|
}
|
|
895
987
|
|
|
896
|
-
|
|
897
|
-
bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our_peer_id) {
|
|
988
|
+
bool RatsClient::send_handshake_unlocked(RatsPeer& peer, const std::string& our_peer_id) {
|
|
898
989
|
std::string handshake_msg = create_handshake_message("handshake", our_peer_id);
|
|
899
|
-
LOG_CLIENT_DEBUG("Sending handshake to
|
|
990
|
+
LOG_CLIENT_DEBUG("Sending handshake to " << peer.peer_id << ": " << handshake_msg);
|
|
900
991
|
|
|
901
|
-
//
|
|
902
|
-
// (which would cause deadlock by trying to lock peers_mutex_ again).
|
|
903
|
-
// Handshakes are always unencrypted since they happen before noise handshake.
|
|
992
|
+
// Handshakes are always unencrypted – enqueue into the peer's send buffer
|
|
904
993
|
std::vector<uint8_t> binary_data(handshake_msg.begin(), handshake_msg.end());
|
|
905
994
|
std::vector<uint8_t> message_with_header = create_message_with_header(binary_data, MessageDataType::STRING);
|
|
906
995
|
|
|
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);
|
|
996
|
+
if (!enqueue_message_unlocked(peer, message_with_header)) {
|
|
997
|
+
LOG_CLIENT_ERROR("Failed to enqueue handshake for " << peer.peer_id);
|
|
914
998
|
return false;
|
|
915
999
|
}
|
|
916
1000
|
|
|
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
|
-
}
|
|
1001
|
+
peer.handshake_state = RatsPeer::HandshakeState::SENT;
|
|
1002
|
+
peer.handshake_start_time = std::chrono::steady_clock::now();
|
|
926
1003
|
|
|
927
1004
|
return true;
|
|
928
1005
|
}
|
|
929
1006
|
|
|
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) {
|
|
1007
|
+
bool RatsClient::handle_handshake_message(socket_t socket, const std::string& initial_peer_id, const std::vector<uint8_t>& data) {
|
|
936
1008
|
// Extract JSON payload from message header
|
|
937
1009
|
MessageHeader header;
|
|
938
1010
|
std::vector<uint8_t> payload;
|
|
939
1011
|
|
|
940
1012
|
if (!parse_message_with_header(data, header, payload)) {
|
|
941
|
-
LOG_CLIENT_ERROR("Failed to parse handshake message header from " <<
|
|
1013
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message header from " << initial_peer_id);
|
|
942
1014
|
return false;
|
|
943
1015
|
}
|
|
944
1016
|
|
|
@@ -951,12 +1023,12 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
951
1023
|
// Parse handshake message directly from payload (no string conversion)
|
|
952
1024
|
HandshakeMessage handshake_msg;
|
|
953
1025
|
if (!parse_handshake_message(payload, handshake_msg)) {
|
|
954
|
-
LOG_CLIENT_ERROR("Failed to parse handshake message from " <<
|
|
1026
|
+
LOG_CLIENT_ERROR("Failed to parse handshake message from " << initial_peer_id);
|
|
955
1027
|
return false;
|
|
956
1028
|
}
|
|
957
1029
|
|
|
958
1030
|
if (!validate_handshake_message(handshake_msg)) {
|
|
959
|
-
LOG_CLIENT_ERROR("Invalid handshake message from " <<
|
|
1031
|
+
LOG_CLIENT_ERROR("Invalid handshake message from " << initial_peer_id);
|
|
960
1032
|
return false;
|
|
961
1033
|
}
|
|
962
1034
|
|
|
@@ -965,19 +1037,13 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
965
1037
|
return false;
|
|
966
1038
|
}
|
|
967
1039
|
|
|
968
|
-
LOG_CLIENT_INFO("Received valid handshake from " <<
|
|
1040
|
+
LOG_CLIENT_INFO("Received valid handshake from " << initial_peer_id
|
|
969
1041
|
<< " (peer_id: " << handshake_msg.peer_id << ")");
|
|
970
1042
|
|
|
971
1043
|
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);
|
|
1044
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
979
1045
|
if (peer_it == peers_.end()) {
|
|
980
|
-
LOG_CLIENT_ERROR("Peer " <<
|
|
1046
|
+
LOG_CLIENT_ERROR("Peer " << initial_peer_id << " not found for socket " << socket);
|
|
981
1047
|
return false;
|
|
982
1048
|
}
|
|
983
1049
|
|
|
@@ -993,22 +1059,18 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
993
1059
|
|
|
994
1060
|
// Update peer mappings with new peer_id if it changed
|
|
995
1061
|
if (old_peer_id != handshake_msg.peer_id) {
|
|
996
|
-
//
|
|
997
|
-
RatsPeer
|
|
998
|
-
|
|
999
|
-
// Erase the old entry from the main peers map.
|
|
1062
|
+
// Move the peer (preserves io_ context with send/recv buffers)
|
|
1063
|
+
RatsPeer peer_moved = std::move(peer_it->second);
|
|
1000
1064
|
peers_.erase(peer_it);
|
|
1001
1065
|
|
|
1002
|
-
|
|
1003
|
-
peer_copy.peer_id = handshake_msg.peer_id;
|
|
1066
|
+
peer_moved.peer_id = handshake_msg.peer_id;
|
|
1004
1067
|
|
|
1005
|
-
//
|
|
1006
|
-
|
|
1007
|
-
socket_to_peer_id_[socket] =
|
|
1008
|
-
address_to_peer_id_[
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
peer_it = peers_.find(peer_copy.peer_id);
|
|
1068
|
+
// Use emplace to avoid extra copy/move
|
|
1069
|
+
auto [new_it, ok] = peers_.emplace(peer_moved.peer_id, std::move(peer_moved));
|
|
1070
|
+
socket_to_peer_id_[socket] = new_it->second.peer_id;
|
|
1071
|
+
address_to_peer_id_[new_it->second.normalized_address] = new_it->second.peer_id;
|
|
1072
|
+
|
|
1073
|
+
peer_it = new_it;
|
|
1012
1074
|
}
|
|
1013
1075
|
|
|
1014
1076
|
RatsPeer& peer = peer_it->second;
|
|
@@ -1045,14 +1107,15 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1045
1107
|
// Simplified handshake logic - just one message type
|
|
1046
1108
|
if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
|
|
1047
1109
|
// This is an incoming handshake - send our handshake back
|
|
1048
|
-
if (send_handshake_unlocked(
|
|
1110
|
+
if (send_handshake_unlocked(peer, get_our_peer_id())) {
|
|
1049
1111
|
// If encryption is enabled, we need to do Noise handshake first
|
|
1050
1112
|
// Set NOISE_PENDING to prevent other threads from sending messages
|
|
1051
1113
|
if (peer.encryption_enabled) {
|
|
1052
1114
|
peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
|
|
1053
|
-
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " <<
|
|
1115
|
+
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
|
|
1054
1116
|
} else {
|
|
1055
1117
|
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1118
|
+
validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
|
|
1056
1119
|
log_handshake_completion_unlocked(peer);
|
|
1057
1120
|
}
|
|
1058
1121
|
|
|
@@ -1062,7 +1125,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1062
1125
|
return true;
|
|
1063
1126
|
} else {
|
|
1064
1127
|
peer.handshake_state = RatsPeer::HandshakeState::FAILED;
|
|
1065
|
-
LOG_CLIENT_ERROR("Failed to send handshake response to " <<
|
|
1128
|
+
LOG_CLIENT_ERROR("Failed to send handshake response to " << initial_peer_id);
|
|
1066
1129
|
return false;
|
|
1067
1130
|
}
|
|
1068
1131
|
} else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
|
|
@@ -1071,9 +1134,10 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1071
1134
|
// Set NOISE_PENDING to prevent other threads from sending messages
|
|
1072
1135
|
if (peer.encryption_enabled) {
|
|
1073
1136
|
peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
|
|
1074
|
-
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " <<
|
|
1137
|
+
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
|
|
1075
1138
|
} else {
|
|
1076
1139
|
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1140
|
+
validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
|
|
1077
1141
|
log_handshake_completion_unlocked(peer);
|
|
1078
1142
|
}
|
|
1079
1143
|
|
|
@@ -1082,7 +1146,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1082
1146
|
|
|
1083
1147
|
return true;
|
|
1084
1148
|
} else {
|
|
1085
|
-
LOG_CLIENT_WARN("Received handshake from " <<
|
|
1149
|
+
LOG_CLIENT_WARN("Received handshake from " << initial_peer_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
|
|
1086
1150
|
return false;
|
|
1087
1151
|
}
|
|
1088
1152
|
}
|
|
@@ -1116,8 +1180,8 @@ void RatsClient::check_handshake_timeouts() {
|
|
|
1116
1180
|
socket_t socket = peer_it->second.socket;
|
|
1117
1181
|
LOG_CLIENT_INFO("Disconnecting peer " << peer_id << " due to handshake timeout");
|
|
1118
1182
|
|
|
1119
|
-
// Clean up peer data
|
|
1120
1183
|
remove_peer_by_id_unlocked(peer_id);
|
|
1184
|
+
poller_remove(socket);
|
|
1121
1185
|
close_socket(socket);
|
|
1122
1186
|
}
|
|
1123
1187
|
}
|
|
@@ -1154,70 +1218,108 @@ bool RatsClient::connect_to_peer(const std::string& host, int port) {
|
|
|
1154
1218
|
return false;
|
|
1155
1219
|
}
|
|
1156
1220
|
|
|
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
|
-
|
|
1221
|
+
// Create TCP connection with timeout (blocking connect is done on a managed thread
|
|
1222
|
+
// to avoid blocking the caller, then the connected socket is handed to the IO loop).
|
|
1223
|
+
add_managed_thread(std::thread([this, host, port, normalized_address]() {
|
|
1224
|
+
socket_t client_socket = create_tcp_client(host, port, TCP_CONNECT_TIMEOUT_MS);
|
|
1225
|
+
if (!is_valid_socket(client_socket)) {
|
|
1226
|
+
LOG_CLIENT_DEBUG("Failed to connect to " << host << ":" << port);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
LOG_CLIENT_INFO("TCP connected to " << host << ":" << port);
|
|
1231
|
+
|
|
1232
|
+
// Switch to non-blocking for the IO poller
|
|
1233
|
+
set_socket_nonblocking(client_socket);
|
|
1234
|
+
|
|
1235
|
+
std::string initial_peer_id = generate_temporary_peer_id(client_socket, normalized_address);
|
|
1236
|
+
|
|
1237
|
+
{
|
|
1238
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1239
|
+
|
|
1240
|
+
// Re-check peer limit and duplicate (could have changed while connecting)
|
|
1241
|
+
// NOTE: Use unlocked variant — peers_mutex_ is already held!
|
|
1242
|
+
if (get_peer_count_unlocked() >= max_peers_) {
|
|
1243
|
+
LOG_CLIENT_DEBUG("connect_to_peer: peer limit reached after TCP connect, aborting fd=" << client_socket);
|
|
1244
|
+
close_socket(client_socket);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (address_to_peer_id_.find(normalized_address) != address_to_peer_id_.end()) {
|
|
1248
|
+
LOG_CLIENT_DEBUG("connect_to_peer: duplicate address " << normalized_address << " after TCP connect, aborting fd=" << client_socket);
|
|
1249
|
+
close_socket(client_socket);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
RatsPeer new_peer(initial_peer_id, host, static_cast<uint16_t>(port),
|
|
1254
|
+
client_socket, normalized_address, true);
|
|
1255
|
+
new_peer.encryption_enabled = is_encryption_enabled();
|
|
1256
|
+
add_peer_unlocked(new_peer);
|
|
1257
|
+
|
|
1258
|
+
// Send initial handshake (enqueued into send buffer)
|
|
1259
|
+
auto peer_it = peers_.find(initial_peer_id);
|
|
1260
|
+
if (peer_it != peers_.end()) {
|
|
1261
|
+
if (!send_handshake_unlocked(peer_it->second, get_our_peer_id())) {
|
|
1262
|
+
LOG_CLIENT_ERROR("Failed to enqueue handshake for outgoing connection to " << host << ":" << port);
|
|
1263
|
+
remove_peer_by_id_unlocked(initial_peer_id);
|
|
1264
|
+
close_socket(client_socket);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Register with poller – PollIn for reads, PollOut to flush the queued handshake
|
|
1271
|
+
poller_add(client_socket, PollIn | PollOut);
|
|
1272
|
+
|
|
1273
|
+
LOG_CLIENT_INFO("Outgoing connection to " << host << ":" << port << " registered with IO poller");
|
|
1274
|
+
}), "connect-" + host + ":" + std::to_string(port));
|
|
1181
1275
|
|
|
1182
1276
|
return true;
|
|
1183
1277
|
}
|
|
1184
1278
|
|
|
1279
|
+
void RatsClient::mark_manual_disconnect(const std::string& peer_id) {
|
|
1280
|
+
std::lock_guard<std::mutex> lock(reconnect_mutex_);
|
|
1281
|
+
manual_disconnect_peers_.insert(peer_id);
|
|
1282
|
+
reconnect_queue_.erase(peer_id);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1185
1285
|
void RatsClient::disconnect_peer(socket_t socket) {
|
|
1186
|
-
// Mark as manually disconnected to prevent auto-reconnection
|
|
1187
1286
|
std::string peer_id = get_peer_id(socket);
|
|
1188
1287
|
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);
|
|
1288
|
+
mark_manual_disconnect(peer_id);
|
|
1193
1289
|
}
|
|
1194
|
-
|
|
1290
|
+
poller_remove(socket);
|
|
1195
1291
|
remove_peer(socket);
|
|
1196
1292
|
close_socket(socket);
|
|
1197
1293
|
}
|
|
1198
1294
|
|
|
1199
1295
|
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
|
-
|
|
1296
|
+
mark_manual_disconnect(peer_id);
|
|
1208
1297
|
socket_t socket = get_peer_socket_by_id(peer_id);
|
|
1209
1298
|
if (is_valid_socket(socket)) {
|
|
1299
|
+
poller_remove(socket);
|
|
1210
1300
|
remove_peer(socket);
|
|
1211
1301
|
close_socket(socket);
|
|
1212
1302
|
}
|
|
1213
1303
|
}
|
|
1214
1304
|
|
|
1215
|
-
//
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1305
|
+
// Peer lookup helpers (assumes peers_mutex_ is already locked)
|
|
1306
|
+
std::unordered_map<std::string, RatsPeer>::iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) {
|
|
1307
|
+
auto sock_it = socket_to_peer_id_.find(socket);
|
|
1308
|
+
if (sock_it != socket_to_peer_id_.end()) {
|
|
1309
|
+
return peers_.find(sock_it->second);
|
|
1310
|
+
}
|
|
1311
|
+
return peers_.end();
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
std::unordered_map<std::string, RatsPeer>::const_iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) const {
|
|
1315
|
+
auto sock_it = socket_to_peer_id_.find(socket);
|
|
1316
|
+
if (sock_it != socket_to_peer_id_.end()) {
|
|
1317
|
+
return peers_.find(sock_it->second);
|
|
1318
|
+
}
|
|
1319
|
+
return peers_.end();
|
|
1219
1320
|
}
|
|
1220
1321
|
|
|
1322
|
+
// Helper methods for peer management
|
|
1221
1323
|
void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
|
|
1222
1324
|
// Assumes peers_mutex_ is already locked
|
|
1223
1325
|
peers_[peer.peer_id] = peer;
|
|
@@ -1227,17 +1329,12 @@ void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
|
|
|
1227
1329
|
|
|
1228
1330
|
void RatsClient::remove_peer(socket_t socket) {
|
|
1229
1331
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1230
|
-
auto
|
|
1231
|
-
if (
|
|
1232
|
-
remove_peer_by_id_unlocked(
|
|
1332
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1333
|
+
if (peer_it != peers_.end()) {
|
|
1334
|
+
remove_peer_by_id_unlocked(peer_it->second.peer_id);
|
|
1233
1335
|
}
|
|
1234
1336
|
}
|
|
1235
1337
|
|
|
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
1338
|
void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
1242
1339
|
// Assumes peers_mutex_ is already locked
|
|
1243
1340
|
|
|
@@ -1246,6 +1343,11 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
|
1246
1343
|
|
|
1247
1344
|
auto it = peers_.find(peer_id_copy);
|
|
1248
1345
|
if (it != peers_.end()) {
|
|
1346
|
+
// Decrement validated peer count if this peer had completed handshake
|
|
1347
|
+
if (it->second.is_handshake_completed()) {
|
|
1348
|
+
validated_peer_count_.fetch_sub(1, std::memory_order_relaxed);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1249
1351
|
// Copy the values we need before erasing to avoid use-after-free
|
|
1250
1352
|
socket_t peer_socket = it->second.socket;
|
|
1251
1353
|
std::string peer_normalized_address = it->second.normalized_address;
|
|
@@ -1253,9 +1355,6 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
|
|
|
1253
1355
|
socket_to_peer_id_.erase(peer_socket);
|
|
1254
1356
|
address_to_peer_id_.erase(peer_normalized_address);
|
|
1255
1357
|
peers_.erase(it);
|
|
1256
|
-
|
|
1257
|
-
// Clean up socket-specific mutex
|
|
1258
|
-
cleanup_socket_send_mutex(peer_socket);
|
|
1259
1358
|
}
|
|
1260
1359
|
}
|
|
1261
1360
|
|
|
@@ -1267,9 +1366,8 @@ bool RatsClient::is_already_connected_to_address(const std::string& normalized_a
|
|
|
1267
1366
|
void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
1268
1367
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1269
1368
|
|
|
1270
|
-
|
|
1271
|
-
if (
|
|
1272
|
-
local_interface_addresses_.push_back(ip_address);
|
|
1369
|
+
auto [it, inserted] = local_interface_addresses_.insert(ip_address);
|
|
1370
|
+
if (inserted) {
|
|
1273
1371
|
LOG_CLIENT_INFO("Added " << ip_address << " to ignore list");
|
|
1274
1372
|
} else {
|
|
1275
1373
|
LOG_CLIENT_DEBUG("IP address " << ip_address << " already in ignore list");
|
|
@@ -1277,7 +1375,7 @@ void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
|
1277
1375
|
}
|
|
1278
1376
|
|
|
1279
1377
|
//common localhost addresses
|
|
1280
|
-
static constexpr std::array<std::string_view,
|
|
1378
|
+
static constexpr std::array<std::string_view,5> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::", "localhost"};
|
|
1281
1379
|
|
|
1282
1380
|
// Local interface address blocking methods
|
|
1283
1381
|
void RatsClient::initialize_local_addresses() {
|
|
@@ -1286,13 +1384,12 @@ void RatsClient::initialize_local_addresses() {
|
|
|
1286
1384
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1287
1385
|
|
|
1288
1386
|
// Get all local interface addresses using network_utils
|
|
1289
|
-
|
|
1387
|
+
auto addrs = network_utils::get_local_interface_addresses();
|
|
1388
|
+
local_interface_addresses_.insert(addrs.begin(), addrs.end());
|
|
1290
1389
|
|
|
1291
|
-
// Add common localhost addresses
|
|
1390
|
+
// Add common localhost addresses
|
|
1292
1391
|
for (const auto& addr : localhost_addrs) {
|
|
1293
|
-
|
|
1294
|
-
local_interface_addresses_.emplace_back(addr);
|
|
1295
|
-
}
|
|
1392
|
+
local_interface_addresses_.emplace(std::string(addr));
|
|
1296
1393
|
}
|
|
1297
1394
|
|
|
1298
1395
|
LOG_CLIENT_INFO("Found " << local_interface_addresses_.size() << " local addresses to block:");
|
|
@@ -1301,36 +1398,47 @@ void RatsClient::initialize_local_addresses() {
|
|
|
1301
1398
|
}
|
|
1302
1399
|
}
|
|
1303
1400
|
|
|
1304
|
-
|
|
1305
1401
|
bool RatsClient::is_blocked_address(const std::string& ip_address) const {
|
|
1306
1402
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1403
|
+
return local_interface_addresses_.count(ip_address) > 0;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
bool RatsClient::can_connect_to_peer(const std::string& ip, int port) const {
|
|
1407
|
+
if (should_ignore_peer(ip, port)) {
|
|
1408
|
+
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - blocked address");
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1307
1411
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1412
|
+
std::string normalized_address = normalize_peer_address(ip, port);
|
|
1413
|
+
if (is_already_connected_to_address(normalized_address)) {
|
|
1414
|
+
LOG_CLIENT_DEBUG("Already connected to " << normalized_address);
|
|
1415
|
+
return false;
|
|
1313
1416
|
}
|
|
1314
1417
|
|
|
1315
|
-
|
|
1418
|
+
if (is_peer_limit_reached()) {
|
|
1419
|
+
LOG_CLIENT_DEBUG("Peer limit reached, cannot connect to " << ip << ":" << port);
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
return true;
|
|
1316
1424
|
}
|
|
1317
1425
|
|
|
1318
1426
|
bool RatsClient::should_ignore_peer(const std::string& ip, int port) const {
|
|
1319
|
-
//
|
|
1320
|
-
|
|
1321
|
-
|
|
1427
|
+
// Check if this is a well-known localhost address
|
|
1428
|
+
bool is_localhost = std::find(localhost_addrs.begin(), localhost_addrs.end(), ip) != localhost_addrs.end();
|
|
1429
|
+
|
|
1430
|
+
if (is_localhost) {
|
|
1431
|
+
// Block self-connections (same port on localhost)
|
|
1432
|
+
if (port == listen_port_) {
|
|
1322
1433
|
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - localhost with same port");
|
|
1323
1434
|
return true;
|
|
1324
1435
|
}
|
|
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") {
|
|
1436
|
+
// Allow localhost on different ports (for testing)
|
|
1329
1437
|
LOG_CLIENT_DEBUG("Allowing localhost peer " << ip << ":" << port << " on different port");
|
|
1330
1438
|
return false;
|
|
1331
1439
|
}
|
|
1332
1440
|
|
|
1333
|
-
//
|
|
1441
|
+
// Block non-localhost local interface addresses
|
|
1334
1442
|
if (is_blocked_address(ip)) {
|
|
1335
1443
|
LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - matches local interface address");
|
|
1336
1444
|
return true;
|
|
@@ -1383,30 +1491,25 @@ bool RatsClient::parse_message_with_header(const std::vector<uint8_t>& message,
|
|
|
1383
1491
|
// Extract payload
|
|
1384
1492
|
payload.assign(message.begin() + MessageHeader::HEADER_SIZE, message.end());
|
|
1385
1493
|
|
|
1386
|
-
LOG_CLIENT_DEBUG("Parsed message header: type=" << static_cast<int>(header.type) << ", payload_size=" << payload.size());
|
|
1387
1494
|
return true;
|
|
1388
1495
|
}
|
|
1389
1496
|
|
|
1390
|
-
//
|
|
1391
|
-
//
|
|
1497
|
+
// Async send – enqueues header + (optionally encrypted) payload into the peer's
|
|
1498
|
+
// ChainedSendBuffer. Does NOT require peers_mutex_; caller passes cached peer data.
|
|
1499
|
+
// The shared_ptr keeps the cipher alive even if the peer is removed concurrently.
|
|
1392
1500
|
bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector<uint8_t>& data,
|
|
1393
1501
|
MessageDataType message_type,
|
|
1394
|
-
rats::NoiseCipherState
|
|
1502
|
+
std::shared_ptr<rats::NoiseCipherState> send_cipher,
|
|
1395
1503
|
const std::string& peer_id_for_logging) {
|
|
1396
1504
|
if (!running_.load()) {
|
|
1397
1505
|
return false;
|
|
1398
1506
|
}
|
|
1399
1507
|
|
|
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
1508
|
// Create message with specified header type
|
|
1406
1509
|
std::vector<uint8_t> message_with_header = create_message_with_header(data, message_type);
|
|
1407
1510
|
|
|
1408
1511
|
if (send_cipher) {
|
|
1409
|
-
// Encrypt the message before
|
|
1512
|
+
// Encrypt the message before enqueuing
|
|
1410
1513
|
std::vector<uint8_t> ciphertext(message_with_header.size() + rats::NOISE_TAG_SIZE);
|
|
1411
1514
|
size_t ct_len = send_cipher->encrypt_with_ad(
|
|
1412
1515
|
nullptr, 0,
|
|
@@ -1420,16 +1523,13 @@ bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector
|
|
|
1420
1523
|
}
|
|
1421
1524
|
|
|
1422
1525
|
ciphertext.resize(ct_len);
|
|
1423
|
-
LOG_CLIENT_DEBUG("
|
|
1526
|
+
LOG_CLIENT_DEBUG("Enqueuing encrypted message for " << peer_id_for_logging << " (" << ct_len << " bytes)");
|
|
1424
1527
|
|
|
1425
|
-
|
|
1426
|
-
int sent = send_tcp_message(socket, ciphertext);
|
|
1427
|
-
return sent > 0;
|
|
1528
|
+
return enqueue_message(socket, ciphertext);
|
|
1428
1529
|
}
|
|
1429
1530
|
|
|
1430
|
-
// Unencrypted path
|
|
1431
|
-
|
|
1432
|
-
return sent > 0;
|
|
1531
|
+
// Unencrypted path
|
|
1532
|
+
return enqueue_message(socket, message_with_header);
|
|
1433
1533
|
}
|
|
1434
1534
|
|
|
1435
1535
|
bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
@@ -1437,25 +1537,22 @@ bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>
|
|
|
1437
1537
|
return false;
|
|
1438
1538
|
}
|
|
1439
1539
|
|
|
1440
|
-
// Cache peer
|
|
1540
|
+
// Cache peer data under lock, then release lock before sending
|
|
1441
1541
|
std::string peer_id;
|
|
1442
|
-
rats::NoiseCipherState
|
|
1542
|
+
std::shared_ptr<rats::NoiseCipherState> send_cipher;
|
|
1443
1543
|
|
|
1444
1544
|
{
|
|
1445
1545
|
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
|
-
}
|
|
1546
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1547
|
+
if (peer_it != peers_.end()) {
|
|
1548
|
+
peer_id = peer_it->second.peer_id;
|
|
1549
|
+
if (peer_it->second.is_noise_encrypted()) {
|
|
1550
|
+
send_cipher = peer_it->second.send_cipher; // shared_ptr copy keeps cipher alive
|
|
1454
1551
|
}
|
|
1455
1552
|
}
|
|
1456
1553
|
}
|
|
1457
1554
|
|
|
1458
|
-
//
|
|
1555
|
+
// peers_mutex_ released -- safe to do potentially slow TCP send
|
|
1459
1556
|
return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
|
|
1460
1557
|
}
|
|
1461
1558
|
|
|
@@ -1465,43 +1562,50 @@ bool RatsClient::send_string_to_peer(socket_t socket, const std::string& data) {
|
|
|
1465
1562
|
return send_binary_to_peer(socket, binary_data, MessageDataType::STRING);
|
|
1466
1563
|
}
|
|
1467
1564
|
|
|
1565
|
+
std::vector<uint8_t> RatsClient::json_to_binary(const nlohmann::json& data) {
|
|
1566
|
+
std::string s = data.dump();
|
|
1567
|
+
return {s.begin(), s.end()};
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1468
1570
|
bool RatsClient::send_json_to_peer(socket_t socket, const nlohmann::json& data) {
|
|
1469
1571
|
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);
|
|
1572
|
+
return send_binary_to_peer(socket, json_to_binary(data), MessageDataType::JSON);
|
|
1474
1573
|
} catch (const nlohmann::json::exception& e) {
|
|
1475
1574
|
LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
|
|
1476
1575
|
return false;
|
|
1477
1576
|
}
|
|
1478
1577
|
}
|
|
1479
1578
|
|
|
1480
|
-
bool RatsClient::send_binary_to_peer_id(const std::string&
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1579
|
+
bool RatsClient::send_binary_to_peer_id(const std::string& peer_id, const std::vector<uint8_t>& data, MessageDataType message_type) {
|
|
1580
|
+
// Cache peer data under lock, then release before sending
|
|
1581
|
+
socket_t socket;
|
|
1582
|
+
std::shared_ptr<rats::NoiseCipherState> send_cipher;
|
|
1583
|
+
|
|
1584
|
+
{
|
|
1585
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1586
|
+
auto it = peers_.find(peer_id);
|
|
1587
|
+
if (it == peers_.end() || !it->second.is_handshake_completed()) {
|
|
1588
|
+
return false;
|
|
1589
|
+
}
|
|
1590
|
+
socket = it->second.socket;
|
|
1591
|
+
if (it->second.is_noise_encrypted()) {
|
|
1592
|
+
send_cipher = it->second.send_cipher; // shared_ptr copy keeps cipher alive
|
|
1593
|
+
}
|
|
1485
1594
|
}
|
|
1486
1595
|
|
|
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);
|
|
1596
|
+
// peers_mutex_ released -- safe to do potentially slow TCP send
|
|
1597
|
+
return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
|
|
1491
1598
|
}
|
|
1492
1599
|
|
|
1493
|
-
bool RatsClient::send_string_to_peer_id(const std::string&
|
|
1600
|
+
bool RatsClient::send_string_to_peer_id(const std::string& peer_id, const std::string& data) {
|
|
1494
1601
|
// Convert string to binary and use primary binary method with STRING type
|
|
1495
1602
|
std::vector<uint8_t> binary_data(data.begin(), data.end());
|
|
1496
|
-
return send_binary_to_peer_id(
|
|
1603
|
+
return send_binary_to_peer_id(peer_id, binary_data, MessageDataType::STRING);
|
|
1497
1604
|
}
|
|
1498
1605
|
|
|
1499
|
-
bool RatsClient::send_json_to_peer_id(const std::string&
|
|
1606
|
+
bool RatsClient::send_json_to_peer_id(const std::string& peer_id, const nlohmann::json& data) {
|
|
1500
1607
|
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);
|
|
1608
|
+
return send_binary_to_peer_id(peer_id, json_to_binary(data), MessageDataType::JSON);
|
|
1505
1609
|
} catch (const nlohmann::json::exception& e) {
|
|
1506
1610
|
LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
|
|
1507
1611
|
return false;
|
|
@@ -1510,10 +1614,7 @@ bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlo
|
|
|
1510
1614
|
|
|
1511
1615
|
int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
|
|
1512
1616
|
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);
|
|
1617
|
+
return broadcast_binary_to_peers(json_to_binary(data), MessageDataType::JSON);
|
|
1517
1618
|
} catch (const nlohmann::json::exception& e) {
|
|
1518
1619
|
LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
|
|
1519
1620
|
return 0;
|
|
@@ -1525,21 +1626,25 @@ int RatsClient::broadcast_binary_to_peers(const std::vector<uint8_t>& data, Mess
|
|
|
1525
1626
|
return 0;
|
|
1526
1627
|
}
|
|
1527
1628
|
|
|
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++;
|
|
1629
|
+
// Collect targets under lock, then enqueue outside
|
|
1630
|
+
std::vector<PeerSendTarget> targets;
|
|
1631
|
+
{
|
|
1632
|
+
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1633
|
+
targets.reserve(peers_.size());
|
|
1634
|
+
for (const auto& [id, peer] : peers_) {
|
|
1635
|
+
if (peer.is_handshake_completed()) {
|
|
1636
|
+
targets.push_back({peer.socket, peer.peer_id,
|
|
1637
|
+
peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
|
|
1539
1638
|
}
|
|
1540
1639
|
}
|
|
1541
1640
|
}
|
|
1542
1641
|
|
|
1642
|
+
int sent_count = 0;
|
|
1643
|
+
for (const auto& t : targets) {
|
|
1644
|
+
if (send_binary_to_peer_unlocked(t.socket, data, message_type, t.send_cipher, t.peer_id)) {
|
|
1645
|
+
sent_count++;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1543
1648
|
return sent_count;
|
|
1544
1649
|
}
|
|
1545
1650
|
|
|
@@ -1549,35 +1654,6 @@ int RatsClient::broadcast_string_to_peers(const std::string& data) {
|
|
|
1549
1654
|
return broadcast_binary_to_peers(binary_data, MessageDataType::STRING);
|
|
1550
1655
|
}
|
|
1551
1656
|
|
|
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
1657
|
// =========================================================================
|
|
1582
1658
|
// Peer Information and Management
|
|
1583
1659
|
// =========================================================================
|
|
@@ -1587,32 +1663,18 @@ std::string RatsClient::get_our_peer_id() const {
|
|
|
1587
1663
|
}
|
|
1588
1664
|
|
|
1589
1665
|
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;
|
|
1666
|
+
// Returns the cached validated peer count (O(1) instead of O(N) scan)
|
|
1667
|
+
return validated_peer_count_.load(std::memory_order_relaxed);
|
|
1598
1668
|
}
|
|
1599
1669
|
|
|
1600
1670
|
int RatsClient::get_peer_count() const {
|
|
1601
|
-
std::
|
|
1602
|
-
return get_peer_count_unlocked();
|
|
1671
|
+
return validated_peer_count_.load(std::memory_order_relaxed);
|
|
1603
1672
|
}
|
|
1604
1673
|
|
|
1605
1674
|
std::string RatsClient::get_peer_id(socket_t socket) const {
|
|
1606
|
-
// Atomic operation - lock once and return copy to avoid race condition
|
|
1607
1675
|
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 "";
|
|
1676
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1677
|
+
return (peer_it != peers_.end()) ? peer_it->second.peer_id : "";
|
|
1616
1678
|
}
|
|
1617
1679
|
|
|
1618
1680
|
socket_t RatsClient::get_peer_socket_by_id(const std::string& peer_id) const {
|
|
@@ -1650,7 +1712,6 @@ std::vector<RatsPeer> RatsClient::get_validated_peers() const {
|
|
|
1650
1712
|
return result;
|
|
1651
1713
|
}
|
|
1652
1714
|
|
|
1653
|
-
|
|
1654
1715
|
std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::string& exclude_peer_id) const {
|
|
1655
1716
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1656
1717
|
|
|
@@ -1681,23 +1742,24 @@ std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::str
|
|
|
1681
1742
|
return selected_peers;
|
|
1682
1743
|
}
|
|
1683
1744
|
|
|
1684
|
-
|
|
1745
|
+
std::optional<RatsPeer> RatsClient::get_peer_by_id(const std::string& peer_id) const {
|
|
1685
1746
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1686
1747
|
auto it = peers_.find(peer_id);
|
|
1687
|
-
|
|
1748
|
+
if (it != peers_.end()) {
|
|
1749
|
+
return it->second;
|
|
1750
|
+
}
|
|
1751
|
+
return std::nullopt;
|
|
1688
1752
|
}
|
|
1689
1753
|
|
|
1690
|
-
|
|
1754
|
+
std::optional<RatsPeer> RatsClient::get_peer_by_socket(socket_t socket) const {
|
|
1691
1755
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
1692
|
-
auto
|
|
1693
|
-
if (
|
|
1694
|
-
|
|
1695
|
-
return (peer_it != peers_.end()) ? &peer_it->second : nullptr;
|
|
1756
|
+
auto peer_it = find_peer_by_socket_unlocked(socket);
|
|
1757
|
+
if (peer_it != peers_.end()) {
|
|
1758
|
+
return peer_it->second;
|
|
1696
1759
|
}
|
|
1697
|
-
return
|
|
1760
|
+
return std::nullopt;
|
|
1698
1761
|
}
|
|
1699
1762
|
|
|
1700
|
-
|
|
1701
1763
|
// Peer limit management methods
|
|
1702
1764
|
int RatsClient::get_max_peers() const {
|
|
1703
1765
|
return max_peers_;
|
|
@@ -1710,15 +1772,10 @@ void RatsClient::set_max_peers(int max_peers) {
|
|
|
1710
1772
|
|
|
1711
1773
|
bool RatsClient::is_peer_limit_reached() const {
|
|
1712
1774
|
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;
|
|
1775
|
+
return get_peer_count_unlocked() >= max_peers_;
|
|
1719
1776
|
}
|
|
1720
1777
|
|
|
1721
|
-
std::string RatsClient::
|
|
1778
|
+
std::string RatsClient::generate_temporary_peer_id(socket_t socket, const std::string& connection_info) {
|
|
1722
1779
|
// Generate unique hash ID using timestamp, socket, connection info, and random component
|
|
1723
1780
|
auto now = std::chrono::high_resolution_clock::now();
|
|
1724
1781
|
auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
|
|
@@ -1769,7 +1826,7 @@ std::string RatsClient::normalize_peer_address(const std::string& ip, int port)
|
|
|
1769
1826
|
|
|
1770
1827
|
// =========================================================================
|
|
1771
1828
|
// Callback Registration
|
|
1772
|
-
//
|
|
1829
|
+
// =========================================================================
|
|
1773
1830
|
|
|
1774
1831
|
void RatsClient::set_connection_callback(ConnectionCallback callback) {
|
|
1775
1832
|
connection_callback_ = callback;
|
|
@@ -1791,325 +1848,6 @@ void RatsClient::set_disconnect_callback(DisconnectCallback callback) {
|
|
|
1791
1848
|
disconnect_callback_ = callback;
|
|
1792
1849
|
}
|
|
1793
1850
|
|
|
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
1851
|
// =========================================================================
|
|
2114
1852
|
// Protocol Configuration
|
|
2115
1853
|
// =========================================================================
|
|
@@ -2140,11 +1878,10 @@ std::string RatsClient::get_protocol_version() const {
|
|
|
2140
1878
|
// Message Exchange API
|
|
2141
1879
|
// =========================================================================
|
|
2142
1880
|
|
|
2143
|
-
|
|
2144
1881
|
void RatsClient::on(const std::string& message_type, MessageCallback callback) {
|
|
2145
1882
|
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2146
1883
|
message_handlers_[message_type].emplace_back(callback, false); // false = not once
|
|
2147
|
-
|
|
1884
|
+
LOG_CLIENT_DEBUG("Registered handler for message type: " << message_type);
|
|
2148
1885
|
}
|
|
2149
1886
|
|
|
2150
1887
|
void RatsClient::once(const std::string& message_type, MessageCallback callback) {
|
|
@@ -2172,15 +1909,15 @@ void RatsClient::send(const std::string& message_type, const nlohmann::json& dat
|
|
|
2172
1909
|
return;
|
|
2173
1910
|
}
|
|
2174
1911
|
|
|
2175
|
-
|
|
1912
|
+
LOG_CLIENT_DEBUG("Sending broadcast message type '" << message_type << "'");
|
|
2176
1913
|
|
|
2177
1914
|
// Create rats message
|
|
2178
1915
|
nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
|
|
2179
1916
|
|
|
2180
1917
|
// Broadcast to all validated peers
|
|
2181
|
-
int sent_count =
|
|
1918
|
+
int sent_count = broadcast_rats_message(message);
|
|
2182
1919
|
|
|
2183
|
-
|
|
1920
|
+
LOG_CLIENT_DEBUG("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
|
|
2184
1921
|
|
|
2185
1922
|
if (callback) {
|
|
2186
1923
|
if (sent_count > 0) {
|
|
@@ -2201,7 +1938,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2201
1938
|
return;
|
|
2202
1939
|
}
|
|
2203
1940
|
|
|
2204
|
-
|
|
1941
|
+
LOG_CLIENT_DEBUG("Sending targeted message type '" << message_type << "' to peer " << peer_id);
|
|
2205
1942
|
|
|
2206
1943
|
// Create rats message
|
|
2207
1944
|
nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
|
|
@@ -2241,7 +1978,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2241
1978
|
|
|
2242
1979
|
bool success = send_json_to_peer(target_socket, message);
|
|
2243
1980
|
|
|
2244
|
-
|
|
1981
|
+
LOG_CLIENT_DEBUG("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
|
|
2245
1982
|
|
|
2246
1983
|
if (callback) {
|
|
2247
1984
|
if (success) {
|
|
@@ -2255,54 +1992,41 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
|
|
|
2255
1992
|
// Message exchange system helpers
|
|
2256
1993
|
void RatsClient::call_message_handlers(const std::string& message_type, const std::string& peer_id, const nlohmann::json& data) {
|
|
2257
1994
|
std::vector<MessageHandler> handlers_to_call;
|
|
2258
|
-
std::vector<MessageHandler> remaining_handlers;
|
|
2259
1995
|
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
// Get handlers to call and identify once handlers
|
|
1996
|
+
// Get handlers to call and remove once handlers atomically
|
|
2263
1997
|
{
|
|
2264
1998
|
std::lock_guard<std::mutex> lock(message_handlers_mutex_);
|
|
2265
1999
|
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 << "'");
|
|
2000
|
+
if (it == message_handlers_.end()) {
|
|
2001
|
+
LOG_CLIENT_DEBUG("No handlers registered for message type '" << message_type << "'");
|
|
2002
|
+
return;
|
|
2280
2003
|
}
|
|
2004
|
+
|
|
2005
|
+
handlers_to_call = it->second;
|
|
2006
|
+
|
|
2007
|
+
// Remove once handlers using erase-remove idiom
|
|
2008
|
+
it->second.erase(
|
|
2009
|
+
std::remove_if(it->second.begin(), it->second.end(),
|
|
2010
|
+
[](const MessageHandler& h) { return h.is_once; }),
|
|
2011
|
+
it->second.end());
|
|
2281
2012
|
}
|
|
2282
2013
|
|
|
2283
|
-
|
|
2014
|
+
LOG_CLIENT_DEBUG("Calling " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
|
|
2284
2015
|
|
|
2285
2016
|
// Call handlers outside of mutex to avoid deadlock
|
|
2286
2017
|
for (const auto& handler : handlers_to_call) {
|
|
2287
2018
|
try {
|
|
2288
|
-
LOG_CLIENT_INFO("Calling handler for message type '" << message_type << "'");
|
|
2289
2019
|
handler.callback(peer_id, data);
|
|
2290
|
-
LOG_CLIENT_INFO("Handler for message type '" << message_type << "' completed successfully");
|
|
2291
2020
|
} catch (const std::exception& e) {
|
|
2292
2021
|
LOG_CLIENT_ERROR("Exception in message handler for type '" << message_type << "': " << e.what());
|
|
2293
2022
|
} catch (...) {
|
|
2294
2023
|
LOG_CLIENT_ERROR("Unknown exception in message handler for type '" << message_type << "'");
|
|
2295
2024
|
}
|
|
2296
2025
|
}
|
|
2297
|
-
|
|
2298
|
-
if (!handlers_to_call.empty()) {
|
|
2299
|
-
LOG_CLIENT_INFO("Called " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
|
|
2300
|
-
}
|
|
2301
2026
|
}
|
|
2302
2027
|
|
|
2303
|
-
|
|
2304
2028
|
// =========================================================================
|
|
2305
|
-
// Rats
|
|
2029
|
+
// Rats Protocol Message Handling
|
|
2306
2030
|
// =========================================================================
|
|
2307
2031
|
|
|
2308
2032
|
nlohmann::json RatsClient::create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id) {
|
|
@@ -2317,26 +2041,26 @@ nlohmann::json RatsClient::create_rats_message(const std::string& type, const nl
|
|
|
2317
2041
|
return message;
|
|
2318
2042
|
}
|
|
2319
2043
|
|
|
2320
|
-
void RatsClient::handle_rats_message(socket_t socket, const std::string&
|
|
2044
|
+
void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_id, const nlohmann::json& message) {
|
|
2321
2045
|
try {
|
|
2322
2046
|
std::string message_type = message.value("type", "");
|
|
2323
2047
|
nlohmann::json payload = message.value("payload", nlohmann::json::object());
|
|
2324
2048
|
std::string sender_peer_id = message.value("sender_peer_id", "");
|
|
2325
2049
|
|
|
2326
|
-
LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " <<
|
|
2050
|
+
LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_id);
|
|
2327
2051
|
|
|
2328
2052
|
// Call registered message handlers for all message types (including custom ones)
|
|
2329
|
-
call_message_handlers(message_type, sender_peer_id.empty() ?
|
|
2053
|
+
call_message_handlers(message_type, sender_peer_id.empty() ? peer_id : sender_peer_id, payload);
|
|
2330
2054
|
|
|
2331
2055
|
// Handle built-in message types for internal functionality
|
|
2332
2056
|
if (message_type == "peer") {
|
|
2333
|
-
handle_peer_exchange_message(socket,
|
|
2057
|
+
handle_peer_exchange_message(socket, peer_id, payload);
|
|
2334
2058
|
}
|
|
2335
2059
|
else if (message_type == "peers_request") {
|
|
2336
|
-
handle_peers_request_message(socket,
|
|
2060
|
+
handle_peers_request_message(socket, peer_id, payload);
|
|
2337
2061
|
}
|
|
2338
2062
|
else if (message_type == "peers_response") {
|
|
2339
|
-
handle_peers_response_message(socket,
|
|
2063
|
+
handle_peers_response_message(socket, peer_id, payload);
|
|
2340
2064
|
}
|
|
2341
2065
|
// Custom message types are now handled by registered handlers above
|
|
2342
2066
|
// No need for else clause - all message types are valid if they have registered handlers
|
|
@@ -2346,54 +2070,39 @@ void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_ha
|
|
|
2346
2070
|
}
|
|
2347
2071
|
}
|
|
2348
2072
|
|
|
2349
|
-
void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string&
|
|
2073
|
+
void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
|
|
2350
2074
|
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 << ")");
|
|
2075
|
+
std::string exchanged_ip = payload.value("ip", "");
|
|
2076
|
+
int exchanged_port = payload.value("port", 0);
|
|
2077
|
+
std::string exchanged_peer_id = payload.value("peer_id", "");
|
|
2361
2078
|
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
LOG_CLIENT_DEBUG("Ignoring exchanged peer " << peer_ip << ":" << peer_port << " - local interface address");
|
|
2079
|
+
if (exchanged_ip.empty() || exchanged_port <= 0 || exchanged_peer_id.empty()) {
|
|
2080
|
+
LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_id);
|
|
2365
2081
|
return;
|
|
2366
2082
|
}
|
|
2367
2083
|
|
|
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
|
-
}
|
|
2084
|
+
LOG_CLIENT_INFO("Received peer exchange: " << exchanged_ip << ":" << exchanged_port << " (peer_id: " << exchanged_peer_id << ")");
|
|
2374
2085
|
|
|
2375
|
-
|
|
2376
|
-
if (is_peer_limit_reached()) {
|
|
2377
|
-
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to exchanged peer " << peer_ip << ":" << peer_port);
|
|
2086
|
+
if (!can_connect_to_peer(exchanged_ip, exchanged_port)) {
|
|
2378
2087
|
return;
|
|
2379
2088
|
}
|
|
2380
2089
|
|
|
2381
2090
|
// 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: " <<
|
|
2091
|
+
add_managed_thread(std::thread([this, exchanged_ip, exchanged_port, exchanged_peer_id]() {
|
|
2092
|
+
if (connect_to_peer(exchanged_ip, exchanged_port)) {
|
|
2093
|
+
LOG_CLIENT_INFO("Successfully connected to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
|
|
2385
2094
|
} else {
|
|
2386
|
-
LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " <<
|
|
2095
|
+
LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
|
|
2387
2096
|
}
|
|
2388
|
-
}), "peer-exchange-connect-" +
|
|
2097
|
+
}), "peer-exchange-connect-" + exchanged_peer_id.substr(0, 8));
|
|
2389
2098
|
|
|
2390
2099
|
} catch (const nlohmann::json::exception& e) {
|
|
2391
2100
|
LOG_CLIENT_ERROR("Failed to handle peer exchange message: " << e.what());
|
|
2392
2101
|
}
|
|
2393
2102
|
}
|
|
2394
2103
|
|
|
2395
|
-
// General broadcasting
|
|
2396
|
-
int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id) {
|
|
2104
|
+
// General broadcasting function
|
|
2105
|
+
int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id, bool validated_only) {
|
|
2397
2106
|
// Serialize JSON once before iterating
|
|
2398
2107
|
std::string json_string;
|
|
2399
2108
|
try {
|
|
@@ -2404,53 +2113,27 @@ int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std:
|
|
|
2404
2113
|
}
|
|
2405
2114
|
std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
|
|
2406
2115
|
|
|
2407
|
-
|
|
2116
|
+
// Collect targets under lock, then enqueue outside
|
|
2117
|
+
std::vector<PeerSendTarget> targets;
|
|
2408
2118
|
{
|
|
2409
2119
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
// Don't send to excluded peer
|
|
2120
|
+
targets.reserve(peers_.size());
|
|
2121
|
+
for (const auto& [id, peer] : peers_) {
|
|
2413
2122
|
if (!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) {
|
|
2414
2123
|
continue;
|
|
2415
2124
|
}
|
|
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++;
|
|
2125
|
+
if (validated_only && !peer.is_handshake_completed()) {
|
|
2126
|
+
continue;
|
|
2421
2127
|
}
|
|
2128
|
+
targets.push_back({peer.socket, peer.peer_id,
|
|
2129
|
+
peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
|
|
2422
2130
|
}
|
|
2423
2131
|
}
|
|
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
2132
|
|
|
2438
2133
|
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
|
-
}
|
|
2134
|
+
for (const auto& t : targets) {
|
|
2135
|
+
if (send_binary_to_peer_unlocked(t.socket, binary_data, MessageDataType::JSON, t.send_cipher, t.peer_id)) {
|
|
2136
|
+
sent_count++;
|
|
2454
2137
|
}
|
|
2455
2138
|
}
|
|
2456
2139
|
return sent_count;
|
|
@@ -2479,7 +2162,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
|
|
|
2479
2162
|
nlohmann::json message = create_peer_exchange_message(new_peer);
|
|
2480
2163
|
|
|
2481
2164
|
// Broadcast to all validated peers except the new peer
|
|
2482
|
-
int sent_count =
|
|
2165
|
+
int sent_count = broadcast_rats_message(message, new_peer.peer_id);
|
|
2483
2166
|
|
|
2484
2167
|
LOG_CLIENT_INFO("Broadcasted peer exchange message for " << new_peer.ip << ":" << new_peer.port
|
|
2485
2168
|
<< " to " << sent_count << " peers");
|
|
@@ -2488,7 +2171,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
|
|
|
2488
2171
|
// Peers request/response system implementation
|
|
2489
2172
|
nlohmann::json RatsClient::create_peers_request_message(const std::string& sender_peer_id) {
|
|
2490
2173
|
nlohmann::json payload;
|
|
2491
|
-
payload["max_peers"] =
|
|
2174
|
+
payload["max_peers"] = MAX_PEERS_REQUEST_COUNT;
|
|
2492
2175
|
payload["requester_info"] = {
|
|
2493
2176
|
{"listen_port", listen_port_},
|
|
2494
2177
|
{"peer_count", get_peer_count()}
|
|
@@ -2502,39 +2185,38 @@ nlohmann::json RatsClient::create_peers_response_message(const std::vector<RatsP
|
|
|
2502
2185
|
nlohmann::json peers_array = nlohmann::json::array();
|
|
2503
2186
|
|
|
2504
2187
|
for (const auto& peer : peers) {
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2188
|
+
peers_array.push_back({
|
|
2189
|
+
{"ip", peer.ip},
|
|
2190
|
+
{"port", peer.port},
|
|
2191
|
+
{"peer_id", peer.peer_id},
|
|
2192
|
+
{"connection_type", peer.is_outgoing ? "outgoing" : "incoming"}
|
|
2193
|
+
});
|
|
2511
2194
|
}
|
|
2512
2195
|
|
|
2513
|
-
payload["peers"] = peers_array;
|
|
2196
|
+
payload["peers"] = std::move(peers_array);
|
|
2514
2197
|
payload["total_peers"] = get_peer_count();
|
|
2515
2198
|
|
|
2516
2199
|
return create_rats_message("peers_response", payload, sender_peer_id);
|
|
2517
2200
|
}
|
|
2518
2201
|
|
|
2519
|
-
|
|
2520
|
-
void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
|
|
2202
|
+
void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
|
|
2521
2203
|
try {
|
|
2522
|
-
int max_peers = payload.value("max_peers",
|
|
2204
|
+
int max_peers = payload.value("max_peers", MAX_PEERS_REQUEST_COUNT);
|
|
2523
2205
|
|
|
2524
|
-
LOG_CLIENT_INFO("Received peers request from " <<
|
|
2206
|
+
LOG_CLIENT_INFO("Received peers request from " << peer_id << " for up to " << max_peers << " peers");
|
|
2525
2207
|
|
|
2526
2208
|
// Get random peers excluding the requester
|
|
2527
|
-
std::vector<RatsPeer> random_peers = get_random_peers(max_peers,
|
|
2209
|
+
std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_id);
|
|
2528
2210
|
|
|
2529
|
-
LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " <<
|
|
2211
|
+
LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_id);
|
|
2530
2212
|
|
|
2531
2213
|
// Create and send peers response
|
|
2532
|
-
nlohmann::json response_message = create_peers_response_message(random_peers,
|
|
2214
|
+
nlohmann::json response_message = create_peers_response_message(random_peers, peer_id);
|
|
2533
2215
|
|
|
2534
2216
|
if (!send_json_to_peer(socket, response_message)) {
|
|
2535
|
-
LOG_CLIENT_ERROR("Failed to send peers response to " <<
|
|
2217
|
+
LOG_CLIENT_ERROR("Failed to send peers response to " << peer_id);
|
|
2536
2218
|
} else {
|
|
2537
|
-
LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " <<
|
|
2219
|
+
LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_id);
|
|
2538
2220
|
}
|
|
2539
2221
|
|
|
2540
2222
|
} catch (const nlohmann::json::exception& e) {
|
|
@@ -2542,55 +2224,39 @@ void RatsClient::handle_peers_request_message(socket_t socket, const std::string
|
|
|
2542
2224
|
}
|
|
2543
2225
|
}
|
|
2544
2226
|
|
|
2545
|
-
void RatsClient::handle_peers_response_message(socket_t socket, const std::string&
|
|
2227
|
+
void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
|
|
2546
2228
|
try {
|
|
2547
2229
|
nlohmann::json peers_array = payload.value("peers", nlohmann::json::array());
|
|
2548
2230
|
int total_peers = payload.value("total_peers", 0);
|
|
2549
2231
|
|
|
2550
|
-
LOG_CLIENT_INFO("Received peers response from " <<
|
|
2232
|
+
LOG_CLIENT_INFO("Received peers response from " << peer_id << " with " << peers_array.size()
|
|
2551
2233
|
<< " peers (total: " << total_peers << ")");
|
|
2552
2234
|
|
|
2553
2235
|
// Process each peer in the response
|
|
2554
2236
|
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
|
-
}
|
|
2237
|
+
std::string resp_ip = peer_info.value("ip", "");
|
|
2238
|
+
int resp_port = peer_info.value("port", 0);
|
|
2239
|
+
std::string resp_peer_id = peer_info.value("peer_id", "");
|
|
2563
2240
|
|
|
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");
|
|
2241
|
+
if (resp_ip.empty() || resp_port <= 0 || resp_peer_id.empty()) {
|
|
2242
|
+
LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_id);
|
|
2569
2243
|
continue;
|
|
2570
2244
|
}
|
|
2571
2245
|
|
|
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
|
-
}
|
|
2246
|
+
LOG_CLIENT_DEBUG("Processing peer from response: " << resp_ip << ":" << resp_port << " (peer_id: " << resp_peer_id << ")");
|
|
2578
2247
|
|
|
2579
|
-
|
|
2580
|
-
if (is_peer_limit_reached()) {
|
|
2581
|
-
LOG_CLIENT_DEBUG("Peer limit reached, not connecting to peer from response " << peer_ip << ":" << peer_port);
|
|
2248
|
+
if (!can_connect_to_peer(resp_ip, resp_port)) {
|
|
2582
2249
|
continue;
|
|
2583
2250
|
}
|
|
2584
2251
|
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
LOG_CLIENT_INFO("Successfully connected to peer from response: " << peer_ip << ":" << peer_port);
|
|
2252
|
+
LOG_CLIENT_DEBUG("Attempting to connect to peer from response: " << resp_ip << ":" << resp_port);
|
|
2253
|
+
add_managed_thread(std::thread([this, resp_ip, resp_port, resp_peer_id]() {
|
|
2254
|
+
if (connect_to_peer(resp_ip, resp_port)) {
|
|
2255
|
+
LOG_CLIENT_INFO("Successfully connected to peer from response: " << resp_ip << ":" << resp_port);
|
|
2590
2256
|
} else {
|
|
2591
|
-
LOG_CLIENT_DEBUG("Failed to connect to peer from response: " <<
|
|
2257
|
+
LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << resp_ip << ":" << resp_port);
|
|
2592
2258
|
}
|
|
2593
|
-
}), "peer-response-connect-" +
|
|
2259
|
+
}), "peer-response-connect-" + resp_peer_id.substr(0, 8));
|
|
2594
2260
|
}
|
|
2595
2261
|
|
|
2596
2262
|
} catch (const nlohmann::json::exception& e) {
|
|
@@ -2609,49 +2275,7 @@ void RatsClient::send_peers_request(socket_t socket, const std::string& our_peer
|
|
|
2609
2275
|
}
|
|
2610
2276
|
|
|
2611
2277
|
// =========================================================================
|
|
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
|
|
2278
|
+
// Helper Functions
|
|
2655
2279
|
// =========================================================================
|
|
2656
2280
|
|
|
2657
2281
|
std::unique_ptr<RatsClient> create_rats_client(int listen_port) {
|
|
@@ -2721,52 +2345,4 @@ bool RatsClient::parse_address_string(const std::string& address_str, std::strin
|
|
|
2721
2345
|
return !out_ip.empty() && out_port > 0 && out_port <= 65535;
|
|
2722
2346
|
}
|
|
2723
2347
|
|
|
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
2348
|
} // namespace librats
|