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.
@@ -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
@@ -45,6 +45,11 @@ bool RatsClient::start_mdns_discovery(const std::string& service_instance_name,
45
45
  // Create mDNS client
46
46
  mdns_client_ = std::make_unique<MdnsClient>(instance_name, listen_port_);
47
47
 
48
+ // Set service type based on protocol name (e.g., "_rats-search._tcp.local.")
49
+ std::string protocol_name = get_protocol_name();
50
+ std::string service_type = "_" + protocol_name + "._tcp.local.";
51
+ mdns_client_->set_service_type(service_type);
52
+
48
53
  // Set service discovery callback
49
54
  mdns_client_->set_service_callback([this](const MdnsService& service, bool is_new) {
50
55
  handle_mdns_service_discovery(service, is_new);
@@ -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;
@@ -25,12 +25,14 @@ namespace librats {
25
25
  MdnsClient::MdnsClient(const std::string& service_instance_name, uint16_t service_port)
26
26
  : service_instance_name_(service_instance_name),
27
27
  service_port_(service_port),
28
+ service_type_(LIBRATS_SERVICE_TYPE), // Default service type
28
29
  multicast_socket_(INVALID_SOCKET_VALUE),
29
30
  running_(false),
30
31
  announcing_(false),
31
32
  discovering_(false),
32
33
  announcement_interval_(std::chrono::seconds(60)),
33
- query_interval_(std::chrono::seconds(30)) {
34
+ query_interval_(std::chrono::seconds(30)),
35
+ rng_(std::random_device{}()) {
34
36
 
35
37
  // Get local network information
36
38
  local_hostname_ = get_local_hostname();
@@ -279,6 +281,11 @@ void MdnsClient::set_query_interval(std::chrono::seconds interval) {
279
281
  query_interval_ = interval;
280
282
  }
281
283
 
284
+ void MdnsClient::set_service_type(const std::string& service_type) {
285
+ service_type_ = service_type;
286
+ LOG_MDNS_INFO("Service type set to: " << service_type_);
287
+ }
288
+
282
289
  bool MdnsClient::create_multicast_socket() {
283
290
  multicast_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
284
291
  if (!librats::is_valid_socket(multicast_socket_)) {
@@ -330,10 +337,18 @@ bool MdnsClient::join_multicast_group() {
330
337
  return false;
331
338
  }
332
339
 
333
- // 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
334
349
  ip_mreq mreq{};
335
350
  inet_pton(AF_INET, MDNS_MULTICAST_IPv4.c_str(), &mreq.imr_multiaddr);
336
- mreq.imr_interface.s_addr = INADDR_ANY;
351
+ mreq.imr_interface = local_interface;
337
352
 
338
353
  if (setsockopt(multicast_socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP,
339
354
  reinterpret_cast<const char*>(&mreq), sizeof(mreq)) < 0) {
@@ -345,6 +360,16 @@ bool MdnsClient::join_multicast_group() {
345
360
  return false;
346
361
  }
347
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
+
348
373
  // Set multicast TTL
349
374
  int ttl = 255;
350
375
  if (setsockopt(multicast_socket_, IPPROTO_IP, IP_MULTICAST_TTL,
@@ -359,7 +384,7 @@ bool MdnsClient::join_multicast_group() {
359
384
  LOG_MDNS_WARN("Failed to disable multicast loopback");
360
385
  }
361
386
 
362
- 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_);
363
388
  return true;
364
389
  }
365
390
 
@@ -368,9 +393,17 @@ bool MdnsClient::leave_multicast_group() {
368
393
  return false;
369
394
  }
370
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
+
371
404
  ip_mreq mreq{};
372
405
  inet_pton(AF_INET, MDNS_MULTICAST_IPv4.c_str(), &mreq.imr_multiaddr);
373
- mreq.imr_interface.s_addr = INADDR_ANY;
406
+ mreq.imr_interface = local_interface;
374
407
 
375
408
  if (setsockopt(multicast_socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP,
376
409
  reinterpret_cast<const char*>(&mreq), sizeof(mreq)) < 0) {
@@ -540,7 +573,7 @@ void MdnsClient::process_query(const DnsMessage& query, const std::string& sende
540
573
  bool should_respond = false;
541
574
 
542
575
  // Check if the question is asking for our service type
543
- if (question.name == LIBRATS_SERVICE_TYPE && question.type == DnsRecordType::PTR) {
576
+ if (is_librats_service(question.name) && question.type == DnsRecordType::PTR) {
544
577
  should_respond = true;
545
578
  }
546
579
 
@@ -557,10 +590,8 @@ void MdnsClient::process_query(const DnsMessage& query, const std::string& sende
557
590
  std::vector<uint8_t> packet = serialize_dns_message(response);
558
591
 
559
592
  // Add a small random delay to avoid response collisions
560
- std::random_device rd;
561
- std::mt19937 gen(rd());
562
593
  std::uniform_int_distribution<> dis(20, 120);
563
- std::this_thread::sleep_for(std::chrono::milliseconds(dis(gen)));
594
+ std::this_thread::sleep_for(std::chrono::milliseconds(dis(rng_)));
564
595
 
565
596
  send_multicast_packet(packet);
566
597
  }
@@ -574,6 +605,12 @@ void MdnsClient::process_response(const DnsMessage& response, const std::string&
574
605
  }
575
606
 
576
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
+
577
614
  MdnsService service;
578
615
  service.ip_address = sender_ip;
579
616
  service.last_seen = std::chrono::steady_clock::now();
@@ -584,23 +621,31 @@ void MdnsClient::extract_service_from_response(const DnsMessage& response, const
584
621
 
585
622
  // Process all answer records
586
623
  for (const auto& record : response.answers) {
587
- if (record.type == DnsRecordType::PTR && is_librats_service(record.name)) {
588
- // Extract service instance name from PTR record data
589
- size_t offset = 0;
590
- service.service_name = read_dns_name(record.data, offset);
591
- has_ptr = true;
592
- 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
+ }
593
637
  }
594
638
  else if (record.type == DnsRecordType::SRV) {
595
- // Extract SRV record data
639
+ // Extract SRV record data using full packet for DNS compression
596
640
  uint16_t priority, weight;
597
- 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)) {
598
643
  has_srv = true;
599
644
  LOG_MDNS_DEBUG("Found SRV record: " << service.host_name << ":" << service.port);
600
645
  }
601
646
  }
602
647
  else if (record.type == DnsRecordType::TXT) {
603
- // Extract TXT record data
648
+ // TXT records don't use DNS compression, use data directly
604
649
  service.txt_records = decode_txt_record(record.data);
605
650
  has_txt = true;
606
651
  LOG_MDNS_DEBUG("Found TXT record with " << service.txt_records.size() << " entries");
@@ -627,7 +672,19 @@ void MdnsClient::extract_service_from_response(const DnsMessage& response, const
627
672
  }
628
673
 
629
674
  bool MdnsClient::is_librats_service(const std::string& service_name) const {
630
- return service_name == LIBRATS_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;
631
688
  }
632
689
 
633
690
  void MdnsClient::add_or_update_service(const MdnsService& service) {
@@ -668,8 +725,8 @@ DnsMessage MdnsClient::create_query_message() {
668
725
  query.header.flags = static_cast<uint16_t>(MdnsFlags::QUERY);
669
726
  query.header.question_count = 1;
670
727
 
671
- // Add question for librats services
672
- DnsQuestion question(LIBRATS_SERVICE_TYPE, DnsRecordType::PTR, DnsRecordClass::CLASS_IN);
728
+ // Add question for services with our service type
729
+ DnsQuestion question(service_type_, DnsRecordType::PTR, DnsRecordClass::CLASS_IN);
673
730
  query.questions.push_back(question);
674
731
 
675
732
  return query;
@@ -689,7 +746,7 @@ DnsMessage MdnsClient::create_announcement_message() {
689
746
  }
690
747
 
691
748
  // Create PTR record
692
- DnsResourceRecord ptr_record = create_ptr_record(LIBRATS_SERVICE_TYPE, our_service_name);
749
+ DnsResourceRecord ptr_record = create_ptr_record(service_type_, our_service_name);
693
750
  announcement.answers.push_back(ptr_record);
694
751
 
695
752
  // Create SRV record
@@ -807,11 +864,42 @@ std::vector<uint8_t> MdnsClient::serialize_dns_message(const DnsMessage& message
807
864
  return buffer;
808
865
  }
809
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
+
810
895
  bool MdnsClient::deserialize_dns_message(const std::vector<uint8_t>& data, DnsMessage& message) {
811
896
  if (data.size() < 12) { // Minimum DNS header size
812
897
  return false;
813
898
  }
814
899
 
900
+ // Store raw packet for DNS compression resolution
901
+ message.raw_packet = data;
902
+
815
903
  size_t offset = 0;
816
904
 
817
905
  try {
@@ -825,62 +913,24 @@ bool MdnsClient::deserialize_dns_message(const std::vector<uint8_t>& data, DnsMe
825
913
 
826
914
  // Read questions
827
915
  message.questions.clear();
916
+ message.questions.reserve(message.header.question_count);
828
917
  for (uint16_t i = 0; i < message.header.question_count; ++i) {
829
918
  DnsQuestion question;
830
919
  question.name = read_dns_name(data, offset);
831
920
  question.type = static_cast<DnsRecordType>(read_uint16(data, offset));
832
921
  question.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
833
- message.questions.push_back(question);
922
+ message.questions.push_back(std::move(question));
834
923
  }
835
924
 
836
- // Read answers
837
- message.answers.clear();
838
- for (uint16_t i = 0; i < message.header.answer_count; ++i) {
839
- DnsResourceRecord record;
840
- record.name = read_dns_name(data, offset);
841
- record.type = static_cast<DnsRecordType>(read_uint16(data, offset));
842
- record.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
843
- record.ttl = read_uint32(data, offset);
844
- uint16_t data_length = read_uint16(data, offset);
845
-
846
- if (offset + data_length > data.size()) {
847
- return false;
848
- }
849
-
850
- record.data.assign(data.begin() + offset, data.begin() + offset + data_length);
851
- offset += data_length;
852
-
853
- 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;
854
928
  }
855
-
856
- // Read authorities (skip for simplicity)
857
- for (uint16_t i = 0; i < message.header.authority_count; ++i) {
858
- read_dns_name(data, offset); // name
859
- read_uint16(data, offset); // type
860
- read_uint16(data, offset); // class
861
- read_uint32(data, offset); // ttl
862
- uint16_t data_length = read_uint16(data, offset);
863
- offset += data_length;
929
+ if (!read_resource_records(data, offset, message.header.authority_count, message.authorities)) {
930
+ return false;
864
931
  }
865
-
866
- // Read additionals
867
- message.additionals.clear();
868
- for (uint16_t i = 0; i < message.header.additional_count; ++i) {
869
- DnsResourceRecord record;
870
- record.name = read_dns_name(data, offset);
871
- record.type = static_cast<DnsRecordType>(read_uint16(data, offset));
872
- record.record_class = static_cast<DnsRecordClass>(read_uint16(data, offset));
873
- record.ttl = read_uint32(data, offset);
874
- uint16_t data_length = read_uint16(data, offset);
875
-
876
- if (offset + data_length > data.size()) {
877
- return false;
878
- }
879
-
880
- record.data.assign(data.begin() + offset, data.begin() + offset + data_length);
881
- offset += data_length;
882
-
883
- message.additionals.push_back(record);
932
+ if (!read_resource_records(data, offset, message.header.additional_count, message.additionals)) {
933
+ return false;
884
934
  }
885
935
 
886
936
  return true;
@@ -917,8 +967,9 @@ std::string MdnsClient::read_dns_name(const std::vector<uint8_t>& buffer, size_t
917
967
  bool jumped = false;
918
968
  size_t original_offset = offset;
919
969
  int jumps = 0;
970
+ const int max_jumps = 10;
920
971
 
921
- while (offset < buffer.size() && jumps < 10) {
972
+ while (offset < buffer.size() && jumps < max_jumps) {
922
973
  uint8_t length = buffer[offset++];
923
974
 
924
975
  if (length == 0) {
@@ -926,20 +977,39 @@ std::string MdnsClient::read_dns_name(const std::vector<uint8_t>& buffer, size_t
926
977
  }
927
978
 
928
979
  if ((length & 0xC0) == 0xC0) {
929
- // 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
+
930
986
  if (!jumped) {
931
987
  original_offset = offset + 1;
932
988
  jumped = true;
933
989
  }
934
990
 
935
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
+
936
999
  offset = pointer;
937
1000
  jumps++;
938
1001
  continue;
939
1002
  }
940
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
+
941
1010
  if (offset + length > buffer.size()) {
942
- break; // Invalid length
1011
+ LOG_MDNS_WARN("DNS label extends beyond buffer");
1012
+ break;
943
1013
  }
944
1014
 
945
1015
  if (!name.empty()) {
@@ -950,6 +1020,10 @@ std::string MdnsClient::read_dns_name(const std::vector<uint8_t>& buffer, size_t
950
1020
  offset += length;
951
1021
  }
952
1022
 
1023
+ if (jumps >= max_jumps) {
1024
+ LOG_MDNS_WARN("Too many DNS compression jumps, possible loop");
1025
+ }
1026
+
953
1027
  if (jumped) {
954
1028
  offset = original_offset;
955
1029
  }
@@ -1058,18 +1132,20 @@ std::vector<uint8_t> MdnsClient::encode_srv_record(uint16_t priority, uint16_t w
1058
1132
  return data;
1059
1133
  }
1060
1134
 
1061
- bool MdnsClient::decode_srv_record(const std::vector<uint8_t>& srv_data, uint16_t& priority, uint16_t& weight, uint16_t& port, std::string& target) {
1062
- 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()) {
1063
1138
  return false;
1064
1139
  }
1065
1140
 
1066
- size_t offset = 0;
1141
+ size_t offset = data_offset;
1067
1142
 
1068
1143
  try {
1069
- priority = read_uint16(srv_data, offset);
1070
- weight = read_uint16(srv_data, offset);
1071
- port = read_uint16(srv_data, offset);
1072
- 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);
1073
1149
  return true;
1074
1150
  } catch (const std::exception&) {
1075
1151
  return false;
@@ -1094,14 +1170,18 @@ std::string MdnsClient::get_local_hostname() {
1094
1170
  std::string MdnsClient::get_local_ip_address() {
1095
1171
  auto addresses = librats::network_utils::get_local_interface_addresses();
1096
1172
 
1097
- // Prefer non-loopback IPv4 addresses
1173
+ // Prefer non-loopback, non-APIPA IPv4 addresses
1098
1174
  for (const auto& addr : addresses) {
1099
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
+ }
1100
1180
  return addr;
1101
1181
  }
1102
1182
  }
1103
1183
 
1104
- // Fall back to first available address
1184
+ // Fall back to first available address (even APIPA if that's all we have)
1105
1185
  if (!addresses.empty()) {
1106
1186
  return addresses[0];
1107
1187
  }
@@ -1120,11 +1200,11 @@ std::string MdnsClient::create_service_instance_name(const std::string& instance
1120
1200
  clean_name = "librats-node";
1121
1201
  }
1122
1202
 
1123
- return clean_name + "." + LIBRATS_SERVICE_TYPE;
1203
+ return clean_name + "." + service_type_;
1124
1204
  }
1125
1205
 
1126
1206
  std::string MdnsClient::extract_instance_name_from_service(const std::string& service_name) {
1127
- size_t pos = service_name.find("." + LIBRATS_SERVICE_TYPE);
1207
+ size_t pos = service_name.find("." + service_type_);
1128
1208
  if (pos != std::string::npos) {
1129
1209
  return service_name.substr(0, pos);
1130
1210
  }
@@ -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
  };
@@ -152,11 +155,13 @@ public:
152
155
  // Configuration
153
156
  void set_announcement_interval(std::chrono::seconds interval);
154
157
  void set_query_interval(std::chrono::seconds interval);
158
+ void set_service_type(const std::string& service_type);
155
159
 
156
160
  private:
157
161
  // Core properties
158
162
  std::string service_instance_name_;
159
163
  uint16_t service_port_;
164
+ std::string service_type_; // Dynamic service type (e.g., "_rats-search._tcp.local.")
160
165
  std::map<std::string, std::string> txt_records_;
161
166
 
162
167
  // Network properties
@@ -185,6 +190,9 @@ private:
185
190
  std::chrono::seconds announcement_interval_;
186
191
  std::chrono::seconds query_interval_;
187
192
 
193
+ // Random number generator for response delays
194
+ mutable std::mt19937 rng_;
195
+
188
196
  // Socket operations
189
197
  bool create_multicast_socket();
190
198
  bool join_multicast_group();
@@ -221,6 +229,8 @@ private:
221
229
  // DNS serialization/deserialization
222
230
  std::vector<uint8_t> serialize_dns_message(const DnsMessage& message);
223
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);
224
234
 
225
235
  // DNS name compression helpers
226
236
  void write_dns_name(std::vector<uint8_t>& buffer, const std::string& name);
@@ -236,7 +246,8 @@ private:
236
246
 
237
247
  // SRV record helpers
238
248
  std::vector<uint8_t> encode_srv_record(uint16_t priority, uint16_t weight, uint16_t port, const std::string& target);
239
- 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);
240
251
 
241
252
  // Utility functions
242
253
  std::string get_local_hostname();
@@ -10,7 +10,9 @@
10
10
  #ifdef _WIN32
11
11
  #include <windows.h>
12
12
  #include <bcrypt.h>
13
- #pragma comment(lib, "bcrypt.lib")
13
+ #ifdef _MSC_VER
14
+ #pragma comment(lib, "bcrypt.lib")
15
+ #endif
14
16
  #else
15
17
  #include <fcntl.h>
16
18
  #include <unistd.h>
@@ -11,7 +11,9 @@
11
11
  #include <sysinfoapi.h>
12
12
  #include <versionhelpers.h>
13
13
  #include <intrin.h>
14
- #pragma comment(lib, "version.lib")
14
+ #ifdef _MSC_VER
15
+ #pragma comment(lib, "version.lib")
16
+ #endif
15
17
  #elif __APPLE__
16
18
  #include <sys/utsname.h>
17
19
  #include <sys/sysctl.h>
@@ -8,7 +8,9 @@
8
8
  #ifdef _WIN32
9
9
  #include <winsock2.h>
10
10
  #include <ws2tcpip.h>
11
- #pragma comment(lib, "ws2_32.lib")
11
+ #ifdef _MSC_VER
12
+ #pragma comment(lib, "ws2_32.lib")
13
+ #endif
12
14
  typedef SOCKET socket_t;
13
15
  #define INVALID_SOCKET_VALUE INVALID_SOCKET
14
16
  #define SOCKET_ERROR_VALUE SOCKET_ERROR