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 +1 -0
- package/native-src/CMakeLists.txt +1 -0
- package/native-src/src/dht.h +1 -1
- package/native-src/src/krpc.h +2 -2
- package/native-src/src/librats.cpp +6 -5
- package/native-src/src/librats.h +55 -0
- package/native-src/src/librats_bittorrent.cpp +65 -0
- package/native-src/src/logger.cpp +11 -0
- package/native-src/src/logger.h +2 -5
- package/native-src/src/mdns.cpp +152 -78
- package/native-src/src/mdns.h +12 -3
- package/package.json +1 -1
- package/src/librats_node.cpp +9 -3
package/binding.gyp
CHANGED
package/native-src/src/dht.h
CHANGED
|
@@ -76,7 +76,7 @@ struct DhtNode {
|
|
|
76
76
|
// Number of consecutive failures (0xff = never pinged, 0 = confirmed good)
|
|
77
77
|
uint8_t fail_count = 0xff;
|
|
78
78
|
|
|
79
|
-
DhtNode() : last_seen(std::chrono::steady_clock::now()) {}
|
|
79
|
+
DhtNode() : id(), last_seen(std::chrono::steady_clock::now()) {}
|
|
80
80
|
DhtNode(const NodeId& id, const Peer& peer)
|
|
81
81
|
: id(id), peer(peer), last_seen(std::chrono::steady_clock::now()) {}
|
|
82
82
|
|
package/native-src/src/krpc.h
CHANGED
|
@@ -50,7 +50,7 @@ struct KrpcNode {
|
|
|
50
50
|
std::string ip;
|
|
51
51
|
uint16_t port;
|
|
52
52
|
|
|
53
|
-
KrpcNode() : port(0) {}
|
|
53
|
+
KrpcNode() : id(), port(0) {}
|
|
54
54
|
KrpcNode(const NodeId& node_id, const std::string& ip_addr, uint16_t port_num)
|
|
55
55
|
: id(node_id), ip(ip_addr), port(port_num) {}
|
|
56
56
|
};
|
|
@@ -79,7 +79,7 @@ struct KrpcMessage {
|
|
|
79
79
|
KrpcErrorCode error_code;
|
|
80
80
|
std::string error_message;
|
|
81
81
|
|
|
82
|
-
KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), port(0), error_code(KrpcErrorCode::GenericError) {}
|
|
82
|
+
KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), response_id(), error_code(KrpcErrorCode::GenericError) {}
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#include <sstream>
|
|
15
15
|
#include <iomanip>
|
|
16
16
|
#include <stdexcept>
|
|
17
|
+
#include <string_view>
|
|
17
18
|
|
|
18
19
|
#ifdef TESTING
|
|
19
20
|
#define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", "[pointer: " << this << "] " << message)
|
|
@@ -1253,6 +1254,8 @@ void RatsClient::add_ignored_address(const std::string& ip_address) {
|
|
|
1253
1254
|
}
|
|
1254
1255
|
}
|
|
1255
1256
|
|
|
1257
|
+
//common localhost addresses
|
|
1258
|
+
static constexpr std::array<std::string_view,4> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::"};
|
|
1256
1259
|
|
|
1257
1260
|
// Local interface address blocking methods
|
|
1258
1261
|
void RatsClient::initialize_local_addresses() {
|
|
@@ -1264,10 +1267,9 @@ void RatsClient::initialize_local_addresses() {
|
|
|
1264
1267
|
local_interface_addresses_ = network_utils::get_local_interface_addresses();
|
|
1265
1268
|
|
|
1266
1269
|
// Add common localhost addresses if not already present
|
|
1267
|
-
std::vector<std::string> localhost_addrs = {"127.0.0.1", "::1", "0.0.0.0", "::"};
|
|
1268
1270
|
for (const auto& addr : localhost_addrs) {
|
|
1269
1271
|
if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
|
|
1270
|
-
local_interface_addresses_.
|
|
1272
|
+
local_interface_addresses_.emplace_back(addr);
|
|
1271
1273
|
}
|
|
1272
1274
|
}
|
|
1273
1275
|
|
|
@@ -1287,10 +1289,9 @@ void RatsClient::refresh_local_addresses() {
|
|
|
1287
1289
|
local_interface_addresses_ = network_utils::get_local_interface_addresses();
|
|
1288
1290
|
|
|
1289
1291
|
// Add common localhost addresses if not already present
|
|
1290
|
-
std::vector<std::string> localhost_addrs = {"127.0.0.1", "::1", "0.0.0.0", "::"};
|
|
1291
1292
|
for (const auto& addr : localhost_addrs) {
|
|
1292
1293
|
if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
|
|
1293
|
-
local_interface_addresses_.
|
|
1294
|
+
local_interface_addresses_.emplace_back(addr);
|
|
1294
1295
|
}
|
|
1295
1296
|
}
|
|
1296
1297
|
|
|
@@ -1808,7 +1809,7 @@ bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::functi
|
|
|
1808
1809
|
// Convert Peer to string addresses for callback
|
|
1809
1810
|
std::vector<std::string> peer_addresses;
|
|
1810
1811
|
for (const auto& peer : peers) {
|
|
1811
|
-
peer_addresses.
|
|
1812
|
+
peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
|
|
1812
1813
|
}
|
|
1813
1814
|
|
|
1814
1815
|
if (callback) {
|
package/native-src/src/librats.h
CHANGED
|
@@ -80,6 +80,7 @@ struct RatsPeer {
|
|
|
80
80
|
|
|
81
81
|
RatsPeer() : handshake_state(HandshakeState::PENDING),
|
|
82
82
|
peer_count(0), encryption_enabled(false), noise_handshake_completed(false),
|
|
83
|
+
remote_static_key(),
|
|
83
84
|
ice_enabled(false), ice_state(IceConnectionState::NEW),
|
|
84
85
|
detected_nat_type(NatType::UNKNOWN), rtt_ms(0), packet_loss_percent(0),
|
|
85
86
|
transport_protocol("UDP") {
|
|
@@ -1453,6 +1454,60 @@ public:
|
|
|
1453
1454
|
*/
|
|
1454
1455
|
void get_torrent_metadata(const std::string& info_hash_hex,
|
|
1455
1456
|
std::function<void(const TorrentInfo&, bool, const std::string&)> callback);
|
|
1457
|
+
|
|
1458
|
+
// =========================================================================
|
|
1459
|
+
// Spider Mode API (requires RATS_SEARCH_FEATURES)
|
|
1460
|
+
// =========================================================================
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* Spider announce callback type
|
|
1464
|
+
* Called when a peer announces they have a torrent (announce_peer request received)
|
|
1465
|
+
* @param info_hash The info hash being announced (as hex string)
|
|
1466
|
+
* @param peer_address The peer that is announcing (ip:port format)
|
|
1467
|
+
*/
|
|
1468
|
+
using SpiderAnnounceCallback = std::function<void(const std::string& info_hash, const std::string& peer_address)>;
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Enable spider mode on DHT
|
|
1472
|
+
* In spider mode:
|
|
1473
|
+
* - Nodes are added to routing table without ping verification
|
|
1474
|
+
* - All announce_peer requests from other peers are collected via callback
|
|
1475
|
+
* @param enable true to enable spider mode, false to disable
|
|
1476
|
+
*/
|
|
1477
|
+
void set_spider_mode(bool enable);
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* Check if spider mode is enabled
|
|
1481
|
+
* @return true if spider mode is enabled
|
|
1482
|
+
*/
|
|
1483
|
+
bool is_spider_mode() const;
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Set callback for announce_peer requests (spider mode)
|
|
1487
|
+
* Called when other peers announce they have a torrent
|
|
1488
|
+
* @param callback The callback to invoke
|
|
1489
|
+
*/
|
|
1490
|
+
void set_spider_announce_callback(SpiderAnnounceCallback callback);
|
|
1491
|
+
|
|
1492
|
+
/**
|
|
1493
|
+
* Set spider ignore mode - when true, incoming requests are not processed
|
|
1494
|
+
* Useful for rate limiting in spider mode
|
|
1495
|
+
* @param ignore true to ignore incoming requests, false to process them
|
|
1496
|
+
*/
|
|
1497
|
+
void set_spider_ignore(bool ignore);
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Check if spider ignore mode is enabled
|
|
1501
|
+
* @return true if ignoring incoming requests
|
|
1502
|
+
*/
|
|
1503
|
+
bool is_spider_ignoring() const;
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* Trigger a single spider walk iteration
|
|
1507
|
+
* Sends find_node to a random node from the routing table
|
|
1508
|
+
* Call this periodically at desired frequency to expand routing table
|
|
1509
|
+
*/
|
|
1510
|
+
void spider_walk();
|
|
1456
1511
|
#endif // RATS_SEARCH_FEATURES
|
|
1457
1512
|
|
|
1458
1513
|
private:
|
|
@@ -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
|
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
|
@@ -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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
844
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
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
|
-
|
|
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>&
|
|
1068
|
-
|
|
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 =
|
|
1141
|
+
size_t offset = data_offset;
|
|
1073
1142
|
|
|
1074
1143
|
try {
|
|
1075
|
-
priority = read_uint16(
|
|
1076
|
-
weight = read_uint16(
|
|
1077
|
-
port = read_uint16(
|
|
1078
|
-
|
|
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
|
}
|
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
|
};
|
|
@@ -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>&
|
|
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
package/src/librats_node.cpp
CHANGED
|
@@ -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
|
-
|
|
239
|
+
if (client_ != nullptr) {
|
|
240
|
+
rats_stop(client_);
|
|
241
|
+
}
|
|
236
242
|
}
|
|
237
243
|
|
|
238
244
|
Napi::Value Connect(const Napi::CallbackInfo& info) {
|