librats 0.5.3 → 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
  ],
@@ -147,6 +147,7 @@ set(LIBRARY_SOURCES
147
147
  src/fs.cpp
148
148
  src/fs.h
149
149
  src/logger.h
150
+ src/logger.cpp
150
151
  src/noise.cpp
151
152
  src/noise.h
152
153
  src/encrypted_socket.cpp
@@ -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
 
@@ -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)
@@ -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:
@@ -162,6 +162,71 @@ void RatsClient::get_torrent_metadata(const std::string& info_hash_hex,
162
162
  bittorrent_client_->get_torrent_metadata_by_hash(info_hash_hex, callback);
163
163
  }
164
164
 
165
+ //=============================================================================
166
+ // Spider Mode API Implementation (requires RATS_SEARCH_FEATURES)
167
+ //=============================================================================
168
+
169
+ void RatsClient::set_spider_mode(bool enable) {
170
+ if (!dht_client_) {
171
+ LOG_CLIENT_WARN("DHT client not available, cannot set spider mode");
172
+ return;
173
+ }
174
+
175
+ dht_client_->set_spider_mode(enable);
176
+ LOG_CLIENT_INFO("Spider mode " << (enable ? "enabled" : "disabled"));
177
+ }
178
+
179
+ bool RatsClient::is_spider_mode() const {
180
+ if (!dht_client_) {
181
+ return false;
182
+ }
183
+ return dht_client_->is_spider_mode();
184
+ }
185
+
186
+ void RatsClient::set_spider_announce_callback(SpiderAnnounceCallback callback) {
187
+ if (!dht_client_) {
188
+ LOG_CLIENT_WARN("DHT client not available, cannot set spider callback");
189
+ return;
190
+ }
191
+
192
+ // Wrap the callback to convert types from DhtClient format to RatsClient format
193
+ dht_client_->set_spider_announce_callback(
194
+ [callback](const InfoHash& info_hash, const Peer& peer) {
195
+ if (callback) {
196
+ std::string info_hash_hex = node_id_to_hex(info_hash);
197
+ std::string peer_address = peer.ip + ":" + std::to_string(peer.port);
198
+ callback(info_hash_hex, peer_address);
199
+ }
200
+ });
201
+
202
+ LOG_CLIENT_DEBUG("Spider announce callback set");
203
+ }
204
+
205
+ void RatsClient::set_spider_ignore(bool ignore) {
206
+ if (!dht_client_) {
207
+ LOG_CLIENT_WARN("DHT client not available, cannot set spider ignore");
208
+ return;
209
+ }
210
+
211
+ dht_client_->set_spider_ignore(ignore);
212
+ LOG_CLIENT_DEBUG("Spider ignore mode " << (ignore ? "enabled" : "disabled"));
213
+ }
214
+
215
+ bool RatsClient::is_spider_ignoring() const {
216
+ if (!dht_client_) {
217
+ return false;
218
+ }
219
+ return dht_client_->is_spider_ignoring();
220
+ }
221
+
222
+ void RatsClient::spider_walk() {
223
+ if (!dht_client_) {
224
+ return;
225
+ }
226
+
227
+ dht_client_->spider_walk();
228
+ }
229
+
165
230
  }
166
231
 
167
232
  #endif // RATS_SEARCH_FEATURES
@@ -0,0 +1,11 @@
1
+ #include "logger.h"
2
+
3
+ namespace librats {
4
+
5
+ Logger& Logger::getInstance() {
6
+ static Logger instance;
7
+ return instance;
8
+ }
9
+
10
+ } // namespace librats
11
+
@@ -34,11 +34,8 @@ enum class LogLevel {
34
34
 
35
35
  class Logger {
36
36
  public:
37
- // Singleton pattern
38
- static Logger& getInstance() {
39
- static Logger instance;
40
- return instance;
41
- }
37
+ // Singleton pattern - implementation in logger.cpp to ensure single instance across TUs
38
+ static Logger& getInstance();
42
39
 
43
40
  // Delete copy constructor and assignment operator
44
41
  Logger(const Logger&) = delete;
@@ -31,7 +31,8 @@ MdnsClient::MdnsClient(const std::string& service_instance_name, uint16_t servic
31
31
  announcing_(false),
32
32
  discovering_(false),
33
33
  announcement_interval_(std::chrono::seconds(60)),
34
- query_interval_(std::chrono::seconds(30)) {
34
+ query_interval_(std::chrono::seconds(30)),
35
+ rng_(std::random_device{}()) {
35
36
 
36
37
  // Get local network information
37
38
  local_hostname_ = get_local_hostname();
@@ -336,10 +337,18 @@ bool MdnsClient::join_multicast_group() {
336
337
  return false;
337
338
  }
338
339
 
339
- // Join IPv4 multicast group
340
+ // Get local interface address for multicast binding
341
+ in_addr local_interface{};
342
+ if (local_ip_address_.empty() || local_ip_address_ == "127.0.0.1") {
343
+ local_interface.s_addr = INADDR_ANY;
344
+ } else {
345
+ inet_pton(AF_INET, local_ip_address_.c_str(), &local_interface);
346
+ }
347
+
348
+ // Join IPv4 multicast group on specific interface
340
349
  ip_mreq mreq{};
341
350
  inet_pton(AF_INET, MDNS_MULTICAST_IPv4.c_str(), &mreq.imr_multiaddr);
342
- mreq.imr_interface.s_addr = INADDR_ANY;
351
+ mreq.imr_interface = local_interface;
343
352
 
344
353
  if (setsockopt(multicast_socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP,
345
354
  reinterpret_cast<const char*>(&mreq), sizeof(mreq)) < 0) {
@@ -351,6 +360,16 @@ bool MdnsClient::join_multicast_group() {
351
360
  return false;
352
361
  }
353
362
 
363
+ // Set outgoing multicast interface (critical for Windows!)
364
+ if (setsockopt(multicast_socket_, IPPROTO_IP, IP_MULTICAST_IF,
365
+ reinterpret_cast<const char*>(&local_interface), sizeof(local_interface)) < 0) {
366
+ #ifdef _WIN32
367
+ LOG_MDNS_WARN("Failed to set multicast interface (error: " << WSAGetLastError() << ")");
368
+ #else
369
+ LOG_MDNS_WARN("Failed to set multicast interface (error: " << strerror(errno) << ")");
370
+ #endif
371
+ }
372
+
354
373
  // Set multicast TTL
355
374
  int ttl = 255;
356
375
  if (setsockopt(multicast_socket_, IPPROTO_IP, IP_MULTICAST_TTL,
@@ -365,7 +384,7 @@ bool MdnsClient::join_multicast_group() {
365
384
  LOG_MDNS_WARN("Failed to disable multicast loopback");
366
385
  }
367
386
 
368
- LOG_MDNS_DEBUG("Joined IPv4 multicast group: " << MDNS_MULTICAST_IPv4);
387
+ LOG_MDNS_INFO("Joined IPv4 multicast group: " << MDNS_MULTICAST_IPv4 << " on interface " << local_ip_address_);
369
388
  return true;
370
389
  }
371
390
 
@@ -374,9 +393,17 @@ bool MdnsClient::leave_multicast_group() {
374
393
  return false;
375
394
  }
376
395
 
396
+ // Use same interface as when joining
397
+ in_addr local_interface{};
398
+ if (local_ip_address_.empty() || local_ip_address_ == "127.0.0.1") {
399
+ local_interface.s_addr = INADDR_ANY;
400
+ } else {
401
+ inet_pton(AF_INET, local_ip_address_.c_str(), &local_interface);
402
+ }
403
+
377
404
  ip_mreq mreq{};
378
405
  inet_pton(AF_INET, MDNS_MULTICAST_IPv4.c_str(), &mreq.imr_multiaddr);
379
- mreq.imr_interface.s_addr = INADDR_ANY;
406
+ mreq.imr_interface = local_interface;
380
407
 
381
408
  if (setsockopt(multicast_socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP,
382
409
  reinterpret_cast<const char*>(&mreq), sizeof(mreq)) < 0) {
@@ -546,7 +573,7 @@ void MdnsClient::process_query(const DnsMessage& query, const std::string& sende
546
573
  bool should_respond = false;
547
574
 
548
575
  // Check if the question is asking for our service type
549
- if (question.name == service_type_ && question.type == DnsRecordType::PTR) {
576
+ if (is_librats_service(question.name) && question.type == DnsRecordType::PTR) {
550
577
  should_respond = true;
551
578
  }
552
579
 
@@ -563,10 +590,8 @@ void MdnsClient::process_query(const DnsMessage& query, const std::string& sende
563
590
  std::vector<uint8_t> packet = serialize_dns_message(response);
564
591
 
565
592
  // Add a small random delay to avoid response collisions
566
- std::random_device rd;
567
- std::mt19937 gen(rd());
568
593
  std::uniform_int_distribution<> dis(20, 120);
569
- std::this_thread::sleep_for(std::chrono::milliseconds(dis(gen)));
594
+ std::this_thread::sleep_for(std::chrono::milliseconds(dis(rng_)));
570
595
 
571
596
  send_multicast_packet(packet);
572
597
  }
@@ -580,6 +605,12 @@ void MdnsClient::process_response(const DnsMessage& response, const std::string&
580
605
  }
581
606
 
582
607
  void MdnsClient::extract_service_from_response(const DnsMessage& response, const std::string& sender_ip) {
608
+ // Validate raw_packet is available for DNS compression resolution
609
+ if (response.raw_packet.empty()) {
610
+ LOG_MDNS_WARN("Cannot extract service: raw_packet is empty");
611
+ return;
612
+ }
613
+
583
614
  MdnsService service;
584
615
  service.ip_address = sender_ip;
585
616
  service.last_seen = std::chrono::steady_clock::now();
@@ -590,23 +621,31 @@ void MdnsClient::extract_service_from_response(const DnsMessage& response, const
590
621
 
591
622
  // Process all answer records
592
623
  for (const auto& record : response.answers) {
593
- if (record.type == DnsRecordType::PTR && is_librats_service(record.name)) {
594
- // Extract service instance name from PTR record data
595
- size_t offset = 0;
596
- service.service_name = read_dns_name(record.data, offset);
597
- has_ptr = true;
598
- LOG_MDNS_DEBUG("Found PTR record: " << service.service_name);
624
+ LOG_MDNS_DEBUG("Processing record: type=" << static_cast<int>(record.type) << " name=" << record.name);
625
+
626
+ if (record.type == DnsRecordType::PTR) {
627
+ // Check if this is our service type
628
+ if (is_librats_service(record.name)) {
629
+ // Extract service instance name from PTR record data
630
+ size_t offset = record.data_offset_in_packet;
631
+ service.service_name = read_dns_name(response.raw_packet, offset);
632
+ has_ptr = true;
633
+ LOG_MDNS_DEBUG("Found PTR record: " << service.service_name);
634
+ } else {
635
+ LOG_MDNS_DEBUG("Skipping PTR record - not our service type: " << record.name << " (expected: " << service_type_ << ")");
636
+ }
599
637
  }
600
638
  else if (record.type == DnsRecordType::SRV) {
601
- // Extract SRV record data
639
+ // Extract SRV record data using full packet for DNS compression
602
640
  uint16_t priority, weight;
603
- if (decode_srv_record(record.data, priority, weight, service.port, service.host_name)) {
641
+ if (decode_srv_record(response.raw_packet, record.data_offset_in_packet,
642
+ priority, weight, service.port, service.host_name)) {
604
643
  has_srv = true;
605
644
  LOG_MDNS_DEBUG("Found SRV record: " << service.host_name << ":" << service.port);
606
645
  }
607
646
  }
608
647
  else if (record.type == DnsRecordType::TXT) {
609
- // Extract TXT record data
648
+ // TXT records don't use DNS compression, use data directly
610
649
  service.txt_records = decode_txt_record(record.data);
611
650
  has_txt = true;
612
651
  LOG_MDNS_DEBUG("Found TXT record with " << service.txt_records.size() << " entries");
@@ -633,7 +672,19 @@ void MdnsClient::extract_service_from_response(const DnsMessage& response, const
633
672
  }
634
673
 
635
674
  bool MdnsClient::is_librats_service(const std::string& service_name) const {
636
- return service_name == service_type_;
675
+ // Normalize both names - remove trailing dots for comparison
676
+ std::string name1 = service_name;
677
+ std::string name2 = service_type_;
678
+
679
+ // Remove trailing dots
680
+ while (!name1.empty() && name1.back() == '.') name1.pop_back();
681
+ while (!name2.empty() && name2.back() == '.') name2.pop_back();
682
+
683
+ // Case-insensitive comparison
684
+ std::transform(name1.begin(), name1.end(), name1.begin(), ::tolower);
685
+ std::transform(name2.begin(), name2.end(), name2.begin(), ::tolower);
686
+
687
+ return name1 == name2;
637
688
  }
638
689
 
639
690
  void MdnsClient::add_or_update_service(const MdnsService& service) {
@@ -813,11 +864,42 @@ std::vector<uint8_t> MdnsClient::serialize_dns_message(const DnsMessage& message
813
864
  return buffer;
814
865
  }
815
866
 
867
+ bool MdnsClient::read_resource_records(const std::vector<uint8_t>& data, size_t& offset,
868
+ uint16_t count, std::vector<DnsResourceRecord>& records) {
869
+ records.clear();
870
+ records.reserve(count);
871
+
872
+ for (uint16_t i = 0; i < count; ++i) {
873
+ DnsResourceRecord record;
874
+ record.name = read_dns_name(data, offset);
875
+ record.type = static_cast<DnsRecordType>(read_uint16(data, offset));
876
+ record.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
877
+ record.ttl = read_uint32(data, offset);
878
+ uint16_t data_length = read_uint16(data, offset);
879
+
880
+ if (offset + data_length > data.size()) {
881
+ return false;
882
+ }
883
+
884
+ // Store offset for DNS compression resolution
885
+ record.data_offset_in_packet = offset;
886
+ record.data.assign(data.begin() + offset, data.begin() + offset + data_length);
887
+ offset += data_length;
888
+
889
+ records.push_back(std::move(record));
890
+ }
891
+
892
+ return true;
893
+ }
894
+
816
895
  bool MdnsClient::deserialize_dns_message(const std::vector<uint8_t>& data, DnsMessage& message) {
817
896
  if (data.size() < 12) { // Minimum DNS header size
818
897
  return false;
819
898
  }
820
899
 
900
+ // Store raw packet for DNS compression resolution
901
+ message.raw_packet = data;
902
+
821
903
  size_t offset = 0;
822
904
 
823
905
  try {
@@ -831,62 +913,24 @@ bool MdnsClient::deserialize_dns_message(const std::vector<uint8_t>& data, DnsMe
831
913
 
832
914
  // Read questions
833
915
  message.questions.clear();
916
+ message.questions.reserve(message.header.question_count);
834
917
  for (uint16_t i = 0; i < message.header.question_count; ++i) {
835
918
  DnsQuestion question;
836
919
  question.name = read_dns_name(data, offset);
837
920
  question.type = static_cast<DnsRecordType>(read_uint16(data, offset));
838
921
  question.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
839
- message.questions.push_back(question);
922
+ message.questions.push_back(std::move(question));
840
923
  }
841
924
 
842
- // Read answers
843
- message.answers.clear();
844
- for (uint16_t i = 0; i < message.header.answer_count; ++i) {
845
- DnsResourceRecord record;
846
- record.name = read_dns_name(data, offset);
847
- record.type = static_cast<DnsRecordType>(read_uint16(data, offset));
848
- record.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
849
- record.ttl = read_uint32(data, offset);
850
- uint16_t data_length = read_uint16(data, offset);
851
-
852
- if (offset + data_length > data.size()) {
853
- return false;
854
- }
855
-
856
- record.data.assign(data.begin() + offset, data.begin() + offset + data_length);
857
- offset += data_length;
858
-
859
- message.answers.push_back(record);
925
+ // Read answers, authorities, and additionals
926
+ if (!read_resource_records(data, offset, message.header.answer_count, message.answers)) {
927
+ return false;
860
928
  }
861
-
862
- // Read authorities (skip for simplicity)
863
- for (uint16_t i = 0; i < message.header.authority_count; ++i) {
864
- read_dns_name(data, offset); // name
865
- read_uint16(data, offset); // type
866
- read_uint16(data, offset); // class
867
- read_uint32(data, offset); // ttl
868
- uint16_t data_length = read_uint16(data, offset);
869
- offset += data_length;
929
+ if (!read_resource_records(data, offset, message.header.authority_count, message.authorities)) {
930
+ return false;
870
931
  }
871
-
872
- // Read additionals
873
- message.additionals.clear();
874
- for (uint16_t i = 0; i < message.header.additional_count; ++i) {
875
- DnsResourceRecord record;
876
- record.name = read_dns_name(data, offset);
877
- record.type = static_cast<DnsRecordType>(read_uint16(data, offset));
878
- record.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
879
- record.ttl = read_uint32(data, offset);
880
- uint16_t data_length = read_uint16(data, offset);
881
-
882
- if (offset + data_length > data.size()) {
883
- return false;
884
- }
885
-
886
- record.data.assign(data.begin() + offset, data.begin() + offset + data_length);
887
- offset += data_length;
888
-
889
- message.additionals.push_back(record);
932
+ if (!read_resource_records(data, offset, message.header.additional_count, message.additionals)) {
933
+ return false;
890
934
  }
891
935
 
892
936
  return true;
@@ -923,8 +967,9 @@ std::string MdnsClient::read_dns_name(const std::vector<uint8_t>& buffer, size_t
923
967
  bool jumped = false;
924
968
  size_t original_offset = offset;
925
969
  int jumps = 0;
970
+ const int max_jumps = 10;
926
971
 
927
- while (offset < buffer.size() && jumps < 10) {
972
+ while (offset < buffer.size() && jumps < max_jumps) {
928
973
  uint8_t length = buffer[offset++];
929
974
 
930
975
  if (length == 0) {
@@ -932,20 +977,39 @@ std::string MdnsClient::read_dns_name(const std::vector<uint8_t>& buffer, size_t
932
977
  }
933
978
 
934
979
  if ((length & 0xC0) == 0xC0) {
935
- // Compression pointer
980
+ // Compression pointer - need one more byte
981
+ if (offset >= buffer.size()) {
982
+ LOG_MDNS_WARN("Truncated DNS compression pointer");
983
+ break;
984
+ }
985
+
936
986
  if (!jumped) {
937
987
  original_offset = offset + 1;
938
988
  jumped = true;
939
989
  }
940
990
 
941
991
  uint16_t pointer = ((length & 0x3F) << 8) | buffer[offset++];
992
+
993
+ // Validate pointer doesn't point beyond buffer or forward (potential loop)
994
+ if (pointer >= buffer.size()) {
995
+ LOG_MDNS_WARN("Invalid DNS compression pointer: " << pointer << " >= " << buffer.size());
996
+ break;
997
+ }
998
+
942
999
  offset = pointer;
943
1000
  jumps++;
944
1001
  continue;
945
1002
  }
946
1003
 
1004
+ // Check for invalid label length (> 63 bytes per RFC 1035)
1005
+ if (length > 63) {
1006
+ LOG_MDNS_WARN("Invalid DNS label length: " << static_cast<int>(length));
1007
+ break;
1008
+ }
1009
+
947
1010
  if (offset + length > buffer.size()) {
948
- break; // Invalid length
1011
+ LOG_MDNS_WARN("DNS label extends beyond buffer");
1012
+ break;
949
1013
  }
950
1014
 
951
1015
  if (!name.empty()) {
@@ -956,6 +1020,10 @@ std::string MdnsClient::read_dns_name(const std::vector<uint8_t>& buffer, size_t
956
1020
  offset += length;
957
1021
  }
958
1022
 
1023
+ if (jumps >= max_jumps) {
1024
+ LOG_MDNS_WARN("Too many DNS compression jumps, possible loop");
1025
+ }
1026
+
959
1027
  if (jumped) {
960
1028
  offset = original_offset;
961
1029
  }
@@ -1064,18 +1132,20 @@ std::vector<uint8_t> MdnsClient::encode_srv_record(uint16_t priority, uint16_t w
1064
1132
  return data;
1065
1133
  }
1066
1134
 
1067
- bool MdnsClient::decode_srv_record(const std::vector<uint8_t>& srv_data, uint16_t& priority, uint16_t& weight, uint16_t& port, std::string& target) {
1068
- if (srv_data.size() < 6) {
1135
+ bool MdnsClient::decode_srv_record(const std::vector<uint8_t>& full_packet, size_t data_offset,
1136
+ uint16_t& priority, uint16_t& weight, uint16_t& port, std::string& target) {
1137
+ if (data_offset + 6 > full_packet.size()) {
1069
1138
  return false;
1070
1139
  }
1071
1140
 
1072
- size_t offset = 0;
1141
+ size_t offset = data_offset;
1073
1142
 
1074
1143
  try {
1075
- priority = read_uint16(srv_data, offset);
1076
- weight = read_uint16(srv_data, offset);
1077
- port = read_uint16(srv_data, offset);
1078
- target = read_dns_name(srv_data, offset);
1144
+ priority = read_uint16(full_packet, offset);
1145
+ weight = read_uint16(full_packet, offset);
1146
+ port = read_uint16(full_packet, offset);
1147
+ // Use full packet for proper DNS compression resolution
1148
+ target = read_dns_name(full_packet, offset);
1079
1149
  return true;
1080
1150
  } catch (const std::exception&) {
1081
1151
  return false;
@@ -1100,14 +1170,18 @@ std::string MdnsClient::get_local_hostname() {
1100
1170
  std::string MdnsClient::get_local_ip_address() {
1101
1171
  auto addresses = librats::network_utils::get_local_interface_addresses();
1102
1172
 
1103
- // Prefer non-loopback IPv4 addresses
1173
+ // Prefer non-loopback, non-APIPA IPv4 addresses
1104
1174
  for (const auto& addr : addresses) {
1105
1175
  if (addr != "127.0.0.1" && addr != "::1" && addr.find(':') == std::string::npos) {
1176
+ // Skip APIPA addresses (169.254.x.x) - these are link-local fallback addresses
1177
+ if (addr.rfind("169.254.", 0) == 0) {
1178
+ continue;
1179
+ }
1106
1180
  return addr;
1107
1181
  }
1108
1182
  }
1109
1183
 
1110
- // Fall back to first available address
1184
+ // Fall back to first available address (even APIPA if that's all we have)
1111
1185
  if (!addresses.empty()) {
1112
1186
  return addresses[0];
1113
1187
  }
@@ -13,6 +13,7 @@
13
13
  #include <map>
14
14
  #include <cstdint>
15
15
  #include <condition_variable>
16
+ #include <random>
16
17
 
17
18
  namespace librats {
18
19
 
@@ -98,10 +99,11 @@ struct DnsResourceRecord {
98
99
  DnsRecordClass record_class;
99
100
  uint32_t ttl;
100
101
  std::vector<uint8_t> data;
102
+ size_t data_offset_in_packet; // Offset of data in original packet for DNS compression
101
103
 
102
- DnsResourceRecord() : type(DnsRecordType::PTR), record_class(DnsRecordClass::CLASS_IN), ttl(120) {}
104
+ DnsResourceRecord() : type(DnsRecordType::PTR), record_class(DnsRecordClass::CLASS_IN), ttl(120), data_offset_in_packet(0) {}
103
105
  DnsResourceRecord(const std::string& n, DnsRecordType t, DnsRecordClass c, uint32_t ttl_val)
104
- : name(n), type(t), record_class(c), ttl(ttl_val) {}
106
+ : name(n), type(t), record_class(c), ttl(ttl_val), data_offset_in_packet(0) {}
105
107
  };
106
108
 
107
109
  // Complete DNS message structure
@@ -111,6 +113,7 @@ struct DnsMessage {
111
113
  std::vector<DnsResourceRecord> answers;
112
114
  std::vector<DnsResourceRecord> authorities;
113
115
  std::vector<DnsResourceRecord> additionals;
116
+ std::vector<uint8_t> raw_packet; // Original packet for DNS compression resolution
114
117
 
115
118
  DnsMessage() = default;
116
119
  };
@@ -187,6 +190,9 @@ private:
187
190
  std::chrono::seconds announcement_interval_;
188
191
  std::chrono::seconds query_interval_;
189
192
 
193
+ // Random number generator for response delays
194
+ mutable std::mt19937 rng_;
195
+
190
196
  // Socket operations
191
197
  bool create_multicast_socket();
192
198
  bool join_multicast_group();
@@ -223,6 +229,8 @@ private:
223
229
  // DNS serialization/deserialization
224
230
  std::vector<uint8_t> serialize_dns_message(const DnsMessage& message);
225
231
  bool deserialize_dns_message(const std::vector<uint8_t>& data, DnsMessage& message);
232
+ bool read_resource_records(const std::vector<uint8_t>& data, size_t& offset,
233
+ uint16_t count, std::vector<DnsResourceRecord>& records);
226
234
 
227
235
  // DNS name compression helpers
228
236
  void write_dns_name(std::vector<uint8_t>& buffer, const std::string& name);
@@ -238,7 +246,8 @@ private:
238
246
 
239
247
  // SRV record helpers
240
248
  std::vector<uint8_t> encode_srv_record(uint16_t priority, uint16_t weight, uint16_t port, const std::string& target);
241
- bool decode_srv_record(const std::vector<uint8_t>& srv_data, uint16_t& priority, uint16_t& weight, uint16_t& port, std::string& target);
249
+ bool decode_srv_record(const std::vector<uint8_t>& full_packet, size_t data_offset,
250
+ uint16_t& priority, uint16_t& weight, uint16_t& port, std::string& target);
242
251
 
243
252
  // Utility functions
244
253
  std::string get_local_hostname();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "librats",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Node.js bindings for librats - A high-performance peer-to-peer networking library",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -190,7 +190,7 @@ public:
190
190
  return exports;
191
191
  }
192
192
 
193
- RatsClient(const Napi::CallbackInfo& info) : Napi::ObjectWrap<RatsClient>(info) {
193
+ RatsClient(const Napi::CallbackInfo& info) : Napi::ObjectWrap<RatsClient>(info), client_(nullptr) {
194
194
  Napi::Env env = info.Env();
195
195
 
196
196
  if (info.Length() < 1 || !info[0].IsNumber()) {
@@ -199,6 +199,10 @@ public:
199
199
  }
200
200
 
201
201
  int port = info[0].As<Napi::Number>().Int32Value();
202
+ if (port < 0 || port > 65535) {
203
+ Napi::RangeError::New(env, "Port number must be between 0 and 65535").ThrowAsJavaScriptException();
204
+ return;
205
+ }
202
206
  client_ = rats_create(port);
203
207
 
204
208
  if (!client_) {
@@ -208,7 +212,7 @@ public:
208
212
  }
209
213
 
210
214
  ~RatsClient() {
211
- if (client_) {
215
+ if (client_ != nullptr) {
212
216
  // Clean up callbacks
213
217
  connection_callbacks.erase(client_);
214
218
  string_callbacks.erase(client_);
@@ -232,7 +236,9 @@ private:
232
236
  }
233
237
 
234
238
  void Stop(const Napi::CallbackInfo& info) {
235
- rats_stop(client_);
239
+ if (client_ != nullptr) {
240
+ rats_stop(client_);
241
+ }
236
242
  }
237
243
 
238
244
  Napi::Value Connect(const Napi::CallbackInfo& info) {