@decentnetwork/peer 0.1.129 → 0.1.131

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 +56 -2
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -968,7 +968,7 @@ export class Peer {
968
968
  uint32ToLe(friendAddress.nospam),
969
969
  appPayload
970
970
  ]);
971
- const routes = await this.#discoverFriendRoutes(friendAddress.publicKey);
971
+ const routes = await this.#discoverFriendRoutes(friendAddress.publicKey, true);
972
972
  this.#debugLog(`friend route discovery done routes=${routes.length}`);
973
973
  if (routes.length === 0) {
974
974
  // The direct net_crypto send needs a known endpoint for the target; to
@@ -2928,10 +2928,35 @@ export class Peer {
2928
2928
  // Ignore invalid offline payloads.
2929
2929
  }
2930
2930
  }
2931
- async #discoverFriendRoutes(friendPublicKey) {
2931
+ /**
2932
+ * @param userInitiated skips the budget. Adding a friend must not wait on a
2933
+ * throttle meant for background upkeep.
2934
+ */
2935
+ async #discoverFriendRoutes(friendPublicKey, userInitiated = false) {
2932
2936
  if (!this.#keyPair) {
2933
2937
  throw new Error("Peer is not started");
2934
2938
  }
2939
+ // Same per-friend budget as #discoverAndCacheFriendEndpoint, enforced HERE
2940
+ // for the same reason: this is the THIRD time today the cost simply moved
2941
+ // to whichever caller was not gated. #sendOnionDhtPk's call sites were
2942
+ // throttled, so the sweep underneath them became the top crypto entry
2943
+ // instead — 17.9% of wall on gfax, climbing (20% -> 27% over ten minutes)
2944
+ // rather than settling, because the DHT-PK path kept a FLAT 25s interval
2945
+ // while the endpoint path had already been given backoff.
2946
+ //
2947
+ // A gate inside the function cannot be bypassed by a call site added later.
2948
+ const routeKey = `routes:${carrierIdFromPublicKey(friendPublicKey)}`;
2949
+ if (!userInitiated) {
2950
+ const misses = this.#onionLookupMisses.get(routeKey) ?? 0;
2951
+ const budget = Math.min(DHT_PK_ANNOUNCE_COOLDOWN_MS * Math.max(1, misses), ONION_LOOKUP_MAX_BACKOFF_MS);
2952
+ const since = Date.now() - (this.#onionLookupCooldown.get(routeKey) ?? 0);
2953
+ if (since < budget) {
2954
+ this.#debugLog(`route discovery for ${routeKey} skipped: ${Math.round(since / 1000)}s ` +
2955
+ `since last (budget ${Math.round(budget / 1000)}s, misses ${misses})`);
2956
+ return [];
2957
+ }
2958
+ this.#onionLookupCooldown.set(routeKey, Date.now());
2959
+ }
2935
2960
  const searchKey = createEphemeralKeyPair();
2936
2961
  const routes = [];
2937
2962
  const routeSeen = new Set();
@@ -3075,6 +3100,15 @@ export class Peer {
3075
3100
  }
3076
3101
  }
3077
3102
  }
3103
+ // Miss accounting drives the backoff above. Without it the budget stays at
3104
+ // its 25s floor forever and the gate does nothing for the friends that
3105
+ // actually cost us — the ones that never resolve.
3106
+ if (!userInitiated) {
3107
+ if (routes.length > 0)
3108
+ this.#onionLookupMisses.delete(routeKey);
3109
+ else
3110
+ this.#onionLookupMisses.set(routeKey, (this.#onionLookupMisses.get(routeKey) ?? 0) + 1);
3111
+ }
3078
3112
  return routes;
3079
3113
  }
3080
3114
  async #discoverAndCacheFriendEndpoint(friendId, searchPublicKey) {
@@ -4821,6 +4855,26 @@ export class Peer {
4821
4855
  if (this.#isUnroutableSelfSource(host, port, observed)) {
4822
4856
  return;
4823
4857
  }
4858
+ // An ADVERTISED private address is only usable if we are on that network.
4859
+ //
4860
+ // Same provenance rule as the self-address check above, one step wider: a
4861
+ // packet that ARRIVED from a private address proves the path exists, but a
4862
+ // peer merely listing one proves nothing — RFC1918 ranges are reused
4863
+ // everywhere. Seen in the field between tokyo (Japan) and a laptop at home:
4864
+ // tokyo adopted 10.0.0.245, an address that only means anything inside that
4865
+ // house, and sent every IP packet there. Friend online, session
4866
+ // established, transport "both", ping 100% loss.
4867
+ //
4868
+ // Only reached for advertised candidates, so the cached-but-not-free
4869
+ // getPhysicalLanSubnets() stays off the per-packet path (see the CCTV CPU
4870
+ // regression note on #isUnroutableSelfSource).
4871
+ if (!observed &&
4872
+ isPrivateAddress(host) &&
4873
+ !isCgnatAddress(host) &&
4874
+ !getPhysicalLanSubnets().some((sub) => isInIpv4Subnet(host, sub))) {
4875
+ this.#debugLog(`ignoring advertised private endpoint ${host}:${port} — not on any of our LANs`);
4876
+ return;
4877
+ }
4824
4878
  if (session.lanRemoteHost &&
4825
4879
  session.remote?.host === session.lanRemoteHost &&
4826
4880
  host !== session.lanRemoteHost &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.129",
3
+ "version": "0.1.131",
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",