librats 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/binding.gyp +1 -0
- package/native-src/CMakeLists.txt +10 -0
- package/native-src/src/dht.cpp +200 -17
- package/native-src/src/dht.h +68 -2
- package/native-src/src/ice.cpp +2 -2
- package/native-src/src/krpc.h +2 -2
- package/native-src/src/librats.cpp +10 -9
- package/native-src/src/librats.h +55 -0
- package/native-src/src/librats_bittorrent.cpp +65 -0
- package/native-src/src/librats_mdns.cpp +5 -0
- package/native-src/src/logger.cpp +11 -0
- package/native-src/src/logger.h +2 -5
- package/native-src/src/mdns.cpp +163 -83
- package/native-src/src/mdns.h +14 -3
- package/native-src/src/noise.cpp +3 -1
- package/native-src/src/os.cpp +3 -1
- package/native-src/src/socket.h +3 -1
- package/native-src/src/stun.cpp +2 -2
- package/package.json +1 -1
- package/src/librats_node.cpp +9 -3
package/binding.gyp
CHANGED
|
@@ -64,6 +64,15 @@ project(librats VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
|
|
|
64
64
|
set(CMAKE_CXX_STANDARD 17)
|
|
65
65
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
66
66
|
|
|
67
|
+
# Flags
|
|
68
|
+
# Compiler warnings configuration
|
|
69
|
+
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|
70
|
+
# additional checks by compiler to found errors ealy on
|
|
71
|
+
add_compile_options(-Wall -Wextra -Wpedantic)
|
|
72
|
+
# not show unused stuff, useful for development
|
|
73
|
+
add_compile_options(-Wno-unused-parameter -Wno-unused-variable -Wno-unused-but-set-variable)
|
|
74
|
+
endif()
|
|
75
|
+
|
|
67
76
|
# Options
|
|
68
77
|
option(RATS_BUILD_EXAMPLES "Build examples" ON)
|
|
69
78
|
option(RATS_BUILD_TESTS "Build unit tests" ON)
|
|
@@ -138,6 +147,7 @@ set(LIBRARY_SOURCES
|
|
|
138
147
|
src/fs.cpp
|
|
139
148
|
src/fs.h
|
|
140
149
|
src/logger.h
|
|
150
|
+
src/logger.cpp
|
|
141
151
|
src/noise.cpp
|
|
142
152
|
src/noise.h
|
|
143
153
|
src/encrypted_socket.cpp
|
package/native-src/src/dht.cpp
CHANGED
|
@@ -457,7 +457,7 @@ void DhtClient::handle_message(const std::vector<uint8_t>& data, const Peer& sen
|
|
|
457
457
|
handle_krpc_message(*krpc_message, sender);
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
void DhtClient::add_node(const DhtNode& node, bool confirmed) {
|
|
460
|
+
void DhtClient::add_node(const DhtNode& node, bool confirmed, bool no_verify) {
|
|
461
461
|
std::lock_guard<std::mutex> ping_lock(pending_pings_mutex_);
|
|
462
462
|
std::lock_guard<std::mutex> lock(routing_table_mutex_);
|
|
463
463
|
|
|
@@ -515,7 +515,7 @@ void DhtClient::add_node(const DhtNode& node, bool confirmed) {
|
|
|
515
515
|
return;
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
-
// All nodes are good - find the "worst" good node for ping verification
|
|
518
|
+
// All nodes are good - find the "worst" good node for ping verification or replacement
|
|
519
519
|
// Worst = highest RTT among nodes not already being pinged
|
|
520
520
|
DhtNode* worst = nullptr;
|
|
521
521
|
for (auto& existing : bucket) {
|
|
@@ -531,6 +531,19 @@ void DhtClient::add_node(const DhtNode& node, bool confirmed) {
|
|
|
531
531
|
return;
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
+
// If no_verify is set, directly replace the worst node without ping verification
|
|
535
|
+
if (no_verify) {
|
|
536
|
+
LOG_DHT_DEBUG("Force adding node " << node_id_to_hex(node.id)
|
|
537
|
+
<< " - replacing worst node " << node_id_to_hex(worst->id)
|
|
538
|
+
<< " (rtt=" << worst->rtt << "ms) without ping verification");
|
|
539
|
+
DhtNode new_node = node;
|
|
540
|
+
if (confirmed) {
|
|
541
|
+
new_node.fail_count = 0;
|
|
542
|
+
}
|
|
543
|
+
*worst = new_node;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
534
547
|
// Initiate ping to worst node - if it doesn't respond, replace with candidate
|
|
535
548
|
LOG_DHT_DEBUG("All nodes good, pinging worst node " << node_id_to_hex(worst->id)
|
|
536
549
|
<< " (rtt=" << worst->rtt << "ms) to verify");
|
|
@@ -557,7 +570,7 @@ std::vector<DhtNode> DhtClient::find_closest_nodes_unlocked(const NodeId& target
|
|
|
557
570
|
candidates.reserve(count * 3 + K_BUCKET_SIZE * 2);
|
|
558
571
|
|
|
559
572
|
// Add nodes from ideal bucket
|
|
560
|
-
if (target_bucket < routing_table_.size()) {
|
|
573
|
+
if (target_bucket >= 0 && static_cast<size_t>(target_bucket) < routing_table_.size()) {
|
|
561
574
|
const auto& bucket = routing_table_[target_bucket];
|
|
562
575
|
candidates.insert(candidates.end(), bucket.begin(), bucket.end());
|
|
563
576
|
LOG_DHT_DEBUG("Collected " << bucket.size() << " nodes from target bucket " << target_bucket);
|
|
@@ -657,11 +670,11 @@ int DhtClient::get_bucket_index(const NodeId& id) {
|
|
|
657
670
|
NodeId distance = xor_distance(node_id_, id);
|
|
658
671
|
|
|
659
672
|
// Find the position of the most significant bit
|
|
660
|
-
for (
|
|
673
|
+
for (size_t i = 0; i < NODE_ID_SIZE; ++i) {
|
|
661
674
|
if (distance[i] != 0) {
|
|
662
675
|
for (int j = 7; j >= 0; --j) {
|
|
663
676
|
if (distance[i] & (1 << j)) {
|
|
664
|
-
return i * 8 + (7 - j);
|
|
677
|
+
return static_cast<int>(i * 8 + (7 - j));
|
|
665
678
|
}
|
|
666
679
|
}
|
|
667
680
|
}
|
|
@@ -705,10 +718,20 @@ void DhtClient::handle_krpc_message(const KrpcMessage& message, const Peer& send
|
|
|
705
718
|
void DhtClient::handle_krpc_ping(const KrpcMessage& message, const Peer& sender) {
|
|
706
719
|
LOG_DHT_DEBUG("Handling KRPC PING from " << node_id_to_hex(message.sender_id) << " at " << sender.ip << ":" << sender.port);
|
|
707
720
|
|
|
721
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
722
|
+
// Spider mode: check if ignoring requests
|
|
723
|
+
if (spider_mode_.load() && spider_ignore_.load()) {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
bool no_verify = spider_mode_.load();
|
|
727
|
+
#else
|
|
728
|
+
bool no_verify = false;
|
|
729
|
+
#endif
|
|
730
|
+
|
|
708
731
|
// Add sender to routing table
|
|
709
732
|
KrpcNode krpc_node(message.sender_id, sender.ip, sender.port);
|
|
710
733
|
DhtNode sender_node = krpc_node_to_dht_node(krpc_node);
|
|
711
|
-
add_node(sender_node);
|
|
734
|
+
add_node(sender_node, true, no_verify);
|
|
712
735
|
|
|
713
736
|
// Respond with ping response
|
|
714
737
|
auto response = KrpcProtocol::create_ping_response(message.transaction_id, node_id_);
|
|
@@ -718,10 +741,20 @@ void DhtClient::handle_krpc_ping(const KrpcMessage& message, const Peer& sender)
|
|
|
718
741
|
void DhtClient::handle_krpc_find_node(const KrpcMessage& message, const Peer& sender) {
|
|
719
742
|
LOG_DHT_DEBUG("Handling KRPC FIND_NODE from " << node_id_to_hex(message.sender_id) << " at " << sender.ip << ":" << sender.port);
|
|
720
743
|
|
|
744
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
745
|
+
// Spider mode: check if ignoring requests
|
|
746
|
+
if (spider_mode_.load() && spider_ignore_.load()) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
bool no_verify = spider_mode_.load();
|
|
750
|
+
#else
|
|
751
|
+
bool no_verify = false;
|
|
752
|
+
#endif
|
|
753
|
+
|
|
721
754
|
// Add sender to routing table
|
|
722
755
|
KrpcNode krpc_node(message.sender_id, sender.ip, sender.port);
|
|
723
756
|
DhtNode sender_node = krpc_node_to_dht_node(krpc_node);
|
|
724
|
-
add_node(sender_node);
|
|
757
|
+
add_node(sender_node, true, no_verify);
|
|
725
758
|
|
|
726
759
|
// Find closest nodes
|
|
727
760
|
auto closest_nodes = find_closest_nodes(message.target_id, K_BUCKET_SIZE);
|
|
@@ -735,10 +768,20 @@ void DhtClient::handle_krpc_find_node(const KrpcMessage& message, const Peer& se
|
|
|
735
768
|
void DhtClient::handle_krpc_get_peers(const KrpcMessage& message, const Peer& sender) {
|
|
736
769
|
LOG_DHT_DEBUG("Handling KRPC GET_PEERS from " << node_id_to_hex(message.sender_id) << " at " << sender.ip << ":" << sender.port << " for info_hash " << node_id_to_hex(message.info_hash));
|
|
737
770
|
|
|
771
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
772
|
+
// Spider mode: check if ignoring requests
|
|
773
|
+
if (spider_mode_.load() && spider_ignore_.load()) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
bool no_verify = spider_mode_.load();
|
|
777
|
+
#else
|
|
778
|
+
bool no_verify = false;
|
|
779
|
+
#endif
|
|
780
|
+
|
|
738
781
|
// Add sender to routing table
|
|
739
782
|
KrpcNode krpc_node(message.sender_id, sender.ip, sender.port);
|
|
740
783
|
DhtNode sender_node = krpc_node_to_dht_node(krpc_node);
|
|
741
|
-
add_node(sender_node);
|
|
784
|
+
add_node(sender_node, true, no_verify);
|
|
742
785
|
|
|
743
786
|
// Generate a token for this peer
|
|
744
787
|
std::string token = generate_token(sender);
|
|
@@ -765,6 +808,20 @@ void DhtClient::handle_krpc_get_peers(const KrpcMessage& message, const Peer& se
|
|
|
765
808
|
void DhtClient::handle_krpc_announce_peer(const KrpcMessage& message, const Peer& sender) {
|
|
766
809
|
LOG_DHT_DEBUG("Handling KRPC ANNOUNCE_PEER from " << node_id_to_hex(message.sender_id) << " at " << sender.ip << ":" << sender.port);
|
|
767
810
|
|
|
811
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
812
|
+
bool is_spider = spider_mode_.load();
|
|
813
|
+
|
|
814
|
+
// Spider mode: check if ignoring requests (but still process announces for callback)
|
|
815
|
+
// Note: We still want to collect announces even when ignoring other requests
|
|
816
|
+
|
|
817
|
+
// In spider mode, skip token verification for maximum collection
|
|
818
|
+
if (!is_spider && !verify_token(sender, message.token)) {
|
|
819
|
+
LOG_DHT_WARN("Invalid token from " << sender.ip << ":" << sender.port << " for KRPC ANNOUNCE_PEER");
|
|
820
|
+
auto error = KrpcProtocol::create_error(message.transaction_id, KrpcErrorCode::ProtocolError, "Invalid token");
|
|
821
|
+
send_krpc_message(error, sender);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
#else
|
|
768
825
|
// Verify token
|
|
769
826
|
if (!verify_token(sender, message.token)) {
|
|
770
827
|
LOG_DHT_WARN("Invalid token from " << sender.ip << ":" << sender.port << " for KRPC ANNOUNCE_PEER");
|
|
@@ -772,16 +829,37 @@ void DhtClient::handle_krpc_announce_peer(const KrpcMessage& message, const Peer
|
|
|
772
829
|
send_krpc_message(error, sender);
|
|
773
830
|
return;
|
|
774
831
|
}
|
|
832
|
+
bool is_spider = false;
|
|
833
|
+
#endif
|
|
775
834
|
|
|
776
835
|
// Add sender to routing table
|
|
777
836
|
KrpcNode krpc_node(message.sender_id, sender.ip, sender.port);
|
|
778
837
|
DhtNode sender_node = krpc_node_to_dht_node(krpc_node);
|
|
779
|
-
add_node(sender_node);
|
|
838
|
+
add_node(sender_node, true, is_spider);
|
|
839
|
+
|
|
840
|
+
// Determine the actual port (BEP 5: implied_port support)
|
|
841
|
+
uint16_t peer_port = message.port;
|
|
780
842
|
|
|
781
843
|
// Store the peer announcement
|
|
782
|
-
Peer announcing_peer(sender.ip,
|
|
844
|
+
Peer announcing_peer(sender.ip, peer_port);
|
|
783
845
|
store_announced_peer(message.info_hash, announcing_peer);
|
|
784
846
|
|
|
847
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
848
|
+
// Spider mode: invoke announce callback (ensured hash - peer definitely has it)
|
|
849
|
+
if (is_spider) {
|
|
850
|
+
SpiderAnnounceCallback callback;
|
|
851
|
+
{
|
|
852
|
+
std::lock_guard<std::mutex> lock(spider_callbacks_mutex_);
|
|
853
|
+
callback = spider_announce_callback_;
|
|
854
|
+
}
|
|
855
|
+
if (callback) {
|
|
856
|
+
LOG_DHT_DEBUG("Spider: invoking announce callback for info_hash " << node_id_to_hex(message.info_hash)
|
|
857
|
+
<< " from " << announcing_peer.ip << ":" << announcing_peer.port);
|
|
858
|
+
callback(message.info_hash, announcing_peer);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
#endif
|
|
862
|
+
|
|
785
863
|
// Respond with acknowledgment
|
|
786
864
|
auto response = KrpcProtocol::create_announce_peer_response(message.transaction_id, node_id_);
|
|
787
865
|
send_krpc_message(response, sender);
|
|
@@ -793,15 +871,21 @@ void DhtClient::handle_krpc_response(const KrpcMessage& message, const Peer& sen
|
|
|
793
871
|
// Check if this is a ping verification response before normal processing
|
|
794
872
|
handle_ping_verification_response(message.transaction_id, message.response_id, sender);
|
|
795
873
|
|
|
874
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
875
|
+
bool no_verify = spider_mode_.load();
|
|
876
|
+
#else
|
|
877
|
+
bool no_verify = false;
|
|
878
|
+
#endif
|
|
879
|
+
|
|
796
880
|
// Add responder to routing table
|
|
797
881
|
KrpcNode krpc_node(message.response_id, sender.ip, sender.port);
|
|
798
882
|
DhtNode sender_node = krpc_node_to_dht_node(krpc_node);
|
|
799
|
-
add_node(sender_node);
|
|
883
|
+
add_node(sender_node, true, no_verify);
|
|
800
884
|
|
|
801
885
|
// Add any nodes from the response (these are nodes we heard about, not confirmed)
|
|
802
886
|
for (const auto& node : message.nodes) {
|
|
803
887
|
DhtNode dht_node = krpc_node_to_dht_node(node);
|
|
804
|
-
add_node(dht_node, false); // Not confirmed - just heard about from another node
|
|
888
|
+
add_node(dht_node, false, no_verify); // Not confirmed - just heard about from another node
|
|
805
889
|
}
|
|
806
890
|
|
|
807
891
|
// Check if this is a response to a pending search (get_peers with peers)
|
|
@@ -1247,9 +1331,9 @@ void DhtClient::refresh_buckets() {
|
|
|
1247
1331
|
int byte_index = static_cast<int>(i / 8);
|
|
1248
1332
|
int bit_index = static_cast<int>(i % 8);
|
|
1249
1333
|
|
|
1250
|
-
if (byte_index < NODE_ID_SIZE) {
|
|
1334
|
+
if (static_cast<size_t>(byte_index) < NODE_ID_SIZE) {
|
|
1251
1335
|
// Clear the target bit and higher bits
|
|
1252
|
-
for (
|
|
1336
|
+
for (size_t j = static_cast<size_t>(byte_index); j < NODE_ID_SIZE; ++j) {
|
|
1253
1337
|
random_id[j] = node_id_[j];
|
|
1254
1338
|
}
|
|
1255
1339
|
|
|
@@ -2156,6 +2240,25 @@ void DhtClient::handle_ping_verification_response(const std::string& transaction
|
|
|
2156
2240
|
if (it != pending_pings_.end()) {
|
|
2157
2241
|
const auto& verification = it->second;
|
|
2158
2242
|
|
|
2243
|
+
// Security check: Verify response comes from the IP we pinged
|
|
2244
|
+
// Normalize IPv6-mapped IPv4 addresses (::ffff:x.x.x.x -> x.x.x.x) for comparison
|
|
2245
|
+
auto normalize_ip = [](const std::string& ip) -> std::string {
|
|
2246
|
+
const std::string ipv4_mapped_prefix = "::ffff:";
|
|
2247
|
+
if (ip.compare(0, ipv4_mapped_prefix.size(), ipv4_mapped_prefix) == 0) {
|
|
2248
|
+
return ip.substr(ipv4_mapped_prefix.size());
|
|
2249
|
+
}
|
|
2250
|
+
return ip;
|
|
2251
|
+
};
|
|
2252
|
+
|
|
2253
|
+
std::string responder_ip_normalized = normalize_ip(responder.ip);
|
|
2254
|
+
std::string expected_ip_normalized = normalize_ip(verification.old_node.peer.ip);
|
|
2255
|
+
|
|
2256
|
+
if (responder_ip_normalized != expected_ip_normalized) {
|
|
2257
|
+
LOG_DHT_WARN("Ping verification response from wrong IP " << responder.ip
|
|
2258
|
+
<< " (expected " << verification.old_node.peer.ip << ") - ignoring");
|
|
2259
|
+
return; // Don't remove from pending_pings_, let it timeout naturally
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2159
2262
|
// BEP 5: We pinged the OLD node to check if it's still alive
|
|
2160
2263
|
if (responder_id == verification.old_node.id) {
|
|
2161
2264
|
// Calculate RTT
|
|
@@ -2182,9 +2285,19 @@ void DhtClient::handle_ping_verification_response(const std::string& transaction
|
|
|
2182
2285
|
}
|
|
2183
2286
|
// Candidate is discarded (not added to routing table)
|
|
2184
2287
|
} else {
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2288
|
+
// Different node ID responded from the same IP:port!
|
|
2289
|
+
// This means the old node is gone and this IP now belongs to a different node.
|
|
2290
|
+
// Replace the old node with the candidate.
|
|
2291
|
+
LOG_DHT_DEBUG("Different node " << node_id_to_hex(responder_id)
|
|
2292
|
+
<< " responded from " << responder.ip << ":" << responder.port
|
|
2293
|
+
<< " (expected old node " << node_id_to_hex(verification.old_node.id)
|
|
2294
|
+
<< ") - old node is gone, replacing with candidate "
|
|
2295
|
+
<< node_id_to_hex(verification.candidate_node.id));
|
|
2296
|
+
|
|
2297
|
+
// Replace old node with candidate
|
|
2298
|
+
DhtNode candidate = verification.candidate_node;
|
|
2299
|
+
candidate.mark_success(); // The responder just proved it's alive
|
|
2300
|
+
perform_replacement(candidate, verification.old_node, verification.bucket_index);
|
|
2188
2301
|
}
|
|
2189
2302
|
|
|
2190
2303
|
// Remove tracking entries
|
|
@@ -2457,4 +2570,74 @@ void DhtClient::set_data_directory(const std::string& directory) {
|
|
|
2457
2570
|
LOG_DHT_DEBUG("Data directory set to: " << data_directory_);
|
|
2458
2571
|
}
|
|
2459
2572
|
|
|
2573
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
2574
|
+
// ============================================================================
|
|
2575
|
+
// SPIDER MODE IMPLEMENTATION
|
|
2576
|
+
// ============================================================================
|
|
2577
|
+
|
|
2578
|
+
void DhtClient::set_spider_mode(bool enable) {
|
|
2579
|
+
spider_mode_.store(enable);
|
|
2580
|
+
if (enable) {
|
|
2581
|
+
LOG_DHT_INFO("Spider mode ENABLED - aggressive node discovery active");
|
|
2582
|
+
} else {
|
|
2583
|
+
LOG_DHT_INFO("Spider mode DISABLED - normal DHT operation");
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
void DhtClient::set_spider_announce_callback(SpiderAnnounceCallback callback) {
|
|
2588
|
+
std::lock_guard<std::mutex> lock(spider_callbacks_mutex_);
|
|
2589
|
+
spider_announce_callback_ = std::move(callback);
|
|
2590
|
+
LOG_DHT_DEBUG("Spider announce callback set");
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
void DhtClient::set_spider_ignore(bool ignore) {
|
|
2594
|
+
spider_ignore_.store(ignore);
|
|
2595
|
+
LOG_DHT_DEBUG("Spider ignore mode set to " << (ignore ? "true" : "false"));
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
void DhtClient::spider_walk() {
|
|
2599
|
+
if (!running_) {
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
// Get a random node from the routing table and send find_node
|
|
2604
|
+
DhtNode target_node;
|
|
2605
|
+
bool found = false;
|
|
2606
|
+
|
|
2607
|
+
{
|
|
2608
|
+
std::lock_guard<std::mutex> lock(routing_table_mutex_);
|
|
2609
|
+
|
|
2610
|
+
// Collect all nodes from non-empty buckets
|
|
2611
|
+
std::vector<DhtNode*> all_nodes;
|
|
2612
|
+
for (auto& bucket : routing_table_) {
|
|
2613
|
+
for (auto& node : bucket) {
|
|
2614
|
+
all_nodes.push_back(&node);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
if (!all_nodes.empty()) {
|
|
2619
|
+
// Pick a random node
|
|
2620
|
+
static std::random_device rd;
|
|
2621
|
+
static std::mt19937 gen(rd());
|
|
2622
|
+
std::uniform_int_distribution<size_t> dis(0, all_nodes.size() - 1);
|
|
2623
|
+
target_node = *all_nodes[dis(gen)];
|
|
2624
|
+
found = true;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
if (found) {
|
|
2629
|
+
// Generate a random target ID for find_node (like spider.js does)
|
|
2630
|
+
NodeId random_target = generate_node_id();
|
|
2631
|
+
send_krpc_find_node(target_node.peer, random_target);
|
|
2632
|
+
LOG_DHT_DEBUG("Spider walk: sent find_node to " << target_node.peer.ip << ":" << target_node.peer.port);
|
|
2633
|
+
} else {
|
|
2634
|
+
// No nodes in routing table, bootstrap
|
|
2635
|
+
LOG_DHT_DEBUG("Spider walk: no nodes in routing table, re-bootstrapping");
|
|
2636
|
+
for (const auto& bootstrap : get_default_bootstrap_nodes()) {
|
|
2637
|
+
send_krpc_find_node(bootstrap, node_id_);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
#endif // RATS_SEARCH_FEATURES
|
|
2642
|
+
|
|
2460
2643
|
} // namespace librats
|
package/native-src/src/dht.h
CHANGED
|
@@ -76,7 +76,7 @@ struct DhtNode {
|
|
|
76
76
|
// Number of consecutive failures (0xff = never pinged, 0 = confirmed good)
|
|
77
77
|
uint8_t fail_count = 0xff;
|
|
78
78
|
|
|
79
|
-
DhtNode() : last_seen(std::chrono::steady_clock::now()) {}
|
|
79
|
+
DhtNode() : id(), last_seen(std::chrono::steady_clock::now()) {}
|
|
80
80
|
DhtNode(const NodeId& id, const Peer& peer)
|
|
81
81
|
: id(id), peer(peer), last_seen(std::chrono::steady_clock::now()) {}
|
|
82
82
|
|
|
@@ -127,6 +127,16 @@ struct DhtNode {
|
|
|
127
127
|
*/
|
|
128
128
|
using PeerDiscoveryCallback = std::function<void(const std::vector<Peer>& peers, const InfoHash& info_hash)>;
|
|
129
129
|
|
|
130
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
131
|
+
/**
|
|
132
|
+
* Spider mode callback
|
|
133
|
+
* Called when a peer announces they have a torrent (announce_peer request received)
|
|
134
|
+
* @param info_hash The info hash being announced
|
|
135
|
+
* @param peer The peer that is announcing (with the port they specified)
|
|
136
|
+
*/
|
|
137
|
+
using SpiderAnnounceCallback = std::function<void(const InfoHash& info_hash, const Peer& peer)>;
|
|
138
|
+
#endif // RATS_SEARCH_FEATURES
|
|
139
|
+
|
|
130
140
|
/**
|
|
131
141
|
* Deferred callbacks structure for avoiding deadlock
|
|
132
142
|
* Callbacks are collected while holding the mutex, then invoked after releasing it
|
|
@@ -253,6 +263,54 @@ public:
|
|
|
253
263
|
*/
|
|
254
264
|
bool is_running() const { return running_; }
|
|
255
265
|
|
|
266
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// SPIDER MODE - Aggressive node discovery and announce collection
|
|
269
|
+
// ============================================================================
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Enable spider mode
|
|
273
|
+
* In spider mode:
|
|
274
|
+
* - Nodes are added to routing table without ping verification
|
|
275
|
+
* - All announce_peer requests from other peers are collected via callback
|
|
276
|
+
* @param enable true to enable spider mode, false to disable
|
|
277
|
+
*/
|
|
278
|
+
void set_spider_mode(bool enable);
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Check if spider mode is enabled
|
|
282
|
+
* @return true if spider mode is enabled
|
|
283
|
+
*/
|
|
284
|
+
bool is_spider_mode() const { return spider_mode_; }
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Set callback for announce_peer requests (spider mode)
|
|
288
|
+
* Called when other peers announce they have a torrent
|
|
289
|
+
* @param callback The callback to invoke
|
|
290
|
+
*/
|
|
291
|
+
void set_spider_announce_callback(SpiderAnnounceCallback callback);
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Set spider ignore mode - when true, incoming requests are not processed
|
|
295
|
+
* Similar to rate limiting in the original spider implementation
|
|
296
|
+
* @param ignore true to ignore incoming requests, false to process them
|
|
297
|
+
*/
|
|
298
|
+
void set_spider_ignore(bool ignore);
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Check if spider ignore mode is enabled
|
|
302
|
+
* @return true if ignoring incoming requests
|
|
303
|
+
*/
|
|
304
|
+
bool is_spider_ignoring() const { return spider_ignore_; }
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Trigger a single spider walk iteration
|
|
308
|
+
* Sends find_node to a random node from the routing table
|
|
309
|
+
* Should be called from external loop at desired frequency
|
|
310
|
+
*/
|
|
311
|
+
void spider_walk();
|
|
312
|
+
#endif // RATS_SEARCH_FEATURES
|
|
313
|
+
|
|
256
314
|
/**
|
|
257
315
|
* Get default BitTorrent DHT bootstrap nodes
|
|
258
316
|
* @return Vector of bootstrap nodes
|
|
@@ -400,6 +458,14 @@ private:
|
|
|
400
458
|
std::condition_variable shutdown_cv_;
|
|
401
459
|
std::mutex shutdown_mutex_; // Lock order: 7 (can be locked independently)
|
|
402
460
|
|
|
461
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
462
|
+
// Spider mode state
|
|
463
|
+
std::atomic<bool> spider_mode_{false};
|
|
464
|
+
std::atomic<bool> spider_ignore_{false}; // When true, ignore incoming requests
|
|
465
|
+
SpiderAnnounceCallback spider_announce_callback_;
|
|
466
|
+
std::mutex spider_callbacks_mutex_; // Protects spider callbacks
|
|
467
|
+
#endif // RATS_SEARCH_FEATURES
|
|
468
|
+
|
|
403
469
|
// Helper functions
|
|
404
470
|
void network_loop();
|
|
405
471
|
void maintenance_loop();
|
|
@@ -423,7 +489,7 @@ private:
|
|
|
423
489
|
void send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash);
|
|
424
490
|
void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token);
|
|
425
491
|
|
|
426
|
-
void add_node(const DhtNode& node, bool confirmed = true);
|
|
492
|
+
void add_node(const DhtNode& node, bool confirmed = true, bool no_verify = false);
|
|
427
493
|
std::vector<DhtNode> find_closest_nodes(const NodeId& target, size_t count = K_BUCKET_SIZE);
|
|
428
494
|
std::vector<DhtNode> find_closest_nodes_unlocked(const NodeId& target, size_t count = K_BUCKET_SIZE);
|
|
429
495
|
int get_bucket_index(const NodeId& id);
|
package/native-src/src/ice.cpp
CHANGED
|
@@ -477,8 +477,8 @@ StunAddress NatTypeDetector::get_mapped_address_different_port(const std::string
|
|
|
477
477
|
|
|
478
478
|
IceAgent::IceAgent(IceRole role, const IceConfig& config)
|
|
479
479
|
: role_(role), config_(config), running_(false), state_(IceConnectionState::NEW),
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
selected_pair_(IceCandidate(), IceCandidate()),
|
|
481
|
+
udp_socket_(INVALID_SOCKET_VALUE), tcp_socket_(INVALID_SOCKET_VALUE) {
|
|
482
482
|
|
|
483
483
|
// Initialize NAT detector
|
|
484
484
|
nat_detector_ = std::make_unique<NatTypeDetector>();
|
package/native-src/src/krpc.h
CHANGED
|
@@ -50,7 +50,7 @@ struct KrpcNode {
|
|
|
50
50
|
std::string ip;
|
|
51
51
|
uint16_t port;
|
|
52
52
|
|
|
53
|
-
KrpcNode() : port(0) {}
|
|
53
|
+
KrpcNode() : id(), port(0) {}
|
|
54
54
|
KrpcNode(const NodeId& node_id, const std::string& ip_addr, uint16_t port_num)
|
|
55
55
|
: id(node_id), ip(ip_addr), port(port_num) {}
|
|
56
56
|
};
|
|
@@ -79,7 +79,7 @@ struct KrpcMessage {
|
|
|
79
79
|
KrpcErrorCode error_code;
|
|
80
80
|
std::string error_message;
|
|
81
81
|
|
|
82
|
-
KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), port(0), error_code(KrpcErrorCode::GenericError) {}
|
|
82
|
+
KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), response_id(), error_code(KrpcErrorCode::GenericError) {}
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#include <sstream>
|
|
15
15
|
#include <iomanip>
|
|
16
16
|
#include <stdexcept>
|
|
17
|
+
#include <string_view>
|
|
17
18
|
|
|
18
19
|
#ifdef TESTING
|
|
19
20
|
#define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", "[pointer: " << this << "] " << message)
|
|
@@ -52,15 +53,15 @@ RatsClient::RatsClient(int listen_port, int max_peers, const NatTraversalConfig&
|
|
|
52
53
|
: listen_port_(listen_port),
|
|
53
54
|
bind_address_(bind_address),
|
|
54
55
|
max_peers_(max_peers),
|
|
55
|
-
nat_config_(nat_config),
|
|
56
56
|
server_socket_(INVALID_SOCKET_VALUE),
|
|
57
57
|
running_(false),
|
|
58
|
-
|
|
58
|
+
nat_config_(nat_config),
|
|
59
|
+
data_directory_("."),
|
|
59
60
|
encryption_enabled_(false),
|
|
60
61
|
detected_nat_type_(NatType::UNKNOWN),
|
|
61
|
-
data_directory_("."),
|
|
62
62
|
custom_protocol_name_("rats"),
|
|
63
|
-
custom_protocol_version_("1.0")
|
|
63
|
+
custom_protocol_version_("1.0"),
|
|
64
|
+
auto_discovery_running_(false) {
|
|
64
65
|
// Initialize STUN client
|
|
65
66
|
stun_client_ = std::make_unique<StunClient>();
|
|
66
67
|
|
|
@@ -1253,6 +1254,8 @@ void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
|
1253
1254
|
}
|
|
1254
1255
|
}
|
|
1255
1256
|
|
|
1257
|
+
//common localhost addresses
|
|
1258
|
+
static constexpr std::array<std::string_view,4> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::"};
|
|
1256
1259
|
|
|
1257
1260
|
// Local interface address blocking methods
|
|
1258
1261
|
void RatsClient::initialize_local_addresses() {
|
|
@@ -1264,10 +1267,9 @@ void RatsClient::initialize_local_addresses() {
|
|
|
1264
1267
|
local_interface_addresses_ = network_utils::get_local_interface_addresses();
|
|
1265
1268
|
|
|
1266
1269
|
// Add common localhost addresses if not already present
|
|
1267
|
-
std::vector<std::string> localhost_addrs = {"127.0.0.1", "::1", "0.0.0.0", "::"};
|
|
1268
1270
|
for (const auto& addr : localhost_addrs) {
|
|
1269
1271
|
if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
|
|
1270
|
-
local_interface_addresses_.
|
|
1272
|
+
local_interface_addresses_.emplace_back(addr);
|
|
1271
1273
|
}
|
|
1272
1274
|
}
|
|
1273
1275
|
|
|
@@ -1287,10 +1289,9 @@ void RatsClient::refresh_local_addresses() {
|
|
|
1287
1289
|
local_interface_addresses_ = network_utils::get_local_interface_addresses();
|
|
1288
1290
|
|
|
1289
1291
|
// Add common localhost addresses if not already present
|
|
1290
|
-
std::vector<std::string> localhost_addrs = {"127.0.0.1", "::1", "0.0.0.0", "::"};
|
|
1291
1292
|
for (const auto& addr : localhost_addrs) {
|
|
1292
1293
|
if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
|
|
1293
|
-
local_interface_addresses_.
|
|
1294
|
+
local_interface_addresses_.emplace_back(addr);
|
|
1294
1295
|
}
|
|
1295
1296
|
}
|
|
1296
1297
|
|
|
@@ -1808,7 +1809,7 @@ bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::functi
|
|
|
1808
1809
|
// Convert Peer to string addresses for callback
|
|
1809
1810
|
std::vector<std::string> peer_addresses;
|
|
1810
1811
|
for (const auto& peer : peers) {
|
|
1811
|
-
peer_addresses.
|
|
1812
|
+
peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
|
|
1812
1813
|
}
|
|
1813
1814
|
|
|
1814
1815
|
if (callback) {
|
package/native-src/src/librats.h
CHANGED
|
@@ -80,6 +80,7 @@ struct RatsPeer {
|
|
|
80
80
|
|
|
81
81
|
RatsPeer() : handshake_state(HandshakeState::PENDING),
|
|
82
82
|
peer_count(0), encryption_enabled(false), noise_handshake_completed(false),
|
|
83
|
+
remote_static_key(),
|
|
83
84
|
ice_enabled(false), ice_state(IceConnectionState::NEW),
|
|
84
85
|
detected_nat_type(NatType::UNKNOWN), rtt_ms(0), packet_loss_percent(0),
|
|
85
86
|
transport_protocol("UDP") {
|
|
@@ -1453,6 +1454,60 @@ public:
|
|
|
1453
1454
|
*/
|
|
1454
1455
|
void get_torrent_metadata(const std::string& info_hash_hex,
|
|
1455
1456
|
std::function<void(const TorrentInfo&, bool, const std::string&)> callback);
|
|
1457
|
+
|
|
1458
|
+
// =========================================================================
|
|
1459
|
+
// Spider Mode API (requires RATS_SEARCH_FEATURES)
|
|
1460
|
+
// =========================================================================
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* Spider announce callback type
|
|
1464
|
+
* Called when a peer announces they have a torrent (announce_peer request received)
|
|
1465
|
+
* @param info_hash The info hash being announced (as hex string)
|
|
1466
|
+
* @param peer_address The peer that is announcing (ip:port format)
|
|
1467
|
+
*/
|
|
1468
|
+
using SpiderAnnounceCallback = std::function<void(const std::string& info_hash, const std::string& peer_address)>;
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Enable spider mode on DHT
|
|
1472
|
+
* In spider mode:
|
|
1473
|
+
* - Nodes are added to routing table without ping verification
|
|
1474
|
+
* - All announce_peer requests from other peers are collected via callback
|
|
1475
|
+
* @param enable true to enable spider mode, false to disable
|
|
1476
|
+
*/
|
|
1477
|
+
void set_spider_mode(bool enable);
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* Check if spider mode is enabled
|
|
1481
|
+
* @return true if spider mode is enabled
|
|
1482
|
+
*/
|
|
1483
|
+
bool is_spider_mode() const;
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Set callback for announce_peer requests (spider mode)
|
|
1487
|
+
* Called when other peers announce they have a torrent
|
|
1488
|
+
* @param callback The callback to invoke
|
|
1489
|
+
*/
|
|
1490
|
+
void set_spider_announce_callback(SpiderAnnounceCallback callback);
|
|
1491
|
+
|
|
1492
|
+
/**
|
|
1493
|
+
* Set spider ignore mode - when true, incoming requests are not processed
|
|
1494
|
+
* Useful for rate limiting in spider mode
|
|
1495
|
+
* @param ignore true to ignore incoming requests, false to process them
|
|
1496
|
+
*/
|
|
1497
|
+
void set_spider_ignore(bool ignore);
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Check if spider ignore mode is enabled
|
|
1501
|
+
* @return true if ignoring incoming requests
|
|
1502
|
+
*/
|
|
1503
|
+
bool is_spider_ignoring() const;
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* Trigger a single spider walk iteration
|
|
1507
|
+
* Sends find_node to a random node from the routing table
|
|
1508
|
+
* Call this periodically at desired frequency to expand routing table
|
|
1509
|
+
*/
|
|
1510
|
+
void spider_walk();
|
|
1456
1511
|
#endif // RATS_SEARCH_FEATURES
|
|
1457
1512
|
|
|
1458
1513
|
private:
|