librats 2.1.4 → 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 +9 -0
- package/lib/index.d.ts +21 -4
- package/lib/index.js +26 -2
- package/native-src/CMakeLists.txt +8 -0
- package/native-src/src/librats/bindings/rats.cpp +34 -3
- package/native-src/src/librats/bindings/rats.h +30 -2
- package/native-src/src/librats/core/types.cpp +3 -2
- package/native-src/src/librats/core/types.h +16 -6
- package/native-src/src/librats/node/circuit_service.h +84 -0
- package/native-src/src/librats/node/node.cpp +39 -0
- package/native-src/src/librats/node/node.h +12 -1
- package/native-src/src/librats/peer/peer_table.cpp +41 -8
- 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/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/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/wire/frame.h +1 -0
- package/package.json +1 -1
- package/src/librats_node.cpp +26 -0
package/README.md
CHANGED
|
@@ -112,12 +112,20 @@ fails. Both are opt-in, and punching needs peers that relay the rendezvous.
|
|
|
112
112
|
```javascript
|
|
113
113
|
node.enablePortMapping(); // UPnP IGD + NAT-PMP
|
|
114
114
|
node.enableHolePunch(true); // true = also relay other peers' rendezvous
|
|
115
|
+
node.enableRelay(false); // last resort; true = also carry others' connections
|
|
115
116
|
node.start();
|
|
116
117
|
|
|
117
118
|
node.punchPeer(peerId); // success arrives as onPeerConnected
|
|
118
119
|
console.log(node.natMapping); // NatMapping.ENDPOINT_INDEPENDENT ⇒ punchable
|
|
119
120
|
```
|
|
120
121
|
|
|
122
|
+
A symmetric NAT cannot be punched at all, and that is what `enableRelay` is for: the
|
|
123
|
+
connection itself is routed through a node both ends already reach. It stays
|
|
124
|
+
encrypted end to end — the relay moves ciphertext — and the peer behaves like any
|
|
125
|
+
other, except that `peerTransport(peerId)` reports `Transport.RELAY`. With both
|
|
126
|
+
enabled the ladder runs itself: a punch that cannot work falls back to a relay, and
|
|
127
|
+
a relayed peer keeps trying to become a direct one.
|
|
128
|
+
|
|
121
129
|
## API
|
|
122
130
|
|
|
123
131
|
### Construction
|
|
@@ -170,6 +178,7 @@ new RatsNode(config) // full config
|
|
|
170
178
|
| mDNS discovery | `enableMdns()` | — |
|
|
171
179
|
| NAT port mapping | `enablePortMapping(upnp?, natpmp?)` | — |
|
|
172
180
|
| Hole punching | `enableHolePunch(serveAsRelay?)` | `punchPeer(peerId)`, `natMapping` |
|
|
181
|
+
| Relaying | `enableRelay(serveAsRelay?)` | `connectViaRelay(peerId)` |
|
|
173
182
|
| Pub/sub | `enablePubsub()` | `subscribe(topic, cb)`, `unsubscribe(topic)`, `publish(topic, data)` |
|
|
174
183
|
| Typed JSON | `enableJson()` | `onJson(type, cb)`, `onceJson(type, cb)`, `offJson(type)`, `sendJson(peerId, type, value)`, `broadcastJson(type, value)` |
|
|
175
184
|
| File transfer | `enableFileTransfer(tempDir?)` | `onFileOffer/onFileProgress/onFileComplete`, `sendFile`, `sendDirectory`, `acceptFile`, `rejectFile`, `cancelFile`, `pauseFile`, `resumeFile` |
|
package/lib/index.d.ts
CHANGED
|
@@ -19,15 +19,18 @@ declare module 'librats' {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Which wire a peer connection runs on.
|
|
23
|
-
* the identical encrypted handshake
|
|
24
|
-
* reliable byte stream underneath is obtained
|
|
22
|
+
* Which wire a peer connection runs on. TCP and UDP carry the identical protocol
|
|
23
|
+
* and the identical encrypted handshake, and differ only in how the ordered,
|
|
24
|
+
* reliable byte stream underneath is obtained; RELAY is that same stream one hop
|
|
25
|
+
* further away, out of another peer's connection rather than out of a socket.
|
|
25
26
|
*/
|
|
26
27
|
export const Transport: {
|
|
27
28
|
/** One kernel socket per peer. */
|
|
28
29
|
readonly TCP: 0;
|
|
29
30
|
/** Reliable stream over the shared UDP socket. */
|
|
30
31
|
readonly UDP: 1;
|
|
32
|
+
/** Carried through a third node (see `enableRelay`). */
|
|
33
|
+
readonly RELAY: 2;
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
/** Bitmask flags used by `node.transports` and `node.peerTransports()`. */
|
|
@@ -57,7 +60,10 @@ declare module 'librats' {
|
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
export type SecurityValue = 0 | 1;
|
|
63
|
+
/** A wire a dial can choose: TCP or UDP. A relay is never dialed. */
|
|
60
64
|
export type TransportValue = 0 | 1;
|
|
65
|
+
/** What a connected peer's link actually runs on, relays included. */
|
|
66
|
+
export type PeerTransportValue = TransportValue | 2;
|
|
61
67
|
export type NatMappingValue = 0 | 1 | 2 | 3;
|
|
62
68
|
export type LogLevelValue = 0 | 1 | 2 | 3;
|
|
63
69
|
|
|
@@ -165,7 +171,7 @@ declare module 'librats' {
|
|
|
165
171
|
/** Cap on established peers (0 = unlimited). Settable at any time. */
|
|
166
172
|
maxPeers: number;
|
|
167
173
|
/** Which wire a connected peer's link runs on, or `null` if not connected. */
|
|
168
|
-
peerTransport(peerId: string):
|
|
174
|
+
peerTransport(peerId: string): PeerTransportValue | null;
|
|
169
175
|
/**
|
|
170
176
|
* Transports a connected peer advertised, as a `TransportMask` bitmask, or
|
|
171
177
|
* `null` if not connected. 0 means the peer did not say (an older build).
|
|
@@ -207,6 +213,17 @@ declare module 'librats' {
|
|
|
207
213
|
* ordinary `onPeerConnected`.
|
|
208
214
|
*/
|
|
209
215
|
punchPeer(peerId: string): void;
|
|
216
|
+
/**
|
|
217
|
+
* Enable relaying: reach a peer nothing else could, through a node both ends
|
|
218
|
+
* are already connected to. `serveAsRelay` (default `false`) also carries
|
|
219
|
+
* OTHER peers' connections, which spends real bandwidth.
|
|
220
|
+
*/
|
|
221
|
+
enableRelay(serveAsRelay?: boolean): void;
|
|
222
|
+
/**
|
|
223
|
+
* Try to reach a peer through a relay. Non-blocking: success arrives as an
|
|
224
|
+
* ordinary `onPeerConnected`.
|
|
225
|
+
*/
|
|
226
|
+
connectViaRelay(peerId: string): void;
|
|
210
227
|
/** A `NatMapping` value describing this node's own NAT. */
|
|
211
228
|
readonly natMapping: NatMappingValue;
|
|
212
229
|
|
package/lib/index.js
CHANGED
|
@@ -59,8 +59,9 @@ const Security = Object.freeze({
|
|
|
59
59
|
* how the ordered, reliable byte stream underneath is obtained.
|
|
60
60
|
*/
|
|
61
61
|
const Transport = Object.freeze({
|
|
62
|
-
TCP: 0,
|
|
63
|
-
UDP: 1,
|
|
62
|
+
TCP: 0, // one kernel socket per peer
|
|
63
|
+
UDP: 1, // reliable stream over the shared UDP socket
|
|
64
|
+
RELAY: 2, // carried through a third node (see enableRelay)
|
|
64
65
|
});
|
|
65
66
|
|
|
66
67
|
/** Bitmask flags used by `node.transports` and `node.peerTransports()`. */
|
|
@@ -250,6 +251,29 @@ class RatsNode {
|
|
|
250
251
|
*/
|
|
251
252
|
punchPeer(peerId) { this._native.punchPeer(peerId); }
|
|
252
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Enable relaying: reach a peer that neither port forwarding nor hole punching
|
|
256
|
+
* could make reachable, by routing the connection through a node both ends are
|
|
257
|
+
* already connected to. The peer that comes out is ordinary in every way — the
|
|
258
|
+
* same end-to-end encryption, the same channels — except that `peerTransport()`
|
|
259
|
+
* reports it as `Transport.RELAY`.
|
|
260
|
+
* @param {boolean} [serveAsRelay=false] also carry OTHER peers' connections.
|
|
261
|
+
* Unlike a hole-punch rendezvous this spends real bandwidth on somebody else's
|
|
262
|
+
* traffic, so it is off by default; a mesh in which nobody serves cannot relay.
|
|
263
|
+
*/
|
|
264
|
+
enableRelay(serveAsRelay = false) { this._native.enableRelay(serveAsRelay); }
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Try to reach a peer through a relay. Non-blocking: success arrives as an
|
|
268
|
+
* ordinary `onPeerConnected`. Throws `NO_SUCH_PEER` when there is nothing to do
|
|
269
|
+
* or nothing to try with — already connected, an attempt already running, in
|
|
270
|
+
* cooldown, or no peer that could carry the connection. Usually unnecessary:
|
|
271
|
+
* with hole punching enabled too, a punch that cannot work hands the target over
|
|
272
|
+
* by itself.
|
|
273
|
+
* @param {string} peerId
|
|
274
|
+
*/
|
|
275
|
+
connectViaRelay(peerId) { this._native.connectViaRelay(peerId); }
|
|
276
|
+
|
|
253
277
|
/** @type {number} a {@link NatMapping} value describing this node's own NAT. */
|
|
254
278
|
get natMapping() { return this._native.natMapping(); }
|
|
255
279
|
|
|
@@ -206,6 +206,8 @@ set(LIBRARY_SOURCES
|
|
|
206
206
|
src/librats/transport/udp_stream.cpp
|
|
207
207
|
src/librats/transport/udp_mux.h
|
|
208
208
|
src/librats/transport/udp_mux.cpp
|
|
209
|
+
src/librats/transport/relay_link.h
|
|
210
|
+
src/librats/transport/relay_link.cpp
|
|
209
211
|
src/librats/transport/connection.h
|
|
210
212
|
src/librats/transport/connection.cpp
|
|
211
213
|
src/librats/transport/reactor.h
|
|
@@ -242,6 +244,7 @@ set(LIBRARY_SOURCES
|
|
|
242
244
|
src/librats/node/dialer.h
|
|
243
245
|
src/librats/node/dialer.cpp
|
|
244
246
|
src/librats/node/dial_service.h
|
|
247
|
+
src/librats/node/circuit_service.h
|
|
245
248
|
src/librats/node/nat_status.h
|
|
246
249
|
src/librats/node/nat_status.cpp
|
|
247
250
|
|
|
@@ -268,6 +271,9 @@ set(LIBRARY_SOURCES
|
|
|
268
271
|
src/librats/subsystems/hole_punch.h
|
|
269
272
|
src/librats/subsystems/hole_punch.cpp
|
|
270
273
|
src/librats/subsystems/hole_punch_service.h
|
|
274
|
+
src/librats/subsystems/relay.h
|
|
275
|
+
src/librats/subsystems/relay.cpp
|
|
276
|
+
src/librats/subsystems/relay_service.h
|
|
271
277
|
|
|
272
278
|
# dht/ — Kademlia DHT + KRPC (bencode is shared with bittorrent, always built)
|
|
273
279
|
src/librats/dht/dht.h
|
|
@@ -649,6 +655,7 @@ if(RATS_BUILD_TESTS)
|
|
|
649
655
|
tests/test_frame.cpp
|
|
650
656
|
tests/test_reactor.cpp
|
|
651
657
|
tests/test_transport_udp.cpp
|
|
658
|
+
tests/test_relay_link.cpp
|
|
652
659
|
tests/test_dialer.cpp
|
|
653
660
|
tests/test_peer_table.cpp
|
|
654
661
|
tests/test_handshake.cpp
|
|
@@ -665,6 +672,7 @@ if(RATS_BUILD_TESTS)
|
|
|
665
672
|
tests/test_reconnection.cpp
|
|
666
673
|
tests/test_nat_status.cpp
|
|
667
674
|
tests/test_hole_punch.cpp
|
|
675
|
+
tests/test_relay.cpp
|
|
668
676
|
tests/test_logging.cpp
|
|
669
677
|
)
|
|
670
678
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include "librats/subsystems/mdns_discovery.h"
|
|
7
7
|
#include "librats/subsystems/port_mapping_service.h"
|
|
8
8
|
#include "librats/subsystems/hole_punch.h"
|
|
9
|
+
#include "librats/subsystems/relay.h"
|
|
9
10
|
#include "librats/subsystems/pubsub.h"
|
|
10
11
|
#include "librats/subsystems/message_json.h"
|
|
11
12
|
#include "librats/subsystems/file_transfer.h"
|
|
@@ -42,6 +43,7 @@ struct RatsHandle {
|
|
|
42
43
|
PingService* ping = nullptr;
|
|
43
44
|
ReconnectionService* reconnect = nullptr;
|
|
44
45
|
HolePunch* punch = nullptr;
|
|
46
|
+
Relay* relay = nullptr;
|
|
45
47
|
#ifdef RATS_SEARCH_FEATURES
|
|
46
48
|
Bittorrent* bittorrent = nullptr;
|
|
47
49
|
#endif
|
|
@@ -299,6 +301,29 @@ rats_error_t rats_enable_hole_punch(rats_t node, int serve_as_relay) {
|
|
|
299
301
|
return RATS_OK;
|
|
300
302
|
}
|
|
301
303
|
|
|
304
|
+
rats_error_t rats_enable_relay(rats_t node, int serve_as_relay) {
|
|
305
|
+
auto* h = as_handle(node);
|
|
306
|
+
if (h->started) return RATS_ERR_ALREADY_STARTED;
|
|
307
|
+
if (!h->relay) {
|
|
308
|
+
Relay::Config config;
|
|
309
|
+
config.serve = serve_as_relay != 0;
|
|
310
|
+
h->relay = h->node->add_subsystem(std::make_unique<Relay>(config));
|
|
311
|
+
}
|
|
312
|
+
return RATS_OK;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
rats_error_t rats_connect_via_relay(rats_t node, const char* peer_id_hex) {
|
|
316
|
+
if (!peer_id_hex) return RATS_ERR_INVALID_ARG;
|
|
317
|
+
auto* h = as_handle(node);
|
|
318
|
+
if (!h->relay) return RATS_ERR_NOT_ENABLED;
|
|
319
|
+
auto id = PeerId::from_hex(peer_id_hex);
|
|
320
|
+
if (!id) return RATS_ERR_INVALID_ARG;
|
|
321
|
+
// As with punching, every reason an attempt is declined is "nothing to do or
|
|
322
|
+
// nothing to try with" — already connected, already trying, in cooldown, or no
|
|
323
|
+
// peer that could carry it — and none of them is separately actionable.
|
|
324
|
+
return h->relay->connect_via_relay(*id) ? RATS_OK : RATS_ERR_NO_SUCH_PEER;
|
|
325
|
+
}
|
|
326
|
+
|
|
302
327
|
rats_error_t rats_punch_peer(rats_t node, const char* peer_id_hex) {
|
|
303
328
|
if (!peer_id_hex) return RATS_ERR_INVALID_ARG;
|
|
304
329
|
auto* h = as_handle(node);
|
|
@@ -338,9 +363,15 @@ int rats_peer_transport(rats_t node, const char* peer_id_hex) {
|
|
|
338
363
|
if (!peer_id_hex) return -1;
|
|
339
364
|
auto id = PeerId::from_hex(peer_id_hex);
|
|
340
365
|
if (!id) return -1;
|
|
341
|
-
for (const PeerInfo& info : node_of(node)->peers())
|
|
342
|
-
if (info.id
|
|
343
|
-
|
|
366
|
+
for (const PeerInfo& info : node_of(node)->peers()) {
|
|
367
|
+
if (info.id != *id) continue;
|
|
368
|
+
switch (info.transport) {
|
|
369
|
+
case TransportKind::Udp: return RATS_TRANSPORT_UDP;
|
|
370
|
+
case TransportKind::Relay: return RATS_TRANSPORT_RELAY;
|
|
371
|
+
case TransportKind::Tcp: break;
|
|
372
|
+
}
|
|
373
|
+
return RATS_TRANSPORT_TCP;
|
|
374
|
+
}
|
|
344
375
|
return -1;
|
|
345
376
|
}
|
|
346
377
|
|
|
@@ -45,8 +45,12 @@ typedef enum {
|
|
|
45
45
|
* the identical encrypted handshake; they differ only in how the ordered,
|
|
46
46
|
* reliable byte stream underneath is obtained. */
|
|
47
47
|
typedef enum {
|
|
48
|
-
RATS_TRANSPORT_TCP
|
|
49
|
-
RATS_TRANSPORT_UDP
|
|
48
|
+
RATS_TRANSPORT_TCP = 0, /* one kernel socket per peer */
|
|
49
|
+
RATS_TRANSPORT_UDP = 1, /* reliable stream over the shared UDP socket */
|
|
50
|
+
/* Carried inside another peer's connection (see rats_enable_relay). Reported
|
|
51
|
+
by rats_peer_transport(); never a value to put in preferred_transport,
|
|
52
|
+
which chooses what a DIAL tries and a relay is never dialed. */
|
|
53
|
+
RATS_TRANSPORT_RELAY = 2
|
|
50
54
|
} rats_transport_t;
|
|
51
55
|
|
|
52
56
|
/* Bitmask of transports (see rats_transports / rats_peer_transports). */
|
|
@@ -192,6 +196,30 @@ RATS_API rats_error_t rats_enable_hole_punch(rats_t node, int serve_as_relay);
|
|
|
192
196
|
* its own to advertise (it needs at least one datagram peer first). */
|
|
193
197
|
RATS_API rats_error_t rats_punch_peer(rats_t node, const char* peer_id_hex);
|
|
194
198
|
|
|
199
|
+
/** Relaying: reach a peer that neither port forwarding nor hole punching could
|
|
200
|
+
* make reachable, by routing the connection through a node both ends are already
|
|
201
|
+
* connected to. Call before start(). The peer that comes out is ordinary in every
|
|
202
|
+
* way — the same end-to-end encryption, the same channels — except that its bytes
|
|
203
|
+
* take a detour, which rats_peer_transport() reports as RATS_TRANSPORT_RELAY.
|
|
204
|
+
*
|
|
205
|
+
* `serve_as_relay` (non-zero) also carries OTHER peers' connections. Unlike a
|
|
206
|
+
* hole-punch rendezvous, that spends real bandwidth on somebody else's traffic, so
|
|
207
|
+
* it is off by default and opted into here; a mesh in which nobody serves cannot
|
|
208
|
+
* relay at all. A serving node forwards only between peers it already holds, never
|
|
209
|
+
* chains circuits, and caps each one by bytes, duration and count. */
|
|
210
|
+
RATS_API rats_error_t rats_enable_relay(rats_t node, int serve_as_relay);
|
|
211
|
+
|
|
212
|
+
/** Try to reach `peer_id_hex` through a relay. Non-blocking: success arrives as an
|
|
213
|
+
* ordinary peer-connected callback. RATS_OK if an attempt was started;
|
|
214
|
+
* RATS_ERR_NOT_ENABLED if relaying is off; RATS_ERR_NO_SUCH_PEER if there is
|
|
215
|
+
* nothing to do or nothing to try with — the peer is already connected, an attempt
|
|
216
|
+
* is already running, it is in cooldown, or this node has no peer that could carry
|
|
217
|
+
* the connection.
|
|
218
|
+
*
|
|
219
|
+
* Usually there is no need to call this: with hole punching enabled too, a punch
|
|
220
|
+
* that cannot work hands the target over by itself. */
|
|
221
|
+
RATS_API rats_error_t rats_connect_via_relay(rats_t node, const char* peer_id_hex);
|
|
222
|
+
|
|
195
223
|
/** What the mesh has shown about this node's own NAT, from the endpoints datagram
|
|
196
224
|
* peers report seeing its shared UDP socket at. One of the RATS_NAT_* values;
|
|
197
225
|
* RATS_NAT_ENDPOINT_DEPENDENT means punching cannot work from here. */
|
|
@@ -33,8 +33,9 @@ const char* to_string(CloseReason r) noexcept {
|
|
|
33
33
|
|
|
34
34
|
const char* to_string(TransportKind t) noexcept {
|
|
35
35
|
switch (t) {
|
|
36
|
-
case TransportKind::Tcp:
|
|
37
|
-
case TransportKind::Udp:
|
|
36
|
+
case TransportKind::Tcp: return "tcp";
|
|
37
|
+
case TransportKind::Udp: return "udp";
|
|
38
|
+
case TransportKind::Relay: return "relay";
|
|
38
39
|
}
|
|
39
40
|
return "?";
|
|
40
41
|
}
|
|
@@ -26,13 +26,23 @@ enum class ConnRole {
|
|
|
26
26
|
Outbound, ///< We dialed out to a remote address.
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
/// Which wire a connection runs over.
|
|
30
|
-
/// same block/frame protocol and the same secure handshake, and
|
|
31
|
-
/// an ordered, reliable byte stream is obtained — the kernel's
|
|
32
|
-
/// library's own reliability layer on top of datagrams (see
|
|
29
|
+
/// Which wire a connection runs over. Tcp and Udp are first-class equals: they
|
|
30
|
+
/// carry the exact same block/frame protocol and the same secure handshake, and
|
|
31
|
+
/// differ only in how an ordered, reliable byte stream is obtained — the kernel's
|
|
32
|
+
/// TCP stack, or the library's own reliability layer on top of datagrams (see
|
|
33
|
+
/// transport/udp_stream.h). Relay is a third way of obtaining that same stream —
|
|
34
|
+
/// out of another peer's connection rather than out of a socket — and is a last
|
|
35
|
+
/// resort rather than an equal (see below).
|
|
33
36
|
enum class TransportKind {
|
|
34
|
-
Tcp,
|
|
35
|
-
Udp,
|
|
37
|
+
Tcp, ///< One kernel socket per peer.
|
|
38
|
+
Udp, ///< Reliable ordered stream over the shared UDP socket (NAT-friendly).
|
|
39
|
+
/// Carried inside another peer's connection: the byte stream is chopped into
|
|
40
|
+
/// messages a third node forwards between the two ends (see subsystems/relay.h).
|
|
41
|
+
/// Not a wire of its own — it is one of the two above, one hop further away —
|
|
42
|
+
/// but it behaves differently enough to be worth naming: it costs the relay
|
|
43
|
+
/// bandwidth, it cannot be dialed, and it must always lose to a direct link
|
|
44
|
+
/// (see PeerTable::add). Never a value connect() or the Dialer chooses.
|
|
45
|
+
Relay,
|
|
36
46
|
};
|
|
37
47
|
|
|
38
48
|
/// How hard an outbound datagram dial tries before it is called failed.
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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); }
|
|
@@ -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) {
|