@aria-cli/wireguard 1.0.36 → 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 +11 -16
- 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/tunnel.js
DELETED
|
@@ -1,474 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* SecureTunnel — TypeScript transport layer wrapping the boringtun native addon.
|
|
4
|
-
*
|
|
5
|
-
* Manages the UDP socket, port forwarding (plaintext ↔ encrypted), and the
|
|
6
|
-
* 250ms timer loop that drives WireGuard's internal state machine. The native
|
|
7
|
-
* addon handles all cryptographic operations; this module handles I/O.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* const tunnel = new SecureTunnel({ privateKey, peerPublicKey, listenPort });
|
|
11
|
-
* await tunnel.start();
|
|
12
|
-
* tunnel.sendPlaintext(Buffer.from("hello"));
|
|
13
|
-
* tunnel.on("plaintext", (data) => console.log("received:", data));
|
|
14
|
-
* tunnel.stop();
|
|
15
|
-
*/
|
|
16
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
-
if (k2 === undefined) k2 = k;
|
|
18
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
-
}
|
|
22
|
-
Object.defineProperty(o, k2, desc);
|
|
23
|
-
}) : (function(o, m, k, k2) {
|
|
24
|
-
if (k2 === undefined) k2 = k;
|
|
25
|
-
o[k2] = m[k];
|
|
26
|
-
}));
|
|
27
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
-
}) : function(o, v) {
|
|
30
|
-
o["default"] = v;
|
|
31
|
-
});
|
|
32
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
-
var ownKeys = function(o) {
|
|
34
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
-
var ar = [];
|
|
36
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
-
return ar;
|
|
38
|
-
};
|
|
39
|
-
return ownKeys(o);
|
|
40
|
-
};
|
|
41
|
-
return function (mod) {
|
|
42
|
-
if (mod && mod.__esModule) return mod;
|
|
43
|
-
var result = {};
|
|
44
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
-
__setModuleDefault(result, mod);
|
|
46
|
-
return result;
|
|
47
|
-
};
|
|
48
|
-
})();
|
|
49
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
-
exports.SecureTunnel = void 0;
|
|
51
|
-
exports.wrapIpv4 = wrapIpv4;
|
|
52
|
-
exports.unwrapIpv4 = unwrapIpv4;
|
|
53
|
-
const dgram = __importStar(require("node:dgram"));
|
|
54
|
-
const fs = __importStar(require("node:fs"));
|
|
55
|
-
const path = __importStar(require("node:path"));
|
|
56
|
-
const node_events_1 = require("node:events");
|
|
57
|
-
function appendSecureTunnelDiagnostic(entry) {
|
|
58
|
-
try {
|
|
59
|
-
const logDir = path.join(process.env.HOME ?? "/tmp", ".aria", "audit");
|
|
60
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
61
|
-
fs.appendFileSync(path.join(logDir, "wireguard-secure-tunnel.jsonl"), JSON.stringify({ timestamp: Date.now(), ...entry }) + "\n");
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// Best-effort diagnostics only.
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Encrypted UDP tunnel backed by boringtun.
|
|
69
|
-
*
|
|
70
|
-
* Events:
|
|
71
|
-
* - "plaintext": decrypted data received from peer
|
|
72
|
-
* - "handshake": WireGuard handshake completed
|
|
73
|
-
* - "error": tunnel or socket error
|
|
74
|
-
* - "close": tunnel stopped
|
|
75
|
-
*/
|
|
76
|
-
/**
|
|
77
|
-
* Wrap arbitrary data in a minimal valid IPv4 packet for boringtun layer-3 tunnel.
|
|
78
|
-
*
|
|
79
|
-
* WireGuard is a layer-3 VPN — boringtun validates that decrypted plaintext is a
|
|
80
|
-
* valid IPv4 or IPv6 packet before returning it. Raw bytes are rejected with
|
|
81
|
-
* "InvalidPacket". This helper constructs a minimal 20-byte IPv4 header wrapping
|
|
82
|
-
* the given payload.
|
|
83
|
-
*/
|
|
84
|
-
function wrapIpv4(payload, srcIp = 0x0a000001, dstIp = 0x0a000002) {
|
|
85
|
-
if (payload.length > 65515) {
|
|
86
|
-
throw new Error("Payload too large for IPv4 (max 65515 bytes)");
|
|
87
|
-
}
|
|
88
|
-
const totalLength = 20 + payload.length;
|
|
89
|
-
const header = Buffer.alloc(20);
|
|
90
|
-
header[0] = 0x45; // Version 4, IHL 5 (20 bytes)
|
|
91
|
-
header.writeUInt16BE(totalLength, 2); // Total length
|
|
92
|
-
header[6] = 0x40; // Don't fragment
|
|
93
|
-
header[8] = 64; // TTL
|
|
94
|
-
header[9] = 0x04; // Protocol: IP-in-IP (placeholder)
|
|
95
|
-
// Source and destination (tunnel-internal)
|
|
96
|
-
header.writeUInt32BE(srcIp, 12);
|
|
97
|
-
header.writeUInt32BE(dstIp, 16);
|
|
98
|
-
// Checksum (RFC 1071)
|
|
99
|
-
let sum = 0;
|
|
100
|
-
for (let i = 0; i < 20; i += 2)
|
|
101
|
-
sum += header.readUInt16BE(i);
|
|
102
|
-
while (sum > 0xffff)
|
|
103
|
-
sum = (sum & 0xffff) + (sum >> 16);
|
|
104
|
-
header.writeUInt16BE(~sum & 0xffff, 10);
|
|
105
|
-
return Buffer.concat([header, payload]);
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Extract payload from an IPv4 packet by reading the IHL field.
|
|
109
|
-
* Optionally validates that the source IP matches the expected peer IP.
|
|
110
|
-
*/
|
|
111
|
-
function unwrapIpv4(packet, expectedSourceIp) {
|
|
112
|
-
if (packet.length < 20)
|
|
113
|
-
return packet;
|
|
114
|
-
// Validate source IP if expected (defense-in-depth against inner-header spoofing)
|
|
115
|
-
if (expectedSourceIp !== undefined) {
|
|
116
|
-
const sourceIp = packet.readUInt32BE(12);
|
|
117
|
-
if (sourceIp !== expectedSourceIp) {
|
|
118
|
-
throw new Error(`Source IP mismatch: expected ${expectedSourceIp.toString(16)}, got ${sourceIp.toString(16)}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
const ihl = (packet.readUInt8(0) & 0x0f) * 4;
|
|
122
|
-
return packet.subarray(ihl);
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* WireGuard message type 4 = transport data (little-endian u32 in first 4 bytes).
|
|
126
|
-
*
|
|
127
|
-
* Only transport data proves an authenticated session. Handshake responses
|
|
128
|
-
* (type 2) are generated when we act as RESPONDER — they do NOT prove the
|
|
129
|
-
* peer completed the handshake with us. Treating type 2 as proof prematurely
|
|
130
|
-
* promotes the tunnel to "connected", which triggers flushQueue → encrypt on
|
|
131
|
-
* a tunnel with no current session → new handshake initiation that overwrites
|
|
132
|
-
* the pending initiator state → the real handshake response is rejected →
|
|
133
|
-
* return path dies.
|
|
134
|
-
*/
|
|
135
|
-
const WG_MSG_TYPE_TRANSPORT_DATA = 4;
|
|
136
|
-
function isTransportData(data) {
|
|
137
|
-
if (!data || data.length < 4)
|
|
138
|
-
return false;
|
|
139
|
-
return data.readUInt32LE(0) === WG_MSG_TYPE_TRANSPORT_DATA;
|
|
140
|
-
}
|
|
141
|
-
class SecureTunnel extends node_events_1.EventEmitter {
|
|
142
|
-
options;
|
|
143
|
-
socket = null;
|
|
144
|
-
tickTimer = null;
|
|
145
|
-
tunnel = null;
|
|
146
|
-
stats = {
|
|
147
|
-
bytesSent: 0,
|
|
148
|
-
bytesReceived: 0,
|
|
149
|
-
handshakes: 0,
|
|
150
|
-
lastHandshake: null,
|
|
151
|
-
active: false,
|
|
152
|
-
};
|
|
153
|
-
/** Tracks whether the first successful decrypt has occurred (handshake completed) */
|
|
154
|
-
handshakeCompleted = false;
|
|
155
|
-
peerHost;
|
|
156
|
-
peerPort;
|
|
157
|
-
/** Unsubscribe function for external socket mode */
|
|
158
|
-
externalSocketUnsub = null;
|
|
159
|
-
constructor(options) {
|
|
160
|
-
super();
|
|
161
|
-
this.options = options;
|
|
162
|
-
this.peerHost = options.peerHost;
|
|
163
|
-
this.peerPort = options.peerPort;
|
|
164
|
-
}
|
|
165
|
-
/** Start the tunnel — binds UDP socket, creates WireGuard tunnel, starts timer */
|
|
166
|
-
async start() {
|
|
167
|
-
if (this.stats.active)
|
|
168
|
-
throw new Error("Tunnel already active");
|
|
169
|
-
appendSecureTunnelDiagnostic({
|
|
170
|
-
event: "secure_tunnel_start_called",
|
|
171
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
172
|
-
peerHost: this.peerHost,
|
|
173
|
-
peerPort: this.peerPort,
|
|
174
|
-
listenPort: this.options.listenPort ?? null,
|
|
175
|
-
externalSocket: Boolean(this.options.externalSocket),
|
|
176
|
-
});
|
|
177
|
-
// Lazy-load the native addon
|
|
178
|
-
const { createTunnel } = await import("./index.js");
|
|
179
|
-
this.tunnel = createTunnel({
|
|
180
|
-
privateKey: this.options.privateKey,
|
|
181
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
182
|
-
presharedKey: this.options.presharedKey,
|
|
183
|
-
keepalive: this.options.keepalive ?? 25,
|
|
184
|
-
});
|
|
185
|
-
let port;
|
|
186
|
-
if (this.options.externalSocket) {
|
|
187
|
-
// Shared socket mode — use NetworkManager's single UDP socket.
|
|
188
|
-
// Don't create our own socket; subscribe to the shared one.
|
|
189
|
-
port = this.options.externalSocket.port;
|
|
190
|
-
this.externalSocketUnsub = this.options.externalSocket.onPacket((msg, rinfo) => this.classifyIncomingPacket(msg, rinfo));
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
// Standalone mode — create our own UDP socket (existing behavior)
|
|
194
|
-
this.socket = dgram.createSocket("udp4");
|
|
195
|
-
port = await new Promise((resolve, reject) => {
|
|
196
|
-
this.socket.on("error", reject);
|
|
197
|
-
this.socket.bind(this.options.listenPort ?? 0, () => {
|
|
198
|
-
const addr = this.socket.address();
|
|
199
|
-
resolve(addr.port);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
// Handle incoming encrypted packets
|
|
203
|
-
this.socket.on("message", (msg, rinfo) => {
|
|
204
|
-
this.handleIncomingPacket(msg, rinfo);
|
|
205
|
-
});
|
|
206
|
-
// Post-bind error/close handlers
|
|
207
|
-
this.socket.on("error", (err) => this.emit("error", err));
|
|
208
|
-
this.socket.on("close", () => this.emit("close"));
|
|
209
|
-
}
|
|
210
|
-
// Start the timer loop — drives WireGuard state machine
|
|
211
|
-
let consecutiveTickErrors = 0;
|
|
212
|
-
const MAX_CONSECUTIVE_TICK_ERRORS = 5;
|
|
213
|
-
this.tickTimer = setInterval(() => {
|
|
214
|
-
if (!this.tunnel)
|
|
215
|
-
return;
|
|
216
|
-
try {
|
|
217
|
-
const result = this.tunnel.tick();
|
|
218
|
-
this.handleResult(result);
|
|
219
|
-
consecutiveTickErrors = 0;
|
|
220
|
-
}
|
|
221
|
-
catch (err) {
|
|
222
|
-
consecutiveTickErrors++;
|
|
223
|
-
this.emit("error", err);
|
|
224
|
-
if (consecutiveTickErrors >= MAX_CONSECUTIVE_TICK_ERRORS) {
|
|
225
|
-
this.emit("error", new Error(`Tunnel tick failed ${MAX_CONSECUTIVE_TICK_ERRORS} consecutive times — stopping tunnel`));
|
|
226
|
-
this.stop();
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}, 250);
|
|
230
|
-
this.stats.active = true;
|
|
231
|
-
appendSecureTunnelDiagnostic({
|
|
232
|
-
event: "secure_tunnel_start_returned",
|
|
233
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
234
|
-
peerHost: this.peerHost,
|
|
235
|
-
peerPort: this.peerPort,
|
|
236
|
-
active: this.stats.active,
|
|
237
|
-
handshakeCompleted: this.handshakeCompleted,
|
|
238
|
-
handshakes: this.stats.handshakes,
|
|
239
|
-
port,
|
|
240
|
-
});
|
|
241
|
-
return port;
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Handle an incoming encrypted packet. Called either by our own socket
|
|
245
|
-
* or by the shared socket dispatcher (in shared socket mode).
|
|
246
|
-
*
|
|
247
|
-
* Returns true if this tunnel consumed the packet, false otherwise.
|
|
248
|
-
*/
|
|
249
|
-
handleIncomingPacket(msg, rinfo) {
|
|
250
|
-
return this.classifyIncomingPacket(msg, rinfo).handled;
|
|
251
|
-
}
|
|
252
|
-
classifyIncomingPacket(msg, rinfo) {
|
|
253
|
-
if (!this.tunnel) {
|
|
254
|
-
return {
|
|
255
|
-
handled: false,
|
|
256
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
257
|
-
outcome: "no_tunnel",
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
try {
|
|
261
|
-
let result = this.tunnel.decrypt(msg, rinfo.address);
|
|
262
|
-
// In shared socket mode, "error" means the packet doesn't belong to
|
|
263
|
-
// this tunnel (wrong key/session/index). Return false so the dispatcher
|
|
264
|
-
// tries the next tunnel.
|
|
265
|
-
if (result.op === "error") {
|
|
266
|
-
const errorDetail = result.data ? result.data.toString() : "decrypt error";
|
|
267
|
-
if (this.options.externalSocket) {
|
|
268
|
-
return {
|
|
269
|
-
handled: false,
|
|
270
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
271
|
-
outcome: "decrypt_error",
|
|
272
|
-
errorDetail,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
this.emit("error", new Error(errorDetail));
|
|
276
|
-
return {
|
|
277
|
-
handled: false,
|
|
278
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
279
|
-
outcome: "decrypt_error",
|
|
280
|
-
errorDetail,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
// "done" from decrypt can mean:
|
|
284
|
-
// 1. Keepalive processed — empty payload from our peer (peer is alive)
|
|
285
|
-
// 2. Cookie reply or handshake message consumed internally
|
|
286
|
-
// In both cases, the native addon may have queued a response (e.g., a
|
|
287
|
-
// handshake confirmation or transport data). Drain any pending ops so
|
|
288
|
-
// they get sent immediately rather than waiting for the next tick().
|
|
289
|
-
if (result.op === "done") {
|
|
290
|
-
// Update peer endpoint from this packet's source — even "done" packets
|
|
291
|
-
// prove the peer is reachable at this address.
|
|
292
|
-
if (rinfo.address && rinfo.port) {
|
|
293
|
-
this.peerHost = rinfo.address;
|
|
294
|
-
this.peerPort = rinfo.port;
|
|
295
|
-
}
|
|
296
|
-
// Drain any queued responses the native addon produced while
|
|
297
|
-
// processing this packet (e.g., handshake confirmation).
|
|
298
|
-
let drainResult = this.tunnel.decrypt(Buffer.alloc(0));
|
|
299
|
-
while (drainResult.op !== "done" && drainResult.op !== "error") {
|
|
300
|
-
if (drainResult.op === "write_to_network" && isTransportData(drainResult.data)) {
|
|
301
|
-
this.markAuthenticatedSessionProof();
|
|
302
|
-
}
|
|
303
|
-
this.handleResult(drainResult);
|
|
304
|
-
drainResult = this.tunnel.decrypt(Buffer.alloc(0));
|
|
305
|
-
}
|
|
306
|
-
appendSecureTunnelDiagnostic({
|
|
307
|
-
event: "secure_tunnel_peer_activity",
|
|
308
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
309
|
-
peerHost: rinfo.address,
|
|
310
|
-
peerPort: rinfo.port,
|
|
311
|
-
handshakeCompleted: this.handshakeCompleted,
|
|
312
|
-
});
|
|
313
|
-
this.emit("peerActivity");
|
|
314
|
-
return {
|
|
315
|
-
handled: true,
|
|
316
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
317
|
-
outcome: "done",
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
// write_to_tunnel or write_to_network — authenticated data from our peer.
|
|
321
|
-
// Update endpoint after successful authenticated decryption.
|
|
322
|
-
// Updating before decrypt allows traffic redirection (tunnel hijacking).
|
|
323
|
-
const handledOutcome = result.op === "write_to_network"
|
|
324
|
-
? "write_to_network"
|
|
325
|
-
: "write_to_tunnel";
|
|
326
|
-
this.peerHost = rinfo.address;
|
|
327
|
-
this.peerPort = rinfo.port;
|
|
328
|
-
if (result.op === "write_to_network" && isTransportData(result.data)) {
|
|
329
|
-
this.markAuthenticatedSessionProof();
|
|
330
|
-
}
|
|
331
|
-
this.handleResult(result);
|
|
332
|
-
// Drain all queued results until "done" (decapsulate can produce multiple).
|
|
333
|
-
let drainCount = 0;
|
|
334
|
-
const MAX_DRAIN = 100;
|
|
335
|
-
while (result.op !== "done" && drainCount < MAX_DRAIN) {
|
|
336
|
-
drainCount++;
|
|
337
|
-
result = this.tunnel.decrypt(Buffer.alloc(0));
|
|
338
|
-
if (result.op === "done")
|
|
339
|
-
break;
|
|
340
|
-
if (result.op === "write_to_network" && isTransportData(result.data)) {
|
|
341
|
-
this.markAuthenticatedSessionProof();
|
|
342
|
-
}
|
|
343
|
-
this.handleResult(result);
|
|
344
|
-
}
|
|
345
|
-
return {
|
|
346
|
-
handled: true,
|
|
347
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
348
|
-
outcome: handledOutcome,
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
catch (err) {
|
|
352
|
-
// In shared socket mode, ANY decrypt exception means the packet
|
|
353
|
-
// doesn't belong to this tunnel. Don't emit — return false.
|
|
354
|
-
if (this.options.externalSocket) {
|
|
355
|
-
return {
|
|
356
|
-
handled: false,
|
|
357
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
358
|
-
outcome: "decrypt_exception",
|
|
359
|
-
errorDetail: err instanceof Error ? err.message : String(err),
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
this.emit("error", err);
|
|
363
|
-
return {
|
|
364
|
-
handled: false,
|
|
365
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
366
|
-
outcome: "decrypt_exception",
|
|
367
|
-
errorDetail: err instanceof Error ? err.message : String(err),
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
/** Whether the tunnel is currently active */
|
|
372
|
-
get isActive() {
|
|
373
|
-
return this.stats.active;
|
|
374
|
-
}
|
|
375
|
-
/** Send plaintext data through the encrypted tunnel */
|
|
376
|
-
sendPlaintext(data) {
|
|
377
|
-
if (!this.tunnel || !this.stats.active) {
|
|
378
|
-
throw new Error("Tunnel not active");
|
|
379
|
-
}
|
|
380
|
-
const srcIp = this.options.tunnelSrcIp ?? 0x0a000001;
|
|
381
|
-
const dstIp = this.options.tunnelDstIp ?? 0x0a000002;
|
|
382
|
-
const ipPacket = wrapIpv4(data, srcIp, dstIp);
|
|
383
|
-
const result = this.tunnel.encrypt(ipPacket);
|
|
384
|
-
this.handleResult(result);
|
|
385
|
-
this.stats.bytesSent += data.length;
|
|
386
|
-
}
|
|
387
|
-
/** Set or update the peer endpoint */
|
|
388
|
-
setPeerEndpoint(host, port) {
|
|
389
|
-
this.peerHost = host;
|
|
390
|
-
this.peerPort = port;
|
|
391
|
-
}
|
|
392
|
-
/** Get tunnel statistics */
|
|
393
|
-
getStats() {
|
|
394
|
-
return { ...this.stats };
|
|
395
|
-
}
|
|
396
|
-
markAuthenticatedSessionProof() {
|
|
397
|
-
if (this.handshakeCompleted) {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
this.handshakeCompleted = true;
|
|
401
|
-
this.stats.handshakes++;
|
|
402
|
-
this.stats.lastHandshake = Date.now();
|
|
403
|
-
appendSecureTunnelDiagnostic({
|
|
404
|
-
event: "secure_tunnel_handshake",
|
|
405
|
-
peerPublicKey: this.options.peerPublicKey,
|
|
406
|
-
peerHost: this.peerHost,
|
|
407
|
-
peerPort: this.peerPort,
|
|
408
|
-
handshakes: this.stats.handshakes,
|
|
409
|
-
});
|
|
410
|
-
this.emit("handshake");
|
|
411
|
-
}
|
|
412
|
-
/** Stop the tunnel — closes socket (or unsubscribes from shared), stops timer */
|
|
413
|
-
stop() {
|
|
414
|
-
if (this.tickTimer) {
|
|
415
|
-
clearInterval(this.tickTimer);
|
|
416
|
-
this.tickTimer = null;
|
|
417
|
-
}
|
|
418
|
-
if (this.externalSocketUnsub) {
|
|
419
|
-
// Shared socket mode — unsubscribe, don't close the shared socket
|
|
420
|
-
this.externalSocketUnsub();
|
|
421
|
-
this.externalSocketUnsub = null;
|
|
422
|
-
}
|
|
423
|
-
if (this.socket) {
|
|
424
|
-
try {
|
|
425
|
-
this.socket.close();
|
|
426
|
-
}
|
|
427
|
-
catch {
|
|
428
|
-
// Socket may already be closed
|
|
429
|
-
}
|
|
430
|
-
this.socket = null;
|
|
431
|
-
}
|
|
432
|
-
this.tunnel = null;
|
|
433
|
-
this.stats.active = false;
|
|
434
|
-
this.emit("close");
|
|
435
|
-
this.removeAllListeners();
|
|
436
|
-
}
|
|
437
|
-
/** Handle a WireGuard tunnel result */
|
|
438
|
-
handleResult(result) {
|
|
439
|
-
switch (result.op) {
|
|
440
|
-
case "write_to_network":
|
|
441
|
-
// Send encrypted packet to peer via own socket or shared socket
|
|
442
|
-
if (result.data && this.peerHost && this.peerPort) {
|
|
443
|
-
if (this.options.externalSocket) {
|
|
444
|
-
this.options.externalSocket.send(result.data, this.peerPort, this.peerHost);
|
|
445
|
-
}
|
|
446
|
-
else if (this.socket) {
|
|
447
|
-
this.socket.send(result.data, this.peerPort, this.peerHost);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
break;
|
|
451
|
-
case "write_to_tunnel":
|
|
452
|
-
// Any write_to_tunnel means the peer successfully decrypted a packet
|
|
453
|
-
// for us — the session is alive. Emit peerActivity so the dead-peer
|
|
454
|
-
// timer resets even for empty WG keepalive responses.
|
|
455
|
-
this.markAuthenticatedSessionProof();
|
|
456
|
-
this.emit("peerActivity");
|
|
457
|
-
// Decrypted plaintext received — unwrap the IPv4 header added by the sender
|
|
458
|
-
if (result.data && result.data.length > 0) {
|
|
459
|
-
const payload = unwrapIpv4(result.data, this.options.tunnelDstIp);
|
|
460
|
-
this.stats.bytesReceived += payload.length;
|
|
461
|
-
this.emit("plaintext", payload);
|
|
462
|
-
}
|
|
463
|
-
break;
|
|
464
|
-
case "done":
|
|
465
|
-
// No action needed
|
|
466
|
-
break;
|
|
467
|
-
case "error":
|
|
468
|
-
this.emit("error", new Error(result.data ? result.data.toString() : "Unknown tunnel error"));
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
exports.SecureTunnel = SecureTunnel;
|
|
474
|
-
//# sourceMappingURL=tunnel.js.map
|