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 CHANGED
@@ -2,6 +2,7 @@
2
2
  "targets": [
3
3
  {
4
4
  "target_name": "librats",
5
+ "product_name": "librats",
5
6
  "sources": [
6
7
  "src/librats_node.cpp"
7
8
  ],
@@ -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
@@ -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 (int i = 0; i < NODE_ID_SIZE; ++i) {
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, message.port);
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 (int j = byte_index; j < NODE_ID_SIZE; ++j) {
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
- LOG_DHT_WARN("Ping verification response from unexpected node " << node_id_to_hex(responder_id)
2186
- << " at " << responder.ip << ":" << responder.port
2187
- << " (expected old node " << node_id_to_hex(verification.old_node.id) << ")");
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
@@ -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);
@@ -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
- udp_socket_(INVALID_SOCKET_VALUE), tcp_socket_(INVALID_SOCKET_VALUE),
481
- selected_pair_(IceCandidate(), IceCandidate()) {
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>();
@@ -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
- auto_discovery_running_(false),
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_.push_back(addr);
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_.push_back(addr);
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.push_back(peer.ip + ":" + std::to_string(peer.port));
1812
+ peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
1812
1813
  }
1813
1814
 
1814
1815
  if (callback) {
@@ -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: