@decentnetwork/peer 0.1.61 → 0.1.63

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 +27 -16
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -1636,22 +1636,18 @@ export class Peer {
1636
1636
  innerKind === PACKET_ID_FILE_SENDREQUEST ||
1637
1637
  innerKind === PACKET_ID_FILE_CONTROL ||
1638
1638
  innerKind === PACKET_ID_FILE_DATA;
1639
- if (!isSingleTransportKind && opened.packetNumber < (state.receiveBufferStart ?? 0)) {
1640
- continue;
1641
- }
1642
- state.peerBaseNonce[state.peerBaseNonce.length - 2] = packet[1];
1643
- state.peerBaseNonce[state.peerBaseNonce.length - 1] = packet[2];
1644
- incrementNonce(state.peerBaseNonce);
1639
+ // Confirm the transport path + liveness for EVERY decrypted packet —
1640
+ // INCLUDING a dual-send duplicate. Receiving over this transport proves
1641
+ // it works, so this MUST run BEFORE the dedup drop below. Otherwise the
1642
+ // duplicate UDP copy (the relay copy already advanced the buffer) is
1643
+ // dropped without ever marking the direct-UDP path live → lastUdpRecvMs
1644
+ // stays unset → udpFresh false → bulk data (files) never ride the LAN
1645
+ // even when LAN UDP is flowing both ways (the snoopy slow-file bug).
1645
1646
  state.lastPingRecvMs = Date.now();
1646
- // Same TCP-vs-UDP rule as the handshake handlers: don't poison
1647
- // the UDP send-back endpoint when this packet came in via TCP.
1648
1647
  if (this.#remoteIsTcp(remote)) {
1649
1648
  state.hasTcpRoute = true;
1650
1649
  }
1651
1650
  else if (viaRelay) {
1652
- // Arrived over the TURN relay. Track relay freshness separately;
1653
- // do NOT set state.remote (that's the direct endpoint). The relay
1654
- // address it came from is already in state.relayRemote.
1655
1651
  if (!state.lastRelayRecvMs) {
1656
1652
  this.#debugLog(`relay_confirmed friend=${friendId} via=${remote.address}:${remote.port} (TURN relay path live)`);
1657
1653
  }
@@ -1659,10 +1655,9 @@ export class Peer {
1659
1655
  }
1660
1656
  else {
1661
1657
  // Adopt the source as our send endpoint — but NEVER downgrade from a
1662
- // same-LAN (physical) path to a non-LAN source. A packet that loops in
1663
- // via the peer's public / Tailscale-exit endpoint must not knock us off
1664
- // the direct LAN path, which is strictly better. (This is the other
1665
- // half of the "use the physical LAN, not Tailscale" rule.)
1658
+ // same-LAN (physical) path to a non-LAN source (a hairpin packet via
1659
+ // the peer's public / Tailscale-exit endpoint must not knock us off
1660
+ // the direct LAN path, which is strictly better).
1666
1661
  const cur = state.remote?.host;
1667
1662
  const onSameLanAlready = !!cur && !cur.startsWith("tcp:") && isPrivateAddress(cur) && !isCgnatAddress(cur) &&
1668
1663
  getPhysicalLanSubnets().some((s) => isInIpv4Subnet(cur, s));
@@ -1676,6 +1671,15 @@ export class Peer {
1676
1671
  }
1677
1672
  state.lastUdpRecvMs = Date.now();
1678
1673
  }
1674
+ // De-duplicate: skip the DISPATCH + nonce advance for an already-seen
1675
+ // packet number. The transport confirmation above already ran, so a
1676
+ // duplicate still keeps the path fresh.
1677
+ if (!isSingleTransportKind && opened.packetNumber < (state.receiveBufferStart ?? 0)) {
1678
+ continue;
1679
+ }
1680
+ state.peerBaseNonce[state.peerBaseNonce.length - 2] = packet[1];
1681
+ state.peerBaseNonce[state.peerBaseNonce.length - 1] = packet[2];
1682
+ incrementNonce(state.peerBaseNonce);
1679
1683
  state.receiveBufferStart = Math.max(state.receiveBufferStart ?? 0, (opened.packetNumber + 1) >>> 0);
1680
1684
  const kind = opened.payload[0];
1681
1685
  const inner = opened.payload.slice(1);
@@ -3472,7 +3476,14 @@ export class Peer {
3472
3476
  // upgrading the path to direct UDP in both directions with no new
3473
3477
  // handshake. If UDP never works, the periodic offer re-punches.
3474
3478
  const haveRealUdp = session.remote && !session.remote.host?.startsWith("tcp:") && session.remote.port !== 0;
3475
- if (!haveRealUdp) {
3479
+ // NEVER point the session at the public srflx when we know a same-LAN
3480
+ // candidate for this peer: the physical LAN is strictly better, and letting
3481
+ // the srflx win here caused a LAN↔public FLAP (the LAN block above set
3482
+ // remote=LAN, then this overwrote it with the Tailscale-exit public IP),
3483
+ // so the direct LAN path never stabilised and udpRecv never confirmed.
3484
+ const haveLanCandidate = (session.endpointCandidates ?? []).some((c) => isPrivateAddress(c.host) && !isCgnatAddress(c.host) &&
3485
+ getPhysicalLanSubnets().some((s) => isInIpv4Subnet(c.host, s)));
3486
+ if (!haveRealUdp && !haveLanCandidate) {
3476
3487
  session.remote = { host, port };
3477
3488
  }
3478
3489
  // Nudge a keepalive out immediately so the upgrade doesn't wait for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
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",