@decentnetwork/lan 0.1.180 → 0.1.182
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/proxy/multi-exit-router.js +30 -1
- package/dist/proxy/route-learner.d.ts +12 -2
- package/dist/proxy/route-learner.js +13 -3
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -136,6 +136,10 @@ const TRIP_AFTER_UPSTREAM_FAILS = 5; // consecutive non-200 CONNECT responses (e
|
|
|
136
136
|
// next to a 1.2 Mbps one) it's benched while a healthy exit exists, so heavy
|
|
137
137
|
// streams don't stall on it. Unproven (unmeasured) exits are never benched.
|
|
138
138
|
const SLOW_EXIT_FLOOR_FRACTION = 0.4;
|
|
139
|
+
// Hosts that must NEVER be learned into an exit region — global services
|
|
140
|
+
// reachable direct from anywhere; routing them through a china exit only 502s
|
|
141
|
+
// and starves it. Guards against the recurring google→china mislearning.
|
|
142
|
+
const NEVER_LEARN = /(^|\.)(google|gstatic|googleapis|googleusercontent|ggpht|gvt1|gvt2|youtube|ytimg|doubleclick|google-analytics|googletagmanager|firebase|crashlytics|facebook|fbcdn|instagram|twitter|twimg|cloudflare|cloudfront|akamai|akamaihd|amazonaws|apple|icloud|mzstatic|microsoft|windows|office|live|azureedge|github|githubusercontent|gitlab|cdn\.jsdelivr|unpkg|npmjs)\.(com|net|org|io|co|edu|dev|app)$/i;
|
|
139
143
|
const TRIP_COOLDOWN_MS = 30_000; // how long a tripped exit stays excluded before it's retried
|
|
140
144
|
// Measures peak short-window throughput of a byte stream. Feed it every chunk
|
|
141
145
|
// length via add(); read peakBps at the end. Peak (not average) shrugs off TCP
|
|
@@ -236,6 +240,20 @@ export function startMultiExitRouter(opts) {
|
|
|
236
240
|
// route kept failing get auto-promoted into the fallback region (and
|
|
237
241
|
// persisted). Explicit domain lists always win over learned entries.
|
|
238
242
|
const learner = new RouteLearner(opts.learnedRoutesPath, log);
|
|
243
|
+
// Auto-purge on startup: drop any EXISTING learned entry for a global host
|
|
244
|
+
// (google/gstatic/…) that older builds mislearned into an exit. So a user just
|
|
245
|
+
// updating + restarting is self-healed — no manual learned-routes cleanup.
|
|
246
|
+
{
|
|
247
|
+
let purged = 0;
|
|
248
|
+
for (const { host } of learner.entries()) {
|
|
249
|
+
if (NEVER_LEARN.test(host)) {
|
|
250
|
+
learner.unlearn(host);
|
|
251
|
+
purged++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (purged > 0)
|
|
255
|
+
log(`purged ${purged} stale global-host route(s) mislearned into an exit`);
|
|
256
|
+
}
|
|
239
257
|
const fallbackRegion = opts.fallbackRegion
|
|
240
258
|
?? regions.find((r) => opts.exits.some((e) => (e.region ?? "default") === r.name))?.name
|
|
241
259
|
?? null;
|
|
@@ -257,6 +275,12 @@ export function startMultiExitRouter(opts) {
|
|
|
257
275
|
function noteDirectBad(host) {
|
|
258
276
|
if (!fallbackRegion || !regionHasExits(fallbackRegion))
|
|
259
277
|
return;
|
|
278
|
+
// Never learn a clearly-GLOBAL host into an exit region. These are reachable
|
|
279
|
+
// direct from anywhere; a china exit only 502s them and wastes its bandwidth
|
|
280
|
+
// (the recurring google→china pollution). Belt-and-suspenders alongside the
|
|
281
|
+
// preconnect fix in suspiciousClose.
|
|
282
|
+
if (NEVER_LEARN.test(host))
|
|
283
|
+
return;
|
|
260
284
|
if (learner.recordDirectBad(host))
|
|
261
285
|
learner.learn(host, fallbackRegion);
|
|
262
286
|
}
|
|
@@ -669,7 +693,9 @@ export function startMultiExitRouter(opts) {
|
|
|
669
693
|
// fallback region for next time.
|
|
670
694
|
const t0 = Date.now();
|
|
671
695
|
let down = 0;
|
|
696
|
+
let sentUp = head?.length ?? 0; // bytes the CLIENT sent upstream (ClientHello etc.)
|
|
672
697
|
up.on("data", (d) => { down += d.length; });
|
|
698
|
+
client.on("data", (d) => { sentUp += d.length; });
|
|
673
699
|
let closed = false;
|
|
674
700
|
const done = () => {
|
|
675
701
|
if (closed)
|
|
@@ -678,7 +704,10 @@ export function startMultiExitRouter(opts) {
|
|
|
678
704
|
directActive = Math.max(0, directActive - 1);
|
|
679
705
|
if (local)
|
|
680
706
|
return;
|
|
681
|
-
|
|
707
|
+
// A short close only counts as a geo-block if the client actually sent a
|
|
708
|
+
// request. A speculative preconnect (sentUp≈0) is ignored, so global
|
|
709
|
+
// hosts (google/gstatic) no longer get mislearned into a china exit.
|
|
710
|
+
if (RouteLearner.suspiciousClose(down, sentUp, Date.now() - t0))
|
|
682
711
|
noteDirectBad(h);
|
|
683
712
|
else
|
|
684
713
|
learner.recordDirectGood(h);
|
|
@@ -8,6 +8,13 @@ export declare const MISCLASSIFIED_AFTER = 2;
|
|
|
8
8
|
* and is never counted. */
|
|
9
9
|
export declare const SUSPICIOUS_MS = 3000;
|
|
10
10
|
export declare const SUSPICIOUS_BYTES = 1024;
|
|
11
|
+
/** Minimum bytes the CLIENT must have sent upstream before a short close counts
|
|
12
|
+
* as a geo-block. A real GFW RST happens AFTER the client's TLS ClientHello
|
|
13
|
+
* (>~100 bytes). A speculative preconnect that Chrome opens and closes WITHOUT
|
|
14
|
+
* sending anything (0 bytes up) is NOT a block — counting it mislearned
|
|
15
|
+
* google/gstatic/googleapis preconnects into the china exit, which then 502'd
|
|
16
|
+
* and starved CCTV. */
|
|
17
|
+
export declare const MIN_CLIENTHELLO_BYTES = 64;
|
|
11
18
|
export declare class RouteLearner {
|
|
12
19
|
#private;
|
|
13
20
|
constructor(path?: string, log?: (msg: string) => void);
|
|
@@ -19,8 +26,11 @@ export declare class RouteLearner {
|
|
|
19
26
|
recordDirectBad(host: string): boolean;
|
|
20
27
|
/** A DIRECT tunnel to the host worked properly — clear its strike counter. */
|
|
21
28
|
recordDirectGood(host: string): void;
|
|
22
|
-
/** Classify an established tunnel's ending: was it a suspicious early close
|
|
23
|
-
|
|
29
|
+
/** Classify an established tunnel's ending: was it a suspicious early close
|
|
30
|
+
* (GFW-style RST after a real request)? `bytesUp` is what the CLIENT sent —
|
|
31
|
+
* a connection that sent no ClientHello (a Chrome preconnect) is NOT a
|
|
32
|
+
* geo-block and must never be learned. */
|
|
33
|
+
static suspiciousClose(bytesDown: number, bytesUp: number, lifetimeMs: number): boolean;
|
|
24
34
|
learn(host: string, region: string): void;
|
|
25
35
|
/** The learned region failed too (all exits down for this host) — forget it
|
|
26
36
|
* so the host can try DIRECT again next time. */
|
|
@@ -25,6 +25,13 @@ export const MISCLASSIFIED_AFTER = 2;
|
|
|
25
25
|
* and is never counted. */
|
|
26
26
|
export const SUSPICIOUS_MS = 3000;
|
|
27
27
|
export const SUSPICIOUS_BYTES = 1024;
|
|
28
|
+
/** Minimum bytes the CLIENT must have sent upstream before a short close counts
|
|
29
|
+
* as a geo-block. A real GFW RST happens AFTER the client's TLS ClientHello
|
|
30
|
+
* (>~100 bytes). A speculative preconnect that Chrome opens and closes WITHOUT
|
|
31
|
+
* sending anything (0 bytes up) is NOT a block — counting it mislearned
|
|
32
|
+
* google/gstatic/googleapis preconnects into the china exit, which then 502'd
|
|
33
|
+
* and starved CCTV. */
|
|
34
|
+
export const MIN_CLIENTHELLO_BYTES = 64;
|
|
28
35
|
/** Learned entries expire after this long (the internet moves; re-verify). */
|
|
29
36
|
const LEARNED_TTL_MS = 30 * 24 * 3600 * 1000;
|
|
30
37
|
/** Cap the failure-counter table so hostile traffic can't grow it unbounded. */
|
|
@@ -68,9 +75,12 @@ export class RouteLearner {
|
|
|
68
75
|
recordDirectGood(host) {
|
|
69
76
|
this.#fails.delete(host);
|
|
70
77
|
}
|
|
71
|
-
/** Classify an established tunnel's ending: was it a suspicious early close
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
/** Classify an established tunnel's ending: was it a suspicious early close
|
|
79
|
+
* (GFW-style RST after a real request)? `bytesUp` is what the CLIENT sent —
|
|
80
|
+
* a connection that sent no ClientHello (a Chrome preconnect) is NOT a
|
|
81
|
+
* geo-block and must never be learned. */
|
|
82
|
+
static suspiciousClose(bytesDown, bytesUp, lifetimeMs) {
|
|
83
|
+
return bytesUp >= MIN_CLIENTHELLO_BYTES && lifetimeMs < SUSPICIOUS_MS && bytesDown < SUSPICIOUS_BYTES;
|
|
74
84
|
}
|
|
75
85
|
learn(host, region) {
|
|
76
86
|
this.#learned.set(host, { region, at: Date.now() });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.182",
|
|
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",
|