librats 0.7.0 → 0.7.2
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/lib/index.d.ts +15 -0
- package/native-src/CMakeLists.txt +5 -0
- package/native-src/src/bt_client.cpp +18 -5
- package/native-src/src/bt_client.h +3 -1
- package/native-src/src/bt_network.cpp +1 -1
- package/native-src/src/bt_peer_connection.cpp +11 -0
- package/native-src/src/bt_peer_connection.h +6 -0
- package/native-src/src/bt_torrent.cpp +17 -6
- package/native-src/src/bt_torrent.h +1 -0
- package/native-src/src/bt_types.h +287 -20
- package/native-src/src/dht.cpp +26 -8
- package/native-src/src/dht.h +1 -1
- package/native-src/src/ice.cpp +1 -1
- package/native-src/src/krpc.cpp +8 -1
- package/native-src/src/krpc.h +3 -2
- package/native-src/src/librats.cpp +162 -74
- package/native-src/src/librats.h +43 -6
- package/native-src/src/librats_bittorrent.cpp +3 -2
- package/native-src/src/librats_c.cpp +20 -0
- package/native-src/src/librats_c.h +4 -0
- package/native-src/src/librats_encryption.cpp +5 -0
- package/native-src/src/librats_logging.cpp +21 -0
- package/native-src/src/logger.h +64 -12
- package/native-src/src/network_utils.cpp +175 -373
- package/native-src/src/network_utils.h +2 -113
- package/native-src/src/socket.cpp +485 -883
- package/native-src/src/socket.h +36 -130
- package/native-src/src/stun.cpp +3 -5
- package/native-src/src/tracker.cpp +3 -2
- package/native-src/src/turn.cpp +4 -6
- package/package.json +1 -1
- package/src/librats_node.cpp +23 -0
package/native-src/src/dht.cpp
CHANGED
|
@@ -62,8 +62,19 @@ bool DhtClient::start() {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
socket_ = create_udp_socket(port_, bind_address_);
|
|
65
|
+
// Fallback to free port
|
|
66
|
+
if (!is_valid_socket(socket_) && port_ > 0) {
|
|
67
|
+
// Requested port is not available, try ephemeral port as fallback
|
|
68
|
+
int original_port = port_;
|
|
69
|
+
LOG_DHT_WARN("UDP port " << original_port << " is not available, falling back to ephemeral port");
|
|
70
|
+
socket_ = create_udp_socket(0, bind_address_);
|
|
71
|
+
if (is_valid_socket(socket_)) {
|
|
72
|
+
port_ = get_bound_port(socket_);
|
|
73
|
+
LOG_DHT_INFO("Fell back from port " << original_port << " to ephemeral port " << port_);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
65
76
|
if (!is_valid_socket(socket_)) {
|
|
66
|
-
LOG_DHT_ERROR("Failed to create dual-stack UDP socket");
|
|
77
|
+
LOG_DHT_ERROR("Failed to create dual-stack UDP socket on port " << port_);
|
|
67
78
|
return false;
|
|
68
79
|
}
|
|
69
80
|
|
|
@@ -380,7 +391,7 @@ void DhtClient::network_loop() {
|
|
|
380
391
|
|
|
381
392
|
while (running_) {
|
|
382
393
|
Peer sender;
|
|
383
|
-
auto data = receive_udp_data(socket_, 1500, sender); // MTU size
|
|
394
|
+
auto data = receive_udp_data(socket_, 1500, sender, -1); // MTU size, blocking
|
|
384
395
|
|
|
385
396
|
if (!data.empty()) {
|
|
386
397
|
LOG_DHT_DEBUG("Received " << data.size() << " bytes from " << sender.ip << ":" << sender.port);
|
|
@@ -937,7 +948,8 @@ void DhtClient::handle_krpc_announce_peer(const KrpcMessage& message, const Peer
|
|
|
937
948
|
}
|
|
938
949
|
|
|
939
950
|
// Determine the actual port (BEP 5: implied_port support)
|
|
940
|
-
|
|
951
|
+
// If implied_port is set, use the UDP source port instead of the explicit port field
|
|
952
|
+
uint16_t peer_port = message.implied_port ? sender.port : message.port;
|
|
941
953
|
|
|
942
954
|
// Store the peer announcement
|
|
943
955
|
Peer announcing_peer(sender.ip, peer_port);
|
|
@@ -1072,7 +1084,7 @@ bool DhtClient::send_krpc_message(const KrpcMessage& message, const Peer& peer)
|
|
|
1072
1084
|
}
|
|
1073
1085
|
|
|
1074
1086
|
LOG_DHT_DEBUG("Sending KRPC message (" << data.size() << " bytes) to " << peer.ip << ":" << peer.port);
|
|
1075
|
-
int result = send_udp_data(socket_, data, peer);
|
|
1087
|
+
int result = send_udp_data(socket_, data, peer.ip, peer.port);
|
|
1076
1088
|
|
|
1077
1089
|
if (result > 0) {
|
|
1078
1090
|
LOG_DHT_DEBUG("Successfully sent KRPC message to " << peer.ip << ":" << peer.port);
|
|
@@ -1101,9 +1113,9 @@ void DhtClient::send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash)
|
|
|
1101
1113
|
send_krpc_message(message, peer);
|
|
1102
1114
|
}
|
|
1103
1115
|
|
|
1104
|
-
void DhtClient::send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token) {
|
|
1116
|
+
void DhtClient::send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port) {
|
|
1105
1117
|
std::string transaction_id = KrpcProtocol::generate_transaction_id();
|
|
1106
|
-
auto message = KrpcProtocol::create_announce_peer_query(transaction_id, node_id_, info_hash, port, token);
|
|
1118
|
+
auto message = KrpcProtocol::create_announce_peer_query(transaction_id, node_id_, info_hash, port, token, implied_port);
|
|
1107
1119
|
send_krpc_message(message, peer);
|
|
1108
1120
|
}
|
|
1109
1121
|
|
|
@@ -2138,13 +2150,19 @@ void DhtClient::send_announce_to_closest_nodes(PendingSearch& search) {
|
|
|
2138
2150
|
|
|
2139
2151
|
LOG_DHT_INFO("Announcing to " << announce_targets.size() << " closest nodes with tokens");
|
|
2140
2152
|
|
|
2153
|
+
// BEP 5: Use implied_port when the announce port is the same as the DHT port.
|
|
2154
|
+
// This tells the receiving node to use the UDP source port from the packet,
|
|
2155
|
+
// which is more accurate when behind NAT.
|
|
2156
|
+
bool use_implied_port = (search.announce_port == port_);
|
|
2157
|
+
|
|
2141
2158
|
// Send announce_peer to each target
|
|
2142
2159
|
for (const auto& [node, token] : announce_targets) {
|
|
2143
2160
|
LOG_DHT_DEBUG("Sending announce_peer to node " << node_id_to_hex(node.id)
|
|
2144
2161
|
<< " at " << node.peer.ip << ":" << node.peer.port
|
|
2145
|
-
<< " with token (distance: " << get_bucket_index(node.id) << ")"
|
|
2162
|
+
<< " with token (distance: " << get_bucket_index(node.id) << ")"
|
|
2163
|
+
<< (use_implied_port ? " [implied_port]" : ""));
|
|
2146
2164
|
|
|
2147
|
-
send_krpc_announce_peer(node.peer, search.info_hash, search.announce_port, token);
|
|
2165
|
+
send_krpc_announce_peer(node.peer, search.info_hash, search.announce_port, token, use_implied_port);
|
|
2148
2166
|
}
|
|
2149
2167
|
|
|
2150
2168
|
LOG_DHT_INFO("Announce completed: sent announce_peer to " << announce_targets.size()
|
package/native-src/src/dht.h
CHANGED
|
@@ -541,7 +541,7 @@ private:
|
|
|
541
541
|
void send_krpc_ping(const Peer& peer);
|
|
542
542
|
void send_krpc_find_node(const Peer& peer, const NodeId& target);
|
|
543
543
|
void send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash);
|
|
544
|
-
void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token);
|
|
544
|
+
void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port = false);
|
|
545
545
|
|
|
546
546
|
void add_node(const DhtNode& node, bool confirmed = true, bool no_verify = false);
|
|
547
547
|
std::vector<DhtNode> find_closest_nodes(const NodeId& target, size_t count = K_BUCKET_SIZE);
|
package/native-src/src/ice.cpp
CHANGED
|
@@ -281,7 +281,7 @@ bool IceManager::ensure_socket() {
|
|
|
281
281
|
return false;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
local_port_ = static_cast<uint16_t>(
|
|
284
|
+
local_port_ = static_cast<uint16_t>(get_bound_port(socket_));
|
|
285
285
|
LOG_ICE_DEBUG("Created ICE socket on port " << local_port_);
|
|
286
286
|
return true;
|
|
287
287
|
}
|
package/native-src/src/krpc.cpp
CHANGED
|
@@ -60,7 +60,7 @@ KrpcMessage KrpcProtocol::create_get_peers_query(const std::string& transaction_
|
|
|
60
60
|
return message;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token) {
|
|
63
|
+
KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port) {
|
|
64
64
|
KrpcMessage message;
|
|
65
65
|
message.type = KrpcMessageType::Query;
|
|
66
66
|
message.transaction_id = transaction_id;
|
|
@@ -68,6 +68,7 @@ KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transact
|
|
|
68
68
|
message.sender_id = sender_id;
|
|
69
69
|
message.info_hash = info_hash;
|
|
70
70
|
message.port = port;
|
|
71
|
+
message.implied_port = implied_port;
|
|
71
72
|
message.token = token;
|
|
72
73
|
return message;
|
|
73
74
|
}
|
|
@@ -176,6 +177,9 @@ BencodeValue KrpcProtocol::encode_query(const KrpcMessage& message) {
|
|
|
176
177
|
args["info_hash"] = BencodeValue(node_id_to_string(message.info_hash));
|
|
177
178
|
args["port"] = BencodeValue(static_cast<int64_t>(message.port));
|
|
178
179
|
args["token"] = BencodeValue(message.token);
|
|
180
|
+
if (message.implied_port) {
|
|
181
|
+
args["implied_port"] = BencodeValue(static_cast<int64_t>(1));
|
|
182
|
+
}
|
|
179
183
|
break;
|
|
180
184
|
}
|
|
181
185
|
|
|
@@ -316,6 +320,9 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_query(const BencodeValue& data
|
|
|
316
320
|
if (args.has_key("token")) {
|
|
317
321
|
message->token = args["token"].as_string();
|
|
318
322
|
}
|
|
323
|
+
if (args.has_key("implied_port")) {
|
|
324
|
+
message->implied_port = (args["implied_port"].as_integer() != 0);
|
|
325
|
+
}
|
|
319
326
|
break;
|
|
320
327
|
}
|
|
321
328
|
|
package/native-src/src/krpc.h
CHANGED
|
@@ -68,6 +68,7 @@ struct KrpcMessage {
|
|
|
68
68
|
NodeId target_id;
|
|
69
69
|
InfoHash info_hash;
|
|
70
70
|
uint16_t port;
|
|
71
|
+
bool implied_port; // BEP 5: if true, use UDP source port instead of 'port' field
|
|
71
72
|
std::string token;
|
|
72
73
|
|
|
73
74
|
// For responses
|
|
@@ -79,7 +80,7 @@ struct KrpcMessage {
|
|
|
79
80
|
KrpcErrorCode error_code;
|
|
80
81
|
std::string error_message;
|
|
81
82
|
|
|
82
|
-
KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), response_id(), error_code(KrpcErrorCode::GenericError) {}
|
|
83
|
+
KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), implied_port(false), response_id(), error_code(KrpcErrorCode::GenericError) {}
|
|
83
84
|
};
|
|
84
85
|
|
|
85
86
|
/**
|
|
@@ -96,7 +97,7 @@ public:
|
|
|
96
97
|
static KrpcMessage create_ping_query(const std::string& transaction_id, const NodeId& sender_id);
|
|
97
98
|
static KrpcMessage create_find_node_query(const std::string& transaction_id, const NodeId& sender_id, const NodeId& target_id);
|
|
98
99
|
static KrpcMessage create_get_peers_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash);
|
|
99
|
-
static KrpcMessage create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token);
|
|
100
|
+
static KrpcMessage create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port = false);
|
|
100
101
|
|
|
101
102
|
static KrpcMessage create_ping_response(const std::string& transaction_id, const NodeId& response_id);
|
|
102
103
|
static KrpcMessage create_find_node_response(const std::string& transaction_id, const NodeId& response_id, const std::vector<KrpcNode>& nodes);
|
|
@@ -146,16 +146,27 @@ bool RatsClient::start() {
|
|
|
146
146
|
initialize_local_addresses();
|
|
147
147
|
|
|
148
148
|
// Create dual-stack server socket (supports both IPv4 and IPv6)
|
|
149
|
-
|
|
149
|
+
server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
|
|
150
|
+
// Fallback to free port
|
|
151
|
+
if (!is_valid_socket(server_socket_) && listen_port_ > 0) {
|
|
152
|
+
// Requested port is not available, try ephemeral port as fallback
|
|
153
|
+
int original_port = listen_port_;
|
|
154
|
+
LOG_CLIENT_WARN("TCP port " << original_port << " is not available, falling back to ephemeral port");
|
|
155
|
+
server_socket_ = create_tcp_server(0, 5, bind_address_);
|
|
156
|
+
if (is_valid_socket(server_socket_)) {
|
|
157
|
+
listen_port_ = get_bound_port(server_socket_);
|
|
158
|
+
LOG_CLIENT_INFO("Fell back from port " << original_port << " to ephemeral port " << listen_port_);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
150
161
|
if (!is_valid_socket(server_socket_)) {
|
|
151
|
-
LOG_CLIENT_ERROR("Failed to create
|
|
162
|
+
LOG_CLIENT_ERROR("Failed to create server socket on port " << listen_port_ <<
|
|
152
163
|
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
153
164
|
return false;
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
// Update listen_port_ with actual bound port if ephemeral port was requested
|
|
157
168
|
if (listen_port_ == 0) {
|
|
158
|
-
listen_port_ =
|
|
169
|
+
listen_port_ = get_bound_port(server_socket_);
|
|
159
170
|
if (listen_port_ == 0) {
|
|
160
171
|
LOG_CLIENT_WARN("Failed to get actual bound port - using port 0");
|
|
161
172
|
} else {
|
|
@@ -448,14 +459,15 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
|
|
|
448
459
|
|
|
449
460
|
// ----- 1. RECEIVE DATA -----
|
|
450
461
|
LOG_CLIENT_DEBUG("Receiving data from socket " << client_socket);
|
|
451
|
-
std::vector<uint8_t> received_bytes =
|
|
462
|
+
std::vector<uint8_t> received_bytes = receive_tcp_message(client_socket);
|
|
452
463
|
|
|
453
464
|
if (received_bytes.empty()) {
|
|
454
465
|
break; // Connection closed or error
|
|
455
466
|
}
|
|
456
467
|
|
|
457
468
|
// ----- 2. DECRYPT IF NEEDED -----
|
|
458
|
-
|
|
469
|
+
// Use vector directly to avoid unnecessary string conversions
|
|
470
|
+
std::vector<uint8_t> data;
|
|
459
471
|
|
|
460
472
|
if (noise_handshake_done) {
|
|
461
473
|
std::string current_peer_id;
|
|
@@ -492,16 +504,18 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
|
|
|
492
504
|
}
|
|
493
505
|
|
|
494
506
|
plaintext.resize(pt_len);
|
|
495
|
-
data = std::
|
|
507
|
+
data = std::move(plaintext);
|
|
496
508
|
LOG_CLIENT_DEBUG("Decrypted message from " << current_peer_id << " (" << pt_len << " bytes)");
|
|
497
509
|
} else {
|
|
498
|
-
data = std::
|
|
510
|
+
data = std::move(received_bytes);
|
|
499
511
|
}
|
|
500
512
|
} else {
|
|
501
|
-
data = std::
|
|
513
|
+
data = std::move(received_bytes);
|
|
502
514
|
}
|
|
503
515
|
|
|
504
|
-
|
|
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 ? "..." : ""));
|
|
505
519
|
|
|
506
520
|
// ----- 3. CONNECTION STATE CHECK (during handshake phase only) -----
|
|
507
521
|
if (!handshake_completed) {
|
|
@@ -531,51 +545,75 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
|
|
|
531
545
|
}
|
|
532
546
|
|
|
533
547
|
// ----- 4. HANDSHAKE PHASE -----
|
|
534
|
-
|
|
548
|
+
// Only check for handshake messages BEFORE handshake is completed
|
|
549
|
+
// This avoids expensive JSON parsing on every message after handshake
|
|
550
|
+
if (!handshake_completed && is_handshake_message(data)) {
|
|
535
551
|
if (!handle_handshake_message(client_socket, peer_hash_id, data)) {
|
|
536
552
|
LOG_CLIENT_ERROR("Failed to handle handshake message from " << peer_hash_id);
|
|
537
553
|
break;
|
|
538
554
|
}
|
|
539
555
|
|
|
540
|
-
// Check if handshake just completed
|
|
556
|
+
// Check if rats handshake just completed (COMPLETED or NOISE_PENDING state)
|
|
541
557
|
if (!handshake_completed) {
|
|
542
558
|
RatsPeer peer_copy;
|
|
543
|
-
bool
|
|
559
|
+
bool rats_handshake_done = false;
|
|
560
|
+
bool needs_noise_handshake = false;
|
|
544
561
|
|
|
545
562
|
{
|
|
546
563
|
std::lock_guard<std::mutex> lock(peers_mutex_);
|
|
547
564
|
auto sock_it = socket_to_peer_id_.find(client_socket);
|
|
548
565
|
if (sock_it != socket_to_peer_id_.end()) {
|
|
549
566
|
auto peer_it = peers_.find(sock_it->second);
|
|
550
|
-
if (peer_it != peers_.end()
|
|
551
|
-
|
|
552
|
-
|
|
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
|
+
}
|
|
553
577
|
}
|
|
554
578
|
}
|
|
555
579
|
}
|
|
556
580
|
|
|
557
581
|
// ----- POST-HANDSHAKE ACTIONS -----
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
LOG_CLIENT_INFO("Handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
|
|
582
|
+
if (rats_handshake_done) {
|
|
583
|
+
LOG_CLIENT_INFO("Rats handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
|
|
561
584
|
|
|
562
585
|
// Remove from reconnection queue if present (successful connection)
|
|
563
586
|
remove_from_reconnect_queue(peer_copy.peer_id);
|
|
564
587
|
|
|
565
588
|
// Noise encryption handshake - only if BOTH sides support encryption
|
|
566
589
|
// peer_copy.encryption_enabled is already negotiated in handle_handshake_message()
|
|
567
|
-
if (
|
|
590
|
+
if (needs_noise_handshake) {
|
|
568
591
|
LOG_CLIENT_INFO("Starting Noise handshake for peer " << peer_copy.peer_id);
|
|
569
592
|
if (perform_noise_handshake(client_socket, peer_copy.peer_id, peer_copy.is_outgoing)) {
|
|
570
593
|
noise_handshake_done = true;
|
|
571
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
|
+
}
|
|
572
608
|
} else {
|
|
573
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;
|
|
574
612
|
}
|
|
575
|
-
} else {
|
|
576
|
-
LOG_CLIENT_DEBUG("Skipping Noise handshake for peer " << peer_copy.peer_id << " - encryption not negotiated");
|
|
577
613
|
}
|
|
578
614
|
|
|
615
|
+
handshake_completed = true;
|
|
616
|
+
|
|
579
617
|
// Connection callback
|
|
580
618
|
if (connection_callback_) {
|
|
581
619
|
connection_callback_(client_socket, peer_copy.peer_id);
|
|
@@ -621,11 +659,11 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
|
|
|
621
659
|
continue;
|
|
622
660
|
}
|
|
623
661
|
|
|
624
|
-
|
|
662
|
+
// Use data directly - no need for extra copy
|
|
625
663
|
MessageHeader header;
|
|
626
664
|
std::vector<uint8_t> payload;
|
|
627
665
|
|
|
628
|
-
if (!parse_message_with_header(
|
|
666
|
+
if (!parse_message_with_header(data, header, payload)) {
|
|
629
667
|
LOG_CLIENT_WARN("No header found in message from " << peer_hash_id);
|
|
630
668
|
continue;
|
|
631
669
|
}
|
|
@@ -741,14 +779,15 @@ std::string RatsClient::create_handshake_message(const std::string& message_type
|
|
|
741
779
|
handshake_msg["message_type"] = message_type;
|
|
742
780
|
handshake_msg["timestamp"] = timestamp;
|
|
743
781
|
handshake_msg["encryption_enabled"] = is_encryption_enabled();
|
|
782
|
+
handshake_msg["listen_port"] = listen_port_;
|
|
744
783
|
|
|
745
784
|
return handshake_msg.dump();
|
|
746
785
|
}
|
|
747
786
|
|
|
748
|
-
bool RatsClient::parse_handshake_message(const std::
|
|
787
|
+
bool RatsClient::parse_handshake_message(const std::vector<uint8_t>& data, HandshakeMessage& out_msg) const {
|
|
749
788
|
try {
|
|
750
|
-
// Use nlohmann::json
|
|
751
|
-
nlohmann::json json_msg = nlohmann::json::parse(
|
|
789
|
+
// Use nlohmann::json with iterators to avoid string conversion
|
|
790
|
+
nlohmann::json json_msg = nlohmann::json::parse(data.begin(), data.end());
|
|
752
791
|
|
|
753
792
|
// Clear the output structure
|
|
754
793
|
out_msg = HandshakeMessage{};
|
|
@@ -762,6 +801,8 @@ bool RatsClient::parse_handshake_message(const std::string& message, HandshakeMe
|
|
|
762
801
|
out_msg.timestamp = json_msg.value("timestamp", static_cast<int64_t>(0));
|
|
763
802
|
// Parse encryption_enabled (default to false for backward compatibility)
|
|
764
803
|
out_msg.encryption_enabled = json_msg.value("encryption_enabled", false);
|
|
804
|
+
// Parse listen_port (default to 0 for backward compatibility with older clients)
|
|
805
|
+
out_msg.listen_port = json_msg.value("listen_port", static_cast<uint16_t>(0));
|
|
765
806
|
|
|
766
807
|
return true;
|
|
767
808
|
|
|
@@ -823,37 +864,30 @@ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
|
|
|
823
864
|
return true;
|
|
824
865
|
}
|
|
825
866
|
|
|
826
|
-
bool RatsClient::is_handshake_message(const std::
|
|
867
|
+
bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
|
|
827
868
|
try {
|
|
828
|
-
std::string json_to_parse = message;
|
|
829
|
-
|
|
830
869
|
// Check if message has our message header (starts with "RATS" magic)
|
|
831
|
-
std::vector<uint8_t> message_data(message.begin(), message.end());
|
|
832
870
|
MessageHeader header;
|
|
833
871
|
std::vector<uint8_t> payload;
|
|
834
872
|
|
|
835
|
-
if (parse_message_with_header(
|
|
873
|
+
if (parse_message_with_header(data, header, payload)) {
|
|
836
874
|
// Message has valid header - extract the JSON payload
|
|
837
875
|
if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
876
|
+
// Parse the JSON message directly from payload
|
|
877
|
+
nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
|
|
878
|
+
std::string expected_protocol;
|
|
879
|
+
{
|
|
880
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
881
|
+
expected_protocol = custom_protocol_name_;
|
|
882
|
+
}
|
|
883
|
+
return json_msg.value("protocol", "") == expected_protocol &&
|
|
884
|
+
json_msg.value("message_type", "") == "handshake";
|
|
842
885
|
}
|
|
843
|
-
|
|
844
|
-
// Message has no header
|
|
886
|
+
// Handshake messages should be string/JSON type
|
|
845
887
|
return false;
|
|
846
888
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
nlohmann::json json_msg = nlohmann::json::parse(json_to_parse);
|
|
850
|
-
std::string expected_protocol;
|
|
851
|
-
{
|
|
852
|
-
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
853
|
-
expected_protocol = custom_protocol_name_;
|
|
854
|
-
}
|
|
855
|
-
return json_msg.value("protocol", "") == expected_protocol &&
|
|
856
|
-
json_msg.value("message_type", "") == "handshake";
|
|
889
|
+
// Message has no header
|
|
890
|
+
return false;
|
|
857
891
|
} catch (const std::exception&) {
|
|
858
892
|
return false;
|
|
859
893
|
}
|
|
@@ -874,7 +908,7 @@ bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our
|
|
|
874
908
|
auto socket_mutex = get_socket_send_mutex(socket);
|
|
875
909
|
std::lock_guard<std::mutex> send_lock(*socket_mutex);
|
|
876
910
|
|
|
877
|
-
int sent =
|
|
911
|
+
int sent = send_tcp_message(socket, message_with_header);
|
|
878
912
|
if (sent <= 0) {
|
|
879
913
|
LOG_CLIENT_ERROR("Failed to send handshake to socket " << socket);
|
|
880
914
|
return false;
|
|
@@ -898,28 +932,25 @@ bool RatsClient::send_handshake(socket_t socket, const std::string& our_peer_id)
|
|
|
898
932
|
return send_handshake_unlocked(socket, our_peer_id);
|
|
899
933
|
}
|
|
900
934
|
|
|
901
|
-
bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::
|
|
902
|
-
// Extract JSON payload from message header
|
|
903
|
-
std::string json_to_parse = message;
|
|
904
|
-
std::vector<uint8_t> message_data(message.begin(), message.end());
|
|
935
|
+
bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::vector<uint8_t>& data) {
|
|
936
|
+
// Extract JSON payload from message header
|
|
905
937
|
MessageHeader header;
|
|
906
938
|
std::vector<uint8_t> payload;
|
|
907
939
|
|
|
908
|
-
if (parse_message_with_header(
|
|
909
|
-
// Message has valid header - extract the JSON payload
|
|
910
|
-
if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
|
|
911
|
-
json_to_parse = std::string(payload.begin(), payload.end());
|
|
912
|
-
} else {
|
|
913
|
-
LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
|
|
914
|
-
return false;
|
|
915
|
-
}
|
|
916
|
-
} else {
|
|
940
|
+
if (!parse_message_with_header(data, header, payload)) {
|
|
917
941
|
LOG_CLIENT_ERROR("Failed to parse handshake message header from " << peer_hash_id);
|
|
918
942
|
return false;
|
|
919
943
|
}
|
|
920
944
|
|
|
945
|
+
// Message has valid header - check the type
|
|
946
|
+
if (header.type != MessageDataType::STRING && header.type != MessageDataType::JSON) {
|
|
947
|
+
LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Parse handshake message directly from payload (no string conversion)
|
|
921
952
|
HandshakeMessage handshake_msg;
|
|
922
|
-
if (!parse_handshake_message(
|
|
953
|
+
if (!parse_handshake_message(payload, handshake_msg)) {
|
|
923
954
|
LOG_CLIENT_ERROR("Failed to parse handshake message from " << peer_hash_id);
|
|
924
955
|
return false;
|
|
925
956
|
}
|
|
@@ -995,12 +1026,35 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
995
1026
|
<< ", remote=" << remote_encryption
|
|
996
1027
|
<< ", result=" << peer.encryption_enabled);
|
|
997
1028
|
|
|
1029
|
+
// For incoming connections, update port to the peer's actual listen port
|
|
1030
|
+
// This is critical for peer exchange to work correctly
|
|
1031
|
+
if (!peer.is_outgoing && handshake_msg.listen_port > 0) {
|
|
1032
|
+
// Remove old address mapping
|
|
1033
|
+
address_to_peer_id_.erase(peer.normalized_address);
|
|
1034
|
+
|
|
1035
|
+
// Update port and normalized address
|
|
1036
|
+
peer.port = handshake_msg.listen_port;
|
|
1037
|
+
peer.normalized_address = normalize_peer_address(peer.ip, peer.port);
|
|
1038
|
+
|
|
1039
|
+
// Add new address mapping
|
|
1040
|
+
address_to_peer_id_[peer.normalized_address] = peer.peer_id;
|
|
1041
|
+
|
|
1042
|
+
LOG_CLIENT_INFO("Updated incoming peer port to listen_port: " << peer.ip << ":" << peer.port);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
998
1045
|
// Simplified handshake logic - just one message type
|
|
999
1046
|
if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
|
|
1000
1047
|
// This is an incoming handshake - send our handshake back
|
|
1001
1048
|
if (send_handshake_unlocked(socket, get_our_peer_id())) {
|
|
1002
|
-
|
|
1003
|
-
|
|
1049
|
+
// If encryption is enabled, we need to do Noise handshake first
|
|
1050
|
+
// Set NOISE_PENDING to prevent other threads from sending messages
|
|
1051
|
+
if (peer.encryption_enabled) {
|
|
1052
|
+
peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
|
|
1053
|
+
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
|
|
1054
|
+
} else {
|
|
1055
|
+
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1056
|
+
log_handshake_completion_unlocked(peer);
|
|
1057
|
+
}
|
|
1004
1058
|
|
|
1005
1059
|
// Append to historical peers file after successful connection
|
|
1006
1060
|
append_peer_to_historical_file(peer);
|
|
@@ -1013,8 +1067,15 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
|
|
|
1013
1067
|
}
|
|
1014
1068
|
} else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
|
|
1015
1069
|
// This is a response to our handshake
|
|
1016
|
-
|
|
1017
|
-
|
|
1070
|
+
// If encryption is enabled, we need to do Noise handshake first
|
|
1071
|
+
// Set NOISE_PENDING to prevent other threads from sending messages
|
|
1072
|
+
if (peer.encryption_enabled) {
|
|
1073
|
+
peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
|
|
1074
|
+
LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
|
|
1075
|
+
} else {
|
|
1076
|
+
peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
|
|
1077
|
+
log_handshake_completion_unlocked(peer);
|
|
1078
|
+
}
|
|
1018
1079
|
|
|
1019
1080
|
// Append to historical peers file after successful connection
|
|
1020
1081
|
append_peer_to_historical_file(peer);
|
|
@@ -1362,12 +1423,12 @@ bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector
|
|
|
1362
1423
|
LOG_CLIENT_DEBUG("Sending encrypted message to " << peer_id_for_logging << " (" << ct_len << " bytes)");
|
|
1363
1424
|
|
|
1364
1425
|
// Send encrypted message using framed protocol
|
|
1365
|
-
int sent =
|
|
1426
|
+
int sent = send_tcp_message(socket, ciphertext);
|
|
1366
1427
|
return sent > 0;
|
|
1367
1428
|
}
|
|
1368
1429
|
|
|
1369
1430
|
// Unencrypted path - use framed messages for reliable large message handling
|
|
1370
|
-
int sent =
|
|
1431
|
+
int sent = send_tcp_message(socket, message_with_header);
|
|
1371
1432
|
return sent > 0;
|
|
1372
1433
|
}
|
|
1373
1434
|
|
|
@@ -1922,6 +1983,33 @@ bool RatsClient::is_automatic_discovery_running() const {
|
|
|
1922
1983
|
return auto_discovery_running_.load();
|
|
1923
1984
|
}
|
|
1924
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
|
+
|
|
1925
2013
|
void RatsClient::automatic_discovery_loop() {
|
|
1926
2014
|
LOG_CLIENT_INFO("Automatic peer discovery loop started");
|
|
1927
2015
|
|
|
@@ -1943,12 +2031,12 @@ void RatsClient::automatic_discovery_loop() {
|
|
|
1943
2031
|
auto now = std::chrono::steady_clock::now();
|
|
1944
2032
|
|
|
1945
2033
|
// Announce combines both announcing our presence and discovering peers
|
|
1946
|
-
//
|
|
1947
|
-
auto interval = (
|
|
1948
|
-
? std::chrono::seconds(15) // Aggressive when no peers
|
|
1949
|
-
: std::chrono::minutes(10); // Less aggressive when connected
|
|
2034
|
+
// Interval scales based on peer count: aggressive when empty, relaxed when nearly full
|
|
2035
|
+
auto interval = calculate_discovery_interval();
|
|
1950
2036
|
|
|
1951
2037
|
if (now - last_announce >= interval) {
|
|
2038
|
+
LOG_CLIENT_DEBUG("Discovery interval: " << interval.count() << "s (peers: "
|
|
2039
|
+
<< get_peer_count() << "/" << max_peers_ << ")");
|
|
1952
2040
|
announce_rats_peer();
|
|
1953
2041
|
last_announce = now;
|
|
1954
2042
|
}
|
|
@@ -2377,8 +2465,8 @@ nlohmann::json RatsClient::create_peer_exchange_message(const RatsPeer& peer) {
|
|
|
2377
2465
|
payload["peer_id"] = peer.peer_id;
|
|
2378
2466
|
payload["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
|
|
2379
2467
|
|
|
2380
|
-
// Create rats message
|
|
2381
|
-
return create_rats_message("peer", payload,
|
|
2468
|
+
// Create rats message - use OUR peer_id as sender, not the advertised peer's id
|
|
2469
|
+
return create_rats_message("peer", payload, get_our_peer_id());
|
|
2382
2470
|
}
|
|
2383
2471
|
|
|
2384
2472
|
void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
|