librats 0.5.1 → 0.5.3
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/README.md +1 -1
- package/lib/index.d.ts +2 -1
- package/native-src/CMakeLists.txt +9 -0
- package/native-src/src/dht.cpp +386 -85
- package/native-src/src/dht.h +95 -22
- package/native-src/src/ice.cpp +2 -2
- package/native-src/src/librats.cpp +62 -42
- package/native-src/src/librats.h +5 -2
- package/native-src/src/librats_c.cpp +18 -2
- package/native-src/src/librats_c.h +3 -1
- package/native-src/src/librats_mdns.cpp +5 -0
- package/native-src/src/mdns.cpp +13 -7
- package/native-src/src/mdns.h +2 -0
- 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 +46 -1
package/native-src/src/dht.h
CHANGED
|
@@ -127,6 +127,16 @@ struct DhtNode {
|
|
|
127
127
|
*/
|
|
128
128
|
using PeerDiscoveryCallback = std::function<void(const std::vector<Peer>& peers, const InfoHash& info_hash)>;
|
|
129
129
|
|
|
130
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
131
|
+
/**
|
|
132
|
+
* Spider mode callback
|
|
133
|
+
* Called when a peer announces they have a torrent (announce_peer request received)
|
|
134
|
+
* @param info_hash The info hash being announced
|
|
135
|
+
* @param peer The peer that is announcing (with the port they specified)
|
|
136
|
+
*/
|
|
137
|
+
using SpiderAnnounceCallback = std::function<void(const InfoHash& info_hash, const Peer& peer)>;
|
|
138
|
+
#endif // RATS_SEARCH_FEATURES
|
|
139
|
+
|
|
130
140
|
/**
|
|
131
141
|
* Deferred callbacks structure for avoiding deadlock
|
|
132
142
|
* Callbacks are collected while holding the mutex, then invoked after releasing it
|
|
@@ -198,9 +208,10 @@ public:
|
|
|
198
208
|
* Announce that this node is a peer for a specific info hash
|
|
199
209
|
* @param info_hash The info hash to announce
|
|
200
210
|
* @param port The port to announce (0 for DHT port)
|
|
211
|
+
* @param callback Optional callback to receive discovered peers during traversal
|
|
201
212
|
* @return true if announcement started successfully, false otherwise
|
|
202
213
|
*/
|
|
203
|
-
bool announce_peer(const InfoHash& info_hash, uint16_t port = 0);
|
|
214
|
+
bool announce_peer(const InfoHash& info_hash, uint16_t port = 0, PeerDiscoveryCallback callback = nullptr);
|
|
204
215
|
|
|
205
216
|
/**
|
|
206
217
|
* Get our node ID
|
|
@@ -227,18 +238,79 @@ public:
|
|
|
227
238
|
*/
|
|
228
239
|
bool is_search_active(const InfoHash& info_hash) const;
|
|
229
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Check if an announce is currently active for an info hash
|
|
243
|
+
* @param info_hash The info hash to check
|
|
244
|
+
* @return true if announce is active, false otherwise
|
|
245
|
+
*/
|
|
246
|
+
bool is_announce_active(const InfoHash& info_hash) const;
|
|
247
|
+
|
|
230
248
|
/**
|
|
231
249
|
* Get number of active searches
|
|
232
250
|
* @return Number of active searches
|
|
233
251
|
*/
|
|
234
252
|
size_t get_active_searches_count() const;
|
|
235
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Get number of active announces
|
|
256
|
+
* @return Number of active announces
|
|
257
|
+
*/
|
|
258
|
+
size_t get_active_announces_count() const;
|
|
259
|
+
|
|
236
260
|
/**
|
|
237
261
|
* Check if DHT is running
|
|
238
262
|
* @return true if running, false otherwise
|
|
239
263
|
*/
|
|
240
264
|
bool is_running() const { return running_; }
|
|
241
265
|
|
|
266
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// SPIDER MODE - Aggressive node discovery and announce collection
|
|
269
|
+
// ============================================================================
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Enable spider mode
|
|
273
|
+
* In spider mode:
|
|
274
|
+
* - Nodes are added to routing table without ping verification
|
|
275
|
+
* - All announce_peer requests from other peers are collected via callback
|
|
276
|
+
* @param enable true to enable spider mode, false to disable
|
|
277
|
+
*/
|
|
278
|
+
void set_spider_mode(bool enable);
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Check if spider mode is enabled
|
|
282
|
+
* @return true if spider mode is enabled
|
|
283
|
+
*/
|
|
284
|
+
bool is_spider_mode() const { return spider_mode_; }
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Set callback for announce_peer requests (spider mode)
|
|
288
|
+
* Called when other peers announce they have a torrent
|
|
289
|
+
* @param callback The callback to invoke
|
|
290
|
+
*/
|
|
291
|
+
void set_spider_announce_callback(SpiderAnnounceCallback callback);
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Set spider ignore mode - when true, incoming requests are not processed
|
|
295
|
+
* Similar to rate limiting in the original spider implementation
|
|
296
|
+
* @param ignore true to ignore incoming requests, false to process them
|
|
297
|
+
*/
|
|
298
|
+
void set_spider_ignore(bool ignore);
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Check if spider ignore mode is enabled
|
|
302
|
+
* @return true if ignoring incoming requests
|
|
303
|
+
*/
|
|
304
|
+
bool is_spider_ignoring() const { return spider_ignore_; }
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Trigger a single spider walk iteration
|
|
308
|
+
* Sends find_node to a random node from the routing table
|
|
309
|
+
* Should be called from external loop at desired frequency
|
|
310
|
+
*/
|
|
311
|
+
void spider_walk();
|
|
312
|
+
#endif // RATS_SEARCH_FEATURES
|
|
313
|
+
|
|
242
314
|
/**
|
|
243
315
|
* Get default BitTorrent DHT bootstrap nodes
|
|
244
316
|
* @return Vector of bootstrap nodes
|
|
@@ -279,10 +351,9 @@ private:
|
|
|
279
351
|
// 1. pending_pings_mutex_ (Ping verification state, nodes_being_replaced_)
|
|
280
352
|
// 2. pending_searches_mutex_ (Search state and transaction mappings)
|
|
281
353
|
// 3. routing_table_mutex_ (core routing data)
|
|
282
|
-
// 4.
|
|
283
|
-
// 5.
|
|
284
|
-
// 6.
|
|
285
|
-
// 7. shutdown_mutex_ (Lowest priority - can be locked independently)
|
|
354
|
+
// 4. announced_peers_mutex_ (Stored peer data)
|
|
355
|
+
// 5. peer_tokens_mutex_ (Token validation data)
|
|
356
|
+
// 6. shutdown_mutex_ (Lowest priority - can be locked independently)
|
|
286
357
|
//
|
|
287
358
|
// Routing table (k-buckets)
|
|
288
359
|
std::vector<std::vector<DhtNode>> routing_table_;
|
|
@@ -301,17 +372,6 @@ private:
|
|
|
301
372
|
std::mutex peer_tokens_mutex_; // Lock order: 6
|
|
302
373
|
|
|
303
374
|
|
|
304
|
-
// Pending announce tracking (for BEP 5 compliance)
|
|
305
|
-
struct PendingAnnounce {
|
|
306
|
-
InfoHash info_hash;
|
|
307
|
-
uint16_t port;
|
|
308
|
-
std::chrono::steady_clock::time_point created_at;
|
|
309
|
-
|
|
310
|
-
PendingAnnounce(const InfoHash& hash, uint16_t p)
|
|
311
|
-
: info_hash(hash), port(p), created_at(std::chrono::steady_clock::now()) {}
|
|
312
|
-
};
|
|
313
|
-
std::unordered_map<std::string, PendingAnnounce> pending_announces_;
|
|
314
|
-
std::mutex pending_announces_mutex_; // Lock order: 4
|
|
315
375
|
|
|
316
376
|
// Pending find_peers tracking (to map transaction IDs to info_hash)
|
|
317
377
|
struct PendingSearch {
|
|
@@ -332,9 +392,16 @@ private:
|
|
|
332
392
|
// Callbacks to invoke when peers are found (supports multiple concurrent searches for same info_hash)
|
|
333
393
|
std::vector<PeerDiscoveryCallback> callbacks;
|
|
334
394
|
|
|
395
|
+
// Announce support: tokens collected during traversal (BEP 5 compliant)
|
|
396
|
+
// Maps node_id -> write_token received from that node
|
|
397
|
+
std::unordered_map<NodeId, std::string> write_tokens;
|
|
398
|
+
bool is_announce; // true if this search is for announce_peer
|
|
399
|
+
uint16_t announce_port; // port to announce (only valid if is_announce)
|
|
400
|
+
|
|
335
401
|
PendingSearch(const InfoHash& hash)
|
|
336
402
|
: info_hash(hash), created_at(std::chrono::steady_clock::now()),
|
|
337
|
-
invoke_count(0), branch_factor(ALPHA), is_finished(false)
|
|
403
|
+
invoke_count(0), branch_factor(ALPHA), is_finished(false),
|
|
404
|
+
is_announce(false), announce_port(0) {}
|
|
338
405
|
};
|
|
339
406
|
std::unordered_map<std::string, PendingSearch> pending_searches_; // info_hash (hex) -> PendingSearch
|
|
340
407
|
mutable std::mutex pending_searches_mutex_; // Lock order: 2
|
|
@@ -391,6 +458,14 @@ private:
|
|
|
391
458
|
std::condition_variable shutdown_cv_;
|
|
392
459
|
std::mutex shutdown_mutex_; // Lock order: 7 (can be locked independently)
|
|
393
460
|
|
|
461
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
462
|
+
// Spider mode state
|
|
463
|
+
std::atomic<bool> spider_mode_{false};
|
|
464
|
+
std::atomic<bool> spider_ignore_{false}; // When true, ignore incoming requests
|
|
465
|
+
SpiderAnnounceCallback spider_announce_callback_;
|
|
466
|
+
std::mutex spider_callbacks_mutex_; // Protects spider callbacks
|
|
467
|
+
#endif // RATS_SEARCH_FEATURES
|
|
468
|
+
|
|
394
469
|
// Helper functions
|
|
395
470
|
void network_loop();
|
|
396
471
|
void maintenance_loop();
|
|
@@ -414,7 +489,7 @@ private:
|
|
|
414
489
|
void send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash);
|
|
415
490
|
void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token);
|
|
416
491
|
|
|
417
|
-
void add_node(const DhtNode& node, bool confirmed = true);
|
|
492
|
+
void add_node(const DhtNode& node, bool confirmed = true, bool no_verify = false);
|
|
418
493
|
std::vector<DhtNode> find_closest_nodes(const NodeId& target, size_t count = K_BUCKET_SIZE);
|
|
419
494
|
std::vector<DhtNode> find_closest_nodes_unlocked(const NodeId& target, size_t count = K_BUCKET_SIZE);
|
|
420
495
|
int get_bucket_index(const NodeId& id);
|
|
@@ -434,10 +509,6 @@ private:
|
|
|
434
509
|
void refresh_buckets();
|
|
435
510
|
void print_statistics();
|
|
436
511
|
|
|
437
|
-
// Pending announce management
|
|
438
|
-
void cleanup_stale_announces();
|
|
439
|
-
void handle_get_peers_response_for_announce(const std::string& transaction_id, const Peer& responder, const std::string& token);
|
|
440
|
-
|
|
441
512
|
// Pending search management
|
|
442
513
|
void cleanup_stale_searches();
|
|
443
514
|
void cleanup_timed_out_search_requests();
|
|
@@ -445,8 +516,10 @@ private:
|
|
|
445
516
|
void handle_get_peers_response_for_search(const std::string& transaction_id, const Peer& responder, const std::vector<Peer>& peers);
|
|
446
517
|
void handle_get_peers_response_with_nodes(const std::string& transaction_id, const Peer& responder, const std::vector<KrpcNode>& nodes);
|
|
447
518
|
void handle_get_peers_empty_response(const std::string& transaction_id, const Peer& responder);
|
|
519
|
+
void save_write_token(PendingSearch& search, const NodeId& node_id, const std::string& token);
|
|
448
520
|
bool add_search_requests(PendingSearch& search, DeferredCallbacks& deferred);
|
|
449
521
|
void add_node_to_search(PendingSearch& search, const DhtNode& node);
|
|
522
|
+
void send_announce_to_closest_nodes(PendingSearch& search);
|
|
450
523
|
|
|
451
524
|
// Peer announcement storage management
|
|
452
525
|
void store_announced_peer(const InfoHash& info_hash, const Peer& peer);
|
package/native-src/src/ice.cpp
CHANGED
|
@@ -477,8 +477,8 @@ StunAddress NatTypeDetector::get_mapped_address_different_port(const std::string
|
|
|
477
477
|
|
|
478
478
|
IceAgent::IceAgent(IceRole role, const IceConfig& config)
|
|
479
479
|
: role_(role), config_(config), running_(false), state_(IceConnectionState::NEW),
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
selected_pair_(IceCandidate(), IceCandidate()),
|
|
481
|
+
udp_socket_(INVALID_SOCKET_VALUE), tcp_socket_(INVALID_SOCKET_VALUE) {
|
|
482
482
|
|
|
483
483
|
// Initialize NAT detector
|
|
484
484
|
nat_detector_ = std::make_unique<NatTypeDetector>();
|
|
@@ -52,15 +52,15 @@ RatsClient::RatsClient(int listen_port, int max_peers, const NatTraversalConfig&
|
|
|
52
52
|
: listen_port_(listen_port),
|
|
53
53
|
bind_address_(bind_address),
|
|
54
54
|
max_peers_(max_peers),
|
|
55
|
-
nat_config_(nat_config),
|
|
56
55
|
server_socket_(INVALID_SOCKET_VALUE),
|
|
57
56
|
running_(false),
|
|
58
|
-
|
|
57
|
+
nat_config_(nat_config),
|
|
58
|
+
data_directory_("."),
|
|
59
59
|
encryption_enabled_(false),
|
|
60
60
|
detected_nat_type_(NatType::UNKNOWN),
|
|
61
|
-
data_directory_("."),
|
|
62
61
|
custom_protocol_name_("rats"),
|
|
63
|
-
custom_protocol_version_("1.0")
|
|
62
|
+
custom_protocol_version_("1.0"),
|
|
63
|
+
auto_discovery_running_(false) {
|
|
64
64
|
// Initialize STUN client
|
|
65
65
|
stun_client_ = std::make_unique<StunClient>();
|
|
66
66
|
|
|
@@ -1817,7 +1817,8 @@ bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::functi
|
|
|
1817
1817
|
});
|
|
1818
1818
|
}
|
|
1819
1819
|
|
|
1820
|
-
bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port
|
|
1820
|
+
bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port,
|
|
1821
|
+
std::function<void(const std::vector<std::string>&)> callback) {
|
|
1821
1822
|
if (!dht_client_ || !dht_client_->is_running()) {
|
|
1822
1823
|
LOG_CLIENT_ERROR("DHT client not running");
|
|
1823
1824
|
return false;
|
|
@@ -1832,10 +1833,25 @@ bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t por
|
|
|
1832
1833
|
port = listen_port_;
|
|
1833
1834
|
}
|
|
1834
1835
|
|
|
1835
|
-
LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
|
|
1836
|
+
LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
|
|
1837
|
+
<< (callback ? " with peer callback" : ""));
|
|
1836
1838
|
|
|
1837
1839
|
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
1838
|
-
|
|
1840
|
+
|
|
1841
|
+
// Create wrapper callback that converts Peer to string addresses (if callback provided)
|
|
1842
|
+
PeerDiscoveryCallback peer_callback = nullptr;
|
|
1843
|
+
if (callback) {
|
|
1844
|
+
peer_callback = [callback](const std::vector<Peer>& peers, const InfoHash& hash) {
|
|
1845
|
+
std::vector<std::string> peer_addresses;
|
|
1846
|
+
peer_addresses.reserve(peers.size());
|
|
1847
|
+
for (const auto& peer : peers) {
|
|
1848
|
+
peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
|
|
1849
|
+
}
|
|
1850
|
+
callback(peer_addresses);
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
return dht_client_->announce_peer(info_hash, port, peer_callback);
|
|
1839
1855
|
}
|
|
1840
1856
|
|
|
1841
1857
|
bool RatsClient::is_dht_running() const {
|
|
@@ -1931,46 +1947,23 @@ void RatsClient::automatic_discovery_loop() {
|
|
|
1931
1947
|
}
|
|
1932
1948
|
}
|
|
1933
1949
|
|
|
1934
|
-
//
|
|
1935
|
-
search_rats_peers();
|
|
1936
|
-
|
|
1937
|
-
{
|
|
1938
|
-
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
1939
|
-
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(10), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
1940
|
-
LOG_CLIENT_INFO("Automatic peer discovery loop stopped during search delay");
|
|
1941
|
-
return;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
// Announce immediately
|
|
1950
|
+
// Announce immediately - this also discovers peers during traversal
|
|
1946
1951
|
announce_rats_peer();
|
|
1947
1952
|
|
|
1948
1953
|
auto last_announce = std::chrono::steady_clock::now();
|
|
1949
|
-
auto last_search = std::chrono::steady_clock::now();
|
|
1950
1954
|
|
|
1951
1955
|
while (auto_discovery_running_.load()) {
|
|
1952
1956
|
auto now = std::chrono::steady_clock::now();
|
|
1953
1957
|
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
}
|
|
1964
|
-
} else {
|
|
1965
|
-
// Peers connected: less aggressive, similar to original logic
|
|
1966
|
-
if (now - last_search >= std::chrono::minutes(5)) {
|
|
1967
|
-
search_rats_peers();
|
|
1968
|
-
last_search = now;
|
|
1969
|
-
}
|
|
1970
|
-
if (now - last_announce >= std::chrono::minutes(10)) {
|
|
1971
|
-
announce_rats_peer();
|
|
1972
|
-
last_announce = now;
|
|
1973
|
-
}
|
|
1958
|
+
// Announce combines both announcing our presence and discovering peers
|
|
1959
|
+
// Adjust frequency based on whether we have peers
|
|
1960
|
+
auto interval = (get_peer_count() == 0)
|
|
1961
|
+
? std::chrono::seconds(15) // Aggressive when no peers
|
|
1962
|
+
: std::chrono::minutes(10); // Less aggressive when connected
|
|
1963
|
+
|
|
1964
|
+
if (now - last_announce >= interval) {
|
|
1965
|
+
announce_rats_peer();
|
|
1966
|
+
last_announce = now;
|
|
1974
1967
|
}
|
|
1975
1968
|
|
|
1976
1969
|
// Use conditional variable for responsive shutdown
|
|
@@ -1994,8 +1987,35 @@ void RatsClient::announce_rats_peer() {
|
|
|
1994
1987
|
std::string discovery_hash = get_discovery_hash();
|
|
1995
1988
|
LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
|
|
1996
1989
|
|
|
1997
|
-
|
|
1998
|
-
|
|
1990
|
+
InfoHash info_hash = hex_to_node_id(discovery_hash);
|
|
1991
|
+
|
|
1992
|
+
if (dht_client_->is_announce_active(info_hash)) {
|
|
1993
|
+
LOG_CLIENT_WARN("Announce already in progress for info hash: " << node_id_to_hex(info_hash));
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// Use announce with callback - combines announce and find_peers in one traversal
|
|
1998
|
+
// Peers discovered during traversal will be returned through the callback
|
|
1999
|
+
if (announce_for_hash(discovery_hash, listen_port_, [this, info_hash](const std::vector<std::string>& peer_addresses) {
|
|
2000
|
+
LOG_CLIENT_INFO("Announce discovered " << peer_addresses.size() << " peers during traversal");
|
|
2001
|
+
|
|
2002
|
+
// Convert peer addresses to Peer objects for handle_dht_peer_discovery()
|
|
2003
|
+
std::vector<Peer> peers;
|
|
2004
|
+
peers.reserve(peer_addresses.size());
|
|
2005
|
+
for (const auto& peer_address : peer_addresses) {
|
|
2006
|
+
std::string ip;
|
|
2007
|
+
int port;
|
|
2008
|
+
if (parse_address_string(peer_address, ip, port)) {
|
|
2009
|
+
peers.push_back(Peer(ip, port));
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// Auto-connect to discovered peers
|
|
2014
|
+
if (!peers.empty()) {
|
|
2015
|
+
handle_dht_peer_discovery(peers, info_hash);
|
|
2016
|
+
}
|
|
2017
|
+
})) {
|
|
2018
|
+
LOG_CLIENT_DEBUG("Successfully started announce with peer discovery for discovery hash");
|
|
1999
2019
|
} else {
|
|
2000
2020
|
LOG_CLIENT_WARN("Failed to announce peer for discovery");
|
|
2001
2021
|
}
|
package/native-src/src/librats.h
CHANGED
|
@@ -606,12 +606,15 @@ public:
|
|
|
606
606
|
std::function<void(const std::vector<std::string>&)> callback);
|
|
607
607
|
|
|
608
608
|
/**
|
|
609
|
-
* Announce our presence for a content hash
|
|
609
|
+
* Announce our presence for a content hash with optional peer discovery callback
|
|
610
|
+
* If callback is provided, peers discovered during DHT traversal will be returned through it
|
|
610
611
|
* @param content_hash Hash to announce for (40-character hex string)
|
|
611
612
|
* @param port Port to announce (default: our listen port)
|
|
613
|
+
* @param callback Optional function to call with discovered peers during traversal
|
|
612
614
|
* @return true if announced successfully
|
|
613
615
|
*/
|
|
614
|
-
bool announce_for_hash(const std::string& content_hash, uint16_t port = 0
|
|
616
|
+
bool announce_for_hash(const std::string& content_hash, uint16_t port = 0,
|
|
617
|
+
std::function<void(const std::vector<std::string>&)> callback = nullptr);
|
|
615
618
|
|
|
616
619
|
/**
|
|
617
620
|
* Check if DHT is currently running
|
|
@@ -386,13 +386,29 @@ int rats_is_dht_running(rats_client_t handle) {
|
|
|
386
386
|
return wrap->client->is_dht_running() ? 1 : 0;
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
-
rats_error_t rats_announce_for_hash(rats_client_t handle, const char* content_hash, int port
|
|
389
|
+
rats_error_t rats_announce_for_hash(rats_client_t handle, const char* content_hash, int port,
|
|
390
|
+
rats_peers_found_cb callback, void* user_data) {
|
|
390
391
|
if (!handle || !content_hash) return RATS_ERROR_INVALID_PARAMETER;
|
|
391
392
|
if (strlen(content_hash) != 40) return RATS_ERROR_INVALID_PARAMETER; // SHA1 hash must be 40 chars
|
|
392
393
|
rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
|
|
393
394
|
|
|
394
395
|
uint16_t announce_port = (port <= 0) ? 0 : static_cast<uint16_t>(port);
|
|
395
|
-
|
|
396
|
+
|
|
397
|
+
// Create C++ callback wrapper if C callback is provided
|
|
398
|
+
std::function<void(const std::vector<std::string>&)> cpp_callback = nullptr;
|
|
399
|
+
if (callback) {
|
|
400
|
+
cpp_callback = [callback, user_data](const std::vector<std::string>& peers) {
|
|
401
|
+
// Convert vector to C-style array
|
|
402
|
+
std::vector<const char*> c_peers;
|
|
403
|
+
c_peers.reserve(peers.size());
|
|
404
|
+
for (const auto& peer : peers) {
|
|
405
|
+
c_peers.push_back(peer.c_str());
|
|
406
|
+
}
|
|
407
|
+
callback(user_data, c_peers.data(), static_cast<int>(c_peers.size()));
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return wrap->client->announce_for_hash(std::string(content_hash), announce_port, cpp_callback) ?
|
|
396
412
|
RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
|
|
397
413
|
}
|
|
398
414
|
|
|
@@ -82,6 +82,7 @@ typedef void (*rats_json_cb)(void* user_data, const char* peer_id, const char* j
|
|
|
82
82
|
typedef void (*rats_disconnect_cb)(void* user_data, const char* peer_id);
|
|
83
83
|
typedef void (*rats_peer_discovered_cb)(void* user_data, const char* host, int port, const char* service_name);
|
|
84
84
|
typedef void (*rats_message_cb)(void* user_data, const char* peer_id, const char* message_data);
|
|
85
|
+
typedef void (*rats_peers_found_cb)(void* user_data, const char** peer_addresses, int count);
|
|
85
86
|
|
|
86
87
|
// Peer configuration
|
|
87
88
|
RATS_API rats_error_t rats_set_max_peers(rats_client_t client, int max_peers);
|
|
@@ -106,7 +107,8 @@ RATS_API int rats_broadcast_json(rats_client_t client, const char* json_str);
|
|
|
106
107
|
RATS_API rats_error_t rats_start_dht_discovery(rats_client_t client, int dht_port);
|
|
107
108
|
RATS_API void rats_stop_dht_discovery(rats_client_t client);
|
|
108
109
|
RATS_API int rats_is_dht_running(rats_client_t client);
|
|
109
|
-
RATS_API rats_error_t rats_announce_for_hash(rats_client_t client, const char* content_hash, int port
|
|
110
|
+
RATS_API rats_error_t rats_announce_for_hash(rats_client_t client, const char* content_hash, int port,
|
|
111
|
+
rats_peers_found_cb callback, void* user_data);
|
|
110
112
|
RATS_API size_t rats_get_dht_routing_table_size(rats_client_t client);
|
|
111
113
|
|
|
112
114
|
// Automatic discovery
|
|
@@ -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/mdns.cpp
CHANGED
|
@@ -25,6 +25,7 @@ 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),
|
|
@@ -279,6 +280,11 @@ void MdnsClient::set_query_interval(std::chrono::seconds interval) {
|
|
|
279
280
|
query_interval_ = interval;
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
void MdnsClient::set_service_type(const std::string& service_type) {
|
|
284
|
+
service_type_ = service_type;
|
|
285
|
+
LOG_MDNS_INFO("Service type set to: " << service_type_);
|
|
286
|
+
}
|
|
287
|
+
|
|
282
288
|
bool MdnsClient::create_multicast_socket() {
|
|
283
289
|
multicast_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
284
290
|
if (!librats::is_valid_socket(multicast_socket_)) {
|
|
@@ -540,7 +546,7 @@ void MdnsClient::process_query(const DnsMessage& query, const std::string& sende
|
|
|
540
546
|
bool should_respond = false;
|
|
541
547
|
|
|
542
548
|
// Check if the question is asking for our service type
|
|
543
|
-
if (question.name ==
|
|
549
|
+
if (question.name == service_type_ && question.type == DnsRecordType::PTR) {
|
|
544
550
|
should_respond = true;
|
|
545
551
|
}
|
|
546
552
|
|
|
@@ -627,7 +633,7 @@ void MdnsClient::extract_service_from_response(const DnsMessage& response, const
|
|
|
627
633
|
}
|
|
628
634
|
|
|
629
635
|
bool MdnsClient::is_librats_service(const std::string& service_name) const {
|
|
630
|
-
return service_name ==
|
|
636
|
+
return service_name == service_type_;
|
|
631
637
|
}
|
|
632
638
|
|
|
633
639
|
void MdnsClient::add_or_update_service(const MdnsService& service) {
|
|
@@ -668,8 +674,8 @@ DnsMessage MdnsClient::create_query_message() {
|
|
|
668
674
|
query.header.flags = static_cast<uint16_t>(MdnsFlags::QUERY);
|
|
669
675
|
query.header.question_count = 1;
|
|
670
676
|
|
|
671
|
-
// Add question for
|
|
672
|
-
DnsQuestion question(
|
|
677
|
+
// Add question for services with our service type
|
|
678
|
+
DnsQuestion question(service_type_, DnsRecordType::PTR, DnsRecordClass::CLASS_IN);
|
|
673
679
|
query.questions.push_back(question);
|
|
674
680
|
|
|
675
681
|
return query;
|
|
@@ -689,7 +695,7 @@ DnsMessage MdnsClient::create_announcement_message() {
|
|
|
689
695
|
}
|
|
690
696
|
|
|
691
697
|
// Create PTR record
|
|
692
|
-
DnsResourceRecord ptr_record = create_ptr_record(
|
|
698
|
+
DnsResourceRecord ptr_record = create_ptr_record(service_type_, our_service_name);
|
|
693
699
|
announcement.answers.push_back(ptr_record);
|
|
694
700
|
|
|
695
701
|
// Create SRV record
|
|
@@ -1120,11 +1126,11 @@ std::string MdnsClient::create_service_instance_name(const std::string& instance
|
|
|
1120
1126
|
clean_name = "librats-node";
|
|
1121
1127
|
}
|
|
1122
1128
|
|
|
1123
|
-
return clean_name + "." +
|
|
1129
|
+
return clean_name + "." + service_type_;
|
|
1124
1130
|
}
|
|
1125
1131
|
|
|
1126
1132
|
std::string MdnsClient::extract_instance_name_from_service(const std::string& service_name) {
|
|
1127
|
-
size_t pos = service_name.find("." +
|
|
1133
|
+
size_t pos = service_name.find("." + service_type_);
|
|
1128
1134
|
if (pos != std::string::npos) {
|
|
1129
1135
|
return service_name.substr(0, pos);
|
|
1130
1136
|
}
|
package/native-src/src/mdns.h
CHANGED
|
@@ -152,11 +152,13 @@ public:
|
|
|
152
152
|
// Configuration
|
|
153
153
|
void set_announcement_interval(std::chrono::seconds interval);
|
|
154
154
|
void set_query_interval(std::chrono::seconds interval);
|
|
155
|
+
void set_service_type(const std::string& service_type);
|
|
155
156
|
|
|
156
157
|
private:
|
|
157
158
|
// Core properties
|
|
158
159
|
std::string service_instance_name_;
|
|
159
160
|
uint16_t service_port_;
|
|
161
|
+
std::string service_type_; // Dynamic service type (e.g., "_rats-search._tcp.local.")
|
|
160
162
|
std::map<std::string, std::string> txt_records_;
|
|
161
163
|
|
|
162
164
|
// Network properties
|
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
|
package/native-src/src/stun.cpp
CHANGED
|
@@ -106,7 +106,7 @@ std::vector<uint8_t> StunClient::create_binding_request() {
|
|
|
106
106
|
std::mt19937 gen(rd());
|
|
107
107
|
std::uniform_int_distribution<> dis(0, 255);
|
|
108
108
|
|
|
109
|
-
for (
|
|
109
|
+
for (size_t i = 0; i < stun::TRANSACTION_ID_SIZE; ++i) {
|
|
110
110
|
request[8 + i] = static_cast<uint8_t>(dis(gen));
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -226,7 +226,7 @@ void StunClient::generate_transaction_id(uint8_t* transaction_id) {
|
|
|
226
226
|
std::mt19937 gen(rd());
|
|
227
227
|
std::uniform_int_distribution<> dis(0, 255);
|
|
228
228
|
|
|
229
|
-
for (
|
|
229
|
+
for (size_t i = 0; i < stun::TRANSACTION_ID_SIZE; ++i) {
|
|
230
230
|
transaction_id[i] = static_cast<uint8_t>(dis(gen));
|
|
231
231
|
}
|
|
232
232
|
}
|
package/package.json
CHANGED
package/src/librats_node.cpp
CHANGED
|
@@ -473,7 +473,52 @@ private:
|
|
|
473
473
|
std::string content_hash = info[0].As<Napi::String>().Utf8Value();
|
|
474
474
|
int port = info[1].As<Napi::Number>().Int32Value();
|
|
475
475
|
|
|
476
|
-
|
|
476
|
+
// Check if optional callback is provided
|
|
477
|
+
rats_peers_found_cb c_callback = nullptr;
|
|
478
|
+
void* callback_user_data = nullptr;
|
|
479
|
+
Napi::ThreadSafeFunction* tsfn_ptr = nullptr;
|
|
480
|
+
|
|
481
|
+
if (info.Length() >= 3 && info[2].IsFunction()) {
|
|
482
|
+
// Create thread-safe function for callback
|
|
483
|
+
auto tsfn = new Napi::ThreadSafeFunction();
|
|
484
|
+
*tsfn = Napi::ThreadSafeFunction::New(
|
|
485
|
+
env,
|
|
486
|
+
info[2].As<Napi::Function>(),
|
|
487
|
+
"AnnounceForHashCallback",
|
|
488
|
+
0,
|
|
489
|
+
1,
|
|
490
|
+
[](Napi::Env) {} // Release callback
|
|
491
|
+
);
|
|
492
|
+
tsfn_ptr = tsfn;
|
|
493
|
+
|
|
494
|
+
c_callback = [](void* user_data, const char** peer_addresses, int count) {
|
|
495
|
+
auto* tsfn = static_cast<Napi::ThreadSafeFunction*>(user_data);
|
|
496
|
+
if (!tsfn) return;
|
|
497
|
+
|
|
498
|
+
// Copy peer addresses for async callback
|
|
499
|
+
std::vector<std::string>* peers = new std::vector<std::string>();
|
|
500
|
+
for (int i = 0; i < count; i++) {
|
|
501
|
+
if (peer_addresses[i]) {
|
|
502
|
+
peers->push_back(peer_addresses[i]);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
tsfn->BlockingCall(peers, [](Napi::Env env, Napi::Function jsCallback, std::vector<std::string>* data) {
|
|
507
|
+
Napi::Array arr = Napi::Array::New(env, data->size());
|
|
508
|
+
for (size_t i = 0; i < data->size(); i++) {
|
|
509
|
+
arr.Set(static_cast<uint32_t>(i), Napi::String::New(env, (*data)[i]));
|
|
510
|
+
}
|
|
511
|
+
jsCallback.Call({arr});
|
|
512
|
+
delete data;
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
tsfn->Release();
|
|
516
|
+
delete tsfn;
|
|
517
|
+
};
|
|
518
|
+
callback_user_data = tsfn_ptr;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
rats_error_t result = rats_announce_for_hash(client_, content_hash.c_str(), port, c_callback, callback_user_data);
|
|
477
522
|
return Napi::Boolean::New(env, result == RATS_SUCCESS);
|
|
478
523
|
}
|
|
479
524
|
|