@decentnetwork/peer 0.1.113 → 0.1.115
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/dist/peer.js +92 -17
- package/package.json +1 -1
package/dist/peer.js
CHANGED
|
@@ -340,6 +340,14 @@ export class Peer {
|
|
|
340
340
|
* When a DHT packet later arrives from one of these sources, its sender pk
|
|
341
341
|
* is that friend's CURRENT DHT key — see #refreshFriendDhtKeyFromDht. */
|
|
342
342
|
#lanProbeTargets = new Map();
|
|
343
|
+
/** Exclusive turn-taking for OWN-HOST probe targets. Own-host targets
|
|
344
|
+
* (our IPs + loopback at native ports) are shared by every stranded
|
|
345
|
+
* friend, so concurrent probes overwrite each other's #lanProbeTargets
|
|
346
|
+
* mapping and the reply gets credited to whoever probed last (observed
|
|
347
|
+
* live: the simulator's key landed on an unrelated friend). One friend
|
|
348
|
+
* holds the slot at a time; per-friend candidate probes are unaffected. */
|
|
349
|
+
#ownHostProbeFriendId;
|
|
350
|
+
#ownHostProbeUntilMs = 0;
|
|
343
351
|
#dhtMaintenanceTimer;
|
|
344
352
|
// Per-friend last DHT-PK send time, keyed by friendId, used even when no
|
|
345
353
|
// session entry exists yet so the connection loop does not flood DHT-PK
|
|
@@ -1800,6 +1808,20 @@ export class Peer {
|
|
|
1800
1808
|
if (!fromTcp) {
|
|
1801
1809
|
this.#cacheFriendRemote(senderId, remote.address, remote.port, request.senderRealPublicKey, request.senderDhtPublicKey);
|
|
1802
1810
|
}
|
|
1811
|
+
else if (request.senderDhtPublicKey && request.senderDhtPublicKey.length === 32) {
|
|
1812
|
+
// TCP arrival: skip the endpoint, but STILL learn the sender's DHT
|
|
1813
|
+
// key. Natives rotate it every app restart and the relay routes
|
|
1814
|
+
// CRYPTO_DATA by it — a relay-only native (its UDP unreachable to
|
|
1815
|
+
// us) otherwise kept its stale key here, every routed send went to
|
|
1816
|
+
// a key the relay didn't know, and the peer — never receiving our
|
|
1817
|
+
// data — re-keyed every 8s forever (the iPhone churn loop).
|
|
1818
|
+
const session = this.#friendSessions.get(senderId);
|
|
1819
|
+
if (session && !(session.friendDhtPublicKey && Buffer.from(session.friendDhtPublicKey).equals(Buffer.from(request.senderDhtPublicKey)))) {
|
|
1820
|
+
this.#debugLog(`dht_key_from_tcp_cookie friend=${senderId} new=${carrierIdFromPublicKey(request.senderDhtPublicKey)}`);
|
|
1821
|
+
session.friendDhtPublicKey = request.senderDhtPublicKey;
|
|
1822
|
+
}
|
|
1823
|
+
this.#learnFriendDhtKey(senderId, request.senderDhtPublicKey);
|
|
1824
|
+
}
|
|
1803
1825
|
const response = createCookieResponse({
|
|
1804
1826
|
request,
|
|
1805
1827
|
receiverDhtSecretKey: this.#keyPair.secretKey,
|
|
@@ -2063,8 +2085,18 @@ export class Peer {
|
|
|
2063
2085
|
else {
|
|
2064
2086
|
if (!state.friendRealPublicKey)
|
|
2065
2087
|
state.friendRealPublicKey = hs.senderRealPublicKey;
|
|
2066
|
-
|
|
2088
|
+
// ALWAYS take the handshake's DHT key, not only when empty: the
|
|
2089
|
+
// handshake authenticates the sender's real key, and natives rotate
|
|
2090
|
+
// their DHT key every restart. Keeping a stale key here sent every
|
|
2091
|
+
// relay-routed CRYPTO_DATA packet to a key the relay didn't know —
|
|
2092
|
+
// silent drop, peer re-keys every 8s (iPhone churn loop).
|
|
2093
|
+
if (hs.senderDhtPublicKey && hs.senderDhtPublicKey.length === 32 &&
|
|
2094
|
+
!(state.friendDhtPublicKey && Buffer.from(state.friendDhtPublicKey).equals(Buffer.from(hs.senderDhtPublicKey)))) {
|
|
2095
|
+
if (state.friendDhtPublicKey) {
|
|
2096
|
+
this.#debugLog(`dht_key_from_hs friend=${friendId} new=${carrierIdFromPublicKey(hs.senderDhtPublicKey)}`);
|
|
2097
|
+
}
|
|
2067
2098
|
state.friendDhtPublicKey = hs.senderDhtPublicKey;
|
|
2099
|
+
}
|
|
2068
2100
|
}
|
|
2069
2101
|
if (hs.senderDhtPublicKey && hs.senderDhtPublicKey.length === 32) {
|
|
2070
2102
|
this.#learnFriendDhtKey(friendId, hs.senderDhtPublicKey);
|
|
@@ -5188,6 +5220,7 @@ export class Peer {
|
|
|
5188
5220
|
};
|
|
5189
5221
|
const tried = new Set();
|
|
5190
5222
|
// (b) known candidates — refresh a rotated DHT key at a live endpoint.
|
|
5223
|
+
// Per-friend targets: no mapping ambiguity, always allowed.
|
|
5191
5224
|
for (const candidate of connectCandidates) {
|
|
5192
5225
|
const key = `${candidate.host}:${candidate.port}`;
|
|
5193
5226
|
if (tried.has(key))
|
|
@@ -5196,17 +5229,25 @@ export class Peer {
|
|
|
5196
5229
|
await probe(candidate.host, candidate.port, false); // cookie already sent above
|
|
5197
5230
|
}
|
|
5198
5231
|
// (a)+(b) own-host targets — find a same-host peer after an IP change.
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5232
|
+
// SHARED targets: only the current slot holder probes them, so the
|
|
5233
|
+
// reply maps to exactly one friend. Slot rotates by expiry; the
|
|
5234
|
+
// cookie backoff staggers contenders so every stranded friend
|
|
5235
|
+
// eventually gets a turn.
|
|
5236
|
+
if (this.#ownHostProbeFriendId === friendId || probeNow >= this.#ownHostProbeUntilMs) {
|
|
5237
|
+
this.#ownHostProbeFriendId = friendId;
|
|
5238
|
+
this.#ownHostProbeUntilMs = probeNow + 10_000;
|
|
5239
|
+
for (const host of [...getLocalIpv4Addresses(), "127.0.0.1"]) {
|
|
5240
|
+
if (isOwnVirtualAddress(host))
|
|
5241
|
+
continue; // never probe into the overlay
|
|
5242
|
+
for (const port of LAN_SWEEP_PORTS) {
|
|
5243
|
+
if (port === ourLocalPort)
|
|
5244
|
+
continue; // our own socket
|
|
5245
|
+
const key = `${host}:${port}`;
|
|
5246
|
+
if (tried.has(key))
|
|
5247
|
+
continue;
|
|
5248
|
+
tried.add(key);
|
|
5249
|
+
await probe(host, port, true);
|
|
5250
|
+
}
|
|
5210
5251
|
}
|
|
5211
5252
|
}
|
|
5212
5253
|
}
|
|
@@ -5237,9 +5278,18 @@ export class Peer {
|
|
|
5237
5278
|
}
|
|
5238
5279
|
catch (error) {
|
|
5239
5280
|
this.#debugLog(`cookie request send failed for ${friendId}: ${error.message}`);
|
|
5281
|
+
// A failed send is still a failed ATTEMPT: count it so (a) backoff
|
|
5282
|
+
// grows instead of hammering a dead address at base cadence forever,
|
|
5283
|
+
// and (b) the rescue probes (gated on retryCount >= 1) still fire for
|
|
5284
|
+
// a friend whose sole candidate errors at the syscall level — before
|
|
5285
|
+
// this, such a friend never probed and could never be rescued.
|
|
5286
|
+
this.#cookieRetryCount.set(friendId, (this.#cookieRetryCount.get(friendId) ?? 0) + 1);
|
|
5240
5287
|
session.pendingEcho = undefined;
|
|
5241
5288
|
session.pendingCookiePeerDhtPublicKey = undefined;
|
|
5242
|
-
|
|
5289
|
+
// Keep cookieRequestSentMs (set at attempt start): it is the loop's
|
|
5290
|
+
// `lastAttempt`. Clearing it made lastAttempt 0, so the cooldown gate
|
|
5291
|
+
// always passed and a friend whose sends error retried every 250ms
|
|
5292
|
+
// tick forever — the "cookie request send failed" log flood.
|
|
5243
5293
|
return false;
|
|
5244
5294
|
}
|
|
5245
5295
|
}
|
|
@@ -5486,11 +5536,36 @@ export class Peer {
|
|
|
5486
5536
|
}
|
|
5487
5537
|
if (!friendId)
|
|
5488
5538
|
return;
|
|
5489
|
-
//
|
|
5490
|
-
//
|
|
5539
|
+
// If ANY established session already uses this DHT key, the key's owner
|
|
5540
|
+
// is known and it is not `friendId` — a shared own-host target (the
|
|
5541
|
+
// simulator) answers EVERY prober's probe, and without this check each
|
|
5542
|
+
// stranded friend in turn "adopted" the sim's key during its slot,
|
|
5543
|
+
// endlessly poisoning its own routing.
|
|
5544
|
+
for (const [fid, s] of this.#friendSessions) {
|
|
5545
|
+
if (fid === friendId || !s.established || !s.friendDhtPublicKey)
|
|
5546
|
+
continue;
|
|
5547
|
+
if (Buffer.from(s.friendDhtPublicKey).equals(Buffer.from(senderDhtPk)))
|
|
5548
|
+
return;
|
|
5549
|
+
}
|
|
5550
|
+
// The sender pk currently maps to a DIFFERENT friend. If that friend's
|
|
5551
|
+
// session is ESTABLISHED, the key genuinely belongs to them — don't
|
|
5552
|
+
// cross-wire. If it is NOT established, the mapping itself may be a
|
|
5553
|
+
// mis-credited earlier probe reply (shared own-host targets), and
|
|
5554
|
+
// refusing here would permanently lock the true owner out — steal it
|
|
5555
|
+
// and scrub the other friend's stale copy so routing stops confusing
|
|
5556
|
+
// the two. A wrong steal self-corrects: the real-key handshake cannot
|
|
5557
|
+
// complete against the wrong node.
|
|
5491
5558
|
const byDht = this.#friendByDhtPk(senderId);
|
|
5492
|
-
if (byDht && byDht.friendId !== friendId)
|
|
5493
|
-
|
|
5559
|
+
if (byDht && byDht.friendId !== friendId) {
|
|
5560
|
+
const otherSession = this.#friendSessions.get(byDht.friendId);
|
|
5561
|
+
if (otherSession?.established)
|
|
5562
|
+
return;
|
|
5563
|
+
this.#debugLog(`dht_key_steal ${senderId} from=${byDht.friendId} to=${friendId} (other unestablished)`);
|
|
5564
|
+
if (otherSession?.friendDhtPublicKey && Buffer.from(otherSession.friendDhtPublicKey).equals(Buffer.from(senderDhtPk))) {
|
|
5565
|
+
otherSession.friendDhtPublicKey = undefined;
|
|
5566
|
+
}
|
|
5567
|
+
this.#friendDhtKeys.delete(byDht.friendId);
|
|
5568
|
+
}
|
|
5494
5569
|
if (senderId === friendId)
|
|
5495
5570
|
return; // already keyed by real pk — nothing to refresh
|
|
5496
5571
|
const session = this.#friendSessions.get(friendId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.115",
|
|
4
4
|
"description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|