@decentnetwork/lan 0.1.149 → 0.1.150

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
@@ -131,9 +131,17 @@ export class ConnectProxy {
131
131
  }
132
132
  const host = m[1];
133
133
  const port = Number(m[2]);
134
- const allowedPorts = this.opts.allowConnectPorts ?? [443, 80];
135
- if (!allowedPorts.includes(port)) {
136
- this.refuse(clientSocket, src, srcName, `${host}:${port}`, 403, `port ${port} not in allowConnectPorts`);
134
+ // Port policy: an EXPLICIT allowConnectPorts restricts to that list; UNSET
135
+ // (the default) allows any port except known-abuse ones (SMTP). The old
136
+ // default of [443,80] silently broke real apps that use non-standard ports
137
+ // (verified: the CCTV/Migu app's uem.migu.cn:18088 got 403'd here while the
138
+ // host itself was perfectly reachable). These are friend-gated private
139
+ // exits, so blanket port blocking bought little and cost app compatibility.
140
+ const DENY_PORTS = new Set([25, 465, 587]); // SMTP — block spam relay abuse
141
+ const allowedPorts = this.opts.allowConnectPorts;
142
+ const portBlocked = allowedPorts ? !allowedPorts.includes(port) : DENY_PORTS.has(port);
143
+ if (portBlocked) {
144
+ this.refuse(clientSocket, src, srcName, `${host}:${port}`, 403, `port ${port} not permitted`);
137
145
  return;
138
146
  }
139
147
  const allowHosts = this.opts.allowHosts ?? [];
@@ -386,8 +386,14 @@ export function startMultiExitRouter(opts) {
386
386
  // Unmeasured exits assume the region's median (or a neutral default) so a
387
387
  // new exit is tried but doesn't monopolize before it's proven.
388
388
  const measuredVals = pool.map((e) => e.speedEwma).filter((v) => v !== null).sort((a, b) => a - b);
389
- const medianBps = measuredVals.length > 0 ? measuredVals[Math.floor(measuredVals.length / 2)] : 2_000_000;
390
- const score = (e) => (e.speedEwma ?? medianBps) / (e.active + 1);
389
+ // Unmeasured exits get a PESSIMISTIC assumption (the region's slowest proven
390
+ // speed, or a low floor) NOT the median. A long-lived video tunnel pins to
391
+ // ONE exit for its whole duration, so it must land on a PROVEN-fast exit, not
392
+ // an unproven one that might turn out to be 0.3 Mbps and stall the player.
393
+ // Unproven exits still get overflow traffic once the proven ones fill up,
394
+ // which measures them safely without risking the video.
395
+ const unprovenBps = measuredVals.length > 0 ? measuredVals[0] : 800_000;
396
+ const score = (e) => (e.speedEwma ?? unprovenBps) / (e.active + 1);
391
397
  return [...pool].sort((a, b) => score(b) - score(a) || a.served - b.served);
392
398
  }
393
399
  // Whether ANY exit is configured for a region (healthy or not). Lets us tell
@@ -399,11 +405,16 @@ export function startMultiExitRouter(opts) {
399
405
  function forward(exit, target, client, head, onFail) {
400
406
  const up = net.connect(exit.port, exit.host);
401
407
  let established = false;
402
- const fail = (why) => {
408
+ const fail = (why, tripExit = true) => {
403
409
  if (!established) {
404
- // Couldn't even establish an instability signal for the breaker, then
405
- // fail over to the next exit.
406
- recordStall(exit, why);
410
+ // Only a CONNECTION-level failure (exit unreachable: timeout / conn
411
+ // error) is an EXIT fault → feed the breaker. An UPSTREAM status (the
412
+ // exit answered but the ORIGIN returned 502/403/etc.) is the origin's
413
+ // problem — it fails identically on every exit, so tripping the exit for
414
+ // it churns the whole pool (a single dead host like sdc3.10086.cn or
415
+ // uem.migu.cn was knocking out all 6 China exits). Just fail over.
416
+ if (tripExit)
417
+ recordStall(exit, why);
407
418
  up.destroy();
408
419
  onFail(why);
409
420
  }
@@ -499,7 +510,8 @@ export function startMultiExitRouter(opts) {
499
510
  up.on("error", () => client.destroy());
500
511
  }
501
512
  else {
502
- fail(`upstream ${status}`);
513
+ // Exit answered but the ORIGIN errored (502/403/…) — not an exit fault.
514
+ fail(`upstream ${status}`, false);
503
515
  }
504
516
  };
505
517
  up.on("data", onData);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.149",
3
+ "version": "0.1.150",
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",