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,605 @@
|
|
|
1
|
+
#include "librats/subsystems/hole_punch.h"
|
|
2
|
+
#include "librats/node/dial_service.h"
|
|
3
|
+
#include "librats/node/node_context.h"
|
|
4
|
+
#include "librats/peer/peer_info.h"
|
|
5
|
+
#include "librats/util/logger.h"
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <optional>
|
|
9
|
+
|
|
10
|
+
namespace librats {
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
constexpr uint8_t kVersion = 1;
|
|
15
|
+
|
|
16
|
+
// Envelope ops — what a message is *for the relay*.
|
|
17
|
+
constexpr uint8_t kOpRelay = 0; ///< sender → relay: pass this on to dst
|
|
18
|
+
constexpr uint8_t kOpRelayed = 1; ///< relay → destination: this came from src
|
|
19
|
+
|
|
20
|
+
// Inner kinds — what the two punching peers say to each other.
|
|
21
|
+
constexpr uint8_t kKindConnect = 0;
|
|
22
|
+
constexpr uint8_t kKindSync = 1;
|
|
23
|
+
|
|
24
|
+
/// Ceiling on addresses a Connect may carry, independent of the local config: a
|
|
25
|
+
/// peer must not be able to make us walk a longer list than we would ever send.
|
|
26
|
+
constexpr size_t kMaxAddressesOnWire = 8;
|
|
27
|
+
|
|
28
|
+
/// Largest envelope worth parsing. Version + op + id + kind + count + the address
|
|
29
|
+
/// list at its widest (IPv6). Anything past this is malformed by construction.
|
|
30
|
+
constexpr size_t kMaxMessageSize = 2 + PeerId::kSize + 3 + kMaxAddressesOnWire * 19;
|
|
31
|
+
|
|
32
|
+
/// The `role` byte of a Connect: is this opening a rendezvous, or answering one?
|
|
33
|
+
constexpr uint8_t kRoleOpening = 0;
|
|
34
|
+
constexpr uint8_t kRoleAnswer = 1;
|
|
35
|
+
|
|
36
|
+
void put_u16(Bytes& out, uint16_t v) {
|
|
37
|
+
out.push_back(static_cast<uint8_t>(v >> 8));
|
|
38
|
+
out.push_back(static_cast<uint8_t>(v & 0xFF));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
uint16_t get_u16(const uint8_t* p) {
|
|
42
|
+
return static_cast<uint16_t>((static_cast<uint16_t>(p[0]) << 8) | p[1]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void put_address(Bytes& out, const Address& a) {
|
|
46
|
+
const ByteView ip = a.ip.bytes(); // 4 (v4) or 16 (v6) raw bytes
|
|
47
|
+
out.push_back(static_cast<uint8_t>(ip.size()));
|
|
48
|
+
out.insert(out.end(), ip.begin(), ip.end());
|
|
49
|
+
put_u16(out, a.port);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Bounds-checked forward reader: any short read aborts the whole message, which
|
|
53
|
+
/// is the only way a hostile payload is allowed to affect us.
|
|
54
|
+
struct Reader {
|
|
55
|
+
const uint8_t* p;
|
|
56
|
+
size_t n;
|
|
57
|
+
|
|
58
|
+
const uint8_t* take(size_t k) {
|
|
59
|
+
if (n < k) return nullptr;
|
|
60
|
+
const uint8_t* at = p;
|
|
61
|
+
p += k; n -= k;
|
|
62
|
+
return at;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
Bytes encode_connect(bool opening, const std::vector<Address>& addresses) {
|
|
67
|
+
Bytes out;
|
|
68
|
+
out.push_back(kKindConnect);
|
|
69
|
+
out.push_back(opening ? kRoleOpening : kRoleAnswer);
|
|
70
|
+
const size_t count = (std::min)(addresses.size(), kMaxAddressesOnWire);
|
|
71
|
+
out.push_back(static_cast<uint8_t>(count));
|
|
72
|
+
for (size_t i = 0; i < count; ++i) put_address(out, addresses[i]);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
struct ConnectBody {
|
|
77
|
+
bool opening = false;
|
|
78
|
+
std::vector<Address> addresses;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/// Parse a Connect body (everything after the kind byte). nullopt on anything
|
|
82
|
+
/// malformed — a partially-read list is never acted on.
|
|
83
|
+
std::optional<ConnectBody> decode_connect(ByteView body) {
|
|
84
|
+
Reader r{body.data(), body.size()};
|
|
85
|
+
const uint8_t* role_p = r.take(1);
|
|
86
|
+
if (!role_p) return std::nullopt;
|
|
87
|
+
if (*role_p != kRoleOpening && *role_p != kRoleAnswer) return std::nullopt;
|
|
88
|
+
|
|
89
|
+
const uint8_t* count_p = r.take(1);
|
|
90
|
+
if (!count_p) return std::nullopt;
|
|
91
|
+
const uint8_t count = *count_p;
|
|
92
|
+
if (count > kMaxAddressesOnWire) return std::nullopt;
|
|
93
|
+
|
|
94
|
+
std::vector<Address> out;
|
|
95
|
+
out.reserve(count);
|
|
96
|
+
for (uint8_t i = 0; i < count; ++i) {
|
|
97
|
+
const uint8_t* len_p = r.take(1);
|
|
98
|
+
if (!len_p) return std::nullopt;
|
|
99
|
+
const uint8_t ip_len = *len_p;
|
|
100
|
+
if (ip_len != 4 && ip_len != 16) return std::nullopt;
|
|
101
|
+
const uint8_t* ip_p = r.take(ip_len);
|
|
102
|
+
if (!ip_p) return std::nullopt;
|
|
103
|
+
const uint8_t* port_p = r.take(2);
|
|
104
|
+
if (!port_p) return std::nullopt;
|
|
105
|
+
|
|
106
|
+
const auto ip = IpAddress::from_bytes(ByteView(ip_p, ip_len));
|
|
107
|
+
if (!ip) return std::nullopt;
|
|
108
|
+
Address addr{*ip, get_u16(port_p)};
|
|
109
|
+
if (!addr.is_valid() || addr.ip.is_any()) continue; // not dialable; skip, don't fail
|
|
110
|
+
out.push_back(addr);
|
|
111
|
+
}
|
|
112
|
+
return ConnectBody{*role_p == kRoleOpening, std::move(out)};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Bytes envelope(uint8_t op, const PeerId& id, const Bytes& inner) {
|
|
116
|
+
Bytes out;
|
|
117
|
+
out.reserve(2 + PeerId::kSize + inner.size());
|
|
118
|
+
out.push_back(kVersion);
|
|
119
|
+
out.push_back(op);
|
|
120
|
+
const auto& bytes = id.bytes();
|
|
121
|
+
out.insert(out.end(), bytes.begin(), bytes.end());
|
|
122
|
+
out.insert(out.end(), inner.begin(), inner.end());
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
} // namespace
|
|
127
|
+
|
|
128
|
+
HolePunch::HolePunch() : HolePunch(Config()) {}
|
|
129
|
+
HolePunch::HolePunch(Config config) : config_(std::move(config)) {}
|
|
130
|
+
HolePunch::~HolePunch() { stop(); }
|
|
131
|
+
|
|
132
|
+
void HolePunch::attach(NodeContext& ctx) {
|
|
133
|
+
network_ = &ctx.network;
|
|
134
|
+
// Both are optional by construction: resolve() answers nullptr when nothing
|
|
135
|
+
// provides the interface, and a node that cannot dial a specific endpoint or
|
|
136
|
+
// does not know its own external one simply cannot punch — it can still relay.
|
|
137
|
+
dialer_ = ctx.services.get<DialService>();
|
|
138
|
+
external_ = ctx.services.get<ExternalAddressService>();
|
|
139
|
+
services_ = &ctx.services;
|
|
140
|
+
|
|
141
|
+
// Offer punching to sibling modules. Registered even without a dialer: punch()
|
|
142
|
+
// answers false on its own then, which is exactly what a caller expects from a
|
|
143
|
+
// node that can only relay.
|
|
144
|
+
ctx.services.provide<HolePunchService>(this);
|
|
145
|
+
|
|
146
|
+
network_->on(MessageType::Punch,
|
|
147
|
+
[this](const Peer& peer, ByteView payload) { handle(peer, payload); });
|
|
148
|
+
|
|
149
|
+
// A peer arriving retires its session at once, rather than at the next tick.
|
|
150
|
+
// Watching for the peer — not for our dial reporting success — is the reliable
|
|
151
|
+
// signal: a punch lands in both directions and the one that survives is often
|
|
152
|
+
// the INBOUND link, which no dial of ours would ever report. The worker still
|
|
153
|
+
// reconciles sessions against the peer set, so this is the fast path, not the
|
|
154
|
+
// only one.
|
|
155
|
+
network_->on_peer_connected([this](const Peer& peer) {
|
|
156
|
+
// A relayed link is not what a punch was for — it is the fallback the punch
|
|
157
|
+
// is trying to make unnecessary, and a punch may well have been started
|
|
158
|
+
// BECAUSE it came up (see subsystems/relay.h). Retiring the session here
|
|
159
|
+
// would call the upgrade off the moment it began.
|
|
160
|
+
const auto info = peer.info();
|
|
161
|
+
if (info && info->transport == TransportKind::Relay) return;
|
|
162
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
163
|
+
sessions_.erase(peer.id());
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void HolePunch::start() {
|
|
168
|
+
// Resolved here rather than in attach(): Relay may be attached after us, and
|
|
169
|
+
// every attach() runs before any start(). Stored before running_ goes true, so
|
|
170
|
+
// an escalation on a reactor thread never sees a half-initialised state.
|
|
171
|
+
if (config_.relay_on_failure && services_)
|
|
172
|
+
relay_.store(services_->get<RelayService>());
|
|
173
|
+
|
|
174
|
+
if (running_.exchange(true)) return;
|
|
175
|
+
worker_ = std::thread([this] { loop(); });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
void HolePunch::stop() {
|
|
179
|
+
relay_.store(nullptr);
|
|
180
|
+
if (!running_.exchange(false)) return;
|
|
181
|
+
cv_.notify_all();
|
|
182
|
+
if (worker_.joinable()) worker_.join();
|
|
183
|
+
|
|
184
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
185
|
+
sessions_.clear();
|
|
186
|
+
cooldown_.clear();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── Starting a punch ────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
bool HolePunch::punch(const PeerId& target) {
|
|
192
|
+
if (!running_.load() || !network_ || !dialer_) return false;
|
|
193
|
+
if (target == network_->local_id()) return false;
|
|
194
|
+
|
|
195
|
+
// Already reachable the ordinary way. Checked before any state is created so a
|
|
196
|
+
// caller can punch() freely on discovery without having to check first. A
|
|
197
|
+
// RELAYED peer is deliberately not "already reachable": punching it is how the
|
|
198
|
+
// circuit gets replaced by a direct link (see subsystems/relay.h).
|
|
199
|
+
for (const PeerId& id : directly_connected())
|
|
200
|
+
if (id == target) return false;
|
|
201
|
+
|
|
202
|
+
if (config_.skip_when_endpoint_dependent && external_ &&
|
|
203
|
+
external_->udp_mapping() == NatMapping::EndpointDependent) {
|
|
204
|
+
LOG_DEBUG("punch", "Not punching to " << target.short_hex()
|
|
205
|
+
<< ": our own mapping is per-destination (symmetric NAT)");
|
|
206
|
+
// Nothing about this will improve with time or retries: no endpoint we can
|
|
207
|
+
// advertise is the one the target's packets would arrive on. This is
|
|
208
|
+
// exactly the case a relayed path exists for.
|
|
209
|
+
escalate_to_relay(target);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const auto addresses = own_punch_addresses();
|
|
214
|
+
if (addresses.empty()) {
|
|
215
|
+
LOG_DEBUG("punch", "Not punching to " << target.short_hex()
|
|
216
|
+
<< ": no external datagram endpoint to advertise yet");
|
|
217
|
+
escalate_to_relay(target);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
{
|
|
222
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
223
|
+
if (sessions_.count(target)) return false; // already running
|
|
224
|
+
if (in_cooldown(target)) return false; // recently gave up
|
|
225
|
+
if (sessions_.size() >= config_.max_sessions) return false;
|
|
226
|
+
|
|
227
|
+
Session session;
|
|
228
|
+
session.initiator = true;
|
|
229
|
+
session.phase = Phase::AwaitingPeerConnect;
|
|
230
|
+
session.deadline = std::chrono::steady_clock::now() + config_.round_timeout;
|
|
231
|
+
sessions_.emplace(target, std::move(session));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!send_connect(target, /*opening=*/true, nullptr)) {
|
|
235
|
+
{
|
|
236
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
237
|
+
sessions_.erase(target); // nobody could carry it; nothing is in flight
|
|
238
|
+
}
|
|
239
|
+
escalate_to_relay(target);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
LOG_DEBUG("punch", "Punch rendezvous started with " << target.short_hex());
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
bool HolePunch::send_connect(const PeerId& target, bool opening, const PeerId* via) {
|
|
247
|
+
const auto addresses = own_punch_addresses();
|
|
248
|
+
if (addresses.empty()) return false;
|
|
249
|
+
return relay_to(target, encode_connect(opening, addresses), via) > 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void HolePunch::send_sync(const PeerId& target, const PeerId* via) {
|
|
253
|
+
Bytes inner;
|
|
254
|
+
inner.push_back(kKindSync);
|
|
255
|
+
relay_to(target, inner, via);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
size_t HolePunch::relay_to(const PeerId& dst, const Bytes& inner, const PeerId* via) {
|
|
259
|
+
const Bytes message = envelope(kOpRelay, dst, inner);
|
|
260
|
+
|
|
261
|
+
// A hop that has already carried something from the target is known to reach it,
|
|
262
|
+
// so the rest of the exchange goes back that way rather than out to everybody
|
|
263
|
+
// again. Only the very first message pays the fan-out.
|
|
264
|
+
if (via) {
|
|
265
|
+
network_->send(*via, MessageType::Punch, ByteView(message));
|
|
266
|
+
return 1;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Nothing known yet: we do not know which of our peers also has the target, so
|
|
270
|
+
// ask a few and let the ones that cannot help drop it. Bounded on purpose —
|
|
271
|
+
// every extra relay is a duplicate rendezvous the far end has to recognise.
|
|
272
|
+
size_t sent = 0;
|
|
273
|
+
for (const PeerId& relay : network_->connected_peers()) {
|
|
274
|
+
if (sent >= config_.max_relays) break;
|
|
275
|
+
if (relay == dst) continue; // if it were connected we would not be punching
|
|
276
|
+
// send() answers backpressure, not delivery, and every id here is a peer we
|
|
277
|
+
// hold — so the message is on its way either way.
|
|
278
|
+
network_->send(relay, MessageType::Punch, ByteView(message));
|
|
279
|
+
++sent;
|
|
280
|
+
}
|
|
281
|
+
return sent;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
std::vector<Address> HolePunch::own_punch_addresses() const {
|
|
285
|
+
if (!external_) return {};
|
|
286
|
+
auto addresses = external_->external_udp_endpoints();
|
|
287
|
+
if (addresses.size() > config_.max_addresses) addresses.resize(config_.max_addresses);
|
|
288
|
+
return addresses;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ── Inbound ─────────────────────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
void HolePunch::handle(const Peer& from, ByteView payload) {
|
|
294
|
+
if (!running_.load()) return;
|
|
295
|
+
if (payload.size() < 2 + PeerId::kSize || payload.size() > kMaxMessageSize) return;
|
|
296
|
+
if (payload.data()[0] != kVersion) return;
|
|
297
|
+
|
|
298
|
+
const uint8_t op = payload.data()[1];
|
|
299
|
+
const auto id = PeerId::from_bytes(ByteView(payload.data() + 2, PeerId::kSize));
|
|
300
|
+
if (!id) return;
|
|
301
|
+
const ByteView inner(payload.data() + 2 + PeerId::kSize,
|
|
302
|
+
payload.size() - 2 - PeerId::kSize);
|
|
303
|
+
if (inner.empty()) return;
|
|
304
|
+
|
|
305
|
+
if (op == kOpRelay) {
|
|
306
|
+
handle_relay_request(from, *id, inner);
|
|
307
|
+
} else if (op == kOpRelayed) {
|
|
308
|
+
// The envelope names who this came from, but only the peer that actually
|
|
309
|
+
// delivered it vouches for anything — and it vouches only for having
|
|
310
|
+
// forwarded it. That is the whole trust model here; see the note in the
|
|
311
|
+
// header about what acting on a forged Connect can cost.
|
|
312
|
+
handle_relayed(from, *id, inner);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
void HolePunch::handle_relay_request(const Peer& from, const PeerId& dst, ByteView inner) {
|
|
317
|
+
if (!config_.enable_relay) return;
|
|
318
|
+
if (dst == network_->local_id() || dst == from.id()) return;
|
|
319
|
+
|
|
320
|
+
// Forward ONLY to a peer we already hold. This is what keeps a relay from being
|
|
321
|
+
// an open reflector: it never resolves an address, never dials, and can only
|
|
322
|
+
// ever deliver to somebody that chose to connect to it. Checked before the
|
|
323
|
+
// budget is spent, so the common case — a fan-out request for a peer we simply
|
|
324
|
+
// do not have — costs the sender nothing.
|
|
325
|
+
const std::vector<PeerId> peers = network_->connected_peers();
|
|
326
|
+
if (std::find(peers.begin(), peers.end(), dst) == peers.end()) return;
|
|
327
|
+
|
|
328
|
+
if (!relay_budget_ok(from.id())) {
|
|
329
|
+
LOG_DEBUG("punch", "Dropping relay from " << from.id().short_hex() << ": over budget");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const Bytes message = envelope(kOpRelayed, from.id(), Bytes(inner.begin(), inner.end()));
|
|
334
|
+
network_->send(dst, MessageType::Punch, ByteView(message));
|
|
335
|
+
relayed_.fetch_add(1, std::memory_order_relaxed);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
void HolePunch::handle_relayed(const Peer& via, const PeerId& src, ByteView inner) {
|
|
339
|
+
if (src == network_->local_id()) return; // a relay looping our own message back
|
|
340
|
+
|
|
341
|
+
const uint8_t kind = inner.data()[0];
|
|
342
|
+
const ByteView body(inner.data() + 1, inner.size() - 1);
|
|
343
|
+
|
|
344
|
+
if (kind == kKindConnect) {
|
|
345
|
+
auto connect = decode_connect(body);
|
|
346
|
+
if (!connect) return; // malformed → ignore, never fatal
|
|
347
|
+
handle_connect(via, src, connect->opening, std::move(connect->addresses));
|
|
348
|
+
} else if (kind == kKindSync) {
|
|
349
|
+
handle_sync(src);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
void HolePunch::handle_connect(const Peer& via, const PeerId& src, bool opening,
|
|
354
|
+
std::vector<Address> addresses) {
|
|
355
|
+
if (addresses.empty()) return;
|
|
356
|
+
if (addresses.size() > config_.max_addresses) addresses.resize(config_.max_addresses);
|
|
357
|
+
|
|
358
|
+
bool reply_with_connect = false;
|
|
359
|
+
bool punch_now = false;
|
|
360
|
+
std::vector<Address> targets;
|
|
361
|
+
const PeerId hop = via.id();
|
|
362
|
+
|
|
363
|
+
{
|
|
364
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
365
|
+
auto it = sessions_.find(src);
|
|
366
|
+
|
|
367
|
+
if (!opening) {
|
|
368
|
+
// The answer to a rendezvous WE opened. Anything else — an answer with
|
|
369
|
+
// no question, or one arriving after we stood down — is stale and is
|
|
370
|
+
// dropped rather than acted on.
|
|
371
|
+
if (it == sessions_.end() || !it->second.initiator) return;
|
|
372
|
+
if (it->second.phase != Phase::AwaitingPeerConnect) return;
|
|
373
|
+
} else if (it != sessions_.end() && it->second.initiator) {
|
|
374
|
+
// Both ends opened a rendezvous at once. Left alone each would wait for
|
|
375
|
+
// the other's Sync and neither would send one, so the same symmetric
|
|
376
|
+
// rule the peer table uses for a cross-connect settles it: the smaller
|
|
377
|
+
// id keeps the round, the larger stands down and answers. Both ends
|
|
378
|
+
// compute it identically, so exactly one of them stands down.
|
|
379
|
+
if (network_->local_id() < src) return; // ours wins; ignore theirs
|
|
380
|
+
it->second.initiator = false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (it == sessions_.end()) {
|
|
384
|
+
if (sessions_.size() >= config_.max_sessions) return;
|
|
385
|
+
if (in_cooldown(src)) return;
|
|
386
|
+
Session session;
|
|
387
|
+
session.initiator = false;
|
|
388
|
+
it = sessions_.emplace(src, std::move(session)).first;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
Session& session = it->second;
|
|
392
|
+
session.peer_addresses = std::move(addresses);
|
|
393
|
+
session.deadline = std::chrono::steady_clock::now() + config_.round_timeout;
|
|
394
|
+
session.via = hop; // this hop just proved it reaches the target
|
|
395
|
+
session.have_via = true;
|
|
396
|
+
|
|
397
|
+
if (session.initiator) {
|
|
398
|
+
// Step 3: the round trip through the relay is now known, so send Sync
|
|
399
|
+
// and start dialing immediately — the peer starts when Sync lands, and
|
|
400
|
+
// the two bursts meet in the middle.
|
|
401
|
+
session.phase = Phase::Punching;
|
|
402
|
+
punch_now = true;
|
|
403
|
+
targets = session.peer_addresses;
|
|
404
|
+
} else {
|
|
405
|
+
session.phase = Phase::AwaitingSync;
|
|
406
|
+
reply_with_connect = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Outside the lock: both reach the network, and dialing may run callbacks inline.
|
|
411
|
+
if (reply_with_connect) {
|
|
412
|
+
if (!send_connect(src, /*opening=*/false, &hop)) {
|
|
413
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
414
|
+
sessions_.erase(src);
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (punch_now) {
|
|
419
|
+
// Order matters: Sync goes first, then we dial. The peer starts dialing when
|
|
420
|
+
// Sync lands, so leaving here first is what buys the half round trip that
|
|
421
|
+
// makes the two bursts overlap.
|
|
422
|
+
send_sync(src, &hop);
|
|
423
|
+
fire_punch(src, targets);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
void HolePunch::handle_sync(const PeerId& src) {
|
|
428
|
+
std::vector<Address> targets;
|
|
429
|
+
{
|
|
430
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
431
|
+
auto it = sessions_.find(src);
|
|
432
|
+
if (it == sessions_.end() || it->second.phase != Phase::AwaitingSync) return;
|
|
433
|
+
if (it->second.peer_addresses.empty()) return;
|
|
434
|
+
it->second.phase = Phase::Punching;
|
|
435
|
+
it->second.deadline = std::chrono::steady_clock::now() + config_.round_timeout;
|
|
436
|
+
targets = it->second.peer_addresses;
|
|
437
|
+
}
|
|
438
|
+
fire_punch(src, targets);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
void HolePunch::fire_punch(const PeerId& target, const std::vector<Address>& addresses) {
|
|
442
|
+
if (!dialer_) return;
|
|
443
|
+
LOG_DEBUG("punch", "Punching to " << target.short_hex() << " at " << addresses.size()
|
|
444
|
+
<< " endpoint(s)");
|
|
445
|
+
for (const Address& addr : addresses)
|
|
446
|
+
dialer_->dial_direct(addr, TransportKind::Udp, config_.profile);
|
|
447
|
+
punches_started_.fetch_add(1, std::memory_order_relaxed);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ── Relay budget ────────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
bool HolePunch::relay_budget_ok(const PeerId& from) {
|
|
453
|
+
const auto now = std::chrono::steady_clock::now();
|
|
454
|
+
std::lock_guard<std::mutex> lock(relay_mutex_);
|
|
455
|
+
|
|
456
|
+
// Bounded by dropping the whole table when it grows past what a real mesh would
|
|
457
|
+
// hold. Cheaper than per-entry expiry on a path that runs per forwarded message,
|
|
458
|
+
// and the only cost of the reset is one over-generous window for everybody.
|
|
459
|
+
if (relay_budget_.size() > 4096) relay_budget_.clear();
|
|
460
|
+
|
|
461
|
+
RelayBudget& budget = relay_budget_[from];
|
|
462
|
+
if (now - budget.window_started >= config_.relay_window) {
|
|
463
|
+
budget.window_started = now;
|
|
464
|
+
budget.spent = 0;
|
|
465
|
+
}
|
|
466
|
+
if (budget.spent >= config_.relay_budget) return false;
|
|
467
|
+
++budget.spent;
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ── Worker ──────────────────────────────────────────────────────────────────
|
|
472
|
+
|
|
473
|
+
void HolePunch::loop() {
|
|
474
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
475
|
+
while (running_.load()) {
|
|
476
|
+
cv_.wait_for(lock, config_.tick, [this] { return !running_.load(); });
|
|
477
|
+
if (!running_.load()) return;
|
|
478
|
+
lock.unlock();
|
|
479
|
+
service_sessions();
|
|
480
|
+
lock.lock();
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
void HolePunch::service_sessions() {
|
|
485
|
+
const auto now = std::chrono::steady_clock::now();
|
|
486
|
+
|
|
487
|
+
// Connected peers first, without the lock: a session whose target is now a peer
|
|
488
|
+
// succeeded, however it got there (our burst, or theirs arriving as inbound).
|
|
489
|
+
// Direct links only — a relayed one is what a punch is trying to replace.
|
|
490
|
+
std::vector<PeerId> connected = directly_connected();
|
|
491
|
+
|
|
492
|
+
struct Retry { PeerId target; PeerId via; bool have_via; };
|
|
493
|
+
std::vector<Retry> retry;
|
|
494
|
+
std::vector<PeerId> exhausted; ///< targets to hand on to the relay, once unlocked
|
|
495
|
+
{
|
|
496
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
497
|
+
|
|
498
|
+
for (auto it = cooldown_.begin(); it != cooldown_.end();) {
|
|
499
|
+
if (now >= it->second) it = cooldown_.erase(it);
|
|
500
|
+
else ++it;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
for (auto it = sessions_.begin(); it != sessions_.end();) {
|
|
504
|
+
const PeerId& target = it->first;
|
|
505
|
+
if (std::find(connected.begin(), connected.end(), target) != connected.end()) {
|
|
506
|
+
LOG_DEBUG("punch", "Punch to " << target.short_hex() << " succeeded");
|
|
507
|
+
it = sessions_.erase(it);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (now < it->second.deadline) { ++it; continue; }
|
|
511
|
+
|
|
512
|
+
// The round is over and the peer is still not here. Only the initiator
|
|
513
|
+
// retries: a responder has no idea whether the other side is still
|
|
514
|
+
// trying, and two ends retrying independently would drift apart from the
|
|
515
|
+
// one thing that makes a punch work — starting together.
|
|
516
|
+
const bool initiator = it->second.initiator;
|
|
517
|
+
if (!initiator || ++it->second.attempt >= config_.attempts) {
|
|
518
|
+
LOG_DEBUG("punch", "Giving up on " << target.short_hex() << " for now");
|
|
519
|
+
// Cooldown is the INITIATOR's verdict on a target and nobody else's.
|
|
520
|
+
// A responder round ending means only that the Sync never came — the
|
|
521
|
+
// initiator is very likely mid-retry at that exact moment (its round
|
|
522
|
+
// started a relay hop earlier than ours, so its deadline expires
|
|
523
|
+
// first). Calling the target off here would drop the retries it is
|
|
524
|
+
// about to send, and block this node's own later punch to it, for
|
|
525
|
+
// the whole cooldown. So a responder just forgets the round.
|
|
526
|
+
if (initiator) {
|
|
527
|
+
begin_cooldown(target);
|
|
528
|
+
exhausted.push_back(target);
|
|
529
|
+
}
|
|
530
|
+
it = sessions_.erase(it);
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
it->second.phase = Phase::AwaitingPeerConnect;
|
|
534
|
+
it->second.peer_addresses.clear();
|
|
535
|
+
it->second.deadline = now + config_.round_timeout;
|
|
536
|
+
retry.push_back(Retry{target, it->second.via, it->second.have_via});
|
|
537
|
+
++it;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
for (const Retry& r : retry) {
|
|
542
|
+
if (send_connect(r.target, /*opening=*/true, r.have_via ? &r.via : nullptr)) continue;
|
|
543
|
+
{
|
|
544
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
545
|
+
sessions_.erase(r.target);
|
|
546
|
+
}
|
|
547
|
+
exhausted.push_back(r.target);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// The rungs above this one are spent: no address we can advertise got through.
|
|
551
|
+
// Handing the target on is the difference between a peer that is unreachable
|
|
552
|
+
// and one that is merely expensive to reach.
|
|
553
|
+
for (const PeerId& target : exhausted) escalate_to_relay(target);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
void HolePunch::escalate_to_relay(const PeerId& target) {
|
|
557
|
+
RelayService* relay = relay_.load();
|
|
558
|
+
if (!relay) return; // no Relay attached, or the fallback is off
|
|
559
|
+
if (!relay->connect_via_relay(target)) return;
|
|
560
|
+
|
|
561
|
+
LOG_DEBUG("punch", "Punching to " << target.short_hex()
|
|
562
|
+
<< " is not going to work; looking for a relay instead");
|
|
563
|
+
|
|
564
|
+
// The target has just changed hands, and the cooldown a give-up started must not
|
|
565
|
+
// outlive that. It exists to stop us hammering a peer we cannot reach — a job
|
|
566
|
+
// the relay now owns, with an attempt timeout and a cooldown of its own.
|
|
567
|
+
//
|
|
568
|
+
// And if that attempt lands, the circuit is precisely the new information that
|
|
569
|
+
// makes another punch worth trying: the two ends are peers at last, so the
|
|
570
|
+
// rendezvous can travel over the very circuit carrying them, which is what
|
|
571
|
+
// Relay asks for the moment the circuit comes up (see subsystems/relay.h).
|
|
572
|
+
// Leaving the cooldown standing would refuse that upgrade before it started —
|
|
573
|
+
// and nothing would ever ask again, so the circuit would outlive its purpose and
|
|
574
|
+
// keep costing a third node bandwidth for the life of the peer.
|
|
575
|
+
//
|
|
576
|
+
// Bounded, not a loop: an upgrade punch that fails lands here again, and
|
|
577
|
+
// connect_via_relay then answers false — the target is already a peer — so the
|
|
578
|
+
// fresh cooldown stands. Taken after the call, never around it: Relay resolves
|
|
579
|
+
// the reverse direction through us, and this mutex must not be held into it.
|
|
580
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
581
|
+
cooldown_.erase(target);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
std::vector<PeerId> HolePunch::directly_connected() const {
|
|
585
|
+
std::vector<PeerId> ids;
|
|
586
|
+
for (const PeerInfo& info : network_->peers())
|
|
587
|
+
if (info.transport != TransportKind::Relay) ids.push_back(info.id);
|
|
588
|
+
return ids;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
bool HolePunch::in_cooldown(const PeerId& target) const {
|
|
592
|
+
auto it = cooldown_.find(target);
|
|
593
|
+
return it != cooldown_.end() && std::chrono::steady_clock::now() < it->second;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
void HolePunch::begin_cooldown(const PeerId& target) {
|
|
597
|
+
cooldown_[target] = std::chrono::steady_clock::now() + config_.cooldown;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
size_t HolePunch::active_sessions() const {
|
|
601
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
602
|
+
return sessions_.size();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
} // namespace librats
|