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
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
|
|
|
@@ -85,9 +91,23 @@ declare module 'librats' {
|
|
|
85
91
|
preferredTransport?: TransportValue;
|
|
86
92
|
/** Delay before the other transport is raced alongside; 0 disables. Default 1200. */
|
|
87
93
|
transportFallbackMs?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Bytes a peer's send queue may hold before an app that keeps sending anyway
|
|
96
|
+
* has that peer dropped with reason `RATS_CLOSE_SLOW_CONSUMER`. 0 (default)
|
|
97
|
+
* uses the library's 8 MiB. A quarter of it is where `peerWritable()` starts
|
|
98
|
+
* returning false. Not a maximum message size.
|
|
99
|
+
*/
|
|
100
|
+
sendQueueLimit?: number;
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
export type PeerHandler = (peerId: string) => void;
|
|
104
|
+
/**
|
|
105
|
+
* `reason` is why the peer went — "RATS_CLOSE_PEER_CLOSED",
|
|
106
|
+
* "RATS_CLOSE_SLOW_CONSUMER" and so on. Worth branching on: a peer that left is
|
|
107
|
+
* one to redial, while SLOW_CONSUMER means *you* were sending faster than the
|
|
108
|
+
* link drained, and redialing that one just repeats the overload.
|
|
109
|
+
*/
|
|
110
|
+
export type PeerDisconnectHandler = (peerId: string, reason: string) => void;
|
|
91
111
|
export type MessageHandler = (peerId: string, data: Buffer) => void;
|
|
92
112
|
export type TopicHandler = (peerId: string, topic: string, data: Buffer) => void;
|
|
93
113
|
export type JsonHandler = (peerId: string, value: any) => void;
|
|
@@ -165,7 +185,7 @@ declare module 'librats' {
|
|
|
165
185
|
/** Cap on established peers (0 = unlimited). Settable at any time. */
|
|
166
186
|
maxPeers: number;
|
|
167
187
|
/** Which wire a connected peer's link runs on, or `null` if not connected. */
|
|
168
|
-
peerTransport(peerId: string):
|
|
188
|
+
peerTransport(peerId: string): PeerTransportValue | null;
|
|
169
189
|
/**
|
|
170
190
|
* Transports a connected peer advertised, as a `TransportMask` bitmask, or
|
|
171
191
|
* `null` if not connected. 0 means the peer did not say (an older build).
|
|
@@ -174,17 +194,31 @@ declare module 'librats' {
|
|
|
174
194
|
|
|
175
195
|
// ---- raw channel messaging ----
|
|
176
196
|
|
|
177
|
-
/**
|
|
197
|
+
/**
|
|
198
|
+
* Send raw bytes on a named channel to one peer. Returning means the message
|
|
199
|
+
* was queued, never that it arrived — if you send in bulk, follow it with
|
|
200
|
+
* `peerWritable()`.
|
|
201
|
+
*/
|
|
178
202
|
send(peerId: string, channel: string, data: string | Buffer): void;
|
|
179
203
|
/** Broadcast raw bytes on a named channel to every connected peer. */
|
|
180
204
|
broadcast(channel: string, data: string | Buffer): void;
|
|
205
|
+
/**
|
|
206
|
+
* Whether this peer's send queue still has room (false also when it is not
|
|
207
|
+
* connected). False means stop: what you just sent was queued like anything
|
|
208
|
+
* else and nothing was dropped, but keep piling on and the peer is dropped
|
|
209
|
+
* with reason `RATS_CLOSE_SLOW_CONSUMER`. Wait for `onPeerWritable`, or poll
|
|
210
|
+
* this. Not a size limit — one message of any size is always queued.
|
|
211
|
+
*/
|
|
212
|
+
peerWritable(peerId: string): boolean;
|
|
181
213
|
/** Register a handler for a named channel. Additive; register before `start()`. */
|
|
182
214
|
on(channel: string, handler: MessageHandler): void;
|
|
183
215
|
|
|
184
216
|
// ---- peer events (register before start) ----
|
|
185
217
|
|
|
186
218
|
onPeerConnected(handler: PeerHandler): void;
|
|
187
|
-
onPeerDisconnected(handler:
|
|
219
|
+
onPeerDisconnected(handler: PeerDisconnectHandler): void;
|
|
220
|
+
/** A peer whose queue had filled has drained back under its mark. */
|
|
221
|
+
onPeerWritable(handler: PeerHandler): void;
|
|
188
222
|
|
|
189
223
|
// ---- discovery (enable before start) ----
|
|
190
224
|
|
|
@@ -207,6 +241,17 @@ declare module 'librats' {
|
|
|
207
241
|
* ordinary `onPeerConnected`.
|
|
208
242
|
*/
|
|
209
243
|
punchPeer(peerId: string): void;
|
|
244
|
+
/**
|
|
245
|
+
* Enable relaying: reach a peer nothing else could, through a node both ends
|
|
246
|
+
* are already connected to. `serveAsRelay` (default `false`) also carries
|
|
247
|
+
* OTHER peers' connections, which spends real bandwidth.
|
|
248
|
+
*/
|
|
249
|
+
enableRelay(serveAsRelay?: boolean): void;
|
|
250
|
+
/**
|
|
251
|
+
* Try to reach a peer through a relay. Non-blocking: success arrives as an
|
|
252
|
+
* ordinary `onPeerConnected`.
|
|
253
|
+
*/
|
|
254
|
+
connectViaRelay(peerId: string): void;
|
|
210
255
|
/** A `NatMapping` value describing this node's own NAT. */
|
|
211
256
|
readonly natMapping: NatMappingValue;
|
|
212
257
|
|
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()`. */
|
|
@@ -192,6 +193,14 @@ class RatsNode {
|
|
|
192
193
|
*/
|
|
193
194
|
broadcast(channel, data) { this._native.broadcast(channel, data); }
|
|
194
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Whether this peer's send queue still has room. False means pause and wait for
|
|
198
|
+
* `onPeerWritable` — keep sending regardless and the peer is dropped with
|
|
199
|
+
* reason `RATS_CLOSE_SLOW_CONSUMER`.
|
|
200
|
+
* @param {string} peerId @returns {boolean}
|
|
201
|
+
*/
|
|
202
|
+
peerWritable(peerId) { return this._native.peerWritable(peerId); }
|
|
203
|
+
|
|
195
204
|
/**
|
|
196
205
|
* Register a handler for a named channel. Additive; register before `start()`.
|
|
197
206
|
* @param {string} channel
|
|
@@ -204,9 +213,16 @@ class RatsNode {
|
|
|
204
213
|
/** @param {(peerId: string) => void} handler fired when a peer connects. */
|
|
205
214
|
onPeerConnected(handler) { this._native.onPeerConnected(handler); }
|
|
206
215
|
|
|
207
|
-
/**
|
|
216
|
+
/**
|
|
217
|
+
* @param {(peerId: string, reason: string) => void} handler fired when a peer
|
|
218
|
+
* disconnects. `reason` is a name like "RATS_CLOSE_SLOW_CONSUMER", which means
|
|
219
|
+
* this node was sending faster than the link drained.
|
|
220
|
+
*/
|
|
208
221
|
onPeerDisconnected(handler) { this._native.onPeerDisconnected(handler); }
|
|
209
222
|
|
|
223
|
+
/** @param {(peerId: string) => void} handler fired when a full queue drained. */
|
|
224
|
+
onPeerWritable(handler) { this._native.onPeerWritable(handler); }
|
|
225
|
+
|
|
210
226
|
// ---- discovery (enable before start) ----
|
|
211
227
|
|
|
212
228
|
/**
|
|
@@ -250,6 +266,29 @@ class RatsNode {
|
|
|
250
266
|
*/
|
|
251
267
|
punchPeer(peerId) { this._native.punchPeer(peerId); }
|
|
252
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Enable relaying: reach a peer that neither port forwarding nor hole punching
|
|
271
|
+
* could make reachable, by routing the connection through a node both ends are
|
|
272
|
+
* already connected to. The peer that comes out is ordinary in every way — the
|
|
273
|
+
* same end-to-end encryption, the same channels — except that `peerTransport()`
|
|
274
|
+
* reports it as `Transport.RELAY`.
|
|
275
|
+
* @param {boolean} [serveAsRelay=false] also carry OTHER peers' connections.
|
|
276
|
+
* Unlike a hole-punch rendezvous this spends real bandwidth on somebody else's
|
|
277
|
+
* traffic, so it is off by default; a mesh in which nobody serves cannot relay.
|
|
278
|
+
*/
|
|
279
|
+
enableRelay(serveAsRelay = false) { this._native.enableRelay(serveAsRelay); }
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Try to reach a peer through a relay. Non-blocking: success arrives as an
|
|
283
|
+
* ordinary `onPeerConnected`. Throws `NO_SUCH_PEER` when there is nothing to do
|
|
284
|
+
* or nothing to try with — already connected, an attempt already running, in
|
|
285
|
+
* cooldown, or no peer that could carry the connection. Usually unnecessary:
|
|
286
|
+
* with hole punching enabled too, a punch that cannot work hands the target over
|
|
287
|
+
* by itself.
|
|
288
|
+
* @param {string} peerId
|
|
289
|
+
*/
|
|
290
|
+
connectViaRelay(peerId) { this._native.connectViaRelay(peerId); }
|
|
291
|
+
|
|
253
292
|
/** @type {number} a {@link NatMapping} value describing this node's own NAT. */
|
|
254
293
|
get natMapping() { return this._native.natMapping(); }
|
|
255
294
|
|
|
@@ -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
|
|
@@ -487,6 +493,11 @@ if(WIN32)
|
|
|
487
493
|
endif()
|
|
488
494
|
|
|
489
495
|
if(ANDROID)
|
|
496
|
+
# The logger routes console output through __android_log_print, because an app's
|
|
497
|
+
# stdout/stderr go nowhere on Android.
|
|
498
|
+
find_library(log-lib log)
|
|
499
|
+
target_link_libraries(rats ${log-lib})
|
|
500
|
+
|
|
490
501
|
if(DEFINED ANDROID_PLATFORM)
|
|
491
502
|
string(REGEX REPLACE "android-" "" ANDROID_API_LEVEL ${ANDROID_PLATFORM})
|
|
492
503
|
math(EXPR ANDROID_API_LEVEL "${ANDROID_API_LEVEL}")
|
|
@@ -649,6 +660,7 @@ if(RATS_BUILD_TESTS)
|
|
|
649
660
|
tests/test_frame.cpp
|
|
650
661
|
tests/test_reactor.cpp
|
|
651
662
|
tests/test_transport_udp.cpp
|
|
663
|
+
tests/test_relay_link.cpp
|
|
652
664
|
tests/test_dialer.cpp
|
|
653
665
|
tests/test_peer_table.cpp
|
|
654
666
|
tests/test_handshake.cpp
|
|
@@ -665,6 +677,7 @@ if(RATS_BUILD_TESTS)
|
|
|
665
677
|
tests/test_reconnection.cpp
|
|
666
678
|
tests/test_nat_status.cpp
|
|
667
679
|
tests/test_hole_punch.cpp
|
|
680
|
+
tests/test_relay.cpp
|
|
668
681
|
tests/test_logging.cpp
|
|
669
682
|
)
|
|
670
683
|
|
|
@@ -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
|
|
@@ -83,6 +85,24 @@ char* dup_string(const std::string& s) {
|
|
|
83
85
|
return out;
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
rats_close_reason_t to_c_reason(CloseReason r) {
|
|
89
|
+
switch (r) {
|
|
90
|
+
case CloseReason::LocalClose: return RATS_CLOSE_LOCAL;
|
|
91
|
+
case CloseReason::PeerClosed: return RATS_CLOSE_PEER_CLOSED;
|
|
92
|
+
case CloseReason::PeerReset: return RATS_CLOSE_PEER_RESET;
|
|
93
|
+
case CloseReason::ConnectFailed: return RATS_CLOSE_CONNECT_FAILED;
|
|
94
|
+
case CloseReason::HandshakeFailed: return RATS_CLOSE_HANDSHAKE_FAILED;
|
|
95
|
+
case CloseReason::ProtocolError: return RATS_CLOSE_PROTOCOL_ERROR;
|
|
96
|
+
case CloseReason::SlowConsumer: return RATS_CLOSE_SLOW_CONSUMER;
|
|
97
|
+
case CloseReason::ReactorShutdown: return RATS_CLOSE_SHUTDOWN;
|
|
98
|
+
case CloseReason::DuplicateConn: return RATS_CLOSE_DUPLICATE;
|
|
99
|
+
case CloseReason::PeerLimit: return RATS_CLOSE_PEER_LIMIT;
|
|
100
|
+
case CloseReason::IdleTimeout: return RATS_CLOSE_IDLE_TIMEOUT;
|
|
101
|
+
case CloseReason::DialSuperseded: return RATS_CLOSE_DIAL_SUPERSEDED;
|
|
102
|
+
}
|
|
103
|
+
return RATS_CLOSE_PEER_CLOSED;
|
|
104
|
+
}
|
|
105
|
+
|
|
86
106
|
} // namespace
|
|
87
107
|
|
|
88
108
|
extern "C" {
|
|
@@ -101,6 +121,24 @@ const char* rats_error_str(rats_error_t err) {
|
|
|
101
121
|
return "RATS_ERR_UNKNOWN";
|
|
102
122
|
}
|
|
103
123
|
|
|
124
|
+
const char* rats_close_reason_str(rats_close_reason_t reason) {
|
|
125
|
+
switch (reason) {
|
|
126
|
+
case RATS_CLOSE_LOCAL: return "RATS_CLOSE_LOCAL";
|
|
127
|
+
case RATS_CLOSE_PEER_CLOSED: return "RATS_CLOSE_PEER_CLOSED";
|
|
128
|
+
case RATS_CLOSE_PEER_RESET: return "RATS_CLOSE_PEER_RESET";
|
|
129
|
+
case RATS_CLOSE_CONNECT_FAILED: return "RATS_CLOSE_CONNECT_FAILED";
|
|
130
|
+
case RATS_CLOSE_HANDSHAKE_FAILED: return "RATS_CLOSE_HANDSHAKE_FAILED";
|
|
131
|
+
case RATS_CLOSE_PROTOCOL_ERROR: return "RATS_CLOSE_PROTOCOL_ERROR";
|
|
132
|
+
case RATS_CLOSE_SLOW_CONSUMER: return "RATS_CLOSE_SLOW_CONSUMER";
|
|
133
|
+
case RATS_CLOSE_SHUTDOWN: return "RATS_CLOSE_SHUTDOWN";
|
|
134
|
+
case RATS_CLOSE_DUPLICATE: return "RATS_CLOSE_DUPLICATE";
|
|
135
|
+
case RATS_CLOSE_PEER_LIMIT: return "RATS_CLOSE_PEER_LIMIT";
|
|
136
|
+
case RATS_CLOSE_IDLE_TIMEOUT: return "RATS_CLOSE_IDLE_TIMEOUT";
|
|
137
|
+
case RATS_CLOSE_DIAL_SUPERSEDED: return "RATS_CLOSE_DIAL_SUPERSEDED";
|
|
138
|
+
}
|
|
139
|
+
return "RATS_CLOSE_UNKNOWN";
|
|
140
|
+
}
|
|
141
|
+
|
|
104
142
|
/* — construction / lifecycle — */
|
|
105
143
|
|
|
106
144
|
rats_config_t rats_config_default(void) {
|
|
@@ -116,6 +154,7 @@ rats_config_t rats_config_default(void) {
|
|
|
116
154
|
c.enable_udp = 1;
|
|
117
155
|
c.preferred_transport = RATS_TRANSPORT_UDP;
|
|
118
156
|
c.transport_fallback_ms = 1200;
|
|
157
|
+
c.send_queue_limit = 0;
|
|
119
158
|
return c;
|
|
120
159
|
}
|
|
121
160
|
|
|
@@ -151,6 +190,7 @@ rats_t rats_create_config(const rats_config_t* cfg) {
|
|
|
151
190
|
? TransportKind::Tcp
|
|
152
191
|
: TransportKind::Udp;
|
|
153
192
|
config.transport_fallback_ms = cfg->transport_fallback_ms;
|
|
193
|
+
config.send_queue_limit = cfg->send_queue_limit;
|
|
154
194
|
}
|
|
155
195
|
return make_handle(std::move(config));
|
|
156
196
|
}
|
|
@@ -222,6 +262,13 @@ rats_error_t rats_send(rats_t node, const char* peer_id_hex,
|
|
|
222
262
|
return RATS_OK;
|
|
223
263
|
}
|
|
224
264
|
|
|
265
|
+
int rats_peer_writable(rats_t node, const char* peer_id_hex) {
|
|
266
|
+
if (!peer_id_hex) return 0;
|
|
267
|
+
auto id = PeerId::from_hex(peer_id_hex);
|
|
268
|
+
if (!id) return 0;
|
|
269
|
+
return node_of(node)->peer_writable(*id) ? 1 : 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
225
272
|
rats_error_t rats_broadcast(rats_t node, const char* channel, const void* data, size_t len) {
|
|
226
273
|
if (!channel) return RATS_ERR_INVALID_ARG;
|
|
227
274
|
node_of(node)->broadcast(channel, ByteView(static_cast<const uint8_t*>(data), len));
|
|
@@ -236,10 +283,18 @@ rats_error_t rats_on_peer_connected(rats_t node, rats_peer_cb cb, void* user) {
|
|
|
236
283
|
return RATS_OK;
|
|
237
284
|
}
|
|
238
285
|
|
|
239
|
-
rats_error_t rats_on_peer_disconnected(rats_t node,
|
|
286
|
+
rats_error_t rats_on_peer_disconnected(rats_t node, rats_peer_disconnect_cb cb, void* user) {
|
|
287
|
+
if (!cb) return RATS_ERR_INVALID_ARG;
|
|
288
|
+
node_of(node)->on_peer_disconnected([cb, user](const PeerId& id, CloseReason reason) {
|
|
289
|
+
cb(user, id.to_hex().c_str(), to_c_reason(reason));
|
|
290
|
+
});
|
|
291
|
+
return RATS_OK;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
rats_error_t rats_on_peer_writable(rats_t node, rats_peer_cb cb, void* user) {
|
|
240
295
|
if (!cb) return RATS_ERR_INVALID_ARG;
|
|
241
|
-
node_of(node)->
|
|
242
|
-
cb(user, id.to_hex().c_str());
|
|
296
|
+
node_of(node)->on_peer_writable([cb, user](const Peer& peer) {
|
|
297
|
+
cb(user, peer.id().to_hex().c_str());
|
|
243
298
|
});
|
|
244
299
|
return RATS_OK;
|
|
245
300
|
}
|
|
@@ -299,6 +354,29 @@ rats_error_t rats_enable_hole_punch(rats_t node, int serve_as_relay) {
|
|
|
299
354
|
return RATS_OK;
|
|
300
355
|
}
|
|
301
356
|
|
|
357
|
+
rats_error_t rats_enable_relay(rats_t node, int serve_as_relay) {
|
|
358
|
+
auto* h = as_handle(node);
|
|
359
|
+
if (h->started) return RATS_ERR_ALREADY_STARTED;
|
|
360
|
+
if (!h->relay) {
|
|
361
|
+
Relay::Config config;
|
|
362
|
+
config.serve = serve_as_relay != 0;
|
|
363
|
+
h->relay = h->node->add_subsystem(std::make_unique<Relay>(config));
|
|
364
|
+
}
|
|
365
|
+
return RATS_OK;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
rats_error_t rats_connect_via_relay(rats_t node, const char* peer_id_hex) {
|
|
369
|
+
if (!peer_id_hex) return RATS_ERR_INVALID_ARG;
|
|
370
|
+
auto* h = as_handle(node);
|
|
371
|
+
if (!h->relay) return RATS_ERR_NOT_ENABLED;
|
|
372
|
+
auto id = PeerId::from_hex(peer_id_hex);
|
|
373
|
+
if (!id) return RATS_ERR_INVALID_ARG;
|
|
374
|
+
// As with punching, every reason an attempt is declined is "nothing to do or
|
|
375
|
+
// nothing to try with" — already connected, already trying, in cooldown, or no
|
|
376
|
+
// peer that could carry it — and none of them is separately actionable.
|
|
377
|
+
return h->relay->connect_via_relay(*id) ? RATS_OK : RATS_ERR_NO_SUCH_PEER;
|
|
378
|
+
}
|
|
379
|
+
|
|
302
380
|
rats_error_t rats_punch_peer(rats_t node, const char* peer_id_hex) {
|
|
303
381
|
if (!peer_id_hex) return RATS_ERR_INVALID_ARG;
|
|
304
382
|
auto* h = as_handle(node);
|
|
@@ -338,9 +416,15 @@ int rats_peer_transport(rats_t node, const char* peer_id_hex) {
|
|
|
338
416
|
if (!peer_id_hex) return -1;
|
|
339
417
|
auto id = PeerId::from_hex(peer_id_hex);
|
|
340
418
|
if (!id) return -1;
|
|
341
|
-
for (const PeerInfo& info : node_of(node)->peers())
|
|
342
|
-
if (info.id
|
|
343
|
-
|
|
419
|
+
for (const PeerInfo& info : node_of(node)->peers()) {
|
|
420
|
+
if (info.id != *id) continue;
|
|
421
|
+
switch (info.transport) {
|
|
422
|
+
case TransportKind::Udp: return RATS_TRANSPORT_UDP;
|
|
423
|
+
case TransportKind::Relay: return RATS_TRANSPORT_RELAY;
|
|
424
|
+
case TransportKind::Tcp: break;
|
|
425
|
+
}
|
|
426
|
+
return RATS_TRANSPORT_TCP;
|
|
427
|
+
}
|
|
344
428
|
return -1;
|
|
345
429
|
}
|
|
346
430
|
|
|
@@ -45,10 +45,41 @@ 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
|
|
|
56
|
+
/* Why a peer connection ended, handed to rats_on_peer_disconnected(). The
|
|
57
|
+
* reason is what tells "I was sending too fast" apart from "the peer left" —
|
|
58
|
+
* without it both look the same and the usual answer to either is to reconnect
|
|
59
|
+
* and repeat whatever caused it. Use rats_close_reason_str() for a name. */
|
|
60
|
+
typedef enum {
|
|
61
|
+
RATS_CLOSE_LOCAL = 0, /* we asked to disconnect */
|
|
62
|
+
RATS_CLOSE_PEER_CLOSED = 1, /* the peer closed cleanly */
|
|
63
|
+
RATS_CLOSE_PEER_RESET = 2, /* connection reset / socket error */
|
|
64
|
+
RATS_CLOSE_CONNECT_FAILED = 3, /* the dial never completed */
|
|
65
|
+
RATS_CLOSE_HANDSHAKE_FAILED = 4, /* secure handshake failed or timed out */
|
|
66
|
+
RATS_CLOSE_PROTOCOL_ERROR = 5, /* malformed frame / decryption failure */
|
|
67
|
+
/* We kept sending while this peer's queue was already past its limit. THE
|
|
68
|
+
* ONE REASON THE APPLICATION CAN PREVENT: heed rats_send()'s answer and
|
|
69
|
+
* rats_peer_writable(), and wait for the writable callback. It does NOT mean
|
|
70
|
+
* a single message was too big — one message is always accepted whatever its
|
|
71
|
+
* size; only piling more on a full queue gets a peer dropped. */
|
|
72
|
+
RATS_CLOSE_SLOW_CONSUMER = 6,
|
|
73
|
+
RATS_CLOSE_SHUTDOWN = 7, /* the node is stopping */
|
|
74
|
+
RATS_CLOSE_DUPLICATE = 8, /* superseded by another link to the same peer */
|
|
75
|
+
RATS_CLOSE_PEER_LIMIT = 9, /* inbound refused: peer limit reached */
|
|
76
|
+
RATS_CLOSE_IDLE_TIMEOUT = 10, /* datagram link went silent */
|
|
77
|
+
RATS_CLOSE_DIAL_SUPERSEDED = 11 /* a racing dial over the other transport won */
|
|
78
|
+
} rats_close_reason_t;
|
|
79
|
+
|
|
80
|
+
/** Static human-readable name for a close reason (never NULL, never freed). */
|
|
81
|
+
RATS_API const char* rats_close_reason_str(rats_close_reason_t reason);
|
|
82
|
+
|
|
52
83
|
/* Bitmask of transports (see rats_transports / rats_peer_transports). */
|
|
53
84
|
#define RATS_TRANSPORT_MASK_TCP 0x1u
|
|
54
85
|
#define RATS_TRANSPORT_MASK_UDP 0x2u
|
|
@@ -98,6 +129,14 @@ typedef struct {
|
|
|
98
129
|
rats_transport_t preferred_transport; /* tried first when dialing (default UDP) */
|
|
99
130
|
uint32_t transport_fallback_ms; /* delay before racing the other transport;
|
|
100
131
|
* 0 = never fall back (default 1200) */
|
|
132
|
+
|
|
133
|
+
/* Bytes a peer's send queue may hold before an application that keeps sending
|
|
134
|
+
* anyway has that peer dropped with RATS_CLOSE_SLOW_CONSUMER. 0 = the library
|
|
135
|
+
* default (8 MiB). A quarter of it is where rats_peer_writable() starts
|
|
136
|
+
* answering 0, so lowering this makes an application feel backpressure sooner.
|
|
137
|
+
* It is NOT a maximum message size: one message is always queued whatever its
|
|
138
|
+
* size. */
|
|
139
|
+
size_t send_queue_limit;
|
|
101
140
|
} rats_config_t;
|
|
102
141
|
|
|
103
142
|
/** A config pre-filled with the library defaults (listening, Noise, ephemeral
|
|
@@ -149,18 +188,44 @@ RATS_API size_t rats_max_peers(rats_t node);
|
|
|
149
188
|
|
|
150
189
|
/* — messaging (named application channel, raw bytes) — */
|
|
151
190
|
|
|
191
|
+
/** Queue bytes for one peer on a named channel. RATS_OK means accepted and
|
|
192
|
+
* queued, never that it arrived. Pair it with rats_peer_writable() below if you
|
|
193
|
+
* send in bulk — that is the only way to learn you are outrunning the link
|
|
194
|
+
* before the peer is dropped for it. */
|
|
152
195
|
RATS_API rats_error_t rats_send(rats_t node, const char* peer_id_hex,
|
|
153
196
|
const char* channel, const void* data, size_t len);
|
|
154
197
|
RATS_API rats_error_t rats_broadcast(rats_t node, const char* channel,
|
|
155
198
|
const void* data, size_t len);
|
|
156
199
|
|
|
200
|
+
/** Whether this peer's send queue still has room — the same question rats_send()
|
|
201
|
+
* answers, asked without sending. Returns 1 for room, 0 for none (and 0 for a
|
|
202
|
+
* peer that is not connected).
|
|
203
|
+
*
|
|
204
|
+
* A 0 says "stop". The message you just sent was queued like any other and
|
|
205
|
+
* nothing was dropped, but keep piling on and this peer is dropped with
|
|
206
|
+
* RATS_CLOSE_SLOW_CONSUMER. Wait for the callback registered with
|
|
207
|
+
* rats_on_peer_writable(), or poll this from a thread of your own.
|
|
208
|
+
*
|
|
209
|
+
* Ask it right after rats_send(): the bytes you just handed over are already
|
|
210
|
+
* counted, so the answer covers your own send and not just what the reactor has
|
|
211
|
+
* got round to. It is NOT a size limit — a single message of any size is always
|
|
212
|
+
* accepted; only sending more on top of a full queue drops a peer. */
|
|
213
|
+
RATS_API int rats_peer_writable(rats_t node, const char* peer_id_hex);
|
|
214
|
+
|
|
157
215
|
/* — callbacks (register before start; invoked on a reactor thread) — */
|
|
158
216
|
|
|
159
217
|
typedef void (*rats_peer_cb)(void* user, const char* peer_id_hex);
|
|
218
|
+
typedef void (*rats_peer_disconnect_cb)(void* user, const char* peer_id_hex,
|
|
219
|
+
rats_close_reason_t reason);
|
|
160
220
|
typedef void (*rats_message_cb)(void* user, const char* peer_id_hex, const void* data, size_t len);
|
|
161
221
|
|
|
162
222
|
RATS_API rats_error_t rats_on_peer_connected(rats_t node, rats_peer_cb cb, void* user);
|
|
163
|
-
RATS_API rats_error_t rats_on_peer_disconnected(rats_t node,
|
|
223
|
+
RATS_API rats_error_t rats_on_peer_disconnected(rats_t node, rats_peer_disconnect_cb cb,
|
|
224
|
+
void* user);
|
|
225
|
+
/** "This peer can be written to again" — fired when a peer whose queue had filled
|
|
226
|
+
* past its mark has drained back under it. The other half of rats_send()
|
|
227
|
+
* reporting 0; an application that never asks never needs this. */
|
|
228
|
+
RATS_API rats_error_t rats_on_peer_writable(rats_t node, rats_peer_cb cb, void* user);
|
|
164
229
|
RATS_API rats_error_t rats_on(rats_t node, const char* channel, rats_message_cb cb, void* user);
|
|
165
230
|
|
|
166
231
|
/* — optional subsystems (enable before start) — */
|
|
@@ -192,6 +257,30 @@ RATS_API rats_error_t rats_enable_hole_punch(rats_t node, int serve_as_relay);
|
|
|
192
257
|
* its own to advertise (it needs at least one datagram peer first). */
|
|
193
258
|
RATS_API rats_error_t rats_punch_peer(rats_t node, const char* peer_id_hex);
|
|
194
259
|
|
|
260
|
+
/** Relaying: reach a peer that neither port forwarding nor hole punching could
|
|
261
|
+
* make reachable, by routing the connection through a node both ends are already
|
|
262
|
+
* connected to. Call before start(). The peer that comes out is ordinary in every
|
|
263
|
+
* way — the same end-to-end encryption, the same channels — except that its bytes
|
|
264
|
+
* take a detour, which rats_peer_transport() reports as RATS_TRANSPORT_RELAY.
|
|
265
|
+
*
|
|
266
|
+
* `serve_as_relay` (non-zero) also carries OTHER peers' connections. Unlike a
|
|
267
|
+
* hole-punch rendezvous, that spends real bandwidth on somebody else's traffic, so
|
|
268
|
+
* it is off by default and opted into here; a mesh in which nobody serves cannot
|
|
269
|
+
* relay at all. A serving node forwards only between peers it already holds, never
|
|
270
|
+
* chains circuits, and caps each one by bytes, duration and count. */
|
|
271
|
+
RATS_API rats_error_t rats_enable_relay(rats_t node, int serve_as_relay);
|
|
272
|
+
|
|
273
|
+
/** Try to reach `peer_id_hex` through a relay. Non-blocking: success arrives as an
|
|
274
|
+
* ordinary peer-connected callback. RATS_OK if an attempt was started;
|
|
275
|
+
* RATS_ERR_NOT_ENABLED if relaying is off; RATS_ERR_NO_SUCH_PEER if there is
|
|
276
|
+
* nothing to do or nothing to try with — the peer is already connected, an attempt
|
|
277
|
+
* is already running, it is in cooldown, or this node has no peer that could carry
|
|
278
|
+
* the connection.
|
|
279
|
+
*
|
|
280
|
+
* Usually there is no need to call this: with hole punching enabled too, a punch
|
|
281
|
+
* that cannot work hands the target over by itself. */
|
|
282
|
+
RATS_API rats_error_t rats_connect_via_relay(rats_t node, const char* peer_id_hex);
|
|
283
|
+
|
|
195
284
|
/** What the mesh has shown about this node's own NAT, from the endpoints datagram
|
|
196
285
|
* peers report seeing its shared UDP socket at. One of the RATS_NAT_* values;
|
|
197
286
|
* RATS_NAT_ENDPOINT_DEPENDENT means punching cannot work from here. */
|
|
@@ -13,7 +13,7 @@ const char* to_string(ConnState s) noexcept {
|
|
|
13
13
|
return "?";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const char* to_string(CloseReason r) noexcept {
|
|
16
|
+
RATS_API const char* to_string(CloseReason r) noexcept {
|
|
17
17
|
switch (r) {
|
|
18
18
|
case CloseReason::LocalClose: return "LocalClose";
|
|
19
19
|
case CloseReason::PeerClosed: return "PeerClosed";
|
|
@@ -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.
|
|
@@ -74,7 +84,9 @@ enum class CloseReason {
|
|
|
74
84
|
ConnectFailed, ///< Outbound transport connect never completed.
|
|
75
85
|
HandshakeFailed, ///< Secure-channel handshake failed or timed out.
|
|
76
86
|
ProtocolError, ///< Malformed frame / decryption failure on the wire.
|
|
77
|
-
SlowConsumer, ///<
|
|
87
|
+
SlowConsumer, ///< Kept sending with the queue already past its high-water
|
|
88
|
+
///< mark. Not "a message was too large": one message is
|
|
89
|
+
///< always queued whatever its size (Connection::send).
|
|
78
90
|
ReactorShutdown, ///< Reactor is stopping.
|
|
79
91
|
DuplicateConn, ///< Redundant connection to a peer we already hold; superseded.
|
|
80
92
|
PeerLimit, ///< Inbound rejected: the configured peer limit is reached.
|
|
@@ -83,11 +95,12 @@ enum class CloseReason {
|
|
|
83
95
|
};
|
|
84
96
|
|
|
85
97
|
const char* to_string(ConnState) noexcept;
|
|
86
|
-
|
|
87
|
-
///
|
|
88
|
-
///
|
|
89
|
-
|
|
90
|
-
///
|
|
98
|
+
/// Exported: a CloseReason is handed to every on_peer_disconnected handler, so a
|
|
99
|
+
/// consumer of the shared build has to be able to render one. (ConnState above is
|
|
100
|
+
/// not — it never leaves Connection.)
|
|
101
|
+
RATS_API const char* to_string(CloseReason) noexcept;
|
|
102
|
+
/// Exported for the same reason: TransportKind is part of the public surface
|
|
103
|
+
/// (NodeConfig::preferred_transport, PeerInfo::transport).
|
|
91
104
|
RATS_API const char* to_string(TransportKind) noexcept;
|
|
92
105
|
|
|
93
106
|
} // namespace librats
|