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,208 @@
|
|
|
1
|
+
#include "librats/transport/relay_link.h"
|
|
2
|
+
|
|
3
|
+
#include "librats/core/io_poller.h" // PollIn / PollOut / PollErr
|
|
4
|
+
#include "librats/util/logger.h"
|
|
5
|
+
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <utility>
|
|
9
|
+
|
|
10
|
+
namespace librats {
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
/// Slices one data message gathers from the caller's list. The send buffer hands
|
|
15
|
+
/// out a length prefix and a body per queued frame, so this covers a run of frames
|
|
16
|
+
/// rather than a single one; a longer backlog than this simply comes back on the
|
|
17
|
+
/// next turn of Connection::flush.
|
|
18
|
+
constexpr size_t kMaxParts = 16;
|
|
19
|
+
|
|
20
|
+
} // namespace
|
|
21
|
+
|
|
22
|
+
Circuit::Circuit(uint32_t id, std::shared_ptr<CircuitCarrier> carrier, bool open,
|
|
23
|
+
uint32_t recv_window, uint32_t peer_window)
|
|
24
|
+
: id_(id),
|
|
25
|
+
carrier_(std::move(carrier)),
|
|
26
|
+
state_(open ? State::Open : State::Pending),
|
|
27
|
+
recv_window_(recv_window == 0 ? kDefaultWindow : recv_window),
|
|
28
|
+
send_credit_(open ? peer_window : 0) {}
|
|
29
|
+
|
|
30
|
+
std::shared_ptr<Circuit> Circuit::opening(uint32_t id, std::shared_ptr<CircuitCarrier> carrier,
|
|
31
|
+
uint32_t recv_window) {
|
|
32
|
+
return std::shared_ptr<Circuit>(
|
|
33
|
+
new Circuit(id, std::move(carrier), /*open=*/false, recv_window, /*peer_window=*/0));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
std::shared_ptr<Circuit> Circuit::accepted(uint32_t id, std::shared_ptr<CircuitCarrier> carrier,
|
|
37
|
+
uint32_t peer_window, uint32_t recv_window) {
|
|
38
|
+
return std::shared_ptr<Circuit>(
|
|
39
|
+
new Circuit(id, std::move(carrier), /*open=*/true, recv_window, peer_window));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Fed by the relay module ─────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
uint32_t Circuit::on_data(ByteView bytes) {
|
|
45
|
+
if (state_ == State::Closed || bytes.empty()) return 0;
|
|
46
|
+
|
|
47
|
+
// The window is the ONE thing bounding what this circuit can cost us and the
|
|
48
|
+
// relay carrying it, so a peer that sends past what it was granted is not
|
|
49
|
+
// merely impolite — it is doing the thing the window exists to prevent. Fail
|
|
50
|
+
// the circuit rather than accept the bytes.
|
|
51
|
+
if (bytes.size() > recv_window_ - recv_in_flight_) {
|
|
52
|
+
LOG_WARN("relay", "Circuit " << id_ << " overran its receive window ("
|
|
53
|
+
<< bytes.size() << " B with " << (recv_window_ - recv_in_flight_)
|
|
54
|
+
<< " B granted); failing it");
|
|
55
|
+
return on_closed(CloseReason::ProtocolError, /*orderly=*/false);
|
|
56
|
+
}
|
|
57
|
+
recv_in_flight_ += static_cast<uint32_t>(bytes.size());
|
|
58
|
+
|
|
59
|
+
// Hand back the consumed prefix before growing: the connection normally drains
|
|
60
|
+
// the inbox to empty on every readable event, so this is nearly always a reset
|
|
61
|
+
// of an already-empty buffer rather than a move.
|
|
62
|
+
if (inbox_read_ == inbox_.size()) {
|
|
63
|
+
inbox_.clear();
|
|
64
|
+
inbox_read_ = 0;
|
|
65
|
+
} else if (inbox_read_ >= kCompactThreshold) {
|
|
66
|
+
inbox_.erase(inbox_.begin(), inbox_.begin() + static_cast<std::ptrdiff_t>(inbox_read_));
|
|
67
|
+
inbox_read_ = 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
inbox_.insert(inbox_.end(), bytes.begin(), bytes.end());
|
|
71
|
+
return PollIn;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
uint32_t Circuit::on_accept(uint32_t peer_window) {
|
|
75
|
+
if (state_ != State::Pending) return 0;
|
|
76
|
+
state_ = State::Open;
|
|
77
|
+
send_credit_ = peer_window;
|
|
78
|
+
// Unconditional, unlike the other openings below: the Connection is still in
|
|
79
|
+
// Connecting and has nothing queued, so want_write_ is false — yet this is
|
|
80
|
+
// precisely the event it is waiting for to finish connecting and start its
|
|
81
|
+
// handshake (see Connection::on_writable).
|
|
82
|
+
return PollOut;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
uint32_t Circuit::on_credit(uint32_t bytes) {
|
|
86
|
+
if (state_ == State::Closed || bytes == 0) return 0;
|
|
87
|
+
// Saturate rather than wrap: a far end that grants nonsense should cost us
|
|
88
|
+
// nothing worse than an over-generous window, and the receiver's own window
|
|
89
|
+
// check is what actually bounds the traffic.
|
|
90
|
+
if (bytes > UINT32_MAX - send_credit_) send_credit_ = UINT32_MAX;
|
|
91
|
+
else send_credit_ += bytes;
|
|
92
|
+
|
|
93
|
+
if (!credit_blocked_) return 0;
|
|
94
|
+
credit_blocked_ = false;
|
|
95
|
+
return writable_event();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
uint32_t Circuit::on_closed(CloseReason reason, bool orderly) {
|
|
99
|
+
if (state_ == State::Closed) return 0;
|
|
100
|
+
state_ = State::Closed;
|
|
101
|
+
reason_ = reason;
|
|
102
|
+
orderly_ = orderly;
|
|
103
|
+
// An orderly close still owes the application whatever the far end sent before
|
|
104
|
+
// it: read() drains the inbox first and only then reports the end of stream, so
|
|
105
|
+
// what this needs is a readable event, not an error. That is the same contract
|
|
106
|
+
// the other links keep (see Link::read).
|
|
107
|
+
return orderly ? PollIn : PollErr;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
uint32_t Circuit::on_carrier_writable() {
|
|
111
|
+
if (!carrier_blocked_) return 0;
|
|
112
|
+
carrier_blocked_ = false;
|
|
113
|
+
return writable_event();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
uint32_t Circuit::writable_event() const noexcept {
|
|
117
|
+
if (blocked() || !want_write_ || state_ != State::Open) return 0;
|
|
118
|
+
return PollOut;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Driven by the Connection ────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
Link::IoResult Circuit::read(ByteSpan into) {
|
|
124
|
+
const size_t available = inbox_.size() - inbox_read_;
|
|
125
|
+
const size_t n = (std::min)(available, into.size());
|
|
126
|
+
if (n > 0) {
|
|
127
|
+
std::memcpy(into.data(), inbox_.data() + inbox_read_, n);
|
|
128
|
+
inbox_read_ += n;
|
|
129
|
+
if (inbox_read_ == inbox_.size()) {
|
|
130
|
+
inbox_.clear();
|
|
131
|
+
inbox_read_ = 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// These bytes are out of the pipe, so the far end may put that much back
|
|
135
|
+
// in. Granted in batches: one small message per half-window rather than one
|
|
136
|
+
// per chunk, which is what keeps the credit scheme's overhead invisible.
|
|
137
|
+
recv_in_flight_ -= static_cast<uint32_t>(n);
|
|
138
|
+
recv_ungranted_ += static_cast<uint32_t>(n);
|
|
139
|
+
if (state_ != State::Closed && recv_ungranted_ >= recv_window_ / kCreditGrantFraction) {
|
|
140
|
+
carrier_->circuit_send_credit(id_, recv_ungranted_);
|
|
141
|
+
recv_ungranted_ = 0;
|
|
142
|
+
}
|
|
143
|
+
return {n, Link::Status::Ok};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Drained. Only now may the end of the stream be reported — data and
|
|
147
|
+
// end-of-stream are never delivered together.
|
|
148
|
+
if (state_ == State::Closed)
|
|
149
|
+
return {0, orderly_ ? Link::Status::Closed : Link::Status::Error};
|
|
150
|
+
return {0, Link::Status::WouldBlock};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Link::IoResult Circuit::write(const ByteView* slices, size_t count) {
|
|
154
|
+
if (state_ == State::Closed) return {0, Link::Status::Error};
|
|
155
|
+
// Pending: opened but not yet accepted. Nothing may go out — and nothing tries
|
|
156
|
+
// to, since the Connection is still Connecting.
|
|
157
|
+
if (state_ != State::Open) return {0, Link::Status::WouldBlock};
|
|
158
|
+
if (blocked()) return {0, Link::Status::WouldBlock};
|
|
159
|
+
|
|
160
|
+
const size_t budget = (std::min)(static_cast<size_t>(send_credit_), kMaxDataChunk);
|
|
161
|
+
if (budget == 0) {
|
|
162
|
+
credit_blocked_ = true;
|
|
163
|
+
return {0, Link::Status::WouldBlock};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// One data message per call, gathered across as many of the caller's slices as
|
|
167
|
+
// fit. Connection::flush loops while a link keeps reporting Ok, so a backlog
|
|
168
|
+
// larger than one chunk simply comes back around — no loop is needed here, and
|
|
169
|
+
// each turn of that loop re-checks the credit and the carrier.
|
|
170
|
+
ByteView parts[kMaxParts];
|
|
171
|
+
size_t nparts = 0;
|
|
172
|
+
size_t taken = 0;
|
|
173
|
+
for (size_t i = 0; i < count && taken < budget && nparts < kMaxParts; ++i) {
|
|
174
|
+
if (slices[i].empty()) continue;
|
|
175
|
+
const size_t take = (std::min)(slices[i].size(), budget - taken);
|
|
176
|
+
parts[nparts++] = ByteView(slices[i].data(), take);
|
|
177
|
+
taken += take;
|
|
178
|
+
}
|
|
179
|
+
if (taken == 0) return {0, Link::Status::WouldBlock};
|
|
180
|
+
|
|
181
|
+
send_credit_ -= static_cast<uint32_t>(taken);
|
|
182
|
+
// The carrier queues what it is given either way; false is "stop and wait", so
|
|
183
|
+
// the bytes are ours to report as written and the block applies to the NEXT
|
|
184
|
+
// call (see CircuitCarrier::circuit_send_data).
|
|
185
|
+
if (!carrier_->circuit_send_data(id_, parts, nparts)) carrier_blocked_ = true;
|
|
186
|
+
return {taken, Link::Status::Ok};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
void Circuit::shutdown(CloseReason reason) {
|
|
190
|
+
if (!close_sent_) {
|
|
191
|
+
close_sent_ = true;
|
|
192
|
+
carrier_->circuit_send_close(id_, reason);
|
|
193
|
+
}
|
|
194
|
+
if (state_ != State::Closed) {
|
|
195
|
+
state_ = State::Closed;
|
|
196
|
+
reason_ = reason;
|
|
197
|
+
orderly_ = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
void Circuit::release() {
|
|
202
|
+
if (released_) return;
|
|
203
|
+
released_ = true;
|
|
204
|
+
state_ = State::Closed;
|
|
205
|
+
carrier_->circuit_released(id_);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
} // namespace librats
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file relay_link.h
|
|
5
|
+
* @brief A peer connection carried inside another peer's connection.
|
|
6
|
+
*
|
|
7
|
+
* When two nodes cannot reach each other directly — a symmetric NAT on one side,
|
|
8
|
+
* a network that swallows UDP, a hole punch that simply did not land — the one
|
|
9
|
+
* thing left is to route their bytes through a node they can both reach. The
|
|
10
|
+
* question is *where* in the stack to do that, and this file is the answer: at the
|
|
11
|
+
* very bottom, as one more way of obtaining the ordered, reliable byte stream a
|
|
12
|
+
* Connection runs on (see link.h).
|
|
13
|
+
*
|
|
14
|
+
* That choice is what makes a relayed peer indistinguishable from a direct one
|
|
15
|
+
* everywhere above:
|
|
16
|
+
*
|
|
17
|
+
* - the Noise_XX handshake runs END TO END between the two peers, so the relay
|
|
18
|
+
* carries ciphertext it cannot read, cannot forge and cannot replay into
|
|
19
|
+
* anything. It is a pipe, not a party;
|
|
20
|
+
* - the self-certifying PeerId and the protocol_id bound into the handshake are
|
|
21
|
+
* checked exactly as usual, so a relay cannot impersonate either end;
|
|
22
|
+
* - block framing, the send queue and its high-water mark, the establish
|
|
23
|
+
* deadline, identify, and the peer table's duplicate resolution all apply
|
|
24
|
+
* unchanged, because there is nothing here for them to know about;
|
|
25
|
+
* - every subsystem — pub/sub, file transfer, PEX — works over a relayed peer
|
|
26
|
+
* without a line of its own.
|
|
27
|
+
*
|
|
28
|
+
* Relaying at the message level instead (a third node forwarding application
|
|
29
|
+
* frames) would have meant a second handshake, a second authentication scheme and
|
|
30
|
+
* a second backpressure story — and would have handed the relay the plaintext.
|
|
31
|
+
*
|
|
32
|
+
* ── The two halves ──────────────────────────────────────────────────────────
|
|
33
|
+
* `Circuit` is the state of one relayed connection: the bytes in flight in each
|
|
34
|
+
* direction and the credit windows that bound them. `RelayLink` is the paper-thin
|
|
35
|
+
* adapter that presents that state to a Connection as a Link. They are separate
|
|
36
|
+
* because the Connection owns its Link outright (unique_ptr, destroyed on the
|
|
37
|
+
* reactor thread) while the module driving the relay protocol has to keep hold of
|
|
38
|
+
* the same circuit to feed it — so the state is shared and only the adapter is
|
|
39
|
+
* owned.
|
|
40
|
+
*
|
|
41
|
+
* `CircuitCarrier` is the other direction: what the circuit needs from whoever
|
|
42
|
+
* speaks the relay protocol. It is deliberately tiny and knows nothing about the
|
|
43
|
+
* wire format, which stays entirely in the module that implements it.
|
|
44
|
+
*
|
|
45
|
+
* ── Flow control, and why it is not optional ────────────────────────────────
|
|
46
|
+
* A relay reads from one peer and writes to another, and it cannot stop reading:
|
|
47
|
+
* the reactor drains a readable connection to the end. So without a bound, a slow
|
|
48
|
+
* receiver makes the relay's send queue to it grow until that peer is dropped as a
|
|
49
|
+
* slow consumer — losing all of its traffic, not just the circuit's, and letting
|
|
50
|
+
* any pair of peers spend a stranger's memory at will.
|
|
51
|
+
*
|
|
52
|
+
* The bound is an end-to-end credit window, granted by the receiver and spent by
|
|
53
|
+
* the sender, exactly as in a stream multiplexer. Each end advertises how much it
|
|
54
|
+
* is willing to have in flight; the sender may not exceed it, and the receiver
|
|
55
|
+
* grants more as it consumes. Two things follow:
|
|
56
|
+
*
|
|
57
|
+
* - the relay can never hold more than about one window per circuit, whatever
|
|
58
|
+
* either end does, so its memory is bounded by configuration rather than by
|
|
59
|
+
* the goodwill of its users;
|
|
60
|
+
* - a slow path makes the circuit slow instead of making it fail. The window
|
|
61
|
+
* only ever binds when the far end is not keeping up, which is precisely when
|
|
62
|
+
* something ought to give.
|
|
63
|
+
*
|
|
64
|
+
* Credit is returned as the bytes are read OUT of the circuit, not as they arrive,
|
|
65
|
+
* and in batches (kCreditGrantFraction of the window) so the grant costs one small
|
|
66
|
+
* message per window-worth of data rather than one per chunk.
|
|
67
|
+
*
|
|
68
|
+
* ── Threading ───────────────────────────────────────────────────────────────
|
|
69
|
+
* A Circuit is touched only by the reactor thread that owns its Connection, which
|
|
70
|
+
* is by construction the same thread that owns the carrier's connection (see
|
|
71
|
+
* node/circuit_service.h). It therefore holds no locks and no atomics, like
|
|
72
|
+
* everything else on this path. The module that owns circuits may keep its table
|
|
73
|
+
* of them under a mutex; the state below must not be reached from off that one
|
|
74
|
+
* thread.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
#include "librats/util/rats_export.h"
|
|
78
|
+
#include "librats/core/bytes.h"
|
|
79
|
+
#include "librats/core/types.h"
|
|
80
|
+
#include "librats/transport/link.h"
|
|
81
|
+
|
|
82
|
+
#include <cstdint>
|
|
83
|
+
#include <memory>
|
|
84
|
+
|
|
85
|
+
namespace librats {
|
|
86
|
+
|
|
87
|
+
/// What a circuit needs from whoever speaks the relay protocol on the wire.
|
|
88
|
+
///
|
|
89
|
+
/// Implemented by the relay module; called by Circuit on the reactor thread. None
|
|
90
|
+
/// of these say anything about the wire format — the implementer frames the
|
|
91
|
+
/// message and hands it to the carrier peer — so this header stays a transport
|
|
92
|
+
/// header with no protocol in it.
|
|
93
|
+
class CircuitCarrier {
|
|
94
|
+
public:
|
|
95
|
+
virtual ~CircuitCarrier() = default;
|
|
96
|
+
|
|
97
|
+
/// Send `count` slices (together no larger than kMaxDataChunk) toward the far
|
|
98
|
+
/// end as one data message.
|
|
99
|
+
/// @return whether the carrier's own send queue still has room. False does NOT
|
|
100
|
+
/// mean the data was dropped — it was queued like any other message —
|
|
101
|
+
/// it means stop, and say so again through on_carrier_writable() once
|
|
102
|
+
/// the queue drains. Exactly what PeerNetwork::send answers, which is
|
|
103
|
+
/// what an implementation normally forwards.
|
|
104
|
+
virtual bool circuit_send_data(uint32_t circuit, const ByteView* slices, size_t count) = 0;
|
|
105
|
+
|
|
106
|
+
/// Grant the far end `bytes` more of our receive window.
|
|
107
|
+
virtual void circuit_send_credit(uint32_t circuit, uint32_t bytes) = 0;
|
|
108
|
+
|
|
109
|
+
/// Tell the far end (and the relay in between) that this circuit is over.
|
|
110
|
+
/// Called at most once per circuit.
|
|
111
|
+
virtual void circuit_send_close(uint32_t circuit, CloseReason reason) = 0;
|
|
112
|
+
|
|
113
|
+
/// The Link is gone: its Connection has been destroyed. The circuit will not
|
|
114
|
+
/// be read or written again and its bookkeeping can be dropped.
|
|
115
|
+
///
|
|
116
|
+
/// The only one of these that is NOT guaranteed to arrive on the reactor
|
|
117
|
+
/// thread. It normally does — a Connection is destroyed by its reactor — but a
|
|
118
|
+
/// link handed to a reactor that never ran the adoption (the node was stopping)
|
|
119
|
+
/// is released when the task queue is destroyed instead, on whichever thread
|
|
120
|
+
/// takes the reactor down. An implementation must therefore be safe to call
|
|
121
|
+
/// here from any thread, and must not assume the circuit ever opened.
|
|
122
|
+
virtual void circuit_released(uint32_t circuit) = 0;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/// One relayed connection's byte stream and credit windows. See the file comment.
|
|
126
|
+
class RATS_API Circuit {
|
|
127
|
+
public:
|
|
128
|
+
/// Largest data message a circuit puts on the wire. Big enough that the
|
|
129
|
+
/// per-message overhead (a relay header, a frame header, an AEAD tag, and the
|
|
130
|
+
/// relay's own copy) is noise against the payload; small enough that one
|
|
131
|
+
/// circuit cannot monopolise the carrier's send queue for long, and that the
|
|
132
|
+
/// relay's per-message work stays granular.
|
|
133
|
+
static constexpr size_t kMaxDataChunk = 16 * 1024;
|
|
134
|
+
|
|
135
|
+
/// Receive window a circuit advertises unless told otherwise. This is the
|
|
136
|
+
/// memory ONE circuit can make the relay (and this node) hold, so it is the
|
|
137
|
+
/// knob that turns "how fast can a relayed peer go" into "how much is a relay
|
|
138
|
+
/// asked to carry": a 256 KiB window keeps a 100 ms round trip busy at roughly
|
|
139
|
+
/// 20 Mbit/s, which is far more than a fallback path is meant to be used for.
|
|
140
|
+
static constexpr uint32_t kDefaultWindow = 256 * 1024;
|
|
141
|
+
|
|
142
|
+
/// Credit is granted back once this much of the window has been consumed.
|
|
143
|
+
/// Halves mean one small grant per half-window of data, and leave the sender
|
|
144
|
+
/// the other half to work with while the grant is in flight.
|
|
145
|
+
static constexpr uint32_t kCreditGrantFraction = 2;
|
|
146
|
+
|
|
147
|
+
/// A circuit we are opening: the request is out, the far end has not answered.
|
|
148
|
+
/// Nothing may be written until on_accept() brings the window it will receive.
|
|
149
|
+
///
|
|
150
|
+
/// @param id the circuit's id on the carrier link (see the relay module).
|
|
151
|
+
/// @param carrier how outbound bytes leave; a shared_ptr because a Connection
|
|
152
|
+
/// can outlive the module that opened the circuit (subsystems
|
|
153
|
+
/// stop before the reactors do).
|
|
154
|
+
/// @param recv_window what we are willing to have in flight toward us.
|
|
155
|
+
static std::shared_ptr<Circuit> opening(uint32_t id, std::shared_ptr<CircuitCarrier> carrier,
|
|
156
|
+
uint32_t recv_window = kDefaultWindow);
|
|
157
|
+
|
|
158
|
+
/// A circuit opened TO us: the far end is already there and its request carried
|
|
159
|
+
/// the window it will accept, so the stream is open from the first instant.
|
|
160
|
+
///
|
|
161
|
+
/// Two factories rather than one flag plus a window that is sometimes ignored,
|
|
162
|
+
/// because "the circuit is open" and "we know what the far end will take" are
|
|
163
|
+
/// the same fact — an open circuit whose send window nobody set would be a
|
|
164
|
+
/// stream that silently refuses to carry anything.
|
|
165
|
+
static std::shared_ptr<Circuit> accepted(uint32_t id, std::shared_ptr<CircuitCarrier> carrier,
|
|
166
|
+
uint32_t peer_window,
|
|
167
|
+
uint32_t recv_window = kDefaultWindow);
|
|
168
|
+
|
|
169
|
+
Circuit(const Circuit&) = delete;
|
|
170
|
+
Circuit& operator=(const Circuit&) = delete;
|
|
171
|
+
|
|
172
|
+
uint32_t id() const noexcept { return id_; }
|
|
173
|
+
/// What we advertise to the far end as our receive window.
|
|
174
|
+
uint32_t recv_window() const noexcept { return recv_window_; }
|
|
175
|
+
bool is_open() const noexcept { return state_ == State::Open; }
|
|
176
|
+
bool is_closed() const noexcept { return state_ == State::Closed; }
|
|
177
|
+
|
|
178
|
+
// ── Fed by the relay module (reactor thread) ────────────────────────────
|
|
179
|
+
//
|
|
180
|
+
// Each of these returns the poll-equivalent events the circuit's Connection
|
|
181
|
+
// should now be given (PollIn / PollOut / PollErr), or 0 for none. The caller
|
|
182
|
+
// dispatches them — the circuit deliberately knows nothing about reactors.
|
|
183
|
+
// Returning the mask rather than raising it keeps that dependency out of the
|
|
184
|
+
// transport layer and lets the caller batch several changes into one wake.
|
|
185
|
+
|
|
186
|
+
/// Bytes arrived from the far end. A peer that overruns the window it was
|
|
187
|
+
/// granted is a protocol error and the circuit is failed rather than allowed
|
|
188
|
+
/// to grow: that window is the whole bound on what a circuit can cost.
|
|
189
|
+
uint32_t on_data(ByteView bytes);
|
|
190
|
+
|
|
191
|
+
/// The far end accepted the circuit and advertised `peer_window` bytes of
|
|
192
|
+
/// receive window. No-op unless the circuit is still pending.
|
|
193
|
+
uint32_t on_accept(uint32_t peer_window);
|
|
194
|
+
|
|
195
|
+
/// The far end granted `bytes` more of send window.
|
|
196
|
+
uint32_t on_credit(uint32_t bytes);
|
|
197
|
+
|
|
198
|
+
/// The circuit is over. `orderly` means the far end closed cleanly, so
|
|
199
|
+
/// whatever it already sent is still delivered before the end of stream is
|
|
200
|
+
/// reported; anything else fails the connection at once.
|
|
201
|
+
uint32_t on_closed(CloseReason reason, bool orderly);
|
|
202
|
+
|
|
203
|
+
/// The carrier's send queue, which had filled up, has room again.
|
|
204
|
+
uint32_t on_carrier_writable();
|
|
205
|
+
|
|
206
|
+
// ── Driven by the Connection through RelayLink (reactor thread) ─────────
|
|
207
|
+
|
|
208
|
+
Link::IoResult read(ByteSpan into);
|
|
209
|
+
Link::IoResult write(const ByteView* slices, size_t count);
|
|
210
|
+
void want_write(bool on) noexcept { want_write_ = on; }
|
|
211
|
+
CloseReason close_reason() const noexcept { return reason_; }
|
|
212
|
+
/// Begin teardown from our side: tell the far end, then refuse further I/O.
|
|
213
|
+
void shutdown(CloseReason reason);
|
|
214
|
+
/// The Link is being destroyed; release the carrier's bookkeeping. Idempotent.
|
|
215
|
+
void release();
|
|
216
|
+
|
|
217
|
+
/// Bytes waiting to be read out (diagnostics and tests).
|
|
218
|
+
size_t pending_input() const noexcept { return inbox_.size() - inbox_read_; }
|
|
219
|
+
/// Send window still available (diagnostics and tests).
|
|
220
|
+
uint32_t send_credit() const noexcept { return send_credit_; }
|
|
221
|
+
|
|
222
|
+
private:
|
|
223
|
+
Circuit(uint32_t id, std::shared_ptr<CircuitCarrier> carrier, bool open,
|
|
224
|
+
uint32_t recv_window, uint32_t peer_window);
|
|
225
|
+
|
|
226
|
+
enum class State : uint8_t {
|
|
227
|
+
Pending, ///< outbound: opened, waiting for the far end to accept
|
|
228
|
+
Open, ///< bytes may flow
|
|
229
|
+
Closed, ///< no further I/O; read() drains what is left, then reports the end
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/// Consumed prefix tolerated before the inbox is compacted. The connection
|
|
233
|
+
/// normally drains the inbox to empty on every readable event, which resets it
|
|
234
|
+
/// for free; this only bounds the pathological case where it does not.
|
|
235
|
+
static constexpr size_t kCompactThreshold = 64 * 1024;
|
|
236
|
+
|
|
237
|
+
bool blocked() const noexcept { return credit_blocked_ || carrier_blocked_; }
|
|
238
|
+
/// PollOut, but only if anyone is waiting for it — see want_write().
|
|
239
|
+
uint32_t writable_event() const noexcept;
|
|
240
|
+
|
|
241
|
+
uint32_t id_;
|
|
242
|
+
std::shared_ptr<CircuitCarrier> carrier_;
|
|
243
|
+
State state_;
|
|
244
|
+
CloseReason reason_ = CloseReason::PeerReset;
|
|
245
|
+
bool orderly_ = false; ///< the far end closed cleanly
|
|
246
|
+
bool released_ = false;
|
|
247
|
+
bool close_sent_ = false;
|
|
248
|
+
|
|
249
|
+
// — inbound —
|
|
250
|
+
Bytes inbox_;
|
|
251
|
+
size_t inbox_read_ = 0; ///< consumed prefix of inbox_
|
|
252
|
+
uint32_t recv_window_; ///< what we advertised to the far end
|
|
253
|
+
uint32_t recv_in_flight_ = 0; ///< delivered to us and not yet granted back
|
|
254
|
+
uint32_t recv_ungranted_ = 0; ///< consumed, owed back to the far end
|
|
255
|
+
|
|
256
|
+
// — outbound —
|
|
257
|
+
uint32_t send_credit_ = 0; ///< bytes the far end still allows us to send
|
|
258
|
+
bool want_write_ = false; ///< the Connection has more to write
|
|
259
|
+
bool credit_blocked_ = false; ///< a write stopped for want of credit
|
|
260
|
+
bool carrier_blocked_ = false;///< a write stopped on the carrier's queue
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/// The Link a Connection holds for a relayed circuit.
|
|
264
|
+
///
|
|
265
|
+
/// Deliberately empty of logic: the state has to be reachable by the module that
|
|
266
|
+
/// drives the relay protocol, which the Connection's unique_ptr would not allow,
|
|
267
|
+
/// so everything lives in the shared Circuit and this is the handle.
|
|
268
|
+
class RATS_API RelayLink final : public Link {
|
|
269
|
+
public:
|
|
270
|
+
explicit RelayLink(std::shared_ptr<Circuit> circuit) : circuit_(std::move(circuit)) {}
|
|
271
|
+
~RelayLink() override { circuit_->release(); }
|
|
272
|
+
|
|
273
|
+
TransportKind kind() const noexcept override { return TransportKind::Relay; }
|
|
274
|
+
|
|
275
|
+
IoResult read(ByteSpan into) override { return circuit_->read(into); }
|
|
276
|
+
IoResult write(const ByteView* slices, size_t count) override {
|
|
277
|
+
return circuit_->write(slices, count);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/// A circuit is "connected" once the far end has accepted it. The Connection
|
|
281
|
+
/// only asks after a PollOut, which the relay module raises on acceptance; a
|
|
282
|
+
/// refusal arrives as PollErr instead and never reaches here.
|
|
283
|
+
bool connect_completed() override { return circuit_->is_open(); }
|
|
284
|
+
|
|
285
|
+
void want_write(bool on) override { circuit_->want_write(on); }
|
|
286
|
+
|
|
287
|
+
/// No endpoint of its own, and deliberately not the relay's: identify pairs the
|
|
288
|
+
/// address a link reports with the peer's advertised listen port to build a
|
|
289
|
+
/// dialable address, and NatStatus reads it as our own NAT mapping. Both would
|
|
290
|
+
/// be wrong here — the address on this path belongs to the node in the middle.
|
|
291
|
+
/// Reporting nothing is what keeps a relayed peer out of that machinery.
|
|
292
|
+
std::optional<Address> remote_endpoint() const override { return std::nullopt; }
|
|
293
|
+
|
|
294
|
+
CloseReason error_reason() const override { return circuit_->close_reason(); }
|
|
295
|
+
void close(CloseReason reason) override { circuit_->shutdown(reason); }
|
|
296
|
+
|
|
297
|
+
Circuit& circuit() noexcept { return *circuit_; }
|
|
298
|
+
|
|
299
|
+
private:
|
|
300
|
+
std::shared_ptr<Circuit> circuit_;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
} // namespace librats
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
#endif
|
|
30
30
|
#else
|
|
31
31
|
#include <unistd.h>
|
|
32
|
+
#if defined(__ANDROID__)
|
|
33
|
+
#include <android/log.h>
|
|
34
|
+
#endif
|
|
32
35
|
#endif
|
|
33
36
|
|
|
34
37
|
namespace librats {
|
|
@@ -149,7 +152,24 @@ public:
|
|
|
149
152
|
if (level < min_level_.load(std::memory_order_relaxed)) {
|
|
150
153
|
return;
|
|
151
154
|
}
|
|
152
|
-
|
|
155
|
+
|
|
156
|
+
#if defined(__ANDROID__)
|
|
157
|
+
// An Android app's stdout/stderr are discarded by default, so the cout/cerr
|
|
158
|
+
// sink below is invisible in logcat. Route console output through the platform
|
|
159
|
+
// logger instead: logcat supplies its own timestamp, level and coloring, so the
|
|
160
|
+
// bare message goes out under the module name as the tag. The file sink is
|
|
161
|
+
// unaffected and stays identical to every other platform.
|
|
162
|
+
if (console_logging_enabled_) {
|
|
163
|
+
__android_log_print(get_android_priority(level),
|
|
164
|
+
module.empty() ? "librats" : module.c_str(),
|
|
165
|
+
"%s", message.c_str());
|
|
166
|
+
}
|
|
167
|
+
if (file_logging_enabled_ && log_file_.is_open()) {
|
|
168
|
+
write_to_file(level, module, message);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
#endif
|
|
172
|
+
|
|
153
173
|
// Prepare console output with colors
|
|
154
174
|
std::ostringstream console_oss;
|
|
155
175
|
|
|
@@ -239,6 +259,18 @@ private:
|
|
|
239
259
|
default: return "UNKNOWN";
|
|
240
260
|
}
|
|
241
261
|
}
|
|
262
|
+
|
|
263
|
+
#if defined(__ANDROID__)
|
|
264
|
+
int get_android_priority(LogLevel level) {
|
|
265
|
+
switch (level) {
|
|
266
|
+
case LogLevel::DEBUG: return ANDROID_LOG_DEBUG;
|
|
267
|
+
case LogLevel::INFO: return ANDROID_LOG_INFO;
|
|
268
|
+
case LogLevel::WARN: return ANDROID_LOG_WARN;
|
|
269
|
+
case LogLevel::ERROR: return ANDROID_LOG_ERROR;
|
|
270
|
+
default: return ANDROID_LOG_INFO;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
#endif
|
|
242
274
|
|
|
243
275
|
std::string get_color_code(LogLevel level) {
|
|
244
276
|
if (!colors_enabled_ || !is_terminal_) return "";
|
|
@@ -411,10 +443,9 @@ private:
|
|
|
411
443
|
std::string old_name = log_file_path_ + "." + std::to_string(i);
|
|
412
444
|
std::string new_name = log_file_path_ + "." + std::to_string(i + 1);
|
|
413
445
|
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
446
|
+
// std::rename() does not replace an existing destination on Windows,
|
|
447
|
+
// so clear the target first - not just the oldest file.
|
|
448
|
+
std::remove(new_name.c_str());
|
|
418
449
|
|
|
419
450
|
// Rename old file to new name
|
|
420
451
|
std::rename(old_name.c_str(), new_name.c_str());
|
|
@@ -422,6 +453,7 @@ private:
|
|
|
422
453
|
|
|
423
454
|
// Move current log file to .1
|
|
424
455
|
std::string backup_name = log_file_path_ + ".1";
|
|
456
|
+
std::remove(backup_name.c_str());
|
|
425
457
|
std::rename(log_file_path_.c_str(), backup_name.c_str());
|
|
426
458
|
}
|
|
427
459
|
|
|
@@ -25,7 +25,21 @@
|
|
|
25
25
|
#include <cerrno>
|
|
26
26
|
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
|
|
27
27
|
defined(__OpenBSD__) || defined(__DragonFly__)
|
|
28
|
+
#ifdef __APPLE__
|
|
29
|
+
#include <TargetConditionals.h>
|
|
30
|
+
#endif
|
|
31
|
+
// Apple ships <net/route.h> in the macOS SDK only. On iOS the PF_ROUTE
|
|
32
|
+
// socket itself is usable but the message declarations are not public, so
|
|
33
|
+
// there is nothing to parse against — those targets take the polling
|
|
34
|
+
// fallback at the bottom of this file (backend_start() returns false).
|
|
35
|
+
// A native backend for iOS belongs on Network.framework's nw_path_monitor
|
|
36
|
+
// rather than on route messages.
|
|
37
|
+
#if !defined(__APPLE__) || (defined(TARGET_OS_OSX) && TARGET_OS_OSX)
|
|
28
38
|
#define RATS_MONITOR_BSD_ROUTES 1
|
|
39
|
+
#endif
|
|
40
|
+
#endif
|
|
41
|
+
|
|
42
|
+
#if defined(RATS_MONITOR_BSD_ROUTES)
|
|
29
43
|
#include <sys/types.h>
|
|
30
44
|
#include <sys/socket.h>
|
|
31
45
|
#include <net/route.h>
|
|
@@ -17,8 +17,17 @@
|
|
|
17
17
|
#include <ifaddrs.h>
|
|
18
18
|
#endif
|
|
19
19
|
|
|
20
|
+
#ifdef __APPLE__
|
|
21
|
+
#include <TargetConditionals.h>
|
|
22
|
+
#endif
|
|
23
|
+
|
|
20
24
|
// macOS / BSD default-gateway lookup via the PF_ROUTE sysctl routing table.
|
|
21
|
-
|
|
25
|
+
// Apple ships <net/route.h> in the macOS SDK only: on iOS (and tvOS/watchOS)
|
|
26
|
+
// the sysctl still exists but its declarations are not public, so those
|
|
27
|
+
// targets skip this branch and rely on append_gateway_heuristics() below,
|
|
28
|
+
// which every platform falls back to anyway.
|
|
29
|
+
#if (defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX) || \
|
|
30
|
+
defined(__FreeBSD__) || defined(__NetBSD__) || \
|
|
22
31
|
defined(__OpenBSD__) || defined(__DragonFly__)
|
|
23
32
|
#define RATS_HAVE_BSD_ROUTES 1
|
|
24
33
|
#include <sys/types.h>
|
|
@@ -46,6 +46,7 @@ enum class MessageType : uint8_t {
|
|
|
46
46
|
Typed = 7, ///< typed JSON message exchange (MessageJson)
|
|
47
47
|
Pex = 8, ///< peer exchange — gossip of known peer addresses (PeerExchange)
|
|
48
48
|
Punch = 9, ///< NAT hole-punch rendezvous, relayed peer→peer (HolePunch)
|
|
49
|
+
Relay = 10, ///< relayed circuits: a peer's connection carried through a third node (Relay)
|
|
49
50
|
};
|
|
50
51
|
|
|
51
52
|
/// Fixed header of an inner message.
|