@decentnetwork/lan 0.1.75 → 0.1.77

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
@@ -126,11 +126,15 @@ export class ConnectProxy {
126
126
  }
127
127
  let bytes = 0;
128
128
  let closed = false;
129
+ let lastBytes = -1;
130
+ let idleTimer;
129
131
  const upstream = net.connect(port, host);
130
132
  const tearDown = (reason) => {
131
133
  if (closed)
132
134
  return;
133
135
  closed = true;
136
+ if (idleTimer)
137
+ clearInterval(idleTimer);
134
138
  upstream.destroy();
135
139
  clientSocket.destroy();
136
140
  this.stats.active = Math.max(0, this.stats.active - 1);
@@ -181,6 +185,26 @@ export class ConnectProxy {
181
185
  upstream.on("end", () => tearDown("upstream end"));
182
186
  clientSocket.on("error", (err) => tearDown(`client error: ${err.message}`));
183
187
  clientSocket.on("end", () => tearDown("client end"));
188
+ // "close" catches abrupt teardowns (RST / socket destroyed) that never
189
+ // emit "end" — without these, a half-dead tunnel leaks.
190
+ upstream.on("close", () => tearDown("upstream close"));
191
+ clientSocket.on("close", () => tearDown("client close"));
192
+ // Idle reaper: when the Carrier path drops mid-stream the broken side
193
+ // sends no FIN, so neither "end" nor "error" fires and the sockets would
194
+ // leak until the kernel's multi-hour TCP keepalive — they pile up under
195
+ // load (1080p opens many high-bandwidth tunnels; two clients on one exit
196
+ // compounds it) and starve the exit. If no bytes flow in EITHER direction
197
+ // across two idle ticks, tear it down. Active streaming keeps `bytes`
198
+ // climbing (and backpressure pauses a one-sided flow), so only genuinely
199
+ // dead tunnels are reaped. Tunable via AGENTNET_TUNNEL_IDLE_MS.
200
+ const idleMs = Number(process.env.AGENTNET_TUNNEL_IDLE_MS) || 120_000;
201
+ idleTimer = setInterval(() => {
202
+ if (bytes === lastBytes)
203
+ tearDown("idle (no data — Carrier path likely dropped)");
204
+ else
205
+ lastBytes = bytes;
206
+ }, idleMs);
207
+ idleTimer.unref?.();
184
208
  }
185
209
  refuse(clientSocket, src, srcName, target, code, reason) {
186
210
  this.stats.totalRefused += 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
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",