@decentnetwork/lan 0.1.71 → 0.1.73

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.
@@ -0,0 +1,55 @@
1
+ # Official exit / infrastructure nodes shipped with @decentnetwork/lan.
2
+ #
3
+ # A fresh install auto-friends ONLY the dora registries (config/default-doras.yaml)
4
+ # and the exits listed here — NOT every machine that happens to be in the dora
5
+ # roster. This keeps a new client's friend list to infrastructure (doras + exits)
6
+ # instead of filling up with other people's personal/compute boxes.
7
+ #
8
+ # Each exit carries a `region`, so `agentnet proxy router` (with no routes.yaml)
9
+ # auto-builds the routing table: China sites → china exits, Japan/Binance →
10
+ # japan exit, Google/YouTube/GitHub/etc → us exits, everything else direct.
11
+ #
12
+ # Mechanism: these userids (plus the dora userids) become the default
13
+ # `dora.autoFriend` whitelist for a freshly-initialised config. The client
14
+ # still LEARNS every roster entry into IPAM (so names/IPs resolve), but only
15
+ # proactively friends infrastructure. Exits still serve arbitrary clients
16
+ # because the daemon auto-ACCEPTS incoming friend-requests — so hub-and-spoke
17
+ # works without meshing every personal machine.
18
+ #
19
+ # To run a full mesh instead (friend everyone in the roster):
20
+ # agentnet dora autofriend all
21
+ # To add/replace an exit: edit this file (data, not code) and republish, or
22
+ # locally: agentnet dora autofriend allow <name|userid> ...
23
+ #
24
+ # `region` is one of: china | japan | us (matches the router's built-in
25
+ # domain lists; see BUILTIN_REGION_DOMAINS in src/proxy/multi-exit-router.ts).
26
+ exits:
27
+ # --- China (China-only / GFW-blocked-from-outside sites) ---
28
+ - name: cn
29
+ userid: 5Aj6uQMd1cNRb9cGT4AwHCN4RdVZjfaGLtgKGhdP1LzN
30
+ virtual_ip: 10.86.1.15
31
+ region: china
32
+ - name: sh
33
+ userid: 6D1PLSVqbSpcxnDMAd8ZXLWycYveeP2hU4g3igDQEqA7
34
+ virtual_ip: 10.86.1.16
35
+ region: china
36
+ - name: callpass
37
+ userid: DZN2L9RV1YkHjqGHMTA6juZhskKA65AD2RyPt4umZVc7
38
+ virtual_ip: 10.86.1.17
39
+ region: china
40
+ # --- Japan (host "lico", dora name node-91) — Binance routes here ---
41
+ - name: tokyo
42
+ userid: 4uNnLnAQHVJ7td657MDaz4UnEuhnTW9sRAPSVeAEEQe4
43
+ virtual_ip: 10.86.68.90
44
+ region: japan
45
+ # --- US (Google, YouTube, GitHub, X/Twitter, LinkedIn, OpenAI) ---
46
+ - name: gojipower
47
+ userid: 2wErj1XreXt1UchE3FGhuvkZ4GoBpo8JGMn8X49nm2ec
48
+ virtual_ip: 10.86.166.16
49
+ region: us
50
+ # snoopy has NO public IP (LAN 10.0.0.115) — proves a NAT'd node can serve
51
+ # as an exit purely over the Carrier tunnel.
52
+ - name: snoopy
53
+ userid: BeMbuKf1poh3U4rjw3eeUAKZsbN2qBSjVZdPSvehmHsw
54
+ virtual_ip: 10.86.156.164
55
+ region: us
@@ -4,7 +4,7 @@
4
4
  import { resolve, dirname } from "path";
5
5
  import { existsSync, mkdirSync, readFileSync } from "fs";
6
6
  import { createConnection } from "net";
7
- import { ConfigLoader, DEFAULT_DORAS } from "../config/loader.js";
7
+ import { ConfigLoader, DEFAULT_DORAS, DEFAULT_EXITS } from "../config/loader.js";
8
8
  import { ipcSocketPath } from "../daemon/ipc.js";
9
9
  import { DaemonServer } from "../daemon/server.js";
10
10
  import { Ipam } from "../ipam/ipam.js";
@@ -1104,24 +1104,63 @@ export async function cmdProxyRouter(args) {
1104
1104
  defaultRegion = routesFile.default ?? "direct";
1105
1105
  console.log(`Loaded ${regions.length} region(s) from ${routesPath}`);
1106
1106
  }
1107
- else {
1108
- // ---- Legacy auto-discovery: all IPAM peers as China exits -------------
1109
- const discovered = peers
1110
- .filter((p) => p.virtualIp)
1111
- .map((p) => resolveExit(p.name || p.virtualIp, args.all ? "all" : "china"))
1112
- .filter(Boolean);
1113
- if (discovered.length === 0) {
1114
- console.error(`No exits given and none discovered from IPAM (${source}).`);
1115
- console.error(`Pass exits explicitly, e.g.:`);
1116
- console.error(` agentnet proxy router --exit cn --exit node-5123`);
1117
- console.error(`Or define regions in ${routesPath} (see docs/INSTALL-NODES.md).`);
1107
+ else if (args.all) {
1108
+ // ---- --all: every discovered peer through exits, single region --------
1109
+ const discovered = peers.filter((p) => p.virtualIp);
1110
+ for (const p of discovered) {
1111
+ exits.push({ name: p.name || p.virtualIp, host: p.virtualIp, port: defaultExitPort, region: "all" });
1112
+ }
1113
+ if (exits.length === 0) {
1114
+ console.error(`No exits discovered from IPAM (${source}). Is the daemon up?`);
1118
1115
  process.exit(1);
1119
1116
  }
1120
- exits.push(...discovered);
1121
- regions.push({ name: args.all ? "all" : "china", domains: [] });
1122
- defaultRegion = args.all ? "all" : "direct";
1123
- console.log(`Auto-discovered ${discovered.length} exit(s) from IPAM (${source}): ${discovered.map((e) => e.name).join(", ")}`);
1124
- console.log(`(health checks will drop any that aren't actually running a proxy)\n`);
1117
+ regions.push({ name: "all", domains: [] });
1118
+ defaultRegion = "all";
1119
+ console.log(`Auto-discovered ${exits.length} exit(s) from IPAM (${source}); routing ALL hosts through them.\n`);
1120
+ }
1121
+ else {
1122
+ // ---- Auto-discovery: curated exits, each in its OWN region -----------
1123
+ // Match live IPAM peers against the shipped exit list (by userid, then
1124
+ // name) and assign each its region (china/japan/us). Non-exit peers
1125
+ // (personal machines) are skipped, so the router never sends traffic
1126
+ // through someone's laptop.
1127
+ const exitByUserid = new Map(DEFAULT_EXITS.map((e) => [e.userid, e]));
1128
+ const exitByName = new Map(DEFAULT_EXITS.map((e) => [e.name, e]));
1129
+ const regionsSeen = new Set();
1130
+ for (const p of peers) {
1131
+ if (!p.virtualIp)
1132
+ continue;
1133
+ const curated = exitByUserid.get(p.carrierId) ?? exitByName.get(p.name);
1134
+ if (!curated)
1135
+ continue;
1136
+ const region = curated.region ?? "china";
1137
+ exits.push({ name: curated.name, host: p.virtualIp, port: defaultExitPort, region });
1138
+ regionsSeen.add(region);
1139
+ }
1140
+ if (exits.length > 0) {
1141
+ for (const r of regionsSeen)
1142
+ regions.push({ name: r, domains: [] });
1143
+ console.log(`Auto-discovered ${exits.length} curated exit(s) from IPAM (${source}): ` +
1144
+ [...regionsSeen].map((r) => `${r}=[${exits.filter((e) => e.region === r).map((e) => e.name).join(",")}]`).join(" "));
1145
+ console.log(`(China→china exits, Japan/Binance→japan, Google/YouTube/etc→us, rest→direct)\n`);
1146
+ }
1147
+ else {
1148
+ // No curated exits in this roster (a different/private network) — fall
1149
+ // back to treating every discovered peer as a China exit.
1150
+ const discovered = peers
1151
+ .filter((p) => p.virtualIp)
1152
+ .map((p) => ({ name: p.name || p.virtualIp, host: p.virtualIp, port: defaultExitPort, region: "china" }));
1153
+ if (discovered.length === 0) {
1154
+ console.error(`No exits given and none discovered from IPAM (${source}).`);
1155
+ console.error(`Pass exits explicitly, e.g.: agentnet proxy router --exit cn --exit sh`);
1156
+ console.error(`Or define regions in ${routesPath} (see docs/INSTALL-NODES.md).`);
1157
+ process.exit(1);
1158
+ }
1159
+ exits.push(...discovered);
1160
+ regions.push({ name: "china", domains: [] });
1161
+ console.log(`Auto-discovered ${discovered.length} exit(s) from IPAM (${source}); none curated, routing China-split.\n`);
1162
+ }
1163
+ defaultRegion = "direct";
1125
1164
  }
1126
1165
  startMultiExitRouter({
1127
1166
  exits,
@@ -26,6 +26,28 @@ export interface DefaultDora {
26
26
  export declare const DEFAULT_DORAS: DefaultDora[];
27
27
  export declare const DEFAULT_DORA_USERID: string;
28
28
  export declare const DEFAULT_DORA_ADDRESS: string;
29
+ /**
30
+ * Official exit / infrastructure nodes (config/default-exits.yaml). These,
31
+ * together with the dora userids, form the default `autoFriend` whitelist so
32
+ * a fresh install only friends infrastructure — not every personal/compute
33
+ * box that shows up in the dora roster. Editable data, not code.
34
+ */
35
+ export interface DefaultExit {
36
+ name: string;
37
+ userid: string;
38
+ virtual_ip?: string;
39
+ /** Routing region: china | japan | us. Used by `agentnet proxy router` to
40
+ * auto-build the domain→region table with zero config. */
41
+ region?: string;
42
+ }
43
+ export declare const DEFAULT_EXITS: DefaultExit[];
44
+ /**
45
+ * The default `dora.autoFriend` whitelist for a fresh config: every dora plus
46
+ * every official exit, by userid. A new client friends only these (the dora
47
+ * roster is still fully learned into IPAM for name/IP resolution). Override
48
+ * with `agentnet dora autofriend all` for a full mesh.
49
+ */
50
+ export declare const DEFAULT_AUTOFRIEND: string[];
29
51
  export declare class ConfigLoader {
30
52
  static defaultConfigPath(): string;
31
53
  static defaultConfigDir(): string;
@@ -72,6 +72,27 @@ export const DEFAULT_DORAS = loadDefaultDoras();
72
72
  // Back-compat single-value exports (first/primary dora).
73
73
  export const DEFAULT_DORA_USERID = DEFAULT_DORAS[0].userid;
74
74
  export const DEFAULT_DORA_ADDRESS = DEFAULT_DORAS[0].address;
75
+ function loadDefaultExits() {
76
+ try {
77
+ const file = resolve(dirname(new URL(import.meta.url).pathname), "../../config/default-exits.yaml");
78
+ const parsed = yaml.load(readFileSync(file, "utf-8"));
79
+ return (parsed?.exits ?? []).filter((e) => e && e.userid);
80
+ }
81
+ catch {
82
+ return [];
83
+ }
84
+ }
85
+ export const DEFAULT_EXITS = loadDefaultExits();
86
+ /**
87
+ * The default `dora.autoFriend` whitelist for a fresh config: every dora plus
88
+ * every official exit, by userid. A new client friends only these (the dora
89
+ * roster is still fully learned into IPAM for name/IP resolution). Override
90
+ * with `agentnet dora autofriend all` for a full mesh.
91
+ */
92
+ export const DEFAULT_AUTOFRIEND = [
93
+ ...DEFAULT_DORAS.map((d) => d.userid),
94
+ ...DEFAULT_EXITS.map((e) => e.userid),
95
+ ];
75
96
  export class ConfigLoader {
76
97
  static defaultConfigPath() {
77
98
  return resolve(this.defaultConfigDir(), "config.yaml");
@@ -174,13 +195,15 @@ export class ConfigLoader {
174
195
  enabled: true,
175
196
  userids: DEFAULT_DORAS.map((d) => d.userid),
176
197
  refreshIntervalMs: 60_000,
177
- // Default: auto-friend every peer in the dora roster. Dora
178
- // membership IS the trust statementjoining a dora means
179
- // "I want to be on this network", and a single-tenant lab is
180
- // the common case. Operators on a multi-tenant / public
181
- // dora opt out with `agentnet dora autofriend none` or
182
- // whitelist via `agentnet dora autofriend allow <peer>...`.
183
- autoFriend: "all",
198
+ // Default: auto-friend ONLY infrastructure the dora registries and
199
+ // the official exit nodes (config/default-exits.yaml)not every
200
+ // personal/compute box in the roster. Hub-and-spoke: a client connects
201
+ // to doras + exits; exits still serve arbitrary clients because the
202
+ // daemon auto-ACCEPTS incoming friend-requests. The full roster is
203
+ // still learned into IPAM for name/IP resolution. Run a full mesh with
204
+ // `agentnet dora autofriend all`, lock down with `... none`, or curate
205
+ // with `agentnet dora autofriend allow <peer>...`.
206
+ autoFriend: DEFAULT_AUTOFRIEND,
184
207
  },
185
208
  };
186
209
  }
@@ -33,15 +33,33 @@ export const BUILTIN_REGION_DOMAINS = {
33
33
  ".volcfcdn.com", ".volccdn.com", ".byteimg.com", ".bytedance.com",
34
34
  ".weibo.com", ".weibocdn.com", ".sinaimg.cn", ".youku.cn",
35
35
  ],
36
- // Japan-region sites / services that geo-fence to a JP IP.
36
+ // Japan-region sites / services that geo-fence to a JP IP, plus Binance
37
+ // (routed through the Japan exit — it's blocked/geo-restricted on many
38
+ // other paths). .bnbstatic.com is Binance's asset CDN; without it the
39
+ // site loads blank.
37
40
  japan: [
38
41
  ".jp",
39
42
  ".nicovideo.jp", ".nimg.jp", ".dmm.com", ".dmm.co.jp",
40
43
  ".abema.tv", ".abema.io", ".tver.jp", ".unext.jp",
41
44
  ".radiko.jp", ".pixiv.net", ".pximg.net",
45
+ ".binance.com", ".binance.org", ".bnbstatic.com", ".binancecnt.com",
42
46
  ],
43
- // US-region streaming / services that geo-fence to a US IP.
47
+ // US region: Western sites commonly blocked/throttled from China (route via
48
+ // a US exit), plus US-geo-fenced streaming.
44
49
  us: [
50
+ // Google / YouTube
51
+ ".google.com", ".google.com.hk", ".gstatic.com", ".googleapis.com",
52
+ ".googleusercontent.com", ".googlevideo.com", ".ggpht.com", ".withgoogle.com", ".goo.gl",
53
+ ".youtube.com", ".youtu.be", ".ytimg.com",
54
+ // GitHub
55
+ ".github.com", ".github.io", ".githubusercontent.com", ".githubassets.com", ".ghcr.io",
56
+ // X / Twitter
57
+ ".twitter.com", ".x.com", ".twimg.com", ".t.co",
58
+ // LinkedIn
59
+ ".linkedin.com", ".licdn.com",
60
+ // OpenAI / ChatGPT
61
+ ".openai.com", ".chatgpt.com", ".oaistatic.com", ".oaiusercontent.com",
62
+ // US-geo-fenced streaming
45
63
  ".hulu.com", ".huluim.com", ".hulustream.com",
46
64
  ".peacocktv.com", ".max.com", ".hbomax.com",
47
65
  ".pluto.tv", ".tubi.tv", ".tubitv.com",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.71",
3
+ "version": "0.1.73",
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",
@@ -19,6 +19,7 @@
19
19
  "dist",
20
20
  "bin",
21
21
  "config/default-doras.yaml",
22
+ "config/default-exits.yaml",
22
23
  "config/routes.example.yaml",
23
24
  "README.md",
24
25
  "LICENSE",