librats 1.0.2 → 2.2.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 +145 -331
- package/binding.gyp +16 -3
- package/lib/index.d.ts +288 -696
- package/lib/index.js +407 -44
- package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
- package/native-src/CMakeLists.txt +404 -179
- package/native-src/LICENSE +1 -1
- package/native-src/src/librats/bindings/rats.cpp +762 -0
- package/native-src/src/librats/bindings/rats.h +380 -0
- package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
- package/native-src/src/librats/bittorrent/bencode.h +176 -0
- package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
- package/native-src/src/librats/bittorrent/bitfield.h +76 -0
- package/native-src/src/librats/bittorrent/byte_io.h +58 -0
- package/native-src/src/librats/bittorrent/choker.cpp +25 -0
- package/native-src/src/librats/bittorrent/choker.h +46 -0
- package/native-src/src/librats/bittorrent/client.cpp +413 -0
- package/native-src/src/librats/bittorrent/client.h +227 -0
- package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
- package/native-src/src/librats/bittorrent/disk_io.h +150 -0
- package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
- package/native-src/src/librats/bittorrent/extensions.h +94 -0
- package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
- package/native-src/src/librats/bittorrent/file_storage.h +79 -0
- package/native-src/src/librats/bittorrent/log.h +42 -0
- package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
- package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
- package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
- package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
- package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
- package/native-src/src/librats/bittorrent/peer_list.h +75 -0
- package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
- package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
- package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
- package/native-src/src/librats/bittorrent/reactor.h +89 -0
- package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
- package/native-src/src/librats/bittorrent/resume_data.h +41 -0
- package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
- package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
- package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
- package/native-src/src/librats/bittorrent/torrent.h +260 -0
- package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
- package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
- package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
- package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
- package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
- package/native-src/src/librats/bittorrent/tracker.h +108 -0
- package/native-src/src/librats/bittorrent/types.cpp +206 -0
- package/native-src/src/librats/bittorrent/types.h +86 -0
- package/native-src/src/librats/core/address.cpp +35 -0
- package/native-src/src/librats/core/address.h +78 -0
- package/native-src/src/librats/core/bytes.h +69 -0
- package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
- package/native-src/src/librats/core/chained_send_buffer.h +183 -0
- package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
- package/native-src/src/librats/core/endpoint_parse.h +31 -0
- package/native-src/src/librats/core/event_bus.h +70 -0
- package/native-src/src/librats/core/host_endpoint.h +56 -0
- package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
- package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
- package/native-src/src/librats/core/ip_address.cpp +120 -0
- package/native-src/src/librats/core/ip_address.h +109 -0
- package/native-src/src/librats/core/mpsc_queue.h +47 -0
- package/native-src/src/librats/core/notifier.h +74 -0
- package/native-src/src/librats/core/receive_buffer.cpp +219 -0
- package/native-src/src/librats/core/receive_buffer.h +171 -0
- package/native-src/src/librats/core/service_registry.h +58 -0
- package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
- package/native-src/src/librats/core/socket.h +496 -0
- package/native-src/src/librats/core/timer_queue.h +105 -0
- package/native-src/src/librats/core/types.cpp +43 -0
- package/native-src/src/librats/core/types.h +103 -0
- package/native-src/src/librats/core/wakeup_pipe.h +83 -0
- package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
- package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
- package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
- package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
- package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
- package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
- package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
- package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
- package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
- package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
- package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
- package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
- package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
- package/native-src/src/librats/crypto/hkdf.c +266 -0
- package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
- package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
- package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
- package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
- package/native-src/src/librats/crypto/poly1305.h +37 -0
- package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
- package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
- package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
- package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
- package/native-src/src/librats/dht/announce.cpp +37 -0
- package/native-src/src/librats/dht/announce.h +41 -0
- package/native-src/src/librats/dht/bep42.cpp +109 -0
- package/native-src/src/librats/dht/bep42.h +48 -0
- package/native-src/src/librats/dht/dht.cpp +501 -0
- package/native-src/src/librats/dht/dht.h +119 -0
- package/native-src/src/librats/dht/dht_runner.cpp +103 -0
- package/native-src/src/librats/dht/dht_runner.h +71 -0
- package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
- package/native-src/src/librats/dht/dos_blocker.h +47 -0
- package/native-src/src/librats/dht/find_peers.cpp +52 -0
- package/native-src/src/librats/dht/find_peers.h +73 -0
- package/native-src/src/librats/dht/id.h +167 -0
- package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
- package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
- package/native-src/src/librats/dht/log.h +38 -0
- package/native-src/src/librats/dht/node.cpp +473 -0
- package/native-src/src/librats/dht/node.h +164 -0
- package/native-src/src/librats/dht/node_entry.h +81 -0
- package/native-src/src/librats/dht/observer.h +72 -0
- package/native-src/src/librats/dht/persistence.cpp +90 -0
- package/native-src/src/librats/dht/persistence.h +32 -0
- package/native-src/src/librats/dht/routing_table.cpp +559 -0
- package/native-src/src/librats/dht/routing_table.h +185 -0
- package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
- package/native-src/src/librats/dht/rpc_manager.h +77 -0
- package/native-src/src/librats/dht/storage.cpp +92 -0
- package/native-src/src/librats/dht/storage.h +74 -0
- package/native-src/src/librats/dht/transport.h +27 -0
- package/native-src/src/librats/dht/traversal.cpp +326 -0
- package/native-src/src/librats/dht/traversal.h +120 -0
- package/native-src/src/librats/dht/udp_transport.cpp +49 -0
- package/native-src/src/librats/dht/udp_transport.h +51 -0
- package/native-src/src/librats/mdns/log.h +22 -0
- package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
- package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
- package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
- package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
- package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
- package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
- package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
- package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
- package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
- package/native-src/src/librats/node/circuit_service.h +84 -0
- package/native-src/src/librats/node/config.h +110 -0
- package/native-src/src/librats/node/dial_service.h +54 -0
- package/native-src/src/librats/node/dialer.cpp +264 -0
- package/native-src/src/librats/node/dialer.h +188 -0
- package/native-src/src/librats/node/host_events.h +26 -0
- package/native-src/src/librats/node/identify.cpp +130 -0
- package/native-src/src/librats/node/identify.h +71 -0
- package/native-src/src/librats/node/nat_status.cpp +103 -0
- package/native-src/src/librats/node/nat_status.h +118 -0
- package/native-src/src/librats/node/node.cpp +865 -0
- package/native-src/src/librats/node/node.h +344 -0
- package/native-src/src/librats/node/node_context.h +33 -0
- package/native-src/src/librats/node/peer_network.h +91 -0
- package/native-src/src/librats/peer/peer.h +49 -0
- package/native-src/src/librats/peer/peer_book.cpp +181 -0
- package/native-src/src/librats/peer/peer_book.h +88 -0
- package/native-src/src/librats/peer/peer_id.cpp +72 -0
- package/native-src/src/librats/peer/peer_id.h +62 -0
- package/native-src/src/librats/peer/peer_info.h +37 -0
- package/native-src/src/librats/peer/peer_table.cpp +170 -0
- package/native-src/src/librats/peer/peer_table.h +148 -0
- package/native-src/src/librats/security/handshaker.h +66 -0
- package/native-src/src/librats/security/identity.h +43 -0
- package/native-src/src/librats/security/noise_security.cpp +122 -0
- package/native-src/src/librats/security/noise_security.h +37 -0
- package/native-src/src/librats/security/plaintext_security.h +106 -0
- package/native-src/src/librats/security/session.h +37 -0
- package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
- package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
- package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
- package/native-src/src/librats/subsystems/bittorrent.h +136 -0
- package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
- package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
- package/native-src/src/librats/subsystems/dht_service.h +36 -0
- package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
- package/native-src/src/librats/subsystems/file_transfer.h +367 -0
- package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
- package/native-src/src/librats/subsystems/hole_punch.h +290 -0
- package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
- package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
- package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
- package/native-src/src/librats/subsystems/message_json.cpp +112 -0
- package/native-src/src/librats/subsystems/message_json.h +88 -0
- package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
- package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
- package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
- package/native-src/src/librats/subsystems/ping_service.h +66 -0
- package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
- package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
- package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
- package/native-src/src/librats/subsystems/pubsub.h +175 -0
- package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
- package/native-src/src/librats/subsystems/reconnection.h +126 -0
- package/native-src/src/librats/subsystems/relay.cpp +1142 -0
- package/native-src/src/librats/subsystems/relay.h +211 -0
- package/native-src/src/librats/subsystems/relay_service.h +46 -0
- package/native-src/src/librats/transport/connection.cpp +343 -0
- package/native-src/src/librats/transport/connection.h +283 -0
- package/native-src/src/librats/transport/link.h +96 -0
- package/native-src/src/librats/transport/reactor.cpp +588 -0
- package/native-src/src/librats/transport/reactor.h +262 -0
- package/native-src/src/librats/transport/reactor_pool.h +81 -0
- package/native-src/src/librats/transport/relay_link.cpp +208 -0
- package/native-src/src/librats/transport/relay_link.h +303 -0
- package/native-src/src/librats/transport/tcp_link.cpp +49 -0
- package/native-src/src/librats/transport/tcp_link.h +43 -0
- package/native-src/src/librats/transport/udp_mux.cpp +617 -0
- package/native-src/src/librats/transport/udp_mux.h +363 -0
- package/native-src/src/librats/transport/udp_packet.cpp +121 -0
- package/native-src/src/librats/transport/udp_packet.h +190 -0
- package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
- package/native-src/src/librats/transport/udp_stream.h +614 -0
- package/native-src/src/librats/util/features.h.in +51 -0
- package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
- package/native-src/src/librats/util/fs.h +136 -0
- package/native-src/src/librats/util/json.cpp +1002 -0
- package/native-src/src/librats/util/json.h +444 -0
- package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
- package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
- package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
- package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
- package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
- package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
- package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
- package/native-src/src/librats/util/rats_export.h +69 -0
- package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
- package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
- package/native-src/src/librats/wire/frame.cpp +76 -0
- package/native-src/src/librats/wire/frame.h +111 -0
- package/native-src/src/librats/wire/message_router.cpp +45 -0
- package/native-src/src/librats/wire/message_router.h +47 -0
- package/package.json +5 -4
- package/scripts/build-librats.js +1 -0
- package/scripts/postinstall.js +3 -3
- package/scripts/prepare-package.js +4 -4
- package/scripts/verify-installation.js +63 -105
- package/src/librats_node.cpp +1067 -1323
- package/native-src/src/bencode.cpp +0 -485
- package/native-src/src/bencode.h +0 -145
- package/native-src/src/bittorrent.cpp +0 -14
- package/native-src/src/bittorrent.h +0 -74
- package/native-src/src/bt_bitfield.cpp +0 -372
- package/native-src/src/bt_bitfield.h +0 -316
- package/native-src/src/bt_choker.cpp +0 -228
- package/native-src/src/bt_choker.h +0 -147
- package/native-src/src/bt_client.cpp +0 -1047
- package/native-src/src/bt_client.h +0 -445
- package/native-src/src/bt_create_torrent.cpp +0 -677
- package/native-src/src/bt_create_torrent.h +0 -473
- package/native-src/src/bt_extension.cpp +0 -469
- package/native-src/src/bt_extension.h +0 -309
- package/native-src/src/bt_file_storage.cpp +0 -261
- package/native-src/src/bt_file_storage.h +0 -298
- package/native-src/src/bt_handshake.cpp +0 -134
- package/native-src/src/bt_handshake.h +0 -157
- package/native-src/src/bt_messages.cpp +0 -364
- package/native-src/src/bt_messages.h +0 -324
- package/native-src/src/bt_network.cpp +0 -1007
- package/native-src/src/bt_network.h +0 -417
- package/native-src/src/bt_peer_connection.cpp +0 -742
- package/native-src/src/bt_peer_connection.h +0 -592
- package/native-src/src/bt_piece_picker.cpp +0 -786
- package/native-src/src/bt_piece_picker.h +0 -473
- package/native-src/src/bt_resume_data.cpp +0 -410
- package/native-src/src/bt_resume_data.h +0 -249
- package/native-src/src/bt_torrent.cpp +0 -2120
- package/native-src/src/bt_torrent.h +0 -641
- package/native-src/src/bt_torrent_info.cpp +0 -659
- package/native-src/src/bt_torrent_info.h +0 -418
- package/native-src/src/bt_types.h +0 -621
- package/native-src/src/chained_send_buffer.cpp +0 -75
- package/native-src/src/chained_send_buffer.h +0 -137
- package/native-src/src/crypto/hkdf.c +0 -266
- package/native-src/src/crypto/poly1305.h +0 -36
- package/native-src/src/dht.cpp +0 -3311
- package/native-src/src/dht.h +0 -717
- package/native-src/src/disk_io.cpp +0 -632
- package/native-src/src/disk_io.h +0 -315
- package/native-src/src/file_transfer.cpp +0 -1415
- package/native-src/src/file_transfer.h +0 -286
- package/native-src/src/fs.h +0 -108
- package/native-src/src/gossipsub.cpp +0 -1139
- package/native-src/src/gossipsub.h +0 -403
- package/native-src/src/ice.cpp +0 -893
- package/native-src/src/ice.h +0 -559
- package/native-src/src/json.hpp +0 -25526
- package/native-src/src/librats.cpp +0 -2378
- package/native-src/src/librats.h +0 -2324
- package/native-src/src/librats_bittorrent.cpp +0 -601
- package/native-src/src/librats_c.cpp +0 -1557
- package/native-src/src/librats_c.h +0 -323
- package/native-src/src/librats_discovery.cpp +0 -402
- package/native-src/src/librats_encryption.cpp +0 -275
- package/native-src/src/librats_file_transfer.cpp +0 -144
- package/native-src/src/librats_gossipsub.cpp +0 -289
- package/native-src/src/librats_ice.cpp +0 -213
- package/native-src/src/librats_log_macros.h +0 -36
- package/native-src/src/librats_logging.cpp +0 -173
- package/native-src/src/librats_mdns.cpp +0 -166
- package/native-src/src/librats_persistence.cpp +0 -796
- package/native-src/src/librats_portmap.cpp +0 -419
- package/native-src/src/librats_reconnection.cpp +0 -218
- package/native-src/src/librats_statistic.cpp +0 -105
- package/native-src/src/librats_storage.cpp +0 -189
- package/native-src/src/rats_export.h +0 -17
- package/native-src/src/receive_buffer.cpp +0 -82
- package/native-src/src/receive_buffer.h +0 -127
- package/native-src/src/socket.h +0 -228
- package/native-src/src/threadmanager.cpp +0 -105
- package/native-src/src/threadmanager.h +0 -53
- package/native-src/src/tracker.cpp +0 -1264
- package/native-src/src/tracker.h +0 -319
- package/native-src/src/turn.cpp +0 -762
- package/native-src/src/turn.h +0 -460
- package/native-src/src/wakeup_pipe.h +0 -60
- /package/native-src/src/{os.h → librats/util/os.h} +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#include "librats/subsystems/bittorrent.h"
|
|
2
|
+
#include "librats/subsystems/dht_service.h"
|
|
3
|
+
#include "librats/node/node_context.h"
|
|
4
|
+
#include "librats/util/logger.h"
|
|
5
|
+
|
|
6
|
+
#include <chrono>
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include <thread>
|
|
9
|
+
#include <utility>
|
|
10
|
+
|
|
11
|
+
namespace librats {
|
|
12
|
+
|
|
13
|
+
Bittorrent::Bittorrent(Config config) : config_(std::move(config)) {}
|
|
14
|
+
|
|
15
|
+
Bittorrent::Bittorrent(const bittorrent::Client::Config& client_config) {
|
|
16
|
+
config_.client = client_config;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
Bittorrent::~Bittorrent() { stop(); }
|
|
20
|
+
|
|
21
|
+
void Bittorrent::attach(NodeContext& ctx) {
|
|
22
|
+
// We only need the service registry — to discover a sibling DhtDiscovery at
|
|
23
|
+
// start(). BitTorrent runs its own transport, so it never touches ctx.network.
|
|
24
|
+
services_ = &ctx.services;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void Bittorrent::start() {
|
|
28
|
+
if (client_) return; // already running
|
|
29
|
+
client_ = std::make_unique<bittorrent::Client>(config_.client);
|
|
30
|
+
|
|
31
|
+
// Borrow the node's DHT swarm if a DhtDiscovery published one and it is up.
|
|
32
|
+
// set_external_dht() must be called before start(); otherwise the client runs
|
|
33
|
+
// DHT-less (trackers/PEX/manual peers still work).
|
|
34
|
+
shared_dht_ = false;
|
|
35
|
+
if (config_.use_node_dht && services_) {
|
|
36
|
+
if (auto* svc = services_->get<DhtService>()) {
|
|
37
|
+
if (DhtClient* shared = svc->dht_client()) {
|
|
38
|
+
client_->set_external_dht(shared);
|
|
39
|
+
shared_dht_ = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
LOG_INFO("bt.node", (shared_dht_ ? "sharing the node's DHT swarm"
|
|
44
|
+
: "running BitTorrent without a DHT"));
|
|
45
|
+
|
|
46
|
+
client_->start();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void Bittorrent::stop() {
|
|
50
|
+
if (!client_) return;
|
|
51
|
+
|
|
52
|
+
// Wake every in-flight metadata watcher and wait for them to finish before we
|
|
53
|
+
// tear down client_, so a watcher's reactor.post() can't race teardown.
|
|
54
|
+
{
|
|
55
|
+
std::unique_lock<std::mutex> lk(meta_mutex_);
|
|
56
|
+
meta_stopping_ = true;
|
|
57
|
+
meta_cv_.notify_all();
|
|
58
|
+
meta_drain_cv_.wait(lk, [&] { return meta_inflight_ == 0; });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
client_->stop(); // never stops the borrowed external DHT (owner's lifecycle)
|
|
62
|
+
client_.reset();
|
|
63
|
+
shared_dht_ = false;
|
|
64
|
+
|
|
65
|
+
std::lock_guard<std::mutex> lk(meta_mutex_);
|
|
66
|
+
meta_stopping_ = false; // ready for a future start()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
DhtClient* Bittorrent::active_dht() const {
|
|
70
|
+
return client_ ? client_->get_dht_client() : nullptr;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//=============================================================================
|
|
74
|
+
// Spider mode wrappers
|
|
75
|
+
//=============================================================================
|
|
76
|
+
|
|
77
|
+
void Bittorrent::set_spider_mode(bool enable) {
|
|
78
|
+
if (auto* d = active_dht()) d->set_spider_mode(enable);
|
|
79
|
+
else LOG_WARN("bt.node", "set_spider_mode ignored: no shared DHT");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
bool Bittorrent::is_spider_mode() const {
|
|
83
|
+
auto* d = active_dht();
|
|
84
|
+
return d && d->is_spider_mode();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void Bittorrent::set_spider_announce_callback(SpiderAnnounceCallback callback) {
|
|
88
|
+
if (auto* d = active_dht()) d->set_spider_announce_callback(std::move(callback));
|
|
89
|
+
else LOG_WARN("bt.node", "set_spider_announce_callback ignored: no shared DHT");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
void Bittorrent::set_spider_ignore(bool ignore) {
|
|
93
|
+
if (auto* d = active_dht()) d->set_spider_ignore(ignore);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
bool Bittorrent::is_spider_ignoring() const {
|
|
97
|
+
auto* d = active_dht();
|
|
98
|
+
return d && d->is_spider_ignoring();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
void Bittorrent::spider_walk() {
|
|
102
|
+
if (auto* d = active_dht()) d->spider_walk();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
size_t Bittorrent::spider_pool_size() const {
|
|
106
|
+
auto* d = active_dht();
|
|
107
|
+
return d ? d->get_spider_pool_size() : 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
size_t Bittorrent::spider_visited_count() const {
|
|
111
|
+
auto* d = active_dht();
|
|
112
|
+
return d ? d->get_spider_visited_count() : 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
void Bittorrent::clear_spider_state() {
|
|
116
|
+
if (auto* d = active_dht()) d->clear_spider_state();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//=============================================================================
|
|
120
|
+
// Metadata-only fetch (BEP 9)
|
|
121
|
+
//=============================================================================
|
|
122
|
+
|
|
123
|
+
void Bittorrent::get_torrent_metadata(const std::string& info_hash_hex,
|
|
124
|
+
MetadataCallback callback, int timeout_ms) {
|
|
125
|
+
fetch_metadata_impl(info_hash_hex, "", 0, /*direct=*/false, std::move(callback), timeout_ms);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void Bittorrent::get_torrent_metadata_from_peer(const std::string& info_hash_hex,
|
|
129
|
+
const std::string& ip, uint16_t port,
|
|
130
|
+
MetadataCallback callback, int timeout_ms) {
|
|
131
|
+
fetch_metadata_impl(info_hash_hex, ip, port, /*direct=*/true, std::move(callback), timeout_ms);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
void Bittorrent::fetch_metadata_impl(const std::string& info_hash_hex, const std::string& ip,
|
|
135
|
+
uint16_t port, bool direct, MetadataCallback callback,
|
|
136
|
+
int timeout_ms) {
|
|
137
|
+
if (!client_) {
|
|
138
|
+
if (callback) callback(bittorrent::TorrentInfo{}, false, "bittorrent not running");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
auto ih = bittorrent::info_hash_from_hex(info_hash_hex);
|
|
142
|
+
if (!ih) {
|
|
143
|
+
if (callback) callback(bittorrent::TorrentInfo{}, false, "invalid info hash");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Completion state shared between the (reactor-thread) metadata callback and
|
|
148
|
+
// the watcher thread that waits on it.
|
|
149
|
+
struct State {
|
|
150
|
+
bool done = false;
|
|
151
|
+
bool success = false;
|
|
152
|
+
bittorrent::TorrentInfo info;
|
|
153
|
+
};
|
|
154
|
+
auto state = std::make_shared<State>();
|
|
155
|
+
|
|
156
|
+
{
|
|
157
|
+
std::lock_guard<std::mutex> lk(meta_mutex_);
|
|
158
|
+
if (meta_stopping_) {
|
|
159
|
+
if (callback) callback(bittorrent::TorrentInfo{}, false, "shutting down");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
++meta_inflight_;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// All Client mutations happen on its reactor thread.
|
|
166
|
+
const std::string magnet = "magnet:?xt=urn:btih:" + info_hash_hex;
|
|
167
|
+
client_->reactor().post([this, magnet, ip, port, direct, state] {
|
|
168
|
+
bittorrent::Torrent* t = client_->add_magnet(magnet);
|
|
169
|
+
if (!t) {
|
|
170
|
+
std::lock_guard<std::mutex> lk(meta_mutex_);
|
|
171
|
+
state->done = true;
|
|
172
|
+
meta_cv_.notify_all();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
t->set_metadata_callback([this, state](const bittorrent::TorrentInfo& info) {
|
|
176
|
+
std::lock_guard<std::mutex> lk(meta_mutex_);
|
|
177
|
+
if (state->done) return;
|
|
178
|
+
state->info = info;
|
|
179
|
+
state->success = true;
|
|
180
|
+
state->done = true;
|
|
181
|
+
meta_cv_.notify_all();
|
|
182
|
+
});
|
|
183
|
+
if (direct && !ip.empty() && port != 0) t->add_peer(ip, port);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
std::thread([this, ih = *ih, callback = std::move(callback), timeout_ms, state] {
|
|
187
|
+
bool success = false;
|
|
188
|
+
bittorrent::TorrentInfo info;
|
|
189
|
+
{
|
|
190
|
+
std::unique_lock<std::mutex> wl(meta_mutex_);
|
|
191
|
+
meta_cv_.wait_for(wl, std::chrono::milliseconds(timeout_ms),
|
|
192
|
+
[&] { return state->done || meta_stopping_; });
|
|
193
|
+
state->done = true; // block any late metadata callback
|
|
194
|
+
success = state->success;
|
|
195
|
+
info = state->info;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Drop the temporary torrent on the reactor thread. client_ is alive:
|
|
199
|
+
// stop() waits on meta_drain_cv_ until meta_inflight_ hits 0.
|
|
200
|
+
if (client_) client_->reactor().post([this, ih] { client_->remove_torrent(ih); });
|
|
201
|
+
|
|
202
|
+
if (callback)
|
|
203
|
+
callback(info, success, success ? std::string() : std::string("metadata timeout"));
|
|
204
|
+
|
|
205
|
+
std::lock_guard<std::mutex> dl(meta_mutex_);
|
|
206
|
+
--meta_inflight_;
|
|
207
|
+
meta_drain_cv_.notify_all();
|
|
208
|
+
}).detach();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
} // namespace librats
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file bittorrent.h
|
|
5
|
+
* @brief BitTorrent as a node Subsystem — wraps bittorrent::Client and shares the node's DHT.
|
|
6
|
+
*
|
|
7
|
+
* Attach it like any other subsystem, BEFORE start():
|
|
8
|
+
*
|
|
9
|
+
* auto* dht = node.add_subsystem(std::make_unique<DhtDiscovery>(dht_cfg));
|
|
10
|
+
* auto* bt = node.add_subsystem(std::make_unique<Bittorrent>(bt_cfg));
|
|
11
|
+
* node.start();
|
|
12
|
+
* bt->client()->add_magnet("magnet:?xt=urn:btih:…", "./downloads");
|
|
13
|
+
*
|
|
14
|
+
* BitTorrent keeps its own transport (bittorrent::Client runs its own reactor and
|
|
15
|
+
* the swarm protocol), so this subsystem does NOT touch the node's peer mesh. What
|
|
16
|
+
* it integrates is lifecycle and the DHT: when a DhtDiscovery is also attached, the
|
|
17
|
+
* client borrows that same Kademlia node (DhtService) instead of standing one up —
|
|
18
|
+
* one routing table, one swarm. Attach Bittorrent AFTER DhtDiscovery so the borrowed
|
|
19
|
+
* client is live at start() and (thanks to reverse-order teardown) still alive
|
|
20
|
+
* through stop(). Without a DhtDiscovery the client simply runs DHT-less (trackers,
|
|
21
|
+
* PEX and manually-added peers still work).
|
|
22
|
+
*
|
|
23
|
+
* Spider mode (the rats-search infohash crawler) lives in the DHT layer; the
|
|
24
|
+
* wrappers here delegate to whichever DHT the client ended up using.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
#include "librats/util/rats_export.h"
|
|
28
|
+
#include "librats/node/peer_network.h" // Subsystem, NodeContext (fwd)
|
|
29
|
+
#include "librats/bittorrent/client.h" // bittorrent::Client, TorrentInfo, InfoHash
|
|
30
|
+
#include "librats/dht/dht.h" // DhtClient, SpiderAnnounceCallback
|
|
31
|
+
|
|
32
|
+
#include <condition_variable>
|
|
33
|
+
#include <cstddef>
|
|
34
|
+
#include <cstdint>
|
|
35
|
+
#include <functional>
|
|
36
|
+
#include <memory>
|
|
37
|
+
#include <mutex>
|
|
38
|
+
#include <string>
|
|
39
|
+
|
|
40
|
+
namespace librats {
|
|
41
|
+
|
|
42
|
+
class ServiceRegistry;
|
|
43
|
+
|
|
44
|
+
class RATS_API Bittorrent final : public Subsystem {
|
|
45
|
+
public:
|
|
46
|
+
struct Config {
|
|
47
|
+
bittorrent::Client::Config client; ///< download path, listen port, …
|
|
48
|
+
bool use_node_dht = true; ///< borrow DhtDiscovery's DHT if attached
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Bittorrent() = default;
|
|
52
|
+
explicit Bittorrent(Config config);
|
|
53
|
+
/// Convenience: configure the underlying client directly, share the node DHT.
|
|
54
|
+
explicit Bittorrent(const bittorrent::Client::Config& client_config);
|
|
55
|
+
~Bittorrent() override;
|
|
56
|
+
|
|
57
|
+
Bittorrent(const Bittorrent&) = delete;
|
|
58
|
+
Bittorrent& operator=(const Bittorrent&) = delete;
|
|
59
|
+
|
|
60
|
+
// — Subsystem —
|
|
61
|
+
void attach(NodeContext& ctx) override;
|
|
62
|
+
void start() override;
|
|
63
|
+
void stop() override;
|
|
64
|
+
|
|
65
|
+
/// The underlying client — drive torrents through it (add_magnet,
|
|
66
|
+
/// add_torrent_file, add_torrent_for_seeding, …). Non-null between start() and
|
|
67
|
+
/// stop(); nullptr otherwise.
|
|
68
|
+
bittorrent::Client* client() noexcept { return client_.get(); }
|
|
69
|
+
const bittorrent::Client* client() const noexcept { return client_.get(); }
|
|
70
|
+
|
|
71
|
+
bool is_running() const { return client_ && client_->is_running(); }
|
|
72
|
+
/// True when start() borrowed the node's DHT rather than running DHT-less.
|
|
73
|
+
bool using_node_dht() const noexcept { return shared_dht_; }
|
|
74
|
+
|
|
75
|
+
//=========================================================================
|
|
76
|
+
// Spider mode (rats-search) — DHT-wide infohash crawling.
|
|
77
|
+
// Delegates to the active DHT (the shared node DHT). No-op before start()
|
|
78
|
+
// or when no DHT is shared.
|
|
79
|
+
//
|
|
80
|
+
// Guarded to match dht.h, which declares SpiderAnnounceCallback behind the
|
|
81
|
+
// same macro: the whole subsystem needs RATS_SEARCH_FEATURES to link, but the
|
|
82
|
+
// header must still *parse* without it (IDE indexers, a consumer probing the
|
|
83
|
+
// installed tree) rather than fail on an undeclared type.
|
|
84
|
+
//=========================================================================
|
|
85
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
86
|
+
void set_spider_mode(bool enable);
|
|
87
|
+
bool is_spider_mode() const;
|
|
88
|
+
void set_spider_announce_callback(SpiderAnnounceCallback callback);
|
|
89
|
+
void set_spider_ignore(bool ignore);
|
|
90
|
+
bool is_spider_ignoring() const;
|
|
91
|
+
void spider_walk();
|
|
92
|
+
size_t spider_pool_size() const;
|
|
93
|
+
size_t spider_visited_count() const;
|
|
94
|
+
void clear_spider_state();
|
|
95
|
+
#endif
|
|
96
|
+
|
|
97
|
+
//=========================================================================
|
|
98
|
+
// Metadata-only fetch (BEP 9). Adds a temporary metadata-only magnet torrent,
|
|
99
|
+
// waits up to timeout_ms for its info dict to arrive, removes the temporary
|
|
100
|
+
// torrent, then invokes `callback` with the result (success=false on timeout).
|
|
101
|
+
// `callback` runs on an internal worker thread; it is invoked exactly once.
|
|
102
|
+
//=========================================================================
|
|
103
|
+
using MetadataCallback = std::function<void(const bittorrent::TorrentInfo& info, bool success,
|
|
104
|
+
const std::string& error)>;
|
|
105
|
+
|
|
106
|
+
/// Fetch metadata by searching the DHT/trackers for peers that have it.
|
|
107
|
+
void get_torrent_metadata(const std::string& info_hash_hex,
|
|
108
|
+
MetadataCallback callback, int timeout_ms = 60000);
|
|
109
|
+
|
|
110
|
+
/// Fetch metadata directly from a known peer (the fast path used when a spider
|
|
111
|
+
/// announce told us exactly who has it).
|
|
112
|
+
void get_torrent_metadata_from_peer(const std::string& info_hash_hex,
|
|
113
|
+
const std::string& ip, uint16_t port,
|
|
114
|
+
MetadataCallback callback, int timeout_ms = 60000);
|
|
115
|
+
|
|
116
|
+
private:
|
|
117
|
+
/// The DHT the client is actually using (the shared node DHT), or nullptr.
|
|
118
|
+
DhtClient* active_dht() const;
|
|
119
|
+
|
|
120
|
+
void fetch_metadata_impl(const std::string& info_hash_hex, const std::string& ip,
|
|
121
|
+
uint16_t port, bool direct, MetadataCallback callback, int timeout_ms);
|
|
122
|
+
|
|
123
|
+
Config config_;
|
|
124
|
+
ServiceRegistry* services_ = nullptr;
|
|
125
|
+
std::unique_ptr<bittorrent::Client> client_;
|
|
126
|
+
bool shared_dht_ = false;
|
|
127
|
+
|
|
128
|
+
// In-flight metadata watchers; stop() drains them before tearing down client_.
|
|
129
|
+
std::mutex meta_mutex_;
|
|
130
|
+
std::condition_variable meta_cv_; ///< metadata arrived OR stopping
|
|
131
|
+
std::condition_variable meta_drain_cv_; ///< a watcher finished
|
|
132
|
+
bool meta_stopping_ = false;
|
|
133
|
+
int meta_inflight_ = 0;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
} // namespace librats
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#include "librats/subsystems/dht_discovery.h"
|
|
2
|
+
#include "librats/node/node_context.h"
|
|
3
|
+
#include "librats/node/host_events.h"
|
|
4
|
+
#include "librats/nat/stun.h"
|
|
5
|
+
#include "librats/crypto/sha1.h"
|
|
6
|
+
#include "librats/util/fs.h"
|
|
7
|
+
#include "librats/util/logger.h"
|
|
8
|
+
|
|
9
|
+
namespace librats {
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
int hex_val(char c) {
|
|
13
|
+
if (c >= '0' && c <= '9') return c - '0';
|
|
14
|
+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
15
|
+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// A couple of well-known public STUN servers, tried in order. Kept short so a
|
|
20
|
+
// total-outage startup costs at most a few timeouts before we fall back to voting.
|
|
21
|
+
std::vector<HostEndpoint> default_stun_servers() {
|
|
22
|
+
return { HostEndpoint("stun.l.google.com", 19302), HostEndpoint("stun1.l.google.com", 19302) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The configured bind literal only applies to its own family; the other family
|
|
26
|
+
// binds the wildcard. An empty config means wildcard for both.
|
|
27
|
+
std::string bind_for(const std::string& cfg, bool want_v6) {
|
|
28
|
+
if (cfg.empty()) return "";
|
|
29
|
+
const bool cfg_is_v6 = cfg.find(':') != std::string::npos;
|
|
30
|
+
return (cfg_is_v6 == want_v6) ? cfg : "";
|
|
31
|
+
}
|
|
32
|
+
} // namespace
|
|
33
|
+
|
|
34
|
+
InfoHash DhtDiscovery::hash_for_key(const std::string& key) {
|
|
35
|
+
const std::string hex = SHA1::hash(key); // 40 hex chars
|
|
36
|
+
InfoHash hash{};
|
|
37
|
+
for (size_t i = 0; i < hash.size() && (2 * i + 1) < hex.size(); ++i)
|
|
38
|
+
hash[i] = static_cast<uint8_t>((hex_val(hex[2 * i]) << 4) | hex_val(hex[2 * i + 1]));
|
|
39
|
+
return hash;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
DhtDiscovery::DhtDiscovery(Config config)
|
|
43
|
+
: config_(std::move(config)) {} // hash_ is resolved in attach() (may depend on the node protocol)
|
|
44
|
+
|
|
45
|
+
DhtDiscovery::~DhtDiscovery() { stop(); }
|
|
46
|
+
|
|
47
|
+
void DhtDiscovery::attach(NodeContext& ctx) {
|
|
48
|
+
network_ = &ctx.network;
|
|
49
|
+
// Namespace discovery by the node's protocol unless an explicit key overrides
|
|
50
|
+
// it: peers of the same app/version announce/search the same hash, and ones with
|
|
51
|
+
// a different protocol (which couldn't complete a handshake anyway) never meet in
|
|
52
|
+
// the DHT. A non-empty discovery_key opts into a custom, protocol-independent net.
|
|
53
|
+
const std::string& key = config_.discovery_key.empty() ? network_->protocol()
|
|
54
|
+
: config_.discovery_key;
|
|
55
|
+
hash_ = hash_for_key(key);
|
|
56
|
+
// Publish the DHT as a borrowable capability so a sibling subsystem (e.g.
|
|
57
|
+
// Bittorrent) can share this Kademlia node instead of running a second one.
|
|
58
|
+
ctx.services.provide<DhtService>(this);
|
|
59
|
+
// A host network change means our public endpoint may have moved. Re-probe it
|
|
60
|
+
// via STUN and re-announce so the DHT advertises our current reachable address
|
|
61
|
+
// rather than the previous network's. We only flag + wake here (cheap, on the
|
|
62
|
+
// node's maintenance thread); the actual STUN runs on our own loop thread.
|
|
63
|
+
ctx.events.on<NetworkChanged>([this](const NetworkChanged&) {
|
|
64
|
+
recover_pending_.store(true);
|
|
65
|
+
wake_.notify_all();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
std::unique_ptr<DhtClient> DhtDiscovery::make_client(AddressFamily family) {
|
|
70
|
+
const bool v6 = (family == AddressFamily::IPv6);
|
|
71
|
+
const char* label = v6 ? "IPv6" : "IPv4";
|
|
72
|
+
|
|
73
|
+
auto client = std::make_unique<DhtClient>(config_.dht_port, bind_for(config_.bind_address, v6),
|
|
74
|
+
config_.data_dir, family);
|
|
75
|
+
if (!client->start()) {
|
|
76
|
+
LOG_WARN("dht-discovery", label << " DHT client failed to start (skipping this family)");
|
|
77
|
+
return nullptr;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const std::vector<HostEndpoint> nodes =
|
|
81
|
+
config_.bootstrap_nodes.empty() ? DhtClient::get_default_bootstrap_nodes() : config_.bootstrap_nodes;
|
|
82
|
+
if (!nodes.empty()) client->bootstrap(nodes);
|
|
83
|
+
|
|
84
|
+
LOG_INFO("dht-discovery", label << " DHT on UDP port " << client->get_port());
|
|
85
|
+
return client;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void DhtDiscovery::start() {
|
|
89
|
+
if (running_.exchange(true)) return;
|
|
90
|
+
|
|
91
|
+
// Make sure the routing-table directory exists before the clients try to use it.
|
|
92
|
+
if (!config_.data_dir.empty()) create_directories(config_.data_dir.c_str());
|
|
93
|
+
|
|
94
|
+
if (config_.enable_ipv4) dht_ = make_client(AddressFamily::IPv4);
|
|
95
|
+
if (config_.enable_ipv6) dht6_ = make_client(AddressFamily::IPv6);
|
|
96
|
+
|
|
97
|
+
if (!dht_ && !dht6_) {
|
|
98
|
+
LOG_ERROR("dht-discovery", "Failed to start DHT client on any address family");
|
|
99
|
+
running_.store(false);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
thread_ = std::thread(&DhtDiscovery::loop, this);
|
|
104
|
+
LOG_INFO("dht-discovery", "Started (" << (dht_ ? "IPv4" : "") << (dht_ && dht6_ ? "+" : "")
|
|
105
|
+
<< (dht6_ ? "IPv6" : "") << "), hash key '" << config_.discovery_key << "'");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void DhtDiscovery::stop() {
|
|
109
|
+
if (!running_.exchange(false)) return;
|
|
110
|
+
wake_.notify_all();
|
|
111
|
+
if (thread_.joinable()) thread_.join();
|
|
112
|
+
if (dht_) { dht_->stop(); dht_.reset(); }
|
|
113
|
+
if (dht6_) { dht6_->stop(); dht6_.reset(); }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
bool DhtDiscovery::is_running() const {
|
|
117
|
+
return running_.load() && ((dht_ && dht_->is_running()) || (dht6_ && dht6_->is_running()));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
uint16_t DhtDiscovery::dht_port() const { return dht_ ? static_cast<uint16_t>(dht_->get_port()) : 0; }
|
|
121
|
+
|
|
122
|
+
uint16_t DhtDiscovery::dht_port_v6() const { return dht6_ ? static_cast<uint16_t>(dht6_->get_port()) : 0; }
|
|
123
|
+
|
|
124
|
+
std::string DhtDiscovery::external_address() const {
|
|
125
|
+
// The IPv4 reflexive address is the public-facing one most callers expect;
|
|
126
|
+
// fall back to the IPv6 client's view if only that family is up.
|
|
127
|
+
if (dht_) {
|
|
128
|
+
std::string a = dht_->get_external_address();
|
|
129
|
+
if (!a.empty()) return a;
|
|
130
|
+
}
|
|
131
|
+
return dht6_ ? dht6_->get_external_address() : "";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Discover our reflexive (public) address via STUN and feed it to the DHT so the
|
|
135
|
+
// node id is BEP-42-correct from the start instead of waiting for "ip" voting.
|
|
136
|
+
// Runs on the loop thread; stop() joins that thread before resetting dht_, so the
|
|
137
|
+
// dht_ access here can never touch a freed client.
|
|
138
|
+
void DhtDiscovery::probe_external_ip() {
|
|
139
|
+
std::vector<HostEndpoint> servers = config_.stun_servers.empty() ? default_stun_servers()
|
|
140
|
+
: config_.stun_servers;
|
|
141
|
+
StunClient stun;
|
|
142
|
+
const int timeout = static_cast<int>(config_.stun_timeout.count());
|
|
143
|
+
for (const HostEndpoint& s : servers) {
|
|
144
|
+
if (!running_.load()) return; // stopping; don't keep probing
|
|
145
|
+
StunResult r = stun.binding_request(s.host, s.port, timeout);
|
|
146
|
+
if (r.success && r.mapped_address) {
|
|
147
|
+
const std::string& ip = r.mapped_address->address;
|
|
148
|
+
LOG_INFO("dht-discovery", "STUN reflexive address " << ip << " (via " << s.host << ":" << s.port << ")");
|
|
149
|
+
// Feed it to whichever clients are up; each ignores it if it's not a
|
|
150
|
+
// public address of its own family (e.g. a v4 reflexive on the v6 client).
|
|
151
|
+
if (running_.load()) for_each_client([&](DhtClient& c) { c.set_external_ip(ip); });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
LOG_DEBUG("dht-discovery", "STUN external-IP discovery failed; relying on DHT ip voting");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
void DhtDiscovery::loop() {
|
|
159
|
+
if (config_.discover_external_ip) probe_external_ip();
|
|
160
|
+
|
|
161
|
+
auto last_announce = std::chrono::steady_clock::now() - config_.announce_interval;
|
|
162
|
+
while (running_.load()) {
|
|
163
|
+
// Network changed: re-learn our public IP and force an immediate re-announce.
|
|
164
|
+
if (recover_pending_.exchange(false)) {
|
|
165
|
+
LOG_INFO("dht-discovery", "Network changed — re-probing STUN and re-announcing");
|
|
166
|
+
if (config_.discover_external_ip) probe_external_ip();
|
|
167
|
+
last_announce = std::chrono::steady_clock::now() - config_.announce_interval;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const auto now = std::chrono::steady_clock::now();
|
|
171
|
+
auto deliver = [this](const std::vector<Address>& peers, const InfoHash& h) { on_peers(peers, h); };
|
|
172
|
+
if (now - last_announce >= config_.announce_interval) {
|
|
173
|
+
// announce_peer runs a get_peers traversal and announces on completion, so it
|
|
174
|
+
// discovers peers itself — feed them to on_peers and skip the separate
|
|
175
|
+
// find_peers this cycle, otherwise we'd run two identical traversals per hash
|
|
176
|
+
// (every node queried twice). find_peers covers the faster search cadence in
|
|
177
|
+
// between announces.
|
|
178
|
+
for_each_client([&](DhtClient& c) { c.announce_peer(hash_, network_->listen_port(), deliver); });
|
|
179
|
+
last_announce = now;
|
|
180
|
+
} else {
|
|
181
|
+
for_each_client([&](DhtClient& c) { c.find_peers(hash_, deliver); });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
std::unique_lock<std::mutex> lock(wait_mutex_);
|
|
185
|
+
wake_.wait_for(lock, config_.search_interval,
|
|
186
|
+
[this] { return !running_.load() || recover_pending_.load(); });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
void DhtDiscovery::on_peers(const std::vector<Address>& peers, const InfoHash& /*info_hash*/) {
|
|
191
|
+
for (const Address& peer : peers) {
|
|
192
|
+
if (peer.ip.is_any() || peer.port == 0) continue;
|
|
193
|
+
{
|
|
194
|
+
std::lock_guard<std::mutex> lock(dialed_mutex_);
|
|
195
|
+
if (!dialed_.insert(peer).second) continue; // already dialed this address
|
|
196
|
+
}
|
|
197
|
+
LOG_DEBUG("dht-discovery", "Dialing discovered peer " << peer.to_string());
|
|
198
|
+
network_->connect(peer);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
} // namespace librats
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file dht_discovery.h
|
|
5
|
+
* @brief Peer discovery via the Kademlia DHT — a thin adapter, not a rewrite.
|
|
6
|
+
*
|
|
7
|
+
* Wraps the existing, well-tested DhtClient (src/dht.h) as a Subsystem WITHOUT
|
|
8
|
+
* modifying it. On start it brings up DhtClients (their own UDP sockets + threads),
|
|
9
|
+
* then periodically announces our TCP listen port under a discovery hash and
|
|
10
|
+
* searches that hash, dialing any peers it finds through the node. The discovery
|
|
11
|
+
* hash namespaces peers of the same application (same key → same hash).
|
|
12
|
+
*
|
|
13
|
+
* Dual-stack: IPv4 and IPv6 are separate Kademlia networks (BEP 32), so each runs
|
|
14
|
+
* its own DhtClient. Both announce/search the same hash and feed discovered peers
|
|
15
|
+
* to the same dial path, so a peer reachable over either family is found. Startup
|
|
16
|
+
* is best-effort per family: if one family can't bind (e.g. a host with no usable
|
|
17
|
+
* IPv6) the subsystem still runs on the other.
|
|
18
|
+
*
|
|
19
|
+
* Everything DHT-specific stays in DhtClient; this class only bridges its
|
|
20
|
+
* callbacks to PeerNetwork::connect().
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
#include "librats/util/rats_export.h"
|
|
24
|
+
#include "librats/node/peer_network.h"
|
|
25
|
+
#include "librats/subsystems/dht_service.h"
|
|
26
|
+
#include "librats/dht/dht.h"
|
|
27
|
+
|
|
28
|
+
#include <atomic>
|
|
29
|
+
#include <chrono>
|
|
30
|
+
#include <condition_variable>
|
|
31
|
+
#include <cstdint>
|
|
32
|
+
#include <memory>
|
|
33
|
+
#include <mutex>
|
|
34
|
+
#include <string>
|
|
35
|
+
#include <thread>
|
|
36
|
+
#include <unordered_set>
|
|
37
|
+
#include <vector>
|
|
38
|
+
|
|
39
|
+
namespace librats {
|
|
40
|
+
|
|
41
|
+
class RATS_API DhtDiscovery final : public Subsystem, public DhtService {
|
|
42
|
+
public:
|
|
43
|
+
struct Config {
|
|
44
|
+
uint16_t dht_port = 0; ///< 0 = ephemeral
|
|
45
|
+
std::string bind_address = "";
|
|
46
|
+
std::string data_dir = ""; ///< routing-table persistence dir (empty = cwd). Set to the node's data_dir to co-locate state.
|
|
47
|
+
bool enable_ipv4 = true; ///< run the IPv4 Kademlia network
|
|
48
|
+
bool enable_ipv6 = true; ///< run the IPv6 Kademlia network (BEP 32)
|
|
49
|
+
std::string discovery_key = ""; ///< DHT namespace. Empty → the node's `protocol` (resolved at attach), so peers of the same app/version discover each other and mismatched protocols (which can't handshake anyway) don't even meet. Set non-empty to override with a custom discovery network.
|
|
50
|
+
std::vector<HostEndpoint> bootstrap_nodes; ///< empty → default internet nodes
|
|
51
|
+
std::chrono::milliseconds search_interval{30000}; /// every 30 seconds
|
|
52
|
+
/// Re-announce cadence. Kept at ~⅓ of the peer TTL (librats stores announced peers
|
|
53
|
+
/// for 30 min, libtorrent ~45 min) so we stay findable with a comfortable margin
|
|
54
|
+
/// even if an announce or two is lost — libtorrent's own default is 15 min. A fresh
|
|
55
|
+
/// node still announces immediately at startup regardless of this value (see loop()).
|
|
56
|
+
std::chrono::milliseconds announce_interval{600000}; // 10 min
|
|
57
|
+
|
|
58
|
+
/// Probe a STUN server once at startup to learn our public IP and seed the
|
|
59
|
+
/// DHT node id per BEP 42. Without it we still converge via the slower
|
|
60
|
+
/// in-DHT "ip" voting, but bootstrap under the wrong node id until then.
|
|
61
|
+
bool discover_external_ip = true;
|
|
62
|
+
std::vector<HostEndpoint> stun_servers; ///< empty → built-in public defaults
|
|
63
|
+
std::chrono::milliseconds stun_timeout{3000};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
explicit DhtDiscovery(Config config);
|
|
67
|
+
~DhtDiscovery() override;
|
|
68
|
+
|
|
69
|
+
void attach(NodeContext& ctx) override;
|
|
70
|
+
void start() override;
|
|
71
|
+
void stop() override;
|
|
72
|
+
|
|
73
|
+
bool is_running() const;
|
|
74
|
+
|
|
75
|
+
/// DhtService: hand out the live Kademlia node so siblings (e.g. Bittorrent)
|
|
76
|
+
/// can share this swarm. IPv4 preferred, IPv6 fallback; nullptr before start()
|
|
77
|
+
/// / after stop(). Borrow it during start() and drop it in stop() (see DhtService).
|
|
78
|
+
DhtClient* dht_client() override { return dht_ ? dht_.get() : dht6_.get(); }
|
|
79
|
+
|
|
80
|
+
uint16_t dht_port() const; ///< IPv4 DHT UDP port (0 if not running)
|
|
81
|
+
uint16_t dht_port_v6() const; ///< IPv6 DHT UDP port (0 if not running)
|
|
82
|
+
InfoHash discovery_hash() const { return hash_; }
|
|
83
|
+
|
|
84
|
+
/// Our external (public) IP currently used to derive the DHT node id, learned
|
|
85
|
+
/// via STUN at startup or in-DHT "ip" voting. "" if not yet known / random.
|
|
86
|
+
std::string external_address() const;
|
|
87
|
+
|
|
88
|
+
/// Map an application key to a stable 20-byte discovery hash (SHA-1).
|
|
89
|
+
static InfoHash hash_for_key(const std::string& key);
|
|
90
|
+
|
|
91
|
+
private:
|
|
92
|
+
void loop();
|
|
93
|
+
void probe_external_ip(); ///< STUN → set_external_ip on each client (runs on the loop thread)
|
|
94
|
+
void on_peers(const std::vector<Address>& peers, const InfoHash& info_hash);
|
|
95
|
+
|
|
96
|
+
/// Bring up one family's DhtClient (bind + bootstrap). Returns nullptr if it
|
|
97
|
+
/// could not bind/start, so the caller can run on whatever family did come up.
|
|
98
|
+
std::unique_ptr<DhtClient> make_client(AddressFamily family);
|
|
99
|
+
|
|
100
|
+
/// Run `fn` on each live client (IPv4 then IPv6). Loop-thread side.
|
|
101
|
+
template <typename Fn>
|
|
102
|
+
void for_each_client(Fn fn) {
|
|
103
|
+
if (dht_) fn(*dht_);
|
|
104
|
+
if (dht6_) fn(*dht6_);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Config config_;
|
|
108
|
+
InfoHash hash_;
|
|
109
|
+
PeerNetwork* network_ = nullptr;
|
|
110
|
+
std::unique_ptr<DhtClient> dht_; ///< IPv4 Kademlia network
|
|
111
|
+
std::unique_ptr<DhtClient> dht6_; ///< IPv6 Kademlia network (BEP 32)
|
|
112
|
+
|
|
113
|
+
std::thread thread_;
|
|
114
|
+
std::atomic<bool> running_{false};
|
|
115
|
+
std::atomic<bool> recover_pending_{false}; ///< network changed: re-STUN + re-announce
|
|
116
|
+
std::mutex wait_mutex_;
|
|
117
|
+
std::condition_variable wake_;
|
|
118
|
+
|
|
119
|
+
std::mutex dialed_mutex_;
|
|
120
|
+
std::unordered_set<Address> dialed_; ///< peers we've already dialed
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
} // namespace librats
|