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,559 @@
|
|
|
1
|
+
#include "librats/dht/routing_table.h"
|
|
2
|
+
#include "librats/dht/bep42.h"
|
|
3
|
+
#include "librats/dht/log.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <array>
|
|
7
|
+
#include <iomanip>
|
|
8
|
+
#include <sstream>
|
|
9
|
+
#include <string>
|
|
10
|
+
|
|
11
|
+
namespace librats {
|
|
12
|
+
namespace dht {
|
|
13
|
+
|
|
14
|
+
namespace {
|
|
15
|
+
|
|
16
|
+
// Locate a contact by id within one set (live or replacements).
|
|
17
|
+
std::vector<NodeEntry>::iterator find_by_id(std::vector<NodeEntry>& v, const NodeId& id) {
|
|
18
|
+
return std::find_if(v.begin(), v.end(), [&](const NodeEntry& n) { return n.id == id; });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The worst contact in `v` (the one every other is "no worse than"), or end() if empty.
|
|
22
|
+
std::vector<NodeEntry>::iterator worst_of(std::vector<NodeEntry>& v) {
|
|
23
|
+
auto worst = v.begin();
|
|
24
|
+
for (auto it = v.begin(); it != v.end(); ++it)
|
|
25
|
+
if (it->is_worse_than(*worst)) worst = it;
|
|
26
|
+
return worst;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// log2 of a power-of-two bucket size: 8 -> 3 ... 128 -> 7.
|
|
30
|
+
constexpr int prefix_bits_for(int bucket_size) noexcept {
|
|
31
|
+
int bits = 0;
|
|
32
|
+
while ((1 << bits) < bucket_size) ++bits;
|
|
33
|
+
return bits;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The largest live-set limit any bucket can have (the widest top bucket); see
|
|
37
|
+
// RoutingTable::bucket_limit. The spread classifier yields prefix_bits_for(limit)
|
|
38
|
+
// bits, so this caps the number of distinct sub-prefix slots.
|
|
39
|
+
constexpr int kMaxBucketLimit = static_cast<int>(kBucketSize) * 16;
|
|
40
|
+
|
|
41
|
+
// Classify a node by the few sub-prefix bits right after the bucket's shared prefix.
|
|
42
|
+
// For a non-last bucket we skip the one bit that *defines* the bucket (it's the same
|
|
43
|
+
// for every entry); the last bucket hasn't split yet and holds both sides, so we keep
|
|
44
|
+
// it. The result indexes a "spread slot" in [0, bucket_size).
|
|
45
|
+
uint8_t classify_prefix(int bucket_idx, bool last_bucket, int bucket_size, const NodeId& id) noexcept {
|
|
46
|
+
const int shift = bucket_idx + (last_bucket ? 0 : 1);
|
|
47
|
+
return static_cast<uint8_t>(bits_at(id, shift, prefix_bits_for(bucket_size)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// True if every live node and `id` carry the same bit at position `idx`: a split at
|
|
51
|
+
// that bit wouldn't separate anyone, so it would be pointless.
|
|
52
|
+
bool all_on_same_side(const std::vector<NodeEntry>& live, const NodeId& id, int idx) noexcept {
|
|
53
|
+
const int byte = idx >> 3;
|
|
54
|
+
const int bit = idx & 7;
|
|
55
|
+
const uint8_t mask = static_cast<uint8_t>(0x80u >> bit);
|
|
56
|
+
int count[2] = {0, 0};
|
|
57
|
+
++count[(id[byte] & mask) ? 1 : 0];
|
|
58
|
+
for (const auto& n : live) ++count[(n.id[byte] & mask) ? 1 : 0];
|
|
59
|
+
return count[0] == 0 || count[1] == 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
} // namespace
|
|
63
|
+
|
|
64
|
+
RoutingTable::RoutingTable(const NodeId& self, bool extended)
|
|
65
|
+
: self_(self), extended_(extended) {
|
|
66
|
+
buckets_.emplace_back(); // always keep at least the catch-all bucket
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
int RoutingTable::bucket_index(const NodeId& id) const noexcept {
|
|
70
|
+
const int prefix = shared_prefix_bits(self_, id);
|
|
71
|
+
const int last = static_cast<int>(buckets_.size()) - 1;
|
|
72
|
+
return prefix < last ? prefix : last;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
int RoutingTable::bucket_limit(int index) const noexcept {
|
|
76
|
+
if (!extended_) return static_cast<int>(kBucketSize);
|
|
77
|
+
// The top buckets hold more contacts (16k, 8k, 4k, 2k) — that's where the
|
|
78
|
+
// keyspace and the first lookup hop are densest.
|
|
79
|
+
static constexpr int kMult[4] = {16, 8, 4, 2};
|
|
80
|
+
return static_cast<int>(kBucketSize) * (index < 4 ? kMult[index] : 1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void RoutingTable::heard_about(const NodeId& id, const Address& endpoint, bool verified) {
|
|
84
|
+
if (id == self_) return;
|
|
85
|
+
NodeEntry e(id, endpoint); // unpinged until it replies
|
|
86
|
+
e.verified = verified;
|
|
87
|
+
add_node(e);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
bool RoutingTable::node_seen(const NodeId& id, const Address& endpoint,
|
|
91
|
+
uint16_t rtt, bool verified) {
|
|
92
|
+
if (id == self_) return false;
|
|
93
|
+
NodeEntry entry(id, endpoint);
|
|
94
|
+
entry.verified = verified;
|
|
95
|
+
entry.record_success(rtt); // confirmed (fail_count = 0), folds in the RTT
|
|
96
|
+
return add_node(entry);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
bool RoutingTable::add_node(NodeEntry e) {
|
|
100
|
+
if (e.id == self_) return false;
|
|
101
|
+
const int idx = bucket_index(e.id);
|
|
102
|
+
Bucket& b = buckets_[idx];
|
|
103
|
+
|
|
104
|
+
// Already live → refresh in place. A confirmed sighting also records success;
|
|
105
|
+
// mere hearsay must not reset a live contact's quality. A confirmed contact that
|
|
106
|
+
// moved to a new endpoint re-points the IP index too.
|
|
107
|
+
if (auto it = find_by_id(b.live, e.id); it != b.live.end()) {
|
|
108
|
+
it->verified = it->verified || e.verified;
|
|
109
|
+
if (e.pinged()) {
|
|
110
|
+
if (it->endpoint.ip != e.endpoint.ip) { ip_untrack(it->endpoint.ip); ip_track(e.endpoint.ip); }
|
|
111
|
+
it->endpoint = e.endpoint;
|
|
112
|
+
it->record_success(e.rtt);
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Sitting in the replacement cache: a confirmed sighting pulls it out to promote,
|
|
118
|
+
// carrying its accumulated quality; mere hearsay leaves it parked. The erase
|
|
119
|
+
// untracks its IP; the shared insert below re-tracks it (at its current endpoint),
|
|
120
|
+
// so a promotion is IP-neutral even if the endpoint changed.
|
|
121
|
+
bool promoted = false;
|
|
122
|
+
if (auto it = find_by_id(b.replacements, e.id); it != b.replacements.end()) {
|
|
123
|
+
if (!e.pinged()) return false;
|
|
124
|
+
NodeEntry carried = *it;
|
|
125
|
+
carried.endpoint = e.endpoint;
|
|
126
|
+
carried.verified = carried.verified || e.verified;
|
|
127
|
+
carried.record_success(e.rtt);
|
|
128
|
+
ip_untrack(it->endpoint.ip);
|
|
129
|
+
b.replacements.erase(it);
|
|
130
|
+
e = carried;
|
|
131
|
+
promoted = true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// A genuinely new IP must pass the IP-diversity gate (rule 1 + rule 2, public IPs
|
|
135
|
+
// only). A promoted contact is already one we know, so it skips the gate.
|
|
136
|
+
if (!promoted && !passes_ip_diversity(b, idx, e)) return false;
|
|
137
|
+
|
|
138
|
+
const int limit = bucket_limit(idx);
|
|
139
|
+
if (static_cast<int>(b.live.size()) < limit) {
|
|
140
|
+
b.live.push_back(e);
|
|
141
|
+
ip_track(e.endpoint.ip);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// The bucket is full. A confirmed contact may split it or displace a weaker one;
|
|
146
|
+
// hearsay just waits in the replacement cache.
|
|
147
|
+
if (e.confirmed()) {
|
|
148
|
+
if (can_split(idx, e)) {
|
|
149
|
+
split_bucket(); // buckets idx and idx + 1 now exist
|
|
150
|
+
const bool added = add_node(e); // re-home e against the now-deeper table
|
|
151
|
+
// The split freed live slots; pull the best standbys up into them, but
|
|
152
|
+
// only after e has claimed its rightful slot.
|
|
153
|
+
fill_from_replacements(idx);
|
|
154
|
+
fill_from_replacements(idx + 1);
|
|
155
|
+
return added;
|
|
156
|
+
}
|
|
157
|
+
if (replace_for_spread(b, idx, limit, e)) return true;
|
|
158
|
+
}
|
|
159
|
+
stash_replacement(b, e);
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
bool RoutingTable::can_split(int index, const NodeEntry& e) const {
|
|
164
|
+
return index + 1 == static_cast<int>(buckets_.size()) // only the last bucket splits
|
|
165
|
+
&& static_cast<int>(buckets_.size()) < kBucketCount - 1 // bounded depth
|
|
166
|
+
&& e.confirmed()
|
|
167
|
+
&& (index == 0 || buckets_[index - 1].live.size() > 1) // don't deepen into near-empty
|
|
168
|
+
&& !all_on_same_side(buckets_[index].live, e.id, index); // a split must actually separate
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
void RoutingTable::split_bucket() {
|
|
172
|
+
const int last = static_cast<int>(buckets_.size()) - 1;
|
|
173
|
+
buckets_.emplace_back(); // may reallocate buckets_ — hold no Bucket& across this
|
|
174
|
+
|
|
175
|
+
Bucket moved = std::move(buckets_[last]);
|
|
176
|
+
buckets_[last] = Bucket{};
|
|
177
|
+
|
|
178
|
+
// Contacts sharing exactly `last` prefix bits with us stay; closer ones (longer
|
|
179
|
+
// shared prefix) move to the new, deeper bucket.
|
|
180
|
+
const auto place = [&](std::vector<NodeEntry>& src, bool live) {
|
|
181
|
+
for (auto& n : src) {
|
|
182
|
+
const int dest = shared_prefix_bits(self_, n.id) <= last ? last : last + 1;
|
|
183
|
+
auto& bucket = buckets_[dest];
|
|
184
|
+
(live ? bucket.live : bucket.replacements).push_back(std::move(n));
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
place(moved.live, true);
|
|
188
|
+
place(moved.replacements, false);
|
|
189
|
+
|
|
190
|
+
// The deeper bucket has a smaller limit, so it may now overflow — spill the rest.
|
|
191
|
+
trim_to_limit(last);
|
|
192
|
+
trim_to_limit(last + 1);
|
|
193
|
+
|
|
194
|
+
// Structural milestone, not per-packet churn: a bucket deepens only a bounded number
|
|
195
|
+
// of times as the table grows, so this stays quiet (unlike add/seen/fail, which must
|
|
196
|
+
// never log). The live composition over time is the heartbeat's describe() job.
|
|
197
|
+
LOG_DEBUG("dht.route", "bucket split → " << buckets_.size() << " bucket(s): #" << last
|
|
198
|
+
<< " keeps " << buckets_[last].live.size() << ", #" << (last + 1)
|
|
199
|
+
<< " gets " << buckets_[last + 1].live.size() << " live");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
bool RoutingTable::replace_for_spread(Bucket& b, int index, int limit, const NodeEntry& e) {
|
|
203
|
+
auto& live = b.live;
|
|
204
|
+
const bool last = (index + 1 == static_cast<int>(buckets_.size()));
|
|
205
|
+
|
|
206
|
+
// 1) A node that has actually failed a query is the first to go.
|
|
207
|
+
NodeEntry* stale = nullptr;
|
|
208
|
+
for (auto& n : live)
|
|
209
|
+
if (n.pinged() && n.fail_count > 0 && (!stale || n.fail_count > stale->fail_count))
|
|
210
|
+
stale = &n;
|
|
211
|
+
if (stale) { assign(*stale, e); return true; }
|
|
212
|
+
|
|
213
|
+
// 2) Otherwise keep a spread of sub-prefixes. Group the live nodes by their slot.
|
|
214
|
+
// classify_prefix yields prefix_bits_for(limit) bits, so the slot index is in
|
|
215
|
+
// [0, 2^prefix_bits). Size the table to the largest possible bucket limit so a
|
|
216
|
+
// wider top bucket can never index out of bounds (tied to bucket_limit's cap).
|
|
217
|
+
static constexpr int kMaxSlots = 1 << prefix_bits_for(kMaxBucketLimit);
|
|
218
|
+
static_assert(kMaxSlots <= 256, "slot index must fit in the uint8_t classify_prefix returns");
|
|
219
|
+
const uint8_t want = classify_prefix(index, last, limit, e.id);
|
|
220
|
+
std::array<std::vector<NodeEntry*>, kMaxSlots> slot{};
|
|
221
|
+
for (auto& n : live) slot[classify_prefix(index, last, limit, n.id)].push_back(&n);
|
|
222
|
+
|
|
223
|
+
const auto worst_in = [](const std::vector<NodeEntry*>& v) {
|
|
224
|
+
NodeEntry* worst = v.front();
|
|
225
|
+
for (auto* n : v) if (n->is_worse_than(*worst)) worst = n;
|
|
226
|
+
return worst;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
if (!slot[want].empty()) {
|
|
230
|
+
// Our slot is taken: displace its worst node only if we're strictly better.
|
|
231
|
+
NodeEntry* worst = worst_in(slot[want]);
|
|
232
|
+
if (worst->is_worse_than(e)) { assign(*worst, e); return true; }
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Our slot is empty: make room by dropping the worst node from an over-full slot,
|
|
237
|
+
// so a healthy contact alone in its slot is never sacrificed.
|
|
238
|
+
NodeEntry* victim = nullptr;
|
|
239
|
+
for (const auto& s : slot)
|
|
240
|
+
if (s.size() > 1)
|
|
241
|
+
for (auto* n : s)
|
|
242
|
+
if (!victim || n->is_worse_than(*victim)) victim = n;
|
|
243
|
+
if (victim) { assign(*victim, e); return true; }
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
void RoutingTable::stash_replacement(Bucket& b, const NodeEntry& entry) {
|
|
248
|
+
auto& r = b.replacements;
|
|
249
|
+
if (auto it = find_by_id(r, entry.id); it != r.end()) {
|
|
250
|
+
assign(*it, entry);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (r.size() < kBucketSize) {
|
|
254
|
+
r.push_back(entry);
|
|
255
|
+
ip_track(entry.endpoint.ip);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
auto worst = worst_of(r);
|
|
259
|
+
if (worst != r.end() && worst->is_worse_than(entry)) assign(*worst, entry);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
void RoutingTable::trim_to_limit(int index) {
|
|
263
|
+
Bucket& b = buckets_[index];
|
|
264
|
+
const int limit = bucket_limit(index);
|
|
265
|
+
// Spilling a live contact into replacements keeps it in the table, so the IP index
|
|
266
|
+
// is untouched. Only the final overflow — a contact dropped from the table — untracks.
|
|
267
|
+
while (static_cast<int>(b.live.size()) > limit) {
|
|
268
|
+
auto worst = worst_of(b.live);
|
|
269
|
+
b.replacements.push_back(std::move(*worst));
|
|
270
|
+
b.live.erase(worst);
|
|
271
|
+
}
|
|
272
|
+
while (b.replacements.size() > kBucketSize) {
|
|
273
|
+
auto worst = worst_of(b.replacements);
|
|
274
|
+
ip_untrack(worst->endpoint.ip);
|
|
275
|
+
b.replacements.erase(worst);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
void RoutingTable::fill_from_replacements(int index) {
|
|
280
|
+
Bucket& b = buckets_[index];
|
|
281
|
+
const int limit = bucket_limit(index);
|
|
282
|
+
while (static_cast<int>(b.live.size()) < limit && !b.replacements.empty()) {
|
|
283
|
+
// Promote the best standby — the one no other standby is better than, so
|
|
284
|
+
// confirmed contacts go before unpinged hearsay.
|
|
285
|
+
auto best = b.replacements.begin();
|
|
286
|
+
for (auto it = b.replacements.begin(); it != b.replacements.end(); ++it)
|
|
287
|
+
if (best->is_worse_than(*it)) best = it;
|
|
288
|
+
b.live.push_back(std::move(*best));
|
|
289
|
+
b.replacements.erase(best);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
void RoutingTable::node_failed(const NodeId& id, const Address& endpoint) {
|
|
294
|
+
const int idx = bucket_index(id);
|
|
295
|
+
Bucket& b = buckets_[idx];
|
|
296
|
+
|
|
297
|
+
auto it = find_by_id(b.live, id);
|
|
298
|
+
if (it == b.live.end()) {
|
|
299
|
+
// A standby we were probing can fail too — drop it if it's spent. Only when the
|
|
300
|
+
// endpoint matches, though: a different node claiming the same id must not knock
|
|
301
|
+
// out the standby we already have for it.
|
|
302
|
+
if (auto r = find_by_id(b.replacements, id);
|
|
303
|
+
r != b.replacements.end() && r->endpoint == endpoint) {
|
|
304
|
+
r->record_failure();
|
|
305
|
+
if (!r->pinged() || r->fail_count >= kMaxFailCount) {
|
|
306
|
+
ip_untrack(r->endpoint.ip);
|
|
307
|
+
b.replacements.erase(r);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// The id is live but now points at a different endpoint — a more recent sighting from
|
|
314
|
+
// another node overwrote it. This timeout belongs to the old node, so ignore it
|
|
315
|
+
// rather than penalise (or evict) the current, healthy contact.
|
|
316
|
+
if (it->endpoint != endpoint) return;
|
|
317
|
+
|
|
318
|
+
it->record_failure();
|
|
319
|
+
|
|
320
|
+
// A liveness check already failed here, so if a standby is ready, evict the
|
|
321
|
+
// failed contact and let the best standbys take the freed slot(s).
|
|
322
|
+
if (!b.replacements.empty()) {
|
|
323
|
+
ip_untrack(it->endpoint.ip);
|
|
324
|
+
b.live.erase(it);
|
|
325
|
+
fill_from_replacements(idx);
|
|
326
|
+
prune_empty_back();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// No standby: keep giving it chances until it's clearly dead.
|
|
331
|
+
if (!it->pinged() || it->fail_count >= kMaxFailCount) {
|
|
332
|
+
ip_untrack(it->endpoint.ip);
|
|
333
|
+
b.live.erase(it);
|
|
334
|
+
}
|
|
335
|
+
prune_empty_back();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
std::vector<NodeEntry> RoutingTable::find_closest(const NodeId& target, std::size_t count,
|
|
339
|
+
bool include_unconfirmed) const {
|
|
340
|
+
std::vector<NodeEntry> result;
|
|
341
|
+
if (count == 0) return result;
|
|
342
|
+
|
|
343
|
+
const auto take = [&](const Bucket& b) {
|
|
344
|
+
for (const auto& n : b.live)
|
|
345
|
+
if (include_unconfirmed || n.confirmed()) result.push_back(n);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// By the XOR metric's prefix structure, the buckets form distance tiers around the
|
|
349
|
+
// target's own bucket `start`:
|
|
350
|
+
// - bucket `start` is the *closest* tier: every contact in it is strictly nearer
|
|
351
|
+
// than any contact in a deeper bucket, so if it alone yields `count` we're done.
|
|
352
|
+
// - the deeper buckets (> start) are all tied at the same leading distance bit and
|
|
353
|
+
// are unordered among themselves, so they must be taken as one whole tier or not
|
|
354
|
+
// at all — we can stop before them, never partway through.
|
|
355
|
+
// - the shallower buckets (< start) are each a strictly farther tier in order, so
|
|
356
|
+
// they can be walked closest-first with an early stop the moment we have enough.
|
|
357
|
+
const int start = bucket_index(target);
|
|
358
|
+
result.reserve(count + static_cast<std::size_t>(buckets_[start].live.size()));
|
|
359
|
+
|
|
360
|
+
take(buckets_[start]);
|
|
361
|
+
if (result.size() < count) // closest tier short -> the deeper tier is needed in full
|
|
362
|
+
for (int i = start + 1; i < static_cast<int>(buckets_.size()); ++i) take(buckets_[i]);
|
|
363
|
+
for (int i = start - 1; i >= 0 && result.size() < count; --i) take(buckets_[i]);
|
|
364
|
+
|
|
365
|
+
const std::size_t k = (std::min)(count, result.size());
|
|
366
|
+
std::partial_sort(result.begin(), result.begin() + k, result.end(),
|
|
367
|
+
[&](const NodeEntry& a, const NodeEntry& c) { return closer_to(a.id, c.id, target); });
|
|
368
|
+
if (result.size() > count) result.resize(count);
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
std::optional<NodeEntry> RoutingTable::next_to_refresh(std::chrono::steady_clock::time_point now) {
|
|
373
|
+
// Order by last touch: a never-contacted contact (last_seen == min) sorts first,
|
|
374
|
+
// then the least-recently-touched.
|
|
375
|
+
NodeEntry* best = nullptr;
|
|
376
|
+
for (auto& b : buckets_)
|
|
377
|
+
for (auto& n : b.live)
|
|
378
|
+
if (!best || n.last_seen < best->last_seen) best = &n;
|
|
379
|
+
|
|
380
|
+
if (!best) return std::nullopt;
|
|
381
|
+
|
|
382
|
+
// Stamp it as touched now: we're about to probe it, so the next refresh moves on to
|
|
383
|
+
// a different contact instead of re-probing this one while its probe is in flight.
|
|
384
|
+
// A reply refreshes last_seen again; a timeout drops the contact (node_failed).
|
|
385
|
+
best->last_seen = now;
|
|
386
|
+
return *best;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
std::size_t RoutingTable::size() const {
|
|
390
|
+
std::size_t n = 0;
|
|
391
|
+
for (const auto& b : buckets_) n += b.live.size();
|
|
392
|
+
return n;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
void RoutingTable::set_self(const NodeId& id) {
|
|
396
|
+
if (id == self_) return;
|
|
397
|
+
|
|
398
|
+
// Drain every contact, re-root the table to a single bucket, then re-home each
|
|
399
|
+
// one — bucket indices are all relative to self_ and are now stale.
|
|
400
|
+
std::vector<NodeEntry> all;
|
|
401
|
+
for (auto& b : buckets_) {
|
|
402
|
+
for (auto& n : b.live) all.push_back(std::move(n));
|
|
403
|
+
for (auto& n : b.replacements) all.push_back(std::move(n));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
self_ = id;
|
|
407
|
+
buckets_.clear();
|
|
408
|
+
buckets_.emplace_back();
|
|
409
|
+
ip_count_.clear(); // rebuilt as add_node re-tracks each contact
|
|
410
|
+
for (const auto& n : all) add_node(n);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
std::vector<NodeEntry> RoutingTable::good_contacts() const {
|
|
414
|
+
std::vector<NodeEntry> out;
|
|
415
|
+
for (const auto& b : buckets_)
|
|
416
|
+
for (const auto& n : b.live)
|
|
417
|
+
if (n.confirmed()) out.push_back(n);
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
void RoutingTable::load_contacts(const std::vector<NodeEntry>& contacts) {
|
|
422
|
+
for (const auto& n : contacts) add_node(n);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
std::string RoutingTable::describe() const {
|
|
426
|
+
// One compact line per contact: "<id8> <ip:port> <rtt> <flags>", where flags are
|
|
427
|
+
// ? = only heard about (never replied) !N = N consecutive failures v = BEP 42-verified
|
|
428
|
+
const auto fmt_contact = [](const NodeEntry& n) {
|
|
429
|
+
std::ostringstream s;
|
|
430
|
+
s << short_hex(n.id) << ' ' << n.endpoint.to_string() << ' ';
|
|
431
|
+
if (n.rtt == NodeEntry::kRttUnknown) s << "-ms";
|
|
432
|
+
else s << n.rtt << "ms";
|
|
433
|
+
if (!n.pinged()) s << " ?";
|
|
434
|
+
else if (n.fail_count > 0) s << " !" << static_cast<int>(n.fail_count);
|
|
435
|
+
if (n.verified) s << " v";
|
|
436
|
+
return s.str();
|
|
437
|
+
};
|
|
438
|
+
// A 10-cell bar showing how full the live set is relative to this bucket's limit.
|
|
439
|
+
const auto fill_bar = [](std::size_t live, int limit) {
|
|
440
|
+
constexpr int width = 10;
|
|
441
|
+
int filled = limit > 0 ? static_cast<int>((live * width + limit / 2) / limit) : 0;
|
|
442
|
+
if (filled > width) filled = width;
|
|
443
|
+
return std::string(static_cast<std::size_t>(filled), '#') +
|
|
444
|
+
std::string(static_cast<std::size_t>(width - filled), '.');
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
std::ostringstream os;
|
|
448
|
+
os << "routing table: " << size() << " node(s) / " << buckets_.size()
|
|
449
|
+
<< " bucket(s) self " << short_hex(self_);
|
|
450
|
+
for (int i = 0; i < static_cast<int>(buckets_.size()); ++i) {
|
|
451
|
+
const Bucket& b = buckets_[i];
|
|
452
|
+
const int limit = bucket_limit(i);
|
|
453
|
+
|
|
454
|
+
std::ostringstream cnt;
|
|
455
|
+
cnt << b.live.size() << '/' << limit;
|
|
456
|
+
|
|
457
|
+
os << "\n #" << std::right << std::setw(2) << i << " "
|
|
458
|
+
<< std::left << std::setw(7) << cnt.str()
|
|
459
|
+
<< "repl " << std::right << std::setw(2) << b.replacements.size() << " "
|
|
460
|
+
<< '[' << fill_bar(b.live.size(), limit) << "] ";
|
|
461
|
+
|
|
462
|
+
if (b.live.empty()) { os << "(empty)"; continue; }
|
|
463
|
+
|
|
464
|
+
// best = the contact no other is better than; worst = the first to be evicted.
|
|
465
|
+
const NodeEntry* best = &b.live.front();
|
|
466
|
+
const NodeEntry* worst = &b.live.front();
|
|
467
|
+
for (const auto& n : b.live) {
|
|
468
|
+
if (best->is_worse_than(n)) best = &n;
|
|
469
|
+
if (n.is_worse_than(*worst)) worst = &n;
|
|
470
|
+
}
|
|
471
|
+
os << "best " << fmt_contact(*best);
|
|
472
|
+
if (b.live.size() > 1) os << " worst " << fmt_contact(*worst);
|
|
473
|
+
}
|
|
474
|
+
return os.str();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
void RoutingTable::prune_empty_back() {
|
|
478
|
+
while (buckets_.size() > 1
|
|
479
|
+
&& buckets_.back().live.empty()
|
|
480
|
+
&& buckets_.back().replacements.empty())
|
|
481
|
+
buckets_.pop_back();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// -- IP-diversity admission ---------------------------------------------------
|
|
485
|
+
|
|
486
|
+
void RoutingTable::ip_track(const IpAddress& ip) {
|
|
487
|
+
if (is_public_address(ip)) ++ip_count_[ip];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
void RoutingTable::ip_untrack(const IpAddress& ip) {
|
|
491
|
+
if (!is_public_address(ip)) return;
|
|
492
|
+
auto it = ip_count_.find(ip);
|
|
493
|
+
if (it == ip_count_.end()) return; // defensive: never underflow past zero
|
|
494
|
+
if (--it->second <= 0) ip_count_.erase(it);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
void RoutingTable::assign(NodeEntry& slot, const NodeEntry& e) {
|
|
498
|
+
ip_untrack(slot.endpoint.ip); // the displaced contact leaves the table
|
|
499
|
+
slot = e;
|
|
500
|
+
ip_track(e.endpoint.ip); // ... and the new one takes its place
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
std::optional<RoutingTable::Located> RoutingTable::find_by_endpoint(const Address& ep) {
|
|
504
|
+
for (int i = 0; i < static_cast<int>(buckets_.size()); ++i) {
|
|
505
|
+
Bucket& b = buckets_[i];
|
|
506
|
+
for (std::size_t j = 0; j < b.live.size(); ++j)
|
|
507
|
+
if (b.live[j].endpoint == ep) return Located{i, true, j};
|
|
508
|
+
for (std::size_t j = 0; j < b.replacements.size(); ++j)
|
|
509
|
+
if (b.replacements[j].endpoint == ep) return Located{i, false, j};
|
|
510
|
+
}
|
|
511
|
+
return std::nullopt;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
bool RoutingTable::passes_ip_diversity(Bucket& b, int idx, const NodeEntry& e) {
|
|
515
|
+
const IpAddress& ip = e.endpoint.ip;
|
|
516
|
+
if (!is_public_address(ip)) return true; // private/loopback/CGNAT: unconstrained
|
|
517
|
+
|
|
518
|
+
// Rule 1 — one contact per IP across the whole table. The same-id sighting was
|
|
519
|
+
// already handled in add_node, so a hit here means this IP is held under a
|
|
520
|
+
// *different* id: a routing-table poisoning signal.
|
|
521
|
+
if (ip_count_.find(ip) != ip_count_.end()) {
|
|
522
|
+
auto found = find_by_endpoint(e.endpoint);
|
|
523
|
+
if (!found) return false; // same IP, different port -> ignore newcomer
|
|
524
|
+
if (!e.pinged()) return false; // unconfirmed id swap -> ignore (poison)
|
|
525
|
+
|
|
526
|
+
// A *confirmed* contact is answering from an endpoint we already hold under
|
|
527
|
+
// another id. The old entry is now suspect, so drop it and don't trust the new
|
|
528
|
+
// one either — libtorrent's conservative "evict on changed id". The freed live
|
|
529
|
+
// slot refills from that bucket's standbys.
|
|
530
|
+
Bucket& eb = buckets_[found->bucket];
|
|
531
|
+
auto& set = found->live ? eb.live : eb.replacements;
|
|
532
|
+
ip_untrack(set[found->index].endpoint.ip);
|
|
533
|
+
set.erase(set.begin() + static_cast<std::ptrdiff_t>(found->index));
|
|
534
|
+
if (found->live) fill_from_replacements(found->bucket);
|
|
535
|
+
prune_empty_back();
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Rule 2 — at most one contact per /24 (v4) or /64 (v6) within this one bucket.
|
|
540
|
+
for (const auto& n : b.live)
|
|
541
|
+
if (ip_too_close(n.endpoint.ip, ip)) return false;
|
|
542
|
+
for (const auto& n : b.replacements)
|
|
543
|
+
if (ip_too_close(n.endpoint.ip, ip)) return false;
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
bool RoutingTable::ip_index_consistent() const {
|
|
548
|
+
std::unordered_map<IpAddress, int> expected;
|
|
549
|
+
for (const auto& b : buckets_) {
|
|
550
|
+
for (const auto& n : b.live)
|
|
551
|
+
if (is_public_address(n.endpoint.ip)) ++expected[n.endpoint.ip];
|
|
552
|
+
for (const auto& n : b.replacements)
|
|
553
|
+
if (is_public_address(n.endpoint.ip)) ++expected[n.endpoint.ip];
|
|
554
|
+
}
|
|
555
|
+
return expected == ip_count_;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
} // namespace dht
|
|
559
|
+
} // namespace librats
|