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,865 @@
|
|
|
1
|
+
#include "librats/node/node.h"
|
|
2
|
+
#include "librats/node/host_events.h"
|
|
3
|
+
#include "librats/node/identify.h"
|
|
4
|
+
#include "librats/subsystems/message_json.h"
|
|
5
|
+
#include "librats/security/noise_security.h"
|
|
6
|
+
#include "librats/security/plaintext_security.h"
|
|
7
|
+
#include "librats/util/fs.h"
|
|
8
|
+
#include "librats/util/logger.h"
|
|
9
|
+
#include "librats/util/network_monitor.h"
|
|
10
|
+
#include "librats/util/network_utils.h"
|
|
11
|
+
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
#include <cstring>
|
|
14
|
+
#include <memory>
|
|
15
|
+
#include <utility>
|
|
16
|
+
|
|
17
|
+
namespace librats {
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
// Pick the listen socket's address family from the bind address:
|
|
22
|
+
// - empty or "::" → DualStack wildcard: one IPv6 socket that also accepts IPv4
|
|
23
|
+
// (IPv4-mapped), so the node is reachable over both families.
|
|
24
|
+
// - other IPv6 literal (contains ':') → IPv6-only, bound to that interface.
|
|
25
|
+
// - IPv4 literal / hostname → IPv4.
|
|
26
|
+
// A specific (non-wildcard) address pins a single interface, so dual-stack only
|
|
27
|
+
// applies to the wildcard cases where binding both families is meaningful.
|
|
28
|
+
AddressFamily family_for_bind(const std::string& bind_address) {
|
|
29
|
+
if (bind_address.empty() || bind_address == "::") return AddressFamily::DualStack;
|
|
30
|
+
if (bind_address.find(':') != std::string::npos) return AddressFamily::IPv6;
|
|
31
|
+
return AddressFamily::IPv4;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// A wildcard/empty IP is never a dialable endpoint; filter it out of identify.
|
|
35
|
+
bool is_unspecified_ip(const std::string& ip) {
|
|
36
|
+
return ip.empty() || ip == "0.0.0.0" || ip == "::";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
std::unique_ptr<SecurityProvider> make_security(const NodeConfig& cfg, const Identity& id) {
|
|
40
|
+
if (cfg.security == NodeConfig::Security::Noise)
|
|
41
|
+
return std::make_unique<NoiseSecurity>(id, cfg.protocol);
|
|
42
|
+
return std::make_unique<PlaintextSecurity>(id, cfg.protocol);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Load a persisted identity from <data_dir>/identity.key, or generate one and
|
|
46
|
+
// save it. An empty data_dir means ephemeral (fresh identity each run).
|
|
47
|
+
Identity load_or_create_identity(const std::string& data_dir) {
|
|
48
|
+
if (data_dir.empty()) return Identity::generate();
|
|
49
|
+
|
|
50
|
+
const std::string key_path = combine_paths(data_dir, "identity.key");
|
|
51
|
+
|
|
52
|
+
size_t size = 0;
|
|
53
|
+
void* data = read_file_binary(key_path.c_str(), &size);
|
|
54
|
+
if (data) {
|
|
55
|
+
if (size == librats::NOISE_DH_SIZE) {
|
|
56
|
+
uint8_t priv[librats::NOISE_DH_SIZE];
|
|
57
|
+
std::memcpy(priv, data, librats::NOISE_DH_SIZE);
|
|
58
|
+
free_file_buffer(data);
|
|
59
|
+
return Identity::from_private_key(priv);
|
|
60
|
+
}
|
|
61
|
+
free_file_buffer(data); // malformed key file → regenerate
|
|
62
|
+
LOG_WARN("node", "Ignoring malformed identity key at " << key_path);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Identity identity = Identity::generate();
|
|
66
|
+
create_directories(data_dir.c_str());
|
|
67
|
+
if (!create_file_binary(key_path.c_str(), identity.static_keypair.private_key, librats::NOISE_DH_SIZE))
|
|
68
|
+
LOG_WARN("node", "Failed to persist identity key to " << key_path);
|
|
69
|
+
return identity;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
} // namespace
|
|
73
|
+
|
|
74
|
+
Node::Node(NodeConfig config)
|
|
75
|
+
: config_(std::move(config)),
|
|
76
|
+
identity_(load_or_create_identity(config_.data_dir)),
|
|
77
|
+
security_(make_security(config_, identity_)),
|
|
78
|
+
reactors_(std::make_unique<ReactorPool>(config_.reactor_threads, *this, *security_)) {
|
|
79
|
+
max_peers_.store(config_.max_peers, std::memory_order_relaxed);
|
|
80
|
+
|
|
81
|
+
DialPolicy policy;
|
|
82
|
+
policy.preferred = config_.preferred_transport;
|
|
83
|
+
policy.allow_tcp = config_.enable_tcp;
|
|
84
|
+
policy.allow_udp = config_.enable_udp;
|
|
85
|
+
policy.fallback_delay = std::chrono::milliseconds(config_.transport_fallback_ms);
|
|
86
|
+
dialer_ = std::make_unique<Dialer>(*reactors_, policy,
|
|
87
|
+
[this](const std::string& host, uint16_t port) {
|
|
88
|
+
report_dial_failed(host, port);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Published here rather than in start(): a subsystem resolves what it needs
|
|
92
|
+
// during attach(), which runs before start(), and both of these are node state
|
|
93
|
+
// that exists from construction. Neither extends Node's public surface — they
|
|
94
|
+
// are narrow capabilities a module asks for by interface (see
|
|
95
|
+
// node/dial_service.h, node/nat_status.h).
|
|
96
|
+
services_.provide<DialService>(this);
|
|
97
|
+
services_.provide<CircuitService>(this);
|
|
98
|
+
services_.provide<ExternalAddressService>(&nat_status_);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Node::~Node() {
|
|
102
|
+
stop();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
MessageJson* Node::json() noexcept { return subsystem<MessageJson>(); }
|
|
108
|
+
|
|
109
|
+
bool Node::open_listeners() {
|
|
110
|
+
// Attempts at finding a port that is free for BOTH transports. A clash
|
|
111
|
+
// between the two is unlikely (TCP and UDP have separate port spaces) but it
|
|
112
|
+
// is not impossible, and silently landing on mismatched ports would make
|
|
113
|
+
// every address this node advertises half-wrong. The same attempts cover a
|
|
114
|
+
// configured port that some other process already holds: attempt N tries
|
|
115
|
+
// listen_port + N, so one squatter on the default port does not leave the
|
|
116
|
+
// host without a node.
|
|
117
|
+
constexpr int kBindAttempts = 5;
|
|
118
|
+
|
|
119
|
+
const AddressFamily family = family_for_bind(config_.bind_address);
|
|
120
|
+
const bool listen_tcp = config_.enable_listen && config_.enable_tcp;
|
|
121
|
+
const bool listen_udp = config_.enable_listen && config_.enable_udp;
|
|
122
|
+
|
|
123
|
+
if (config_.enable_listen && !listen_tcp && !listen_udp) {
|
|
124
|
+
LOG_ERROR("node", "enable_listen is set but neither transport is enabled");
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!config_.enable_listen) {
|
|
129
|
+
// Dial-only. TCP needs no socket up front, but the datagram transport
|
|
130
|
+
// still wants its one shared socket — that single socket is what gives
|
|
131
|
+
// every peer one NAT mapping instead of one each. It just does not need a
|
|
132
|
+
// predictable port, since nobody dials us.
|
|
133
|
+
if (!config_.enable_udp) return true;
|
|
134
|
+
udp_socket_ = create_udp_socket(0, config_.bind_address, family);
|
|
135
|
+
if (!is_valid_socket(udp_socket_))
|
|
136
|
+
LOG_WARN("node", "Could not open a UDP socket; dials will fall back to TCP");
|
|
137
|
+
else
|
|
138
|
+
reactors_->listen_udp(udp_socket_, family);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (int attempt = 0; attempt < kBindAttempts; ++attempt) {
|
|
143
|
+
socket_t udp = RATS_INVALID_SOCKET;
|
|
144
|
+
socket_t tcp = RATS_INVALID_SOCKET;
|
|
145
|
+
|
|
146
|
+
// Walk upward from the configured port; an ephemeral one is re-requested
|
|
147
|
+
// as 0 each time, since the kernel picks a different port on its own.
|
|
148
|
+
const int candidate = config_.listen_port == 0 ? 0 : config_.listen_port + attempt;
|
|
149
|
+
if (candidate > 65535) break;
|
|
150
|
+
uint16_t port = static_cast<uint16_t>(candidate);
|
|
151
|
+
|
|
152
|
+
// Which socket names the port, and which one has to accept the name.
|
|
153
|
+
//
|
|
154
|
+
// Whichever binds second is the one that can be refused: the kernel only
|
|
155
|
+
// ever hands out a port that is free for the protocol it is allocating
|
|
156
|
+
// for, and the two protocols have separate port spaces. So the order is a
|
|
157
|
+
// choice of which refusal to face, and the sides are not symmetric.
|
|
158
|
+
//
|
|
159
|
+
// The stream socket names it first, because the scarcity is on its side.
|
|
160
|
+
// Both draw from the same ephemeral range, but every outbound TCP
|
|
161
|
+
// connection on the host holds a source port in it, and goes on holding
|
|
162
|
+
// it through TIME_WAIT after it closes; a busy machine keeps tens of
|
|
163
|
+
// thousands there, and nearly nothing competes for the UDP half of the
|
|
164
|
+
// same range. Letting UDP name the port and then demanding TCP match it
|
|
165
|
+
// is a bet against that crowd, and under load it loses often enough to
|
|
166
|
+
// fail a node's start outright.
|
|
167
|
+
//
|
|
168
|
+
// That reasoning is about a port being *taken*, though, and on Windows
|
|
169
|
+
// the usual refusal is a port being *reserved* — the blocks Hyper-V,
|
|
170
|
+
// WinNAT and WSL2 carve out of the ephemeral range, which are not the
|
|
171
|
+
// same blocks for the two protocols. Against a reservation, retrying the
|
|
172
|
+
// same way barely moves: Windows hands out ephemeral ports in ascending
|
|
173
|
+
// order while a reserved block is contiguous and can be hundreds wide, so
|
|
174
|
+
// each retry lands one port further into the same hole. A retry therefore
|
|
175
|
+
// swaps the roles rather than repeating them — an ephemeral bind on the
|
|
176
|
+
// refused side steps clear of that side's reservations in one move, since
|
|
177
|
+
// the kernel never allocates out of them. Only worth doing when the port
|
|
178
|
+
// is ours to choose, and never on the last attempt, so running out of
|
|
179
|
+
// attempts still ends on the conservative order.
|
|
180
|
+
const bool udp_names_port = listen_tcp && listen_udp && port == 0
|
|
181
|
+
&& attempt % 2 == 1 && attempt + 1 < kBindAttempts;
|
|
182
|
+
if (udp_names_port) {
|
|
183
|
+
udp = create_udp_socket(0, config_.bind_address, family);
|
|
184
|
+
// Failing on a port the kernel picked itself is not a failure another
|
|
185
|
+
// port would fix, so there is nothing to walk to: leave the datagram
|
|
186
|
+
// socket to the usual path below and let TCP name the port after all.
|
|
187
|
+
if (is_valid_socket(udp))
|
|
188
|
+
port = static_cast<uint16_t>(get_bound_port(udp));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (listen_tcp) {
|
|
192
|
+
tcp = create_tcp_server(port, 128, config_.bind_address, family);
|
|
193
|
+
if (!is_valid_socket(tcp)) {
|
|
194
|
+
// A port that is taken, or that Windows keeps reserved, is the
|
|
195
|
+
// bind failure another port can answer. Every other one — no
|
|
196
|
+
// permission for a privileged port, a bind address that is not on
|
|
197
|
+
// this host, IPv6 switched off — fails identically on all 65535 of
|
|
198
|
+
// them, and walking the range would only bury the real error under
|
|
199
|
+
// four more copies of it.
|
|
200
|
+
const bool retry = last_error_was_port_unavailable()
|
|
201
|
+
&& attempt + 1 < kBindAttempts;
|
|
202
|
+
if (is_valid_socket(udp)) close_socket(udp);
|
|
203
|
+
if (retry) {
|
|
204
|
+
LOG_WARN("node", "TCP port " << port << " is unavailable, trying another");
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
LOG_ERROR("node", "Failed to listen on "
|
|
208
|
+
<< config_.bind_address << ":" << port);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
if (port == 0) port = static_cast<uint16_t>(get_bound_port(tcp));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Skipped when the datagram socket already has the port: it named it.
|
|
215
|
+
if (listen_udp && !is_valid_socket(udp)) {
|
|
216
|
+
udp = create_udp_socket(port, config_.bind_address, family);
|
|
217
|
+
if (is_valid_socket(udp)) {
|
|
218
|
+
if (port == 0) port = static_cast<uint16_t>(get_bound_port(udp));
|
|
219
|
+
} else if (listen_tcp) {
|
|
220
|
+
// Worth another attempt only for a port another port would fix,
|
|
221
|
+
// and only while the next attempt would actually move: both the
|
|
222
|
+
// ephemeral case and the configured one land on a different port
|
|
223
|
+
// next time round. Windows makes this path the common one — its
|
|
224
|
+
// reserved ranges differ between TCP and UDP, so a port the stream
|
|
225
|
+
// socket was handed can be refused to the datagram one.
|
|
226
|
+
if (last_error_was_port_unavailable() && attempt + 1 < kBindAttempts) {
|
|
227
|
+
close_socket(tcp);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
// Out of attempts, or a failure no other port would fix: the node
|
|
231
|
+
// still has a working transport, so it runs on that one rather
|
|
232
|
+
// than refusing to start.
|
|
233
|
+
LOG_WARN("node", "UDP port " << port << " unavailable; this node runs TCP-only");
|
|
234
|
+
} else {
|
|
235
|
+
// UDP-only node: nothing to fall back to, so an unavailable port
|
|
236
|
+
// is walked past exactly as it is for TCP above.
|
|
237
|
+
if (last_error_was_port_unavailable() && attempt + 1 < kBindAttempts) {
|
|
238
|
+
LOG_WARN("node", "UDP port " << port << " is unavailable, trying another");
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
LOG_ERROR("node", "Failed to listen on UDP "
|
|
242
|
+
<< config_.bind_address << ":" << port);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (config_.listen_port != 0 && port != config_.listen_port) {
|
|
248
|
+
LOG_WARN("node", "Configured port " << config_.listen_port
|
|
249
|
+
<< " was unavailable; listening on " << port << " instead");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
listen_socket_ = tcp;
|
|
253
|
+
udp_socket_ = udp;
|
|
254
|
+
listen_port_ = port;
|
|
255
|
+
if (is_valid_socket(tcp)) {
|
|
256
|
+
reactors_->listen(tcp);
|
|
257
|
+
transports_ |= PeerTransportTcp;
|
|
258
|
+
}
|
|
259
|
+
if (is_valid_socket(udp)) {
|
|
260
|
+
reactors_->listen_udp(udp, family);
|
|
261
|
+
transports_ |= PeerTransportUdp;
|
|
262
|
+
}
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Reached only by running the port range off its end; the last attempt does
|
|
267
|
+
// not retry, it settles for the transport it has.
|
|
268
|
+
LOG_ERROR("node", "No free port found from " << config_.listen_port
|
|
269
|
+
<< " upward for " << config_.bind_address);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
bool Node::start() {
|
|
274
|
+
if (running_.exchange(true)) return false;
|
|
275
|
+
init_socket_library();
|
|
276
|
+
|
|
277
|
+
dialer_->start(); // a node may be restarted; clear any state from the last run
|
|
278
|
+
|
|
279
|
+
if (!open_listeners()) {
|
|
280
|
+
running_.store(false);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Compute our advertised addresses once; the network monitor refreshes them
|
|
285
|
+
// on change. Done here (not per-connection) so the send path stays syscall-free.
|
|
286
|
+
rebuild_advertised_addresses(network_utils::get_local_interface_addresses());
|
|
287
|
+
|
|
288
|
+
// Attach subsystems (registers their message handlers, event subscriptions
|
|
289
|
+
// and service providers) BEFORE any reactor thread runs, so the router and the
|
|
290
|
+
// event/service tables are fully built with no concurrent writes.
|
|
291
|
+
NodeContext ctx{*this, events_, services_};
|
|
292
|
+
for (auto& s : subsystems_) s->attach(ctx);
|
|
293
|
+
|
|
294
|
+
reactors_->start();
|
|
295
|
+
|
|
296
|
+
for (auto& s : subsystems_) s->start();
|
|
297
|
+
|
|
298
|
+
start_network_monitor();
|
|
299
|
+
|
|
300
|
+
LOG_INFO("node", "Node " << identity_.id.short_hex() << " started on port " << listen_port_
|
|
301
|
+
<< " (" << ((transports_ & PeerTransportTcp) ? "tcp" : "")
|
|
302
|
+
<< ((transports_ == (PeerTransportTcp | PeerTransportUdp)) ? "+" : "")
|
|
303
|
+
<< ((transports_ & PeerTransportUdp) ? "udp" : "")
|
|
304
|
+
<< ", " << reactors_->size() << " reactor(s), "
|
|
305
|
+
<< subsystems_.size() << " subsystem(s))");
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
void Node::stop() {
|
|
310
|
+
if (!running_.exchange(false)) return;
|
|
311
|
+
dialer_->stop(); // no more fallback attempts get started
|
|
312
|
+
stop_network_monitor(); // no more NetworkChanged after this
|
|
313
|
+
// Stop subsystems in reverse attach order, so a subsystem that depends on an
|
|
314
|
+
// earlier one (e.g. Bittorrent borrowing DhtDiscovery's DhtClient) tears down
|
|
315
|
+
// before the dependency it borrowed is itself stopped and destroyed.
|
|
316
|
+
for (auto it = subsystems_.rbegin(); it != subsystems_.rend(); ++it) (*it)->stop();
|
|
317
|
+
reactors_->stop(); // then join reactors; close connections
|
|
318
|
+
// The listen sockets are owned by us, not the reactors — close them here,
|
|
319
|
+
// after the reactor threads have joined (so nothing is still polling them), to
|
|
320
|
+
// release the bound port immediately on stop() rather than leaking it until
|
|
321
|
+
// destruction.
|
|
322
|
+
if (is_valid_socket(listen_socket_)) {
|
|
323
|
+
close_socket(listen_socket_);
|
|
324
|
+
listen_socket_ = RATS_INVALID_SOCKET;
|
|
325
|
+
}
|
|
326
|
+
if (is_valid_socket(udp_socket_)) {
|
|
327
|
+
close_socket(udp_socket_);
|
|
328
|
+
udp_socket_ = RATS_INVALID_SOCKET;
|
|
329
|
+
}
|
|
330
|
+
transports_ = 0;
|
|
331
|
+
LOG_INFO("node", "Node " << identity_.id.short_hex() << " stopped");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── Host network-change watch ────────────────────────────────────────────────
|
|
335
|
+
//
|
|
336
|
+
// One monitor, many reactors: the NetworkMonitor detects a change on its own
|
|
337
|
+
// thread and hands the new address set to the maintenance thread, which performs
|
|
338
|
+
// the (possibly slow) EventBus emit. Subscribers — PortMappingService, DhtDiscovery,
|
|
339
|
+
// … — react independently without the monitor knowing they exist.
|
|
340
|
+
|
|
341
|
+
void Node::start_network_monitor() {
|
|
342
|
+
if (!config_.enable_network_monitor) return;
|
|
343
|
+
|
|
344
|
+
maintenance_stop_ = false;
|
|
345
|
+
maintenance_thread_ = std::thread([this] { maintenance_loop(); });
|
|
346
|
+
|
|
347
|
+
monitor_ = std::make_unique<NetworkMonitor>();
|
|
348
|
+
monitor_->start([this](const std::vector<std::string>& addresses) {
|
|
349
|
+
// Runs on the monitor thread; just hand off and return promptly. Coalesces:
|
|
350
|
+
// a burst collapses to one emit carrying the latest address set.
|
|
351
|
+
{
|
|
352
|
+
std::lock_guard<std::mutex> lock(maintenance_mutex_);
|
|
353
|
+
pending_addresses_ = addresses;
|
|
354
|
+
maintenance_pending_ = true;
|
|
355
|
+
}
|
|
356
|
+
maintenance_cv_.notify_one();
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
void Node::stop_network_monitor() {
|
|
361
|
+
monitor_.reset(); // joins the monitor thread → no further hand-offs
|
|
362
|
+
|
|
363
|
+
{
|
|
364
|
+
std::lock_guard<std::mutex> lock(maintenance_mutex_);
|
|
365
|
+
maintenance_stop_ = true;
|
|
366
|
+
}
|
|
367
|
+
maintenance_cv_.notify_one();
|
|
368
|
+
if (maintenance_thread_.joinable()) maintenance_thread_.join();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
void Node::maintenance_loop() {
|
|
372
|
+
for (;;) {
|
|
373
|
+
std::vector<std::string> addresses;
|
|
374
|
+
{
|
|
375
|
+
std::unique_lock<std::mutex> lock(maintenance_mutex_);
|
|
376
|
+
maintenance_cv_.wait(lock, [this] { return maintenance_pending_ || maintenance_stop_; });
|
|
377
|
+
if (maintenance_stop_) return;
|
|
378
|
+
maintenance_pending_ = false;
|
|
379
|
+
addresses = std::move(pending_addresses_);
|
|
380
|
+
}
|
|
381
|
+
LOG_INFO("node", "Network change: " << addresses.size()
|
|
382
|
+
<< " local address(es); notifying subsystems");
|
|
383
|
+
rebuild_advertised_addresses(addresses); // keep identify's advertised set fresh
|
|
384
|
+
// Every external endpoint we learned was a mapping made by the path that
|
|
385
|
+
// just changed underneath us. Holding on to it would aim a punch at a port
|
|
386
|
+
// that no longer exists; peers re-report as their links come back.
|
|
387
|
+
nat_status_.reset();
|
|
388
|
+
events_.emit(NetworkChanged{std::move(addresses)});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ── Connections ─────────────────────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
void Node::connect(const Address& address) { connect(address.ip.to_string(), address.port); }
|
|
395
|
+
|
|
396
|
+
void Node::connect(const std::string& host, uint16_t port) {
|
|
397
|
+
// The dialer owns the transport choice: preferred first, the other raced in
|
|
398
|
+
// after the fallback delay, loser dropped. See node/dialer.h.
|
|
399
|
+
dialer_->dial(host, port);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
bool Node::dial_direct(const Address& addr, TransportKind kind, const DialProfile& profile) {
|
|
403
|
+
if (!addr.is_valid()) return false;
|
|
404
|
+
if (kind == TransportKind::Udp && !reactors_->has_udp()) return false;
|
|
405
|
+
if (kind == TransportKind::Tcp && !config_.enable_tcp) return false;
|
|
406
|
+
// A relayed circuit is not dialed: it is negotiated with a third node and then
|
|
407
|
+
// adopted (see node/circuit_service.h). There is no address to aim at here.
|
|
408
|
+
if (kind == TransportKind::Relay) return false;
|
|
409
|
+
|
|
410
|
+
// Straight to the reactor, deliberately around the Dialer: this dial is not a
|
|
411
|
+
// race and must not become one (see node/dial_service.h). The Dialer is left
|
|
412
|
+
// unaware of it, which is exactly right — its bookkeeping is keyed by
|
|
413
|
+
// (reactor, conn) and simply does not match, so the connection's later
|
|
414
|
+
// established/closed reports are no-ops there rather than corrections.
|
|
415
|
+
const ConnId id = reactors_->pick(kind).connect(addr.ip.to_string(), addr.port, kind, profile);
|
|
416
|
+
return id != kInvalidConnId;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ── CircuitService: a relayed byte stream becomes a peer connection ─────────
|
|
420
|
+
|
|
421
|
+
std::optional<PeerRoute> Node::adopt_circuit(const PeerId& carrier, std::unique_ptr<Link> link,
|
|
422
|
+
ConnRole role, bool connected) {
|
|
423
|
+
if (!link) return std::nullopt;
|
|
424
|
+
|
|
425
|
+
// The carrier has to still be a peer: it is the only thing this circuit's bytes
|
|
426
|
+
// can travel through, and a connection whose carrier is already gone would
|
|
427
|
+
// simply sit there until the establish deadline reaped it.
|
|
428
|
+
const auto carrier_route = peers_.route(carrier);
|
|
429
|
+
if (!carrier_route) return std::nullopt;
|
|
430
|
+
|
|
431
|
+
// Inbound circuits go through the same admission gate as an accepted socket.
|
|
432
|
+
// A relayed connection is cheaper for whoever opens it than a direct one — no
|
|
433
|
+
// NAT to get through, someone else's bandwidth — so if anything it deserves the
|
|
434
|
+
// gate more, not less.
|
|
435
|
+
if (role == ConnRole::Inbound && !admit_inbound()) return std::nullopt;
|
|
436
|
+
|
|
437
|
+
Reactor& reactor = reactors_->by_index(carrier_route->reactor);
|
|
438
|
+
// Same reactor as the carrier, which is the whole point: every byte of this
|
|
439
|
+
// circuit arrives on the carrier's connection and leaves through it, so the two
|
|
440
|
+
// share a thread and the relayed path needs no locks and no hand-offs.
|
|
441
|
+
const ConnId id = reactor.reserve_conn_id();
|
|
442
|
+
reactor.adopt_link(id, std::move(link), role, connected);
|
|
443
|
+
return PeerRoute{carrier_route->reactor, id};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
void Node::wake_circuit(PeerRoute route, uint32_t events) {
|
|
447
|
+
reactors_->by_index(route.reactor).wake(route.conn, events);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
void Node::close_circuit(PeerRoute route, CloseReason reason) {
|
|
451
|
+
reactors_->by_index(route.reactor).close(route.conn, reason);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
void Node::report_dial_failed(const std::string& host, uint16_t port) {
|
|
455
|
+
// Only a numeric target can be handed on as an Address; a dial by hostname has
|
|
456
|
+
// no dialable Address to report (peers cannot use our resolver), and the
|
|
457
|
+
// subscribers of this event — a reconnection policy, say — deal in addresses.
|
|
458
|
+
const auto ip = IpAddress::parse(host);
|
|
459
|
+
if (!ip) return;
|
|
460
|
+
const Address addr{*ip, port};
|
|
461
|
+
for (auto& cb : dial_failed_) cb(addr);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
std::optional<Peer> Node::peer(const PeerId& id) {
|
|
465
|
+
auto route = peers_.route(id);
|
|
466
|
+
if (!route) return std::nullopt;
|
|
467
|
+
return make_peer(id, *route);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ── Application messaging ────────────────────────────────────────────────────
|
|
471
|
+
|
|
472
|
+
bool Node::send(const PeerId& to, std::string_view channel, ByteView payload) {
|
|
473
|
+
// Route and writability in one lookup. The answer is the queue's state as of
|
|
474
|
+
// now rather than after this message lands — the send itself is handed to the
|
|
475
|
+
// owning reactor and completes there — which is all a backpressure hint can
|
|
476
|
+
// ever be, and is enough: it is the *previous* messages that filled the queue.
|
|
477
|
+
auto dest = peers_.destination(to);
|
|
478
|
+
if (!dest) return false;
|
|
479
|
+
// Charged before the hand-off and discharged by the reactor task, so a caller
|
|
480
|
+
// in a tight loop is answered on what it has actually offered rather than on
|
|
481
|
+
// what the reactor has managed to look at so far.
|
|
482
|
+
const size_t in_transit =
|
|
483
|
+
dest->owed->fetch_add(payload.size(), std::memory_order_relaxed) + payload.size();
|
|
484
|
+
route_send(dest->route, FrameHeader{MessageType::App, 0, MessageRouter::channel_id(channel)},
|
|
485
|
+
payload.to_bytes(), dest->owed);
|
|
486
|
+
return dest->writable && in_transit <= send_low_water();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
bool Node::peer_writable(const PeerId& id) const {
|
|
490
|
+
const auto dest = peers_.destination(id);
|
|
491
|
+
return dest && dest->writable;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
bool Node::broadcast(std::string_view channel, ByteView payload) {
|
|
495
|
+
auto data = std::make_shared<const Bytes>(payload.to_bytes());
|
|
496
|
+
FrameHeader header{MessageType::App, 0, MessageRouter::channel_id(channel)};
|
|
497
|
+
reactors_->for_each([&](Reactor& r) { r.broadcast(header, data); });
|
|
498
|
+
return peers_.all_writable();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ── PeerNetwork (subsystems) ─────────────────────────────────────────────────
|
|
502
|
+
|
|
503
|
+
bool Node::send(const PeerId& to, MessageType type, ByteView payload) {
|
|
504
|
+
auto dest = peers_.destination(to);
|
|
505
|
+
if (!dest) return false;
|
|
506
|
+
const size_t in_transit =
|
|
507
|
+
dest->owed->fetch_add(payload.size(), std::memory_order_relaxed) + payload.size();
|
|
508
|
+
route_send(dest->route, FrameHeader{type, 0, 0}, payload.to_bytes(), dest->owed);
|
|
509
|
+
return dest->writable && in_transit <= send_low_water();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
bool Node::broadcast(MessageType type, ByteView payload) {
|
|
513
|
+
auto data = std::make_shared<const Bytes>(payload.to_bytes());
|
|
514
|
+
FrameHeader header{type, 0, 0};
|
|
515
|
+
reactors_->for_each([&](Reactor& r) { r.broadcast(header, data); });
|
|
516
|
+
return peers_.all_writable();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
std::vector<PeerId> Node::connected_peers() const {
|
|
520
|
+
std::vector<PeerId> ids;
|
|
521
|
+
for (const auto& info : peers_.snapshot()) ids.push_back(info.id);
|
|
522
|
+
return ids;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ── Routed send/close (used by Node and Peer) ───────────────────────────────
|
|
526
|
+
|
|
527
|
+
void Node::route_send(PeerRoute route, FrameHeader header, Bytes payload,
|
|
528
|
+
std::shared_ptr<std::atomic<size_t>> owed) {
|
|
529
|
+
Reactor& reactor = reactors_->by_index(route.reactor);
|
|
530
|
+
ConnId conn = route.conn;
|
|
531
|
+
auto data = std::make_shared<Bytes>(std::move(payload));
|
|
532
|
+
const size_t weight = data->size();
|
|
533
|
+
reactor.execute([&reactor, conn, header, data, owed, weight] {
|
|
534
|
+
// Discharged whether or not the connection is still there, and *before*
|
|
535
|
+
// the frame is queued rather than after: from here on these bytes are
|
|
536
|
+
// counted by the send queue instead, and the connection weighs both
|
|
537
|
+
// together (Connection::backlog). Discharging afterwards would leave a
|
|
538
|
+
// window where they were counted twice — and, worse, one where they were
|
|
539
|
+
// counted by neither, which is what an opening must never be reported on.
|
|
540
|
+
if (owed) owed->fetch_sub(weight, std::memory_order_relaxed);
|
|
541
|
+
if (auto* c = reactor.find(conn)) c->send(header, ByteView(*data));
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
size_t Node::send_low_water() const noexcept {
|
|
546
|
+
const size_t limit = config_.send_queue_limit != 0 ? config_.send_queue_limit
|
|
547
|
+
: Connection::kDefaultSendHighWater;
|
|
548
|
+
return limit / 4;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
void Node::route_close(PeerRoute route) {
|
|
552
|
+
reactors_->by_index(route.reactor).close(route.conn, CloseReason::LocalClose);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ── ConnectionDelegate (reactor thread) ─────────────────────────────────────
|
|
556
|
+
|
|
557
|
+
bool Node::admit_inbound() {
|
|
558
|
+
// Coarse gate at accept time: refuse new inbound when at capacity, before any
|
|
559
|
+
// handshake cost. The exact per-peer cap (which can tell a reconnect of a
|
|
560
|
+
// known peer from a brand-new one) is enforced in on_established below.
|
|
561
|
+
const size_t cap = max_peers_.load(std::memory_order_relaxed);
|
|
562
|
+
return cap == 0 || peers_.size() < cap;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
void Node::on_established(Connection& conn) {
|
|
566
|
+
if (config_.send_queue_limit != 0) conn.set_send_high_water(config_.send_queue_limit);
|
|
567
|
+
|
|
568
|
+
// Settle the dial race first: this attempt won its target, so any sibling
|
|
569
|
+
// attempt over the other transport is redundant and gets cancelled — unless it
|
|
570
|
+
// has established too, in which case it arrives here as an ordinary duplicate
|
|
571
|
+
// and the peer table decides between them (see dialer.h). Done before
|
|
572
|
+
// the checks below so a self-connection or a rejected duplicate still counts
|
|
573
|
+
// as "this dial resolved" rather than leaving the race running.
|
|
574
|
+
if (conn.role() == ConnRole::Outbound)
|
|
575
|
+
dialer_->on_established(PeerRoute{conn.reactor_index(), conn.id()});
|
|
576
|
+
|
|
577
|
+
// Reject self-connections: a self-certifying handshake against our own
|
|
578
|
+
// listener yields our own id. (Common once DHT/discovery starts dialing.)
|
|
579
|
+
if (conn.remote_id() == identity_.id) {
|
|
580
|
+
reactors_->by_index(conn.reactor_index()).close(conn.id(), CloseReason::LocalClose);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Peer-limit backstop. Inbound handshakes that raced past admit_inbound()
|
|
585
|
+
// before the cap was hit are rejected here, now that we know the remote id.
|
|
586
|
+
// A reconnect/duplicate of an already-known peer does not grow the count, so
|
|
587
|
+
// it is allowed through (the peer-table tie-break supersedes the old link).
|
|
588
|
+
// Our own outbound dials are intentional and never rejected.
|
|
589
|
+
const size_t cap = max_peers_.load(std::memory_order_relaxed);
|
|
590
|
+
if (cap != 0 && conn.role() == ConnRole::Inbound &&
|
|
591
|
+
peers_.size() >= cap && !peers_.contains(conn.remote_id())) {
|
|
592
|
+
LOG_DEBUG("node", "Rejecting inbound peer " << conn.remote_id().short_hex()
|
|
593
|
+
<< "; peer limit (" << cap << ") reached");
|
|
594
|
+
reactors_->by_index(conn.reactor_index()).close(conn.id(), CloseReason::PeerLimit);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
PeerInfo info;
|
|
599
|
+
info.id = conn.remote_id();
|
|
600
|
+
info.direction = conn.role();
|
|
601
|
+
info.transport = conn.transport();
|
|
602
|
+
// Outbound: remember the address we dialed — but only if it was a numeric IP.
|
|
603
|
+
// A dial-by-hostname target isn't a dialable Address (peers can't use our name),
|
|
604
|
+
// and the resolved IP is re-learned via identify anyway.
|
|
605
|
+
if (conn.has_dial_address())
|
|
606
|
+
if (auto ip = IpAddress::parse(conn.dial_host()))
|
|
607
|
+
info.addresses.push_back(Address{*ip, conn.dial_port()});
|
|
608
|
+
|
|
609
|
+
const PeerRoute route{conn.reactor_index(), conn.id()};
|
|
610
|
+
// Symmetric tie-break for a simultaneous cross-connect: both peers keep the
|
|
611
|
+
// link initiated by the smaller id, so they converge on the same connection.
|
|
612
|
+
const bool prefer_outbound = identity_.id < conn.remote_id();
|
|
613
|
+
const auto outcome = peers_.add(info, route, prefer_outbound);
|
|
614
|
+
|
|
615
|
+
// Tear down the loser of a duplicate/cross-connect race so it can't linger
|
|
616
|
+
// holding an fd. Its on_closed is a no-op: the peer table no longer maps its
|
|
617
|
+
// route, so it neither evicts the survivor nor fires a spurious disconnect.
|
|
618
|
+
if (outcome.close)
|
|
619
|
+
reactors_->by_index(outcome.close->reactor).close(outcome.close->conn, CloseReason::DuplicateConn);
|
|
620
|
+
|
|
621
|
+
// Hand the peer's in-transit counter to the connection that now carries its
|
|
622
|
+
// traffic, so the send queue's marks weigh what callers have offered as well
|
|
623
|
+
// as what is already queued — the two halves of one backlog, and the reason a
|
|
624
|
+
// caller told "no room" is always the one told when the room comes back. The
|
|
625
|
+
// route check keeps the loser of a race off the survivor's counter.
|
|
626
|
+
if (const auto dest = peers_.destination(conn.remote_id()); dest && dest->route == route)
|
|
627
|
+
conn.set_in_transit(dest->owed);
|
|
628
|
+
|
|
629
|
+
// Fire "connected" only on the 0→1 transition. A reconnect/duplicate that
|
|
630
|
+
// merely swapped the live route keeps the peer connected from the app's view.
|
|
631
|
+
if (outcome.result == PeerTable::AddResult::NewPeer) {
|
|
632
|
+
LOG_INFO("node", "Peer " << conn.remote_id().short_hex() << " connected ("
|
|
633
|
+
<< (conn.role() == ConnRole::Inbound ? "inbound" : "outbound")
|
|
634
|
+
<< " over " << to_string(conn.transport()) << ")");
|
|
635
|
+
Peer handle = make_peer(conn.remote_id(), route);
|
|
636
|
+
for (auto& cb : peer_connected_) cb(handle);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Tell the peer how to reach us, and what address we see it at. Skipped for a
|
|
640
|
+
// rejected duplicate (its connection was just closed above); sent on the
|
|
641
|
+
// surviving link otherwise — including the winner of a cross-connect race.
|
|
642
|
+
if (outcome.result != PeerTable::AddResult::Rejected)
|
|
643
|
+
send_identify(conn);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
void Node::on_frame(Connection& conn, const Frame& frame) {
|
|
647
|
+
// Control is the node's own plane (identify); it never reaches the app router.
|
|
648
|
+
if (frame.header.type == MessageType::Control) {
|
|
649
|
+
handle_identify(conn, frame);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
Peer peer = make_peer(conn.remote_id(), PeerRoute{conn.reactor_index(), conn.id()});
|
|
653
|
+
if (!router_.dispatch(peer, frame)) {
|
|
654
|
+
LOG_DEBUG("node", "No handler for frame from " << conn.remote_id().short_hex()
|
|
655
|
+
<< " (type " << static_cast<int>(frame.header.type)
|
|
656
|
+
<< ", channel " << frame.header.channel << ")");
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
void Node::on_closed(Connection& conn, CloseReason reason) {
|
|
661
|
+
// Tell the dial race this attempt is out. If it was the last one and none
|
|
662
|
+
// succeeded, the dialer reports the target as failed — a redial policy
|
|
663
|
+
// (ReconnectionService) needs to know its in-flight dial resolved, or it would
|
|
664
|
+
// wait out a timeout before retrying. The dialer coalesces that into exactly
|
|
665
|
+
// one report per target, however many transports were raced.
|
|
666
|
+
if (conn.role() == ConnRole::Outbound)
|
|
667
|
+
dialer_->on_closed(PeerRoute{conn.reactor_index(), conn.id()});
|
|
668
|
+
|
|
669
|
+
// Only peers that actually established were registered.
|
|
670
|
+
if (conn.remote_id().is_zero()) return;
|
|
671
|
+
|
|
672
|
+
const PeerId id = conn.remote_id();
|
|
673
|
+
const PeerRoute route{conn.reactor_index(), conn.id()};
|
|
674
|
+
|
|
675
|
+
// Disconnect fires only for the connection currently registered for this peer.
|
|
676
|
+
// A loser of a duplicate race (or a rejected self-connection) is not mapped
|
|
677
|
+
// under its route, so removing it is a no-op and must NOT surface a disconnect
|
|
678
|
+
// for a peer that is still connected over the surviving link.
|
|
679
|
+
if (!peers_.remove(id, route)) return;
|
|
680
|
+
// What this peer told us about our external endpoint went with it: the mapping
|
|
681
|
+
// it observed may well have been made for its link alone, and keeping it would
|
|
682
|
+
// let a long-gone peer keep voting on how our NAT behaves.
|
|
683
|
+
nat_status_.forget(id);
|
|
684
|
+
LOG_INFO("node", "Peer " << id.short_hex() << " disconnected (" << to_string(reason) << ")");
|
|
685
|
+
for (auto& cb : peer_disconnected_) cb(id);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
void Node::on_writable_changed(Connection& conn, bool writable) {
|
|
689
|
+
// Only an established peer has a table entry to record this against; a
|
|
690
|
+
// connection still handshaking cannot be sent to anyway.
|
|
691
|
+
if (conn.remote_id().is_zero()) return;
|
|
692
|
+
const PeerRoute route{conn.reactor_index(), conn.id()};
|
|
693
|
+
peers_.set_writable(conn.remote_id(), route, writable);
|
|
694
|
+
|
|
695
|
+
// Only the opening is worth waking anyone for. "You have filled up" is
|
|
696
|
+
// already answered by send() returning false at the moment it happens, so an
|
|
697
|
+
// event for it would tell the caller what it has just been told.
|
|
698
|
+
if (!writable) return;
|
|
699
|
+
Peer handle = make_peer(conn.remote_id(), route);
|
|
700
|
+
for (auto& cb : peer_writable_) cb(handle);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
void Node::on_dial_aborted(uint8_t reactor_index, ConnId id,
|
|
704
|
+
const std::string& /*host*/, uint16_t /*port*/) {
|
|
705
|
+
// The attempt died before there was ever a Connection (unresolvable host, no
|
|
706
|
+
// socket). It resolves exactly like a connection that closed without
|
|
707
|
+
// establishing, so the dialer can move on to the next transport at once.
|
|
708
|
+
dialer_->on_closed(PeerRoute{reactor_index, id});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// ── Identify: dialable-address discovery (reactor thread) ────────────────────
|
|
712
|
+
//
|
|
713
|
+
// A TCP socket only exposes a peer's ephemeral source port, so an inbound peer's
|
|
714
|
+
// dialable address is unknowable from the connection alone. Right after the
|
|
715
|
+
// handshake each side sends a Control/identify frame; the receiver pairs the
|
|
716
|
+
// peer's advertised listen port with the IP it sees to recover the dialable
|
|
717
|
+
// address, and learns its own public address from the peer's observation.
|
|
718
|
+
|
|
719
|
+
void Node::send_identify(Connection& conn) {
|
|
720
|
+
IdentifyMessage msg;
|
|
721
|
+
msg.listen_port = listen_port_;
|
|
722
|
+
msg.addresses = advertised_addresses();
|
|
723
|
+
// Both transports share listen_port_, so which of them a peer may dial there
|
|
724
|
+
// is a property of the node, not of the address — say it once, here.
|
|
725
|
+
msg.transports = transports_;
|
|
726
|
+
const IpAddress seen_ip = conn.remote_ip();
|
|
727
|
+
if (!seen_ip.is_any()) {
|
|
728
|
+
// The port we saw the peer at is worth reporting on exactly one of the two
|
|
729
|
+
// wires. On TCP it is an ephemeral port its OS picked for this one outbound
|
|
730
|
+
// socket and tells the peer nothing it can use. On a datagram link every
|
|
731
|
+
// peer shares ONE socket, so that port is what the peer's NAT mapped that
|
|
732
|
+
// socket to — the port a third party has to aim at to reach it, and the
|
|
733
|
+
// only thing a hole punch can be pointed at. So: report it there, and only
|
|
734
|
+
// there. A zero port keeps the old meaning ("IP only"), which is what an
|
|
735
|
+
// older peer already expects and what TCP still sends.
|
|
736
|
+
uint16_t seen_port = 0;
|
|
737
|
+
if (conn.transport() == TransportKind::Udp)
|
|
738
|
+
if (const auto endpoint = conn.remote_endpoint()) seen_port = endpoint->port;
|
|
739
|
+
msg.observed = Address{seen_ip, seen_port};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const Bytes payload = msg.encode();
|
|
743
|
+
conn.send(FrameHeader{MessageType::Control, 0, 0}, ByteView(payload));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
void Node::handle_identify(Connection& conn, const Frame& frame) {
|
|
747
|
+
const auto msg = IdentifyMessage::decode(frame.payload);
|
|
748
|
+
if (!msg) {
|
|
749
|
+
LOG_DEBUG("node", "Ignoring malformed identify from " << conn.remote_id().short_hex());
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// The peer's dialable addresses: the address we see it at paired with its
|
|
754
|
+
// advertised listen port (the linchpin for inbound peers), plus any extra
|
|
755
|
+
// addresses it self-advertised. PeerTable de-duplicates and caps the set.
|
|
756
|
+
const PeerRoute identify_route{conn.reactor_index(), conn.id()};
|
|
757
|
+
const IpAddress seen_ip = conn.remote_ip();
|
|
758
|
+
if (msg->transports != 0) {
|
|
759
|
+
peers_.set_supported_transports(conn.remote_id(), identify_route, msg->transports);
|
|
760
|
+
// ...and tell the dialer, which is the half that actually saves anything:
|
|
761
|
+
// without it every later dial to this address pays the fallback delay again
|
|
762
|
+
// to rediscover what we are being told right here. Only addresses this node
|
|
763
|
+
// can vouch for are recorded — the one we dialed ourselves, and the IP we
|
|
764
|
+
// observe the peer at paired with the listen port it claims. The addresses
|
|
765
|
+
// in msg->addresses are the peer's unverified word about where *anyone*
|
|
766
|
+
// lives, so letting them carry a transports mask would let one peer set the
|
|
767
|
+
// dial order for somebody else's address.
|
|
768
|
+
if (conn.has_dial_address())
|
|
769
|
+
dialer_->remember(conn.dial_host(), conn.dial_port(), msg->transports);
|
|
770
|
+
if (msg->listen_port != 0 && !seen_ip.is_any())
|
|
771
|
+
dialer_->remember(seen_ip.to_string(), msg->listen_port, msg->transports);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
std::vector<Address> candidates;
|
|
775
|
+
if (msg->listen_port != 0 && !seen_ip.is_any())
|
|
776
|
+
candidates.push_back(Address{seen_ip, msg->listen_port});
|
|
777
|
+
for (const Address& a : msg->addresses)
|
|
778
|
+
if (a.port != 0 && !a.ip.is_any())
|
|
779
|
+
candidates.push_back(a);
|
|
780
|
+
|
|
781
|
+
if (!candidates.empty()) {
|
|
782
|
+
const auto added = peers_.add_addresses(conn.remote_id(), identify_route, candidates);
|
|
783
|
+
if (!added.empty())
|
|
784
|
+
LOG_DEBUG("node", "Learned " << added.size() << " address(es) for peer "
|
|
785
|
+
<< conn.remote_id().short_hex() << " (e.g. " << added.front().to_string() << ")");
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Learn our own public address: pair the IP the peer saw us at with OUR listen
|
|
789
|
+
// port, since on TCP its observed port is our ephemeral source port and is not
|
|
790
|
+
// dialable.
|
|
791
|
+
if (msg->observed && !msg->observed->ip.is_any()) {
|
|
792
|
+
if (listen_port_ != 0)
|
|
793
|
+
record_observed_address(Address{msg->observed->ip, listen_port_});
|
|
794
|
+
|
|
795
|
+
// A datagram peer reports the port as well, and there it is not ephemeral:
|
|
796
|
+
// it is what a NAT mapped our one shared socket to. That endpoint — not the
|
|
797
|
+
// listen port, which a NAT is under no obligation to preserve — is what a
|
|
798
|
+
// third peer would have to aim at, so it is kept apart from the address set
|
|
799
|
+
// above and classified across peers (see node/nat_status.h).
|
|
800
|
+
if (conn.transport() == TransportKind::Udp && msg->observed->port != 0)
|
|
801
|
+
nat_status_.record_udp_observation(conn.remote_id(), *msg->observed,
|
|
802
|
+
is_own_endpoint(*msg->observed));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
bool Node::is_own_endpoint(const Address& addr) const {
|
|
807
|
+
std::lock_guard<std::mutex> lock(advertised_mutex_);
|
|
808
|
+
// The advertised set is every local interface IP paired with our listen port —
|
|
809
|
+
// so a match means the peer reached us without anything rewriting the endpoint
|
|
810
|
+
// on the way, i.e. there is no NAT between us.
|
|
811
|
+
return std::find(advertised_addresses_.begin(), advertised_addresses_.end(), addr) !=
|
|
812
|
+
advertised_addresses_.end();
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
std::vector<Address> Node::advertised_addresses() const {
|
|
816
|
+
std::lock_guard<std::mutex> lock(advertised_mutex_);
|
|
817
|
+
return advertised_addresses_; // hot path: just hand back the stored snapshot
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
void Node::rebuild_advertised_addresses(const std::vector<std::string>& local_ips) {
|
|
821
|
+
// Pair each local interface IP with our listen port. A non-listening node has
|
|
822
|
+
// nothing dialable to advertise. Called off the hot path: once at start() and
|
|
823
|
+
// again whenever the NetworkMonitor reports the interface set changed.
|
|
824
|
+
std::vector<Address> fresh;
|
|
825
|
+
if (listen_port_ != 0) {
|
|
826
|
+
for (const std::string& ip : local_ips) {
|
|
827
|
+
if (is_unspecified_ip(ip)) continue;
|
|
828
|
+
fresh.push_back(Address{ip, listen_port_});
|
|
829
|
+
if (fresh.size() >= IdentifyMessage::kMaxAddresses) break;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
std::lock_guard<std::mutex> lock(advertised_mutex_);
|
|
833
|
+
advertised_addresses_ = std::move(fresh);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
void Node::record_observed_address(const Address& addr) {
|
|
837
|
+
std::lock_guard<std::mutex> lock(observed_mutex_);
|
|
838
|
+
if (std::find(observed_addresses_.begin(), observed_addresses_.end(), addr) != observed_addresses_.end())
|
|
839
|
+
return;
|
|
840
|
+
if (observed_addresses_.size() >= 16) return; // bound (NAT remap churn / hostile peers)
|
|
841
|
+
observed_addresses_.push_back(addr);
|
|
842
|
+
LOG_DEBUG("node", "Observed own address " << addr.to_string() << " (reported by a peer)");
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
std::vector<Address> Node::observed_addresses() const {
|
|
846
|
+
std::lock_guard<std::mutex> lock(observed_mutex_);
|
|
847
|
+
return observed_addresses_;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ── Peer handle methods (defined here for the full Node type) ────────────────
|
|
851
|
+
|
|
852
|
+
void Peer::send(std::string_view channel, ByteView payload) const {
|
|
853
|
+
node_->route_send(route_, FrameHeader{MessageType::App, 0, MessageRouter::channel_id(channel)},
|
|
854
|
+
payload.to_bytes());
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
void Peer::disconnect() const {
|
|
858
|
+
node_->route_close(route_);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
std::optional<PeerInfo> Peer::info() const {
|
|
862
|
+
return node_->peers_.info(id_);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
} // namespace librats
|