@decentnetwork/peer 0.1.65 → 0.1.66
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 +0 -3
- package/dist/peer.js +60 -179
- package/package.json +1 -1
package/dist/peer.d.ts
CHANGED
|
@@ -194,9 +194,6 @@ export declare class Peer {
|
|
|
194
194
|
* tcp-relay, discovery itself is broken — we don't even know
|
|
195
195
|
* where to send a cookie request. */
|
|
196
196
|
endpointCandidatesCount: number;
|
|
197
|
-
/** The actual candidate endpoints (host:port) — lets diag see whether a
|
|
198
|
-
* same-LAN host candidate (e.g. 10.0.0.x) was received but not adopted. */
|
|
199
|
-
endpointCandidates: string[];
|
|
200
197
|
/** Last time we sent an outbound cookie request via UDP for this
|
|
201
198
|
* peer (null = never). Used to confirm Phase 1.2 retries are
|
|
202
199
|
* actually firing. */
|
package/dist/peer.js
CHANGED
|
@@ -82,23 +82,6 @@ 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
|
-
// Half-open breaker: if the peer proposes a NEW session key this many times over
|
|
86
|
-
// this long while our session looks "alive", accept it (the peer can't decrypt
|
|
87
|
-
// our current session — a desynced half-open session, observed as a flood of
|
|
88
|
-
// "no session matched"). Each handshake carries a fresh cookie so these are
|
|
89
|
-
// genuine attempts, not replays. Bounded so it never fires on a healthy session.
|
|
90
|
-
const REHS_ACCEPT_COUNT = readEnvInt("DECENT_REHS_ACCEPT_COUNT", 4);
|
|
91
|
-
const REHS_ACCEPT_MS = readEnvInt("DECENT_REHS_ACCEPT_MS", 2500);
|
|
92
|
-
const REHS_WINDOW_MS = readEnvInt("DECENT_REHS_WINDOW_MS", 8000);
|
|
93
|
-
// Re-handshake when we keep receiving undecryptable crypto data from a known
|
|
94
|
-
// friend's endpoint (desynced sessions), rate-limited per friend.
|
|
95
|
-
// Conservative: deleting a session forces a re-handshake which ROTATES our
|
|
96
|
-
// ephemeral key, so doing it too eagerly turns a transient blip into a
|
|
97
|
-
// rotate→desync→delete thrash. Only nuke a session that's been UNABLE to
|
|
98
|
-
// decrypt anything for a long time (genuinely dead, well past any
|
|
99
|
-
// establishment-transition window), and at most once per long interval.
|
|
100
|
-
const REINIT_ON_DESYNC_MS = readEnvInt("DECENT_REINIT_ON_DESYNC_MS", 12000);
|
|
101
|
-
const REINIT_STUCK_MS = readEnvInt("DECENT_REINIT_STUCK_MS", 20000);
|
|
102
85
|
// How long a relay path stays "confirmed" (carrying bulk data on its own, no
|
|
103
86
|
// tcp-relay fan-out) after the last packet received over it. The relay
|
|
104
87
|
// keepalive refreshes this every ~4s on a healthy path, so a generous window
|
|
@@ -273,9 +256,6 @@ export class Peer {
|
|
|
273
256
|
// #initiateSession every few seconds for every friend; without this
|
|
274
257
|
// dedupe, lower-pubkey side floods the log with "deferring to peer".
|
|
275
258
|
#initiateSkipLogged = new Set();
|
|
276
|
-
// Per-friend last time we deleted a desynced session (rate-limit; survives the
|
|
277
|
-
// session deletion, unlike a field on the session object itself).
|
|
278
|
-
#lastDesyncDeleteMs = new Map();
|
|
279
259
|
// Cached server-reflexive (public) UDP endpoint of our #udp socket,
|
|
280
260
|
// learned via STUN. Cone-NAT mappings are stable for the socket
|
|
281
261
|
// lifetime, so we only re-probe every ~60s. Used to tell friends where
|
|
@@ -1082,7 +1062,6 @@ export class Peer {
|
|
|
1082
1062
|
ourLocalUdpPort: this.#udp?.localPort() ?? null,
|
|
1083
1063
|
friendUdpEndpoint: friendUdp,
|
|
1084
1064
|
endpointCandidatesCount: s.endpointCandidates?.length ?? 0,
|
|
1085
|
-
endpointCandidates: (s.endpointCandidates ?? []).map((c) => `${c.host}:${c.port}`),
|
|
1086
1065
|
cookieRequestSentMs: s.cookieRequestSentMs ?? null,
|
|
1087
1066
|
};
|
|
1088
1067
|
}
|
|
@@ -1456,10 +1435,9 @@ export class Peer {
|
|
|
1456
1435
|
const lastAlive = state.lastPingRecvMs ?? state.sessionEstablishedAtMs;
|
|
1457
1436
|
const currentSessionAlive = Date.now() - lastAlive < FRIEND_TIMEOUT_MS;
|
|
1458
1437
|
if (sinceEstablished > 1000 && currentSessionAlive) {
|
|
1459
|
-
//
|
|
1460
|
-
//
|
|
1461
|
-
//
|
|
1462
|
-
// healthy sessions; the real fix was the TURN-crash fix, not this.)
|
|
1438
|
+
// Verbose only — peers retransmit handshakes aggressively, so
|
|
1439
|
+
// this line fires dozens of times per minute on a stuck pair
|
|
1440
|
+
// and drowns out signal. Visible with DECENT_DEBUG_VERBOSE=1.
|
|
1463
1441
|
this.#debugVerboseLog(`hs_recv ignored friend=${friendId} (new session pubkey on live established session, ${sinceEstablished}ms after establish)`);
|
|
1464
1442
|
return;
|
|
1465
1443
|
}
|
|
@@ -1492,21 +1470,13 @@ export class Peer {
|
|
|
1492
1470
|
this.#debugLog(`hs_recv friend=${friendId} initiated=${weInitiated ? 1 : 0} remote=${remote.address}:${remote.port}`);
|
|
1493
1471
|
if (!wasEstablished) {
|
|
1494
1472
|
this.#debugLog(`friend_connected friend=${friendId} remote=${remote.address}:${remote.port}`);
|
|
1495
|
-
//
|
|
1496
|
-
//
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
//
|
|
1500
|
-
//
|
|
1501
|
-
|
|
1502
|
-
// breaks one-way (observed: two boxes on one LAN where each only saw
|
|
1503
|
-
// the other one-directionally). The peer's same-subnet filter drops
|
|
1504
|
-
// the LAN candidate when it doesn't apply, so offering is always safe.
|
|
1505
|
-
const r = state.remote;
|
|
1506
|
-
const remoteIsSameLan = !!r && isPrivateAddress(r.host) && !isCgnatAddress(r.host) &&
|
|
1507
|
-
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(r.host, s));
|
|
1508
|
-
const haveLanToOffer = getPhysicalLanAddresses().some((ip) => isPrivateAddress(ip) && !isCgnatAddress(ip));
|
|
1509
|
-
if ((state.hasTcpRoute && !r) || (!remoteIsSameLan && haveLanToOffer)) {
|
|
1473
|
+
// If we only have a TCP-relay path, immediately tell the peer our
|
|
1474
|
+
// public UDP endpoint so they can hole-punch — don't wait for the
|
|
1475
|
+
// 15s UDP-retry loop, which can miss a flapping relay session's
|
|
1476
|
+
// short-lived establishment window. Both sides do this on every
|
|
1477
|
+
// (re)establishment, so a brief tcp-relay window is enough to
|
|
1478
|
+
// bootstrap the direct UDP path. See docs/UDP-DIRECT-PLAN.md.
|
|
1479
|
+
if (state.hasTcpRoute && !state.remote) {
|
|
1510
1480
|
void this.#sendUdpEndpointOffer(friendId).catch(() => undefined);
|
|
1511
1481
|
}
|
|
1512
1482
|
}
|
|
@@ -1628,50 +1598,34 @@ export class Peer {
|
|
|
1628
1598
|
innerKind === PACKET_ID_FILE_SENDREQUEST ||
|
|
1629
1599
|
innerKind === PACKET_ID_FILE_CONTROL ||
|
|
1630
1600
|
innerKind === PACKET_ID_FILE_DATA;
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
// even when LAN UDP is flowing both ways (the snoopy slow-file bug).
|
|
1601
|
+
if (!isSingleTransportKind && opened.packetNumber < (state.receiveBufferStart ?? 0)) {
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
state.peerBaseNonce[state.peerBaseNonce.length - 2] = packet[1];
|
|
1605
|
+
state.peerBaseNonce[state.peerBaseNonce.length - 1] = packet[2];
|
|
1606
|
+
incrementNonce(state.peerBaseNonce);
|
|
1638
1607
|
state.lastPingRecvMs = Date.now();
|
|
1608
|
+
// Same TCP-vs-UDP rule as the handshake handlers: don't poison
|
|
1609
|
+
// the UDP send-back endpoint when this packet came in via TCP.
|
|
1639
1610
|
if (this.#remoteIsTcp(remote)) {
|
|
1640
1611
|
state.hasTcpRoute = true;
|
|
1641
1612
|
}
|
|
1642
1613
|
else if (viaRelay) {
|
|
1614
|
+
// Arrived over the TURN relay. Track relay freshness separately;
|
|
1615
|
+
// do NOT set state.remote (that's the direct endpoint). The relay
|
|
1616
|
+
// address it came from is already in state.relayRemote.
|
|
1643
1617
|
if (!state.lastRelayRecvMs) {
|
|
1644
1618
|
this.#debugLog(`relay_confirmed friend=${friendId} via=${remote.address}:${remote.port} (TURN relay path live)`);
|
|
1645
1619
|
}
|
|
1646
1620
|
state.lastRelayRecvMs = Date.now();
|
|
1647
1621
|
}
|
|
1648
1622
|
else {
|
|
1649
|
-
|
|
1650
|
-
// same-LAN (physical) path to a non-LAN source (a hairpin packet via
|
|
1651
|
-
// the peer's public / Tailscale-exit endpoint must not knock us off
|
|
1652
|
-
// the direct LAN path, which is strictly better).
|
|
1653
|
-
const cur = state.remote?.host;
|
|
1654
|
-
const onSameLanAlready = !!cur && !cur.startsWith("tcp:") && isPrivateAddress(cur) && !isCgnatAddress(cur) &&
|
|
1655
|
-
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(cur, s));
|
|
1656
|
-
const incomingSameLan = isPrivateAddress(remote.address) && !isCgnatAddress(remote.address) &&
|
|
1657
|
-
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(remote.address, s));
|
|
1658
|
-
if (incomingSameLan || !onSameLanAlready) {
|
|
1659
|
-
state.remote = { host: remote.address, port: remote.port };
|
|
1660
|
-
}
|
|
1623
|
+
state.remote = { host: remote.address, port: remote.port };
|
|
1661
1624
|
if (!state.lastUdpRecvMs) {
|
|
1662
1625
|
this.#debugLog(`udp_confirmed friend=${friendId} via=${remote.address}:${remote.port} (direct UDP path live)`);
|
|
1663
1626
|
}
|
|
1664
1627
|
state.lastUdpRecvMs = Date.now();
|
|
1665
1628
|
}
|
|
1666
|
-
// De-duplicate: skip the DISPATCH + nonce advance for an already-seen
|
|
1667
|
-
// packet number. The transport confirmation above already ran, so a
|
|
1668
|
-
// duplicate still keeps the path fresh.
|
|
1669
|
-
if (!isSingleTransportKind && opened.packetNumber < (state.receiveBufferStart ?? 0)) {
|
|
1670
|
-
continue;
|
|
1671
|
-
}
|
|
1672
|
-
state.peerBaseNonce[state.peerBaseNonce.length - 2] = packet[1];
|
|
1673
|
-
state.peerBaseNonce[state.peerBaseNonce.length - 1] = packet[2];
|
|
1674
|
-
incrementNonce(state.peerBaseNonce);
|
|
1675
1629
|
state.receiveBufferStart = Math.max(state.receiveBufferStart ?? 0, (opened.packetNumber + 1) >>> 0);
|
|
1676
1630
|
const kind = opened.payload[0];
|
|
1677
1631
|
const inner = opened.payload.slice(1);
|
|
@@ -1824,16 +1778,7 @@ export class Peer {
|
|
|
1824
1778
|
this.#debugVerboseLog(`unknown messenger packet kind ${kind} from ${friendId}`);
|
|
1825
1779
|
return;
|
|
1826
1780
|
}
|
|
1827
|
-
|
|
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.
|
|
1836
|
-
this.#debugVerboseLog(`crypto data received from ${remote.address}:${remote.port} but no session matched`);
|
|
1781
|
+
this.#debugLog(`crypto data received from ${remote.address}:${remote.port} but no session matched`);
|
|
1837
1782
|
return;
|
|
1838
1783
|
}
|
|
1839
1784
|
if (packet[0] === NET_PACKET_ONION_DATA_RESPONSE && this.#announceDataKey) {
|
|
@@ -3232,13 +3177,11 @@ export class Peer {
|
|
|
3232
3177
|
resolve();
|
|
3233
3178
|
});
|
|
3234
3179
|
});
|
|
3235
|
-
// CRITICAL:
|
|
3236
|
-
//
|
|
3237
|
-
//
|
|
3238
|
-
//
|
|
3239
|
-
//
|
|
3240
|
-
// "getaddrinfo ENOTFOUND tokyo.fi.chat", which churned every session).
|
|
3241
|
-
// Swallow it — the relay simply won't allocate/relay over this server.
|
|
3180
|
+
// CRITICAL: persistent 'error' listener. A TURN send to an unresolvable
|
|
3181
|
+
// host (transient DNS NXDOMAIN, e.g. tokyo.fi.chat) emits an 'error' on
|
|
3182
|
+
// this dgram socket; with no listener Node re-throws it as an uncaught
|
|
3183
|
+
// exception and CRASHES THE WHOLE DAEMON (every session dropped, mass
|
|
3184
|
+
// re-handshake churn). Swallow it — the relay just won't allocate here.
|
|
3242
3185
|
sock.on("error", (e) => this.#debugLog(`turn socket error (${srv.host}): ${e.message}`));
|
|
3243
3186
|
const client = new TurnClient({
|
|
3244
3187
|
sock,
|
|
@@ -3408,19 +3351,8 @@ export class Peer {
|
|
|
3408
3351
|
if (++ln >= 6)
|
|
3409
3352
|
clearInterval(lanTimer);
|
|
3410
3353
|
}, 120);
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
// internet egresses via Tailscale advertises its exit's public IP as
|
|
3414
|
-
// its srflx, so the "direct" public path actually loops out through
|
|
3415
|
-
// Tailscale and back). So adopt the LAN candidate as session.remote even
|
|
3416
|
-
// when we already have a public "real UDP" remote — UNLESS we're already
|
|
3417
|
-
// on a same-LAN address (don't flap between two LAN candidates). Without
|
|
3418
|
-
// this the session stuck to the public/Tailscale endpoint and never sent
|
|
3419
|
-
// over the LAN (observed: session.remote stayed public, data on relay).
|
|
3420
|
-
const cur = session.remote?.host;
|
|
3421
|
-
const onSameLanAlready = !!cur && !cur.startsWith("tcp:") && isPrivateAddress(cur) && !isCgnatAddress(cur) &&
|
|
3422
|
-
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(cur, s));
|
|
3423
|
-
if (!onSameLanAlready) {
|
|
3354
|
+
const haveRealUdp = session.remote && !session.remote.host?.startsWith("tcp:") && session.remote.port !== 0;
|
|
3355
|
+
if (!haveRealUdp) {
|
|
3424
3356
|
session.remote = { host: lanHost, port: lanPort };
|
|
3425
3357
|
}
|
|
3426
3358
|
if (session.established) {
|
|
@@ -3457,14 +3389,7 @@ export class Peer {
|
|
|
3457
3389
|
// upgrading the path to direct UDP in both directions with no new
|
|
3458
3390
|
// handshake. If UDP never works, the periodic offer re-punches.
|
|
3459
3391
|
const haveRealUdp = session.remote && !session.remote.host?.startsWith("tcp:") && session.remote.port !== 0;
|
|
3460
|
-
|
|
3461
|
-
// candidate for this peer: the physical LAN is strictly better, and letting
|
|
3462
|
-
// the srflx win here caused a LAN↔public FLAP (the LAN block above set
|
|
3463
|
-
// remote=LAN, then this overwrote it with the Tailscale-exit public IP),
|
|
3464
|
-
// so the direct LAN path never stabilised and udpRecv never confirmed.
|
|
3465
|
-
const haveLanCandidate = (session.endpointCandidates ?? []).some((c) => isPrivateAddress(c.host) && !isCgnatAddress(c.host) &&
|
|
3466
|
-
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(c.host, s)));
|
|
3467
|
-
if (!haveRealUdp && !haveLanCandidate) {
|
|
3392
|
+
if (!haveRealUdp) {
|
|
3468
3393
|
session.remote = { host, port };
|
|
3469
3394
|
}
|
|
3470
3395
|
// Nudge a keepalive out immediately so the upgrade doesn't wait for
|
|
@@ -4042,39 +3967,6 @@ export class Peer {
|
|
|
4042
3967
|
if (!node.isTcp)
|
|
4043
3968
|
void this.#sendDhtPing(node);
|
|
4044
3969
|
}
|
|
4045
|
-
// 4. LAN-direct upgrade. Re-offer our endpoint (public srflx + LAN host
|
|
4046
|
-
// candidate) to any established friend whose current path is NOT already
|
|
4047
|
-
// a same-LAN address, as long as we have a LAN address to advertise. This
|
|
4048
|
-
// upgrades sessions that came up over a public UDP endpoint (internet
|
|
4049
|
-
// hairpin) to the direct LAN path even without a re-establish, and self-
|
|
4050
|
-
// stops once the path becomes same-LAN. The peer's same-subnet filter
|
|
4051
|
-
// drops the candidate when it doesn't apply, so this is always safe.
|
|
4052
|
-
const haveLan = getPhysicalLanAddresses().some((ip) => isPrivateAddress(ip) && !isCgnatAddress(ip));
|
|
4053
|
-
if (haveLan) {
|
|
4054
|
-
for (const [friendId, session] of this.#friendSessions.entries()) {
|
|
4055
|
-
if (!session.established)
|
|
4056
|
-
continue;
|
|
4057
|
-
const r = session.remote;
|
|
4058
|
-
const remoteIsSameLan = !!r && isPrivateAddress(r.host) && !isCgnatAddress(r.host) &&
|
|
4059
|
-
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(r.host, s));
|
|
4060
|
-
if (!remoteIsSameLan) {
|
|
4061
|
-
// If we ALREADY hold a same-LAN candidate for this peer (learned from
|
|
4062
|
-
// an earlier offer), switch the session onto it NOW — don't wait to
|
|
4063
|
-
// receive yet another offer at exactly the right moment. That delay is
|
|
4064
|
-
// what kept sessions stuck on the public/Tailscale-hairpin path after
|
|
4065
|
-
// the staggered restarts. Then send a keepalive over the LAN so the
|
|
4066
|
-
// peer learns our LAN source and replies over it; the downgrade guard
|
|
4067
|
-
// keeps us there once it sticks.
|
|
4068
|
-
const lanCand = (session.endpointCandidates ?? []).find((c) => isPrivateAddress(c.host) && !isCgnatAddress(c.host) &&
|
|
4069
|
-
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(c.host, s)));
|
|
4070
|
-
if (lanCand) {
|
|
4071
|
-
session.remote = { host: lanCand.host, port: lanCand.port };
|
|
4072
|
-
void this.#sendMessengerPacket(friendId, PACKET_ID_ALIVE, new Uint8Array()).catch(() => undefined);
|
|
4073
|
-
}
|
|
4074
|
-
void this.#sendUdpEndpointOffer(friendId).catch(() => undefined);
|
|
4075
|
-
}
|
|
4076
|
-
}
|
|
4077
|
-
}
|
|
4078
3970
|
}
|
|
4079
3971
|
/**
|
|
4080
3972
|
* Send an onion DHT-PK announcement to a friend so they learn our DHT
|
|
@@ -5007,15 +4899,7 @@ function isInIpv4Subnet(host, subnet) {
|
|
|
5007
4899
|
// path onto the overlay or loops into our own mesh, then silently falls back to
|
|
5008
4900
|
// the relay — the snoopy/lili "lastUdpRecv=never -> bufferbloat" path. (lili
|
|
5009
4901
|
// runs Tailscale; that's exactly this.)
|
|
5010
|
-
|
|
5011
|
-
// zerotier, wireguard, the agentnet TUN), and especially container/VM bridges.
|
|
5012
|
-
// Docker creates `docker0` AND per-network `br-<hash>` bridges (172.x); libvirt
|
|
5013
|
-
// `virbr0`; k8s `cni0`/`flannel`/`kube`. These were leaking their 172.x address
|
|
5014
|
-
// into the LAN host-candidate offer, so a peer on a real 10.x LAN advertised a
|
|
5015
|
-
// Docker-bridge IP its LAN partner could never match → LAN-direct never engaged
|
|
5016
|
-
// (observed: two boxes on one 10.0.0.0/24 stuck hairpinning through the public
|
|
5017
|
-
// internet). `^bridge` did NOT cover `br-…`.
|
|
5018
|
-
const VIRTUAL_IFACE_RE = /^(utun|tun|tap|wg|tailscale|zt|ham|agentnet|awdl|llw|gif|stf|bridge|br-|virbr|cni|flannel|weave|kube|vnet|vnic|vmnet|veth|docker)/i;
|
|
4902
|
+
const VIRTUAL_IFACE_RE = /^(utun|tun|tap|wg|tailscale|zt|ham|agentnet|awdl|llw|gif|stf|bridge|vnic|vmnet|veth|docker)/i;
|
|
5019
4903
|
/** True for RFC 6598 carrier-grade-NAT space 100.64.0.0/10 (Tailscale's range). */
|
|
5020
4904
|
function isCgnatAddress(host) {
|
|
5021
4905
|
const o = host.split(".");
|
|
@@ -5027,53 +4911,50 @@ function isCgnatAddress(host) {
|
|
|
5027
4911
|
}
|
|
5028
4912
|
/** Local IPv4 addresses on PHYSICAL interfaces only (excludes overlay/virtual
|
|
5029
4913
|
* interfaces by name and the CGNAT range). Used for LAN-direct host candidates. */
|
|
5030
|
-
|
|
5031
|
-
|
|
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 = [];
|
|
4914
|
+
function getPhysicalLanAddresses() {
|
|
4915
|
+
const out = [];
|
|
5045
4916
|
try {
|
|
5046
4917
|
for (const [name, list] of Object.entries(networkInterfaces())) {
|
|
5047
4918
|
if (!list || VIRTUAL_IFACE_RE.test(name))
|
|
5048
4919
|
continue;
|
|
5049
4920
|
for (const info of list) {
|
|
5050
|
-
if (info.family !== "IPv4" || info.internal
|
|
4921
|
+
if (info.family !== "IPv4" || info.internal)
|
|
5051
4922
|
continue;
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
if (addr !== undefined && mask !== undefined && mask !== 0) {
|
|
5056
|
-
subnets.push({ networkBits: (addr & mask) >>> 0, maskBits: mask });
|
|
5057
|
-
}
|
|
4923
|
+
if (isCgnatAddress(info.address))
|
|
4924
|
+
continue;
|
|
4925
|
+
out.push(info.address);
|
|
5058
4926
|
}
|
|
5059
4927
|
}
|
|
5060
4928
|
}
|
|
5061
4929
|
catch {
|
|
5062
4930
|
// best-effort
|
|
5063
4931
|
}
|
|
5064
|
-
|
|
5065
|
-
_lanSubnetsCache = subnets;
|
|
5066
|
-
}
|
|
5067
|
-
function getPhysicalLanAddresses() {
|
|
5068
|
-
refreshLanIfaceCache();
|
|
5069
|
-
return _lanAddrsCache;
|
|
4932
|
+
return out;
|
|
5070
4933
|
}
|
|
5071
4934
|
/** Subnets of PHYSICAL interfaces only — for matching a peer's host candidate
|
|
5072
4935
|
* to OUR real LAN (so we never match a peer's Tailscale/TUN address against our
|
|
5073
|
-
* own overlay subnet).
|
|
4936
|
+
* own overlay subnet). */
|
|
5074
4937
|
function getPhysicalLanSubnets() {
|
|
5075
|
-
|
|
5076
|
-
|
|
4938
|
+
const out = [];
|
|
4939
|
+
try {
|
|
4940
|
+
for (const [name, list] of Object.entries(networkInterfaces())) {
|
|
4941
|
+
if (!list || VIRTUAL_IFACE_RE.test(name))
|
|
4942
|
+
continue;
|
|
4943
|
+
for (const info of list) {
|
|
4944
|
+
if (info.family !== "IPv4" || info.internal || isCgnatAddress(info.address))
|
|
4945
|
+
continue;
|
|
4946
|
+
const addr = ipv4ToInt(info.address);
|
|
4947
|
+
const mask = netmaskToInt(info.netmask);
|
|
4948
|
+
if (addr === undefined || mask === undefined || mask === 0)
|
|
4949
|
+
continue;
|
|
4950
|
+
out.push({ networkBits: (addr & mask) >>> 0, maskBits: mask });
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
catch {
|
|
4955
|
+
// best-effort
|
|
4956
|
+
}
|
|
4957
|
+
return out;
|
|
5077
4958
|
}
|
|
5078
4959
|
function isPrivateAddress(host) {
|
|
5079
4960
|
// 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.66",
|
|
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",
|