@decentnetwork/peer 0.1.55 → 0.1.57
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 +3 -0
- package/dist/peer.js +70 -11
- package/package.json +1 -1
package/dist/peer.d.ts
CHANGED
|
@@ -194,6 +194,9 @@ 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[];
|
|
197
200
|
/** Last time we sent an outbound cookie request via UDP for this
|
|
198
201
|
* peer (null = never). Used to confirm Phase 1.2 retries are
|
|
199
202
|
* actually firing. */
|
package/dist/peer.js
CHANGED
|
@@ -1062,6 +1062,7 @@ export class Peer {
|
|
|
1062
1062
|
ourLocalUdpPort: this.#udp?.localPort() ?? null,
|
|
1063
1063
|
friendUdpEndpoint: friendUdp,
|
|
1064
1064
|
endpointCandidatesCount: s.endpointCandidates?.length ?? 0,
|
|
1065
|
+
endpointCandidates: (s.endpointCandidates ?? []).map((c) => `${c.host}:${c.port}`),
|
|
1065
1066
|
cookieRequestSentMs: s.cookieRequestSentMs ?? null,
|
|
1066
1067
|
};
|
|
1067
1068
|
}
|
|
@@ -1470,13 +1471,21 @@ export class Peer {
|
|
|
1470
1471
|
this.#debugLog(`hs_recv friend=${friendId} initiated=${weInitiated ? 1 : 0} remote=${remote.address}:${remote.port}`);
|
|
1471
1472
|
if (!wasEstablished) {
|
|
1472
1473
|
this.#debugLog(`friend_connected friend=${friendId} remote=${remote.address}:${remote.port}`);
|
|
1473
|
-
//
|
|
1474
|
-
//
|
|
1475
|
-
//
|
|
1476
|
-
//
|
|
1477
|
-
//
|
|
1478
|
-
//
|
|
1479
|
-
|
|
1474
|
+
// Send our UDP endpoint offer (public srflx + LAN host candidate)
|
|
1475
|
+
// whenever the current path isn't already a same-LAN direct one:
|
|
1476
|
+
// - relay-only sessions need it to bootstrap a direct UDP path; and
|
|
1477
|
+
// - sessions that came up over a PUBLIC UDP endpoint (internet
|
|
1478
|
+
// hairpin) when both peers are actually on the SAME LAN — without
|
|
1479
|
+
// the offer they never learn each other's 10.x/192.168.x address and
|
|
1480
|
+
// stay stuck on the public path, which a symmetric / hairpin NAT
|
|
1481
|
+
// breaks one-way (observed: two boxes on one LAN where each only saw
|
|
1482
|
+
// the other one-directionally). The peer's same-subnet filter drops
|
|
1483
|
+
// the LAN candidate when it doesn't apply, so offering is always safe.
|
|
1484
|
+
const r = state.remote;
|
|
1485
|
+
const remoteIsSameLan = !!r && isPrivateAddress(r.host) && !isCgnatAddress(r.host) &&
|
|
1486
|
+
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(r.host, s));
|
|
1487
|
+
const haveLanToOffer = getPhysicalLanAddresses().some((ip) => isPrivateAddress(ip) && !isCgnatAddress(ip));
|
|
1488
|
+
if ((state.hasTcpRoute && !r) || (!remoteIsSameLan && haveLanToOffer)) {
|
|
1480
1489
|
void this.#sendUdpEndpointOffer(friendId).catch(() => undefined);
|
|
1481
1490
|
}
|
|
1482
1491
|
}
|
|
@@ -1620,7 +1629,19 @@ export class Peer {
|
|
|
1620
1629
|
state.lastRelayRecvMs = Date.now();
|
|
1621
1630
|
}
|
|
1622
1631
|
else {
|
|
1623
|
-
|
|
1632
|
+
// Adopt the source as our send endpoint — but NEVER downgrade from a
|
|
1633
|
+
// same-LAN (physical) path to a non-LAN source. A packet that loops in
|
|
1634
|
+
// via the peer's public / Tailscale-exit endpoint must not knock us off
|
|
1635
|
+
// the direct LAN path, which is strictly better. (This is the other
|
|
1636
|
+
// half of the "use the physical LAN, not Tailscale" rule.)
|
|
1637
|
+
const cur = state.remote?.host;
|
|
1638
|
+
const onSameLanAlready = !!cur && !cur.startsWith("tcp:") && isPrivateAddress(cur) && !isCgnatAddress(cur) &&
|
|
1639
|
+
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(cur, s));
|
|
1640
|
+
const incomingSameLan = isPrivateAddress(remote.address) && !isCgnatAddress(remote.address) &&
|
|
1641
|
+
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(remote.address, s));
|
|
1642
|
+
if (incomingSameLan || !onSameLanAlready) {
|
|
1643
|
+
state.remote = { host: remote.address, port: remote.port };
|
|
1644
|
+
}
|
|
1624
1645
|
if (!state.lastUdpRecvMs) {
|
|
1625
1646
|
this.#debugLog(`udp_confirmed friend=${friendId} via=${remote.address}:${remote.port} (direct UDP path live)`);
|
|
1626
1647
|
}
|
|
@@ -3345,8 +3366,19 @@ export class Peer {
|
|
|
3345
3366
|
if (++ln >= 6)
|
|
3346
3367
|
clearInterval(lanTimer);
|
|
3347
3368
|
}, 120);
|
|
3348
|
-
|
|
3349
|
-
|
|
3369
|
+
// The physical LAN is strictly better than ANY public path — direct, no
|
|
3370
|
+
// NAT, no relay, and crucially no Tailscale-exit hairpin (a peer whose
|
|
3371
|
+
// internet egresses via Tailscale advertises its exit's public IP as
|
|
3372
|
+
// its srflx, so the "direct" public path actually loops out through
|
|
3373
|
+
// Tailscale and back). So adopt the LAN candidate as session.remote even
|
|
3374
|
+
// when we already have a public "real UDP" remote — UNLESS we're already
|
|
3375
|
+
// on a same-LAN address (don't flap between two LAN candidates). Without
|
|
3376
|
+
// this the session stuck to the public/Tailscale endpoint and never sent
|
|
3377
|
+
// over the LAN (observed: session.remote stayed public, data on relay).
|
|
3378
|
+
const cur = session.remote?.host;
|
|
3379
|
+
const onSameLanAlready = !!cur && !cur.startsWith("tcp:") && isPrivateAddress(cur) && !isCgnatAddress(cur) &&
|
|
3380
|
+
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(cur, s));
|
|
3381
|
+
if (!onSameLanAlready) {
|
|
3350
3382
|
session.remote = { host: lanHost, port: lanPort };
|
|
3351
3383
|
}
|
|
3352
3384
|
if (session.established) {
|
|
@@ -3961,6 +3993,25 @@ export class Peer {
|
|
|
3961
3993
|
if (!node.isTcp)
|
|
3962
3994
|
void this.#sendDhtPing(node);
|
|
3963
3995
|
}
|
|
3996
|
+
// 4. LAN-direct upgrade. Re-offer our endpoint (public srflx + LAN host
|
|
3997
|
+
// candidate) to any established friend whose current path is NOT already
|
|
3998
|
+
// a same-LAN address, as long as we have a LAN address to advertise. This
|
|
3999
|
+
// upgrades sessions that came up over a public UDP endpoint (internet
|
|
4000
|
+
// hairpin) to the direct LAN path even without a re-establish, and self-
|
|
4001
|
+
// stops once the path becomes same-LAN. The peer's same-subnet filter
|
|
4002
|
+
// drops the candidate when it doesn't apply, so this is always safe.
|
|
4003
|
+
const haveLan = getPhysicalLanAddresses().some((ip) => isPrivateAddress(ip) && !isCgnatAddress(ip));
|
|
4004
|
+
if (haveLan) {
|
|
4005
|
+
for (const [friendId, session] of this.#friendSessions.entries()) {
|
|
4006
|
+
if (!session.established)
|
|
4007
|
+
continue;
|
|
4008
|
+
const r = session.remote;
|
|
4009
|
+
const remoteIsSameLan = !!r && isPrivateAddress(r.host) && !isCgnatAddress(r.host) &&
|
|
4010
|
+
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(r.host, s));
|
|
4011
|
+
if (!remoteIsSameLan)
|
|
4012
|
+
void this.#sendUdpEndpointOffer(friendId).catch(() => undefined);
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
3964
4015
|
}
|
|
3965
4016
|
/**
|
|
3966
4017
|
* Send an onion DHT-PK announcement to a friend so they learn our DHT
|
|
@@ -4893,7 +4944,15 @@ function isInIpv4Subnet(host, subnet) {
|
|
|
4893
4944
|
// path onto the overlay or loops into our own mesh, then silently falls back to
|
|
4894
4945
|
// the relay — the snoopy/lili "lastUdpRecv=never -> bufferbloat" path. (lili
|
|
4895
4946
|
// runs Tailscale; that's exactly this.)
|
|
4896
|
-
|
|
4947
|
+
// Names of interfaces that are NOT a real physical LAN — overlays (tailscale,
|
|
4948
|
+
// zerotier, wireguard, the agentnet TUN), and especially container/VM bridges.
|
|
4949
|
+
// Docker creates `docker0` AND per-network `br-<hash>` bridges (172.x); libvirt
|
|
4950
|
+
// `virbr0`; k8s `cni0`/`flannel`/`kube`. These were leaking their 172.x address
|
|
4951
|
+
// into the LAN host-candidate offer, so a peer on a real 10.x LAN advertised a
|
|
4952
|
+
// Docker-bridge IP its LAN partner could never match → LAN-direct never engaged
|
|
4953
|
+
// (observed: two boxes on one 10.0.0.0/24 stuck hairpinning through the public
|
|
4954
|
+
// internet). `^bridge` did NOT cover `br-…`.
|
|
4955
|
+
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;
|
|
4897
4956
|
/** True for RFC 6598 carrier-grade-NAT space 100.64.0.0/10 (Tailscale's range). */
|
|
4898
4957
|
function isCgnatAddress(host) {
|
|
4899
4958
|
const o = host.split(".");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.57",
|
|
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",
|