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,473 @@
|
|
|
1
|
+
#include "librats/dht/node.h"
|
|
2
|
+
#include "librats/dht/announce.h"
|
|
3
|
+
#include "librats/dht/bep42.h"
|
|
4
|
+
#include "librats/dht/krpc.h"
|
|
5
|
+
#include "librats/dht/log.h"
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <chrono>
|
|
9
|
+
#include <random>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
#include <utility>
|
|
12
|
+
|
|
13
|
+
namespace librats {
|
|
14
|
+
namespace dht {
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
// Convert routing-table contacts to the wire form used in find_node/get_peers replies.
|
|
19
|
+
std::vector<KrpcNode> to_krpc(const std::vector<NodeEntry>& nodes) {
|
|
20
|
+
std::vector<KrpcNode> out;
|
|
21
|
+
out.reserve(nodes.size());
|
|
22
|
+
for (const auto& n : nodes) out.emplace_back(n.id, n.endpoint.ip, n.endpoint.port);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// A capped, comma-joined rendering of peer endpoints for a log line: show the first few
|
|
27
|
+
// and summarise the rest, so even a large result set stays one readable line. Only ever
|
|
28
|
+
// called inside a LOG_* argument, so it costs nothing when the level is filtered out.
|
|
29
|
+
std::string format_peers(const std::vector<Address>& peers, std::size_t max = 6) {
|
|
30
|
+
std::ostringstream s;
|
|
31
|
+
const std::size_t shown = (std::min)(peers.size(), max);
|
|
32
|
+
for (std::size_t i = 0; i < shown; ++i) {
|
|
33
|
+
if (i) s << ", ";
|
|
34
|
+
s << peers[i].to_string();
|
|
35
|
+
}
|
|
36
|
+
if (peers.size() > shown) s << " (+" << (peers.size() - shown) << " more)";
|
|
37
|
+
return s.str();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A bare liveness ping. Owned solely by the RpcManager while in flight; on the
|
|
41
|
+
// outcome it just records the contact's liveness in the routing table. This is the
|
|
42
|
+
// "ping" half of ping-before-replace — a timeout here lets the table promote a standby.
|
|
43
|
+
class PingObserver : public Observer {
|
|
44
|
+
public:
|
|
45
|
+
PingObserver(RoutingTable& table, const NodeId& id, const Address& ep)
|
|
46
|
+
: Observer(id, ep), table_(table) {}
|
|
47
|
+
|
|
48
|
+
void on_response(const KrpcMessage& msg, uint16_t rtt, TimePoint) override {
|
|
49
|
+
// BEP 42: a contact whose id is derived from its IP is harder to spoof and is
|
|
50
|
+
// preferred when the table has to choose who to drop.
|
|
51
|
+
const bool verified = verify_node_id_for_ip(msg.response_id, endpoint_.ip);
|
|
52
|
+
table_.node_seen(msg.response_id, endpoint_, rtt, verified);
|
|
53
|
+
}
|
|
54
|
+
void on_timeout(TimePoint) override { table_.node_failed(id_, endpoint_); }
|
|
55
|
+
void on_short_timeout(TimePoint) override {}
|
|
56
|
+
|
|
57
|
+
private:
|
|
58
|
+
RoutingTable& table_;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
} // namespace
|
|
62
|
+
|
|
63
|
+
Node::Node(Transport& transport, const NodeId& self, bool ipv6)
|
|
64
|
+
: transport_(transport), self_(self), ipv6_(ipv6), table_(self), rpc_(transport) {}
|
|
65
|
+
|
|
66
|
+
void Node::on_datagram(const std::vector<uint8_t>& data, const Address& from, TimePoint now) {
|
|
67
|
+
now_ = now;
|
|
68
|
+
if (!dos_.allow(from.ip, now)) return; // flooding source — ignore
|
|
69
|
+
|
|
70
|
+
auto msg = KrpcProtocol::decode_message(data);
|
|
71
|
+
if (!msg) return;
|
|
72
|
+
|
|
73
|
+
switch (msg->type) {
|
|
74
|
+
case KrpcMessageType::Query: handle_query(*msg, from, now); break;
|
|
75
|
+
case KrpcMessageType::Response: handle_response(*msg, from, now); break;
|
|
76
|
+
case KrpcMessageType::Error: rpc_.handle_response(*msg, from, now); break; // → observer timeout
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void Node::handle_query(const KrpcMessage& msg, const Address& from, TimePoint now) {
|
|
81
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
82
|
+
const bool spider = spider_mode_.load();
|
|
83
|
+
// While ignoring, still take announce_peer (the whole point of a crawl) but drop the rest.
|
|
84
|
+
if (spider && spider_ignore_.load() && msg.query_type != KrpcQueryType::AnnouncePeer) return;
|
|
85
|
+
// Answer with a neighbour id only to IPs we've crawled, so we don't pollute organic peers.
|
|
86
|
+
const bool use_neighbor = spider && spider_contacted(from.ip);
|
|
87
|
+
if (spider) add_spider_node(NodeEntry(msg.sender_id, from));
|
|
88
|
+
#else
|
|
89
|
+
const bool use_neighbor = false;
|
|
90
|
+
#endif
|
|
91
|
+
|
|
92
|
+
LOG_DEBUG("dht.rpc", "<- " << KrpcProtocol::query_type_to_string(msg.query_type)
|
|
93
|
+
<< " from " << from.to_string() << " (" << short_hex(msg.sender_id) << ")");
|
|
94
|
+
|
|
95
|
+
// Organic senders join the routing table; crawled IPs are kept out of it.
|
|
96
|
+
auto learn = [&](const NodeId& sender) {
|
|
97
|
+
// A node that merely *queries* us hasn't proven a round-trip: the UDP source
|
|
98
|
+
// address can be spoofed, so we must not trust it as a confirmed contact (which
|
|
99
|
+
// we'd hand out in find_node replies and let split buckets). Record it as
|
|
100
|
+
// heard_about — an unconfirmed candidate that a refresh ping must verify before
|
|
101
|
+
// we route with it. We still carry the BEP 42 verdict on the (id, ip) pair as a
|
|
102
|
+
// tie-breaker among candidates.
|
|
103
|
+
if (!use_neighbor)
|
|
104
|
+
table_.heard_about(sender, from, verify_node_id_for_ip(sender, from.ip));
|
|
105
|
+
};
|
|
106
|
+
auto reply_id = [&](const NodeId& anchor) -> NodeId {
|
|
107
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
108
|
+
if (use_neighbor) return neighbor_id(anchor);
|
|
109
|
+
#endif
|
|
110
|
+
(void)anchor;
|
|
111
|
+
return self_;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
switch (msg.query_type) {
|
|
115
|
+
case KrpcQueryType::Ping: {
|
|
116
|
+
learn(msg.sender_id);
|
|
117
|
+
send_message(KrpcProtocol::create_ping_response(msg.transaction_id, reply_id(msg.sender_id)), from);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case KrpcQueryType::FindNode: {
|
|
121
|
+
learn(msg.sender_id);
|
|
122
|
+
auto nodes = to_krpc(table_.find_closest(msg.target_id, kBucketSize));
|
|
123
|
+
send_message(KrpcProtocol::create_find_node_response(msg.transaction_id, reply_id(msg.target_id), nodes), from);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case KrpcQueryType::GetPeers: {
|
|
127
|
+
learn(msg.sender_id);
|
|
128
|
+
const std::string token = tokens_.generate(from, msg.info_hash, now);
|
|
129
|
+
const auto peers = peers_.get(msg.info_hash);
|
|
130
|
+
const NodeId rid = reply_id(msg.info_hash);
|
|
131
|
+
KrpcMessage reply = peers.empty()
|
|
132
|
+
? KrpcProtocol::create_get_peers_response_with_nodes(
|
|
133
|
+
msg.transaction_id, rid, to_krpc(table_.find_closest(msg.info_hash, kBucketSize)), token)
|
|
134
|
+
: KrpcProtocol::create_get_peers_response(msg.transaction_id, rid, peers, token);
|
|
135
|
+
send_message(reply, from);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case KrpcQueryType::AnnouncePeer: {
|
|
139
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
140
|
+
const bool skip_token = spider; // crawl collects every announce it can
|
|
141
|
+
#else
|
|
142
|
+
const bool skip_token = false;
|
|
143
|
+
#endif
|
|
144
|
+
if (!skip_token && !tokens_.verify(from, msg.info_hash, msg.token, now)) {
|
|
145
|
+
LOG_DEBUG("dht.rpc", "announce_peer from " << from.to_string() << " rejected: invalid token");
|
|
146
|
+
send_message(KrpcProtocol::create_error(msg.transaction_id,
|
|
147
|
+
KrpcErrorCode::ProtocolError, "Invalid token"), from);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
learn(msg.sender_id);
|
|
151
|
+
const uint16_t port = msg.implied_port ? from.port : msg.port;
|
|
152
|
+
const Address peer(from.ip, port);
|
|
153
|
+
peers_.store(msg.info_hash, peer, now);
|
|
154
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
155
|
+
if (spider && spider_announce_) spider_announce_(msg.info_hash, peer);
|
|
156
|
+
#endif
|
|
157
|
+
send_message(KrpcProtocol::create_announce_peer_response(msg.transaction_id, reply_id(msg.info_hash)), from);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
void Node::handle_response(const KrpcMessage& msg, const Address& from, TimePoint now) {
|
|
164
|
+
if (!msg.external_ip.is_unspecified()) maybe_update_external_ip(msg.external_ip, from);
|
|
165
|
+
// The matching observer (a lookup or a ping) feeds the routing table and drives
|
|
166
|
+
// its lookup; anti-spoofing and timeouts live in the RpcManager.
|
|
167
|
+
rpc_.handle_response(msg, from, now);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
void Node::send_message(const KrpcMessage& msg, const Address& to) {
|
|
171
|
+
KrpcMessage out = msg;
|
|
172
|
+
// BEP 42: tell the requester the address we see them at, so they can derive a
|
|
173
|
+
// compliant node id (and so we learn our own address from their replies).
|
|
174
|
+
if (out.type == KrpcMessageType::Response && out.external_ip.is_unspecified()) {
|
|
175
|
+
out.external_ip = to.ip; // already numeric bytes — no string round-trip
|
|
176
|
+
out.external_port = to.port;
|
|
177
|
+
}
|
|
178
|
+
const auto data = KrpcProtocol::encode_message(out);
|
|
179
|
+
if (!data.empty()) transport_.send(to, data);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
void Node::ping(const NodeId& id, const Address& ep, TimePoint now) {
|
|
183
|
+
auto obs = std::make_shared<PingObserver>(table_, id, ep);
|
|
184
|
+
KrpcMessage q = KrpcProtocol::create_ping_query("", self_);
|
|
185
|
+
rpc_.invoke(q, ep, obs, now); // the RpcManager owns obs until it resolves
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
FindPeers& Node::start_lookup(std::unique_ptr<FindPeers> lookup, TimePoint now) {
|
|
189
|
+
now_ = now; // so a synchronous completion inside start() measures a sane duration
|
|
190
|
+
lookup->set_want(want());
|
|
191
|
+
// Self-heal: when the routing table is too thin to seed a lookup (cold start, or every
|
|
192
|
+
// contact has died), fall back to the bootstrap routers — mirrors libtorrent's
|
|
193
|
+
// traversal_algorithm::add_router_entries(). Without this, a node whose table empties
|
|
194
|
+
// can never recover: every lookup would converge instantly on an empty candidate set,
|
|
195
|
+
// and the self-refresh in tick() never fires while the table is empty.
|
|
196
|
+
if (table_.size() < kAlpha)
|
|
197
|
+
for (const auto& ep : bootstrap_nodes_) lookup->add_seed(ep);
|
|
198
|
+
FindPeers& ref = *lookup;
|
|
199
|
+
lookups_.push_back(std::move(lookup));
|
|
200
|
+
ref.start(now); // may complete synchronously (e.g. empty table) — reaped next tick
|
|
201
|
+
return ref;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
void Node::bootstrap(const std::vector<Address>& nodes, TimePoint now) {
|
|
205
|
+
now_ = now; // bootstrap seeds and starts its lookup without going through start_lookup
|
|
206
|
+
// A self-targeted get_peers lookup populates the table with our neighbourhood; on
|
|
207
|
+
// completion we report how warm the table got.
|
|
208
|
+
auto boot = std::make_unique<FindPeers>(table_, rpc_, self_, self_,
|
|
209
|
+
FindPeers::PeersCallback{},
|
|
210
|
+
[this](const std::vector<Address>&) {
|
|
211
|
+
LOG_INFO("dht.find", "bootstrap complete — routing table: " << table_.size()
|
|
212
|
+
<< " node(s), " << table_.bucket_count() << " bucket(s)");
|
|
213
|
+
});
|
|
214
|
+
LOG_DEBUG("dht.find", "bootstrap lookup L" << boot->id() << ": " << nodes.size()
|
|
215
|
+
<< " seed(s), table=" << table_.size());
|
|
216
|
+
boot->set_want(want());
|
|
217
|
+
for (const auto& ep : nodes) boot->add_seed(ep);
|
|
218
|
+
FindPeers& ref = *boot;
|
|
219
|
+
lookups_.push_back(std::move(boot));
|
|
220
|
+
ref.start(now);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
void Node::find_peers(const InfoHash& info_hash, FindPeers::PeersCallback on_peers,
|
|
224
|
+
FindPeers::DoneCallback on_done, TimePoint now) {
|
|
225
|
+
// Peers as they arrive — the actual endpoints, so progress is visible mid-lookup.
|
|
226
|
+
auto peers = [this, info_hash, cb = std::move(on_peers)](const std::vector<Address>& fresh) {
|
|
227
|
+
LOG_DEBUG("dht.find", "find_peers " << short_hex(info_hash) << ": +" << fresh.size()
|
|
228
|
+
<< " peer(s): " << format_peers(fresh));
|
|
229
|
+
if (cb) cb(fresh);
|
|
230
|
+
};
|
|
231
|
+
// The result line: total peers, how long it took, and (capped) which ones.
|
|
232
|
+
auto done = [this, info_hash, start = now, cb = std::move(on_done)](const std::vector<Address>& all) {
|
|
233
|
+
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now_ - start).count();
|
|
234
|
+
LOG_INFO("dht.find", "find_peers " << short_hex(info_hash) << " → " << all.size()
|
|
235
|
+
<< " peer(s) in " << (ms < 0 ? 0 : ms) << "ms"
|
|
236
|
+
<< (all.empty() ? "" : ": " + format_peers(all)));
|
|
237
|
+
if (cb) cb(all);
|
|
238
|
+
};
|
|
239
|
+
auto lookup = std::make_unique<FindPeers>(table_, rpc_, self_, info_hash,
|
|
240
|
+
std::move(peers), std::move(done));
|
|
241
|
+
LOG_DEBUG("dht.find", "find_peers " << short_hex(info_hash) << " L" << lookup->id()
|
|
242
|
+
<< " started (table=" << table_.size() << ")");
|
|
243
|
+
start_lookup(std::move(lookup), now);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
void Node::announce_peer(const InfoHash& info_hash, uint16_t port, bool implied_port,
|
|
247
|
+
FindPeers::PeersCallback on_peers, FindPeers::DoneCallback on_done, TimePoint now) {
|
|
248
|
+
// An announce is a get_peers traversal that announces on completion, so it discovers
|
|
249
|
+
// peers along the way too. Stream them as they arrive (like find_peers), so a caller
|
|
250
|
+
// that both announces and wants peers needs only this one lookup, not a second one.
|
|
251
|
+
auto peers = [this, info_hash, cb = std::move(on_peers)](const std::vector<Address>& fresh) {
|
|
252
|
+
LOG_DEBUG("dht.find", "announce " << short_hex(info_hash) << ": +" << fresh.size()
|
|
253
|
+
<< " peer(s): " << format_peers(fresh));
|
|
254
|
+
if (cb) cb(fresh);
|
|
255
|
+
};
|
|
256
|
+
auto done = [this, info_hash, start = now, cb = std::move(on_done)](const std::vector<Address>& all) {
|
|
257
|
+
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now_ - start).count();
|
|
258
|
+
LOG_INFO("dht.find", "announce " << short_hex(info_hash) << " complete in "
|
|
259
|
+
<< (ms < 0 ? 0 : ms) << "ms (" << all.size() << " peer(s) seen)");
|
|
260
|
+
if (cb) cb(all);
|
|
261
|
+
};
|
|
262
|
+
auto lookup = std::make_unique<Announce>(table_, rpc_, self_, info_hash, port, implied_port,
|
|
263
|
+
std::move(peers), std::move(done));
|
|
264
|
+
LOG_DEBUG("dht.find", "announce " << short_hex(info_hash) << " L" << lookup->id() << " on port " << port
|
|
265
|
+
<< (implied_port ? " (implied)" : "") << " started (table=" << table_.size() << ")");
|
|
266
|
+
start_lookup(std::move(lookup), now);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
void Node::cancel_lookup(const InfoHash& info_hash) {
|
|
270
|
+
// Erasing a Traversal cancels its in-flight RPCs in its destructor.
|
|
271
|
+
lookups_.erase(std::remove_if(lookups_.begin(), lookups_.end(),
|
|
272
|
+
[&](const std::unique_ptr<Traversal>& t) { return t->target() == info_hash; }),
|
|
273
|
+
lookups_.end());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
bool Node::lookup_active(const InfoHash& info_hash, bool announce) const {
|
|
277
|
+
for (const auto& t : lookups_)
|
|
278
|
+
if (!t->finished() && t->is_announce() == announce && t->target() == info_hash) return true;
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
std::size_t Node::lookup_count(bool announce) const {
|
|
283
|
+
std::size_t n = 0;
|
|
284
|
+
for (const auto& t : lookups_)
|
|
285
|
+
if (!t->finished() && t->is_announce() == announce) ++n;
|
|
286
|
+
return n;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
void Node::tick(TimePoint now) {
|
|
290
|
+
now_ = now;
|
|
291
|
+
rpc_.tick(now); // fire query timeouts (drives lookups + ping-before-replace)
|
|
292
|
+
reap_finished();
|
|
293
|
+
peers_.expire(now); // drop announced peers older than the TTL
|
|
294
|
+
|
|
295
|
+
// Refresh one stale contact per interval (confirms hearsay, prunes dead nodes).
|
|
296
|
+
if (now - last_refresh_ >= kRefreshInterval) {
|
|
297
|
+
if (auto stale = table_.next_to_refresh(now)) ping(stale->id, stale->endpoint, now);
|
|
298
|
+
last_refresh_ = now;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Periodically re-explore our own neighbourhood to keep the table fresh.
|
|
302
|
+
if (now - last_self_refresh_ >= kSelfRefreshInterval && lookups_.empty() && !table_.empty()) {
|
|
303
|
+
start_lookup(std::make_unique<FindPeers>(table_, rpc_, self_, self_,
|
|
304
|
+
FindPeers::PeersCallback{}, FindPeers::DoneCallback{}), now);
|
|
305
|
+
last_self_refresh_ = now;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// DEBUG heartbeat: periodic activity counters plus a pretty routing-table dump, so
|
|
309
|
+
// the live state is visible over time without tracing every event. describe() is only
|
|
310
|
+
// built when DEBUG is actually enabled (the macro guards on the level).
|
|
311
|
+
if (now - last_state_log_ >= kStateLogInterval) {
|
|
312
|
+
LOG_DEBUG("dht", "activity: " << lookups_.size() << " lookup(s), "
|
|
313
|
+
<< rpc_.outstanding() << " rpc in-flight, "
|
|
314
|
+
<< peers_.hash_count() << " stored hash(es)");
|
|
315
|
+
LOG_DEBUG("dht.route", table_.describe());
|
|
316
|
+
last_state_log_ = now;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
void Node::reap_finished() {
|
|
321
|
+
lookups_.erase(std::remove_if(lookups_.begin(), lookups_.end(),
|
|
322
|
+
[](const std::unique_ptr<Traversal>& t) { return t->finished(); }),
|
|
323
|
+
lookups_.end());
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
void Node::maybe_update_external_ip(const IpAddress& reported_ip, const Address& from) {
|
|
327
|
+
if (!is_public_address(reported_ip)) return; // IpAddress overload — no re-parse
|
|
328
|
+
if (reported_ip.is_v6() != ipv6_) return; // wrong family
|
|
329
|
+
if (reported_ip == external_address_) return;
|
|
330
|
+
|
|
331
|
+
if (!ip_voters_.insert(from.ip).second) return; // one vote per distinct responder
|
|
332
|
+
const int votes = ++ip_votes_[reported_ip];
|
|
333
|
+
LOG_DEBUG("dht", "external IP vote: " << reported_ip.to_string() << " (" << votes << "/"
|
|
334
|
+
<< kExternalIpVoteThreshold << ", from " << from.ip.to_string() << ")");
|
|
335
|
+
if (votes >= kExternalIpVoteThreshold) {
|
|
336
|
+
ip_votes_.clear();
|
|
337
|
+
ip_voters_.clear();
|
|
338
|
+
set_external_ip(reported_ip);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
void Node::set_external_ip(const IpAddress& ip) {
|
|
343
|
+
if (!is_public_address(ip)) return;
|
|
344
|
+
if (ip.is_v6() != ipv6_) return; // separate networks per family
|
|
345
|
+
const bool changed = (ip != external_address_);
|
|
346
|
+
external_address_ = ip;
|
|
347
|
+
|
|
348
|
+
if (verify_node_id_for_ip(self_, ip)) { // our id already matches this address
|
|
349
|
+
if (changed) LOG_INFO("dht", "external address is " << ip.to_string() << " (node id already BEP 42-compliant)");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
std::mt19937 gen(std::random_device{}());
|
|
354
|
+
NodeId new_id;
|
|
355
|
+
if (!generate_node_id_from_ip(ip, new_id, gen)) return;
|
|
356
|
+
self_ = new_id;
|
|
357
|
+
table_.set_self(new_id); // re-bucket everything against the new id
|
|
358
|
+
LOG_INFO("dht", "external address is " << ip.to_string() << ", regenerated node id "
|
|
359
|
+
<< short_hex(self_) << " (BEP 42)");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
std::vector<HostEndpoint> Node::default_bootstrap_nodes() {
|
|
363
|
+
// Hostnames resolve per-family at send time; dht.libtorrent.org also has an AAAA
|
|
364
|
+
// record, giving IPv6 a reliable entry point.
|
|
365
|
+
return {
|
|
366
|
+
{"router.bittorrent.com", 6881},
|
|
367
|
+
{"dht.transmissionbt.com", 6881},
|
|
368
|
+
{"router.utorrent.com", 6881},
|
|
369
|
+
{"dht.libtorrent.org", 25401},
|
|
370
|
+
{"dht.aelitis.com", 6881},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
375
|
+
|
|
376
|
+
namespace {
|
|
377
|
+
// Forwards a successful reply to a callback and ignores timeouts. Owned solely by the
|
|
378
|
+
// RpcManager while in flight — used by the spider crawl to feed discovered nodes back.
|
|
379
|
+
class CallbackObserver : public Observer {
|
|
380
|
+
public:
|
|
381
|
+
using Fn = std::function<void(const KrpcMessage&, const Address&)>;
|
|
382
|
+
CallbackObserver(const NodeId& id, const Address& ep, Fn on_reply)
|
|
383
|
+
: Observer(id, ep), on_reply_(std::move(on_reply)) {}
|
|
384
|
+
void on_response(const KrpcMessage& msg, uint16_t, TimePoint) override {
|
|
385
|
+
if (on_reply_) on_reply_(msg, endpoint_);
|
|
386
|
+
}
|
|
387
|
+
void on_timeout(TimePoint) override {}
|
|
388
|
+
void on_short_timeout(TimePoint) override {}
|
|
389
|
+
|
|
390
|
+
private:
|
|
391
|
+
Fn on_reply_;
|
|
392
|
+
};
|
|
393
|
+
} // namespace
|
|
394
|
+
|
|
395
|
+
NodeId Node::neighbor_id(const NodeId& target) const {
|
|
396
|
+
NodeId out;
|
|
397
|
+
std::copy(target.begin(), target.begin() + 10, out.begin()); // look close to the target
|
|
398
|
+
std::copy(self_.begin() + 10, self_.end(), out.begin() + 10); // but still uniquely us
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
void Node::add_spider_node(const NodeEntry& node) {
|
|
403
|
+
for (auto& n : spider_pool_)
|
|
404
|
+
if (n.id == node.id) { n.endpoint = node.endpoint; return; }
|
|
405
|
+
if (spider_pool_.size() < kMaxSpiderNodes) {
|
|
406
|
+
spider_pool_.push_back(node);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
static thread_local std::mt19937 gen(std::random_device{}()); // pool full → evict a random slot
|
|
410
|
+
spider_pool_[std::uniform_int_distribution<std::size_t>(0, spider_pool_.size() - 1)(gen)] = node;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
bool Node::spider_contacted(const IpAddress& ip) const {
|
|
414
|
+
return spider_contacted_ips_.count(ip) > 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
void Node::spider_absorb(const KrpcMessage& msg, const Address& from) {
|
|
418
|
+
add_spider_node(NodeEntry(msg.response_id, from));
|
|
419
|
+
for (const auto& n : msg.nodes) add_spider_node(NodeEntry(n.id, Address(n.ip, n.port)));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
void Node::clear_spider_state() {
|
|
423
|
+
spider_pool_.clear();
|
|
424
|
+
spider_visited_.clear();
|
|
425
|
+
spider_contacted_ips_.clear();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
void Node::spider_walk(TimePoint now) {
|
|
429
|
+
static thread_local std::mt19937 gen(std::random_device{}());
|
|
430
|
+
|
|
431
|
+
if (spider_visited_.size() >= kMaxSpiderVisited) spider_visited_.clear();
|
|
432
|
+
if (spider_contacted_ips_.size() >= kMaxSpiderContactedIps) spider_contacted_ips_.clear();
|
|
433
|
+
|
|
434
|
+
if (spider_pool_.empty()) {
|
|
435
|
+
for (const auto& n : table_.find_closest(self_, kMaxSpiderNodes, true)) {
|
|
436
|
+
if (spider_pool_.size() >= kMaxSpiderNodes) break;
|
|
437
|
+
spider_pool_.push_back(n);
|
|
438
|
+
}
|
|
439
|
+
if (spider_pool_.empty()) {
|
|
440
|
+
if (now - last_spider_bootstrap_ >= std::chrono::seconds(30)) {
|
|
441
|
+
last_spider_bootstrap_ = now;
|
|
442
|
+
// Reseed from the resolved seed set the facade gave us. The engine has no
|
|
443
|
+
// resolver, so it can only reuse already-resolved Addresses — the hostname
|
|
444
|
+
// defaults live at the facade layer (DhtClient) and are resolved there.
|
|
445
|
+
if (!bootstrap_nodes_.empty()) bootstrap(bootstrap_nodes_, now);
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
auto pick = [&] { return std::uniform_int_distribution<std::size_t>(0, spider_pool_.size() - 1)(gen); };
|
|
452
|
+
NodeEntry target = spider_pool_[pick()];
|
|
453
|
+
for (int i = 0; i < 10; ++i) {
|
|
454
|
+
const NodeEntry& cand = spider_pool_[pick()];
|
|
455
|
+
if (spider_visited_.find(cand.id) == spider_visited_.end()) { target = cand; break; }
|
|
456
|
+
}
|
|
457
|
+
spider_visited_.insert(target.id);
|
|
458
|
+
spider_contacted_ips_.insert(target.endpoint.ip);
|
|
459
|
+
|
|
460
|
+
NodeId random_target;
|
|
461
|
+
{ std::uniform_int_distribution<int> b(0, 255); for (auto& x : random_target) x = static_cast<uint8_t>(b(gen)); }
|
|
462
|
+
|
|
463
|
+
// find_node toward a random target, posing as a neighbour of the node we ask.
|
|
464
|
+
KrpcMessage q = KrpcProtocol::create_find_node_query("", neighbor_id(target.id), random_target);
|
|
465
|
+
auto obs = std::make_shared<CallbackObserver>(target.id, target.endpoint,
|
|
466
|
+
[this](const KrpcMessage& m, const Address& ep) { spider_absorb(m, ep); });
|
|
467
|
+
rpc_.invoke(q, target.endpoint, obs, now);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
#endif // RATS_SEARCH_FEATURES
|
|
471
|
+
|
|
472
|
+
} // namespace dht
|
|
473
|
+
} // namespace librats
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file node.h
|
|
5
|
+
* @brief The Kademlia engine: routing table + RPC + storage + lookups, wired together.
|
|
6
|
+
*
|
|
7
|
+
* Pure logic, no sockets and no threads — it sends through an injected Transport and
|
|
8
|
+
* is driven from outside by on_datagram() (a packet arrived) and tick() (time passed).
|
|
9
|
+
* That makes the whole DHT unit-testable with a mock transport. A thin UdpTransport +
|
|
10
|
+
* DhtClient facade (Phase 5) supply the real socket and threading.
|
|
11
|
+
*
|
|
12
|
+
* One node serves a single address family (IPv4 or IPv6 — separate Kademlia networks
|
|
13
|
+
* per BEP 32). It answers incoming queries (ping/find_node/get_peers/announce_peer)
|
|
14
|
+
* and runs its own lookups (bootstrap, find_peers, announce_peer).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#include "librats/core/address.h"
|
|
18
|
+
#include "librats/core/host_endpoint.h"
|
|
19
|
+
#include "librats/dht/dos_blocker.h"
|
|
20
|
+
#include "librats/dht/find_peers.h"
|
|
21
|
+
#include "librats/dht/id.h"
|
|
22
|
+
#include "librats/dht/observer.h"
|
|
23
|
+
#include "librats/dht/routing_table.h"
|
|
24
|
+
#include "librats/dht/rpc_manager.h"
|
|
25
|
+
#include "librats/dht/storage.h"
|
|
26
|
+
#include "librats/dht/transport.h"
|
|
27
|
+
|
|
28
|
+
#include <atomic>
|
|
29
|
+
#include <chrono>
|
|
30
|
+
#include <cstddef>
|
|
31
|
+
#include <cstdint>
|
|
32
|
+
#include <functional>
|
|
33
|
+
#include <map>
|
|
34
|
+
#include <memory>
|
|
35
|
+
#include <random>
|
|
36
|
+
#include <set>
|
|
37
|
+
#include <string>
|
|
38
|
+
#include <unordered_set>
|
|
39
|
+
#include <vector>
|
|
40
|
+
|
|
41
|
+
namespace librats {
|
|
42
|
+
struct KrpcMessage;
|
|
43
|
+
|
|
44
|
+
namespace dht {
|
|
45
|
+
|
|
46
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
47
|
+
// Invoked when another peer announces it has a torrent (spider mode collects these).
|
|
48
|
+
using SpiderAnnounceCallback = std::function<void(const InfoHash&, const Address&)>;
|
|
49
|
+
#endif
|
|
50
|
+
|
|
51
|
+
class Node {
|
|
52
|
+
public:
|
|
53
|
+
Node(Transport& transport, const NodeId& self, bool ipv6);
|
|
54
|
+
|
|
55
|
+
// -- driven by the transport layer -----------------------------------------
|
|
56
|
+
void on_datagram(const std::vector<uint8_t>& data, const Address& from, TimePoint now);
|
|
57
|
+
void tick(TimePoint now); // periodic maintenance
|
|
58
|
+
|
|
59
|
+
// -- our own lookups --------------------------------------------------------
|
|
60
|
+
void bootstrap(const std::vector<Address>& nodes, TimePoint now);
|
|
61
|
+
// Remember the (already resolved, numeric) seed set so an internal reseed — e.g. the
|
|
62
|
+
// spider crawl when its frontier empties — can reuse it instead of unresolved hostnames.
|
|
63
|
+
void set_bootstrap_nodes(std::vector<Address> nodes) { bootstrap_nodes_ = std::move(nodes); }
|
|
64
|
+
void find_peers(const InfoHash& info_hash, FindPeers::PeersCallback on_peers,
|
|
65
|
+
FindPeers::DoneCallback on_done, TimePoint now);
|
|
66
|
+
void announce_peer(const InfoHash& info_hash, uint16_t port, bool implied_port,
|
|
67
|
+
FindPeers::PeersCallback on_peers, FindPeers::DoneCallback on_done, TimePoint now);
|
|
68
|
+
void cancel_lookup(const InfoHash& info_hash); // drop active lookups for it
|
|
69
|
+
bool lookup_active(const InfoHash& info_hash, bool announce) const; // is one running?
|
|
70
|
+
std::size_t lookup_count(bool announce) const; // how many running
|
|
71
|
+
|
|
72
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
73
|
+
// Spider mode — aggressive crawl that collects announce_peer requests from the
|
|
74
|
+
// network. set/walk/clear must run on the loop thread (post them); the on/off and
|
|
75
|
+
// ignore flags are atomics readable from anywhere.
|
|
76
|
+
// Just flips the atomic (safe from any thread); spider_walk seeds the pool lazily.
|
|
77
|
+
void set_spider_mode(bool enable) noexcept { spider_mode_.store(enable); }
|
|
78
|
+
bool is_spider_mode() const noexcept { return spider_mode_.load(); }
|
|
79
|
+
void set_spider_ignore(bool ignore) noexcept { spider_ignore_.store(ignore); }
|
|
80
|
+
bool is_spider_ignoring() const noexcept { return spider_ignore_.load(); }
|
|
81
|
+
void set_spider_announce_callback(SpiderAnnounceCallback cb) { spider_announce_ = std::move(cb); }
|
|
82
|
+
void spider_walk(TimePoint now);
|
|
83
|
+
void clear_spider_state();
|
|
84
|
+
std::size_t spider_pool_size() const noexcept { return spider_pool_.size(); }
|
|
85
|
+
std::size_t spider_visited_count() const noexcept { return spider_visited_.size(); }
|
|
86
|
+
#endif
|
|
87
|
+
|
|
88
|
+
// -- accessors / config -----------------------------------------------------
|
|
89
|
+
const NodeId& self() const noexcept { return self_; }
|
|
90
|
+
bool is_ipv6() const noexcept { return ipv6_; }
|
|
91
|
+
void set_external_ip(const IpAddress& ip); // BEP 42: re-derive our id
|
|
92
|
+
const IpAddress& external_address() const noexcept { return external_address_; }
|
|
93
|
+
RoutingTable& routing_table() noexcept { return table_; }
|
|
94
|
+
const RoutingTable& routing_table() const noexcept { return table_; }
|
|
95
|
+
std::size_t active_lookups() const noexcept { return lookups_.size(); }
|
|
96
|
+
|
|
97
|
+
static std::vector<HostEndpoint> default_bootstrap_nodes();
|
|
98
|
+
|
|
99
|
+
private:
|
|
100
|
+
// incoming query handlers (server side)
|
|
101
|
+
void handle_query(const KrpcMessage& msg, const Address& from, TimePoint now);
|
|
102
|
+
void handle_response(const KrpcMessage& msg, const Address& from, TimePoint now);
|
|
103
|
+
|
|
104
|
+
void send_message(const KrpcMessage& msg, const Address& to); // a response (echoes txn, stamps ip)
|
|
105
|
+
void ping(const NodeId& id, const Address& ep, TimePoint now); // liveness probe
|
|
106
|
+
void maybe_update_external_ip(const IpAddress& reported_ip, const Address& from);
|
|
107
|
+
FindPeers& start_lookup(std::unique_ptr<FindPeers> lookup, TimePoint now);
|
|
108
|
+
void reap_finished();
|
|
109
|
+
std::vector<std::string> want() const { return {ipv6_ ? "n6" : "n4"}; }
|
|
110
|
+
|
|
111
|
+
Transport& transport_;
|
|
112
|
+
NodeId self_;
|
|
113
|
+
bool ipv6_;
|
|
114
|
+
RoutingTable table_;
|
|
115
|
+
RpcManager rpc_;
|
|
116
|
+
PeerStore peers_;
|
|
117
|
+
TokenManager tokens_;
|
|
118
|
+
DosBlocker dos_;
|
|
119
|
+
std::vector<std::unique_ptr<Traversal>> lookups_;
|
|
120
|
+
std::vector<Address> bootstrap_nodes_; // resolved seeds, reused by an internal reseed
|
|
121
|
+
|
|
122
|
+
// BEP 42: learn our external address from the "ip" nodes echo back, but require a
|
|
123
|
+
// consensus of distinct responders before trusting it (one node can't poison us).
|
|
124
|
+
static constexpr int kExternalIpVoteThreshold = 5;
|
|
125
|
+
IpAddress external_address_;
|
|
126
|
+
std::map<IpAddress, int> ip_votes_;
|
|
127
|
+
std::set<IpAddress> ip_voters_;
|
|
128
|
+
|
|
129
|
+
// maintenance cadence
|
|
130
|
+
static constexpr std::chrono::seconds kRefreshInterval{6};
|
|
131
|
+
static constexpr std::chrono::minutes kSelfRefreshInterval{6};
|
|
132
|
+
static constexpr std::chrono::seconds kStateLogInterval{30}; // DEBUG heartbeat cadence
|
|
133
|
+
TimePoint last_refresh_{};
|
|
134
|
+
TimePoint last_self_refresh_{};
|
|
135
|
+
TimePoint last_state_log_{};
|
|
136
|
+
|
|
137
|
+
// The most recent model time, stamped at every entry point (on_datagram / tick /
|
|
138
|
+
// start_lookup / bootstrap). Lets a lookup's done-callback measure its own duration
|
|
139
|
+
// without the engine threading `now` through every callback signature.
|
|
140
|
+
TimePoint now_{};
|
|
141
|
+
|
|
142
|
+
#ifdef RATS_SEARCH_FEATURES
|
|
143
|
+
// An id that looks close to `target` (first 10 bytes from target, last 10 from us),
|
|
144
|
+
// so peers route more queries our way during a crawl.
|
|
145
|
+
NodeId neighbor_id(const NodeId& target) const;
|
|
146
|
+
void add_spider_node(const NodeEntry& node);
|
|
147
|
+
void spider_absorb(const KrpcMessage& msg, const Address& from); // reply → spider pool
|
|
148
|
+
bool spider_contacted(const IpAddress& ip) const;
|
|
149
|
+
|
|
150
|
+
std::atomic<bool> spider_mode_{false};
|
|
151
|
+
std::atomic<bool> spider_ignore_{false};
|
|
152
|
+
SpiderAnnounceCallback spider_announce_;
|
|
153
|
+
std::vector<NodeEntry> spider_pool_; // crawl frontier (kept apart from the routing table)
|
|
154
|
+
std::unordered_set<NodeId, NodeIdHash> spider_visited_;
|
|
155
|
+
std::unordered_set<IpAddress> spider_contacted_ips_;
|
|
156
|
+
TimePoint last_spider_bootstrap_{};
|
|
157
|
+
static constexpr std::size_t kMaxSpiderNodes = 2000;
|
|
158
|
+
static constexpr std::size_t kMaxSpiderVisited = 10000;
|
|
159
|
+
static constexpr std::size_t kMaxSpiderContactedIps = 10000;
|
|
160
|
+
#endif
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
} // namespace dht
|
|
164
|
+
} // namespace librats
|