@decentnetwork/lan 0.1.26 → 0.1.27

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.
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
  */
@@ -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
- // When PeerManager creates a session (inbound or outbound), register packet handler.
344
- // Guard against double-attachment: this event fires from both the responder-side
345
- // handshake-req handler and the initiator-side openPacketSession completion. Without
346
- // the WeakSet check, each peer-arriving packet would be written to TUN twice, which
347
- // shows up as `(DUP!)` ICMP replies on the sender side.
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.listenedSessions.add(session);
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.26",
3
+ "version": "0.1.27",
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",