@decentnetwork/lan 0.1.8 → 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/daemon/server.js +42 -0
- package/dist/dora/dora-integration.d.ts +9 -0
- package/dist/dora/dora-integration.js +15 -1
- 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/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
|
};
|
|
@@ -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",
|