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,185 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file routing_table.h
|
|
5
|
+
* @brief A Kademlia routing table with libtorrent-style dynamic buckets.
|
|
6
|
+
*
|
|
7
|
+
* Pure in-memory state — no sockets, no locks. It lives entirely on the DHT actor
|
|
8
|
+
* thread, so it never synchronises. It also never sends anything itself: liveness
|
|
9
|
+
* pings are issued by the owning Node, which reports the outcome back through
|
|
10
|
+
* node_seen() (it replied) / node_failed() (it timed out). That keeps the table a
|
|
11
|
+
* testable data structure and the I/O in one place.
|
|
12
|
+
*
|
|
13
|
+
* Buckets are an ordered list rooted at our own id: buckets_[0] is the *farthest*
|
|
14
|
+
* region of the keyspace and buckets_.back() the *closest* catch-all. When the last
|
|
15
|
+
* bucket overflows and a split would actually separate its contacts, a new bucket is
|
|
16
|
+
* appended and the contacts re-homed. Buckets near the top are larger (the extended
|
|
17
|
+
* routing table) because that is where the keyspace — and the first hop of almost any
|
|
18
|
+
* lookup — is densest, so more contacts there means fewer hops.
|
|
19
|
+
*
|
|
20
|
+
* Each bucket holds a *live* set — the contacts we route with, up to its size limit —
|
|
21
|
+
* plus a small *replacement cache* of standbys promoted the moment a live contact
|
|
22
|
+
* dies. Contact quality (RTT, failures, BEP 42 verification) lives on the NodeEntry
|
|
23
|
+
* and drives who gets kept, refreshed, or evicted, while a sub-prefix "spread" rule
|
|
24
|
+
* keeps a full bucket covering as many sub-branches as possible.
|
|
25
|
+
*
|
|
26
|
+
* Sybil/eclipse resistance (BEP 42). Admission is IP-diversity limited so no single
|
|
27
|
+
* operator can pack the table with contacts it controls and hijack lookups:
|
|
28
|
+
* 1. at most one contact per IP across the whole table, and
|
|
29
|
+
* 2. at most one contact per /24 (IPv4) or /64 (IPv6) within any single bucket.
|
|
30
|
+
* Only *public* addresses are constrained — private/loopback/CGNAT IPs can't be the
|
|
31
|
+
* source of a real Sybil attack and would wrongly collide on a LAN (this mirrors the
|
|
32
|
+
* BEP 42 exemption already applied by verify_node_id_for_ip). A per-IP count index
|
|
33
|
+
* (ip_count_) makes rule 1 an O(1) check; rule 2 is a small per-bucket scan.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
#include "librats/core/address.h"
|
|
37
|
+
#include "librats/dht/id.h"
|
|
38
|
+
#include "librats/dht/node_entry.h"
|
|
39
|
+
|
|
40
|
+
#include <chrono>
|
|
41
|
+
#include <cstddef>
|
|
42
|
+
#include <optional>
|
|
43
|
+
#include <string>
|
|
44
|
+
#include <unordered_map>
|
|
45
|
+
#include <vector>
|
|
46
|
+
|
|
47
|
+
namespace librats {
|
|
48
|
+
namespace dht {
|
|
49
|
+
|
|
50
|
+
class RoutingTable {
|
|
51
|
+
public:
|
|
52
|
+
// Consecutive failures tolerated before a contact with no standby is dropped.
|
|
53
|
+
static constexpr uint8_t kMaxFailCount = 5;
|
|
54
|
+
|
|
55
|
+
// `extended` enables the larger top buckets (the extended routing table). On by
|
|
56
|
+
// default for production; tests that want uniform k-sized buckets pass false.
|
|
57
|
+
explicit RoutingTable(const NodeId& self, bool extended = true);
|
|
58
|
+
|
|
59
|
+
// -- insertion --------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
// We learned this node exists from someone else's reply. Recorded as unpinged
|
|
62
|
+
// (a candidate to verify); it can fill a live slot but never splits or evicts a
|
|
63
|
+
// confirmed contact. `verified` is the caller's BEP 42 verdict on the (id, ip)
|
|
64
|
+
// pair, used only as a tie-breaker between unpinged candidates.
|
|
65
|
+
void heard_about(const NodeId& id, const Address& endpoint, bool verified = false);
|
|
66
|
+
|
|
67
|
+
// This node replied to us: recorded as confirmed with its RTT. A confirmed
|
|
68
|
+
// contact may split a full bucket or displace a weaker one, but a healthy contact
|
|
69
|
+
// alone in its sub-prefix slot is only removed via node_failed(). Returns true if
|
|
70
|
+
// it ended up live.
|
|
71
|
+
bool node_seen(const NodeId& id, const Address& endpoint,
|
|
72
|
+
uint16_t rtt = NodeEntry::kRttUnknown, bool verified = false);
|
|
73
|
+
|
|
74
|
+
// A query to this node timed out. Since this only fires after a real liveness
|
|
75
|
+
// check failed, the contact is replaced immediately if a standby is waiting;
|
|
76
|
+
// otherwise it's kept until it exhausts kMaxFailCount, then dropped.
|
|
77
|
+
void node_failed(const NodeId& id, const Address& endpoint);
|
|
78
|
+
|
|
79
|
+
// -- queries ----------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
// Up to `count` contacts closest to `target` (XOR metric), closest first. By
|
|
82
|
+
// default only confirmed contacts are returned (for answering queries); pass
|
|
83
|
+
// include_unconfirmed=true to also seed our own lookups from unpinged contacts.
|
|
84
|
+
std::vector<NodeEntry> find_closest(const NodeId& target, std::size_t count = kBucketSize,
|
|
85
|
+
bool include_unconfirmed = false) const;
|
|
86
|
+
|
|
87
|
+
// The live contact most in need of a liveness check: the least-recently-touched
|
|
88
|
+
// (a never-contacted contact sorts first). The chosen contact is stamped as touched
|
|
89
|
+
// at `now`, so successive refreshes rotate through different contacts instead of
|
|
90
|
+
// re-probing this one while its probe is still outstanding. nullopt when there are
|
|
91
|
+
// no live contacts.
|
|
92
|
+
std::optional<NodeEntry> next_to_refresh(std::chrono::steady_clock::time_point now);
|
|
93
|
+
|
|
94
|
+
std::size_t size() const; // number of live contacts
|
|
95
|
+
bool empty() const { return size() == 0; }
|
|
96
|
+
int bucket_count() const noexcept { return static_cast<int>(buckets_.size()); }
|
|
97
|
+
|
|
98
|
+
// A pretty, multi-line snapshot for periodic DEBUG logging: a header plus one row
|
|
99
|
+
// per bucket with its live/limit fill bar and best & worst contact. Pure formatting,
|
|
100
|
+
// no I/O — built only when actually logged (callers guard on the log level).
|
|
101
|
+
std::string describe() const;
|
|
102
|
+
|
|
103
|
+
// -- node id changes (BEP 42) ----------------------------------------------
|
|
104
|
+
|
|
105
|
+
const NodeId& self() const noexcept { return self_; }
|
|
106
|
+
void set_self(const NodeId& id); // re-buckets every contact against the new id
|
|
107
|
+
|
|
108
|
+
// -- persistence ------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
std::vector<NodeEntry> good_contacts() const; // confirmed live contacts, to save
|
|
111
|
+
void load_contacts(const std::vector<NodeEntry>& contacts); // bulk restore, preserving quality
|
|
112
|
+
|
|
113
|
+
// -- diagnostics ------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
// True iff the per-IP index (ip_count_) matches a fresh count over the buckets.
|
|
116
|
+
// Cheap invariant used by tests to prove the IP accounting stays balanced across
|
|
117
|
+
// arbitrary add/evict/fail churn; not needed at runtime.
|
|
118
|
+
bool ip_index_consistent() const;
|
|
119
|
+
|
|
120
|
+
private:
|
|
121
|
+
struct Bucket {
|
|
122
|
+
std::vector<NodeEntry> live; // active contacts, up to bucket_limit(index)
|
|
123
|
+
std::vector<NodeEntry> replacements; // standbys, up to kBucketSize
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// The bucket `id` belongs to: its shared-prefix length with us, capped to the
|
|
127
|
+
// last (catch-all) bucket. buckets_ is never empty, so this is always valid.
|
|
128
|
+
int bucket_index(const NodeId& id) const noexcept;
|
|
129
|
+
// Live-set size limit for a bucket by position: larger near the top when the
|
|
130
|
+
// extended table is on, otherwise a flat kBucketSize.
|
|
131
|
+
int bucket_limit(int index) const noexcept;
|
|
132
|
+
|
|
133
|
+
// The one insertion path. `e` carries its own quality (pinged/confirmed via
|
|
134
|
+
// fail_count). Returns true if it ended up in a live set.
|
|
135
|
+
bool add_node(NodeEntry e);
|
|
136
|
+
|
|
137
|
+
// -- IP-diversity admission (Sybil/eclipse resistance) ----------------------
|
|
138
|
+
|
|
139
|
+
// May a genuinely new (not-already-known-by-id) contact `e` enter bucket `idx`?
|
|
140
|
+
// Enforces the two rules above for public IPs only; a same-endpoint contact that
|
|
141
|
+
// has quietly changed its id is evicted here as a poisoning signal. Non-public
|
|
142
|
+
// addresses are always admissible. Rare collision handling lives in this one place.
|
|
143
|
+
bool passes_ip_diversity(Bucket& b, int idx, const NodeEntry& e);
|
|
144
|
+
|
|
145
|
+
// Locate the contact currently at endpoint `ep` (ip+port) anywhere in the table.
|
|
146
|
+
// Only walked on a public-IP collision, so the O(table) cost is off the hot path.
|
|
147
|
+
struct Located { int bucket; bool live; std::size_t index; };
|
|
148
|
+
std::optional<Located> find_by_endpoint(const Address& ep);
|
|
149
|
+
|
|
150
|
+
// Keep ip_count_ in lock-step with the buckets. The invariant is simple and local:
|
|
151
|
+
// every push into a live/replacement set calls ip_track, every removal ip_untrack,
|
|
152
|
+
// and an in-place overwrite goes through assign() (untrack old + track new). Moves
|
|
153
|
+
// between the two sets are therefore self-balancing. Only public IPs are indexed.
|
|
154
|
+
void ip_track(const IpAddress& ip);
|
|
155
|
+
void ip_untrack(const IpAddress& ip);
|
|
156
|
+
void assign(NodeEntry& slot, const NodeEntry& e); // overwrite a slot, keeping the index synced
|
|
157
|
+
|
|
158
|
+
// May the (full, last) bucket at `index` be split to fit a confirmed `e`?
|
|
159
|
+
bool can_split(int index, const NodeEntry& e) const;
|
|
160
|
+
// Append a bucket and re-home the old last bucket's contacts by shared prefix.
|
|
161
|
+
void split_bucket();
|
|
162
|
+
// Bucket `b` (index `index`, limit `limit`) is full and `e` is confirmed: free a
|
|
163
|
+
// slot by evicting the least valuable node while preserving sub-prefix spread.
|
|
164
|
+
bool replace_for_spread(Bucket& b, int index, int limit, const NodeEntry& e);
|
|
165
|
+
// Add/refresh a standby, evicting the worst one if the cache is full.
|
|
166
|
+
void stash_replacement(Bucket& b, const NodeEntry& entry);
|
|
167
|
+
// Spill live-set overflow into replacements and cap the cache (after a split).
|
|
168
|
+
void trim_to_limit(int index);
|
|
169
|
+
// Promote the best standbys into any free live slots a split opened up, so a
|
|
170
|
+
// parked contact isn't stuck (replacements are never pinged or refreshed).
|
|
171
|
+
void fill_from_replacements(int index);
|
|
172
|
+
// Drop trailing empty buckets, but always keep at least one.
|
|
173
|
+
void prune_empty_back();
|
|
174
|
+
|
|
175
|
+
NodeId self_;
|
|
176
|
+
bool extended_;
|
|
177
|
+
std::vector<Bucket> buckets_; // [0] = farthest, back() = closest catch-all
|
|
178
|
+
|
|
179
|
+
// Count of live+replacement contacts per *public* IP across the whole table. Backs
|
|
180
|
+
// rule 1 (one contact per IP) as an O(1) lookup; kept balanced by ip_track/untrack.
|
|
181
|
+
std::unordered_map<IpAddress, int> ip_count_;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
} // namespace dht
|
|
185
|
+
} // namespace librats
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#include "librats/dht/rpc_manager.h"
|
|
2
|
+
#include "librats/dht/krpc.h"
|
|
3
|
+
#include "librats/dht/log.h"
|
|
4
|
+
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
namespace librats {
|
|
8
|
+
namespace dht {
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
// Transaction ids are 2-byte strings on the wire.
|
|
13
|
+
std::string txn_to_string(uint16_t t) {
|
|
14
|
+
std::string s(2, '\0');
|
|
15
|
+
s[0] = static_cast<char>((t >> 8) & 0xff);
|
|
16
|
+
s[1] = static_cast<char>(t & 0xff);
|
|
17
|
+
return s;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
bool string_to_txn(const std::string& s, uint16_t& out) {
|
|
21
|
+
if (s.size() != 2) return false;
|
|
22
|
+
out = static_cast<uint16_t>((static_cast<uint8_t>(s[0]) << 8) | static_cast<uint8_t>(s[1]));
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
} // namespace
|
|
27
|
+
|
|
28
|
+
uint16_t RpcManager::next_txn() {
|
|
29
|
+
// Random, unpredictable transaction ids. A counter is trivially predictable, so an off-path
|
|
30
|
+
// attacker could forge replies to our queries; with a random id it must guess a
|
|
31
|
+
// 16-bit value *and* spoof the exact endpoint we queried (handle_response checks
|
|
32
|
+
// both). pending_ is keyed by txn, so re-draw on the rare collision with an id
|
|
33
|
+
// still in flight — 65536 ids vastly exceed what we ever have outstanding.
|
|
34
|
+
std::uniform_int_distribution<uint32_t> draw(0, 0xffff);
|
|
35
|
+
for (int i = 0; i < 65536; ++i) {
|
|
36
|
+
const uint16_t t = static_cast<uint16_t>(draw(rng_));
|
|
37
|
+
if (pending_.find(t) == pending_.end()) return t;
|
|
38
|
+
}
|
|
39
|
+
return static_cast<uint16_t>(draw(rng_)); // table full — unreachable in practice
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
bool RpcManager::invoke(KrpcMessage& msg, const Address& to, const ObserverPtr& obs, TimePoint now) {
|
|
43
|
+
const uint16_t t = next_txn();
|
|
44
|
+
msg.transaction_id = txn_to_string(t);
|
|
45
|
+
|
|
46
|
+
const std::vector<uint8_t> data = KrpcProtocol::encode_message(msg);
|
|
47
|
+
if (data.empty()) return false;
|
|
48
|
+
|
|
49
|
+
transport_.send(to, data);
|
|
50
|
+
obs->txn_ = t;
|
|
51
|
+
pending_[t] = Pending{obs, to, now};
|
|
52
|
+
LOG_DEBUG("dht.rpc", "→ " << KrpcProtocol::query_type_to_string(msg.query_type)
|
|
53
|
+
<< " to " << to.to_string() << " [txn " << t << "]");
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void RpcManager::send_oneshot(KrpcMessage& msg, const Address& to) {
|
|
58
|
+
msg.transaction_id = txn_to_string(next_txn()); // the peer may echo it; we don't track the reply
|
|
59
|
+
const std::vector<uint8_t> data = KrpcProtocol::encode_message(msg);
|
|
60
|
+
if (!data.empty()) {
|
|
61
|
+
transport_.send(to, data);
|
|
62
|
+
LOG_DEBUG("dht.rpc", "→ " << KrpcProtocol::query_type_to_string(msg.query_type)
|
|
63
|
+
<< " to " << to.to_string() << " (oneshot)");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
bool RpcManager::handle_response(const KrpcMessage& msg, const Address& from, TimePoint now) {
|
|
68
|
+
uint16_t t;
|
|
69
|
+
if (!string_to_txn(msg.transaction_id, t)) return false;
|
|
70
|
+
|
|
71
|
+
auto it = pending_.find(t);
|
|
72
|
+
if (it == pending_.end()) return false; // unknown / already resolved
|
|
73
|
+
if (it->second.endpoint != from) { // anti-spoof: not from whom we queried
|
|
74
|
+
LOG_DEBUG("dht.rpc", "dropped reply [txn " << t << "]: from " << from.to_string()
|
|
75
|
+
<< " != queried " << it->second.endpoint.to_string());
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ObserverPtr obs = it->second.obs; // keep alive across the callback
|
|
80
|
+
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - it->second.sent).count();
|
|
81
|
+
const uint16_t rtt = elapsed < 0 ? 0 : (elapsed > 0xfffe ? uint16_t(0xfffe) : uint16_t(elapsed));
|
|
82
|
+
pending_.erase(it);
|
|
83
|
+
|
|
84
|
+
if (msg.type == KrpcMessageType::Error) {
|
|
85
|
+
// A peer that explicitly rejected us is alive but refused — a different signal from
|
|
86
|
+
// silence, so log it distinctly even though it drives the same failed-query path.
|
|
87
|
+
LOG_DEBUG("dht.rpc", "<- error from " << from.to_string() << " [txn " << t << "]: "
|
|
88
|
+
<< static_cast<int>(msg.error_code) << ' ' << msg.error_message);
|
|
89
|
+
obs->on_timeout(now); // an error is a failed query
|
|
90
|
+
} else {
|
|
91
|
+
// The success half of the → query pair, carrying the round-trip we just measured.
|
|
92
|
+
LOG_DEBUG("dht.rpc", "<- response from " << from.to_string() << " (" << short_hex(msg.response_id)
|
|
93
|
+
<< ") [txn " << t << "] " << rtt << "ms");
|
|
94
|
+
obs->on_response(msg, rtt, now);
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void RpcManager::tick(TimePoint now) {
|
|
100
|
+
// Collect first (callbacks mutate pending_), and keep the observers alive while we call them.
|
|
101
|
+
std::vector<ObserverPtr> shorts, fulls;
|
|
102
|
+
for (auto& [t, p] : pending_) {
|
|
103
|
+
const auto elapsed = now - p.sent;
|
|
104
|
+
if (elapsed >= kFullTimeout)
|
|
105
|
+
fulls.push_back(p.obs);
|
|
106
|
+
else if (elapsed >= kShortTimeout && !p.obs->has(Observer::kShortTimeout))
|
|
107
|
+
shorts.push_back(p.obs);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (auto& o : shorts) o->on_short_timeout(now); // keeps the pending entry
|
|
111
|
+
for (auto& o : fulls) {
|
|
112
|
+
// Only the *full* timeout is logged (a query truly gave up) — short timeouts just
|
|
113
|
+
// widen the branch factor and fire constantly, so they stay silent.
|
|
114
|
+
LOG_DEBUG("dht.rpc", "timeout " << short_hex(o->id()) << ' ' << o->endpoint().to_string());
|
|
115
|
+
cancel(o.get());
|
|
116
|
+
o->on_timeout(now);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void RpcManager::cancel(Observer* obs) {
|
|
121
|
+
if (!obs) return;
|
|
122
|
+
auto it = pending_.find(obs->txn_);
|
|
123
|
+
if (it != pending_.end() && it->second.obs.get() == obs) pending_.erase(it);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
} // namespace dht
|
|
127
|
+
} // namespace librats
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file rpc_manager.h
|
|
5
|
+
* @brief Tracks outstanding DHT queries: transaction ids, timeouts, anti-spoofing.
|
|
6
|
+
*
|
|
7
|
+
* One unified place for every query the node sends — lookups, liveness pings,
|
|
8
|
+
* bootstrap — each represented by an Observer. invoke() stamps a transaction id,
|
|
9
|
+
* encodes and sends the message, and remembers it; handle_response() matches a reply
|
|
10
|
+
* back to its observer (only if it came from the very endpoint we queried) and
|
|
11
|
+
* dispatches it; tick() ages out the silent ones with a two-level timeout.
|
|
12
|
+
*
|
|
13
|
+
* Single-threaded actor: no locks, and time is passed in rather than read from a
|
|
14
|
+
* clock, so behaviour is fully deterministic and testable.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#include "librats/core/address.h"
|
|
18
|
+
#include "librats/dht/observer.h"
|
|
19
|
+
#include "librats/dht/transport.h"
|
|
20
|
+
|
|
21
|
+
#include <chrono>
|
|
22
|
+
#include <cstddef>
|
|
23
|
+
#include <cstdint>
|
|
24
|
+
#include <random>
|
|
25
|
+
#include <unordered_map>
|
|
26
|
+
|
|
27
|
+
namespace librats {
|
|
28
|
+
struct KrpcMessage;
|
|
29
|
+
|
|
30
|
+
namespace dht {
|
|
31
|
+
|
|
32
|
+
class RpcManager {
|
|
33
|
+
public:
|
|
34
|
+
// After the short timeout we free the query's concurrency slot but keep waiting;
|
|
35
|
+
// after the full timeout we give up on it.
|
|
36
|
+
static constexpr std::chrono::milliseconds kShortTimeout{2000};
|
|
37
|
+
static constexpr std::chrono::milliseconds kFullTimeout{15000};
|
|
38
|
+
|
|
39
|
+
explicit RpcManager(Transport& transport)
|
|
40
|
+
: transport_(transport), rng_(std::random_device{}()) {}
|
|
41
|
+
|
|
42
|
+
// Stamp a transaction id on `msg`, send it to `to`, and register `obs` to receive
|
|
43
|
+
// the outcome. Returns false if the message could not be encoded.
|
|
44
|
+
bool invoke(KrpcMessage& msg, const Address& to, const ObserverPtr& obs, TimePoint now);
|
|
45
|
+
|
|
46
|
+
// Send a query we don't track the reply to (e.g. announce_peer). Fire-and-forget.
|
|
47
|
+
void send_oneshot(KrpcMessage& msg, const Address& to);
|
|
48
|
+
|
|
49
|
+
// Route a reply/error back to its observer. Returns true only if it matched an
|
|
50
|
+
// outstanding query AND arrived from the endpoint we queried (anti-spoofing);
|
|
51
|
+
// unmatched or spoofed replies are ignored.
|
|
52
|
+
bool handle_response(const KrpcMessage& msg, const Address& from, TimePoint now);
|
|
53
|
+
|
|
54
|
+
// Fire short/full timeouts for queries that have gone quiet. Call periodically.
|
|
55
|
+
void tick(TimePoint now);
|
|
56
|
+
|
|
57
|
+
// Forget the in-flight query for `obs`, if any (the traversal is abandoning it).
|
|
58
|
+
void cancel(Observer* obs);
|
|
59
|
+
|
|
60
|
+
std::size_t outstanding() const noexcept { return pending_.size(); }
|
|
61
|
+
|
|
62
|
+
private:
|
|
63
|
+
struct Pending {
|
|
64
|
+
ObserverPtr obs;
|
|
65
|
+
Address endpoint;
|
|
66
|
+
TimePoint sent;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
uint16_t next_txn();
|
|
70
|
+
|
|
71
|
+
Transport& transport_;
|
|
72
|
+
std::unordered_map<uint16_t, Pending> pending_;
|
|
73
|
+
std::mt19937 rng_; // seeds unpredictable transaction ids (anti-spoofing)
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
} // namespace dht
|
|
77
|
+
} // namespace librats
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#include "librats/dht/storage.h"
|
|
2
|
+
#include "librats/crypto/sha1.h"
|
|
3
|
+
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <random>
|
|
6
|
+
|
|
7
|
+
namespace librats {
|
|
8
|
+
namespace dht {
|
|
9
|
+
|
|
10
|
+
// ---- TokenManager ----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
void TokenManager::maybe_rotate(TimePointSys now) {
|
|
13
|
+
if (!initialized_) {
|
|
14
|
+
std::random_device rd;
|
|
15
|
+
for (auto& b : secret_) b = static_cast<uint8_t>(rd());
|
|
16
|
+
prev_secret_ = secret_;
|
|
17
|
+
rotated_at_ = now;
|
|
18
|
+
initialized_ = true;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (now - rotated_at_ >= kRotateInterval) {
|
|
22
|
+
prev_secret_ = secret_;
|
|
23
|
+
std::random_device rd;
|
|
24
|
+
for (auto& b : secret_) b = static_cast<uint8_t>(rd());
|
|
25
|
+
rotated_at_ = now;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
std::string TokenManager::compute(const Address& querier, const InfoHash& info_hash,
|
|
30
|
+
const std::array<uint8_t, 16>& secret) const {
|
|
31
|
+
// Port is deliberately excluded — a NAT may show a different source port on the
|
|
32
|
+
// follow-up announce, but the address stays the same.
|
|
33
|
+
const ByteView ip = querier.ip.bytes();
|
|
34
|
+
std::vector<uint8_t> data;
|
|
35
|
+
data.reserve(secret.size() + ip.size() + info_hash.size());
|
|
36
|
+
data.insert(data.end(), secret.begin(), secret.end());
|
|
37
|
+
data.insert(data.end(), ip.begin(), ip.end());
|
|
38
|
+
data.insert(data.end(), info_hash.begin(), info_hash.end());
|
|
39
|
+
return SHA1::hash_bytes(data);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
std::string TokenManager::generate(const Address& querier, const InfoHash& info_hash, TimePointSys now) {
|
|
43
|
+
maybe_rotate(now);
|
|
44
|
+
return compute(querier, info_hash, secret_);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
bool TokenManager::verify(const Address& querier, const InfoHash& info_hash,
|
|
48
|
+
const std::string& token, TimePointSys now) {
|
|
49
|
+
if (token.empty()) return false;
|
|
50
|
+
maybe_rotate(now);
|
|
51
|
+
return token == compute(querier, info_hash, secret_)
|
|
52
|
+
|| token == compute(querier, info_hash, prev_secret_);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- PeerStore -------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
void PeerStore::store(const InfoHash& info_hash, const Address& peer, TimePointSys now) {
|
|
58
|
+
auto& peers = table_[info_hash];
|
|
59
|
+
for (auto& e : peers) {
|
|
60
|
+
if (e.peer == peer) { // already known → just refresh its timestamp
|
|
61
|
+
e.added = now;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
peers.push_back({peer, now});
|
|
66
|
+
if (peers.size() > kMaxPeersPerHash) peers.erase(peers.begin()); // drop the oldest
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
std::vector<Address> PeerStore::get(const InfoHash& info_hash, std::size_t max) const {
|
|
70
|
+
std::vector<Address> out;
|
|
71
|
+
auto it = table_.find(info_hash);
|
|
72
|
+
if (it == table_.end()) return out;
|
|
73
|
+
for (const auto& e : it->second) {
|
|
74
|
+
out.push_back(e.peer);
|
|
75
|
+
if (out.size() >= max) break;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void PeerStore::expire(TimePointSys now) {
|
|
81
|
+
for (auto it = table_.begin(); it != table_.end();) {
|
|
82
|
+
auto& peers = it->second;
|
|
83
|
+
peers.erase(std::remove_if(peers.begin(), peers.end(),
|
|
84
|
+
[&](const Entry& e) { return now - e.added > kPeerTtl; }),
|
|
85
|
+
peers.end());
|
|
86
|
+
if (peers.empty()) it = table_.erase(it);
|
|
87
|
+
else ++it;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
} // namespace dht
|
|
92
|
+
} // namespace librats
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file storage.h
|
|
5
|
+
* @brief Server-side DHT state: announce write-tokens and the announced-peer table (BEP 5).
|
|
6
|
+
*
|
|
7
|
+
* Both are pure data driven by the node's incoming-query handlers, with time passed
|
|
8
|
+
* in so they're deterministic and lock-free (single actor thread).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include "librats/core/address.h"
|
|
12
|
+
#include "librats/dht/id.h"
|
|
13
|
+
|
|
14
|
+
#include <array>
|
|
15
|
+
#include <chrono>
|
|
16
|
+
#include <cstddef>
|
|
17
|
+
#include <cstdint>
|
|
18
|
+
#include <map>
|
|
19
|
+
#include <string>
|
|
20
|
+
#include <vector>
|
|
21
|
+
|
|
22
|
+
namespace librats {
|
|
23
|
+
namespace dht {
|
|
24
|
+
|
|
25
|
+
using TimePointSys = std::chrono::steady_clock::time_point;
|
|
26
|
+
|
|
27
|
+
// Write tokens (BEP 5). A token is handed out with every get_peers reply and must be
|
|
28
|
+
// presented back on announce_peer — proving the announcer can actually receive at the
|
|
29
|
+
// address it claims. token = SHA1(secret || querier_ip || info_hash): unforgeable
|
|
30
|
+
// (the secret is private) and bound to both the address and the info-hash, so it can't
|
|
31
|
+
// be replayed for a different peer or torrent. The secret rotates periodically; the
|
|
32
|
+
// previous one stays valid across a single rotation so tokens don't expire abruptly.
|
|
33
|
+
class TokenManager {
|
|
34
|
+
public:
|
|
35
|
+
static constexpr std::chrono::minutes kRotateInterval{5};
|
|
36
|
+
|
|
37
|
+
std::string generate(const Address& querier, const InfoHash& info_hash, TimePointSys now);
|
|
38
|
+
bool verify(const Address& querier, const InfoHash& info_hash,
|
|
39
|
+
const std::string& token, TimePointSys now);
|
|
40
|
+
|
|
41
|
+
private:
|
|
42
|
+
void maybe_rotate(TimePointSys now);
|
|
43
|
+
std::string compute(const Address& querier, const InfoHash& info_hash,
|
|
44
|
+
const std::array<uint8_t, 16>& secret) const;
|
|
45
|
+
|
|
46
|
+
std::array<uint8_t, 16> secret_{};
|
|
47
|
+
std::array<uint8_t, 16> prev_secret_{};
|
|
48
|
+
TimePointSys rotated_at_{};
|
|
49
|
+
bool initialized_ = false;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// The peers that announced themselves under each info-hash, so we can answer
|
|
53
|
+
// get_peers. Peers expire after kPeerTtl; each info-hash is capped to bound memory.
|
|
54
|
+
class PeerStore {
|
|
55
|
+
public:
|
|
56
|
+
static constexpr std::chrono::minutes kPeerTtl{30};
|
|
57
|
+
static constexpr std::size_t kMaxPeersPerHash = 128;
|
|
58
|
+
|
|
59
|
+
void store(const InfoHash& info_hash, const Address& peer, TimePointSys now);
|
|
60
|
+
std::vector<Address> get(const InfoHash& info_hash, std::size_t max = kMaxPeersPerHash) const;
|
|
61
|
+
void expire(TimePointSys now);
|
|
62
|
+
|
|
63
|
+
std::size_t hash_count() const noexcept { return table_.size(); }
|
|
64
|
+
|
|
65
|
+
private:
|
|
66
|
+
struct Entry {
|
|
67
|
+
Address peer;
|
|
68
|
+
TimePointSys added;
|
|
69
|
+
};
|
|
70
|
+
std::map<InfoHash, std::vector<Entry>> table_;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
} // namespace dht
|
|
74
|
+
} // namespace librats
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file transport.h
|
|
5
|
+
* @brief The outbound datagram sink the DHT sends through.
|
|
6
|
+
*
|
|
7
|
+
* The real implementation (Phase 5) owns a UDP socket; tests substitute a mock that
|
|
8
|
+
* just records what was sent. Sending is fire-and-forget and must never block — the
|
|
9
|
+
* whole DHT runs on one actor thread.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#include "librats/core/address.h"
|
|
13
|
+
|
|
14
|
+
#include <cstdint>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
namespace librats {
|
|
18
|
+
namespace dht {
|
|
19
|
+
|
|
20
|
+
class Transport {
|
|
21
|
+
public:
|
|
22
|
+
virtual ~Transport() = default;
|
|
23
|
+
virtual void send(const Address& to, const std::vector<uint8_t>& datagram) = 0;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
} // namespace dht
|
|
27
|
+
} // namespace librats
|