librats 0.7.2 → 0.9.0
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 +9 -4
- package/lib/index.d.ts +9 -22
- package/native-src/CMakeLists.txt +132 -50
- package/native-src/cmake/ratsConfig.cmake.in +8 -0
- package/native-src/src/bt_network.cpp +296 -183
- package/native-src/src/bt_network.h +25 -5
- package/native-src/src/crypto/sha256.c +1 -1
- package/native-src/src/dht.cpp +297 -47
- package/native-src/src/dht.h +70 -6
- package/native-src/src/file_transfer.cpp +1185 -1578
- package/native-src/src/file_transfer.h +240 -521
- package/native-src/src/io_poller.cpp +917 -0
- package/native-src/src/io_poller.h +138 -0
- package/native-src/src/krpc.cpp +161 -96
- package/native-src/src/krpc.h +18 -4
- package/native-src/src/librats.cpp +812 -1236
- package/native-src/src/librats.h +207 -208
- package/native-src/src/librats_bittorrent.cpp +1 -5
- package/native-src/src/librats_c.cpp +22 -39
- package/native-src/src/librats_discovery.cpp +377 -0
- package/native-src/src/librats_encryption.cpp +130 -283
- package/native-src/src/librats_file_transfer.cpp +27 -109
- package/native-src/src/librats_gossipsub.cpp +1 -5
- package/native-src/src/librats_ice.cpp +5 -1
- package/native-src/src/librats_log_macros.h +36 -0
- package/native-src/src/librats_logging.cpp +1 -7
- package/native-src/src/librats_mdns.cpp +1 -11
- package/native-src/src/librats_persistence.cpp +1 -11
- package/native-src/src/librats_reconnection.cpp +2 -13
- package/native-src/src/librats_statistic.cpp +105 -0
- package/native-src/src/socket.cpp +15 -3
- package/package.json +1 -1
- package/scripts/build-librats.js +3 -0
- package/scripts/prepare-package.js +10 -0
- package/src/librats_node.cpp +53 -64
|
@@ -679,42 +679,36 @@ char* rats_send_directory(rats_client_t handle, const char* peer_id, const char*
|
|
|
679
679
|
rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
|
|
680
680
|
|
|
681
681
|
std::string remote_name = remote_directory_name ? std::string(remote_directory_name) : std::string("");
|
|
682
|
-
|
|
683
|
-
|
|
682
|
+
(void)recursive; // directory transfers are always recursive
|
|
683
|
+
std::string transfer_id = wrap->client->send_directory(std::string(peer_id), std::string(directory_path), remote_name);
|
|
684
|
+
|
|
684
685
|
return transfer_id.empty() ? nullptr : rats_strdup_owned(transfer_id);
|
|
685
686
|
}
|
|
686
687
|
|
|
688
|
+
// Pull-style requests are not supported in this version; transfers are push-only.
|
|
687
689
|
char* rats_request_file(rats_client_t handle, const char* peer_id, const char* remote_file_path, const char* local_path) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
std::string transfer_id = wrap->client->request_file(std::string(peer_id), std::string(remote_file_path), std::string(local_path));
|
|
692
|
-
|
|
693
|
-
return transfer_id.empty() ? nullptr : rats_strdup_owned(transfer_id);
|
|
690
|
+
(void)handle; (void)peer_id; (void)remote_file_path; (void)local_path;
|
|
691
|
+
return nullptr;
|
|
694
692
|
}
|
|
695
693
|
|
|
696
694
|
char* rats_request_directory(rats_client_t handle, const char* peer_id, const char* remote_directory_path, const char* local_directory_path, int recursive) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
std::string transfer_id = wrap->client->request_directory(std::string(peer_id), std::string(remote_directory_path), std::string(local_directory_path), recursive != 0);
|
|
701
|
-
|
|
702
|
-
return transfer_id.empty() ? nullptr : rats_strdup_owned(transfer_id);
|
|
695
|
+
(void)handle; (void)peer_id; (void)remote_directory_path; (void)local_directory_path; (void)recursive;
|
|
696
|
+
return nullptr;
|
|
703
697
|
}
|
|
704
698
|
|
|
705
699
|
rats_error_t rats_accept_directory_transfer(rats_client_t handle, const char* transfer_id, const char* local_path) {
|
|
706
700
|
if (!handle || !transfer_id || !local_path) return RATS_ERROR_INVALID_PARAMETER;
|
|
707
701
|
rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
|
|
708
|
-
|
|
709
|
-
return wrap->client->
|
|
702
|
+
|
|
703
|
+
return wrap->client->accept_file_transfer(std::string(transfer_id), std::string(local_path)) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
|
|
710
704
|
}
|
|
711
705
|
|
|
712
706
|
rats_error_t rats_reject_directory_transfer(rats_client_t handle, const char* transfer_id, const char* reason) {
|
|
713
707
|
if (!handle || !transfer_id) return RATS_ERROR_INVALID_PARAMETER;
|
|
714
708
|
rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
|
|
715
|
-
|
|
709
|
+
|
|
716
710
|
std::string reject_reason = reason ? std::string(reason) : std::string("");
|
|
717
|
-
return wrap->client->
|
|
711
|
+
return wrap->client->reject_file_transfer(std::string(transfer_id), reject_reason) ? RATS_SUCCESS : RATS_ERROR_OPERATION_FAILED;
|
|
718
712
|
}
|
|
719
713
|
|
|
720
714
|
rats_error_t rats_pause_file_transfer(rats_client_t handle, const char* transfer_id) {
|
|
@@ -768,44 +762,33 @@ void rats_set_file_request_callback(rats_client_t handle, rats_file_request_cb c
|
|
|
768
762
|
wrap->file_request_ud = user_data;
|
|
769
763
|
|
|
770
764
|
if (cb) {
|
|
771
|
-
|
|
765
|
+
// Fires for every incoming offer (file or directory). The C app should
|
|
766
|
+
// then call rats_accept_file_transfer()/rats_reject_file_transfer().
|
|
767
|
+
wrap->client->on_file_transfer_request([wrap](const IncomingTransferOffer& offer) {
|
|
772
768
|
if (wrap->file_request_cb) {
|
|
773
|
-
wrap->file_request_cb(wrap->file_request_ud, peer_id.c_str(),
|
|
769
|
+
wrap->file_request_cb(wrap->file_request_ud, offer.peer_id.c_str(),
|
|
770
|
+
offer.transfer_id.c_str(), "", offer.name.c_str());
|
|
774
771
|
}
|
|
775
|
-
return true; // Accept the file request
|
|
776
772
|
});
|
|
777
773
|
}
|
|
778
774
|
}
|
|
779
775
|
|
|
776
|
+
// Directory offers arrive through the unified file-request callback above;
|
|
777
|
+
// this setter is kept for ABI compatibility and simply records the callback.
|
|
780
778
|
void rats_set_directory_request_callback(rats_client_t handle, rats_directory_request_cb cb, void* user_data) {
|
|
781
779
|
if (!handle) return;
|
|
782
780
|
rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
|
|
783
781
|
wrap->directory_request_cb = cb;
|
|
784
782
|
wrap->directory_request_ud = user_data;
|
|
785
|
-
|
|
786
|
-
if (cb) {
|
|
787
|
-
wrap->client->on_directory_request([wrap](const std::string& peer_id, const std::string& directory_path, bool recursive, const std::string& transfer_id) {
|
|
788
|
-
if (wrap->directory_request_cb) {
|
|
789
|
-
wrap->directory_request_cb(wrap->directory_request_ud, peer_id.c_str(), transfer_id.c_str(), directory_path.c_str(), "");
|
|
790
|
-
}
|
|
791
|
-
return true; // Accept the directory request
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
783
|
}
|
|
795
784
|
|
|
785
|
+
// Directory progress arrives through the unified file-progress callback above;
|
|
786
|
+
// this setter is kept for ABI compatibility and simply records the callback.
|
|
796
787
|
void rats_set_directory_progress_callback(rats_client_t handle, rats_directory_progress_cb cb, void* user_data) {
|
|
797
788
|
if (!handle) return;
|
|
798
789
|
rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
|
|
799
790
|
wrap->directory_progress_cb = cb;
|
|
800
791
|
wrap->directory_progress_ud = user_data;
|
|
801
|
-
|
|
802
|
-
if (cb) {
|
|
803
|
-
wrap->client->on_directory_transfer_progress([wrap](const std::string& transfer_id, const std::string& current_file, uint64_t files_completed, uint64_t total_files, uint64_t bytes_completed, uint64_t total_bytes) {
|
|
804
|
-
if (wrap->directory_progress_cb) {
|
|
805
|
-
wrap->directory_progress_cb(wrap->directory_progress_ud, transfer_id.c_str(), static_cast<int>(files_completed), static_cast<int>(total_files), current_file.c_str());
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
792
|
}
|
|
810
793
|
|
|
811
794
|
// Peer information
|
|
@@ -847,7 +830,7 @@ char* rats_get_peer_info_json(rats_client_t handle, const char* peer_id) {
|
|
|
847
830
|
if (!handle || !peer_id) return nullptr;
|
|
848
831
|
rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
|
|
849
832
|
|
|
850
|
-
|
|
833
|
+
auto peer = wrap->client->get_peer_by_id(std::string(peer_id));
|
|
851
834
|
if (!peer) return nullptr;
|
|
852
835
|
|
|
853
836
|
// Create JSON representation of peer info
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#include "librats.h"
|
|
2
|
+
#include "librats_log_macros.h"
|
|
3
|
+
#include "sha1.h"
|
|
4
|
+
|
|
5
|
+
namespace librats {
|
|
6
|
+
|
|
7
|
+
// =========================================================================
|
|
8
|
+
// Peer Discovery Methods
|
|
9
|
+
// =========================================================================
|
|
10
|
+
|
|
11
|
+
bool RatsClient::start_dht_discovery(int dht_port) {
|
|
12
|
+
if (dht_client_ && dht_client_->is_running()) {
|
|
13
|
+
LOG_CLIENT_WARN("DHT discovery is already running");
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
LOG_CLIENT_INFO("Starting DHT discovery on port " << dht_port <<
|
|
18
|
+
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
19
|
+
|
|
20
|
+
auto bootstrap_nodes = DhtClient::get_default_bootstrap_nodes();
|
|
21
|
+
|
|
22
|
+
// IPv4 DHT (also shared with the BitTorrent subsystem) - required.
|
|
23
|
+
dht_client_ = std::make_unique<DhtClient>(dht_port, bind_address_, data_directory_, AddressFamily::IPv4);
|
|
24
|
+
if (!dht_client_->start()) {
|
|
25
|
+
LOG_CLIENT_ERROR("Failed to start IPv4 DHT client");
|
|
26
|
+
dht_client_.reset();
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (!dht_client_->bootstrap(bootstrap_nodes)) {
|
|
30
|
+
LOG_CLIENT_WARN("Failed to bootstrap IPv4 DHT");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// IPv6 DHT - best effort. Shares the same port via a V6ONLY socket. If the host has
|
|
34
|
+
// no usable IPv6 stack, socket creation fails and we simply run IPv4-only.
|
|
35
|
+
dht_client_v6_ = std::make_unique<DhtClient>(dht_port, bind_address_, data_directory_, AddressFamily::IPv6);
|
|
36
|
+
if (dht_client_v6_->start()) {
|
|
37
|
+
if (!dht_client_v6_->bootstrap(bootstrap_nodes)) {
|
|
38
|
+
LOG_CLIENT_WARN("Failed to bootstrap IPv6 DHT");
|
|
39
|
+
}
|
|
40
|
+
LOG_CLIENT_INFO("IPv6 DHT client started");
|
|
41
|
+
} else {
|
|
42
|
+
LOG_CLIENT_INFO("IPv6 DHT unavailable on this host, running IPv4-only");
|
|
43
|
+
dht_client_v6_.reset();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Start automatic peer discovery (this thread also performs the best-effort STUN probe
|
|
47
|
+
// that derives our BEP 42 node ID — see automatic_discovery_loop).
|
|
48
|
+
start_automatic_peer_discovery();
|
|
49
|
+
|
|
50
|
+
LOG_CLIENT_INFO("DHT discovery started successfully");
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void RatsClient::stop_dht_discovery() {
|
|
55
|
+
if (!dht_client_ && !dht_client_v6_) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
LOG_CLIENT_INFO("Stopping DHT discovery");
|
|
60
|
+
|
|
61
|
+
// Stop automatic peer discovery
|
|
62
|
+
stop_automatic_peer_discovery();
|
|
63
|
+
|
|
64
|
+
if (dht_client_) {
|
|
65
|
+
dht_client_->stop();
|
|
66
|
+
dht_client_.reset();
|
|
67
|
+
}
|
|
68
|
+
if (dht_client_v6_) {
|
|
69
|
+
dht_client_v6_->stop();
|
|
70
|
+
dht_client_v6_.reset();
|
|
71
|
+
}
|
|
72
|
+
LOG_CLIENT_INFO("DHT discovery stopped");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::function<void(const std::vector<std::string>&)> callback) {
|
|
76
|
+
if (!is_dht_running()) {
|
|
77
|
+
LOG_CLIENT_ERROR("DHT client not running");
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (content_hash.length() != CONTENT_HASH_HEX_LENGTH) {
|
|
82
|
+
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected " << CONTENT_HASH_HEX_LENGTH << ")");
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
LOG_CLIENT_INFO("Finding peers for content hash: " << content_hash);
|
|
87
|
+
|
|
88
|
+
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
89
|
+
|
|
90
|
+
// Shared wrapper: discovered peers (IPv4 or IPv6) are delivered through the same callback.
|
|
91
|
+
PeerDiscoveryCallback peer_callback = [callback](const std::vector<Peer>& peers, const InfoHash&) {
|
|
92
|
+
std::vector<std::string> peer_addresses;
|
|
93
|
+
peer_addresses.reserve(peers.size());
|
|
94
|
+
for (const auto& peer : peers) {
|
|
95
|
+
peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
|
|
96
|
+
}
|
|
97
|
+
if (callback) {
|
|
98
|
+
callback(peer_addresses);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Fan out to both Kademlia networks; succeed if at least one accepts the search.
|
|
103
|
+
bool started = false;
|
|
104
|
+
if (dht_client_ && dht_client_->is_running()) {
|
|
105
|
+
started |= dht_client_->find_peers(info_hash, peer_callback);
|
|
106
|
+
}
|
|
107
|
+
if (dht_client_v6_ && dht_client_v6_->is_running()) {
|
|
108
|
+
started |= dht_client_v6_->find_peers(info_hash, peer_callback);
|
|
109
|
+
}
|
|
110
|
+
return started;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port,
|
|
114
|
+
std::function<void(const std::vector<std::string>&)> callback) {
|
|
115
|
+
if (!is_dht_running()) {
|
|
116
|
+
LOG_CLIENT_ERROR("DHT client not running");
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (content_hash.length() != CONTENT_HASH_HEX_LENGTH) {
|
|
121
|
+
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected " << CONTENT_HASH_HEX_LENGTH << ")");
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (port == 0) {
|
|
126
|
+
port = listen_port_;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
|
|
130
|
+
<< (callback ? " with peer callback" : ""));
|
|
131
|
+
|
|
132
|
+
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
133
|
+
|
|
134
|
+
// Create wrapper callback that converts Peer to string addresses (if callback provided)
|
|
135
|
+
PeerDiscoveryCallback peer_callback = nullptr;
|
|
136
|
+
if (callback) {
|
|
137
|
+
peer_callback = [callback](const std::vector<Peer>& peers, const InfoHash& hash) {
|
|
138
|
+
std::vector<std::string> peer_addresses;
|
|
139
|
+
peer_addresses.reserve(peers.size());
|
|
140
|
+
for (const auto& peer : peers) {
|
|
141
|
+
peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
|
|
142
|
+
}
|
|
143
|
+
callback(peer_addresses);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Announce on both Kademlia networks so IPv4 and IPv6 peers can both find us.
|
|
148
|
+
bool started = false;
|
|
149
|
+
if (dht_client_ && dht_client_->is_running()) {
|
|
150
|
+
started |= dht_client_->announce_peer(info_hash, port, peer_callback);
|
|
151
|
+
}
|
|
152
|
+
if (dht_client_v6_ && dht_client_v6_->is_running()) {
|
|
153
|
+
started |= dht_client_v6_->announce_peer(info_hash, port, peer_callback);
|
|
154
|
+
}
|
|
155
|
+
return started;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
bool RatsClient::is_dht_running() const {
|
|
159
|
+
return (dht_client_ && dht_client_->is_running()) ||
|
|
160
|
+
(dht_client_v6_ && dht_client_v6_->is_running());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
size_t RatsClient::get_dht_routing_table_size() const {
|
|
164
|
+
size_t total = 0;
|
|
165
|
+
if (dht_client_) {
|
|
166
|
+
total += dht_client_->get_routing_table_size();
|
|
167
|
+
}
|
|
168
|
+
if (dht_client_v6_) {
|
|
169
|
+
total += dht_client_v6_->get_routing_table_size();
|
|
170
|
+
}
|
|
171
|
+
return total;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
void RatsClient::handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash) {
|
|
175
|
+
LOG_CLIENT_INFO("DHT discovered " << peers.size() << " peers for info hash: " << node_id_to_hex(info_hash));
|
|
176
|
+
|
|
177
|
+
// Auto-connect to discovered peers
|
|
178
|
+
for (const auto& peer : peers) {
|
|
179
|
+
if (!can_connect_to_peer(peer.ip, peer.port)) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
LOG_CLIENT_DEBUG("Attempting to connect to discovered peer: " << peer.ip << ":" << peer.port);
|
|
184
|
+
|
|
185
|
+
// Try to connect to the peer (non-blocking, managed thread for graceful shutdown)
|
|
186
|
+
add_managed_thread(std::thread([this, peer]() {
|
|
187
|
+
if (connect_to_peer(peer.ip, peer.port)) {
|
|
188
|
+
LOG_CLIENT_INFO("Successfully connected to DHT discovered peer: " << peer.ip << ":" << peer.port);
|
|
189
|
+
} else {
|
|
190
|
+
LOG_CLIENT_DEBUG("Failed to connect to DHT discovered peer: " << peer.ip << ":" << peer.port);
|
|
191
|
+
}
|
|
192
|
+
}), "dht-connect-" + peer.ip);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void RatsClient::start_automatic_peer_discovery() {
|
|
197
|
+
if (auto_discovery_running_.load()) {
|
|
198
|
+
LOG_CLIENT_WARN("Automatic peer discovery is already running");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
LOG_CLIENT_INFO("Starting automatic rats peer discovery");
|
|
203
|
+
auto_discovery_running_.store(true);
|
|
204
|
+
auto_discovery_thread_ = std::thread(&RatsClient::automatic_discovery_loop, this);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
void RatsClient::stop_automatic_peer_discovery() {
|
|
208
|
+
if (!auto_discovery_running_.load()) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
LOG_CLIENT_INFO("Stopping automatic peer discovery");
|
|
213
|
+
auto_discovery_running_.store(false);
|
|
214
|
+
|
|
215
|
+
if (auto_discovery_thread_.joinable()) {
|
|
216
|
+
auto_discovery_thread_.join();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
LOG_CLIENT_INFO("Automatic peer discovery stopped");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
bool RatsClient::is_automatic_discovery_running() const {
|
|
223
|
+
return auto_discovery_running_.load();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
std::chrono::seconds RatsClient::calculate_discovery_interval() const {
|
|
227
|
+
int peer_count = get_peer_count();
|
|
228
|
+
|
|
229
|
+
// No peers - aggressive discovery
|
|
230
|
+
if (peer_count == 0) {
|
|
231
|
+
return std::chrono::seconds(15);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Calculate fill ratio
|
|
235
|
+
float fill_ratio = static_cast<float>(peer_count) / static_cast<float>(max_peers_);
|
|
236
|
+
|
|
237
|
+
// Graduated intervals based on fill ratio
|
|
238
|
+
if (fill_ratio < 0.25f) {
|
|
239
|
+
// Less than 25% full - still fairly aggressive
|
|
240
|
+
return std::chrono::seconds(60); // 1 minute
|
|
241
|
+
} else if (fill_ratio < 0.50f) {
|
|
242
|
+
// 25-50% full - moderate
|
|
243
|
+
return std::chrono::seconds(180); // 3 minutes
|
|
244
|
+
} else if (fill_ratio < 0.75f) {
|
|
245
|
+
// 50-75% full - relaxed
|
|
246
|
+
return std::chrono::seconds(600); // 10 minutes
|
|
247
|
+
} else {
|
|
248
|
+
// 75-100% full - very relaxed (mostly just re-announcing)
|
|
249
|
+
return std::chrono::seconds(1800); // 30 minutes
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
void RatsClient::automatic_discovery_loop() {
|
|
254
|
+
LOG_CLIENT_INFO("Automatic peer discovery loop started");
|
|
255
|
+
|
|
256
|
+
// Initial delay to let DHT bootstrap
|
|
257
|
+
{
|
|
258
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
259
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(INITIAL_DISCOVERY_DELAY_SECONDS), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
260
|
+
LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Best-effort: discover our public IP via STUN and derive a BEP 42 node ID from it before
|
|
266
|
+
// we announce, so we announce under the correct ID. This runs on the discovery thread (not
|
|
267
|
+
// a standalone one) on purpose: stop_dht_discovery() joins this thread via
|
|
268
|
+
// stop_automatic_peer_discovery() BEFORE resetting the DHT clients, so set_external_ip()
|
|
269
|
+
// can never touch a destroyed DhtClient. It complements the in-DHT "ip"-field voting, which
|
|
270
|
+
// keeps the node ID correct if the address changes or STUN is unavailable.
|
|
271
|
+
if (auto_discovery_running_.load() && running_.load()) {
|
|
272
|
+
auto mapped = discover_public_address("stun.l.google.com", 19302, 4000);
|
|
273
|
+
if (mapped) {
|
|
274
|
+
// set_external_ip validates the address family, so feeding both instances is safe:
|
|
275
|
+
// only the matching-family DHT adopts it. (A default IPv4 STUN server yields an IPv4
|
|
276
|
+
// address; the IPv6 node ID is learned organically from IPv6 DHT responses.)
|
|
277
|
+
if (dht_client_) {
|
|
278
|
+
dht_client_->set_external_ip(mapped->address);
|
|
279
|
+
}
|
|
280
|
+
if (dht_client_v6_) {
|
|
281
|
+
dht_client_v6_->set_external_ip(mapped->address);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Announce immediately - this also discovers peers during traversal
|
|
287
|
+
announce_rats_peer();
|
|
288
|
+
|
|
289
|
+
auto last_announce = std::chrono::steady_clock::now();
|
|
290
|
+
|
|
291
|
+
while (auto_discovery_running_.load()) {
|
|
292
|
+
auto now = std::chrono::steady_clock::now();
|
|
293
|
+
|
|
294
|
+
// Announce combines both announcing our presence and discovering peers
|
|
295
|
+
// Interval scales based on peer count: aggressive when empty, relaxed when nearly full
|
|
296
|
+
auto interval = calculate_discovery_interval();
|
|
297
|
+
|
|
298
|
+
if (now - last_announce >= interval) {
|
|
299
|
+
LOG_CLIENT_DEBUG("Discovery interval: " << interval.count() << "s (peers: "
|
|
300
|
+
<< get_peer_count() << "/" << max_peers_ << ")");
|
|
301
|
+
announce_rats_peer();
|
|
302
|
+
last_announce = now;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Use conditional variable for responsive shutdown
|
|
306
|
+
{
|
|
307
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
308
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
LOG_CLIENT_INFO("Automatic peer discovery loop stopped");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
void RatsClient::announce_rats_peer() {
|
|
318
|
+
if (!dht_client_ || !dht_client_->is_running()) {
|
|
319
|
+
LOG_CLIENT_WARN("DHT client not running, cannot announce peer");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
std::string discovery_hash = get_discovery_hash();
|
|
324
|
+
LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
|
|
325
|
+
|
|
326
|
+
InfoHash info_hash = hex_to_node_id(discovery_hash);
|
|
327
|
+
|
|
328
|
+
// Skip only if every running DHT network already has this announce in flight.
|
|
329
|
+
// (announce_peer() also dedups internally, so this is just to avoid redundant work.)
|
|
330
|
+
bool v4_busy = !dht_client_ || !dht_client_->is_running() || dht_client_->is_announce_active(info_hash);
|
|
331
|
+
bool v6_busy = !dht_client_v6_ || !dht_client_v6_->is_running() || dht_client_v6_->is_announce_active(info_hash);
|
|
332
|
+
if (v4_busy && v6_busy) {
|
|
333
|
+
LOG_CLIENT_WARN("Announce already in progress for info hash: " << node_id_to_hex(info_hash));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Use announce with callback - combines announce and find_peers in one traversal
|
|
338
|
+
// Peers discovered during traversal will be returned through the callback
|
|
339
|
+
if (announce_for_hash(discovery_hash, listen_port_, [this, info_hash](const std::vector<std::string>& peer_addresses) {
|
|
340
|
+
LOG_CLIENT_INFO("Announce discovered " << peer_addresses.size() << " peers during traversal");
|
|
341
|
+
|
|
342
|
+
// Convert peer addresses to Peer objects for handle_dht_peer_discovery()
|
|
343
|
+
std::vector<Peer> peers;
|
|
344
|
+
peers.reserve(peer_addresses.size());
|
|
345
|
+
for (const auto& peer_address : peer_addresses) {
|
|
346
|
+
std::string ip;
|
|
347
|
+
int port;
|
|
348
|
+
if (parse_address_string(peer_address, ip, port)) {
|
|
349
|
+
peers.push_back(Peer(ip, port));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Auto-connect to discovered peers
|
|
354
|
+
if (!peers.empty()) {
|
|
355
|
+
handle_dht_peer_discovery(peers, info_hash);
|
|
356
|
+
}
|
|
357
|
+
})) {
|
|
358
|
+
LOG_CLIENT_DEBUG("Successfully started announce with peer discovery for discovery hash");
|
|
359
|
+
} else {
|
|
360
|
+
LOG_CLIENT_WARN("Failed to announce peer for discovery");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
std::string RatsClient::get_discovery_hash() const {
|
|
365
|
+
std::lock_guard<std::mutex> lock(protocol_config_mutex_);
|
|
366
|
+
// Generate discovery hash based on current protocol configuration
|
|
367
|
+
std::string discovery_string = custom_protocol_name_ + "_peer_discovery_v" + custom_protocol_version_;
|
|
368
|
+
return SHA1::hash(discovery_string);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
std::string RatsClient::get_rats_peer_discovery_hash() {
|
|
372
|
+
// Well-known hash for rats peer discovery
|
|
373
|
+
// Compute SHA1 hash of "rats_peer_discovery_v1.0"
|
|
374
|
+
return SHA1::hash("rats_peer_discovery_v1.0");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
}
|