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,175 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file pubsub.h
|
|
5
|
+
* @brief Topic-based publish/subscribe — a GossipSub implementation over PeerNetwork.
|
|
6
|
+
*
|
|
7
|
+
* This is the real GossipSub, not plain floodsub. Each subscribed topic maintains
|
|
8
|
+
* a bounded *mesh* of full-message peers (formed and torn down with GRAFT/PRUNE),
|
|
9
|
+
* published messages are eagerly pushed along the mesh, and a lazy-pull gossip
|
|
10
|
+
* layer (IHAVE/IWANT, driven by the heartbeat) lets a peer recover a message it
|
|
11
|
+
* missed. Publishing to a topic we are not subscribed to uses a short-lived
|
|
12
|
+
* *fanout* set instead of a mesh. A node-id+sequence dedup key stops a message
|
|
13
|
+
* looping around the mesh, and a message cache keeps recent payloads so IWANT can
|
|
14
|
+
* be answered with real content.
|
|
15
|
+
*
|
|
16
|
+
* It depends on nothing but PeerNetwork — no Node, no reactor internals, no
|
|
17
|
+
* `friend`. A background heartbeat thread (started by start(), joined by stop())
|
|
18
|
+
* runs mesh maintenance and gossip emission once per interval.
|
|
19
|
+
*
|
|
20
|
+
* Wire format (MessageType::Gossip payload), all integers big-endian. The first
|
|
21
|
+
* byte is the op; a 40-byte message id is origin(32 id-bytes) || seqno(u64 BE):
|
|
22
|
+
* PUBLISH [0][origin:32][seqno:u64][topic_len:u16][topic][data]
|
|
23
|
+
* SUBSCRIBE [1][topic_len:u16][topic]
|
|
24
|
+
* UNSUBSCRIBE [2][topic_len:u16][topic]
|
|
25
|
+
* GRAFT [3][topic_len:u16][topic] // "add me to your mesh for topic"
|
|
26
|
+
* PRUNE [4][topic_len:u16][topic] // "drop me from your mesh for topic"
|
|
27
|
+
* IHAVE [5][topic_len:u16][topic][count:u16][id:40]* // "I hold these message ids"
|
|
28
|
+
* IWANT [6][count:u16][id:40]* // "send me these message ids"
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
#include "librats/util/rats_export.h"
|
|
32
|
+
#include "librats/node/peer_network.h"
|
|
33
|
+
#include "librats/peer/peer.h"
|
|
34
|
+
#include "librats/core/bytes.h"
|
|
35
|
+
#include "librats/peer/peer_id.h"
|
|
36
|
+
|
|
37
|
+
#include <atomic>
|
|
38
|
+
#include <chrono>
|
|
39
|
+
#include <condition_variable>
|
|
40
|
+
#include <cstdint>
|
|
41
|
+
#include <deque>
|
|
42
|
+
#include <functional>
|
|
43
|
+
#include <mutex>
|
|
44
|
+
#include <random>
|
|
45
|
+
#include <string>
|
|
46
|
+
#include <thread>
|
|
47
|
+
#include <unordered_map>
|
|
48
|
+
#include <unordered_set>
|
|
49
|
+
#include <utility>
|
|
50
|
+
#include <vector>
|
|
51
|
+
|
|
52
|
+
namespace librats {
|
|
53
|
+
|
|
54
|
+
/// Outcome of validating an inbound published message before it is delivered or
|
|
55
|
+
/// forwarded. REJECT drops it (and would penalise the sender in a scored build);
|
|
56
|
+
/// IGNORE drops it silently; ACCEPT delivers locally and forwards along the mesh.
|
|
57
|
+
enum class ValidationResult { Accept, Reject, Ignore };
|
|
58
|
+
|
|
59
|
+
class RATS_API PubSub final : public Subsystem {
|
|
60
|
+
public:
|
|
61
|
+
using Handler = std::function<void(const PeerId& from, const std::string& topic, ByteView data)>;
|
|
62
|
+
using Validator = std::function<ValidationResult(const PeerId& from, const std::string& topic, ByteView data)>;
|
|
63
|
+
|
|
64
|
+
/// GossipSub tuning. Defaults mirror the libp2p reference (D=6, D_low=4,
|
|
65
|
+
/// D_high=12) and are sensible for small-to-medium meshes.
|
|
66
|
+
struct Config {
|
|
67
|
+
int mesh_target = 6; ///< D — desired mesh degree per topic
|
|
68
|
+
int mesh_low = 4; ///< D_low — graft more peers below this
|
|
69
|
+
int mesh_high = 12; ///< D_high— prune peers above this
|
|
70
|
+
int fanout_size = 6; ///< peers used to publish to a topic we are not subscribed to
|
|
71
|
+
int gossip_factor = 6; ///< D_lazy— peers we emit IHAVE to per heartbeat per topic
|
|
72
|
+
std::chrono::milliseconds fanout_ttl{60000}; ///< drop a fanout set idle this long
|
|
73
|
+
std::chrono::milliseconds heartbeat_interval{1000}; ///< mesh maintenance + gossip cadence
|
|
74
|
+
int history_length = 5; ///< heartbeat windows the message cache keeps (for IWANT)
|
|
75
|
+
int history_gossip = 3; ///< of those, how many windows IHAVE advertises
|
|
76
|
+
size_t seen_limit = 8192; ///< dedup ids remembered
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
PubSub();
|
|
80
|
+
explicit PubSub(Config config);
|
|
81
|
+
~PubSub() override;
|
|
82
|
+
|
|
83
|
+
/// Subscribe to a topic and deliver matching messages to `handler`.
|
|
84
|
+
void subscribe(const std::string& topic, Handler handler);
|
|
85
|
+
void unsubscribe(const std::string& topic);
|
|
86
|
+
|
|
87
|
+
/// Publish `data` on `topic`: along the mesh if we are subscribed, else via fanout.
|
|
88
|
+
void publish(const std::string& topic, ByteView data);
|
|
89
|
+
|
|
90
|
+
bool is_subscribed(const std::string& topic) const;
|
|
91
|
+
std::vector<std::string> subscribed_topics() const;
|
|
92
|
+
std::vector<PeerId> peers_for_topic(const std::string& topic) const; ///< known subscribers
|
|
93
|
+
std::vector<PeerId> mesh_peers(const std::string& topic) const; ///< our mesh for topic
|
|
94
|
+
|
|
95
|
+
/// Gate inbound messages for a topic; `topic == ""` installs a global validator
|
|
96
|
+
/// used when no per-topic validator is registered.
|
|
97
|
+
void set_validator(const std::string& topic, Validator validator);
|
|
98
|
+
|
|
99
|
+
// Subsystem.
|
|
100
|
+
void attach(NodeContext& ctx) override;
|
|
101
|
+
void start() override; ///< launch the heartbeat thread
|
|
102
|
+
void stop() override; ///< stop and join it
|
|
103
|
+
|
|
104
|
+
private:
|
|
105
|
+
struct Topic {
|
|
106
|
+
std::unordered_set<PeerId, PeerId::Hash> subscribers; ///< peers that announced interest
|
|
107
|
+
std::unordered_set<PeerId, PeerId::Hash> mesh; ///< full-message peering (we subscribe)
|
|
108
|
+
std::unordered_set<PeerId, PeerId::Hash> fanout; ///< publish targets when not subscribed
|
|
109
|
+
std::chrono::steady_clock::time_point last_fanout{};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
struct CachedMessage {
|
|
113
|
+
std::string topic;
|
|
114
|
+
Bytes frame; ///< the whole PUBLISH frame, resent verbatim to answer IWANT
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Inbound dispatch (all run on a reactor thread).
|
|
118
|
+
void on_new_peer(const Peer& peer);
|
|
119
|
+
void on_peer_gone(const PeerId& id);
|
|
120
|
+
void on_gossip(const Peer& peer, ByteView payload);
|
|
121
|
+
|
|
122
|
+
void recv_subscription(const PeerId& from, const std::string& topic, bool subscribe);
|
|
123
|
+
void recv_graft(const PeerId& from, const std::string& topic);
|
|
124
|
+
void recv_prune(const PeerId& from, const std::string& topic);
|
|
125
|
+
void recv_publish(const PeerId& from, ByteView frame, const PeerId& origin, uint64_t seqno,
|
|
126
|
+
const std::string& topic, ByteView data);
|
|
127
|
+
void recv_ihave(const PeerId& from, const std::string& topic, const std::vector<std::string>& ids);
|
|
128
|
+
void recv_iwant(const PeerId& from, const std::vector<std::string>& ids);
|
|
129
|
+
|
|
130
|
+
// Heartbeat (background thread).
|
|
131
|
+
void heartbeat_loop();
|
|
132
|
+
void do_heartbeat();
|
|
133
|
+
|
|
134
|
+
/// Bring `topic`'s mesh toward [mesh_low, mesh_high] around mesh_target, queuing
|
|
135
|
+
/// the GRAFT/PRUNE control messages to send after the lock is released. Requires
|
|
136
|
+
/// `mutex_` held; a no-op unless we are subscribed to `topic`.
|
|
137
|
+
using CtrlList = std::vector<std::pair<PeerId, std::string>>;
|
|
138
|
+
void maintain_mesh_locked(const std::string& topic, CtrlList& grafts, CtrlList& prunes);
|
|
139
|
+
|
|
140
|
+
// Helpers.
|
|
141
|
+
void send_ctrl(const PeerId& to, uint8_t op, const std::string& topic);
|
|
142
|
+
void broadcast_ctrl(uint8_t op, const std::string& topic);
|
|
143
|
+
void deliver_local(const PeerId& from, const std::string& topic, ByteView data);
|
|
144
|
+
Handler handler_for(const std::string& topic) const;
|
|
145
|
+
ValidationResult validate(const PeerId& from, const std::string& topic, ByteView data);
|
|
146
|
+
bool mark_seen(const std::string& id); ///< false if already seen
|
|
147
|
+
void cache_message(const std::string& id, const std::string& topic, const Bytes& frame);
|
|
148
|
+
std::vector<PeerId> random_sample(std::vector<PeerId> in, int n);
|
|
149
|
+
|
|
150
|
+
PeerNetwork* network_ = nullptr;
|
|
151
|
+
Config config_;
|
|
152
|
+
|
|
153
|
+
mutable std::mutex mutex_; ///< guards subscriptions_, topics_, validators_, seqno_
|
|
154
|
+
uint64_t seqno_ = 0;
|
|
155
|
+
std::unordered_map<std::string, Handler> subscriptions_; ///< topic -> local handler
|
|
156
|
+
std::unordered_map<std::string, Topic> topics_; ///< topic -> peering state
|
|
157
|
+
std::unordered_map<std::string, Validator> validators_; ///< topic -> validator
|
|
158
|
+
Validator global_validator_;
|
|
159
|
+
|
|
160
|
+
mutable std::mutex mcache_mutex_; ///< guards mcache_, history_, seen_*
|
|
161
|
+
std::unordered_map<std::string, CachedMessage> mcache_; ///< id -> cached frame (for IWANT)
|
|
162
|
+
std::deque<std::vector<std::string>> history_; ///< gossip windows of ids (front = newest)
|
|
163
|
+
std::unordered_set<std::string> seen_; ///< dedup set
|
|
164
|
+
std::deque<std::string> seen_order_; ///< FIFO eviction order for seen_
|
|
165
|
+
|
|
166
|
+
std::mutex rng_mutex_;
|
|
167
|
+
std::mt19937 rng_;
|
|
168
|
+
|
|
169
|
+
std::atomic<bool> running_{false};
|
|
170
|
+
std::thread heartbeat_thread_;
|
|
171
|
+
std::mutex hb_mutex_;
|
|
172
|
+
std::condition_variable hb_cv_;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
} // namespace librats
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#include "librats/subsystems/reconnection.h"
|
|
2
|
+
#include "librats/node/node_context.h"
|
|
3
|
+
#include "librats/util/logger.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <chrono>
|
|
7
|
+
#include <unordered_set>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
namespace librats {
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
// Wall clock at the edge, kept out of PeerBook so the book stays pure/testable.
|
|
14
|
+
uint64_t now_secs() {
|
|
15
|
+
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::seconds>(
|
|
16
|
+
std::chrono::system_clock::now().time_since_epoch()).count());
|
|
17
|
+
}
|
|
18
|
+
} // namespace
|
|
19
|
+
|
|
20
|
+
ReconnectionService::ReconnectionService() : ReconnectionService(Config()) {}
|
|
21
|
+
|
|
22
|
+
ReconnectionService::ReconnectionService(Config config) : config_(std::move(config)) {
|
|
23
|
+
// Build the book up front so the pointer is fixed for the object's life (no race
|
|
24
|
+
// with reactor-thread reads in on_connected) and so add() before start() records
|
|
25
|
+
// into an already-loaded book rather than clobbering it.
|
|
26
|
+
if (!config_.store_path.empty()) {
|
|
27
|
+
book_ = std::make_unique<PeerBook>(config_.store_path);
|
|
28
|
+
book_->load();
|
|
29
|
+
// Age out the long tail and cap the archive immediately on load.
|
|
30
|
+
book_->prune(now_secs(), static_cast<uint64_t>(config_.archive_max_age.count()), config_.archive_max);
|
|
31
|
+
book_->save();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ReconnectionService::~ReconnectionService() { stop(); }
|
|
36
|
+
|
|
37
|
+
void ReconnectionService::add(const Address& address) {
|
|
38
|
+
{
|
|
39
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
40
|
+
if (targets_.count(address)) return;
|
|
41
|
+
if (targets_.size() >= config_.max_targets) {
|
|
42
|
+
LOG_WARN("reconnect", "Active-target cap (" << config_.max_targets
|
|
43
|
+
<< ") reached; dropping " << address.to_string());
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
Target& t = targets_[address];
|
|
47
|
+
t.address = address;
|
|
48
|
+
t.next_attempt = std::chrono::steady_clock::now();
|
|
49
|
+
}
|
|
50
|
+
if (book_) { book_->note_seen(address, now_secs()); book_->save(); }
|
|
51
|
+
wake_.notify_all();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void ReconnectionService::remove(const Address& address) {
|
|
55
|
+
bool erased;
|
|
56
|
+
{
|
|
57
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
58
|
+
erased = targets_.erase(address) > 0;
|
|
59
|
+
}
|
|
60
|
+
if (book_ && book_->remove(address)) book_->save();
|
|
61
|
+
if (erased) LOG_DEBUG("reconnect", "Stopped reconnecting to " << address.to_string());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
size_t ReconnectionService::target_count() const {
|
|
65
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
66
|
+
return targets_.size();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
std::vector<Address> ReconnectionService::known_peers(size_t n) const {
|
|
70
|
+
if (!book_) return {};
|
|
71
|
+
return book_->best(n, now_secs(), static_cast<uint64_t>(config_.archive_max_age.count()));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
void ReconnectionService::attach(NodeContext& ctx) {
|
|
75
|
+
network_ = &ctx.network;
|
|
76
|
+
network_->on_peer_connected([this](const Peer& peer) { on_connected(peer); });
|
|
77
|
+
network_->on_peer_disconnected([this](const PeerId& id) { on_disconnected(id); });
|
|
78
|
+
network_->on_dial_failed([this](const Address& addr) { on_dial_failed(addr); });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
void ReconnectionService::start() {
|
|
82
|
+
if (running_.exchange(true)) return;
|
|
83
|
+
|
|
84
|
+
// Seed the active set with the most promising peers from the book — the
|
|
85
|
+
// working set is "best N recent", the rest of the book stays a passive
|
|
86
|
+
// reserve pool reachable via known_peers().
|
|
87
|
+
if (book_) {
|
|
88
|
+
const auto recent = book_->best(config_.startup_targets, now_secs(),
|
|
89
|
+
static_cast<uint64_t>(config_.archive_max_age.count()));
|
|
90
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
91
|
+
for (const Address& addr : recent) {
|
|
92
|
+
if (targets_.size() >= config_.max_targets) break;
|
|
93
|
+
auto [it, inserted] = targets_.try_emplace(addr);
|
|
94
|
+
if (inserted) { it->second.address = addr; it->second.next_attempt = std::chrono::steady_clock::now(); }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
thread_ = std::thread(&ReconnectionService::loop, this);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
void ReconnectionService::stop() {
|
|
101
|
+
if (!running_.exchange(false)) return;
|
|
102
|
+
wake_.notify_all();
|
|
103
|
+
if (thread_.joinable()) thread_.join();
|
|
104
|
+
if (book_) book_->save();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// A successful connection. Two jobs: record the address in the book, and — the
|
|
108
|
+
// success-side mirror of on_dial_failed — clear this target's in-flight dial state
|
|
109
|
+
// right now. We must NOT wait for the loop to reconcile from the live-peer snapshot:
|
|
110
|
+
// a peer that connects and then drops again BETWEEN ticks would otherwise leave its
|
|
111
|
+
// dial flagged in-flight until dial_timeout, needlessly stalling the redial for
|
|
112
|
+
// seconds even though the address is plainly reachable. (For an inbound peer
|
|
113
|
+
// info->addresses is still empty at this point — its dialable address arrives later
|
|
114
|
+
// via identify — so an inbound link matches no target here and is reconciled by the
|
|
115
|
+
// loop's live snapshot instead; inbound links are thus not auto-persisted either.)
|
|
116
|
+
void ReconnectionService::on_connected(const Peer& peer) {
|
|
117
|
+
auto info = peer.info();
|
|
118
|
+
if (!info) return;
|
|
119
|
+
|
|
120
|
+
const uint64_t now = now_secs();
|
|
121
|
+
const auto steady_now = std::chrono::steady_clock::now();
|
|
122
|
+
bool book_changed = false;
|
|
123
|
+
{
|
|
124
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
125
|
+
for (const Address& addr : info->addresses) {
|
|
126
|
+
auto it = targets_.find(addr);
|
|
127
|
+
if (it == targets_.end()) {
|
|
128
|
+
if (!config_.persist_discovered) continue;
|
|
129
|
+
if (targets_.size() >= config_.max_targets) continue; // bound active growth
|
|
130
|
+
it = targets_.emplace(addr, Target{}).first;
|
|
131
|
+
it->second.address = addr;
|
|
132
|
+
}
|
|
133
|
+
it->second.mark_connected(steady_now); // resolve the in-flight dial now
|
|
134
|
+
if (book_) { book_->note_connected(addr, peer.id(), now); book_changed = true; }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (book_ && book_changed) book_->save();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// A peer dropped. The loop reconciles which targets are live from the directory
|
|
141
|
+
// snapshot, so there is no per-target state to clear here — just wake it so the
|
|
142
|
+
// re-dial happens at once rather than waiting out a tick.
|
|
143
|
+
void ReconnectionService::on_disconnected(const PeerId& /*id*/) {
|
|
144
|
+
wake_.notify_all();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// An outbound dial we asked for closed before establishing. Clear the in-flight
|
|
148
|
+
// flag so the target becomes eligible again — it then re-dials on its own backoff
|
|
149
|
+
// schedule (next_attempt, set when we dispatched the dial). Without this signal
|
|
150
|
+
// the target would stay "dialing" until the dial_deadline backstop, needlessly
|
|
151
|
+
// stretching every retry to that timeout.
|
|
152
|
+
void ReconnectionService::on_dial_failed(const Address& address) {
|
|
153
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
154
|
+
auto it = targets_.find(address);
|
|
155
|
+
if (it != targets_.end()) it->second.dialing = false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
std::chrono::milliseconds ReconnectionService::backoff_for(int attempts) const {
|
|
159
|
+
// base * 2^(attempts-1), capped at max.
|
|
160
|
+
auto delay = config_.base_backoff;
|
|
161
|
+
for (int i = 1; i < attempts && delay < config_.max_backoff; ++i) delay *= 2;
|
|
162
|
+
return (std::min)(delay, config_.max_backoff); // parenthesized: dodge windows.h min macro
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
void ReconnectionService::loop() {
|
|
166
|
+
std::unordered_set<Address> live; // reused across ticks to avoid re-allocating
|
|
167
|
+
while (running_.load()) {
|
|
168
|
+
const auto now = std::chrono::steady_clock::now();
|
|
169
|
+
|
|
170
|
+
// Ground truth for "this target is already connected": the union of every
|
|
171
|
+
// live peer's dialable addresses, identify-learned ones included. Matching
|
|
172
|
+
// on this — instead of a flag set only on the on_peer_connected event — is
|
|
173
|
+
// what lets us recognise a peer reached over a route we did not dial (an
|
|
174
|
+
// inbound link, or a cross-connect the directory resolved to the other
|
|
175
|
+
// connection). Without it such a peer is never seen as connected and we
|
|
176
|
+
// re-dial it forever, each dial torn down by the node as a duplicate.
|
|
177
|
+
live.clear();
|
|
178
|
+
for (const PeerInfo& p : network_->peers())
|
|
179
|
+
for (const Address& a : p.addresses)
|
|
180
|
+
live.insert(a);
|
|
181
|
+
|
|
182
|
+
std::vector<Address> to_dial, gave_up;
|
|
183
|
+
{
|
|
184
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
185
|
+
for (auto it = targets_.begin(); it != targets_.end();) {
|
|
186
|
+
Target& target = it->second;
|
|
187
|
+
|
|
188
|
+
// Connected right now (over any route): keep the retry state armed
|
|
189
|
+
// so a later drop re-dials promptly, and never dial a live peer.
|
|
190
|
+
if (live.count(it->first)) {
|
|
191
|
+
target.mark_connected(now);
|
|
192
|
+
++it;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// A dial is already in flight: don't pile on a duplicate while the
|
|
197
|
+
// handshake completes. We clear `dialing` on dial failure; the next
|
|
198
|
+
// tick clears it once the peer shows up live; dial_deadline is only a
|
|
199
|
+
// backstop for the case where neither signal ever arrives.
|
|
200
|
+
if (target.dialing && now < target.dial_deadline) { ++it; continue; }
|
|
201
|
+
if (target.next_attempt > now) { ++it; continue; }
|
|
202
|
+
|
|
203
|
+
// Give up actively dialing a persistently-dead target: drop it from
|
|
204
|
+
// the active set (it stays in the book as history, ranked down by its
|
|
205
|
+
// failure streak, until it ages out). A reconnect resets attempts, so
|
|
206
|
+
// only addresses that never came back are reaped.
|
|
207
|
+
if (config_.max_attempts > 0 && target.attempts >= static_cast<int>(config_.max_attempts)) {
|
|
208
|
+
gave_up.push_back(target.address);
|
|
209
|
+
it = targets_.erase(it);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
to_dial.push_back(target.address);
|
|
214
|
+
target.attempts++;
|
|
215
|
+
target.dialing = true;
|
|
216
|
+
target.dial_deadline = now + config_.dial_timeout;
|
|
217
|
+
target.next_attempt = now + backoff_for(target.attempts);
|
|
218
|
+
++it;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (book_ && !gave_up.empty()) {
|
|
222
|
+
const uint64_t ts = now_secs();
|
|
223
|
+
for (const Address& addr : gave_up) book_->note_failure(addr, ts);
|
|
224
|
+
book_->save();
|
|
225
|
+
}
|
|
226
|
+
for (const Address& addr : gave_up)
|
|
227
|
+
LOG_DEBUG("reconnect", "Giving up on " << addr.to_string() << " after "
|
|
228
|
+
<< config_.max_attempts << " attempts");
|
|
229
|
+
for (const Address& addr : to_dial) {
|
|
230
|
+
LOG_DEBUG("reconnect", "Dialing " << addr.to_string());
|
|
231
|
+
network_->connect(addr);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
std::unique_lock<std::mutex> lock(wait_mutex_);
|
|
235
|
+
wake_.wait_for(lock, config_.tick, [this] { return !running_.load(); });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
} // namespace librats
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file reconnection.h
|
|
5
|
+
* @brief Keeps connections to a set of peer addresses alive with backoff.
|
|
6
|
+
*
|
|
7
|
+
* A Subsystem that remembers peer addresses (optionally persisted via PeerBook)
|
|
8
|
+
* and re-dials them when they drop, using exponential backoff. Built only on
|
|
9
|
+
* PeerNetwork: it dials via connect() and, each tick, reconciles its targets
|
|
10
|
+
* against the set of currently-connected peers (PeerNetwork::peers(), whose
|
|
11
|
+
* addresses include the dialable endpoints learned via identify). A target whose
|
|
12
|
+
* address is served by a live peer — over ANY route, inbound or outbound — is
|
|
13
|
+
* left alone; only the rest are dialed. Reconciling against this snapshot, rather
|
|
14
|
+
* than trusting a flag toggled on the connect event, is what stops us from
|
|
15
|
+
* endlessly re-dialing a peer already connected over a link we did not initiate
|
|
16
|
+
* (an inbound link, or a cross-connect the directory resolved to the other side).
|
|
17
|
+
*
|
|
18
|
+
* A target is dropped when the app calls remove(), or — if Config::max_attempts
|
|
19
|
+
* is set — after that many consecutive failed dials without ever connecting (it
|
|
20
|
+
* "gives up", freeing memory and pruning the store). A successful connection
|
|
21
|
+
* resets the attempt counter, so only persistently-dead addresses are reaped.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
#include "librats/util/rats_export.h"
|
|
25
|
+
#include "librats/node/peer_network.h"
|
|
26
|
+
#include "librats/peer/peer.h"
|
|
27
|
+
#include "librats/core/address.h"
|
|
28
|
+
#include "librats/peer/peer_id.h"
|
|
29
|
+
#include "librats/peer/peer_book.h"
|
|
30
|
+
|
|
31
|
+
#include <atomic>
|
|
32
|
+
#include <chrono>
|
|
33
|
+
#include <condition_variable>
|
|
34
|
+
#include <memory>
|
|
35
|
+
#include <mutex>
|
|
36
|
+
#include <string>
|
|
37
|
+
#include <thread>
|
|
38
|
+
#include <unordered_map>
|
|
39
|
+
|
|
40
|
+
namespace librats {
|
|
41
|
+
|
|
42
|
+
class RATS_API ReconnectionService final : public Subsystem {
|
|
43
|
+
public:
|
|
44
|
+
struct Config {
|
|
45
|
+
std::string store_path = ""; ///< persist the peer book here (empty = memory only)
|
|
46
|
+
bool persist_discovered = true; ///< remember dialed peers automatically
|
|
47
|
+
size_t max_targets = 1024; ///< cap on ACTIVE re-dial targets (bounds memory + dial fan-out)
|
|
48
|
+
size_t max_attempts = 0; ///< give up actively dialing a target after this many consecutive failures; 0 = retry forever
|
|
49
|
+
size_t startup_targets = 32; ///< on start, actively re-dial this many best peers from the book
|
|
50
|
+
size_t archive_max = 4096; ///< cap on the persistent peer book (history of everyone we met)
|
|
51
|
+
std::chrono::seconds archive_max_age{std::chrono::hours(24 * 30)}; ///< forget peers unseen this long (30 days)
|
|
52
|
+
std::chrono::milliseconds base_backoff{1000};
|
|
53
|
+
std::chrono::milliseconds max_backoff{60000};
|
|
54
|
+
std::chrono::milliseconds tick{1000};
|
|
55
|
+
std::chrono::milliseconds dial_timeout{15000}; ///< assume an in-flight dial is dead after this if no connect/fail event arrives (backstop only)
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
ReconnectionService();
|
|
59
|
+
explicit ReconnectionService(Config config);
|
|
60
|
+
~ReconnectionService() override;
|
|
61
|
+
|
|
62
|
+
/// Register an address to keep connected. Persists it if a store is configured.
|
|
63
|
+
void add(const Address& address);
|
|
64
|
+
|
|
65
|
+
/// Stop reconnecting to an address: drops it as a target and from the store.
|
|
66
|
+
/// Use when the application intentionally parts with a peer and does not want
|
|
67
|
+
/// it re-dialed. (A later fresh connection to it may re-learn it if
|
|
68
|
+
/// persist_discovered is on.)
|
|
69
|
+
void remove(const Address& address);
|
|
70
|
+
|
|
71
|
+
size_t target_count() const;
|
|
72
|
+
|
|
73
|
+
/// The passive reserve pool: up to n best-known peer addresses from the book
|
|
74
|
+
/// (history of everyone we have connected to), most promising first. Not dialed
|
|
75
|
+
/// automatically beyond the startup seed — exposed for the app / discovery to
|
|
76
|
+
/// use as a bootstrap source when peer count is low.
|
|
77
|
+
std::vector<Address> known_peers(size_t n) const;
|
|
78
|
+
|
|
79
|
+
void attach(NodeContext& ctx) override;
|
|
80
|
+
void start() override;
|
|
81
|
+
void stop() override;
|
|
82
|
+
|
|
83
|
+
private:
|
|
84
|
+
struct Target {
|
|
85
|
+
Address address;
|
|
86
|
+
bool dialing = false; ///< a connect() is in flight; don't pile on a duplicate
|
|
87
|
+
int attempts = 0; ///< failed dials since the target was last seen connected
|
|
88
|
+
std::chrono::steady_clock::time_point next_attempt; ///< earliest time to (re)dial
|
|
89
|
+
std::chrono::steady_clock::time_point dial_deadline; ///< backstop: give up waiting on the in-flight dial after this
|
|
90
|
+
|
|
91
|
+
/// Record that this target is connected as of `now`: no dial is in flight
|
|
92
|
+
/// any more and the failure streak resets, so a later drop re-dials at once
|
|
93
|
+
/// (an address that just worked earns no backoff penalty). Used by BOTH the
|
|
94
|
+
/// loop's live-snapshot reconciliation and the on_connected event, so the
|
|
95
|
+
/// two paths that clear an in-flight dial can never drift apart.
|
|
96
|
+
void mark_connected(std::chrono::steady_clock::time_point now) {
|
|
97
|
+
dialing = false;
|
|
98
|
+
attempts = 0;
|
|
99
|
+
next_attempt = now;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
void on_connected(const Peer& peer);
|
|
104
|
+
void on_disconnected(const PeerId& id);
|
|
105
|
+
void on_dial_failed(const Address& address);
|
|
106
|
+
void loop();
|
|
107
|
+
std::chrono::milliseconds backoff_for(int attempts) const;
|
|
108
|
+
|
|
109
|
+
Config config_;
|
|
110
|
+
PeerNetwork* network_ = nullptr;
|
|
111
|
+
// Built once in the constructor (when store_path is set) and never reassigned,
|
|
112
|
+
// so the pointer is safe to read from any thread; PeerBook is itself internally
|
|
113
|
+
// synchronized. (Creating it in start() raced reads from on_connected, which can
|
|
114
|
+
// fire on a reactor thread before this subsystem's start() returns.)
|
|
115
|
+
std::unique_ptr<PeerBook> book_;
|
|
116
|
+
|
|
117
|
+
std::thread thread_;
|
|
118
|
+
std::atomic<bool> running_{false};
|
|
119
|
+
std::mutex wait_mutex_;
|
|
120
|
+
std::condition_variable wake_;
|
|
121
|
+
|
|
122
|
+
mutable std::mutex mutex_;
|
|
123
|
+
std::unordered_map<Address, Target> targets_; ///< keyed by the peer Address
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
} // namespace librats
|