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
|
@@ -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);
|
package/native-src/src/logger.h
CHANGED
|
@@ -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;
|
package/native-src/src/mdns.cpp
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
672
|
-
DnsQuestion question(
|
|
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(
|
|
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
|
|
838
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
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
|
-
|
|
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>&
|
|
1062
|
-
|
|
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 =
|
|
1141
|
+
size_t offset = data_offset;
|
|
1067
1142
|
|
|
1068
1143
|
try {
|
|
1069
|
-
priority = read_uint16(
|
|
1070
|
-
weight = read_uint16(
|
|
1071
|
-
port = read_uint16(
|
|
1072
|
-
|
|
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 + "." +
|
|
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("." +
|
|
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
|
}
|
package/native-src/src/mdns.h
CHANGED
|
@@ -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>&
|
|
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();
|
package/native-src/src/noise.cpp
CHANGED
package/native-src/src/os.cpp
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
#include <sysinfoapi.h>
|
|
12
12
|
#include <versionhelpers.h>
|
|
13
13
|
#include <intrin.h>
|
|
14
|
-
#
|
|
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>
|
package/native-src/src/socket.h
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
#ifdef _WIN32
|
|
9
9
|
#include <winsock2.h>
|
|
10
10
|
#include <ws2tcpip.h>
|
|
11
|
-
#
|
|
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
|