@aria-cli/wireguard 1.0.37 → 1.0.38
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/index.d.ts +59 -0
- package/index.js +52 -52
- package/npm/darwin-arm64/package.json +18 -0
- package/npm/darwin-arm64/wireguard.darwin-arm64.node +0 -0
- package/npm/darwin-x64/package.json +18 -0
- package/npm/darwin-x64/wireguard.darwin-x64.node +0 -0
- package/npm/linux-arm64-gnu/package.json +18 -0
- package/npm/linux-arm64-gnu/wireguard.linux-arm64-gnu.node +0 -0
- package/npm/linux-x64-gnu/package.json +18 -0
- package/npm/linux-x64-gnu/wireguard.linux-x64-gnu.node +0 -0
- package/npm/win32-x64-msvc/package.json +18 -0
- package/npm/win32-x64-msvc/wireguard.win32-x64-msvc.node +0 -0
- package/package.json +9 -11
- package/wireguard.darwin-arm64.node +0 -0
- package/wireguard.darwin-x64.node +0 -0
- package/wireguard.linux-arm64-gnu.node +0 -0
- package/wireguard.linux-x64-gnu.node +0 -0
- package/wireguard.win32-x64-msvc.node +0 -0
- package/dist/.aria-build-stamp.json +0 -4
- package/dist/bootstrap-authority.js +0 -47
- package/dist/bootstrap-tls.js +0 -69
- package/dist/db-owner-fencing.js +0 -44
- package/dist/derp-relay.js +0 -311
- package/dist/index.js +0 -100
- package/dist/nat.js +0 -397
- package/dist/network-state-store.js +0 -248
- package/dist/network.js +0 -3391
- package/dist/peer-discovery.js +0 -486
- package/dist/resilient-tunnel.js +0 -389
- package/dist/route-ownership.js +0 -79
- package/dist/tunnel.js +0 -474
package/dist/nat.js
DELETED
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* STUN client + NAT traversal for ARIA secure networking.
|
|
4
|
-
*
|
|
5
|
-
* Discovers external IP:port via STUN (RFC 5389), enabling WireGuard
|
|
6
|
-
* peers to find each other through NAT. Falls back to a UDP relay
|
|
7
|
-
* when symmetric NAT prevents direct connectivity.
|
|
8
|
-
*
|
|
9
|
-
* No external dependencies — implements STUN binding request/response
|
|
10
|
-
* directly using node:dgram.
|
|
11
|
-
*/
|
|
12
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
-
if (k2 === undefined) k2 = k;
|
|
14
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
-
}
|
|
18
|
-
Object.defineProperty(o, k2, desc);
|
|
19
|
-
}) : (function(o, m, k, k2) {
|
|
20
|
-
if (k2 === undefined) k2 = k;
|
|
21
|
-
o[k2] = m[k];
|
|
22
|
-
}));
|
|
23
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
-
}) : function(o, v) {
|
|
26
|
-
o["default"] = v;
|
|
27
|
-
});
|
|
28
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
-
var ownKeys = function(o) {
|
|
30
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
-
var ar = [];
|
|
32
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
-
return ar;
|
|
34
|
-
};
|
|
35
|
-
return ownKeys(o);
|
|
36
|
-
};
|
|
37
|
-
return function (mod) {
|
|
38
|
-
if (mod && mod.__esModule) return mod;
|
|
39
|
-
var result = {};
|
|
40
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
-
__setModuleDefault(result, mod);
|
|
42
|
-
return result;
|
|
43
|
-
};
|
|
44
|
-
})();
|
|
45
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
-
exports.StunClient = void 0;
|
|
47
|
-
exports.discoverEndpoint = discoverEndpoint;
|
|
48
|
-
exports.detectNatType = detectNatType;
|
|
49
|
-
const dgram = __importStar(require("node:dgram"));
|
|
50
|
-
const crypto = __importStar(require("node:crypto"));
|
|
51
|
-
const os = __importStar(require("node:os"));
|
|
52
|
-
// STUN message types (RFC 5389)
|
|
53
|
-
const STUN_BINDING_REQUEST = 0x0001;
|
|
54
|
-
const STUN_BINDING_RESPONSE = 0x0101;
|
|
55
|
-
const STUN_MAGIC_COOKIE = 0x2112a442;
|
|
56
|
-
const STUN_ATTR_XOR_MAPPED_ADDRESS = 0x0020;
|
|
57
|
-
const STUN_ATTR_MAPPED_ADDRESS = 0x0001;
|
|
58
|
-
/**
|
|
59
|
-
* Detect whether the route to a STUN server exits through a tunnel/VPN
|
|
60
|
-
* interface. If so, STUN results reflect the tunnel exit IP — not the
|
|
61
|
-
* machine's real public IP — and should be discarded.
|
|
62
|
-
*
|
|
63
|
-
* Detection: uses a connected UDP socket to discover the OS-selected source
|
|
64
|
-
* address, then checks if that address belongs to an interface with a
|
|
65
|
-
* zero MAC address (00:00:00:00:00:00). Tunnel/VPN interfaces (WireGuard,
|
|
66
|
-
* OpenVPN, Tailscale, IPSec, ZeroTier, etc.) have no hardware address.
|
|
67
|
-
* Physical interfaces (Ethernet, WiFi) always have a real MAC.
|
|
68
|
-
*/
|
|
69
|
-
function routeExitsThroughTunnel(host, port) {
|
|
70
|
-
return new Promise((resolve) => {
|
|
71
|
-
const probe = dgram.createSocket("udp4");
|
|
72
|
-
const timer = setTimeout(() => {
|
|
73
|
-
probe.close();
|
|
74
|
-
resolve(false);
|
|
75
|
-
}, 2000);
|
|
76
|
-
probe.connect(port, host, () => {
|
|
77
|
-
clearTimeout(timer);
|
|
78
|
-
try {
|
|
79
|
-
const sourceAddr = probe.address().address;
|
|
80
|
-
const ifaces = os.networkInterfaces();
|
|
81
|
-
for (const addrs of Object.values(ifaces)) {
|
|
82
|
-
for (const a of addrs ?? []) {
|
|
83
|
-
if (a.address === sourceAddr && a.mac === "00:00:00:00:00:00") {
|
|
84
|
-
probe.close();
|
|
85
|
-
resolve(true);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
// ignore
|
|
93
|
-
}
|
|
94
|
-
probe.close();
|
|
95
|
-
resolve(false);
|
|
96
|
-
});
|
|
97
|
-
probe.on("error", () => {
|
|
98
|
-
clearTimeout(timer);
|
|
99
|
-
probe.close();
|
|
100
|
-
resolve(false);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
// Well-known public STUN servers (no authentication required)
|
|
105
|
-
const DEFAULT_STUN_SERVERS = [
|
|
106
|
-
"stun.l.google.com:19302",
|
|
107
|
-
"stun1.l.google.com:19302",
|
|
108
|
-
"stun.cloudflare.com:3478",
|
|
109
|
-
];
|
|
110
|
-
/**
|
|
111
|
-
* Build a STUN Binding Request message (RFC 5389).
|
|
112
|
-
* Header: type (2) + length (2) + magic cookie (4) + transaction ID (12) = 20 bytes.
|
|
113
|
-
*/
|
|
114
|
-
function buildBindingRequest() {
|
|
115
|
-
const msg = Buffer.alloc(20);
|
|
116
|
-
msg.writeUInt16BE(STUN_BINDING_REQUEST, 0); // Message type
|
|
117
|
-
msg.writeUInt16BE(0, 2); // Message length (no attributes)
|
|
118
|
-
msg.writeUInt32BE(STUN_MAGIC_COOKIE, 4); // Magic cookie
|
|
119
|
-
const txId = crypto.randomBytes(12);
|
|
120
|
-
txId.copy(msg, 8); // Transaction ID
|
|
121
|
-
return { message: msg, transactionId: txId };
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Parse a STUN Binding Response to extract the mapped address.
|
|
125
|
-
* Handles both XOR-MAPPED-ADDRESS (preferred) and MAPPED-ADDRESS (fallback).
|
|
126
|
-
*/
|
|
127
|
-
function parseBindingResponse(msg, expectedTxId) {
|
|
128
|
-
if (msg.length < 20)
|
|
129
|
-
return null;
|
|
130
|
-
const msgType = msg.readUInt16BE(0);
|
|
131
|
-
if (msgType !== STUN_BINDING_RESPONSE)
|
|
132
|
-
return null;
|
|
133
|
-
// Verify magic cookie
|
|
134
|
-
if (msg.readUInt32BE(4) !== STUN_MAGIC_COOKIE)
|
|
135
|
-
return null;
|
|
136
|
-
// Verify transaction ID
|
|
137
|
-
if (!msg.subarray(8, 20).equals(expectedTxId))
|
|
138
|
-
return null;
|
|
139
|
-
const attrLength = msg.readUInt16BE(2);
|
|
140
|
-
let offset = 20;
|
|
141
|
-
const end = 20 + attrLength;
|
|
142
|
-
let mappedAddress = null;
|
|
143
|
-
while (offset + 4 <= end) {
|
|
144
|
-
const attrType = msg.readUInt16BE(offset);
|
|
145
|
-
const attrLen = msg.readUInt16BE(offset + 2);
|
|
146
|
-
const attrStart = offset + 4;
|
|
147
|
-
if (attrType === STUN_ATTR_XOR_MAPPED_ADDRESS && attrLen >= 8) {
|
|
148
|
-
const family = msg.readUInt8(attrStart + 1);
|
|
149
|
-
if (family === 0x01) {
|
|
150
|
-
// IPv4
|
|
151
|
-
const xPort = msg.readUInt16BE(attrStart + 2) ^ (STUN_MAGIC_COOKIE >> 16);
|
|
152
|
-
const xAddr = msg.readUInt32BE(attrStart + 4) ^ STUN_MAGIC_COOKIE;
|
|
153
|
-
const a = (xAddr >> 24) & 0xff;
|
|
154
|
-
const b = (xAddr >> 16) & 0xff;
|
|
155
|
-
const c = (xAddr >> 8) & 0xff;
|
|
156
|
-
const d = xAddr & 0xff;
|
|
157
|
-
return { address: `${a}.${b}.${c}.${d}`, port: xPort };
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (attrType === STUN_ATTR_MAPPED_ADDRESS && attrLen >= 8) {
|
|
161
|
-
const family = msg.readUInt8(attrStart + 1);
|
|
162
|
-
if (family === 0x01) {
|
|
163
|
-
// IPv4 (not XORed)
|
|
164
|
-
const port = msg.readUInt16BE(attrStart + 2);
|
|
165
|
-
const addr = msg.readUInt32BE(attrStart + 4);
|
|
166
|
-
const a = (addr >> 24) & 0xff;
|
|
167
|
-
const b = (addr >> 16) & 0xff;
|
|
168
|
-
const c = (addr >> 8) & 0xff;
|
|
169
|
-
const d = addr & 0xff;
|
|
170
|
-
mappedAddress = { address: `${a}.${b}.${c}.${d}`, port };
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Attribute value is padded to 4-byte boundary
|
|
174
|
-
offset = attrStart + Math.ceil(attrLen / 4) * 4;
|
|
175
|
-
}
|
|
176
|
-
return mappedAddress;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Discover our external endpoint by sending a STUN Binding Request.
|
|
180
|
-
*
|
|
181
|
-
* Tries multiple STUN servers in parallel, returns the first successful response.
|
|
182
|
-
* Timeout: 3 seconds per server, 5 second total.
|
|
183
|
-
*/
|
|
184
|
-
/**
|
|
185
|
-
* Discover external endpoint via STUN.
|
|
186
|
-
*
|
|
187
|
-
* @param server STUN server hostname:port (or undefined to try defaults)
|
|
188
|
-
* @param timeoutMs Timeout in milliseconds
|
|
189
|
-
* @param existingSocket Optional: use this socket for STUN instead of creating
|
|
190
|
-
* ephemeral ones. This is critical for the shared-socket WG model — STUN must
|
|
191
|
-
* discover the NAT mapping of the ACTUAL listening socket, not a throwaway one.
|
|
192
|
-
* When provided, the socket is NOT closed after discovery.
|
|
193
|
-
*/
|
|
194
|
-
async function discoverEndpoint(server, timeoutMs = 5000, existingSocket) {
|
|
195
|
-
const servers = server ? [server] : DEFAULT_STUN_SERVERS;
|
|
196
|
-
// Pre-check: if the route to the STUN server exits through a tunnel
|
|
197
|
-
// interface (VPN), the result will be the VPN exit IP — not our real
|
|
198
|
-
// public IP. Reject early to prevent advertising a wrong endpoint.
|
|
199
|
-
const [firstHost, firstPortStr] = servers[0].split(":");
|
|
200
|
-
const isTunnel = await routeExitsThroughTunnel(firstHost, parseInt(firstPortStr ?? "3478", 10));
|
|
201
|
-
if (isTunnel) {
|
|
202
|
-
throw new Error("STUN route exits through a tunnel interface — result would be unreliable");
|
|
203
|
-
}
|
|
204
|
-
return new Promise((resolve, reject) => {
|
|
205
|
-
let resolved = false;
|
|
206
|
-
const ownedSockets = []; // sockets WE created (will be closed)
|
|
207
|
-
const timer = setTimeout(() => {
|
|
208
|
-
if (!resolved) {
|
|
209
|
-
resolved = true;
|
|
210
|
-
ownedSockets.forEach((s) => {
|
|
211
|
-
try {
|
|
212
|
-
s.close();
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
// ignore
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
reject(new Error(`STUN discovery timed out after ${timeoutMs}ms`));
|
|
219
|
-
}
|
|
220
|
-
}, timeoutMs);
|
|
221
|
-
for (const srv of servers) {
|
|
222
|
-
const [host, portStr] = srv.split(":");
|
|
223
|
-
const port = parseInt(portStr ?? "3478", 10);
|
|
224
|
-
// Use existing socket if provided (shared socket model), else create ephemeral
|
|
225
|
-
const socket = existingSocket ?? dgram.createSocket("udp4");
|
|
226
|
-
if (!existingSocket)
|
|
227
|
-
ownedSockets.push(socket);
|
|
228
|
-
const { message, transactionId } = buildBindingRequest();
|
|
229
|
-
const handler = (msg) => {
|
|
230
|
-
if (resolved)
|
|
231
|
-
return;
|
|
232
|
-
const result = parseBindingResponse(msg, transactionId);
|
|
233
|
-
if (result) {
|
|
234
|
-
resolved = true;
|
|
235
|
-
clearTimeout(timer);
|
|
236
|
-
// Remove our handler from the existing socket (don't close it)
|
|
237
|
-
if (existingSocket) {
|
|
238
|
-
existingSocket.off("message", handler);
|
|
239
|
-
}
|
|
240
|
-
ownedSockets.forEach((s) => {
|
|
241
|
-
try {
|
|
242
|
-
s.close();
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
// ignore
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
resolve({ ...result, server: srv });
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
socket.on("message", handler);
|
|
252
|
-
socket.on("error", () => {
|
|
253
|
-
// Individual socket errors are non-fatal — other servers may succeed
|
|
254
|
-
if (!existingSocket) {
|
|
255
|
-
try {
|
|
256
|
-
socket.close();
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
/* already closed */
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
socket.send(message, port, host);
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Detect NAT type by querying two STUN servers and comparing mapped ports.
|
|
269
|
-
*
|
|
270
|
-
* Symmetric NAT allocates a new external port for each destination,
|
|
271
|
-
* so mapped ports will differ across STUN servers. Cone NATs reuse
|
|
272
|
-
* the same external port, so mapped ports will match.
|
|
273
|
-
*
|
|
274
|
-
* Returns "unknown" if fewer than 2 STUN servers respond.
|
|
275
|
-
*/
|
|
276
|
-
async function detectNatType(servers = DEFAULT_STUN_SERVERS, timeoutMs = 5000) {
|
|
277
|
-
const results = [];
|
|
278
|
-
// Query each server individually (sequential to avoid port reuse confusion)
|
|
279
|
-
for (const server of servers.slice(0, 3)) {
|
|
280
|
-
try {
|
|
281
|
-
const result = await discoverEndpoint(server, timeoutMs);
|
|
282
|
-
results.push(result);
|
|
283
|
-
if (results.length >= 2)
|
|
284
|
-
break; // Only need 2 for comparison
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
// Server unreachable — try next
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (results.length < 2) {
|
|
291
|
-
return { natType: "unknown", results };
|
|
292
|
-
}
|
|
293
|
-
// Compare mapped ports: if different → symmetric NAT
|
|
294
|
-
const port1 = results[0].port;
|
|
295
|
-
const port2 = results[1].port;
|
|
296
|
-
if (port1 !== port2) {
|
|
297
|
-
return { natType: "symmetric", results };
|
|
298
|
-
}
|
|
299
|
-
// Same port → cone or restricted (both allow direct connections)
|
|
300
|
-
// We can't distinguish full cone from restricted cone with STUN alone
|
|
301
|
-
// (would need a STUN server that sends from a different IP), but both work for us.
|
|
302
|
-
return { natType: "full_cone", results };
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* STUN client for periodic endpoint discovery.
|
|
306
|
-
*
|
|
307
|
-
* Use for ongoing NAT traversal — discovers and tracks endpoint changes.
|
|
308
|
-
*/
|
|
309
|
-
class StunClient {
|
|
310
|
-
servers;
|
|
311
|
-
pollIntervalMs;
|
|
312
|
-
interval = null;
|
|
313
|
-
lastResult = null;
|
|
314
|
-
_natType = null;
|
|
315
|
-
/** Number of consecutive discovery failures (reset to 0 on success) */
|
|
316
|
-
consecutiveFailures = 0;
|
|
317
|
-
constructor(servers = DEFAULT_STUN_SERVERS, pollIntervalMs = 60_000) {
|
|
318
|
-
this.servers = servers;
|
|
319
|
-
this.pollIntervalMs = pollIntervalMs;
|
|
320
|
-
}
|
|
321
|
-
/** Get the detected NAT type (null if not yet detected) */
|
|
322
|
-
getNatType() {
|
|
323
|
-
return this._natType;
|
|
324
|
-
}
|
|
325
|
-
/** Get the last discovered endpoint */
|
|
326
|
-
getEndpoint() {
|
|
327
|
-
return this.lastResult;
|
|
328
|
-
}
|
|
329
|
-
/** Discover endpoint once — uses first configured server (or defaults) */
|
|
330
|
-
async discover() {
|
|
331
|
-
const result = await discoverEndpoint(this.servers[0], 5000);
|
|
332
|
-
this.lastResult = result;
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
/** Start periodic endpoint discovery. Detects NAT type on first call. */
|
|
336
|
-
start(onUpdate) {
|
|
337
|
-
if (this.interval)
|
|
338
|
-
return;
|
|
339
|
-
// Check once if we're behind a VPN. If so, STUN will always return
|
|
340
|
-
// the wrong IP — skip the entire poll loop instead of spamming errors.
|
|
341
|
-
void (async () => {
|
|
342
|
-
const [firstHost, firstPortStr] = this.servers[0].split(":");
|
|
343
|
-
const isTunnel = await routeExitsThroughTunnel(firstHost, parseInt(firstPortStr ?? "3478", 10));
|
|
344
|
-
if (isTunnel) {
|
|
345
|
-
if (typeof process !== "undefined" && process.stderr) {
|
|
346
|
-
process.stderr.write("[wireguard] STUN skipped — route exits through VPN/tunnel interface\n");
|
|
347
|
-
}
|
|
348
|
-
return; // Don't start the poll loop
|
|
349
|
-
}
|
|
350
|
-
// Initial discovery + NAT type detection
|
|
351
|
-
try {
|
|
352
|
-
if (!this._natType) {
|
|
353
|
-
const detection = await detectNatType(this.servers);
|
|
354
|
-
this._natType = detection.natType;
|
|
355
|
-
if (detection.results.length > 0) {
|
|
356
|
-
this.lastResult = detection.results[0];
|
|
357
|
-
this.consecutiveFailures = 0;
|
|
358
|
-
onUpdate?.(detection.results[0]);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (!this.lastResult) {
|
|
362
|
-
const r = await this.discover();
|
|
363
|
-
this.consecutiveFailures = 0;
|
|
364
|
-
onUpdate?.(r);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
catch {
|
|
368
|
-
this.consecutiveFailures++;
|
|
369
|
-
}
|
|
370
|
-
// Start periodic refresh only after confirming STUN works
|
|
371
|
-
this.interval = setInterval(async () => {
|
|
372
|
-
try {
|
|
373
|
-
const result = await this.discover();
|
|
374
|
-
this.consecutiveFailures = 0;
|
|
375
|
-
onUpdate?.(result);
|
|
376
|
-
}
|
|
377
|
-
catch {
|
|
378
|
-
this.consecutiveFailures++;
|
|
379
|
-
if (this.consecutiveFailures >= 3) {
|
|
380
|
-
if (typeof process !== "undefined" && process.stderr) {
|
|
381
|
-
process.stderr.write(`[wireguard] STUN discovery failed ${this.consecutiveFailures} consecutive times\n`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}, this.pollIntervalMs);
|
|
386
|
-
})();
|
|
387
|
-
}
|
|
388
|
-
/** Stop periodic discovery */
|
|
389
|
-
stop() {
|
|
390
|
-
if (this.interval) {
|
|
391
|
-
clearInterval(this.interval);
|
|
392
|
-
this.interval = null;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
exports.StunClient = StunClient;
|
|
397
|
-
//# sourceMappingURL=nat.js.map
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* NetworkStateStore — canonical, machine-scoped persistence for network control-plane state.
|
|
4
|
-
*
|
|
5
|
-
* All network-adjacent durable state (peers, signing keys, revocations, nonces, trust)
|
|
6
|
-
* lives in ONE canonical store at ARIA_HOME/network/state.db, independent of any
|
|
7
|
-
* arion-specific Memoria DB. This eliminates the split-brain where peer state could
|
|
8
|
-
* drift across multiple DB paths.
|
|
9
|
-
*
|
|
10
|
-
* PeerRegistry, RevocationStore, and other network stores all use the Database
|
|
11
|
-
* returned by getDatabase().
|
|
12
|
-
*/
|
|
13
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
-
}
|
|
19
|
-
Object.defineProperty(o, k2, desc);
|
|
20
|
-
}) : (function(o, m, k, k2) {
|
|
21
|
-
if (k2 === undefined) k2 = k;
|
|
22
|
-
o[k2] = m[k];
|
|
23
|
-
}));
|
|
24
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
-
}) : function(o, v) {
|
|
27
|
-
o["default"] = v;
|
|
28
|
-
});
|
|
29
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
-
var ownKeys = function(o) {
|
|
31
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
-
var ar = [];
|
|
33
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
-
return ar;
|
|
35
|
-
};
|
|
36
|
-
return ownKeys(o);
|
|
37
|
-
};
|
|
38
|
-
return function (mod) {
|
|
39
|
-
if (mod && mod.__esModule) return mod;
|
|
40
|
-
var result = {};
|
|
41
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
-
__setModuleDefault(result, mod);
|
|
43
|
-
return result;
|
|
44
|
-
};
|
|
45
|
-
})();
|
|
46
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.NetworkStateStore = void 0;
|
|
48
|
-
exports.canonicalNetworkStatePath = canonicalNetworkStatePath;
|
|
49
|
-
const fs = __importStar(require("node:fs"));
|
|
50
|
-
const path = __importStar(require("node:path"));
|
|
51
|
-
const network_runtime_1 = require("@aria-cli/tools/network-runtime");
|
|
52
|
-
/**
|
|
53
|
-
* Canonical schema for the network state DB.
|
|
54
|
-
*
|
|
55
|
-
* Only creates tables that PeerRegistry depends on (it does NO schema creation).
|
|
56
|
-
* Other stores (RevocationStore, PeerTrustStore, PeerSigningKeyStore,
|
|
57
|
-
* InviteConsumeLedger, DurableReplayGuard, etc.) create their own tables
|
|
58
|
-
* via ensureTable() in their constructors — they're self-managing.
|
|
59
|
-
*/
|
|
60
|
-
const NETWORK_PEERS_TABLE_SQL = `
|
|
61
|
-
CREATE TABLE IF NOT EXISTS network_peers (
|
|
62
|
-
node_id TEXT PRIMARY KEY,
|
|
63
|
-
public_key TEXT NOT NULL,
|
|
64
|
-
name TEXT NOT NULL,
|
|
65
|
-
endpoint_host TEXT,
|
|
66
|
-
endpoint_port INTEGER,
|
|
67
|
-
endpoint_revision INTEGER NOT NULL DEFAULT 0,
|
|
68
|
-
control_endpoint_host TEXT,
|
|
69
|
-
control_endpoint_port INTEGER,
|
|
70
|
-
control_tls_ca_fingerprint TEXT,
|
|
71
|
-
preshared_key TEXT,
|
|
72
|
-
allowed_ips TEXT DEFAULT '0.0.0.0/0',
|
|
73
|
-
invite_token TEXT,
|
|
74
|
-
status TEXT DEFAULT 'active',
|
|
75
|
-
last_handshake INTEGER,
|
|
76
|
-
created_at INTEGER,
|
|
77
|
-
updated_at INTEGER,
|
|
78
|
-
signing_public_key TEXT
|
|
79
|
-
);`;
|
|
80
|
-
const NETWORK_PEERS_INDEX_SQL = `
|
|
81
|
-
CREATE INDEX IF NOT EXISTS idx_network_peers_name ON network_peers(name);
|
|
82
|
-
CREATE INDEX IF NOT EXISTS idx_network_peers_status ON network_peers(status);
|
|
83
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_network_peers_public_key
|
|
84
|
-
ON network_peers(public_key);
|
|
85
|
-
`;
|
|
86
|
-
const SCHEMA_SQL = `
|
|
87
|
-
-- Peer registry canonical hard-cut schema
|
|
88
|
-
${NETWORK_PEERS_TABLE_SQL}
|
|
89
|
-
|
|
90
|
-
-- Token replay protection (used by PeerRegistry.claimToken/releaseToken)
|
|
91
|
-
CREATE TABLE IF NOT EXISTS token_claims (
|
|
92
|
-
nonce TEXT PRIMARY KEY,
|
|
93
|
-
claimed_at INTEGER NOT NULL
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
-- Pending tunnel/pair requests (used by PeerRegistry.listByStatus)
|
|
97
|
-
CREATE TABLE IF NOT EXISTS pending_tunnel (
|
|
98
|
-
id TEXT PRIMARY KEY,
|
|
99
|
-
peer_name TEXT NOT NULL,
|
|
100
|
-
peer_public_key TEXT NOT NULL,
|
|
101
|
-
status TEXT DEFAULT 'pending',
|
|
102
|
-
created_at INTEGER NOT NULL,
|
|
103
|
-
updated_at INTEGER
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
-- Schema version tracking
|
|
107
|
-
CREATE TABLE IF NOT EXISTS network_schema_version (
|
|
108
|
-
version INTEGER NOT NULL
|
|
109
|
-
);
|
|
110
|
-
`;
|
|
111
|
-
const CURRENT_SCHEMA_VERSION = 6;
|
|
112
|
-
class NetworkStateStore {
|
|
113
|
-
options;
|
|
114
|
-
db = null;
|
|
115
|
-
dbPath;
|
|
116
|
-
constructor(options) {
|
|
117
|
-
this.options = options;
|
|
118
|
-
this.dbPath = options.dbPath ?? path.join(options.ariaHome, "network", "state.db");
|
|
119
|
-
}
|
|
120
|
-
/** Canonical path to the network state DB */
|
|
121
|
-
get path() {
|
|
122
|
-
return this.dbPath;
|
|
123
|
-
}
|
|
124
|
-
/** Whether the store has been opened */
|
|
125
|
-
get isOpen() {
|
|
126
|
-
return this.db !== null;
|
|
127
|
-
}
|
|
128
|
-
/** Open the database, creating schema if needed */
|
|
129
|
-
open() {
|
|
130
|
-
if (this.db)
|
|
131
|
-
return this.db;
|
|
132
|
-
// Ensure directory exists
|
|
133
|
-
const dir = path.dirname(this.dbPath);
|
|
134
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
135
|
-
// Dynamic import better-sqlite3 (same pattern as PeerRegistry)
|
|
136
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
137
|
-
const BetterSqlite3 = require("better-sqlite3");
|
|
138
|
-
this.db = new BetterSqlite3(this.dbPath);
|
|
139
|
-
try {
|
|
140
|
-
this.db.pragma("journal_mode = WAL");
|
|
141
|
-
this.db.pragma("busy_timeout = 5000");
|
|
142
|
-
// Create schema
|
|
143
|
-
this.db.exec(SCHEMA_SQL);
|
|
144
|
-
this.reconcilePeerTableSchema(this.db);
|
|
145
|
-
this.db.exec(NETWORK_PEERS_INDEX_SQL);
|
|
146
|
-
// Track schema version
|
|
147
|
-
const versionRow = this.db
|
|
148
|
-
.prepare("SELECT version FROM network_schema_version LIMIT 1")
|
|
149
|
-
.get();
|
|
150
|
-
if (!versionRow) {
|
|
151
|
-
this.db
|
|
152
|
-
.prepare("INSERT INTO network_schema_version (version) VALUES (?)")
|
|
153
|
-
.run(CURRENT_SCHEMA_VERSION);
|
|
154
|
-
}
|
|
155
|
-
else if (versionRow.version < CURRENT_SCHEMA_VERSION) {
|
|
156
|
-
this.db
|
|
157
|
-
.prepare("UPDATE network_schema_version SET version = ?")
|
|
158
|
-
.run(CURRENT_SCHEMA_VERSION);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
this.db.close();
|
|
163
|
-
this.db = null;
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
166
|
-
return this.db;
|
|
167
|
-
}
|
|
168
|
-
reconcilePeerTableSchema(db) {
|
|
169
|
-
const tableRow = db
|
|
170
|
-
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='network_peers'")
|
|
171
|
-
.get();
|
|
172
|
-
if (!tableRow?.sql)
|
|
173
|
-
return;
|
|
174
|
-
const tableSql = tableRow.sql.toLowerCase();
|
|
175
|
-
const hasLegacyStatusCheck = tableSql.includes("check") && !tableSql.includes("pending_verification");
|
|
176
|
-
const columns = db.prepare("PRAGMA table_info(network_peers)").all();
|
|
177
|
-
const columnNames = new Set(columns.map((column) => column.name));
|
|
178
|
-
const primaryKey = columns.find((column) => column.pk === 1)?.name;
|
|
179
|
-
const requiredColumns = [
|
|
180
|
-
"public_key",
|
|
181
|
-
"node_id",
|
|
182
|
-
"name",
|
|
183
|
-
"endpoint_host",
|
|
184
|
-
"endpoint_port",
|
|
185
|
-
"endpoint_revision",
|
|
186
|
-
"control_endpoint_host",
|
|
187
|
-
"control_endpoint_port",
|
|
188
|
-
"control_tls_ca_fingerprint",
|
|
189
|
-
"preshared_key",
|
|
190
|
-
"allowed_ips",
|
|
191
|
-
"invite_token",
|
|
192
|
-
"status",
|
|
193
|
-
"last_handshake",
|
|
194
|
-
"created_at",
|
|
195
|
-
"updated_at",
|
|
196
|
-
"signing_public_key",
|
|
197
|
-
];
|
|
198
|
-
const missingColumns = requiredColumns.filter((column) => !columnNames.has(column));
|
|
199
|
-
const legacySchemaProblems = [];
|
|
200
|
-
if (hasLegacyStatusCheck) {
|
|
201
|
-
legacySchemaProblems.push("legacy status check");
|
|
202
|
-
}
|
|
203
|
-
if (columnNames.has("message_port")) {
|
|
204
|
-
legacySchemaProblems.push("message_port column");
|
|
205
|
-
}
|
|
206
|
-
if (columnNames.has("peer_id")) {
|
|
207
|
-
legacySchemaProblems.push("legacy peer_id column");
|
|
208
|
-
}
|
|
209
|
-
if (primaryKey !== "node_id") {
|
|
210
|
-
legacySchemaProblems.push(`primary key is ${primaryKey ?? "missing"}`);
|
|
211
|
-
}
|
|
212
|
-
if (missingColumns.length > 0) {
|
|
213
|
-
legacySchemaProblems.push(`missing columns: ${missingColumns.join(", ")}`);
|
|
214
|
-
}
|
|
215
|
-
if (legacySchemaProblems.length === 0) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
throw new Error(`Legacy network_peers schema requires hard reset: ${legacySchemaProblems.join(", ")}`);
|
|
219
|
-
}
|
|
220
|
-
/** Get the underlying Database handle (opens if needed) */
|
|
221
|
-
getDatabase() {
|
|
222
|
-
return this.open();
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Claim the owner epoch for this runtime on the shared network state DB.
|
|
226
|
-
* Must be called after open() and before any durable writes.
|
|
227
|
-
* Throws StaleOwnerError if a newer generation already owns the DB.
|
|
228
|
-
*/
|
|
229
|
-
claimOwnerEpoch(generation) {
|
|
230
|
-
const db = this.open();
|
|
231
|
-
(0, network_runtime_1.claimDbOwnerEpoch)(db, generation);
|
|
232
|
-
}
|
|
233
|
-
/** Close the database */
|
|
234
|
-
close() {
|
|
235
|
-
if (this.db) {
|
|
236
|
-
this.db.close();
|
|
237
|
-
this.db = null;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
exports.NetworkStateStore = NetworkStateStore;
|
|
242
|
-
/**
|
|
243
|
-
* Resolve the canonical network state DB path for a given ARIA home.
|
|
244
|
-
*/
|
|
245
|
-
function canonicalNetworkStatePath(ariaHome) {
|
|
246
|
-
return path.join(ariaHome, "network", "state.db");
|
|
247
|
-
}
|
|
248
|
-
//# sourceMappingURL=network-state-store.js.map
|