librats 0.8.0 → 0.9.1
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 +122 -48
- package/native-src/cmake/ratsConfig.cmake.in +8 -0
- 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 +7 -0
- package/native-src/src/krpc.cpp +161 -96
- package/native-src/src/krpc.h +18 -4
- package/native-src/src/librats.cpp +3 -0
- package/native-src/src/librats.h +61 -154
- package/native-src/src/librats_c.cpp +21 -38
- package/native-src/src/librats_discovery.cpp +107 -39
- package/native-src/src/librats_file_transfer.cpp +25 -103
- package/native-src/src/librats_ice.cpp +4 -0
- package/native-src/src/librats_statistic.cpp +10 -2
- 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
package/native-src/src/librats.h
CHANGED
|
@@ -1166,185 +1166,88 @@ public:
|
|
|
1166
1166
|
// =========================================================================
|
|
1167
1167
|
// File Transfer API
|
|
1168
1168
|
// =========================================================================
|
|
1169
|
-
|
|
1169
|
+
//
|
|
1170
|
+
// Streams files and directory trees to connected peers. A transfer is
|
|
1171
|
+
// offered to the peer, who accepts (choosing a destination) or rejects it.
|
|
1172
|
+
// See file_transfer.h for the FileTransferManager that implements it.
|
|
1173
|
+
|
|
1170
1174
|
/**
|
|
1171
|
-
* Get the file transfer manager instance
|
|
1172
|
-
* @return Reference to the file transfer manager
|
|
1175
|
+
* Get the file transfer manager instance.
|
|
1173
1176
|
*/
|
|
1174
1177
|
FileTransferManager& get_file_transfer_manager();
|
|
1175
|
-
|
|
1178
|
+
|
|
1176
1179
|
/**
|
|
1177
|
-
* Check
|
|
1178
|
-
* @return true if file transfer manager is initialized
|
|
1180
|
+
* Check whether the file transfer manager is initialized.
|
|
1179
1181
|
*/
|
|
1180
1182
|
bool is_file_transfer_available() const;
|
|
1181
|
-
|
|
1182
|
-
// Sending and Requesting
|
|
1183
|
+
|
|
1183
1184
|
/**
|
|
1184
|
-
* Send a file to a peer
|
|
1185
|
-
* @param peer_id
|
|
1186
|
-
* @param file_path
|
|
1187
|
-
* @param remote_filename Optional
|
|
1188
|
-
* @return Transfer ID
|
|
1185
|
+
* Send a file to a peer.
|
|
1186
|
+
* @param peer_id Target peer ID
|
|
1187
|
+
* @param file_path Local file to send
|
|
1188
|
+
* @param remote_filename Optional name to present to the peer
|
|
1189
|
+
* @return Transfer ID, or empty string on immediate failure
|
|
1189
1190
|
*/
|
|
1190
|
-
std::string send_file(const std::string& peer_id, const std::string& file_path,
|
|
1191
|
-
|
|
1192
|
-
|
|
1191
|
+
std::string send_file(const std::string& peer_id, const std::string& file_path,
|
|
1192
|
+
const std::string& remote_filename = "");
|
|
1193
|
+
|
|
1193
1194
|
/**
|
|
1194
|
-
* Send
|
|
1195
|
-
* @param peer_id
|
|
1196
|
-
* @param directory_path Local directory
|
|
1197
|
-
* @param
|
|
1198
|
-
* @
|
|
1199
|
-
* @return Transfer ID if successful, empty string if failed
|
|
1195
|
+
* Send a directory tree (recursively) to a peer.
|
|
1196
|
+
* @param peer_id Target peer ID
|
|
1197
|
+
* @param directory_path Local directory to send
|
|
1198
|
+
* @param remote_name Optional name to present to the peer
|
|
1199
|
+
* @return Transfer ID, or empty string on immediate failure
|
|
1200
1200
|
*/
|
|
1201
1201
|
std::string send_directory(const std::string& peer_id, const std::string& directory_path,
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
* Request a file from a remote peer
|
|
1206
|
-
* @param peer_id Target peer ID
|
|
1207
|
-
* @param remote_file_path Path to file on remote peer
|
|
1208
|
-
* @param local_path Local path where file should be saved
|
|
1209
|
-
* @return Transfer ID if successful, empty string if failed
|
|
1210
|
-
*/
|
|
1211
|
-
std::string request_file(const std::string& peer_id, const std::string& remote_file_path,
|
|
1212
|
-
const std::string& local_path);
|
|
1213
|
-
|
|
1214
|
-
/**
|
|
1215
|
-
* Request a directory from a remote peer
|
|
1216
|
-
* @param peer_id Target peer ID
|
|
1217
|
-
* @param remote_directory_path Path to directory on remote peer
|
|
1218
|
-
* @param local_directory_path Local path where directory should be saved
|
|
1219
|
-
* @param recursive Whether to include subdirectories (default: true)
|
|
1220
|
-
* @return Transfer ID if successful, empty string if failed
|
|
1221
|
-
*/
|
|
1222
|
-
std::string request_directory(const std::string& peer_id, const std::string& remote_directory_path,
|
|
1223
|
-
const std::string& local_directory_path, bool recursive = true);
|
|
1224
|
-
|
|
1225
|
-
// Accept/Reject Operations
|
|
1202
|
+
const std::string& remote_name = "");
|
|
1203
|
+
|
|
1226
1204
|
/**
|
|
1227
|
-
* Accept an incoming file
|
|
1228
|
-
*
|
|
1229
|
-
* @param local_path Local path where file should be saved
|
|
1230
|
-
* @return true if accepted successfully
|
|
1205
|
+
* Accept an incoming transfer. For a file, local_path is the destination
|
|
1206
|
+
* file path; for a directory, it is the destination directory.
|
|
1231
1207
|
*/
|
|
1232
1208
|
bool accept_file_transfer(const std::string& transfer_id, const std::string& local_path);
|
|
1233
|
-
|
|
1209
|
+
|
|
1234
1210
|
/**
|
|
1235
|
-
* Reject an incoming
|
|
1236
|
-
* @param transfer_id Transfer identifier from request
|
|
1237
|
-
* @param reason Optional reason for rejection
|
|
1238
|
-
* @return true if rejected successfully
|
|
1211
|
+
* Reject an incoming transfer.
|
|
1239
1212
|
*/
|
|
1240
1213
|
bool reject_file_transfer(const std::string& transfer_id, const std::string& reason = "");
|
|
1241
|
-
|
|
1242
|
-
/**
|
|
1243
|
-
* Accept an incoming directory transfer
|
|
1244
|
-
* @param transfer_id Transfer identifier from request
|
|
1245
|
-
* @param local_path Local path where directory should be saved
|
|
1246
|
-
* @return true if accepted successfully
|
|
1247
|
-
*/
|
|
1248
|
-
bool accept_directory_transfer(const std::string& transfer_id, const std::string& local_path);
|
|
1249
|
-
|
|
1250
|
-
/**
|
|
1251
|
-
* Reject an incoming directory transfer
|
|
1252
|
-
* @param transfer_id Transfer identifier from request
|
|
1253
|
-
* @param reason Optional reason for rejection
|
|
1254
|
-
* @return true if rejected successfully
|
|
1255
|
-
*/
|
|
1256
|
-
bool reject_directory_transfer(const std::string& transfer_id, const std::string& reason = "");
|
|
1257
|
-
|
|
1258
|
-
// Transfer Control
|
|
1259
|
-
/**
|
|
1260
|
-
* Pause an active file transfer
|
|
1261
|
-
* @param transfer_id Transfer to pause
|
|
1262
|
-
* @return true if paused successfully
|
|
1263
|
-
*/
|
|
1214
|
+
|
|
1215
|
+
/** Pause an active transfer (either direction). */
|
|
1264
1216
|
bool pause_file_transfer(const std::string& transfer_id);
|
|
1265
|
-
|
|
1266
|
-
/**
|
|
1267
|
-
* Resume a paused file transfer
|
|
1268
|
-
* @param transfer_id Transfer to resume
|
|
1269
|
-
* @return true if resumed successfully
|
|
1270
|
-
*/
|
|
1217
|
+
|
|
1218
|
+
/** Resume a paused transfer. */
|
|
1271
1219
|
bool resume_file_transfer(const std::string& transfer_id);
|
|
1272
|
-
|
|
1273
|
-
/**
|
|
1274
|
-
* Cancel an active or paused file transfer
|
|
1275
|
-
* @param transfer_id Transfer to cancel
|
|
1276
|
-
* @return true if cancelled successfully
|
|
1277
|
-
*/
|
|
1220
|
+
|
|
1221
|
+
/** Cancel an active or paused transfer. */
|
|
1278
1222
|
bool cancel_file_transfer(const std::string& transfer_id);
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
/**
|
|
1282
|
-
* Get file transfer progress information
|
|
1283
|
-
* @param transfer_id Transfer to query
|
|
1284
|
-
* @return Progress information or nullptr if not found
|
|
1285
|
-
*/
|
|
1223
|
+
|
|
1224
|
+
/** Get a progress snapshot, or nullptr if the transfer is unknown. */
|
|
1286
1225
|
std::shared_ptr<FileTransferProgress> get_file_transfer_progress(const std::string& transfer_id) const;
|
|
1287
|
-
|
|
1288
|
-
/**
|
|
1289
|
-
* Get all active file transfers
|
|
1290
|
-
* @return Vector of transfer progress objects
|
|
1291
|
-
*/
|
|
1226
|
+
|
|
1227
|
+
/** Get progress snapshots for all non-finished transfers. */
|
|
1292
1228
|
std::vector<std::shared_ptr<FileTransferProgress>> get_active_file_transfers() const;
|
|
1293
|
-
|
|
1294
|
-
/**
|
|
1295
|
-
* Get file transfer statistics
|
|
1296
|
-
* @return JSON object with transfer statistics
|
|
1297
|
-
*/
|
|
1229
|
+
|
|
1230
|
+
/** Get aggregate file transfer statistics as JSON. */
|
|
1298
1231
|
nlohmann::json get_file_transfer_statistics() const;
|
|
1299
|
-
|
|
1300
|
-
/**
|
|
1301
|
-
* Set file transfer configuration
|
|
1302
|
-
* @param config Transfer configuration settings
|
|
1303
|
-
*/
|
|
1232
|
+
|
|
1233
|
+
/** Replace the file transfer configuration. */
|
|
1304
1234
|
void set_file_transfer_config(const FileTransferConfig& config);
|
|
1305
|
-
|
|
1306
|
-
/**
|
|
1307
|
-
* Get current file transfer configuration
|
|
1308
|
-
* @return Current configuration settings
|
|
1309
|
-
*/
|
|
1235
|
+
|
|
1236
|
+
/** Get the current file transfer configuration. */
|
|
1310
1237
|
FileTransferConfig get_file_transfer_config() const;
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
/**
|
|
1320
|
-
* Set file transfer completion callback
|
|
1321
|
-
* @param callback Function to call when transfers complete
|
|
1322
|
-
*/
|
|
1323
|
-
void on_file_transfer_completed(FileTransferCompletedCallback callback);
|
|
1324
|
-
|
|
1325
|
-
/**
|
|
1326
|
-
* Set incoming file transfer request callback
|
|
1327
|
-
* @param callback Function to call when receiving transfer requests
|
|
1328
|
-
*/
|
|
1329
|
-
void on_file_transfer_request(FileTransferRequestCallback callback);
|
|
1330
|
-
|
|
1331
|
-
/**
|
|
1332
|
-
* Set directory transfer progress callback
|
|
1333
|
-
* @param callback Function to call with directory transfer progress
|
|
1334
|
-
*/
|
|
1335
|
-
void on_directory_transfer_progress(DirectoryTransferProgressCallback callback);
|
|
1336
|
-
|
|
1337
|
-
/**
|
|
1338
|
-
* Set file request callback (called when receiving file requests)
|
|
1339
|
-
* @param callback Function to call when receiving file requests
|
|
1340
|
-
*/
|
|
1341
|
-
void on_file_request(FileRequestCallback callback);
|
|
1342
|
-
|
|
1238
|
+
|
|
1239
|
+
/** Set the progress callback (fires for both directions). */
|
|
1240
|
+
void on_file_transfer_progress(TransferProgressCallback callback);
|
|
1241
|
+
|
|
1242
|
+
/** Set the completion callback (fires once per transfer). */
|
|
1243
|
+
void on_file_transfer_completed(TransferCompletedCallback callback);
|
|
1244
|
+
|
|
1343
1245
|
/**
|
|
1344
|
-
* Set
|
|
1345
|
-
*
|
|
1246
|
+
* Set the incoming-offer callback. The handler should call
|
|
1247
|
+
* accept_file_transfer()/reject_file_transfer(). Without it, offers are
|
|
1248
|
+
* auto-rejected.
|
|
1346
1249
|
*/
|
|
1347
|
-
void
|
|
1250
|
+
void on_file_transfer_request(TransferOfferCallback callback);
|
|
1348
1251
|
|
|
1349
1252
|
// =========================================================================
|
|
1350
1253
|
// ICE (NAT Traversal) API
|
|
@@ -2059,9 +1962,13 @@ private:
|
|
|
2059
1962
|
JsonDataCallback json_data_callback_;
|
|
2060
1963
|
DisconnectCallback disconnect_callback_;
|
|
2061
1964
|
|
|
2062
|
-
// DHT
|
|
1965
|
+
// DHT clients for peer discovery. IPv4 and IPv6 are separate Kademlia networks
|
|
1966
|
+
// (BEP 32), so each family runs its own client. dht_client_ (IPv4) is also the one
|
|
1967
|
+
// shared with the BitTorrent subsystem; dht_client_v6_ is created best-effort when
|
|
1968
|
+
// IPv6 is available.
|
|
2063
1969
|
std::unique_ptr<DhtClient> dht_client_;
|
|
2064
|
-
|
|
1970
|
+
std::unique_ptr<DhtClient> dht_client_v6_;
|
|
1971
|
+
|
|
2065
1972
|
// mDNS client for local network discovery
|
|
2066
1973
|
std::unique_ptr<MdnsClient> mdns_client_;
|
|
2067
1974
|
std::function<void(const std::string&, int, const std::string&)> mdns_callback_;
|
|
@@ -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
|
|
@@ -13,94 +13,124 @@ bool RatsClient::start_dht_discovery(int dht_port) {
|
|
|
13
13
|
LOG_CLIENT_WARN("DHT discovery is already running");
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
LOG_CLIENT_INFO("Starting DHT discovery on port " << dht_port <<
|
|
18
18
|
(bind_address_.empty() ? "" : " bound to " + bind_address_));
|
|
19
|
-
|
|
20
|
-
|
|
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);
|
|
21
24
|
if (!dht_client_->start()) {
|
|
22
|
-
LOG_CLIENT_ERROR("Failed to start DHT client");
|
|
25
|
+
LOG_CLIENT_ERROR("Failed to start IPv4 DHT client");
|
|
23
26
|
dht_client_.reset();
|
|
24
27
|
return false;
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
// Bootstrap with default nodes
|
|
28
|
-
auto bootstrap_nodes = DhtClient::get_default_bootstrap_nodes();
|
|
29
29
|
if (!dht_client_->bootstrap(bootstrap_nodes)) {
|
|
30
|
-
LOG_CLIENT_WARN("Failed to bootstrap DHT");
|
|
30
|
+
LOG_CLIENT_WARN("Failed to bootstrap IPv4 DHT");
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
//
|
|
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).
|
|
34
48
|
start_automatic_peer_discovery();
|
|
35
|
-
|
|
49
|
+
|
|
36
50
|
LOG_CLIENT_INFO("DHT discovery started successfully");
|
|
37
51
|
return true;
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
void RatsClient::stop_dht_discovery() {
|
|
41
|
-
if (!dht_client_) {
|
|
55
|
+
if (!dht_client_ && !dht_client_v6_) {
|
|
42
56
|
return;
|
|
43
57
|
}
|
|
44
|
-
|
|
58
|
+
|
|
45
59
|
LOG_CLIENT_INFO("Stopping DHT discovery");
|
|
46
|
-
|
|
60
|
+
|
|
47
61
|
// Stop automatic peer discovery
|
|
48
62
|
stop_automatic_peer_discovery();
|
|
49
|
-
|
|
50
|
-
dht_client_
|
|
51
|
-
|
|
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
|
+
}
|
|
52
72
|
LOG_CLIENT_INFO("DHT discovery stopped");
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::function<void(const std::vector<std::string>&)> callback) {
|
|
56
|
-
if (!
|
|
76
|
+
if (!is_dht_running()) {
|
|
57
77
|
LOG_CLIENT_ERROR("DHT client not running");
|
|
58
78
|
return false;
|
|
59
79
|
}
|
|
60
|
-
|
|
80
|
+
|
|
61
81
|
if (content_hash.length() != CONTENT_HASH_HEX_LENGTH) {
|
|
62
82
|
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected " << CONTENT_HASH_HEX_LENGTH << ")");
|
|
63
83
|
return false;
|
|
64
84
|
}
|
|
65
|
-
|
|
85
|
+
|
|
66
86
|
LOG_CLIENT_INFO("Finding peers for content hash: " << content_hash);
|
|
67
|
-
|
|
87
|
+
|
|
68
88
|
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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&) {
|
|
72
92
|
std::vector<std::string> peer_addresses;
|
|
93
|
+
peer_addresses.reserve(peers.size());
|
|
73
94
|
for (const auto& peer : peers) {
|
|
74
95
|
peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
|
|
75
96
|
}
|
|
76
|
-
|
|
77
97
|
if (callback) {
|
|
78
98
|
callback(peer_addresses);
|
|
79
99
|
}
|
|
80
|
-
}
|
|
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;
|
|
81
111
|
}
|
|
82
112
|
|
|
83
113
|
bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port,
|
|
84
114
|
std::function<void(const std::vector<std::string>&)> callback) {
|
|
85
|
-
if (!
|
|
115
|
+
if (!is_dht_running()) {
|
|
86
116
|
LOG_CLIENT_ERROR("DHT client not running");
|
|
87
117
|
return false;
|
|
88
118
|
}
|
|
89
|
-
|
|
119
|
+
|
|
90
120
|
if (content_hash.length() != CONTENT_HASH_HEX_LENGTH) {
|
|
91
121
|
LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected " << CONTENT_HASH_HEX_LENGTH << ")");
|
|
92
122
|
return false;
|
|
93
123
|
}
|
|
94
|
-
|
|
124
|
+
|
|
95
125
|
if (port == 0) {
|
|
96
126
|
port = listen_port_;
|
|
97
127
|
}
|
|
98
|
-
|
|
128
|
+
|
|
99
129
|
LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
|
|
100
130
|
<< (callback ? " with peer callback" : ""));
|
|
101
|
-
|
|
131
|
+
|
|
102
132
|
InfoHash info_hash = hex_to_node_id(content_hash);
|
|
103
|
-
|
|
133
|
+
|
|
104
134
|
// Create wrapper callback that converts Peer to string addresses (if callback provided)
|
|
105
135
|
PeerDiscoveryCallback peer_callback = nullptr;
|
|
106
136
|
if (callback) {
|
|
@@ -113,19 +143,32 @@ bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t por
|
|
|
113
143
|
callback(peer_addresses);
|
|
114
144
|
};
|
|
115
145
|
}
|
|
116
|
-
|
|
117
|
-
|
|
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;
|
|
118
156
|
}
|
|
119
157
|
|
|
120
158
|
bool RatsClient::is_dht_running() const {
|
|
121
|
-
return dht_client_ && dht_client_->is_running()
|
|
159
|
+
return (dht_client_ && dht_client_->is_running()) ||
|
|
160
|
+
(dht_client_v6_ && dht_client_v6_->is_running());
|
|
122
161
|
}
|
|
123
162
|
|
|
124
163
|
size_t RatsClient::get_dht_routing_table_size() const {
|
|
125
|
-
|
|
126
|
-
|
|
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();
|
|
127
170
|
}
|
|
128
|
-
return
|
|
171
|
+
return total;
|
|
129
172
|
}
|
|
130
173
|
|
|
131
174
|
void RatsClient::handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash) {
|
|
@@ -219,6 +262,27 @@ void RatsClient::automatic_discovery_loop() {
|
|
|
219
262
|
}
|
|
220
263
|
}
|
|
221
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
|
+
|
|
222
286
|
// Announce immediately - this also discovers peers during traversal
|
|
223
287
|
announce_rats_peer();
|
|
224
288
|
|
|
@@ -261,7 +325,11 @@ void RatsClient::announce_rats_peer() {
|
|
|
261
325
|
|
|
262
326
|
InfoHash info_hash = hex_to_node_id(discovery_hash);
|
|
263
327
|
|
|
264
|
-
if
|
|
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) {
|
|
265
333
|
LOG_CLIENT_WARN("Announce already in progress for info hash: " << node_id_to_hex(info_hash));
|
|
266
334
|
return;
|
|
267
335
|
}
|