@decentnetwork/peer 0.1.64 → 0.1.65
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 +46 -88
- package/package.json +1 -1
package/dist/peer.js
CHANGED
|
@@ -1456,30 +1456,13 @@ export class Peer {
|
|
|
1456
1456
|
const lastAlive = state.lastPingRecvMs ?? state.sessionEstablishedAtMs;
|
|
1457
1457
|
const currentSessionAlive = Date.now() - lastAlive < FRIEND_TIMEOUT_MS;
|
|
1458
1458
|
if (sinceEstablished > 1000 && currentSessionAlive) {
|
|
1459
|
-
//
|
|
1460
|
-
//
|
|
1461
|
-
//
|
|
1462
|
-
//
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
// to sustained attempts so it never disturbs a healthy session.
|
|
1466
|
-
const now = Date.now();
|
|
1467
|
-
if (state.rehsFirstMs === undefined || now - (state.rehsLastMs ?? 0) > REHS_WINDOW_MS) {
|
|
1468
|
-
state.rehsFirstMs = now;
|
|
1469
|
-
state.rehsCount = 0;
|
|
1470
|
-
}
|
|
1471
|
-
state.rehsLastMs = now;
|
|
1472
|
-
state.rehsCount = (state.rehsCount ?? 0) + 1;
|
|
1473
|
-
const stuck = state.rehsCount >= REHS_ACCEPT_COUNT && now - state.rehsFirstMs >= REHS_ACCEPT_MS;
|
|
1474
|
-
if (!stuck) {
|
|
1475
|
-
this.#debugVerboseLog(`hs_recv ignored friend=${friendId} (new session pubkey on live session, attempt ${state.rehsCount}, ${sinceEstablished}ms after establish)`);
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
this.#debugLog(`hs_recv accepting re-handshake friend=${friendId} (half-open breaker: ${state.rehsCount} new-key attempts over ${now - state.rehsFirstMs}ms — peer can't decrypt our session)`);
|
|
1459
|
+
// Reject a new-key handshake while our session is alive — toxcore does
|
|
1460
|
+
// the same. (The "half-open breaker" that accepted these after N tries
|
|
1461
|
+
// was reverted: accepting a new key ROTATES the session, which churned
|
|
1462
|
+
// healthy sessions; the real fix was the TURN-crash fix, not this.)
|
|
1463
|
+
this.#debugVerboseLog(`hs_recv ignored friend=${friendId} (new session pubkey on live established session, ${sinceEstablished}ms after establish)`);
|
|
1464
|
+
return;
|
|
1479
1465
|
}
|
|
1480
|
-
// Converging on a fresh session — clear the breaker counter.
|
|
1481
|
-
state.rehsCount = 0;
|
|
1482
|
-
state.rehsFirstMs = undefined;
|
|
1483
1466
|
this.#debugLog(`hs_recv accepting re-handshake friend=${friendId} (current session wedged/dead, ` +
|
|
1484
1467
|
`lastPingRecv=${state.lastPingRecvMs ? Date.now() - state.lastPingRecvMs + "ms ago" : "never"})`);
|
|
1485
1468
|
}
|
|
@@ -1841,43 +1824,15 @@ export class Peer {
|
|
|
1841
1824
|
this.#debugVerboseLog(`unknown messenger packet kind ${kind} from ${friendId}`);
|
|
1842
1825
|
return;
|
|
1843
1826
|
}
|
|
1844
|
-
//
|
|
1845
|
-
//
|
|
1846
|
-
//
|
|
1847
|
-
//
|
|
1848
|
-
//
|
|
1849
|
-
//
|
|
1850
|
-
//
|
|
1851
|
-
//
|
|
1852
|
-
|
|
1853
|
-
if (this.#remoteIsTcp(remote)) {
|
|
1854
|
-
const m = /^tcp:([A-Za-z0-9]+)/.exec(remote.address);
|
|
1855
|
-
if (m && this.#friendSessions.has(m[1]))
|
|
1856
|
-
desyncedFid = m[1];
|
|
1857
|
-
}
|
|
1858
|
-
else if (!viaRelay) {
|
|
1859
|
-
for (const [fid, st] of this.#friendSessions.entries()) {
|
|
1860
|
-
if ((st.remote?.host === remote.address && st.remote?.port === remote.port) ||
|
|
1861
|
-
(st.endpointCandidates ?? []).some((c) => c.host === remote.address && c.port === remote.port)) {
|
|
1862
|
-
desyncedFid = fid;
|
|
1863
|
-
break;
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
if (desyncedFid) {
|
|
1868
|
-
const st = this.#friendSessions.get(desyncedFid);
|
|
1869
|
-
// Only a GENUINELY stuck session (no successful decrypt for a while) —
|
|
1870
|
-
// never nuke a healthy one on a stray/late undecryptable packet — and
|
|
1871
|
-
// rate-limited via a map that survives the delete.
|
|
1872
|
-
const working = st.lastPingRecvMs !== undefined && Date.now() - st.lastPingRecvMs < REINIT_STUCK_MS;
|
|
1873
|
-
const lastDel = this.#lastDesyncDeleteMs.get(desyncedFid) ?? 0;
|
|
1874
|
-
if (!working && Date.now() - lastDel > REINIT_ON_DESYNC_MS) {
|
|
1875
|
-
this.#lastDesyncDeleteMs.set(desyncedFid, Date.now());
|
|
1876
|
-
this.#debugLog(`no-session-matched from ${remote.address} (friend ${desyncedFid}) — desynced, deleting session for clean re-handshake`);
|
|
1877
|
-
this.#friendSessions.delete(desyncedFid);
|
|
1878
|
-
void this.#initiateSession(desyncedFid).catch(() => undefined);
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1827
|
+
// Reverted: we used to DELETE a "desynced" session here to force a clean
|
|
1828
|
+
// re-handshake. It caused more harm than good — deleting a session
|
|
1829
|
+
// interrupts the data plane, and a transient/late undecryptable packet
|
|
1830
|
+
// (common over the relay) triggered a delete→re-key→re-desync thrash that
|
|
1831
|
+
// churned MANY friends' sessions (observed: CCTV jitter + slow file
|
|
1832
|
+
// transfer to 4090, not just snoopy). The real instability was the daemon
|
|
1833
|
+
// CRASH (TURN DNS); with that fixed, sessions stay converged on their own.
|
|
1834
|
+
// Just drop the undecryptable packet — net_crypto's normal re-handshake
|
|
1835
|
+
// path recovers a genuinely dead session.
|
|
1881
1836
|
this.#debugVerboseLog(`crypto data received from ${remote.address}:${remote.port} but no session matched`);
|
|
1882
1837
|
return;
|
|
1883
1838
|
}
|
|
@@ -5072,50 +5027,53 @@ function isCgnatAddress(host) {
|
|
|
5072
5027
|
}
|
|
5073
5028
|
/** Local IPv4 addresses on PHYSICAL interfaces only (excludes overlay/virtual
|
|
5074
5029
|
* interfaces by name and the CGNAT range). Used for LAN-direct host candidates. */
|
|
5075
|
-
|
|
5076
|
-
|
|
5030
|
+
// CACHED interface enumeration. getPhysicalLanSubnets/Addresses are called on
|
|
5031
|
+
// the receive hot path (the same-LAN downgrade guard runs per packet); calling
|
|
5032
|
+
// networkInterfaces() — a syscall — for every CRYPTO_DATA packet pegged the CPU
|
|
5033
|
+
// on a high-rate stream (CCTV jitter/instability). Cache the result for 5s; LAN
|
|
5034
|
+
// interfaces almost never change, so this is safe and ~free.
|
|
5035
|
+
let _lanIfaceCacheMs = 0;
|
|
5036
|
+
let _lanAddrsCache = [];
|
|
5037
|
+
let _lanSubnetsCache = [];
|
|
5038
|
+
function refreshLanIfaceCache() {
|
|
5039
|
+
const now = Date.now();
|
|
5040
|
+
if (_lanIfaceCacheMs !== 0 && now - _lanIfaceCacheMs < 5000)
|
|
5041
|
+
return;
|
|
5042
|
+
_lanIfaceCacheMs = now;
|
|
5043
|
+
const addrs = [];
|
|
5044
|
+
const subnets = [];
|
|
5077
5045
|
try {
|
|
5078
5046
|
for (const [name, list] of Object.entries(networkInterfaces())) {
|
|
5079
5047
|
if (!list || VIRTUAL_IFACE_RE.test(name))
|
|
5080
5048
|
continue;
|
|
5081
5049
|
for (const info of list) {
|
|
5082
|
-
if (info.family !== "IPv4" || info.internal)
|
|
5083
|
-
continue;
|
|
5084
|
-
if (isCgnatAddress(info.address))
|
|
5050
|
+
if (info.family !== "IPv4" || info.internal || isCgnatAddress(info.address))
|
|
5085
5051
|
continue;
|
|
5086
|
-
|
|
5052
|
+
addrs.push(info.address);
|
|
5053
|
+
const addr = ipv4ToInt(info.address);
|
|
5054
|
+
const mask = netmaskToInt(info.netmask);
|
|
5055
|
+
if (addr !== undefined && mask !== undefined && mask !== 0) {
|
|
5056
|
+
subnets.push({ networkBits: (addr & mask) >>> 0, maskBits: mask });
|
|
5057
|
+
}
|
|
5087
5058
|
}
|
|
5088
5059
|
}
|
|
5089
5060
|
}
|
|
5090
5061
|
catch {
|
|
5091
5062
|
// best-effort
|
|
5092
5063
|
}
|
|
5093
|
-
|
|
5064
|
+
_lanAddrsCache = addrs;
|
|
5065
|
+
_lanSubnetsCache = subnets;
|
|
5066
|
+
}
|
|
5067
|
+
function getPhysicalLanAddresses() {
|
|
5068
|
+
refreshLanIfaceCache();
|
|
5069
|
+
return _lanAddrsCache;
|
|
5094
5070
|
}
|
|
5095
5071
|
/** Subnets of PHYSICAL interfaces only — for matching a peer's host candidate
|
|
5096
5072
|
* to OUR real LAN (so we never match a peer's Tailscale/TUN address against our
|
|
5097
|
-
* own overlay subnet). */
|
|
5073
|
+
* own overlay subnet). Cached (see refreshLanIfaceCache). */
|
|
5098
5074
|
function getPhysicalLanSubnets() {
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
for (const [name, list] of Object.entries(networkInterfaces())) {
|
|
5102
|
-
if (!list || VIRTUAL_IFACE_RE.test(name))
|
|
5103
|
-
continue;
|
|
5104
|
-
for (const info of list) {
|
|
5105
|
-
if (info.family !== "IPv4" || info.internal || isCgnatAddress(info.address))
|
|
5106
|
-
continue;
|
|
5107
|
-
const addr = ipv4ToInt(info.address);
|
|
5108
|
-
const mask = netmaskToInt(info.netmask);
|
|
5109
|
-
if (addr === undefined || mask === undefined || mask === 0)
|
|
5110
|
-
continue;
|
|
5111
|
-
out.push({ networkBits: (addr & mask) >>> 0, maskBits: mask });
|
|
5112
|
-
}
|
|
5113
|
-
}
|
|
5114
|
-
}
|
|
5115
|
-
catch {
|
|
5116
|
-
// best-effort
|
|
5117
|
-
}
|
|
5118
|
-
return out;
|
|
5075
|
+
refreshLanIfaceCache();
|
|
5076
|
+
return _lanSubnetsCache;
|
|
5119
5077
|
}
|
|
5120
5078
|
function isPrivateAddress(host) {
|
|
5121
5079
|
// RFC1918 IPv4: 10/8, 172.16/12, 192.168/16; loopback 127/8;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.65",
|
|
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",
|