@decentnetwork/lan 0.1.7 → 0.1.9
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/config/loader.js +7 -5
- package/dist/daemon/server.js +42 -0
- package/dist/dora/dora-integration.d.ts +9 -0
- package/dist/dora/dora-integration.js +25 -9
- package/dist/router/packet-router.d.ts +10 -0
- package/dist/router/packet-router.js +32 -0
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/config/loader.js
CHANGED
|
@@ -108,11 +108,13 @@ export class ConfigLoader {
|
|
|
108
108
|
enabled: false,
|
|
109
109
|
userids: [],
|
|
110
110
|
refreshIntervalMs: 60_000,
|
|
111
|
-
// Default:
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
111
|
+
// Default: auto-friend every peer in the dora roster. Dora
|
|
112
|
+
// membership IS the trust statement — joining a dora means
|
|
113
|
+
// "I want to be on this network", and a single-tenant lab is
|
|
114
|
+
// the common case. Operators on a multi-tenant / public
|
|
115
|
+
// dora opt out with `agentnet dora autofriend none` or
|
|
116
|
+
// whitelist via `agentnet dora autofriend allow <peer>...`.
|
|
117
|
+
autoFriend: "all",
|
|
116
118
|
},
|
|
117
119
|
};
|
|
118
120
|
}
|
package/dist/daemon/server.js
CHANGED
|
@@ -191,6 +191,48 @@ export class DaemonServer {
|
|
|
191
191
|
ipam: this.ipam,
|
|
192
192
|
nodeName: this.config.node.name,
|
|
193
193
|
preferredIp: this.config.network.ip,
|
|
194
|
+
// Fires when dora's background retry eventually succeeds
|
|
195
|
+
// AFTER the initial bootstrap already returned the fallback
|
|
196
|
+
// IP. At that point the TUN is up on the fallback (e.g.
|
|
197
|
+
// 10.86.1.10) but our record in dora's roster says we're
|
|
198
|
+
// at 10.86.1.15 — other peers route to .15, our kernel
|
|
199
|
+
// doesn't recognize it as local, packets drop. Rebuild
|
|
200
|
+
// the TUN at the new IP so traffic actually reaches us.
|
|
201
|
+
onAllocatedIpChanged: async (newIp) => {
|
|
202
|
+
if (!this.tunDevice || !this.routeManager)
|
|
203
|
+
return;
|
|
204
|
+
const currentIp = this.tunDevice.getConfig().ip;
|
|
205
|
+
if (currentIp === newIp)
|
|
206
|
+
return;
|
|
207
|
+
this.logger.info(`Reconfiguring TUN: dora-allocated IP changed ${currentIp} -> ${newIp}`);
|
|
208
|
+
const ifname = this.tunDevice.getInterface();
|
|
209
|
+
try {
|
|
210
|
+
await this.routeManager.cleanup(ifname, this.config.network.subnet, currentIp);
|
|
211
|
+
await this.tunDevice.close();
|
|
212
|
+
this.tunDevice = new TunDevice({
|
|
213
|
+
config: {
|
|
214
|
+
name: this.config.network.interface,
|
|
215
|
+
ip: newIp,
|
|
216
|
+
subnet: this.config.network.subnet,
|
|
217
|
+
},
|
|
218
|
+
mockMode: this.useMockTun,
|
|
219
|
+
});
|
|
220
|
+
await this.tunDevice.open();
|
|
221
|
+
if (!this.useMockTun) {
|
|
222
|
+
await this.routeManager.configureTun({
|
|
223
|
+
...this.tunDevice.getConfig(),
|
|
224
|
+
name: this.tunDevice.getInterface(),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// Re-attach the packet-router's TUN listener — the
|
|
228
|
+
// listener was bound to the old TunDevice instance.
|
|
229
|
+
this.packetRouter?.swapTunDevice(this.tunDevice);
|
|
230
|
+
this.logger.info(`TUN now at ${newIp}`);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
this.logger.error(`Failed to reconfigure TUN to ${newIp}: ${err instanceof Error ? err.message : err}`);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
194
236
|
});
|
|
195
237
|
const doraIp = await this.doraIntegration.bootstrap();
|
|
196
238
|
if (doraIp && doraIp !== tunIp) {
|
|
@@ -23,6 +23,15 @@ export interface DoraIntegrationOptions {
|
|
|
23
23
|
/** Preferred virtual IP from local config — sent as `requestedIp` so a
|
|
24
24
|
* restart keeps the same address when possible. */
|
|
25
25
|
preferredIp?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Fires when dora register completes AFTER the initial bootstrap
|
|
28
|
+
* already returned (i.e. the retry path). At that moment the TUN
|
|
29
|
+
* is already configured at the fallback IP, but our actual record
|
|
30
|
+
* in dora's roster has a different IP. The daemon needs to
|
|
31
|
+
* reconfigure the TUN, or the peer is reachable at neither IP
|
|
32
|
+
* (TUN listens on fallback, peers' IPAM says our allocated IP).
|
|
33
|
+
*/
|
|
34
|
+
onAllocatedIpChanged?: (newIp: string) => Promise<void> | void;
|
|
26
35
|
}
|
|
27
36
|
export declare class DoraIntegration {
|
|
28
37
|
private opts;
|
|
@@ -194,10 +194,24 @@ export class DoraIntegration {
|
|
|
194
194
|
this.logger.debug("Retrying dora bootstrap…");
|
|
195
195
|
// Don't await — let it run async and clear the timer if it
|
|
196
196
|
// succeeds, otherwise leave the timer running.
|
|
197
|
-
void this.bootstrap().then(() => {
|
|
197
|
+
void this.bootstrap().then(async () => {
|
|
198
198
|
if (this.registered && this.retryBootstrapTimer) {
|
|
199
199
|
clearInterval(this.retryBootstrapTimer);
|
|
200
200
|
this.retryBootstrapTimer = undefined;
|
|
201
|
+
// Notify the daemon so it can reconfigure the TUN — the
|
|
202
|
+
// TUN is already up at the fallback IP at this point, but
|
|
203
|
+
// dora gave us a different IP and our record in the roster
|
|
204
|
+
// reflects that. Without rebuild, other peers route to our
|
|
205
|
+
// dora IP and our kernel drops the packet because the TUN
|
|
206
|
+
// listens on the fallback.
|
|
207
|
+
if (this.allocatedIp && this.opts.onAllocatedIpChanged) {
|
|
208
|
+
try {
|
|
209
|
+
await this.opts.onAllocatedIpChanged(this.allocatedIp);
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
this.logger.warn(`onAllocatedIpChanged callback failed: ${err instanceof Error ? err.message : err}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
201
215
|
}
|
|
202
216
|
});
|
|
203
217
|
};
|
|
@@ -318,14 +332,16 @@ export class DoraIntegration {
|
|
|
318
332
|
this.friendRequested.add(entry.userid);
|
|
319
333
|
return;
|
|
320
334
|
}
|
|
321
|
-
// Policy gate.
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
|
|
335
|
+
// Policy gate. The undefined default is "all" because a peer
|
|
336
|
+
// that just joined a dora explicitly said "I want to be on
|
|
337
|
+
// this network" — that's the trust statement. Without "all"
|
|
338
|
+
// as the legacy-config default, new peers can never join an
|
|
339
|
+
// existing mesh: their roster fills up but everyone else's
|
|
340
|
+
// legacy configs (lacking autoFriend) silently refuse to
|
|
341
|
+
// friend them back. Operators on a public / multi-tenant dora
|
|
342
|
+
// can opt out with `agentnet dora autofriend none` or supply
|
|
343
|
+
// a whitelist via `agentnet dora autofriend allow <peer>...`.
|
|
344
|
+
const policy = this.opts.config.autoFriend ?? "all";
|
|
329
345
|
if (!this.policyAllows(entry, policy)) {
|
|
330
346
|
// Mark as "seen" so we don't re-evaluate the policy every
|
|
331
347
|
// 60s for entries we deliberately skip — but DON'T mark as
|
|
@@ -30,6 +30,16 @@ export declare class PacketRouter extends EventEmitter {
|
|
|
30
30
|
constructor(opts: PacketRouterOptions);
|
|
31
31
|
start(): Promise<void>;
|
|
32
32
|
stop(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Swap the active TUN device. Used when dora's background retry
|
|
35
|
+
* succeeds with a different IP than the daemon's initial fallback
|
|
36
|
+
* — the daemon tears down the old TUN, brings up a fresh one at
|
|
37
|
+
* the new IP, and calls this to re-wire the packet read loop +
|
|
38
|
+
* the packet event handler.
|
|
39
|
+
*
|
|
40
|
+
* Caller is responsible for opening the new TUN before calling.
|
|
41
|
+
*/
|
|
42
|
+
swapTunDevice(newDevice: TunDevice): Promise<void>;
|
|
33
43
|
/**
|
|
34
44
|
* Periodically send a 1-byte ping through each connected packet session
|
|
35
45
|
* so the Carrier crypto session doesn't idle out. The receiver's IpParser
|
|
@@ -83,6 +83,38 @@ export class PacketRouter extends EventEmitter {
|
|
|
83
83
|
this.sessionManager.closeAll();
|
|
84
84
|
this.logger.info("Packet router stopped");
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Swap the active TUN device. Used when dora's background retry
|
|
88
|
+
* succeeds with a different IP than the daemon's initial fallback
|
|
89
|
+
* — the daemon tears down the old TUN, brings up a fresh one at
|
|
90
|
+
* the new IP, and calls this to re-wire the packet read loop +
|
|
91
|
+
* the packet event handler.
|
|
92
|
+
*
|
|
93
|
+
* Caller is responsible for opening the new TUN before calling.
|
|
94
|
+
*/
|
|
95
|
+
async swapTunDevice(newDevice) {
|
|
96
|
+
if (this.tunDevice === newDevice)
|
|
97
|
+
return;
|
|
98
|
+
this.logger.info("Swapping TUN device");
|
|
99
|
+
// Old device is already torn down by the caller (or about to be).
|
|
100
|
+
this.tunDevice = newDevice;
|
|
101
|
+
if (!this.isRunning)
|
|
102
|
+
return;
|
|
103
|
+
this.tunDevice.on("packet", (packet) => {
|
|
104
|
+
this.handleOutgoingPacket(packet).catch((err) => {
|
|
105
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
106
|
+
if (msg.includes("offline") ||
|
|
107
|
+
msg.includes("no transport") ||
|
|
108
|
+
msg.includes("Handshake timeout")) {
|
|
109
|
+
this.logger.debug(`Outgoing packet dropped: ${msg}`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.logger.error("Outgoing packet error:", err);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
await this.tunDevice.startReadLoop();
|
|
117
|
+
}
|
|
86
118
|
/**
|
|
87
119
|
* Periodically send a 1-byte ping through each connected packet session
|
|
88
120
|
* so the Carrier crypto session doesn't idle out. The receiver's IpParser
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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",
|