@decentnetwork/lan 0.1.26 → 0.1.28
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/bin/tun-helper-darwin-amd64 +0 -0
- package/bin/tun-helper-darwin-arm64 +0 -0
- package/bin/tun-helper-linux-amd64 +0 -0
- package/bin/tun-helper-linux-arm64 +0 -0
- package/dist/carrier/peer-manager.d.ts +14 -0
- package/dist/carrier/peer-manager.js +13 -0
- package/dist/daemon/server.js +27 -3
- package/dist/router/packet-router.d.ts +10 -0
- package/dist/router/packet-router.js +41 -12
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -119,6 +119,20 @@ export declare class PeerManager extends EventEmitter {
|
|
|
119
119
|
* Get an existing session for a peer (for routing logic).
|
|
120
120
|
*/
|
|
121
121
|
getSession(pubkey: string): PacketSession | null;
|
|
122
|
+
/**
|
|
123
|
+
* Snapshot of all current PacketSession entries keyed by their peer
|
|
124
|
+
* userid. Used by PacketRouter to "adopt" sessions that were created
|
|
125
|
+
* BEFORE the router itself was constructed — without this, any
|
|
126
|
+
* HANDSHAKE_REQ that lands during the small window between
|
|
127
|
+
* peerManager.start() and the PacketRouter constructor leaves a
|
|
128
|
+
* session with no "packet" listener attached. Symptom: peer
|
|
129
|
+
* stats show `packetsForwarded=N` outbound but our `packetsReceived=0`,
|
|
130
|
+
* pings flow one way and time out the other.
|
|
131
|
+
*/
|
|
132
|
+
getAllSessions(): Array<{
|
|
133
|
+
pubkey: string;
|
|
134
|
+
session: PacketSession;
|
|
135
|
+
}>;
|
|
122
136
|
/**
|
|
123
137
|
* Internal: Create and register a session.
|
|
124
138
|
*/
|
|
@@ -296,6 +296,19 @@ export class PeerManager extends EventEmitter {
|
|
|
296
296
|
getSession(pubkey) {
|
|
297
297
|
return this.sessions.get(pubkey) || null;
|
|
298
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Snapshot of all current PacketSession entries keyed by their peer
|
|
301
|
+
* userid. Used by PacketRouter to "adopt" sessions that were created
|
|
302
|
+
* BEFORE the router itself was constructed — without this, any
|
|
303
|
+
* HANDSHAKE_REQ that lands during the small window between
|
|
304
|
+
* peerManager.start() and the PacketRouter constructor leaves a
|
|
305
|
+
* session with no "packet" listener attached. Symptom: peer
|
|
306
|
+
* stats show `packetsForwarded=N` outbound but our `packetsReceived=0`,
|
|
307
|
+
* pings flow one way and time out the other.
|
|
308
|
+
*/
|
|
309
|
+
getAllSessions() {
|
|
310
|
+
return Array.from(this.sessions, ([pubkey, session]) => ({ pubkey, session }));
|
|
311
|
+
}
|
|
299
312
|
/**
|
|
300
313
|
* Internal: Create and register a session.
|
|
301
314
|
*/
|
package/dist/daemon/server.js
CHANGED
|
@@ -218,9 +218,29 @@ export class DaemonServer {
|
|
|
218
218
|
// 6. Optional dora (DHCP-style) registration. When enabled and the
|
|
219
219
|
// server is reachable, dora hands us a virtual IP and tells us
|
|
220
220
|
// who else is on the network — eliminating the manual ipam.yaml
|
|
221
|
-
// sync between operators.
|
|
222
|
-
//
|
|
221
|
+
// sync between operators.
|
|
222
|
+
//
|
|
223
|
+
// Fallback policy when dora is unreachable: DON'T use
|
|
224
|
+
// config.network.ip blindly. `agentnet init` defaults every
|
|
225
|
+
// fresh install to 10.86.1.10, so two new peers that can't
|
|
226
|
+
// reach dora will BOTH claim the same fallback and silently
|
|
227
|
+
// collide — symptom seen in the wild as packets going to the
|
|
228
|
+
// wrong daemon and 100% loss to legitimate peers. Use the
|
|
229
|
+
// deterministic-from-userid IP instead (sha256(userid) → last
|
|
230
|
+
// two octets of 10.86.X.Y). That guarantees every peer gets a
|
|
231
|
+
// unique IP keyed off identity, with no shared default to
|
|
232
|
+
// collide on. If dora comes up later, the
|
|
233
|
+
// onAllocatedIpChanged callback swaps the TUN to dora's value.
|
|
223
234
|
let tunIp = this.config.network.ip;
|
|
235
|
+
const ownUserid = this.peerManager.getPubkey();
|
|
236
|
+
const deterministicFallback = Ipam.deterministicIpForUserid(ownUserid);
|
|
237
|
+
if (tunIp === "10.86.1.10" || !tunIp) {
|
|
238
|
+
// The init-default fallback IP — every fresh decentlan installs with
|
|
239
|
+
// this. Override with a per-identity deterministic value before
|
|
240
|
+
// dora even gets to try.
|
|
241
|
+
tunIp = deterministicFallback;
|
|
242
|
+
this.logger.info(`Using deterministic fallback IP ${tunIp} (derived from userid; avoids the 10.86.1.10 init-default collision)`);
|
|
243
|
+
}
|
|
224
244
|
if (this.config.dora?.enabled && (this.config.dora.userids?.length ?? 0) > 0) {
|
|
225
245
|
// Need to be on the Carrier network before dora can talk to its
|
|
226
246
|
// server. Wait synchronously here — without joinNetwork the
|
|
@@ -234,7 +254,11 @@ export class DaemonServer {
|
|
|
234
254
|
peerManager: this.peerManager,
|
|
235
255
|
ipam: this.ipam,
|
|
236
256
|
nodeName: this.config.node.name,
|
|
237
|
-
|
|
257
|
+
// Hand dora our deterministic fallback as the requestedIp so
|
|
258
|
+
// a successful register returns the same IP across restarts
|
|
259
|
+
// (avoids the dora-stole-someone-else's-IP race that bit us
|
|
260
|
+
// when ubuntu was momentarily offline during reallocation).
|
|
261
|
+
preferredIp: tunIp,
|
|
238
262
|
// Fires when dora's background retry eventually succeeds
|
|
239
263
|
// AFTER the initial bootstrap already returned the fallback
|
|
240
264
|
// IP. At that point the TUN is up on the fallback (e.g.
|
|
@@ -80,5 +80,15 @@ export declare class PacketRouter extends EventEmitter {
|
|
|
80
80
|
* Handle incoming packet (Carrier -> peer -> TUN).
|
|
81
81
|
*/
|
|
82
82
|
private handleIncomingPacket;
|
|
83
|
+
/**
|
|
84
|
+
* Attach the "packet" listener to a single session. Shared by both the
|
|
85
|
+
* session-opened event handler AND the adopt-existing-sessions pass
|
|
86
|
+
* run when PacketRouter starts. The WeakSet check makes calling this
|
|
87
|
+
* multiple times for the same session safe — without it, an early
|
|
88
|
+
* session would get two listeners and every inbound packet would be
|
|
89
|
+
* written to TUN twice (visible as `(DUP!)` ICMP replies on the
|
|
90
|
+
* sender side).
|
|
91
|
+
*/
|
|
92
|
+
private attachSessionListener;
|
|
83
93
|
private setupCarrierHandlers;
|
|
84
94
|
}
|
|
@@ -339,22 +339,51 @@ export class PacketRouter extends EventEmitter {
|
|
|
339
339
|
this.recordDrop(parsed.dstIp, "tun-write-failed", msg);
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Attach the "packet" listener to a single session. Shared by both the
|
|
344
|
+
* session-opened event handler AND the adopt-existing-sessions pass
|
|
345
|
+
* run when PacketRouter starts. The WeakSet check makes calling this
|
|
346
|
+
* multiple times for the same session safe — without it, an early
|
|
347
|
+
* session would get two listeners and every inbound packet would be
|
|
348
|
+
* written to TUN twice (visible as `(DUP!)` ICMP replies on the
|
|
349
|
+
* sender side).
|
|
350
|
+
*/
|
|
351
|
+
attachSessionListener(pubkey, session) {
|
|
352
|
+
if (this.listenedSessions.has(session))
|
|
353
|
+
return;
|
|
354
|
+
this.listenedSessions.add(session);
|
|
355
|
+
this.sessionManager.registerInboundSession(pubkey, session);
|
|
356
|
+
session.on("packet", (packet) => {
|
|
357
|
+
this.handleIncomingPacket(pubkey, packet).catch((err) => {
|
|
358
|
+
this.logger.error("Incoming packet error:", err);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
342
362
|
setupCarrierHandlers() {
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
363
|
+
// Adopt any sessions that already exist by the time PacketRouter is
|
|
364
|
+
// constructed. PeerManager.start() opens Carrier and friends can
|
|
365
|
+
// start handshaking IMMEDIATELY; if Vultr (or any peer) sends a
|
|
366
|
+
// HANDSHAKE_REQ before PacketRouter is built, PeerManager's
|
|
367
|
+
// session-opened event fires with nobody listening — the session
|
|
368
|
+
// ends up in PeerManager.sessions but its "packet" callback is
|
|
369
|
+
// never wired, so every inbound DATA frame is silently dropped.
|
|
370
|
+
// Symptom seen in the wild: Vultr stats `packetsForwarded=N`
|
|
371
|
+
// outbound but mac-dev stats `packetsReceived=0`, pings flow one
|
|
372
|
+
// way and time out the other.
|
|
373
|
+
for (const { pubkey, session } of this.peerManager.getAllSessions()) {
|
|
374
|
+
this.attachSessionListener(pubkey, session);
|
|
375
|
+
}
|
|
376
|
+
// When PeerManager creates a session AFTER this point (inbound or
|
|
377
|
+
// outbound), register the packet handler. Guard against double-
|
|
378
|
+
// attachment: this event fires from both the responder-side
|
|
379
|
+
// handshake-req handler and the initiator-side openPacketSession
|
|
380
|
+
// completion. Without the WeakSet check, each peer-arriving packet
|
|
381
|
+
// would be written to TUN twice, which shows up as `(DUP!)` ICMP
|
|
382
|
+
// replies on the sender side.
|
|
348
383
|
this.peerManager.on("session-opened", ({ pubkey }) => {
|
|
349
384
|
const session = this.peerManager.getSession(pubkey);
|
|
350
385
|
if (session && !this.listenedSessions.has(session)) {
|
|
351
|
-
this.
|
|
352
|
-
this.sessionManager.registerInboundSession(pubkey, session);
|
|
353
|
-
session.on("packet", (packet) => {
|
|
354
|
-
this.handleIncomingPacket(pubkey, packet).catch((err) => {
|
|
355
|
-
this.logger.error("Incoming packet error:", err);
|
|
356
|
-
});
|
|
357
|
-
});
|
|
386
|
+
this.attachSessionListener(pubkey, session);
|
|
358
387
|
}
|
|
359
388
|
});
|
|
360
389
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|