@decentnetwork/peer 0.1.38 → 0.1.40
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.js +79 -10
- package/dist/types/peer.d.ts +10 -0
- package/package.json +1 -1
package/dist/peer.js
CHANGED
|
@@ -592,8 +592,8 @@ export class Peer {
|
|
|
592
592
|
}
|
|
593
593
|
this.#debugLog(`friend-request: announce stored on ${this.#lastSelfAnnounceStoredCount} node(s) before send`);
|
|
594
594
|
const appPayload = encodeFriendRequestPacket({
|
|
595
|
-
name: PEER_NICKNAME,
|
|
596
|
-
descr: PEER_STATUS_MESSAGE || "decent peer",
|
|
595
|
+
name: this.#opts.nickname ?? PEER_NICKNAME,
|
|
596
|
+
descr: (this.#opts.statusMessage ?? PEER_STATUS_MESSAGE) || "decent peer",
|
|
597
597
|
hello: hello ?? ""
|
|
598
598
|
});
|
|
599
599
|
const friendReqPayload = concatBytes([
|
|
@@ -2812,9 +2812,11 @@ export class Peer {
|
|
|
2812
2812
|
// callback. Most Carrier UIs read the displayed name from the
|
|
2813
2813
|
// userinfo flatbuffers payload below, but this keeps both paths
|
|
2814
2814
|
// consistent.
|
|
2815
|
-
const
|
|
2815
|
+
const nick = this.#opts.nickname ?? PEER_NICKNAME;
|
|
2816
|
+
const descr = this.#opts.statusMessage ?? PEER_STATUS_MESSAGE;
|
|
2817
|
+
const nameBytes = new TextEncoder().encode(nick);
|
|
2816
2818
|
await this.#sendMessengerPacket(friendId, PACKET_ID_NICKNAME, nameBytes);
|
|
2817
|
-
this.#debugLog(`nickname "${
|
|
2819
|
+
this.#debugLog(`nickname "${nick}" sent to ${friendId}`);
|
|
2818
2820
|
// Carrier C SDK transports its full user-profile (name, descr,
|
|
2819
2821
|
// gender, phone, email, region, has_avatar) as a FlatBuffers
|
|
2820
2822
|
// PACKET_TYPE_USERINFO (3) payload sent via toxcore's
|
|
@@ -2826,11 +2828,11 @@ export class Peer {
|
|
|
2826
2828
|
// __flatbuffers_soffset_read_from_pe (observed in Beagle 1.8.6).
|
|
2827
2829
|
await sleep(250);
|
|
2828
2830
|
const userInfo = encodeUserInfoPacket({
|
|
2829
|
-
name:
|
|
2830
|
-
descr
|
|
2831
|
+
name: nick,
|
|
2832
|
+
descr
|
|
2831
2833
|
});
|
|
2832
2834
|
await this.#sendMessengerPacket(friendId, PACKET_ID_STATUSMESSAGE, userInfo);
|
|
2833
|
-
this.#debugLog(`userinfo sent to ${friendId} (name="${
|
|
2835
|
+
this.#debugLog(`userinfo sent to ${friendId} (name="${nick}", descr="${descr}")`);
|
|
2834
2836
|
}
|
|
2835
2837
|
catch (error) {
|
|
2836
2838
|
this.#debugLog(`send profile failed for ${friendId}: ${error.message}`);
|
|
@@ -3058,7 +3060,7 @@ export class Peer {
|
|
|
3058
3060
|
// "direct" option we advertise is our srflx (public) address, which the
|
|
3059
3061
|
// NAT must hairpin back onto the LAN — most don't, so two machines in one
|
|
3060
3062
|
// office silently fall back to the TURN relay (~260ms for a <1ms hop).
|
|
3061
|
-
const lanIp =
|
|
3063
|
+
const lanIp = getPhysicalLanAddresses().find((ip) => isPrivateAddress(ip));
|
|
3062
3064
|
const lanOctets = lanIp ? lanIp.split(".").map((s) => parseInt(s, 10)) : undefined;
|
|
3063
3065
|
const lanPort = this.#udp.localPort() ?? 0;
|
|
3064
3066
|
const lanValid = !!lanOctets && lanOctets.length === 4 && !lanOctets.some((o) => Number.isNaN(o)) && lanPort > 0;
|
|
@@ -3147,8 +3149,9 @@ export class Peer {
|
|
|
3147
3149
|
const lanPort = ((payload[16] << 8) | payload[17]) >>> 0;
|
|
3148
3150
|
const sameLan = lanPort !== 0 &&
|
|
3149
3151
|
isPrivateAddress(lanHost) &&
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
+
!isCgnatAddress(lanHost) &&
|
|
3153
|
+
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(lanHost, s)) &&
|
|
3154
|
+
!(getPhysicalLanAddresses().includes(lanHost) && this.#udp.localPort() === lanPort);
|
|
3152
3155
|
if (sameLan) {
|
|
3153
3156
|
this.#rememberEndpointCandidate(session, lanHost, lanPort);
|
|
3154
3157
|
this.#debugLog(`udp-endpoint LAN candidate from ${friendId}: ${lanHost}:${lanPort} (same subnet) — punching`);
|
|
@@ -4433,6 +4436,72 @@ function isInIpv4Subnet(host, subnet) {
|
|
|
4433
4436
|
return false;
|
|
4434
4437
|
return ((ip & subnet.maskBits) >>> 0) === subnet.networkBits;
|
|
4435
4438
|
}
|
|
4439
|
+
// Interface names that are VIRTUAL / overlay networks, not a physical LAN:
|
|
4440
|
+
// Tailscale & utun tunnels (utun* on macOS, tailscale*/tun* on Linux),
|
|
4441
|
+
// WireGuard (wg*), ZeroTier (zt*), the decentlan TUN itself (agentnet*), and
|
|
4442
|
+
// assorted macOS virtual links (awdl/llw/gif/stf/bridge). A LAN-direct host
|
|
4443
|
+
// candidate MUST be a physical-LAN address: advertising/punching an overlay
|
|
4444
|
+
// address (e.g. Tailscale 100.x, or our own 10.86.x mesh IP) sends the "direct"
|
|
4445
|
+
// path onto the overlay or loops into our own mesh, then silently falls back to
|
|
4446
|
+
// the relay — the snoopy/lili "lastUdpRecv=never -> bufferbloat" path. (lili
|
|
4447
|
+
// runs Tailscale; that's exactly this.)
|
|
4448
|
+
const VIRTUAL_IFACE_RE = /^(utun|tun|tap|wg|tailscale|zt|ham|agentnet|awdl|llw|gif|stf|bridge|vnic|vmnet|veth|docker)/i;
|
|
4449
|
+
/** True for RFC 6598 carrier-grade-NAT space 100.64.0.0/10 (Tailscale's range). */
|
|
4450
|
+
function isCgnatAddress(host) {
|
|
4451
|
+
const o = host.split(".");
|
|
4452
|
+
if (o.length !== 4)
|
|
4453
|
+
return false;
|
|
4454
|
+
const a = Number(o[0]);
|
|
4455
|
+
const b = Number(o[1]);
|
|
4456
|
+
return a === 100 && b >= 64 && b <= 127;
|
|
4457
|
+
}
|
|
4458
|
+
/** Local IPv4 addresses on PHYSICAL interfaces only (excludes overlay/virtual
|
|
4459
|
+
* interfaces by name and the CGNAT range). Used for LAN-direct host candidates. */
|
|
4460
|
+
function getPhysicalLanAddresses() {
|
|
4461
|
+
const out = [];
|
|
4462
|
+
try {
|
|
4463
|
+
for (const [name, list] of Object.entries(networkInterfaces())) {
|
|
4464
|
+
if (!list || VIRTUAL_IFACE_RE.test(name))
|
|
4465
|
+
continue;
|
|
4466
|
+
for (const info of list) {
|
|
4467
|
+
if (info.family !== "IPv4" || info.internal)
|
|
4468
|
+
continue;
|
|
4469
|
+
if (isCgnatAddress(info.address))
|
|
4470
|
+
continue;
|
|
4471
|
+
out.push(info.address);
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
catch {
|
|
4476
|
+
// best-effort
|
|
4477
|
+
}
|
|
4478
|
+
return out;
|
|
4479
|
+
}
|
|
4480
|
+
/** Subnets of PHYSICAL interfaces only — for matching a peer's host candidate
|
|
4481
|
+
* to OUR real LAN (so we never match a peer's Tailscale/TUN address against our
|
|
4482
|
+
* own overlay subnet). */
|
|
4483
|
+
function getPhysicalLanSubnets() {
|
|
4484
|
+
const out = [];
|
|
4485
|
+
try {
|
|
4486
|
+
for (const [name, list] of Object.entries(networkInterfaces())) {
|
|
4487
|
+
if (!list || VIRTUAL_IFACE_RE.test(name))
|
|
4488
|
+
continue;
|
|
4489
|
+
for (const info of list) {
|
|
4490
|
+
if (info.family !== "IPv4" || info.internal || isCgnatAddress(info.address))
|
|
4491
|
+
continue;
|
|
4492
|
+
const addr = ipv4ToInt(info.address);
|
|
4493
|
+
const mask = netmaskToInt(info.netmask);
|
|
4494
|
+
if (addr === undefined || mask === undefined || mask === 0)
|
|
4495
|
+
continue;
|
|
4496
|
+
out.push({ networkBits: (addr & mask) >>> 0, maskBits: mask });
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
catch {
|
|
4501
|
+
// best-effort
|
|
4502
|
+
}
|
|
4503
|
+
return out;
|
|
4504
|
+
}
|
|
4436
4505
|
function isPrivateAddress(host) {
|
|
4437
4506
|
// RFC1918 IPv4: 10/8, 172.16/12, 192.168/16; loopback 127/8;
|
|
4438
4507
|
// link-local 169.254/16; carrier-grade NAT 100.64/10. IPv6 link-local fe80::/10
|
package/dist/types/peer.d.ts
CHANGED
|
@@ -40,6 +40,16 @@ export type PeerOptions = {
|
|
|
40
40
|
* optimisation only applies to packet 64 and custom-id data is duplicated.
|
|
41
41
|
*/
|
|
42
42
|
bulkDataPacketId?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Display name this peer advertises to friends (Carrier nickname, packet 48 +
|
|
45
|
+
* the userinfo profile). Without it every node reports the build default
|
|
46
|
+
* "@decentnetwork/peer" and friend lists can't tell anyone apart. decentlan
|
|
47
|
+
* passes its node name (e.g. "mac-dev", "cn", "tokyo"). Falls back to
|
|
48
|
+
* DECENT_PEER_NAME / the build default when unset.
|
|
49
|
+
*/
|
|
50
|
+
nickname?: string;
|
|
51
|
+
/** Status/description line shown alongside the nickname. */
|
|
52
|
+
statusMessage?: string;
|
|
43
53
|
compatibilityMode?: CompatibilityMode;
|
|
44
54
|
debugLabel?: string;
|
|
45
55
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
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",
|