librats 2.1.4 → 2.3.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 +9 -0
- package/lib/index.d.ts +51 -6
- package/lib/index.js +42 -3
- package/native-src/CMakeLists.txt +13 -0
- package/native-src/src/librats/bindings/rats.cpp +90 -6
- package/native-src/src/librats/bindings/rats.h +92 -3
- package/native-src/src/librats/core/types.cpp +4 -3
- package/native-src/src/librats/core/types.h +25 -12
- package/native-src/src/librats/node/circuit_service.h +84 -0
- package/native-src/src/librats/node/config.h +9 -2
- package/native-src/src/librats/node/node.cpp +59 -5
- package/native-src/src/librats/node/node.h +38 -7
- package/native-src/src/librats/node/peer_network.h +19 -2
- package/native-src/src/librats/peer/peer.h +17 -6
- package/native-src/src/librats/peer/peer_table.cpp +41 -8
- package/native-src/src/librats/security/noise_security.cpp +2 -0
- package/native-src/src/librats/security/plaintext_security.h +1 -0
- package/native-src/src/librats/security/session.h +7 -0
- package/native-src/src/librats/storage/storage.cpp +434 -213
- package/native-src/src/librats/storage/storage.h +163 -19
- package/native-src/src/librats/subsystems/file_transfer.cpp +1 -1
- package/native-src/src/librats/subsystems/hole_punch.cpp +81 -8
- package/native-src/src/librats/subsystems/hole_punch.h +24 -0
- package/native-src/src/librats/subsystems/pubsub.cpp +1 -1
- package/native-src/src/librats/subsystems/reconnection.cpp +1 -1
- package/native-src/src/librats/subsystems/relay.cpp +1143 -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 +40 -8
- package/native-src/src/librats/transport/connection.h +12 -2
- package/native-src/src/librats/transport/reactor.cpp +56 -8
- package/native-src/src/librats/transport/reactor.h +38 -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/util/logger.h +37 -5
- package/native-src/src/librats/util/network_monitor.cpp +14 -0
- package/native-src/src/librats/util/network_utils.cpp +10 -1
- package/native-src/src/librats/wire/frame.h +1 -0
- package/package.json +1 -1
- package/src/librats_node.cpp +76 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file circuit_service.h
|
|
5
|
+
* @brief Turning a relayed byte stream into a peer connection — the capability a
|
|
6
|
+
* relay module needs and PeerNetwork deliberately does not offer.
|
|
7
|
+
*
|
|
8
|
+
* A relay module speaks a protocol (who forwards what to whom) and owns the byte
|
|
9
|
+
* stream that comes out of it (transport/relay_link.h). What it cannot do on its
|
|
10
|
+
* own is the one remaining step: making that stream a Connection, so the ordinary
|
|
11
|
+
* Noise handshake, framing, identify and peer table all run over it and the peer
|
|
12
|
+
* at the far end becomes an ordinary peer. That step needs the reactor pool, which
|
|
13
|
+
* a Subsystem has no business holding.
|
|
14
|
+
*
|
|
15
|
+
* So this is the narrow escape hatch, published by the Node in its ServiceRegistry
|
|
16
|
+
* next to DialService, and resolved by whoever needs it:
|
|
17
|
+
*
|
|
18
|
+
* if (auto* circuits = ctx.services.get<CircuitService>())
|
|
19
|
+
* auto route = circuits->adopt_circuit(relay_id, std::move(link),
|
|
20
|
+
* ConnRole::Outbound, false);
|
|
21
|
+
*
|
|
22
|
+
* ── One thread, chosen for you ──────────────────────────────────────────────
|
|
23
|
+
* The node places the circuit on the reactor that owns the CARRIER's connection,
|
|
24
|
+
* and that is the whole reason this call exists rather than a bare "adopt this
|
|
25
|
+
* link somewhere". Every byte of a circuit arrives on the carrier's connection and
|
|
26
|
+
* leaves through it, so putting the two on one thread means the entire relayed
|
|
27
|
+
* path is handled without a lock, a queue or a hand-off — the same shared-nothing
|
|
28
|
+
* property every other connection has. Any other placement would move bytes
|
|
29
|
+
* between reactor threads twice per message.
|
|
30
|
+
*
|
|
31
|
+
* The route comes back synchronously (the ConnId is reserved before the adoption
|
|
32
|
+
* is posted, exactly as Reactor::connect does), so the caller can wake and close
|
|
33
|
+
* the circuit from the moment it asks for one.
|
|
34
|
+
*
|
|
35
|
+
* ── What is deliberately NOT here ───────────────────────────────────────────
|
|
36
|
+
* Nothing about relaying: no relay selection, no protocol, no policy about when a
|
|
37
|
+
* circuit is worth opening. Those belong to the module. This interface would serve
|
|
38
|
+
* any transport that arrives from outside the reactor pool.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
#include "librats/core/types.h"
|
|
42
|
+
#include "librats/peer/peer_id.h"
|
|
43
|
+
#include "librats/peer/peer_table.h" // PeerRoute
|
|
44
|
+
#include "librats/transport/link.h"
|
|
45
|
+
|
|
46
|
+
#include <cstdint>
|
|
47
|
+
#include <memory>
|
|
48
|
+
#include <optional>
|
|
49
|
+
|
|
50
|
+
namespace librats {
|
|
51
|
+
|
|
52
|
+
class CircuitService {
|
|
53
|
+
public:
|
|
54
|
+
virtual ~CircuitService() = default;
|
|
55
|
+
|
|
56
|
+
/// Adopt `link` as a connection carried by the peer `carrier`, on the reactor
|
|
57
|
+
/// that owns the carrier's own connection.
|
|
58
|
+
///
|
|
59
|
+
/// @param role Outbound for a circuit we opened, Inbound for one opened to
|
|
60
|
+
/// us. It is what the secure handshake and the peer table's
|
|
61
|
+
/// duplicate resolution read, so it must say who asked.
|
|
62
|
+
/// @param connected whether the far end is already there. An inbound circuit is
|
|
63
|
+
/// (the opener would not have sent it otherwise); an outbound
|
|
64
|
+
/// one is not until its acceptance arrives, and until then the
|
|
65
|
+
/// connection waits in Connecting for a PollOut.
|
|
66
|
+
/// @return the new connection's route, or nullopt when the carrier is no longer
|
|
67
|
+
/// a peer, or when an inbound circuit would push this node past its peer
|
|
68
|
+
/// limit. Nothing is started in that case and `link` is released.
|
|
69
|
+
virtual std::optional<PeerRoute> adopt_circuit(const PeerId& carrier,
|
|
70
|
+
std::unique_ptr<Link> link,
|
|
71
|
+
ConnRole role, bool connected) = 0;
|
|
72
|
+
|
|
73
|
+
/// Deliver poll-equivalent events (PollIn / PollOut / PollErr, see
|
|
74
|
+
/// core/io_poller.h) to a circuit connection — the events a socket-backed link
|
|
75
|
+
/// would have got from the poller. Thread-safe, and a no-op for a route that is
|
|
76
|
+
/// already gone.
|
|
77
|
+
virtual void wake_circuit(PeerRoute route, uint32_t events) = 0;
|
|
78
|
+
|
|
79
|
+
/// Tear a circuit connection down. Thread-safe; a no-op for a route that is
|
|
80
|
+
/// already gone.
|
|
81
|
+
virtual void close_circuit(PeerRoute route, CloseReason reason) = 0;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
} // namespace librats
|
|
@@ -52,8 +52,15 @@ struct RATS_API NodeConfig {
|
|
|
52
52
|
/// connection. 0 disables the fallback: only the preferred transport is tried.
|
|
53
53
|
uint32_t transport_fallback_ms = 1200;
|
|
54
54
|
|
|
55
|
-
/// Bytes a peer's send queue may hold before
|
|
56
|
-
/// consumer. 0 uses the library
|
|
55
|
+
/// Bytes a peer's send queue may hold before an application that keeps
|
|
56
|
+
/// sending anyway has the peer dropped as a slow consumer. 0 uses the library
|
|
57
|
+
/// default (8 MiB).
|
|
58
|
+
///
|
|
59
|
+
/// It is not a maximum message size. A single message is always queued
|
|
60
|
+
/// whatever its size — a message cannot be sent by halves, and a healthy
|
|
61
|
+
/// connection must not die over one large frame — so the queue's real ceiling
|
|
62
|
+
/// is this plus one message. What gets a peer dropped is offering *another*
|
|
63
|
+
/// message while the queue is still over the mark.
|
|
57
64
|
///
|
|
58
65
|
/// A quarter of this is the mark at which send() starts answering "no room"
|
|
59
66
|
/// and on_peer_writable is what says the room is back — so lowering it makes
|
|
@@ -94,6 +94,7 @@ Node::Node(NodeConfig config)
|
|
|
94
94
|
// are narrow capabilities a module asks for by interface (see
|
|
95
95
|
// node/dial_service.h, node/nat_status.h).
|
|
96
96
|
services_.provide<DialService>(this);
|
|
97
|
+
services_.provide<CircuitService>(this);
|
|
97
98
|
services_.provide<ExternalAddressService>(&nat_status_);
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -402,6 +403,9 @@ bool Node::dial_direct(const Address& addr, TransportKind kind, const DialProfil
|
|
|
402
403
|
if (!addr.is_valid()) return false;
|
|
403
404
|
if (kind == TransportKind::Udp && !reactors_->has_udp()) return false;
|
|
404
405
|
if (kind == TransportKind::Tcp && !config_.enable_tcp) return false;
|
|
406
|
+
// A relayed circuit is not dialed: it is negotiated with a third node and then
|
|
407
|
+
// adopted (see node/circuit_service.h). There is no address to aim at here.
|
|
408
|
+
if (kind == TransportKind::Relay) return false;
|
|
405
409
|
|
|
406
410
|
// Straight to the reactor, deliberately around the Dialer: this dial is not a
|
|
407
411
|
// race and must not become one (see node/dial_service.h). The Dialer is left
|
|
@@ -412,6 +416,41 @@ bool Node::dial_direct(const Address& addr, TransportKind kind, const DialProfil
|
|
|
412
416
|
return id != kInvalidConnId;
|
|
413
417
|
}
|
|
414
418
|
|
|
419
|
+
// ── CircuitService: a relayed byte stream becomes a peer connection ─────────
|
|
420
|
+
|
|
421
|
+
std::optional<PeerRoute> Node::adopt_circuit(const PeerId& carrier, std::unique_ptr<Link> link,
|
|
422
|
+
ConnRole role, bool connected) {
|
|
423
|
+
if (!link) return std::nullopt;
|
|
424
|
+
|
|
425
|
+
// The carrier has to still be a peer: it is the only thing this circuit's bytes
|
|
426
|
+
// can travel through, and a connection whose carrier is already gone would
|
|
427
|
+
// simply sit there until the establish deadline reaped it.
|
|
428
|
+
const auto carrier_route = peers_.route(carrier);
|
|
429
|
+
if (!carrier_route) return std::nullopt;
|
|
430
|
+
|
|
431
|
+
// Inbound circuits go through the same admission gate as an accepted socket.
|
|
432
|
+
// A relayed connection is cheaper for whoever opens it than a direct one — no
|
|
433
|
+
// NAT to get through, someone else's bandwidth — so if anything it deserves the
|
|
434
|
+
// gate more, not less.
|
|
435
|
+
if (role == ConnRole::Inbound && !admit_inbound()) return std::nullopt;
|
|
436
|
+
|
|
437
|
+
Reactor& reactor = reactors_->by_index(carrier_route->reactor);
|
|
438
|
+
// Same reactor as the carrier, which is the whole point: every byte of this
|
|
439
|
+
// circuit arrives on the carrier's connection and leaves through it, so the two
|
|
440
|
+
// share a thread and the relayed path needs no locks and no hand-offs.
|
|
441
|
+
const ConnId id = reactor.reserve_conn_id();
|
|
442
|
+
reactor.adopt_link(id, std::move(link), role, connected);
|
|
443
|
+
return PeerRoute{carrier_route->reactor, id};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
void Node::wake_circuit(PeerRoute route, uint32_t events) {
|
|
447
|
+
reactors_->by_index(route.reactor).wake(route.conn, events);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
void Node::close_circuit(PeerRoute route, CloseReason reason) {
|
|
451
|
+
reactors_->by_index(route.reactor).close(route.conn, reason);
|
|
452
|
+
}
|
|
453
|
+
|
|
415
454
|
void Node::report_dial_failed(const std::string& host, uint16_t port) {
|
|
416
455
|
// Only a numeric target can be handed on as an Address; a dial by hostname has
|
|
417
456
|
// no dialable Address to report (peers cannot use our resolver), and the
|
|
@@ -449,7 +488,15 @@ bool Node::send(const PeerId& to, std::string_view channel, ByteView payload) {
|
|
|
449
488
|
|
|
450
489
|
bool Node::peer_writable(const PeerId& id) const {
|
|
451
490
|
const auto dest = peers_.destination(id);
|
|
452
|
-
|
|
491
|
+
if (!dest) return false;
|
|
492
|
+
// Both halves of the backlog, exactly as send() weighs them: what the reactor
|
|
493
|
+
// has already queued (`writable`) *and* what a caller has handed over that the
|
|
494
|
+
// reactor has not taken up yet (`owed`). Reporting only the first would answer
|
|
495
|
+
// "there is room" to a caller that has just filled the queue itself and is
|
|
496
|
+
// waiting to hear otherwise — the queue it filled has not been looked at yet,
|
|
497
|
+
// so nothing about it has changed, and no event is coming either.
|
|
498
|
+
return dest->writable &&
|
|
499
|
+
dest->owed->load(std::memory_order_relaxed) <= send_low_water();
|
|
453
500
|
}
|
|
454
501
|
|
|
455
502
|
bool Node::broadcast(std::string_view channel, ByteView payload) {
|
|
@@ -643,7 +690,7 @@ void Node::on_closed(Connection& conn, CloseReason reason) {
|
|
|
643
690
|
// let a long-gone peer keep voting on how our NAT behaves.
|
|
644
691
|
nat_status_.forget(id);
|
|
645
692
|
LOG_INFO("node", "Peer " << id.short_hex() << " disconnected (" << to_string(reason) << ")");
|
|
646
|
-
for (auto& cb : peer_disconnected_) cb(id);
|
|
693
|
+
for (auto& cb : peer_disconnected_) cb(id, reason);
|
|
647
694
|
}
|
|
648
695
|
|
|
649
696
|
void Node::on_writable_changed(Connection& conn, bool writable) {
|
|
@@ -810,9 +857,16 @@ std::vector<Address> Node::observed_addresses() const {
|
|
|
810
857
|
|
|
811
858
|
// ── Peer handle methods (defined here for the full Node type) ────────────────
|
|
812
859
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
860
|
+
bool Peer::send(std::string_view channel, ByteView payload) const {
|
|
861
|
+
// Deliberately by id, not by this handle's route. The route existed to skip the
|
|
862
|
+
// directory lookup on the reply path — but answering "is there room?" needs the
|
|
863
|
+
// peer's writability and its in-transit counter, both of which live in the
|
|
864
|
+
// directory, so the lookup is unavoidable the moment send() has an answer to
|
|
865
|
+
// give. Once it is being paid anyway, the captured route buys nothing and can
|
|
866
|
+
// only be wrong: a handle outlives its connection (a relayed link superseded by
|
|
867
|
+
// a direct one, a dial race, a reconnect), and sending on the superseded one
|
|
868
|
+
// drops the message with nothing said. The peer is what the caller means.
|
|
869
|
+
return node_->send(id_, channel, payload);
|
|
816
870
|
}
|
|
817
871
|
|
|
818
872
|
void Peer::disconnect() const {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
#include "librats/peer/peer_info.h"
|
|
49
49
|
#include "librats/security/identity.h"
|
|
50
50
|
#include "librats/security/handshaker.h" // SecurityProvider
|
|
51
|
+
#include "librats/node/circuit_service.h"
|
|
51
52
|
#include "librats/node/config.h"
|
|
52
53
|
#include "librats/node/dial_service.h"
|
|
53
54
|
#include "librats/node/dialer.h"
|
|
@@ -74,7 +75,10 @@ namespace librats {
|
|
|
74
75
|
class NetworkMonitor; // util/network_monitor.h — owned via unique_ptr, included in node.cpp
|
|
75
76
|
class MessageJson; // subsystems/message_json.h — reached via json() (json.h stays out of node.h)
|
|
76
77
|
|
|
77
|
-
class RATS_API Node final : public ConnectionDelegate,
|
|
78
|
+
class RATS_API Node final : public ConnectionDelegate,
|
|
79
|
+
public PeerNetwork,
|
|
80
|
+
public DialService,
|
|
81
|
+
public CircuitService {
|
|
78
82
|
public:
|
|
79
83
|
/// Construct a node from its configuration (see NodeConfig). This only loads
|
|
80
84
|
/// the identity and prepares the layers; no socket is opened until start().
|
|
@@ -163,6 +167,13 @@ public:
|
|
|
163
167
|
bool dial_direct(const Address& addr, TransportKind kind,
|
|
164
168
|
const DialProfile& profile) override;
|
|
165
169
|
|
|
170
|
+
// — CircuitService: make a relayed byte stream an ordinary peer connection
|
|
171
|
+
// (see node/circuit_service.h; used by the relay module) —
|
|
172
|
+
std::optional<PeerRoute> adopt_circuit(const PeerId& carrier, std::unique_ptr<Link> link,
|
|
173
|
+
ConnRole role, bool connected) override;
|
|
174
|
+
void wake_circuit(PeerRoute route, uint32_t events) override;
|
|
175
|
+
void close_circuit(PeerRoute route, CloseReason reason) override;
|
|
176
|
+
|
|
166
177
|
// — peer admission limit (0 = unlimited; guards inbound, not our own dials) —
|
|
167
178
|
size_t max_peers() const noexcept { return max_peers_.load(std::memory_order_relaxed); }
|
|
168
179
|
void set_max_peers(size_t n) noexcept { max_peers_.store(n, std::memory_order_relaxed); }
|
|
@@ -182,6 +193,14 @@ public:
|
|
|
182
193
|
/// low-water mark, and an application that keeps going regardless
|
|
183
194
|
/// will eventually have the peer dropped as a slow consumer. Wait for
|
|
184
195
|
/// on_peer_writable instead. Also false if the peer is not connected.
|
|
196
|
+
///
|
|
197
|
+
/// "Stop" is meant literally, and yielding is part of it: the mark is
|
|
198
|
+
/// re-tested inside the reactor task this call hands off to, and it is
|
|
199
|
+
/// that test which flips the peer to unwritable and later raises
|
|
200
|
+
/// on_peer_writable. A caller that answers a false by looping straight
|
|
201
|
+
/// back into send() therefore starves the very thread that would tell
|
|
202
|
+
/// it to stop — the queue keeps growing while the signal it is waiting
|
|
203
|
+
/// for never gets a turn to be produced.
|
|
185
204
|
bool send(const PeerId& to, std::string_view channel, ByteView payload);
|
|
186
205
|
/// Send raw bytes on a named channel to every connected peer.
|
|
187
206
|
/// @return whether *every* one of them still has room — a fan-out can only
|
|
@@ -194,12 +213,24 @@ public:
|
|
|
194
213
|
/// transfer, a large stream — address peers individually with send().
|
|
195
214
|
bool broadcast(std::string_view channel, ByteView payload);
|
|
196
215
|
|
|
197
|
-
/// Whether a peer's send queue
|
|
198
|
-
///
|
|
199
|
-
///
|
|
200
|
-
///
|
|
201
|
-
///
|
|
202
|
-
|
|
216
|
+
/// Whether a peer's send queue has room for more. False for a peer that is
|
|
217
|
+
/// not connected.
|
|
218
|
+
///
|
|
219
|
+
/// The same question send() answers, asked without sending anything: it
|
|
220
|
+
/// weighs both halves of what the peer is carrying — the bytes the reactor
|
|
221
|
+
/// has queued, and the bytes a caller has handed to send() that the reactor
|
|
222
|
+
/// has not taken up yet. So it is safe to poll: a caller that has just filled
|
|
223
|
+
/// the queue in a tight loop keeps being told "no room" until the reactor has
|
|
224
|
+
/// actually looked at what it was given, rather than being told "go on"
|
|
225
|
+
/// because nothing observable has changed yet.
|
|
226
|
+
///
|
|
227
|
+
/// It stays a hint about a queue that drains as it is read, so the ordinary
|
|
228
|
+
/// flow is unchanged: the signal to stop is the return of send(), and the
|
|
229
|
+
/// signal to resume is on_peer_writable. This is for a caller that must wait
|
|
230
|
+
/// for room on a thread of its own — the event alone cannot serve it, because
|
|
231
|
+
/// a queue that filled *only* with bytes still in transit never crossed
|
|
232
|
+
/// anything on the connection and so raises no event when they drain.
|
|
233
|
+
bool peer_writable(const PeerId& id) const override;
|
|
203
234
|
|
|
204
235
|
// — events (register before start(); invoked on a reactor thread). Multiple
|
|
205
236
|
// listeners are supported, so subsystems and the app can both subscribe. —
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
#include "librats/peer/peer_id.h"
|
|
18
18
|
#include "librats/peer/peer_info.h"
|
|
19
19
|
#include "librats/core/address.h"
|
|
20
|
+
#include "librats/core/types.h" // CloseReason
|
|
20
21
|
|
|
21
22
|
#include <cstdint>
|
|
22
23
|
#include <functional>
|
|
@@ -33,7 +34,12 @@ public:
|
|
|
33
34
|
using MessageHandler = std::function<void(const Peer&, ByteView)>;
|
|
34
35
|
|
|
35
36
|
using PeerEventHandler = std::function<void(const Peer&)>;
|
|
36
|
-
|
|
37
|
+
/// A peer went away, and why. The reason matters as much as the event: an
|
|
38
|
+
/// application dropped as a slow consumer (CloseReason::SlowConsumer) has to
|
|
39
|
+
/// slow down, while one whose peer simply left should reconnect — and without
|
|
40
|
+
/// the reason those look identical, so the usual answer to both is to redial
|
|
41
|
+
/// and repeat whatever caused it.
|
|
42
|
+
using PeerDisconnectHandler = std::function<void(const PeerId&, CloseReason)>;
|
|
37
43
|
using DialFailedHandler = std::function<void(const Address&)>;
|
|
38
44
|
|
|
39
45
|
virtual const PeerId& local_id() const = 0;
|
|
@@ -49,7 +55,9 @@ public:
|
|
|
49
55
|
/// Send to one peer. @return whether that peer's send queue still has room;
|
|
50
56
|
/// false means "stop and wait for on_peer_writable" — the message is queued
|
|
51
57
|
/// either way, but continuing past this is what gets a peer dropped as a slow
|
|
52
|
-
/// consumer. Also false if the peer is not connected.
|
|
58
|
+
/// consumer. Also false if the peer is not connected. Wait on the event and
|
|
59
|
+
/// not on a poll of the queue's state: the answer is re-derived on the reactor
|
|
60
|
+
/// thread, so a caller that spins instead of yielding never lets it change.
|
|
53
61
|
virtual bool send(const PeerId& to, MessageType type, ByteView payload) = 0;
|
|
54
62
|
/// Send to every connected peer. @return whether *every* one of them still
|
|
55
63
|
/// has room, so a subsystem that fans out can pause on the slowest.
|
|
@@ -72,6 +80,15 @@ public:
|
|
|
72
80
|
/// subsystem that never checks that return never needs this either. Runs on a
|
|
73
81
|
/// reactor thread. Default: not offered (nothing subscribes).
|
|
74
82
|
virtual void on_peer_writable(PeerEventHandler /*handler*/) {}
|
|
83
|
+
/// The same question send() answers — "has this peer room for more?" — asked
|
|
84
|
+
/// without sending anything. For a subsystem that paces a long fan-out from a
|
|
85
|
+
/// thread of its own (StorageManager streaming a snapshot): the event alone
|
|
86
|
+
/// cannot serve it, because a queue filled *only* with bytes handed to send()
|
|
87
|
+
/// that the reactor has not taken up yet never crossed anything on the
|
|
88
|
+
/// connection, so nothing raises on_peer_writable when they drain. Safe to
|
|
89
|
+
/// poll — it weighs those in-flight bytes too. False for an unknown peer.
|
|
90
|
+
/// Default: always writable, all a mock that merely moves messages can claim.
|
|
91
|
+
virtual bool peer_writable(const PeerId& /*id*/) const { return true; }
|
|
75
92
|
};
|
|
76
93
|
|
|
77
94
|
struct NodeContext; // node/node_context.h — bundles network + events + services
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* @file peer.h
|
|
5
5
|
* @brief A lightweight handle to a connected peer.
|
|
6
6
|
*
|
|
7
|
-
* Peer is a value passed to callbacks. It carries the peer's id and its
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* consult the directory
|
|
7
|
+
* Peer is a value passed to callbacks. It carries the peer's id and its route
|
|
8
|
+
* (which reactor + connection). disconnect() uses the route, reaching that exact
|
|
9
|
+
* connection with no directory lookup. send() goes by id instead: it has to
|
|
10
|
+
* consult the directory anyway for the backpressure answer it returns, and by
|
|
11
|
+
* then the peer's *current* route is the better destination — a handle can
|
|
12
|
+
* outlive the link it names. info() consults the directory on demand too.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
#include "librats/util/rats_export.h"
|
|
@@ -27,8 +29,17 @@ class RATS_API Peer {
|
|
|
27
29
|
public:
|
|
28
30
|
const PeerId& id() const noexcept { return id_; }
|
|
29
31
|
|
|
30
|
-
/// Send bytes on a named application channel
|
|
31
|
-
|
|
32
|
+
/// Send bytes on a named application channel, to this peer over whichever
|
|
33
|
+
/// connection currently serves it.
|
|
34
|
+
///
|
|
35
|
+
/// @return whether that peer's queue still has room — the same answer, and the
|
|
36
|
+
/// same contract, as Node::send(): false means stop and wait for
|
|
37
|
+
/// on_peer_writable rather than keep going. A handler that replies
|
|
38
|
+
/// through this handle is the most ordinary way to write to a peer, so
|
|
39
|
+
/// it has to be able to feel backpressure like any other sender; while
|
|
40
|
+
/// this returned void it could not, and the bytes it queued were
|
|
41
|
+
/// invisible to peer_writable() into the bargain.
|
|
42
|
+
bool send(std::string_view channel, ByteView payload) const;
|
|
32
43
|
|
|
33
44
|
/// Request this peer be disconnected.
|
|
34
45
|
void disconnect() const;
|
|
@@ -5,6 +5,30 @@
|
|
|
5
5
|
|
|
6
6
|
namespace librats {
|
|
7
7
|
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
/// How much a transport is worth when two connections to the same peer have to be
|
|
11
|
+
/// ranked. Both ends see the same pair of transports and apply the same order, so
|
|
12
|
+
/// they converge on the same survivor without exchanging a word.
|
|
13
|
+
///
|
|
14
|
+
/// Udp — one socket, one NAT mapping, a source port that can be dialed back
|
|
15
|
+
/// (see dialer.h). The best link there is.
|
|
16
|
+
/// Tcp — a direct link all the same, just a less useful one to a NAT.
|
|
17
|
+
/// Relay — not a direct link at all: every byte costs a third node bandwidth and
|
|
18
|
+
/// a round trip. Ranked last here for completeness; in practice a relayed
|
|
19
|
+
/// link is settled before this by the rule in add(), which lets ANY direct
|
|
20
|
+
/// link beat it regardless of role.
|
|
21
|
+
int transport_rank(TransportKind t) noexcept {
|
|
22
|
+
switch (t) {
|
|
23
|
+
case TransportKind::Udp: return 2;
|
|
24
|
+
case TransportKind::Tcp: return 1;
|
|
25
|
+
case TransportKind::Relay: return 0;
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
} // namespace
|
|
31
|
+
|
|
8
32
|
PeerTable::AddOutcome PeerTable::add(const PeerInfo& info, PeerRoute route,
|
|
9
33
|
bool prefer_outbound) {
|
|
10
34
|
std::unique_lock<std::shared_mutex> lock(mutex_);
|
|
@@ -26,23 +50,32 @@ PeerTable::AddOutcome PeerTable::add(const PeerInfo& info, PeerRoute route,
|
|
|
26
50
|
// first" gets a different answer at each end, and two ends that each keep the
|
|
27
51
|
// link the other just dropped are left with no link at all.
|
|
28
52
|
//
|
|
53
|
+
// one relayed, one not ⇒ not a race at all but an upgrade (or a late relay
|
|
54
|
+
// arriving under a direct link): the direct one wins,
|
|
55
|
+
// whatever the roles. Ranked FIRST, ahead of the role
|
|
56
|
+
// rule, because a relayed link that won on role would
|
|
57
|
+
// keep costing a third node bandwidth for the life of a
|
|
58
|
+
// peer we can reach ourselves. Both ends see the same
|
|
59
|
+
// pair of transports, so both reach this same verdict.
|
|
29
60
|
// opposite roles ⇒ cross-connect: keep the link started by the smaller id
|
|
30
61
|
// (prefer_outbound, opposite at each end by construction).
|
|
31
62
|
// different wires ⇒ a dial race whose attempts both got through. Both ends
|
|
32
|
-
// see the transports
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
// UDP" ranks the pair only because there are exactly two
|
|
36
|
-
// wires; a third would need an explicit order, or the two
|
|
37
|
-
// ends could rank the same pair differently.
|
|
63
|
+
// see the transports and rank them by the same explicit
|
|
64
|
+
// order (transport_rank above), so neither can rank the
|
|
65
|
+
// same pair differently.
|
|
38
66
|
// same role and wire ⇒ a reconnect, not a simultaneous pair: the old link is
|
|
39
67
|
// stale or already dead, so the newcomer is the live one.
|
|
68
|
+
const bool cur_relayed = cur.info.transport == TransportKind::Relay;
|
|
69
|
+
const bool new_relayed = info.transport == TransportKind::Relay;
|
|
70
|
+
|
|
40
71
|
bool keep_new = true;
|
|
41
|
-
if (
|
|
72
|
+
if (cur_relayed != new_relayed) {
|
|
73
|
+
keep_new = !new_relayed;
|
|
74
|
+
} else if (cur.info.direction != info.direction) {
|
|
42
75
|
const ConnRole survivor = prefer_outbound ? ConnRole::Outbound : ConnRole::Inbound;
|
|
43
76
|
keep_new = (info.direction == survivor);
|
|
44
77
|
} else if (cur.info.transport != info.transport) {
|
|
45
|
-
keep_new = (info.transport
|
|
78
|
+
keep_new = transport_rank(info.transport) > transport_rank(cur.info.transport);
|
|
46
79
|
}
|
|
47
80
|
|
|
48
81
|
if (keep_new) {
|
|
@@ -32,6 +32,7 @@ class PlaintextSession final : public Session {
|
|
|
32
32
|
public:
|
|
33
33
|
explicit PlaintextSession(PeerId remote) : remote_id_(remote) {}
|
|
34
34
|
bool encrypt(ByteView plain, Bytes& out) override { out.assign(plain.begin(), plain.end()); return true; }
|
|
35
|
+
size_t overhead() const noexcept override { return 0; }
|
|
35
36
|
bool decrypt(ByteView cipher, Bytes& out) override { out.assign(cipher.begin(), cipher.end()); return true; }
|
|
36
37
|
const PeerId& remote_id() const override { return remote_id_; }
|
|
37
38
|
bool is_secure() const override { return false; }
|
|
@@ -30,6 +30,13 @@ public:
|
|
|
30
30
|
/// The remote peer's identity, proven during the handshake.
|
|
31
31
|
virtual const PeerId& remote_id() const = 0;
|
|
32
32
|
|
|
33
|
+
/// Bytes encrypt() adds to a plaintext of any size (an AEAD tag, typically).
|
|
34
|
+
/// The send path needs the ciphertext's size *before* encrypting it, because
|
|
35
|
+
/// a message it turns out it cannot frame must be refused without a nonce
|
|
36
|
+
/// having been spent on it — the counters run in lockstep at both ends, so a
|
|
37
|
+
/// message encrypted and then not sent would break every one after it.
|
|
38
|
+
virtual size_t overhead() const noexcept = 0;
|
|
39
|
+
|
|
33
40
|
/// True if traffic is actually encrypted (false for the plaintext passthrough).
|
|
34
41
|
virtual bool is_secure() const = 0;
|
|
35
42
|
};
|