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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "librats/subsystems/hole_punch.h"
|
|
2
2
|
#include "librats/node/dial_service.h"
|
|
3
3
|
#include "librats/node/node_context.h"
|
|
4
|
+
#include "librats/peer/peer_info.h"
|
|
4
5
|
#include "librats/util/logger.h"
|
|
5
6
|
|
|
6
7
|
#include <algorithm>
|
|
@@ -135,6 +136,7 @@ void HolePunch::attach(NodeContext& ctx) {
|
|
|
135
136
|
// does not know its own external one simply cannot punch — it can still relay.
|
|
136
137
|
dialer_ = ctx.services.get<DialService>();
|
|
137
138
|
external_ = ctx.services.get<ExternalAddressService>();
|
|
139
|
+
services_ = &ctx.services;
|
|
138
140
|
|
|
139
141
|
// Offer punching to sibling modules. Registered even without a dialer: punch()
|
|
140
142
|
// answers false on its own then, which is exactly what a caller expects from a
|
|
@@ -151,17 +153,30 @@ void HolePunch::attach(NodeContext& ctx) {
|
|
|
151
153
|
// reconciles sessions against the peer set, so this is the fast path, not the
|
|
152
154
|
// only one.
|
|
153
155
|
network_->on_peer_connected([this](const Peer& peer) {
|
|
156
|
+
// A relayed link is not what a punch was for — it is the fallback the punch
|
|
157
|
+
// is trying to make unnecessary, and a punch may well have been started
|
|
158
|
+
// BECAUSE it came up (see subsystems/relay.h). Retiring the session here
|
|
159
|
+
// would call the upgrade off the moment it began.
|
|
160
|
+
const auto info = peer.info();
|
|
161
|
+
if (info && info->transport == TransportKind::Relay) return;
|
|
154
162
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
155
163
|
sessions_.erase(peer.id());
|
|
156
164
|
});
|
|
157
165
|
}
|
|
158
166
|
|
|
159
167
|
void HolePunch::start() {
|
|
168
|
+
// Resolved here rather than in attach(): Relay may be attached after us, and
|
|
169
|
+
// every attach() runs before any start(). Stored before running_ goes true, so
|
|
170
|
+
// an escalation on a reactor thread never sees a half-initialised state.
|
|
171
|
+
if (config_.relay_on_failure && services_)
|
|
172
|
+
relay_.store(services_->get<RelayService>());
|
|
173
|
+
|
|
160
174
|
if (running_.exchange(true)) return;
|
|
161
175
|
worker_ = std::thread([this] { loop(); });
|
|
162
176
|
}
|
|
163
177
|
|
|
164
178
|
void HolePunch::stop() {
|
|
179
|
+
relay_.store(nullptr);
|
|
165
180
|
if (!running_.exchange(false)) return;
|
|
166
181
|
cv_.notify_all();
|
|
167
182
|
if (worker_.joinable()) worker_.join();
|
|
@@ -178,14 +193,20 @@ bool HolePunch::punch(const PeerId& target) {
|
|
|
178
193
|
if (target == network_->local_id()) return false;
|
|
179
194
|
|
|
180
195
|
// Already reachable the ordinary way. Checked before any state is created so a
|
|
181
|
-
// caller can punch() freely on discovery without having to check first.
|
|
182
|
-
|
|
196
|
+
// caller can punch() freely on discovery without having to check first. A
|
|
197
|
+
// RELAYED peer is deliberately not "already reachable": punching it is how the
|
|
198
|
+
// circuit gets replaced by a direct link (see subsystems/relay.h).
|
|
199
|
+
for (const PeerId& id : directly_connected())
|
|
183
200
|
if (id == target) return false;
|
|
184
201
|
|
|
185
202
|
if (config_.skip_when_endpoint_dependent && external_ &&
|
|
186
203
|
external_->udp_mapping() == NatMapping::EndpointDependent) {
|
|
187
204
|
LOG_DEBUG("punch", "Not punching to " << target.short_hex()
|
|
188
205
|
<< ": our own mapping is per-destination (symmetric NAT)");
|
|
206
|
+
// Nothing about this will improve with time or retries: no endpoint we can
|
|
207
|
+
// advertise is the one the target's packets would arrive on. This is
|
|
208
|
+
// exactly the case a relayed path exists for.
|
|
209
|
+
escalate_to_relay(target);
|
|
189
210
|
return false;
|
|
190
211
|
}
|
|
191
212
|
|
|
@@ -193,6 +214,7 @@ bool HolePunch::punch(const PeerId& target) {
|
|
|
193
214
|
if (addresses.empty()) {
|
|
194
215
|
LOG_DEBUG("punch", "Not punching to " << target.short_hex()
|
|
195
216
|
<< ": no external datagram endpoint to advertise yet");
|
|
217
|
+
escalate_to_relay(target);
|
|
196
218
|
return false;
|
|
197
219
|
}
|
|
198
220
|
|
|
@@ -210,8 +232,11 @@ bool HolePunch::punch(const PeerId& target) {
|
|
|
210
232
|
}
|
|
211
233
|
|
|
212
234
|
if (!send_connect(target, /*opening=*/true, nullptr)) {
|
|
213
|
-
|
|
214
|
-
|
|
235
|
+
{
|
|
236
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
237
|
+
sessions_.erase(target); // nobody could carry it; nothing is in flight
|
|
238
|
+
}
|
|
239
|
+
escalate_to_relay(target);
|
|
215
240
|
return false;
|
|
216
241
|
}
|
|
217
242
|
LOG_DEBUG("punch", "Punch rendezvous started with " << target.short_hex());
|
|
@@ -461,10 +486,12 @@ void HolePunch::service_sessions() {
|
|
|
461
486
|
|
|
462
487
|
// Connected peers first, without the lock: a session whose target is now a peer
|
|
463
488
|
// succeeded, however it got there (our burst, or theirs arriving as inbound).
|
|
464
|
-
|
|
489
|
+
// Direct links only — a relayed one is what a punch is trying to replace.
|
|
490
|
+
std::vector<PeerId> connected = directly_connected();
|
|
465
491
|
|
|
466
492
|
struct Retry { PeerId target; PeerId via; bool have_via; };
|
|
467
493
|
std::vector<Retry> retry;
|
|
494
|
+
std::vector<PeerId> exhausted; ///< targets to hand on to the relay, once unlocked
|
|
468
495
|
{
|
|
469
496
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
470
497
|
|
|
@@ -496,7 +523,10 @@ void HolePunch::service_sessions() {
|
|
|
496
523
|
// first). Calling the target off here would drop the retries it is
|
|
497
524
|
// about to send, and block this node's own later punch to it, for
|
|
498
525
|
// the whole cooldown. So a responder just forgets the round.
|
|
499
|
-
if (initiator)
|
|
526
|
+
if (initiator) {
|
|
527
|
+
begin_cooldown(target);
|
|
528
|
+
exhausted.push_back(target);
|
|
529
|
+
}
|
|
500
530
|
it = sessions_.erase(it);
|
|
501
531
|
continue;
|
|
502
532
|
}
|
|
@@ -510,9 +540,52 @@ void HolePunch::service_sessions() {
|
|
|
510
540
|
|
|
511
541
|
for (const Retry& r : retry) {
|
|
512
542
|
if (send_connect(r.target, /*opening=*/true, r.have_via ? &r.via : nullptr)) continue;
|
|
513
|
-
|
|
514
|
-
|
|
543
|
+
{
|
|
544
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
545
|
+
sessions_.erase(r.target);
|
|
546
|
+
}
|
|
547
|
+
exhausted.push_back(r.target);
|
|
515
548
|
}
|
|
549
|
+
|
|
550
|
+
// The rungs above this one are spent: no address we can advertise got through.
|
|
551
|
+
// Handing the target on is the difference between a peer that is unreachable
|
|
552
|
+
// and one that is merely expensive to reach.
|
|
553
|
+
for (const PeerId& target : exhausted) escalate_to_relay(target);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
void HolePunch::escalate_to_relay(const PeerId& target) {
|
|
557
|
+
RelayService* relay = relay_.load();
|
|
558
|
+
if (!relay) return; // no Relay attached, or the fallback is off
|
|
559
|
+
if (!relay->connect_via_relay(target)) return;
|
|
560
|
+
|
|
561
|
+
LOG_DEBUG("punch", "Punching to " << target.short_hex()
|
|
562
|
+
<< " is not going to work; looking for a relay instead");
|
|
563
|
+
|
|
564
|
+
// The target has just changed hands, and the cooldown a give-up started must not
|
|
565
|
+
// outlive that. It exists to stop us hammering a peer we cannot reach — a job
|
|
566
|
+
// the relay now owns, with an attempt timeout and a cooldown of its own.
|
|
567
|
+
//
|
|
568
|
+
// And if that attempt lands, the circuit is precisely the new information that
|
|
569
|
+
// makes another punch worth trying: the two ends are peers at last, so the
|
|
570
|
+
// rendezvous can travel over the very circuit carrying them, which is what
|
|
571
|
+
// Relay asks for the moment the circuit comes up (see subsystems/relay.h).
|
|
572
|
+
// Leaving the cooldown standing would refuse that upgrade before it started —
|
|
573
|
+
// and nothing would ever ask again, so the circuit would outlive its purpose and
|
|
574
|
+
// keep costing a third node bandwidth for the life of the peer.
|
|
575
|
+
//
|
|
576
|
+
// Bounded, not a loop: an upgrade punch that fails lands here again, and
|
|
577
|
+
// connect_via_relay then answers false — the target is already a peer — so the
|
|
578
|
+
// fresh cooldown stands. Taken after the call, never around it: Relay resolves
|
|
579
|
+
// the reverse direction through us, and this mutex must not be held into it.
|
|
580
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
581
|
+
cooldown_.erase(target);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
std::vector<PeerId> HolePunch::directly_connected() const {
|
|
585
|
+
std::vector<PeerId> ids;
|
|
586
|
+
for (const PeerInfo& info : network_->peers())
|
|
587
|
+
if (info.transport != TransportKind::Relay) ids.push_back(info.id);
|
|
588
|
+
return ids;
|
|
516
589
|
}
|
|
517
590
|
|
|
518
591
|
bool HolePunch::in_cooldown(const PeerId& target) const {
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
#include "librats/peer/peer.h"
|
|
96
96
|
#include "librats/peer/peer_id.h"
|
|
97
97
|
#include "librats/subsystems/hole_punch_service.h"
|
|
98
|
+
#include "librats/subsystems/relay_service.h"
|
|
98
99
|
|
|
99
100
|
#include <atomic>
|
|
100
101
|
#include <chrono>
|
|
@@ -108,6 +109,7 @@
|
|
|
108
109
|
namespace librats {
|
|
109
110
|
|
|
110
111
|
class DialService;
|
|
112
|
+
class ServiceRegistry;
|
|
111
113
|
|
|
112
114
|
/// Published as HolePunchService, so a module that discovers a peer it cannot dial
|
|
113
115
|
/// (PeerExchange) can hand the id over without depending on this class.
|
|
@@ -163,6 +165,12 @@ public:
|
|
|
163
165
|
|
|
164
166
|
/// Session bookkeeping cadence — retries, timeouts, cooldown expiry.
|
|
165
167
|
std::chrono::milliseconds tick{250};
|
|
168
|
+
|
|
169
|
+
/// When a punch cannot be attempted at all, or has been given up on, hand
|
|
170
|
+
/// the target to RelayService — the next rung down the ladder (see
|
|
171
|
+
/// subsystems/relay.h). Costs nothing when no Relay is attached: the
|
|
172
|
+
/// service simply does not resolve.
|
|
173
|
+
bool relay_on_failure = true;
|
|
166
174
|
};
|
|
167
175
|
|
|
168
176
|
HolePunch();
|
|
@@ -235,6 +243,17 @@ private:
|
|
|
235
243
|
void service_sessions();
|
|
236
244
|
|
|
237
245
|
// — helpers —
|
|
246
|
+
/// Hand `target` to the relay module, if one is attached and the fallback is
|
|
247
|
+
/// on. Called when a punch is impossible or has run out of attempts — the
|
|
248
|
+
/// point at which a relayed path stops being the worse option and becomes the
|
|
249
|
+
/// only one. Retires the target's cooldown when the relay takes it on, so that
|
|
250
|
+
/// the upgrade punch a circuit asks for is not refused by the very give-up that
|
|
251
|
+
/// produced the circuit. Caller must NOT hold the mutex.
|
|
252
|
+
void escalate_to_relay(const PeerId& target);
|
|
253
|
+
/// Peers reachable over a DIRECT link. A relayed peer is deliberately not one:
|
|
254
|
+
/// a punch to it is an upgrade in progress, and counting the circuit as success
|
|
255
|
+
/// would retire the session before it had done anything.
|
|
256
|
+
std::vector<PeerId> directly_connected() const;
|
|
238
257
|
std::vector<Address> own_punch_addresses() const;
|
|
239
258
|
bool relay_budget_ok(const PeerId& from);
|
|
240
259
|
bool in_cooldown(const PeerId& target) const; ///< caller holds mutex_
|
|
@@ -244,6 +263,11 @@ private:
|
|
|
244
263
|
PeerNetwork* network_ = nullptr;
|
|
245
264
|
DialService* dialer_ = nullptr;
|
|
246
265
|
ExternalAddressService* external_ = nullptr;
|
|
266
|
+
ServiceRegistry* services_ = nullptr;
|
|
267
|
+
/// Resolved in start(), not attach(): Relay may be attached after us, and every
|
|
268
|
+
/// attach() runs before any start(). Atomic because it is read from reactor
|
|
269
|
+
/// threads and the worker alike.
|
|
270
|
+
std::atomic<RelayService*> relay_{nullptr};
|
|
247
271
|
|
|
248
272
|
std::atomic<bool> running_{false};
|
|
249
273
|
std::atomic<uint64_t> punches_started_{0};
|