librats 1.0.2 → 2.1.4
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 +137 -332
- package/binding.gyp +16 -3
- package/lib/index.d.ts +271 -696
- package/lib/index.js +383 -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 +396 -179
- package/native-src/LICENSE +1 -1
- package/native-src/src/librats/bindings/rats.cpp +731 -0
- package/native-src/src/librats/bindings/rats.h +352 -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 +42 -0
- package/native-src/src/librats/core/types.h +93 -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/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 +826 -0
- package/native-src/src/librats/node/node.h +333 -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 +137 -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 +532 -0
- package/native-src/src/librats/subsystems/hole_punch.h +266 -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/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 +540 -0
- package/native-src/src/librats/transport/reactor.h +224 -0
- package/native-src/src/librats/transport/reactor_pool.h +81 -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 +110 -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 +1042 -1324
- 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,617 @@
|
|
|
1
|
+
#include "librats/transport/udp_mux.h"
|
|
2
|
+
#include "librats/core/io_poller.h"
|
|
3
|
+
#include "librats/crypto/blake2s.h"
|
|
4
|
+
#include "librats/util/logger.h"
|
|
5
|
+
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <random>
|
|
9
|
+
#include <utility>
|
|
10
|
+
|
|
11
|
+
namespace librats {
|
|
12
|
+
|
|
13
|
+
namespace {
|
|
14
|
+
|
|
15
|
+
/// Fresh bytes for a cookie secret. Straight from the system source rather than
|
|
16
|
+
/// the mux's mt19937: that generator is seeded once and its output is predictable
|
|
17
|
+
/// from enough samples, which is the wrong property for the one value standing
|
|
18
|
+
/// between a forged source address and an allocation.
|
|
19
|
+
void fill_random(uint8_t* out, size_t len) {
|
|
20
|
+
std::random_device dev;
|
|
21
|
+
for (size_t i = 0; i < len; i += sizeof(unsigned int)) {
|
|
22
|
+
const unsigned int word = dev();
|
|
23
|
+
const size_t n = (std::min)(sizeof(word), len - i);
|
|
24
|
+
std::memcpy(out + i, &word, n);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
} // namespace
|
|
29
|
+
|
|
30
|
+
UdpMux::UdpMux(socket_t socket, AddressFamily family, UdpMuxDelegate& delegate,
|
|
31
|
+
UdpMuxLimits limits)
|
|
32
|
+
: socket_(socket), family_(family), delegate_(delegate), limits_(limits),
|
|
33
|
+
recv_storage_(kUdpBatchMax * rudp::kMaxDatagram),
|
|
34
|
+
// Only where a batch is really one syscall. Without that, send_datagram()
|
|
35
|
+
// goes straight to the socket and never stages anything, so this would be
|
|
36
|
+
// 39 KiB per node that is allocated and never touched.
|
|
37
|
+
send_storage_(kUdpBatchIsOneSyscall ? kUdpBatchMax * rudp::kMaxDatagram : 0),
|
|
38
|
+
secret_rotated_at_(Clock::now()),
|
|
39
|
+
rng_(std::random_device{}()) {
|
|
40
|
+
set_socket_buffer_sizes(socket_, kSocketBufferBytes, kSocketBufferBytes);
|
|
41
|
+
|
|
42
|
+
// Point each slot at its slice of the storage, once. From here on a batch only
|
|
43
|
+
// ever moves lengths and endpoints around — the buffers never move.
|
|
44
|
+
for (size_t i = 0; i < kUdpBatchMax; ++i) {
|
|
45
|
+
recv_slots_[i].data = recv_storage_.data() + i * rudp::kMaxDatagram;
|
|
46
|
+
if (kUdpBatchIsOneSyscall)
|
|
47
|
+
send_slots_[i].data = send_storage_.data() + i * rudp::kMaxDatagram;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (CookieSecret& secret : cookie_secret_) fill_random(secret.data(), secret.size());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
UdpMux::~UdpMux() {
|
|
54
|
+
shutdown();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Wire ────────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
void UdpMux::send_datagram(const Address& to, const uint8_t* data, size_t len) {
|
|
60
|
+
if (len == 0 || len > rudp::kMaxDatagram) return; // nothing this class produces
|
|
61
|
+
|
|
62
|
+
// Where the socket cannot take a batch in one call, staging buys nothing:
|
|
63
|
+
// flush_output() would make exactly the per-datagram calls this makes here, and
|
|
64
|
+
// the copy into the staging buffer on the way there would be pure loss — it
|
|
65
|
+
// would also undo the headroom trick in OutPacket, whose whole purpose is to
|
|
66
|
+
// hand the socket the packet where it already lies. So go straight to the wire.
|
|
67
|
+
if constexpr (!kUdpBatchIsOneSyscall) {
|
|
68
|
+
send_udp_to(socket_, data, len, to, family_);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Staged, not sent: a full congestion window is dozens of datagrams produced
|
|
73
|
+
// back to back, and handing each one to the kernel separately is where most of
|
|
74
|
+
// this transport's CPU used to go. They leave together in flush_output(), which
|
|
75
|
+
// every entry point calls before it returns — so nothing waits on a timer.
|
|
76
|
+
if (staged_ == kUdpBatchMax) flush_output();
|
|
77
|
+
|
|
78
|
+
UdpBatchSlot& slot = send_slots_[staged_++];
|
|
79
|
+
slot.endpoint = to;
|
|
80
|
+
slot.len = len;
|
|
81
|
+
std::memcpy(slot.data, data, len);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
void UdpMux::flush_output() {
|
|
85
|
+
if (staged_ == 0) return;
|
|
86
|
+
const size_t staged = staged_;
|
|
87
|
+
staged_ = 0; // spent either way: what the socket refused is a dropped datagram
|
|
88
|
+
|
|
89
|
+
const size_t sent = send_udp_batch(socket_, send_slots_.data(), staged, family_);
|
|
90
|
+
if (sent < staged)
|
|
91
|
+
LOG_DEBUG("udp", "Socket took " << sent << "/" << staged
|
|
92
|
+
<< " datagrams; the rest are dropped (retransmission covers them)");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void UdpMux::stream_events(UdpStream& stream, uint32_t events) {
|
|
96
|
+
// Recorded, never delivered from here: see the file comment. A stream with no
|
|
97
|
+
// connection (one that is lingering after its connection went away) has
|
|
98
|
+
// nowhere to send events, and quietly drops them.
|
|
99
|
+
const ConnId id = stream.conn_id();
|
|
100
|
+
if (id == kInvalidConnId) return;
|
|
101
|
+
|
|
102
|
+
// Coalesce. A stream raises PollIn for every packet it delivers, so a run of
|
|
103
|
+
// datagrams from one peer used to queue one dispatch per packet — and every one
|
|
104
|
+
// after the first found the connection's buffer already drained, having paid a
|
|
105
|
+
// map lookup and a read attempt to find that out.
|
|
106
|
+
//
|
|
107
|
+
// Scanning the tail rather than the whole list is what keeps this O(1) in the
|
|
108
|
+
// size of the node: during a receive batch the list cannot be longer than the
|
|
109
|
+
// batch, so the window covers all of it and the coalescing is exact; in tick(),
|
|
110
|
+
// where the list can hold an entry per stream, each stream is visited once and
|
|
111
|
+
// there is nothing to coalesce anyway.
|
|
112
|
+
const size_t from = pending_events_.size() > kUdpBatchMax
|
|
113
|
+
? pending_events_.size() - kUdpBatchMax
|
|
114
|
+
: 0;
|
|
115
|
+
for (size_t i = from; i < pending_events_.size(); ++i) {
|
|
116
|
+
if (pending_events_[i].first != id) continue;
|
|
117
|
+
pending_events_[i].second |= events;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
pending_events_.emplace_back(id, events);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
void UdpMux::send_reset(const Address& to, uint32_t conn_id) {
|
|
124
|
+
uint8_t buf[rudp::kHeaderSize];
|
|
125
|
+
rudp::Packet p;
|
|
126
|
+
p.type = rudp::PacketType::Reset;
|
|
127
|
+
p.conn_id = conn_id;
|
|
128
|
+
send_datagram(to, buf, rudp::encode(p, buf));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void UdpMux::send_retry(const Address& to, uint32_t conn_id, uint32_t cookie) {
|
|
132
|
+
uint8_t cookie_bytes[rudp::kCookieSize] = {
|
|
133
|
+
static_cast<uint8_t>(cookie >> 24), static_cast<uint8_t>(cookie >> 16),
|
|
134
|
+
static_cast<uint8_t>(cookie >> 8), static_cast<uint8_t>(cookie),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
uint8_t buf[rudp::kHeaderSize + rudp::kCookieSize];
|
|
138
|
+
rudp::Packet p;
|
|
139
|
+
p.type = rudp::PacketType::Retry;
|
|
140
|
+
p.conn_id = conn_id;
|
|
141
|
+
p.payload = ByteView(cookie_bytes, sizeof(cookie_bytes));
|
|
142
|
+
send_datagram(to, buf, rudp::encode(p, buf));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
bool UdpMux::spend_reply_budget(Clock::time_point now) {
|
|
146
|
+
// A window anchored to the clock, not a counter reset by tick(). The mux no
|
|
147
|
+
// longer sweeps on a fixed cadence, so "per tick" would mean "at whatever rate
|
|
148
|
+
// the streams happen to need servicing" — and on the node this budget actually
|
|
149
|
+
// protects, one with no streams at all being flooded with junk, that rate is
|
|
150
|
+
// zero. The rate itself is unchanged: 64 per 20 ms.
|
|
151
|
+
if (now - reply_window_started_ >= kUnsolicitedReplyWindow) {
|
|
152
|
+
reply_window_started_ = now;
|
|
153
|
+
replies_in_window_ = 0;
|
|
154
|
+
}
|
|
155
|
+
if (replies_in_window_ >= kMaxUnsolicitedReplies) return false;
|
|
156
|
+
++replies_in_window_;
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Deadline scheduling ─────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
void UdpMux::arm(uint32_t id, Entry& entry) {
|
|
163
|
+
std::optional<Clock::time_point> due = entry.stream->next_deadline();
|
|
164
|
+
|
|
165
|
+
if (entry.linger_until != Clock::time_point{}) {
|
|
166
|
+
// A released stream is also on a clock of its own. And once it has handed
|
|
167
|
+
// over everything it owed — or died trying — there is nothing left to wait
|
|
168
|
+
// for, so it wants collecting at the first opportunity rather than at the
|
|
169
|
+
// end of the linger. tick() runs once per reactor turn, so "the epoch"
|
|
170
|
+
// here means the very next turn.
|
|
171
|
+
if (entry.stream->flushed() || entry.stream->dead()) due = Clock::time_point{};
|
|
172
|
+
else if (!due) due = entry.linger_until;
|
|
173
|
+
else due = (std::min)(*due, entry.linger_until);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!due) return; // dead and still owned: release() will drop it, no timer needed
|
|
177
|
+
|
|
178
|
+
// Already covered. Note the asymmetry — a deadline that moved *earlier* must
|
|
179
|
+
// get a slot of its own, one that moved later needs nothing: the existing slot
|
|
180
|
+
// simply visits the stream early, finds nothing due, and re-arms from there.
|
|
181
|
+
if (entry.armed && *due >= entry.scheduled) return;
|
|
182
|
+
|
|
183
|
+
entry.scheduled = *due;
|
|
184
|
+
entry.armed = true;
|
|
185
|
+
due_.push_back(Due{*due, id});
|
|
186
|
+
std::push_heap(due_.begin(), due_.end(), LaterFirst{});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
void UdpMux::compact_due() {
|
|
190
|
+
// Lazy deletion leaves a slot behind for every deadline that moved earlier, and
|
|
191
|
+
// for every stream that went away with a far-off deadline still scheduled — up
|
|
192
|
+
// to 45 s of idle timeout on a node with heavy connection churn. Each is
|
|
193
|
+
// dropped when it surfaces, so this only exists to stop them accumulating in
|
|
194
|
+
// between; the thresholds keep it off the path of anything but real churn.
|
|
195
|
+
if (due_.size() <= 64 || due_.size() <= 4 * streams_.size()) return;
|
|
196
|
+
|
|
197
|
+
std::vector<Due> live;
|
|
198
|
+
live.reserve(streams_.size());
|
|
199
|
+
for (const Due& slot : due_) {
|
|
200
|
+
const auto it = streams_.find(slot.id);
|
|
201
|
+
if (it != streams_.end() && it->second.armed && it->second.scheduled == slot.at)
|
|
202
|
+
live.push_back(slot);
|
|
203
|
+
}
|
|
204
|
+
due_.swap(live);
|
|
205
|
+
std::make_heap(due_.begin(), due_.end(), LaterFirst{});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
int UdpMux::next_timeout_ms(int max_ms) const noexcept {
|
|
209
|
+
if (due_.empty()) return max_ms; // no stream wants waking: contribute nothing
|
|
210
|
+
|
|
211
|
+
const auto now = Clock::now();
|
|
212
|
+
const auto at = due_.front().at;
|
|
213
|
+
if (at <= now) return 0;
|
|
214
|
+
// Round up, exactly as TimerQueue does: truncating a sub-millisecond remainder
|
|
215
|
+
// to 0 would poll with a 0 ms timeout and burn CPU until the deadline instead
|
|
216
|
+
// of sleeping through it.
|
|
217
|
+
const auto ms = std::chrono::ceil<std::chrono::milliseconds>(at - now).count();
|
|
218
|
+
return static_cast<int>((std::min<long long>)(ms, max_ms));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
void UdpMux::stream_touched(UdpStream& stream) {
|
|
222
|
+
const auto it = streams_.find(stream.recv_id());
|
|
223
|
+
if (it == streams_.end() || it->second.stream.get() != &stream) return;
|
|
224
|
+
arm(it->first, it->second);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ── Address validation ──────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
uint32_t UdpMux::cookie_for(const Address& from, uint32_t conn_id,
|
|
230
|
+
const CookieSecret& secret) const {
|
|
231
|
+
// A keyed hash of everything the cookie has to be tied to, truncated to the
|
|
232
|
+
// four bytes the wire carries. The secret goes in first: BLAKE2 is designed to
|
|
233
|
+
// be a MAC in exactly this prefix form (it has no length-extension weakness to
|
|
234
|
+
// work around), so there is no reason to reach for HMAC here.
|
|
235
|
+
//
|
|
236
|
+
// Binding the port as well as the address is what makes the cookie useless to
|
|
237
|
+
// anyone but the socket that asked for it, and binding conn_id keeps one cookie
|
|
238
|
+
// from opening a second stream. Nothing binds a timestamp: the secret's own
|
|
239
|
+
// rotation is what expires a cookie.
|
|
240
|
+
const ByteView ip = from.ip.bytes();
|
|
241
|
+
const uint8_t tail[6] = {
|
|
242
|
+
static_cast<uint8_t>(from.port >> 8), static_cast<uint8_t>(from.port),
|
|
243
|
+
static_cast<uint8_t>(conn_id >> 24), static_cast<uint8_t>(conn_id >> 16),
|
|
244
|
+
static_cast<uint8_t>(conn_id >> 8), static_cast<uint8_t>(conn_id),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
rats_blake2s_context_t ctx;
|
|
248
|
+
rats_blake2s_reset(&ctx);
|
|
249
|
+
rats_blake2s_update(&ctx, secret.data(), secret.size());
|
|
250
|
+
rats_blake2s_update(&ctx, ip.data(), ip.size());
|
|
251
|
+
rats_blake2s_update(&ctx, tail, sizeof(tail));
|
|
252
|
+
|
|
253
|
+
uint8_t hash[RATS_BLAKE2S_HASH_SIZE];
|
|
254
|
+
rats_blake2s_finish(&ctx, hash);
|
|
255
|
+
return (static_cast<uint32_t>(hash[0]) << 24) | (static_cast<uint32_t>(hash[1]) << 16) |
|
|
256
|
+
(static_cast<uint32_t>(hash[2]) << 8) | static_cast<uint32_t>(hash[3]);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
bool UdpMux::cookie_valid(const Address& from, uint32_t conn_id, ByteView payload) const {
|
|
260
|
+
if (payload.size() != rudp::kCookieSize) return false;
|
|
261
|
+
|
|
262
|
+
const uint8_t* bytes = payload.data();
|
|
263
|
+
const uint32_t offered = (static_cast<uint32_t>(bytes[0]) << 24) |
|
|
264
|
+
(static_cast<uint32_t>(bytes[1]) << 16) |
|
|
265
|
+
(static_cast<uint32_t>(bytes[2]) << 8) |
|
|
266
|
+
static_cast<uint32_t>(bytes[3]);
|
|
267
|
+
|
|
268
|
+
// Both secrets, so a cookie handed out just before a rotation is still good.
|
|
269
|
+
for (const CookieSecret& secret : cookie_secret_)
|
|
270
|
+
if (cookie_for(from, conn_id, secret) == offered) return true;
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
void UdpMux::rotate_cookie_secret(Clock::time_point now) {
|
|
275
|
+
if (now - secret_rotated_at_ < kCookieSecretLifetime) return;
|
|
276
|
+
cookie_secret_[1] = cookie_secret_[0];
|
|
277
|
+
fill_random(cookie_secret_[0].data(), cookie_secret_[0].size());
|
|
278
|
+
secret_rotated_at_ = now;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── Inbound datagrams ───────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
bool UdpMux::on_readable() {
|
|
284
|
+
const auto now = Clock::now();
|
|
285
|
+
// Cookies are minted from here (a Retry answering an unproven Syn), so this is
|
|
286
|
+
// where the secret has to be current. tick() rotates it too, but a node with no
|
|
287
|
+
// streams has no deadlines and therefore no ticks — and that is exactly the
|
|
288
|
+
// node a flood of Syns arrives at.
|
|
289
|
+
rotate_cookie_secret(now);
|
|
290
|
+
|
|
291
|
+
size_t drained = 0;
|
|
292
|
+
bool more = false;
|
|
293
|
+
for (;;) {
|
|
294
|
+
if (drained >= kMaxDatagramsPerRead) { more = true; break; }
|
|
295
|
+
|
|
296
|
+
// Never ask for more than the cap allows, so the bound on how long one
|
|
297
|
+
// flood can hold the loop stays exact rather than approximate.
|
|
298
|
+
const size_t want = (std::min)(kUdpBatchMax, kMaxDatagramsPerRead - drained);
|
|
299
|
+
for (size_t i = 0; i < want; ++i) recv_slots_[i].len = rudp::kMaxDatagram;
|
|
300
|
+
|
|
301
|
+
const std::ptrdiff_t got = recv_udp_batch(socket_, recv_slots_.data(), want);
|
|
302
|
+
if (got == kUdpRecvWouldBlock) break;
|
|
303
|
+
// A per-destination error (an ICMP unreachable for an earlier datagram,
|
|
304
|
+
// which Windows in particular reports on the *next* receive) says nothing
|
|
305
|
+
// about the socket, so keep draining rather than abandoning every other
|
|
306
|
+
// peer on it. The loop is bounded, so a persistent error cannot spin.
|
|
307
|
+
if (got == kUdpRecvError) { ++drained; continue; }
|
|
308
|
+
|
|
309
|
+
for (std::ptrdiff_t i = 0; i < got; ++i) {
|
|
310
|
+
const UdpBatchSlot& slot = recv_slots_[static_cast<size_t>(i)];
|
|
311
|
+
rudp::Packet p;
|
|
312
|
+
if (!rudp::decode(slot.data, slot.len, p)) continue;
|
|
313
|
+
handle_datagram(p, slot.endpoint, now);
|
|
314
|
+
}
|
|
315
|
+
drained += static_cast<size_t>(got);
|
|
316
|
+
|
|
317
|
+
// Hand this batch's events over before reading the next one, rather than
|
|
318
|
+
// saving them all for the end of the drain. What a stream delivers sits in
|
|
319
|
+
// its in-order buffer until its connection reads it out, so deferring the
|
|
320
|
+
// whole drain let that buffer grow to everything one peer managed to send
|
|
321
|
+
// in it — a megabyte and more, written and then read back through main
|
|
322
|
+
// memory, and enough to close the receive window mid-drain. Draining per
|
|
323
|
+
// batch keeps it to one batch's worth, which stays in cache.
|
|
324
|
+
//
|
|
325
|
+
// Safe here and nowhere earlier: the slot loop above has finished, so no
|
|
326
|
+
// reference into streams_ is live, and a handler that closes its connection
|
|
327
|
+
// only marks it — the teardown that would free a stream runs later, on the
|
|
328
|
+
// reactor.
|
|
329
|
+
dispatch_pending();
|
|
330
|
+
// A short batch usually means the queue is empty, but not always (a
|
|
331
|
+
// per-datagram error truncates one too), and the kqueue backend is
|
|
332
|
+
// edge-triggered — so keep going until the socket says would-block.
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
dispatch_pending();
|
|
336
|
+
flush_output(); // the acks and repairs the batch above produced leave together
|
|
337
|
+
return more;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
void UdpMux::handle_datagram(const rudp::Packet& p, const Address& from, Clock::time_point now) {
|
|
341
|
+
auto it = streams_.find(p.conn_id);
|
|
342
|
+
if (it != streams_.end()) {
|
|
343
|
+
UdpStream& stream = *it->second.stream;
|
|
344
|
+
// The connection id is the whole authorisation to touch this stream, and a
|
|
345
|
+
// datagram source address is trivially forged. Pinning the stream to the
|
|
346
|
+
// address it was established with means an attacker would have to guess a
|
|
347
|
+
// random 32-bit id *and* be on the path. (It also means this transport does
|
|
348
|
+
// not follow a peer across a NAT rebind — the peer redials, which the
|
|
349
|
+
// handshake makes cheap and safe.)
|
|
350
|
+
if (stream.remote() != from) return;
|
|
351
|
+
stream.on_packet(p, now);
|
|
352
|
+
arm(it->first, it->second); // the packet may have brought a deadline forward
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (p.type == rudp::PacketType::Syn) {
|
|
357
|
+
if (admit_syn(p, from, now)) accept_inbound(p, from, now);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (p.type == rudp::PacketType::Reset) {
|
|
362
|
+
// A reset for a stream the sender never knew carries the id *it* was
|
|
363
|
+
// given, which is our send id rather than our recv id — see find_for_reset.
|
|
364
|
+
if (UdpStream* stream = find_for_reset(p.conn_id, from)) {
|
|
365
|
+
stream->on_packet(p, now);
|
|
366
|
+
stream_touched(*stream); // now dead, or a lingering one worth collecting
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Nothing here answers to that id: a stream we closed, or one left over from a
|
|
372
|
+
// previous run of this node. Say so, so the peer gives up now instead of
|
|
373
|
+
// retransmitting for a minute — but never more than a bounded rate, so a flood
|
|
374
|
+
// of garbage cannot turn this socket into a reflector.
|
|
375
|
+
if (spend_reply_budget(now)) send_reset(from, p.conn_id);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
bool UdpMux::admit_syn(const rudp::Packet& syn, const Address& from, Clock::time_point now) {
|
|
379
|
+
// Everything this function refuses is refused BEFORE a stream exists. That is
|
|
380
|
+
// the whole point: a Syn is sixteen bytes from an address nobody has verified,
|
|
381
|
+
// and answering it with a stream, a connection and a half-built Noise handshake
|
|
382
|
+
// is a trade an attacker with a forged source address would happily make a
|
|
383
|
+
// million times a second. TCP is spared this by its three-way handshake and by
|
|
384
|
+
// the file descriptor an accepted connection costs; here the two checks below
|
|
385
|
+
// stand in for both.
|
|
386
|
+
//
|
|
387
|
+
// The id the dialer listens on is one below the one it sent under, so that is
|
|
388
|
+
// where any answer has to go — the same pairing accept_inbound() relies on.
|
|
389
|
+
const uint32_t reply_id = syn.conn_id - 1;
|
|
390
|
+
|
|
391
|
+
if (streams_.size() >= limits_.max_streams) {
|
|
392
|
+
LOG_DEBUG("udp", "Refusing a dial from " << from.to_string() << ": at the stream ceiling ("
|
|
393
|
+
<< limits_.max_streams << ")");
|
|
394
|
+
if (spend_reply_budget(now)) send_reset(from, reply_id);
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (streams_.size() < limits_.validate_above) return true; // not under pressure
|
|
399
|
+
|
|
400
|
+
if (cookie_valid(from, syn.conn_id, syn.payload)) return true;
|
|
401
|
+
|
|
402
|
+
// Unproven. Hand back a cookie and keep nothing: if the address was forged, the
|
|
403
|
+
// cookie goes to whoever really lives there and this dial simply never happens.
|
|
404
|
+
if (spend_reply_budget(now))
|
|
405
|
+
send_retry(from, reply_id, cookie_for(from, syn.conn_id, cookie_secret_[0]));
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
void UdpMux::accept_inbound(const rudp::Packet& syn, const Address& from, Clock::time_point now) {
|
|
410
|
+
// Id pairing: the dialer picked `base`, keeps `base` as its recv id and sends
|
|
411
|
+
// under `base + 1`. So a Syn's conn_id is our recv id, and one less is what we
|
|
412
|
+
// must send under to reach the dialer. One random number, two ids, no
|
|
413
|
+
// negotiation round trip.
|
|
414
|
+
const uint32_t recv_id = syn.conn_id;
|
|
415
|
+
const uint32_t send_id = syn.conn_id - 1;
|
|
416
|
+
|
|
417
|
+
auto stream = std::make_unique<UdpStream>(*this, from, recv_id, send_id,
|
|
418
|
+
ConnRole::Inbound, now);
|
|
419
|
+
UdpStream* raw = stream.get();
|
|
420
|
+
streams_.emplace(recv_id, Entry{std::move(stream)});
|
|
421
|
+
|
|
422
|
+
// Adoption takes the link; refusing it destroys the link, which releases the
|
|
423
|
+
// stream — so `raw` must not be touched again on that path.
|
|
424
|
+
const ConnId id = delegate_.adopt_inbound_link(std::make_unique<UdpStreamLink>(*this, *raw));
|
|
425
|
+
if (id == kInvalidConnId) {
|
|
426
|
+
send_reset(from, send_id);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
LOG_DEBUG("udp", "Accepted stream " << recv_id << " from " << from.to_string());
|
|
431
|
+
raw->on_packet(syn, now); // acknowledges the Syn and opens the stream
|
|
432
|
+
stream_touched(*raw); // first deadline: the idle timer, at the very least
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
UdpStream* UdpMux::find_for_reset(uint32_t conn_id, const Address& from) {
|
|
436
|
+
// A reset generated for an unknown stream can only echo the id that was in the
|
|
437
|
+
// datagram that provoked it — which is our *send* id, not the recv id the map
|
|
438
|
+
// is keyed by. The two always differ by exactly one (see accept_inbound), so
|
|
439
|
+
// two probes find it, and both are validated against the send id and the peer
|
|
440
|
+
// address before anything is acted on.
|
|
441
|
+
for (const uint32_t candidate : {conn_id - 1, conn_id + 1}) {
|
|
442
|
+
auto it = streams_.find(candidate);
|
|
443
|
+
if (it == streams_.end()) continue;
|
|
444
|
+
UdpStream& stream = *it->second.stream;
|
|
445
|
+
if (stream.send_id() == conn_id && stream.remote() == from) return &stream;
|
|
446
|
+
}
|
|
447
|
+
return nullptr;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ── Outbound ────────────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
bool UdpMux::allocate_ids(uint32_t& recv_id, uint32_t& send_id) {
|
|
453
|
+
std::uniform_int_distribution<uint32_t> pick(1, 0xFFFFFFFEu); // keeps base+1 non-zero
|
|
454
|
+
for (int attempt = 0; attempt < 32; ++attempt) {
|
|
455
|
+
const uint32_t base = pick(rng_);
|
|
456
|
+
if (streams_.count(base) || streams_.count(base + 1)) continue;
|
|
457
|
+
recv_id = base;
|
|
458
|
+
send_id = base + 1;
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
return false; // 32 collisions in a 4-billion space: the map is implausibly full
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
std::unique_ptr<Link> UdpMux::connect(const Address& remote, DialProfile profile) {
|
|
465
|
+
uint32_t recv_id = 0, send_id = 0;
|
|
466
|
+
if (!allocate_ids(recv_id, send_id)) {
|
|
467
|
+
LOG_WARN("udp", "Could not allocate a connection id for " << remote.to_string());
|
|
468
|
+
return nullptr;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
auto stream = std::make_unique<UdpStream>(*this, remote, recv_id, send_id,
|
|
472
|
+
ConnRole::Outbound, Clock::now(), profile);
|
|
473
|
+
UdpStream* raw = stream.get();
|
|
474
|
+
const auto inserted = streams_.emplace(recv_id, Entry{std::move(stream)});
|
|
475
|
+
arm(recv_id, inserted.first->second); // the Syn's retransmission timeout
|
|
476
|
+
flush_output(); // the Syn is the dial; it does not wait for company
|
|
477
|
+
return std::make_unique<UdpStreamLink>(*this, *raw);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
481
|
+
|
|
482
|
+
void UdpMux::release(UdpStream& stream) {
|
|
483
|
+
auto it = streams_.find(stream.recv_id());
|
|
484
|
+
if (it == streams_.end() || it->second.stream.get() != &stream) return;
|
|
485
|
+
|
|
486
|
+
stream.set_conn_id(kInvalidConnId); // events have nowhere to go from here on
|
|
487
|
+
|
|
488
|
+
if (stream.connecting()) {
|
|
489
|
+
// A dial that was called off (the other transport won the race, or the
|
|
490
|
+
// node is going down). There is nothing to hand over, and a lingering Syn
|
|
491
|
+
// would keep asking a peer to open a stream nobody will read.
|
|
492
|
+
stream.abort(Clock::now());
|
|
493
|
+
streams_.erase(it);
|
|
494
|
+
} else if (stream.dead() || stream.flushed()) {
|
|
495
|
+
streams_.erase(it);
|
|
496
|
+
} else {
|
|
497
|
+
// Something is still owed to the peer. Keep the stream just long enough to
|
|
498
|
+
// hand it over — this is the user-space equivalent of the kernel continuing
|
|
499
|
+
// to retransmit after close(), and without it a node that sends a final
|
|
500
|
+
// message and disconnects would drop it on the floor.
|
|
501
|
+
it->second.linger_until = Clock::now() + kLingerTimeout;
|
|
502
|
+
arm(it->first, it->second); // the linger is a deadline like any other
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// The Connection told the link goodbye just before letting go of it, so a Fin
|
|
506
|
+
// or a Reset is usually staged by the time we get here. It must not outlive the
|
|
507
|
+
// call that produced it.
|
|
508
|
+
flush_output();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
void UdpMux::tick() {
|
|
512
|
+
// The whole point of the deadline heap: with nothing scheduled, or nothing yet
|
|
513
|
+
// due, this is one comparison — which is what makes it safe for the reactor to
|
|
514
|
+
// call on every turn of its loop instead of arming a timer for it.
|
|
515
|
+
if (due_.empty()) return;
|
|
516
|
+
const auto now = Clock::now();
|
|
517
|
+
if (due_.front().at > now) return;
|
|
518
|
+
|
|
519
|
+
rotate_cookie_secret(now);
|
|
520
|
+
|
|
521
|
+
// Only what has come due. Bounded by the slots that were already scheduled when
|
|
522
|
+
// this call began: servicing a stream re-arms it, and a re-arm that lands on
|
|
523
|
+
// "now" (a lingering stream that just flushed) would otherwise be picked up by
|
|
524
|
+
// this same loop. Deferring those to the next turn — which the reactor takes
|
|
525
|
+
// immediately, since next_timeout_ms() then returns 0 — keeps the loop finite
|
|
526
|
+
// by construction rather than by argument.
|
|
527
|
+
size_t budget = due_.size();
|
|
528
|
+
while (budget-- > 0 && !due_.empty() && due_.front().at <= now) {
|
|
529
|
+
const Due slot = due_.front();
|
|
530
|
+
std::pop_heap(due_.begin(), due_.end(), LaterFirst{});
|
|
531
|
+
due_.pop_back();
|
|
532
|
+
|
|
533
|
+
const auto it = streams_.find(slot.id);
|
|
534
|
+
if (it == streams_.end()) continue; // the stream went away; the slot is litter
|
|
535
|
+
Entry& entry = it->second;
|
|
536
|
+
// Superseded: a later arm() gave this stream an earlier slot, which has
|
|
537
|
+
// already been (or is about to be) serviced. Lazy deletion, so arm() never
|
|
538
|
+
// has to find and remove anything.
|
|
539
|
+
if (!entry.armed || entry.scheduled != slot.at) continue;
|
|
540
|
+
entry.armed = false;
|
|
541
|
+
|
|
542
|
+
UdpStream& stream = *entry.stream;
|
|
543
|
+
stream.tick(now);
|
|
544
|
+
|
|
545
|
+
const bool lingering = entry.linger_until != Clock::time_point{};
|
|
546
|
+
if (lingering && (stream.flushed() || stream.dead() || now >= entry.linger_until)) {
|
|
547
|
+
streams_.erase(it);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
arm(slot.id, entry);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
compact_due();
|
|
554
|
+
dispatch_pending();
|
|
555
|
+
flush_output(); // one syscall for every stream's retransmissions and keep-alives
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
void UdpMux::shutdown() {
|
|
559
|
+
const auto now = Clock::now();
|
|
560
|
+
for (auto& entry : streams_) entry.second.stream->abort(now);
|
|
561
|
+
flush_output(); // the goodbyes go out before the streams they came from
|
|
562
|
+
streams_.clear();
|
|
563
|
+
due_.clear();
|
|
564
|
+
pending_events_.clear();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
void UdpMux::dispatch_pending() {
|
|
568
|
+
if (pending_events_.empty()) return;
|
|
569
|
+
// Swap the batch out first: a handler may close its connection, which releases
|
|
570
|
+
// a stream and can record further events. Those are picked up by the next
|
|
571
|
+
// drain or tick rather than extending the loop we are inside.
|
|
572
|
+
auto batch = std::move(pending_events_);
|
|
573
|
+
pending_events_.clear();
|
|
574
|
+
for (const auto& [id, events] : batch) delegate_.dispatch_link_events(id, events);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// ── UdpStreamLink ───────────────────────────────────────────────────────────
|
|
578
|
+
|
|
579
|
+
Link::IoResult UdpStreamLink::read(ByteSpan into) {
|
|
580
|
+
const size_t n = stream_.read(into.data(), into.size());
|
|
581
|
+
// Draining the in-order buffer can re-open a receive window we had advertised
|
|
582
|
+
// as full, which the stream owes the peer an acknowledgement for *at once* —
|
|
583
|
+
// the earliest deadline there is. Re-arming here is what stops that ack from
|
|
584
|
+
// waiting on some unrelated timer.
|
|
585
|
+
if (n > 0) mux_.stream_touched(stream_);
|
|
586
|
+
|
|
587
|
+
if (n > 0) return {n, Status::Ok};
|
|
588
|
+
if (stream_.eof()) return {0, Status::Closed};
|
|
589
|
+
if (stream_.dead()) return {0, Status::Error};
|
|
590
|
+
return {0, Status::WouldBlock};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
Link::IoResult UdpStreamLink::write(const ByteView* slices, size_t count) {
|
|
594
|
+
if (stream_.dead()) return {0, Status::Error};
|
|
595
|
+
const size_t n = stream_.write(slices, count, UdpMux::Clock::now());
|
|
596
|
+
// Whatever went out started the retransmission timer. On a stream that was idle
|
|
597
|
+
// a moment ago that is a deadline hundreds of milliseconds out, against a
|
|
598
|
+
// keep-alive ten seconds out — so without this the first loss on a quiet stream
|
|
599
|
+
// would not be repaired until the keep-alive happened to wake it.
|
|
600
|
+
if (n > 0) mux_.stream_touched(stream_);
|
|
601
|
+
return {n, n > 0 ? Status::Ok : Status::WouldBlock};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
void UdpStreamLink::close(CloseReason reason) {
|
|
605
|
+
const auto now = UdpMux::Clock::now();
|
|
606
|
+
// An application-level close deserves an orderly one: queue a Fin behind
|
|
607
|
+
// whatever is still unsent, so the peer receives every byte and then a clean
|
|
608
|
+
// end of stream. Anything else — a protocol error, a duplicate connection, the
|
|
609
|
+
// node going down — has nothing worth flushing, so say so at once.
|
|
610
|
+
if (reason == CloseReason::LocalClose || reason == CloseReason::PeerClosed)
|
|
611
|
+
stream_.begin_close(now);
|
|
612
|
+
else
|
|
613
|
+
stream_.abort(now);
|
|
614
|
+
mux_.stream_touched(stream_);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
} // namespace librats
|