@decentnetwork/peer 0.1.68 → 0.1.69

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.
Files changed (2) hide show
  1. package/dist/peer.js +28 -34
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -1318,7 +1318,7 @@ export class Peer {
1318
1318
  // poison the UDP send-back path.
1319
1319
  const fromTcp = this.#remoteIsTcp(remote);
1320
1320
  if (!fromTcp) {
1321
- matchedSession.remote = { host: remote.address, port: remote.port };
1321
+ this.#adoptRemote(matchedSession, remote.address, remote.port);
1322
1322
  }
1323
1323
  else {
1324
1324
  matchedSession.hasTcpRoute = true;
@@ -1392,13 +1392,7 @@ export class Peer {
1392
1392
  state.hasTcpRoute = true;
1393
1393
  }
1394
1394
  else {
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
- }
1395
+ this.#adoptRemote(state, remote.address, remote.port);
1402
1396
  }
1403
1397
  state.lastPingRecvMs = Date.now();
1404
1398
  this.#debugVerboseLog(`hs_recv duplicate friend=${friendId} remote=${remote.address}:${remote.port} (session preserved)`);
@@ -1488,13 +1482,7 @@ export class Peer {
1488
1482
  // Don't set state.remote — there's no UDP endpoint for this peer.
1489
1483
  }
1490
1484
  else {
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
- }
1485
+ this.#adoptRemote(state, remote.address, remote.port);
1498
1486
  }
1499
1487
  if (isNewSession) {
1500
1488
  state.sendPacketNumber = 0;
@@ -1664,22 +1652,12 @@ export class Peer {
1664
1652
  state.lastRelayRecvMs = Date.now();
1665
1653
  }
1666
1654
  else {
1667
- // Adopt the source as our send endpoint — but NEVER downgrade from a
1668
- // locked same-LAN (physical) path to a non-LAN source (a hairpin packet
1669
- // via the peer's public / Tailscale-exit endpoint must not knock us off
1670
- // the direct LAN path, which is strictly better).
1671
- //
1672
- // CCTV-safe: the LAN decision (which needs a networkInterfaces() syscall)
1673
- // is made ONLY in the periodic offer/maintenance paths, which record the
1674
- // locked peer LAN host in state.lanRemoteHost. Here in the per-packet hot
1675
- // path we do a single string compare — NO interface syscall — so a
1676
- // high-rate stream (CCTV: thousands of packets/sec) never pays for it.
1677
- // (The per-packet getPhysicalLanSubnets() call this replaces was the
1678
- // CCTV CPU regression.)
1679
- const lockedOnLan = !!state.lanRemoteHost && state.remote?.host === state.lanRemoteHost;
1680
- if (!lockedOnLan || remote.address === state.lanRemoteHost) {
1681
- state.remote = { host: remote.address, port: remote.port };
1682
- }
1655
+ // Adopt the source as our send endpoint — via #adoptRemote, which
1656
+ // refuses to downgrade off a locked physical-LAN path to a public /
1657
+ // Tailscale-exit hairpin source. Cheap string compare, NO interface
1658
+ // syscall (that was the CCTV CPU regression) safe at CCTV packet
1659
+ // rates.
1660
+ this.#adoptRemote(state, remote.address, remote.port);
1683
1661
  if (!state.lastUdpRecvMs) {
1684
1662
  this.#debugLog(`udp_confirmed friend=${friendId} via=${remote.address}:${remote.port} (direct UDP path live)`);
1685
1663
  }
@@ -3146,7 +3124,7 @@ export class Peer {
3146
3124
  };
3147
3125
  this.#friendSessions.set(friendId, session);
3148
3126
  }
3149
- session.remote = { host, port };
3127
+ this.#adoptRemote(session, host, port);
3150
3128
  this.#rememberEndpointCandidate(session, host, port);
3151
3129
  if (realPublicKey && !session.friendRealPublicKey) {
3152
3130
  session.friendRealPublicKey = realPublicKey;
@@ -3155,6 +3133,22 @@ export class Peer {
3155
3133
  session.friendDhtPublicKey = dhtPublicKey;
3156
3134
  }
3157
3135
  }
3136
+ /** Single choke point for setting session.remote (the direct-UDP send
3137
+ * endpoint). Never downgrades off a LOCKED physical-LAN path to a non-LAN
3138
+ * endpoint (public srflx / Tailscale-exit hairpin): the LAN lock
3139
+ * (session.lanRemoteHost) is decided at candidate-learn / maintenance time —
3140
+ * the only places allowed the getPhysicalLanSubnets() syscall — so this guard
3141
+ * is just a string compare and is safe to call from the per-packet hot path.
3142
+ * Replacing the per-packet getPhysicalLanSubnets() call here was the fix for
3143
+ * the CCTV CPU regression. */
3144
+ #adoptRemote(session, host, port) {
3145
+ if (session.lanRemoteHost &&
3146
+ session.remote?.host === session.lanRemoteHost &&
3147
+ host !== session.lanRemoteHost) {
3148
+ return; // stay locked on the physical-LAN path
3149
+ }
3150
+ session.remote = { host, port };
3151
+ }
3158
3152
  #rememberEndpointCandidate(session, host, port) {
3159
3153
  const now = Date.now();
3160
3154
  const next = (session.endpointCandidates ?? []).filter((candidate) => !(candidate.host === host && candidate.port === port));
@@ -3499,7 +3493,7 @@ export class Peer {
3499
3493
  const haveLanCandidate = (session.endpointCandidates ?? []).some((c) => isPrivateAddress(c.host) && !isCgnatAddress(c.host) &&
3500
3494
  getPhysicalLanSubnets().some((s) => isInIpv4Subnet(c.host, s)));
3501
3495
  if (!haveRealUdp && !haveLanCandidate) {
3502
- session.remote = { host, port };
3496
+ this.#adoptRemote(session, host, port);
3503
3497
  }
3504
3498
  // Nudge a keepalive out immediately so the upgrade doesn't wait for
3505
3499
  // the next periodic ALIVE — fans out over UDP (punched) + TCP.
@@ -3684,7 +3678,7 @@ export class Peer {
3684
3678
  if (tcpAvailable)
3685
3679
  session.hasTcpRoute = true;
3686
3680
  if (connectCandidates.length > 0) {
3687
- session.remote = connectCandidates[0];
3681
+ this.#adoptRemote(session, connectCandidates[0].host, connectCandidates[0].port);
3688
3682
  const endpointKey = `${connectCandidates[0].host}:${connectCandidates[0].port}`;
3689
3683
  if (this.#lastEndpointSelectedKey.get(friendId) !== endpointKey) {
3690
3684
  this.#debugLog(`endpoint_selected friend=${friendId} endpoint=${endpointKey}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.68",
3
+ "version": "0.1.69",
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",