@decentnetwork/peer 0.1.67 → 0.1.68
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.d.ts +4 -0
- package/dist/peer.js +40 -2
- package/package.json +1 -1
package/dist/peer.d.ts
CHANGED
|
@@ -201,6 +201,10 @@ export declare class Peer {
|
|
|
201
201
|
* peer (null = never). Used to confirm Phase 1.2 retries are
|
|
202
202
|
* actually firing. */
|
|
203
203
|
cookieRequestSentMs: number | null;
|
|
204
|
+
/** The peer's physical-LAN host we've locked onto (set by the periodic
|
|
205
|
+
* offer/maintenance LAN-adoption code). When set and equal to udpRemote's
|
|
206
|
+
* host, the per-packet receive path refuses a public/hairpin downgrade. */
|
|
207
|
+
lanRemoteHost: string | null;
|
|
204
208
|
} | null;
|
|
205
209
|
waitForFriendRequest(timeoutMs?: number): Promise<FriendRequest>;
|
|
206
210
|
/**
|
package/dist/peer.js
CHANGED
|
@@ -82,6 +82,11 @@ const FRIEND_PING_INTERVAL_MS = readEnvInt("DECENT_FRIEND_PING_INTERVAL_MS", 400
|
|
|
82
82
|
// slow enough not to burn CPU on idle peers. Tunable via env.
|
|
83
83
|
const FRIEND_CONNECTION_LOOP_MS = readEnvInt("DECENT_FRIEND_CONNECTION_LOOP_MS", 250);
|
|
84
84
|
const FRIEND_TIMEOUT_MS = readEnvInt("DECENT_FRIEND_TIMEOUT_MS", 32000);
|
|
85
|
+
// How long the direct UDP path may go without an inbound packet before we
|
|
86
|
+
// release a physical-LAN lock (so a peer that left the LAN can recover onto the
|
|
87
|
+
// public/relay path). 12s ≈ 3 missed 4s keepalives — long enough to never trip
|
|
88
|
+
// on a healthy same-LAN path, short enough to recover quickly.
|
|
89
|
+
const LAN_LOCK_STALE_MS = readEnvInt("DECENT_LAN_LOCK_STALE_MS", 12000);
|
|
85
90
|
// Half-open breaker: if the peer proposes a NEW session key this many times over
|
|
86
91
|
// this long while our session looks "alive", accept it (the peer can't decrypt
|
|
87
92
|
// our current session — a desynced half-open session, observed as a flood of
|
|
@@ -1084,6 +1089,7 @@ export class Peer {
|
|
|
1084
1089
|
endpointCandidatesCount: s.endpointCandidates?.length ?? 0,
|
|
1085
1090
|
endpointCandidates: (s.endpointCandidates ?? []).map((c) => `${c.host}:${c.port}`),
|
|
1086
1091
|
cookieRequestSentMs: s.cookieRequestSentMs ?? null,
|
|
1092
|
+
lanRemoteHost: s.lanRemoteHost ?? null,
|
|
1087
1093
|
};
|
|
1088
1094
|
}
|
|
1089
1095
|
waitForFriendRequest(timeoutMs = 30000) {
|
|
@@ -1386,7 +1392,13 @@ export class Peer {
|
|
|
1386
1392
|
state.hasTcpRoute = true;
|
|
1387
1393
|
}
|
|
1388
1394
|
else {
|
|
1389
|
-
|
|
1395
|
+
// Same LAN-lock guard as the crypto-data path: a duplicate handshake
|
|
1396
|
+
// retransmitted via the public/hairpin endpoint must not knock us off
|
|
1397
|
+
// a locked physical-LAN path. Cheap string compare — no syscall.
|
|
1398
|
+
const lockedOnLan = !!state.lanRemoteHost && state.remote?.host === state.lanRemoteHost;
|
|
1399
|
+
if (!lockedOnLan || remote.address === state.lanRemoteHost) {
|
|
1400
|
+
state.remote = { host: remote.address, port: remote.port };
|
|
1401
|
+
}
|
|
1390
1402
|
}
|
|
1391
1403
|
state.lastPingRecvMs = Date.now();
|
|
1392
1404
|
this.#debugVerboseLog(`hs_recv duplicate friend=${friendId} remote=${remote.address}:${remote.port} (session preserved)`);
|
|
@@ -1476,7 +1488,13 @@ export class Peer {
|
|
|
1476
1488
|
// Don't set state.remote — there's no UDP endpoint for this peer.
|
|
1477
1489
|
}
|
|
1478
1490
|
else {
|
|
1479
|
-
|
|
1491
|
+
// LAN-lock guard (see crypto-data path): don't let an establish/
|
|
1492
|
+
// re-handshake arriving over the public/hairpin endpoint downgrade us
|
|
1493
|
+
// off a locked physical-LAN path. Cheap string compare — no syscall.
|
|
1494
|
+
const lockedOnLan = !!state.lanRemoteHost && state.remote?.host === state.lanRemoteHost;
|
|
1495
|
+
if (!lockedOnLan || remote.address === state.lanRemoteHost) {
|
|
1496
|
+
state.remote = { host: remote.address, port: remote.port };
|
|
1497
|
+
}
|
|
1480
1498
|
}
|
|
1481
1499
|
if (isNewSession) {
|
|
1482
1500
|
state.sendPacketNumber = 0;
|
|
@@ -3144,6 +3162,15 @@ export class Peer {
|
|
|
3144
3162
|
session.endpointCandidates = next
|
|
3145
3163
|
.sort((a, b) => b.updatedMs - a.updatedMs)
|
|
3146
3164
|
.slice(0, 12);
|
|
3165
|
+
// The moment we learn the peer's physical-LAN host candidate, record it as
|
|
3166
|
+
// the LAN lock so the per-packet/handshake hot paths refuse a public/hairpin
|
|
3167
|
+
// downgrade (cheap string compare there, no syscall). This runs at
|
|
3168
|
+
// candidate-learn time (offer/discovery), not per packet, so the cached
|
|
3169
|
+
// getPhysicalLanSubnets() call is fine here.
|
|
3170
|
+
if (isPrivateAddress(host) && !isCgnatAddress(host) &&
|
|
3171
|
+
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(host, s))) {
|
|
3172
|
+
session.lanRemoteHost = host;
|
|
3173
|
+
}
|
|
3147
3174
|
}
|
|
3148
3175
|
/**
|
|
3149
3176
|
* Learn our own server-reflexive (public) UDP endpoint by sending a
|
|
@@ -4061,6 +4088,17 @@ export class Peer {
|
|
|
4061
4088
|
for (const [friendId, session] of this.#friendSessions.entries()) {
|
|
4062
4089
|
if (!session.established)
|
|
4063
4090
|
continue;
|
|
4091
|
+
// Release a stale LAN lock. If we're locked onto a LAN host but the
|
|
4092
|
+
// direct UDP path has gone quiet (peer left the LAN / changed network),
|
|
4093
|
+
// the lock would otherwise block recovery onto the public/relay path.
|
|
4094
|
+
// Clearing it lets the hot-path guard adopt a working endpoint again.
|
|
4095
|
+
if (session.lanRemoteHost &&
|
|
4096
|
+
session.remote?.host === session.lanRemoteHost &&
|
|
4097
|
+
session.lastUdpRecvMs !== undefined &&
|
|
4098
|
+
Date.now() - session.lastUdpRecvMs > LAN_LOCK_STALE_MS) {
|
|
4099
|
+
this.#debugLog(`lan_lock_released friend=${friendId} host=${session.lanRemoteHost} (no direct UDP recv for >${LAN_LOCK_STALE_MS}ms)`);
|
|
4100
|
+
session.lanRemoteHost = undefined;
|
|
4101
|
+
}
|
|
4064
4102
|
const r = session.remote;
|
|
4065
4103
|
const remoteIsSameLan = !!r && isPrivateAddress(r.host) && !isCgnatAddress(r.host) &&
|
|
4066
4104
|
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(r.host, s));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.68",
|
|
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",
|