@decentnetwork/peer 0.1.38 → 0.1.39

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 +70 -3
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -3058,7 +3058,7 @@ export class Peer {
3058
3058
  // "direct" option we advertise is our srflx (public) address, which the
3059
3059
  // NAT must hairpin back onto the LAN — most don't, so two machines in one
3060
3060
  // office silently fall back to the TURN relay (~260ms for a <1ms hop).
3061
- const lanIp = getLocalIpv4Addresses().find((ip) => isPrivateAddress(ip));
3061
+ const lanIp = getPhysicalLanAddresses().find((ip) => isPrivateAddress(ip));
3062
3062
  const lanOctets = lanIp ? lanIp.split(".").map((s) => parseInt(s, 10)) : undefined;
3063
3063
  const lanPort = this.#udp.localPort() ?? 0;
3064
3064
  const lanValid = !!lanOctets && lanOctets.length === 4 && !lanOctets.some((o) => Number.isNaN(o)) && lanPort > 0;
@@ -3147,8 +3147,9 @@ export class Peer {
3147
3147
  const lanPort = ((payload[16] << 8) | payload[17]) >>> 0;
3148
3148
  const sameLan = lanPort !== 0 &&
3149
3149
  isPrivateAddress(lanHost) &&
3150
- getLocalIpv4Subnets().some((s) => isInIpv4Subnet(lanHost, s)) &&
3151
- !(getLocalIpv4Addresses().includes(lanHost) && this.#udp.localPort() === lanPort);
3150
+ !isCgnatAddress(lanHost) &&
3151
+ getPhysicalLanSubnets().some((s) => isInIpv4Subnet(lanHost, s)) &&
3152
+ !(getPhysicalLanAddresses().includes(lanHost) && this.#udp.localPort() === lanPort);
3152
3153
  if (sameLan) {
3153
3154
  this.#rememberEndpointCandidate(session, lanHost, lanPort);
3154
3155
  this.#debugLog(`udp-endpoint LAN candidate from ${friendId}: ${lanHost}:${lanPort} (same subnet) — punching`);
@@ -4433,6 +4434,72 @@ function isInIpv4Subnet(host, subnet) {
4433
4434
  return false;
4434
4435
  return ((ip & subnet.maskBits) >>> 0) === subnet.networkBits;
4435
4436
  }
4437
+ // Interface names that are VIRTUAL / overlay networks, not a physical LAN:
4438
+ // Tailscale & utun tunnels (utun* on macOS, tailscale*/tun* on Linux),
4439
+ // WireGuard (wg*), ZeroTier (zt*), the decentlan TUN itself (agentnet*), and
4440
+ // assorted macOS virtual links (awdl/llw/gif/stf/bridge). A LAN-direct host
4441
+ // candidate MUST be a physical-LAN address: advertising/punching an overlay
4442
+ // address (e.g. Tailscale 100.x, or our own 10.86.x mesh IP) sends the "direct"
4443
+ // path onto the overlay or loops into our own mesh, then silently falls back to
4444
+ // the relay — the snoopy/lili "lastUdpRecv=never -> bufferbloat" path. (lili
4445
+ // runs Tailscale; that's exactly this.)
4446
+ const VIRTUAL_IFACE_RE = /^(utun|tun|tap|wg|tailscale|zt|ham|agentnet|awdl|llw|gif|stf|bridge|vnic|vmnet|veth|docker)/i;
4447
+ /** True for RFC 6598 carrier-grade-NAT space 100.64.0.0/10 (Tailscale's range). */
4448
+ function isCgnatAddress(host) {
4449
+ const o = host.split(".");
4450
+ if (o.length !== 4)
4451
+ return false;
4452
+ const a = Number(o[0]);
4453
+ const b = Number(o[1]);
4454
+ return a === 100 && b >= 64 && b <= 127;
4455
+ }
4456
+ /** Local IPv4 addresses on PHYSICAL interfaces only (excludes overlay/virtual
4457
+ * interfaces by name and the CGNAT range). Used for LAN-direct host candidates. */
4458
+ function getPhysicalLanAddresses() {
4459
+ const out = [];
4460
+ try {
4461
+ for (const [name, list] of Object.entries(networkInterfaces())) {
4462
+ if (!list || VIRTUAL_IFACE_RE.test(name))
4463
+ continue;
4464
+ for (const info of list) {
4465
+ if (info.family !== "IPv4" || info.internal)
4466
+ continue;
4467
+ if (isCgnatAddress(info.address))
4468
+ continue;
4469
+ out.push(info.address);
4470
+ }
4471
+ }
4472
+ }
4473
+ catch {
4474
+ // best-effort
4475
+ }
4476
+ return out;
4477
+ }
4478
+ /** Subnets of PHYSICAL interfaces only — for matching a peer's host candidate
4479
+ * to OUR real LAN (so we never match a peer's Tailscale/TUN address against our
4480
+ * own overlay subnet). */
4481
+ function getPhysicalLanSubnets() {
4482
+ const out = [];
4483
+ try {
4484
+ for (const [name, list] of Object.entries(networkInterfaces())) {
4485
+ if (!list || VIRTUAL_IFACE_RE.test(name))
4486
+ continue;
4487
+ for (const info of list) {
4488
+ if (info.family !== "IPv4" || info.internal || isCgnatAddress(info.address))
4489
+ continue;
4490
+ const addr = ipv4ToInt(info.address);
4491
+ const mask = netmaskToInt(info.netmask);
4492
+ if (addr === undefined || mask === undefined || mask === 0)
4493
+ continue;
4494
+ out.push({ networkBits: (addr & mask) >>> 0, maskBits: mask });
4495
+ }
4496
+ }
4497
+ }
4498
+ catch {
4499
+ // best-effort
4500
+ }
4501
+ return out;
4502
+ }
4436
4503
  function isPrivateAddress(host) {
4437
4504
  // RFC1918 IPv4: 10/8, 172.16/12, 192.168/16; loopback 127/8;
4438
4505
  // link-local 169.254/16; carrier-grade NAT 100.64/10. IPv6 link-local fe80::/10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
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",