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,290 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file hole_punch.h
|
|
5
|
+
* @brief UDP hole punching: two nodes behind NATs dial each other at the same
|
|
6
|
+
* instant, arranged through a peer they both already have.
|
|
7
|
+
*
|
|
8
|
+
* ── Why this can work at all ────────────────────────────────────────────────
|
|
9
|
+
* A NAT drops an inbound datagram unless something went out to that peer first.
|
|
10
|
+
* So neither side can be dialed — but if BOTH dial at once, each one's outbound
|
|
11
|
+
* Syn opens the mapping and the filter its peer's Syn needs, and the two cross
|
|
12
|
+
* mid-path. Whichever direction survives becomes an ordinary connection.
|
|
13
|
+
*
|
|
14
|
+
* Everything below that is already in place and is deliberately left alone:
|
|
15
|
+
*
|
|
16
|
+
* - The punch packet is the Syn. A datagram dial sends one immediately
|
|
17
|
+
* (UdpMux::connect) and retries it, so a punch needs no new packet type on the
|
|
18
|
+
* wire — only a different retry shape (DialProfile::punch(): dense probes with
|
|
19
|
+
* no backoff, because the first Syns are *expected* to die on the peer's NAT).
|
|
20
|
+
* - One socket, one mapping. Every UDP peer shares the node's single datagram
|
|
21
|
+
* socket, so the port a peer sees us send from is the port anyone can aim at.
|
|
22
|
+
* That is what identify now reports on datagram links, and what nat_status.h
|
|
23
|
+
* turns into "is this mapping stable enough to punch through at all".
|
|
24
|
+
* - Both sides succeeding is fine. A punch normally lands in both directions,
|
|
25
|
+
* leaving two connections; PeerTable::add resolves that pair identically at
|
|
26
|
+
* both ends (the link from the smaller PeerId survives), exactly as it does for
|
|
27
|
+
* any simultaneous cross-connect.
|
|
28
|
+
*
|
|
29
|
+
* ── The only genuinely hard part: agreeing on "now" ─────────────────────────
|
|
30
|
+
* Punching works when the two bursts overlap, and the peers have no shared clock.
|
|
31
|
+
* The fix (as in libp2p's DCUtR) is to derive the instant from the round trip
|
|
32
|
+
* itself rather than from any clock. With A the initiator, B the target and R a
|
|
33
|
+
* peer both are connected to:
|
|
34
|
+
*
|
|
35
|
+
* 1. A →R→ B Connect{addrs of A} A notes the time it sent this
|
|
36
|
+
* 2. B →R→ A Connect{addrs of B} B now waits
|
|
37
|
+
* 3. A →R→ B Sync A starts dialing AT ONCE
|
|
38
|
+
* 4. B receives Sync B starts dialing AT ONCE
|
|
39
|
+
*
|
|
40
|
+
* A starts half a relayed round trip early and B starts on arrival, so the two
|
|
41
|
+
* bursts meet in the middle of the path. No clock synchronisation, and the accuracy
|
|
42
|
+
* degrades gracefully — a slow relay widens the window rather than breaking it,
|
|
43
|
+
* which is why the punch profile fires several probes instead of one.
|
|
44
|
+
*
|
|
45
|
+
* ── The relay ───────────────────────────────────────────────────────────────
|
|
46
|
+
* R is an ordinary node running this same subsystem. It forwards ONLY control
|
|
47
|
+
* messages, ONLY to a peer it is already connected to, and ONLY within a per-peer
|
|
48
|
+
* budget. It never opens a connection, never resolves an address and never carries
|
|
49
|
+
* application data — so it cannot be turned into an open reflector, and the traffic
|
|
50
|
+
* it does relay is a few dozen bytes per punch.
|
|
51
|
+
*
|
|
52
|
+
* ── Wire format (MessageType::Punch), big-endian ────────────────────────────
|
|
53
|
+
* envelope : [u8 ver=1][u8 op]
|
|
54
|
+
* op=0 Relay : [32B dst_id][inner…] sender → relay
|
|
55
|
+
* op=1 Relayed : [32B src_id][inner…] relay → destination
|
|
56
|
+
* inner : [u8 kind]
|
|
57
|
+
* kind=0 Connect : [u8 role][u8 n][ n × { u8 ip_len, ip bytes, u16 port } ]
|
|
58
|
+
* kind=1 Sync : —
|
|
59
|
+
*
|
|
60
|
+
* `role` says whether a Connect opens a rendezvous (0) or answers one (1). Without
|
|
61
|
+
* it the two are indistinguishable, and an initiator would read the answer it asked
|
|
62
|
+
* for as a competing rendezvous — hand the round to the peer, and the exchange would
|
|
63
|
+
* ping-pong Connects forever with neither side ever sending Sync. It is also what
|
|
64
|
+
* makes the collision case (both ends opening at once) recognisable, and therefore
|
|
65
|
+
* settleable by the same symmetric PeerId rule the peer table uses.
|
|
66
|
+
*
|
|
67
|
+
* The inner message is end-to-end between the two punching peers; the relay copies
|
|
68
|
+
* it across without looking inside. Every length is bounds-checked and every count
|
|
69
|
+
* capped, so a malformed or hostile payload is dropped rather than acted on.
|
|
70
|
+
*
|
|
71
|
+
* ── What this does NOT do ───────────────────────────────────────────────────
|
|
72
|
+
* A symmetric NAT (a fresh mapping per destination) cannot be punched through by
|
|
73
|
+
* any endpoint we can advertise, and this subsystem does not pretend otherwise: it
|
|
74
|
+
* checks NatMapping first and declines rather than firing a burst that cannot land.
|
|
75
|
+
* There is no TCP punching (the datagram wire is the one that fits), no port
|
|
76
|
+
* prediction (it would need more than the one socket the design is built on) and no
|
|
77
|
+
* relaying of data (a punch that fails, fails).
|
|
78
|
+
*
|
|
79
|
+
* Trust: the addresses a peer advertises for itself are its own unverified word, and
|
|
80
|
+
* a relay could forge a Connect naming somebody else's endpoint. Acting on that
|
|
81
|
+
* means sending a handful of 16-byte Syns to an address of another node's choosing —
|
|
82
|
+
* the same exposure PeerExchange already accepts when it dials what a peer told it
|
|
83
|
+
* about, and bounded here by the per-session address cap and the retry limits.
|
|
84
|
+
*
|
|
85
|
+
* Threading: message handlers run on reactor threads; one worker thread drives
|
|
86
|
+
* session timeouts and retries. All session state is behind one mutex, and no
|
|
87
|
+
* blocking work happens on a reactor thread.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
#include "librats/util/rats_export.h"
|
|
91
|
+
#include "librats/core/address.h"
|
|
92
|
+
#include "librats/core/types.h"
|
|
93
|
+
#include "librats/node/nat_status.h"
|
|
94
|
+
#include "librats/node/peer_network.h"
|
|
95
|
+
#include "librats/peer/peer.h"
|
|
96
|
+
#include "librats/peer/peer_id.h"
|
|
97
|
+
#include "librats/subsystems/hole_punch_service.h"
|
|
98
|
+
#include "librats/subsystems/relay_service.h"
|
|
99
|
+
|
|
100
|
+
#include <atomic>
|
|
101
|
+
#include <chrono>
|
|
102
|
+
#include <condition_variable>
|
|
103
|
+
#include <cstdint>
|
|
104
|
+
#include <mutex>
|
|
105
|
+
#include <thread>
|
|
106
|
+
#include <unordered_map>
|
|
107
|
+
#include <vector>
|
|
108
|
+
|
|
109
|
+
namespace librats {
|
|
110
|
+
|
|
111
|
+
class DialService;
|
|
112
|
+
class ServiceRegistry;
|
|
113
|
+
|
|
114
|
+
/// Published as HolePunchService, so a module that discovers a peer it cannot dial
|
|
115
|
+
/// (PeerExchange) can hand the id over without depending on this class.
|
|
116
|
+
class RATS_API HolePunch final : public Subsystem, public HolePunchService {
|
|
117
|
+
public:
|
|
118
|
+
struct Config {
|
|
119
|
+
/// Peers asked to carry one punch's rendezvous. More than one because we
|
|
120
|
+
/// cannot know which of our peers also has the target; each relay that does
|
|
121
|
+
/// not simply drops the request. Small, because every extra relay is a
|
|
122
|
+
/// duplicate rendezvous the target has to recognise and ignore.
|
|
123
|
+
size_t max_relays = 3;
|
|
124
|
+
|
|
125
|
+
/// Endpoints advertised to the target, and therefore dialed by it. Capped:
|
|
126
|
+
/// this is the fan-out one peer's claim can make us produce.
|
|
127
|
+
size_t max_addresses = 4;
|
|
128
|
+
|
|
129
|
+
/// Rendezvous rounds before a target is given up on for `cooldown`.
|
|
130
|
+
int attempts = 3;
|
|
131
|
+
|
|
132
|
+
/// How long one round may take from the first Connect to a live connection.
|
|
133
|
+
/// Covers two relayed hops plus the punch burst itself.
|
|
134
|
+
std::chrono::milliseconds round_timeout{6000};
|
|
135
|
+
|
|
136
|
+
/// How long after giving up before the same target may be punched again.
|
|
137
|
+
/// Only a rendezvous WE opened ever ends in one: a round we merely answered
|
|
138
|
+
/// timing out says nothing about the target, and calling it off there would
|
|
139
|
+
/// silently drop the retries the initiator is already sending.
|
|
140
|
+
std::chrono::milliseconds cooldown{60000};
|
|
141
|
+
|
|
142
|
+
/// Concurrent punch sessions. Bounds both memory and how many dial bursts
|
|
143
|
+
/// this node can be talked into producing at once.
|
|
144
|
+
size_t max_sessions = 32;
|
|
145
|
+
|
|
146
|
+
/// Serve as a rendezvous for other peers' punches. Cheap (a few dozen bytes
|
|
147
|
+
/// forwarded per punch) but it is still work done on somebody else's behalf,
|
|
148
|
+
/// so it is a choice rather than an assumption.
|
|
149
|
+
bool enable_relay = true;
|
|
150
|
+
|
|
151
|
+
/// Relay budget: messages one peer may have forwarded per window. A punch
|
|
152
|
+
/// costs two forwarded messages per round, so the default leaves generous
|
|
153
|
+
/// room for honest use while capping what one peer can spend us.
|
|
154
|
+
size_t relay_budget = 12;
|
|
155
|
+
std::chrono::milliseconds relay_window{1000};
|
|
156
|
+
|
|
157
|
+
/// Refuse to punch when the mesh says our own mapping is per-destination
|
|
158
|
+
/// (symmetric NAT): no endpoint we could advertise would be the one the
|
|
159
|
+
/// target's packets arrive on, so the burst cannot land. Turn off only to
|
|
160
|
+
/// try anyway (a NAT can be misclassified by an unlucky sample).
|
|
161
|
+
bool skip_when_endpoint_dependent = true;
|
|
162
|
+
|
|
163
|
+
/// How hard each punch dial retries its Syn.
|
|
164
|
+
DialProfile profile = DialProfile::punch();
|
|
165
|
+
|
|
166
|
+
/// Session bookkeeping cadence — retries, timeouts, cooldown expiry.
|
|
167
|
+
std::chrono::milliseconds tick{250};
|
|
168
|
+
|
|
169
|
+
/// When a punch cannot be attempted at all, or has been given up on, hand
|
|
170
|
+
/// the target to RelayService — the next rung down the ladder (see
|
|
171
|
+
/// subsystems/relay.h). Costs nothing when no Relay is attached: the
|
|
172
|
+
/// service simply does not resolve.
|
|
173
|
+
bool relay_on_failure = true;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
HolePunch();
|
|
177
|
+
explicit HolePunch(Config config);
|
|
178
|
+
~HolePunch() override;
|
|
179
|
+
|
|
180
|
+
void attach(NodeContext& ctx) override;
|
|
181
|
+
void start() override;
|
|
182
|
+
void stop() override;
|
|
183
|
+
|
|
184
|
+
/// Try to reach `target` by punching. Non-blocking: the rendezvous runs in the
|
|
185
|
+
/// background and a successful punch surfaces as an ordinary peer-connected
|
|
186
|
+
/// event. A no-op when already connected to the target, when a session for it
|
|
187
|
+
/// is already running, when it is still in cooldown, or when this node has
|
|
188
|
+
/// nothing punchable to advertise (see nat_status.h).
|
|
189
|
+
/// @return whether a session was actually started.
|
|
190
|
+
bool punch(const PeerId& target) override;
|
|
191
|
+
|
|
192
|
+
/// Sessions currently in flight (diagnostics and tests).
|
|
193
|
+
size_t active_sessions() const;
|
|
194
|
+
/// Punch bursts this node has fired (diagnostics and tests).
|
|
195
|
+
uint64_t punches_started() const noexcept { return punches_started_.load(); }
|
|
196
|
+
/// Messages forwarded on behalf of other peers (diagnostics and tests).
|
|
197
|
+
uint64_t relayed() const noexcept { return relayed_.load(); }
|
|
198
|
+
|
|
199
|
+
private:
|
|
200
|
+
enum class Phase : uint8_t {
|
|
201
|
+
AwaitingPeerConnect, ///< initiator: our Connect is out, waiting for theirs
|
|
202
|
+
AwaitingSync, ///< responder: our Connect is out, waiting for Sync
|
|
203
|
+
Punching, ///< the burst is in flight
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
struct Session {
|
|
207
|
+
bool initiator = false;
|
|
208
|
+
Phase phase = Phase::AwaitingPeerConnect;
|
|
209
|
+
int attempt = 0;
|
|
210
|
+
std::vector<Address> peer_addresses;
|
|
211
|
+
std::chrono::steady_clock::time_point deadline{};
|
|
212
|
+
/// The relay that last carried something from the target, if any. Once one
|
|
213
|
+
/// hop is known to work, the rest of the exchange goes back the same way —
|
|
214
|
+
/// it is the one path proven to reach the target, and it keeps the fan-out
|
|
215
|
+
/// (and the load on peers that cannot help) to the first message alone.
|
|
216
|
+
PeerId via{};
|
|
217
|
+
bool have_via = false;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// — message handling (reactor threads) —
|
|
221
|
+
void handle(const Peer& from, ByteView payload);
|
|
222
|
+
void handle_relay_request(const Peer& from, const PeerId& dst, ByteView inner);
|
|
223
|
+
void handle_relayed(const Peer& via, const PeerId& src, ByteView inner);
|
|
224
|
+
/// @param opening whether the sender is opening a rendezvous (rather than
|
|
225
|
+
/// answering ours) — see the `role` byte in the wire format above.
|
|
226
|
+
void handle_connect(const Peer& via, const PeerId& src, bool opening,
|
|
227
|
+
std::vector<Address> addresses);
|
|
228
|
+
void handle_sync(const PeerId& src);
|
|
229
|
+
|
|
230
|
+
// — outgoing —
|
|
231
|
+
/// Wrap `inner` in a Relay envelope and hand it to peers that might reach `dst`:
|
|
232
|
+
/// just `via` when a working hop is already known, otherwise up to max_relays of
|
|
233
|
+
/// our peers. Returns how many were given it.
|
|
234
|
+
size_t relay_to(const PeerId& dst, const Bytes& inner, const PeerId* via);
|
|
235
|
+
bool send_connect(const PeerId& target, bool opening, const PeerId* via);
|
|
236
|
+
void send_sync(const PeerId& target, const PeerId* via);
|
|
237
|
+
/// Fire the dial burst at everything the peer advertised. Caller must NOT hold
|
|
238
|
+
/// the mutex: dialing reaches the reactor, which may run callbacks inline.
|
|
239
|
+
void fire_punch(const PeerId& target, const std::vector<Address>& addresses);
|
|
240
|
+
|
|
241
|
+
// — worker —
|
|
242
|
+
void loop();
|
|
243
|
+
void service_sessions();
|
|
244
|
+
|
|
245
|
+
// — helpers —
|
|
246
|
+
/// Hand `target` to the relay module, if one is attached and the fallback is
|
|
247
|
+
/// on. Called when a punch is impossible or has run out of attempts — the
|
|
248
|
+
/// point at which a relayed path stops being the worse option and becomes the
|
|
249
|
+
/// only one. Retires the target's cooldown when the relay takes it on, so that
|
|
250
|
+
/// the upgrade punch a circuit asks for is not refused by the very give-up that
|
|
251
|
+
/// produced the circuit. Caller must NOT hold the mutex.
|
|
252
|
+
void escalate_to_relay(const PeerId& target);
|
|
253
|
+
/// Peers reachable over a DIRECT link. A relayed peer is deliberately not one:
|
|
254
|
+
/// a punch to it is an upgrade in progress, and counting the circuit as success
|
|
255
|
+
/// would retire the session before it had done anything.
|
|
256
|
+
std::vector<PeerId> directly_connected() const;
|
|
257
|
+
std::vector<Address> own_punch_addresses() const;
|
|
258
|
+
bool relay_budget_ok(const PeerId& from);
|
|
259
|
+
bool in_cooldown(const PeerId& target) const; ///< caller holds mutex_
|
|
260
|
+
void begin_cooldown(const PeerId& target); ///< caller holds mutex_
|
|
261
|
+
|
|
262
|
+
Config config_;
|
|
263
|
+
PeerNetwork* network_ = nullptr;
|
|
264
|
+
DialService* dialer_ = nullptr;
|
|
265
|
+
ExternalAddressService* external_ = nullptr;
|
|
266
|
+
ServiceRegistry* services_ = nullptr;
|
|
267
|
+
/// Resolved in start(), not attach(): Relay may be attached after us, and every
|
|
268
|
+
/// attach() runs before any start(). Atomic because it is read from reactor
|
|
269
|
+
/// threads and the worker alike.
|
|
270
|
+
std::atomic<RelayService*> relay_{nullptr};
|
|
271
|
+
|
|
272
|
+
std::atomic<bool> running_{false};
|
|
273
|
+
std::atomic<uint64_t> punches_started_{0};
|
|
274
|
+
std::atomic<uint64_t> relayed_{0};
|
|
275
|
+
|
|
276
|
+
mutable std::mutex mutex_;
|
|
277
|
+
std::condition_variable cv_;
|
|
278
|
+
std::thread worker_;
|
|
279
|
+
std::unordered_map<PeerId, Session, PeerId::Hash> sessions_;
|
|
280
|
+
std::unordered_map<PeerId, std::chrono::steady_clock::time_point, PeerId::Hash> cooldown_;
|
|
281
|
+
|
|
282
|
+
struct RelayBudget {
|
|
283
|
+
std::chrono::steady_clock::time_point window_started{};
|
|
284
|
+
size_t spent = 0;
|
|
285
|
+
};
|
|
286
|
+
std::mutex relay_mutex_;
|
|
287
|
+
std::unordered_map<PeerId, RelayBudget, PeerId::Hash> relay_budget_;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
} // namespace librats
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file hole_punch_service.h
|
|
5
|
+
* @brief Capability that lets a sibling module ask for a NAT hole punch by PeerId.
|
|
6
|
+
*
|
|
7
|
+
* Published by HolePunch via ServiceRegistry (see service_registry.h). A discovery
|
|
8
|
+
* module knows *who* it failed to reach long before it knows why — PeerExchange, for
|
|
9
|
+
* instance, learns a peer's id together with an address that then refuses to dial —
|
|
10
|
+
* and this is the whole contract it needs to hand that id over: no addresses, no
|
|
11
|
+
* relay selection, no NAT reasoning, all of which are HolePunch's own business.
|
|
12
|
+
*
|
|
13
|
+
* Resolving it returns nullptr when hole punching is not enabled, which is exactly
|
|
14
|
+
* what makes the fallback optional at the consumer's side:
|
|
15
|
+
*
|
|
16
|
+
* if (auto* punch = ctx.services.get<HolePunchService>()) punch->punch(id);
|
|
17
|
+
*
|
|
18
|
+
* The provider registers during attach(), so resolve it in start() (every attach()
|
|
19
|
+
* runs before any start()) rather than in your own attach(), where the provider may
|
|
20
|
+
* not have been attached yet. The pointer is NON-owning and valid while the node is.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
#include "librats/util/rats_export.h"
|
|
24
|
+
#include "librats/peer/peer_id.h"
|
|
25
|
+
|
|
26
|
+
namespace librats {
|
|
27
|
+
|
|
28
|
+
struct RATS_API HolePunchService {
|
|
29
|
+
virtual ~HolePunchService() = default;
|
|
30
|
+
|
|
31
|
+
/// Try to reach `target` by punching. Non-blocking; a successful punch surfaces
|
|
32
|
+
/// as an ordinary peer-connected event. @return whether a rendezvous actually
|
|
33
|
+
/// started — false is routine (already connected, already punching, in cooldown,
|
|
34
|
+
/// or nothing punchable to advertise) and needs no handling by the caller.
|
|
35
|
+
virtual bool punch(const PeerId& target) = 0;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
} // namespace librats
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#include "librats/subsystems/mdns_discovery.h"
|
|
2
|
+
#include "librats/node/node_context.h"
|
|
3
|
+
#include "librats/util/logger.h"
|
|
4
|
+
|
|
5
|
+
namespace librats {
|
|
6
|
+
|
|
7
|
+
MdnsDiscovery::MdnsDiscovery() : MdnsDiscovery(Config()) {}
|
|
8
|
+
|
|
9
|
+
MdnsDiscovery::MdnsDiscovery(Config config) : config_(std::move(config)) {}
|
|
10
|
+
|
|
11
|
+
MdnsDiscovery::~MdnsDiscovery() { stop(); }
|
|
12
|
+
|
|
13
|
+
void MdnsDiscovery::attach(NodeContext& ctx) { network_ = &ctx.network; }
|
|
14
|
+
|
|
15
|
+
void MdnsDiscovery::start() {
|
|
16
|
+
if (running_.exchange(true)) return;
|
|
17
|
+
|
|
18
|
+
instance_ = config_.instance_name.empty() ? ("rats-" + network_->local_id().short_hex())
|
|
19
|
+
: config_.instance_name;
|
|
20
|
+
const uint16_t port = network_->listen_port();
|
|
21
|
+
|
|
22
|
+
mdns_ = std::make_unique<MdnsClient>(instance_, port);
|
|
23
|
+
mdns_->set_service_callback([this](const MdnsService& service, bool is_new) { on_service(service, is_new); });
|
|
24
|
+
if (!mdns_->start()) {
|
|
25
|
+
LOG_ERROR("mdns-discovery", "Failed to start mDNS client");
|
|
26
|
+
running_.store(false);
|
|
27
|
+
mdns_.reset();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
mdns_->announce_service(instance_, port);
|
|
31
|
+
mdns_->start_discovery();
|
|
32
|
+
LOG_INFO("mdns-discovery", "Announcing '" << instance_ << "' on port " << port);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void MdnsDiscovery::stop() {
|
|
36
|
+
if (!running_.exchange(false)) return;
|
|
37
|
+
if (mdns_) {
|
|
38
|
+
mdns_->stop_discovery();
|
|
39
|
+
mdns_->stop_announcing();
|
|
40
|
+
mdns_->stop();
|
|
41
|
+
mdns_.reset();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
bool MdnsDiscovery::is_running() const { return running_.load() && mdns_ && mdns_->is_running(); }
|
|
46
|
+
|
|
47
|
+
void MdnsDiscovery::on_service(const MdnsService& service, bool /*is_new*/) {
|
|
48
|
+
if (service.ip_address.empty() || service.port == 0) return;
|
|
49
|
+
// Skip our own announcement (its name carries our instance label).
|
|
50
|
+
if (service.service_name.find(instance_) != std::string::npos) return;
|
|
51
|
+
|
|
52
|
+
// service.ip_address comes off the wire (untrusted); only dial a valid numeric IP.
|
|
53
|
+
const auto ip = IpAddress::parse(service.ip_address);
|
|
54
|
+
if (!ip) return;
|
|
55
|
+
|
|
56
|
+
const Address addr{*ip, service.port};
|
|
57
|
+
{
|
|
58
|
+
std::lock_guard<std::mutex> lock(dialed_mutex_);
|
|
59
|
+
if (!dialed_.insert(addr).second) return; // already dialed this address
|
|
60
|
+
}
|
|
61
|
+
LOG_DEBUG("mdns-discovery", "Dialing discovered service " << service.service_name
|
|
62
|
+
<< " at " << addr.to_string());
|
|
63
|
+
network_->connect(addr);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
} // namespace librats
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file mdns_discovery.h
|
|
5
|
+
* @brief Local-network peer discovery via mDNS — a thin adapter, not a rewrite.
|
|
6
|
+
*
|
|
7
|
+
* Wraps the existing MdnsClient (src/mdns.h) as a Subsystem WITHOUT modifying it.
|
|
8
|
+
* On start it announces our TCP listen port as an mDNS service and browses for
|
|
9
|
+
* the same service type, dialing discovered instances through the node. Each
|
|
10
|
+
* node uses a unique instance name (derived from its PeerId) so two nodes on the
|
|
11
|
+
* same host don't collide and can filter out their own announcement.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#include "librats/util/rats_export.h"
|
|
15
|
+
#include "librats/node/peer_network.h"
|
|
16
|
+
#include "librats/mdns/mdns.h"
|
|
17
|
+
|
|
18
|
+
#include <atomic>
|
|
19
|
+
#include <memory>
|
|
20
|
+
#include <mutex>
|
|
21
|
+
#include <string>
|
|
22
|
+
#include <unordered_set>
|
|
23
|
+
|
|
24
|
+
namespace librats {
|
|
25
|
+
|
|
26
|
+
class RATS_API MdnsDiscovery final : public Subsystem {
|
|
27
|
+
public:
|
|
28
|
+
struct Config {
|
|
29
|
+
std::string instance_name = ""; ///< empty → derived from our PeerId
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
MdnsDiscovery();
|
|
33
|
+
explicit MdnsDiscovery(Config config);
|
|
34
|
+
~MdnsDiscovery() override;
|
|
35
|
+
|
|
36
|
+
void attach(NodeContext& ctx) override;
|
|
37
|
+
void start() override;
|
|
38
|
+
void stop() override;
|
|
39
|
+
|
|
40
|
+
bool is_running() const;
|
|
41
|
+
|
|
42
|
+
private:
|
|
43
|
+
void on_service(const MdnsService& service, bool is_new);
|
|
44
|
+
|
|
45
|
+
Config config_;
|
|
46
|
+
std::string instance_;
|
|
47
|
+
PeerNetwork* network_ = nullptr;
|
|
48
|
+
std::unique_ptr<MdnsClient> mdns_;
|
|
49
|
+
std::atomic<bool> running_{false};
|
|
50
|
+
|
|
51
|
+
std::mutex dialed_mutex_;
|
|
52
|
+
std::unordered_set<Address> dialed_; ///< peers we've already dialed
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
} // namespace librats
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#include "librats/subsystems/message_json.h"
|
|
2
|
+
#include "librats/node/node_context.h"
|
|
3
|
+
#include "librats/util/logger.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
|
|
7
|
+
namespace librats {
|
|
8
|
+
|
|
9
|
+
void MessageJson::attach(NodeContext& ctx) {
|
|
10
|
+
network_ = &ctx.network;
|
|
11
|
+
network_->on(MessageType::Typed,
|
|
12
|
+
[this](const Peer& peer, ByteView payload) { on_typed(peer.id(), payload); });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ── Registration ────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
void MessageJson::on(const std::string& type, Handler handler) {
|
|
18
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
19
|
+
handlers_[type].push_back({std::move(handler), /*once=*/false});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
void MessageJson::once(const std::string& type, Handler handler) {
|
|
23
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
24
|
+
handlers_[type].push_back({std::move(handler), /*once=*/true});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void MessageJson::off(const std::string& type) {
|
|
28
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
29
|
+
handlers_.erase(type);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Sending ─────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
Bytes MessageJson::encode(const std::string& type, const librats::Json& data) {
|
|
35
|
+
const std::string body = data.dump();
|
|
36
|
+
Bytes out;
|
|
37
|
+
out.reserve(2 + type.size() + body.size());
|
|
38
|
+
out.push_back(static_cast<uint8_t>((type.size() >> 8) & 0xFF));
|
|
39
|
+
out.push_back(static_cast<uint8_t>(type.size() & 0xFF));
|
|
40
|
+
out.insert(out.end(), type.begin(), type.end());
|
|
41
|
+
out.insert(out.end(), body.begin(), body.end());
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void MessageJson::send(const std::string& type, const librats::Json& data, SendCallback cb) {
|
|
46
|
+
if (!network_) { if (cb) cb(false, "not attached to a network"); return; }
|
|
47
|
+
|
|
48
|
+
const Bytes payload = encode(type, data);
|
|
49
|
+
const size_t n = network_->connected_peers().size();
|
|
50
|
+
network_->broadcast(MessageType::Typed, ByteView(payload));
|
|
51
|
+
if (cb) cb(n > 0, n > 0 ? "" : "no connected peers");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void MessageJson::send(const PeerId& to, const std::string& type, const librats::Json& data,
|
|
55
|
+
SendCallback cb) {
|
|
56
|
+
if (!network_) { if (cb) cb(false, "not attached to a network"); return; }
|
|
57
|
+
|
|
58
|
+
// A send() to an unknown peer is a safe no-op, so only pay for the directory
|
|
59
|
+
// lookup when the caller actually wants a delivery verdict.
|
|
60
|
+
if (cb) {
|
|
61
|
+
const auto peers = network_->connected_peers();
|
|
62
|
+
if (std::find(peers.begin(), peers.end(), to) == peers.end()) {
|
|
63
|
+
cb(false, "peer not connected");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
network_->send(to, MessageType::Typed, ByteView(encode(type, data)));
|
|
68
|
+
if (cb) cb(true, "");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Receiving (reactor thread) ──────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
void MessageJson::on_typed(const PeerId& from, ByteView payload) {
|
|
74
|
+
const uint8_t* p = payload.data();
|
|
75
|
+
const size_t n = payload.size();
|
|
76
|
+
if (n < 2) return;
|
|
77
|
+
|
|
78
|
+
const size_t type_len = (static_cast<size_t>(p[0]) << 8) | p[1];
|
|
79
|
+
if (2 + type_len > n) return;
|
|
80
|
+
|
|
81
|
+
std::string type(reinterpret_cast<const char*>(p + 2), type_len);
|
|
82
|
+
librats::Json data = librats::Json::parse(p + 2 + type_len, p + n, nullptr, /*allow_exceptions=*/false);
|
|
83
|
+
if (data.is_discarded()) {
|
|
84
|
+
LOG_WARN("msgex", "Malformed JSON for type '" << type << "' from " << from.short_hex());
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Snapshot the handlers under the lock and drop the one-shot ones, then invoke
|
|
89
|
+
// outside the lock so a handler may freely (un)register without deadlocking.
|
|
90
|
+
std::vector<Entry> to_call;
|
|
91
|
+
{
|
|
92
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
93
|
+
auto it = handlers_.find(type);
|
|
94
|
+
if (it == handlers_.end()) return;
|
|
95
|
+
to_call = it->second;
|
|
96
|
+
auto& vec = it->second;
|
|
97
|
+
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const Entry& e) { return e.once; }), vec.end());
|
|
98
|
+
if (vec.empty()) handlers_.erase(it);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const Entry& e : to_call) {
|
|
102
|
+
try {
|
|
103
|
+
e.handler(from, data);
|
|
104
|
+
} catch (const std::exception& ex) {
|
|
105
|
+
LOG_ERROR("msgex", "Handler for '" << type << "' threw: " << ex.what());
|
|
106
|
+
} catch (...) {
|
|
107
|
+
LOG_ERROR("msgex", "Handler for '" << type << "' threw an unknown exception");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} // namespace librats
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file message_json.h
|
|
5
|
+
* @brief Typed JSON message exchange over PeerNetwork.
|
|
6
|
+
*
|
|
7
|
+
* A familiar `on`/`once`/`off` + `send` messaging API as a Subsystem: an
|
|
8
|
+
* application names a message *type* (a string) and exchanges librats::Json
|
|
9
|
+
* payloads with peers, without caring about framing or channels. Attach it like
|
|
10
|
+
* any subsystem; reach it later via node.json() (or subsystem<MessageJson>()):
|
|
11
|
+
*
|
|
12
|
+
* node.add_subsystem(std::make_unique<MessageJson>());
|
|
13
|
+
* node.json()->on("chat", [](const PeerId& from, const json& j){ ... });
|
|
14
|
+
* node.start();
|
|
15
|
+
* node.json()->send(peer_id, "chat", json{{"text","hi"}});
|
|
16
|
+
*
|
|
17
|
+
* Two deliberate properties:
|
|
18
|
+
* - the sender is the *authenticated* PeerId from the handshake, not a
|
|
19
|
+
* self-reported field in the payload (which could be spoofed);
|
|
20
|
+
* - no JSON envelope on the wire — just [type][payload], encrypted by the
|
|
21
|
+
* transport like everything else.
|
|
22
|
+
*
|
|
23
|
+
* Wire format (MessageType::Typed payload):
|
|
24
|
+
* [type_len:u16][type bytes][json payload bytes] (json as compact text)
|
|
25
|
+
*
|
|
26
|
+
* Handlers run on a reactor thread; do not block in them. Registration is
|
|
27
|
+
* thread-safe and may happen before or after start().
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
#include "librats/util/rats_export.h"
|
|
31
|
+
#include "librats/node/peer_network.h"
|
|
32
|
+
#include "librats/peer/peer.h"
|
|
33
|
+
#include "librats/peer/peer_id.h"
|
|
34
|
+
#include "librats/core/bytes.h"
|
|
35
|
+
#include "librats/util/json.h"
|
|
36
|
+
|
|
37
|
+
#include <functional>
|
|
38
|
+
#include <mutex>
|
|
39
|
+
#include <string>
|
|
40
|
+
#include <unordered_map>
|
|
41
|
+
#include <vector>
|
|
42
|
+
|
|
43
|
+
namespace librats {
|
|
44
|
+
|
|
45
|
+
class RATS_API MessageJson final : public Subsystem {
|
|
46
|
+
public:
|
|
47
|
+
using Handler = std::function<void(const PeerId& from, const librats::Json& data)>;
|
|
48
|
+
using SendCallback = std::function<void(bool ok, const std::string& error)>;
|
|
49
|
+
|
|
50
|
+
/// Register a handler for `type`. Additive: multiple handlers may coexist and
|
|
51
|
+
/// all fire (in registration order) for each received message of that type.
|
|
52
|
+
void on(const std::string& type, Handler handler);
|
|
53
|
+
|
|
54
|
+
/// Like on(), but the handler is removed right after it fires once.
|
|
55
|
+
void once(const std::string& type, Handler handler);
|
|
56
|
+
|
|
57
|
+
/// Remove every handler registered for `type`.
|
|
58
|
+
void off(const std::string& type);
|
|
59
|
+
|
|
60
|
+
/// Broadcast `data` of `type` to all connected peers. `cb`, if given, reports
|
|
61
|
+
/// whether there was at least one peer to send to.
|
|
62
|
+
void send(const std::string& type, const librats::Json& data, SendCallback cb = nullptr);
|
|
63
|
+
|
|
64
|
+
/// Send `data` of `type` to one peer. `cb`, if given, reports success or the
|
|
65
|
+
/// reason it could not be sent (e.g. the peer is not connected).
|
|
66
|
+
void send(const PeerId& to, const std::string& type, const librats::Json& data,
|
|
67
|
+
SendCallback cb = nullptr);
|
|
68
|
+
|
|
69
|
+
// Subsystem — no background thread; purely event-driven.
|
|
70
|
+
void attach(NodeContext& ctx) override;
|
|
71
|
+
void start() override {}
|
|
72
|
+
void stop() override {}
|
|
73
|
+
|
|
74
|
+
private:
|
|
75
|
+
void on_typed(const PeerId& from, ByteView payload);
|
|
76
|
+
static Bytes encode(const std::string& type, const librats::Json& data);
|
|
77
|
+
|
|
78
|
+
struct Entry {
|
|
79
|
+
Handler handler;
|
|
80
|
+
bool once;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
PeerNetwork* network_ = nullptr;
|
|
84
|
+
mutable std::mutex mutex_;
|
|
85
|
+
std::unordered_map<std::string, std::vector<Entry>> handlers_;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
} // namespace librats
|