@omindu/yaksha 1.0.0
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/LICENSE +21 -0
- package/bin/yaksha.js +329 -0
- package/package.json +28 -0
- package/src/bypass/firewall-evasion.js +354 -0
- package/src/client/index.js +544 -0
- package/src/core/connection.js +393 -0
- package/src/core/encryption.js +299 -0
- package/src/core/protocol.js +268 -0
- package/src/features/dns-override.js +403 -0
- package/src/features/multi-path.js +394 -0
- package/src/features/sni-spoof.js +355 -0
- package/src/features/tls-camouflage.js +369 -0
- package/src/features/traffic-obfuscation.js +338 -0
- package/src/index.js +106 -0
- package/src/security/auth.js +441 -0
- package/src/security/levels.js +316 -0
- package/src/server/index.js +551 -0
- package/src/utils/buffer-pool.js +150 -0
- package/src/utils/config.js +205 -0
- package/src/utils/logger.js +105 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Yaksha VPN Client
|
|
5
|
+
* High-performance VPN client with automatic reconnection and all features
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EventEmitter = require('events');
|
|
9
|
+
const Connection = require('../core/connection');
|
|
10
|
+
const Protocol = require('../core/protocol');
|
|
11
|
+
const Encryption = require('../core/encryption');
|
|
12
|
+
const SecurityLevels = require('../security/levels');
|
|
13
|
+
const SNISpoofing = require('../features/sni-spoof');
|
|
14
|
+
const TrafficObfuscation = require('../features/traffic-obfuscation');
|
|
15
|
+
const MultiPath = require('../features/multi-path');
|
|
16
|
+
const DNSOverride = require('../features/dns-override');
|
|
17
|
+
const TLSCamouflage = require('../features/tls-camouflage');
|
|
18
|
+
const FirewallEvasion = require('../bypass/firewall-evasion');
|
|
19
|
+
const Config = require('../utils/config');
|
|
20
|
+
const Logger = require('../utils/logger');
|
|
21
|
+
|
|
22
|
+
class YakshaClient extends EventEmitter {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
super();
|
|
25
|
+
|
|
26
|
+
// Configuration
|
|
27
|
+
this.config = new Config(options);
|
|
28
|
+
this.serverHost = options.server || 'localhost';
|
|
29
|
+
this.serverPort = options.port || 8443;
|
|
30
|
+
this.securityLevel = options.securityLevel || 'medium';
|
|
31
|
+
this.autoReconnect = options.autoReconnect !== false;
|
|
32
|
+
this.authCredentials = options.authCredentials || {};
|
|
33
|
+
this.customSecurityConfig = options.customSecurityConfig || null;
|
|
34
|
+
|
|
35
|
+
// Security configuration
|
|
36
|
+
if (this.securityLevel === 'custom' && this.customSecurityConfig) {
|
|
37
|
+
this.securityConfig = SecurityLevels.createCustomConfig(
|
|
38
|
+
this.customSecurityConfig,
|
|
39
|
+
this.customSecurityConfig.baseLevel || 'medium'
|
|
40
|
+
);
|
|
41
|
+
} else {
|
|
42
|
+
this.securityConfig = SecurityLevels.getConfig(this.securityLevel);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Core components
|
|
46
|
+
this.protocol = new Protocol({
|
|
47
|
+
maxPayloadSize: options.maxPayloadSize || 65535,
|
|
48
|
+
enablePadding: this.securityConfig.obfuscation !== 'none'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.encryption = new Encryption(this.securityLevel, {
|
|
52
|
+
customConfig: this.securityLevel === 'custom' ? this.securityConfig : null
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Feature modules
|
|
56
|
+
this.features = {
|
|
57
|
+
sniSpoofing: new SNISpoofing(this.securityLevel, {
|
|
58
|
+
enabled: options.features?.sniSpoofing !== false,
|
|
59
|
+
bugHost: options.bugHost || this.securityConfig.bugHost || null
|
|
60
|
+
}),
|
|
61
|
+
obfuscation: new TrafficObfuscation(this.securityLevel, {
|
|
62
|
+
enabled: options.features?.trafficObfuscation !== false
|
|
63
|
+
}),
|
|
64
|
+
multiPath: new MultiPath(this.securityLevel, {
|
|
65
|
+
enabled: options.features?.multiPath !== false
|
|
66
|
+
}),
|
|
67
|
+
dnsOverride: new DNSOverride(this.securityLevel, {
|
|
68
|
+
enabled: options.features?.dnsOverride !== false
|
|
69
|
+
}),
|
|
70
|
+
tlsCamouflage: new TLSCamouflage(this.securityLevel, {
|
|
71
|
+
enabled: options.features?.tlsCamouflage !== false
|
|
72
|
+
}),
|
|
73
|
+
firewallEvasion: new FirewallEvasion(this.securityLevel, {
|
|
74
|
+
enabled: options.features?.firewallEvasion !== false
|
|
75
|
+
})
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.logger = new Logger({
|
|
79
|
+
prefix: 'Client',
|
|
80
|
+
level: options.logLevel || 'info'
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Client state
|
|
84
|
+
this.connection = null;
|
|
85
|
+
this.sessionId = null;
|
|
86
|
+
this.sequence = 0;
|
|
87
|
+
this.connected = false;
|
|
88
|
+
this.authenticated = false;
|
|
89
|
+
|
|
90
|
+
// Statistics
|
|
91
|
+
this.stats = {
|
|
92
|
+
connectedAt: null,
|
|
93
|
+
bytesReceived: 0,
|
|
94
|
+
bytesSent: 0,
|
|
95
|
+
packetsReceived: 0,
|
|
96
|
+
packetsSent: 0,
|
|
97
|
+
reconnects: 0,
|
|
98
|
+
errors: 0
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Connect to VPN server
|
|
104
|
+
*/
|
|
105
|
+
async connect() {
|
|
106
|
+
if (this.connected) {
|
|
107
|
+
throw new Error('Already connected');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.logger.info(`Connecting to Yaksha VPN Server at ${this.serverHost}:${this.serverPort}`);
|
|
111
|
+
this.logger.info(`Security Level: ${this.securityLevel.toUpperCase()}`);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// Check if multi-path is enabled
|
|
115
|
+
if (this.features.multiPath.enabled) {
|
|
116
|
+
await this._connectMultiPath();
|
|
117
|
+
} else {
|
|
118
|
+
await this._connectSinglePath();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Perform handshake
|
|
122
|
+
await this._performHandshake();
|
|
123
|
+
|
|
124
|
+
this.connected = true;
|
|
125
|
+
this.stats.connectedAt = Date.now();
|
|
126
|
+
|
|
127
|
+
this.emit('connected', {
|
|
128
|
+
server: `${this.serverHost}:${this.serverPort}`,
|
|
129
|
+
sessionId: this.sessionId
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this.logger.info(`Connected successfully. Session ID: ${this.sessionId}`);
|
|
133
|
+
|
|
134
|
+
// Start keepalive
|
|
135
|
+
this._startKeepalive();
|
|
136
|
+
|
|
137
|
+
} catch (error) {
|
|
138
|
+
this.logger.error('Connection failed:', error.message);
|
|
139
|
+
this.stats.errors++;
|
|
140
|
+
await this.disconnect();
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Connect using single path
|
|
147
|
+
*/
|
|
148
|
+
async _connectSinglePath() {
|
|
149
|
+
this.connection = new Connection({
|
|
150
|
+
host: this.serverHost,
|
|
151
|
+
port: this.serverPort,
|
|
152
|
+
autoReconnect: this.autoReconnect
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Set up event handlers
|
|
156
|
+
this.connection.on('tcp:data', (data) => {
|
|
157
|
+
this._handleData(data);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
this.connection.on('udp:data', (data) => {
|
|
161
|
+
this._handleData(data);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
this.connection.on('disconnected', () => {
|
|
165
|
+
this._handleDisconnection();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
this.connection.on('reconnecting', (attempt, delay) => {
|
|
169
|
+
this.logger.info(`Reconnecting... (attempt ${attempt}, delay ${delay}ms)`);
|
|
170
|
+
this.emit('reconnecting', attempt, delay);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
this.connection.on('error', (error) => {
|
|
174
|
+
this.logger.error('Connection error:', error.message);
|
|
175
|
+
this.stats.errors++;
|
|
176
|
+
this.emit('error', error);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await this.connection.connect();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Connect using multi-path
|
|
184
|
+
*/
|
|
185
|
+
async _connectMultiPath() {
|
|
186
|
+
// Initialize multi-path routing
|
|
187
|
+
await this.features.multiPath.initializePaths(
|
|
188
|
+
this.serverHost,
|
|
189
|
+
this.serverPort,
|
|
190
|
+
{
|
|
191
|
+
autoReconnect: this.autoReconnect
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Set up event handlers
|
|
196
|
+
this.features.multiPath.on('data', (data, pathIndex) => {
|
|
197
|
+
this._handleData(data);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
this.features.multiPath.on('path-error', (pathIndex, error) => {
|
|
201
|
+
this.logger.warn(`Path ${pathIndex} error:`, error.message);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
this.features.multiPath.on('path-disconnected', (pathIndex) => {
|
|
205
|
+
this.logger.warn(`Path ${pathIndex} disconnected`);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Perform handshake with server
|
|
211
|
+
*/
|
|
212
|
+
async _performHandshake() {
|
|
213
|
+
// Generate encryption keys
|
|
214
|
+
this.encryption.generateKeys();
|
|
215
|
+
|
|
216
|
+
// Build handshake packet
|
|
217
|
+
const handshakeData = JSON.stringify({
|
|
218
|
+
version: '1.1.0',
|
|
219
|
+
clientPublicKey: this.encryption.keys.publicKey
|
|
220
|
+
? this.encryption.keys.publicKey.toString('base64')
|
|
221
|
+
: null,
|
|
222
|
+
securityLevel: this.securityLevel,
|
|
223
|
+
credentials: this.authCredentials,
|
|
224
|
+
features: {
|
|
225
|
+
sniSpoofing: this.features.sniSpoofing.enabled,
|
|
226
|
+
obfuscation: this.features.obfuscation.enabled,
|
|
227
|
+
multiPath: this.features.multiPath.enabled,
|
|
228
|
+
dnsOverride: this.features.dnsOverride.enabled,
|
|
229
|
+
tlsCamouflage: this.features.tlsCamouflage.enabled,
|
|
230
|
+
firewallEvasion: this.features.firewallEvasion.enabled
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Generate temporary session ID
|
|
235
|
+
const tempSessionId = Math.floor(Math.random() * 0xFFFFFFFF);
|
|
236
|
+
|
|
237
|
+
const { packet } = this.protocol.createHandshake(
|
|
238
|
+
tempSessionId,
|
|
239
|
+
this.sequence++,
|
|
240
|
+
Buffer.from(handshakeData, 'utf8')
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Send handshake
|
|
244
|
+
await this._sendPacket(packet);
|
|
245
|
+
|
|
246
|
+
// Wait for handshake response
|
|
247
|
+
return new Promise((resolve, reject) => {
|
|
248
|
+
const timeout = setTimeout(() => {
|
|
249
|
+
reject(new Error('Handshake timeout'));
|
|
250
|
+
}, 10000);
|
|
251
|
+
|
|
252
|
+
const handler = (data) => {
|
|
253
|
+
try {
|
|
254
|
+
const { header, payload } = this.protocol.deserialize(data);
|
|
255
|
+
|
|
256
|
+
if (header.type === Protocol.PACKET_TYPES.HANDSHAKE) {
|
|
257
|
+
const response = JSON.parse(payload.toString('utf8'));
|
|
258
|
+
|
|
259
|
+
// Store session ID
|
|
260
|
+
this.sessionId = response.sessionId;
|
|
261
|
+
|
|
262
|
+
// Perform key exchange if server provided public key
|
|
263
|
+
if (response.publicKey) {
|
|
264
|
+
const serverPublicKey = Buffer.from(response.publicKey, 'base64');
|
|
265
|
+
this.encryption.keyExchange(serverPublicKey);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Wait for auth response
|
|
269
|
+
this.once('_authResponse', (authSuccess) => {
|
|
270
|
+
clearTimeout(timeout);
|
|
271
|
+
if (authSuccess) {
|
|
272
|
+
this.authenticated = true;
|
|
273
|
+
resolve();
|
|
274
|
+
} else {
|
|
275
|
+
reject(new Error('Authentication failed'));
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
} else if (header.type === Protocol.PACKET_TYPES.DATA) {
|
|
279
|
+
// Authentication response
|
|
280
|
+
const response = JSON.parse(payload.toString('utf8'));
|
|
281
|
+
this.emit('_authResponse', response.success);
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
// Ignore parse errors during handshake
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
this.once('_rawData', handler);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Handle incoming data
|
|
294
|
+
*/
|
|
295
|
+
_handleData(data) {
|
|
296
|
+
this.emit('_rawData', data);
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const { header, payload } = this.protocol.deserialize(data);
|
|
300
|
+
|
|
301
|
+
this.stats.packetsReceived++;
|
|
302
|
+
this.stats.bytesReceived += data.length;
|
|
303
|
+
|
|
304
|
+
// Handle different packet types
|
|
305
|
+
switch (header.type) {
|
|
306
|
+
case Protocol.PACKET_TYPES.DATA:
|
|
307
|
+
this._handleDataPacket(payload);
|
|
308
|
+
break;
|
|
309
|
+
|
|
310
|
+
case Protocol.PACKET_TYPES.KEEPALIVE:
|
|
311
|
+
// Server keepalive response
|
|
312
|
+
break;
|
|
313
|
+
|
|
314
|
+
case Protocol.PACKET_TYPES.CLOSE:
|
|
315
|
+
const reason = payload.toString('utf8');
|
|
316
|
+
this.logger.info(`Server closing connection: ${reason}`);
|
|
317
|
+
this.disconnect();
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
default:
|
|
321
|
+
this.logger.warn(`Unknown packet type: ${header.type}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
} catch (error) {
|
|
325
|
+
this.logger.error('Error handling data:', error.message);
|
|
326
|
+
this.stats.errors++;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Handle data packet
|
|
332
|
+
*/
|
|
333
|
+
_handleDataPacket(payload) {
|
|
334
|
+
if (!this.authenticated) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Decrypt data if encrypted
|
|
339
|
+
try {
|
|
340
|
+
// In production, decrypt payload here
|
|
341
|
+
// const decrypted = this.encryption.decrypt(...);
|
|
342
|
+
|
|
343
|
+
this.emit('data', payload);
|
|
344
|
+
|
|
345
|
+
} catch (error) {
|
|
346
|
+
this.logger.error('Error handling data packet:', error.message);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Send data through VPN tunnel
|
|
352
|
+
*/
|
|
353
|
+
async send(data, protocol = 'udp') {
|
|
354
|
+
if (!this.connected || !this.authenticated) {
|
|
355
|
+
throw new Error('Not connected or authenticated');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
// Apply features in order
|
|
360
|
+
|
|
361
|
+
// 1. TLS Camouflage
|
|
362
|
+
if (this.features.tlsCamouflage.enabled) {
|
|
363
|
+
data = this.features.tlsCamouflage.camouflage(data);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 2. Traffic Obfuscation
|
|
367
|
+
if (this.features.obfuscation.enabled) {
|
|
368
|
+
const { data: obfuscated } = this.features.obfuscation.obfuscate(data);
|
|
369
|
+
data = obfuscated;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 3. Firewall Evasion
|
|
373
|
+
if (this.features.firewallEvasion.enabled) {
|
|
374
|
+
data = this.features.firewallEvasion.evadeDPI(data);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 4. Create protocol packet
|
|
378
|
+
const { packet } = this.protocol.createData(
|
|
379
|
+
this.sessionId,
|
|
380
|
+
this.sequence++,
|
|
381
|
+
data
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// 5. Send via multi-path or single path
|
|
385
|
+
await this._sendPacket(packet, protocol);
|
|
386
|
+
|
|
387
|
+
this.stats.packetsSent++;
|
|
388
|
+
this.stats.bytesSent += packet.length;
|
|
389
|
+
|
|
390
|
+
} catch (error) {
|
|
391
|
+
this.logger.error('Error sending data:', error.message);
|
|
392
|
+
this.stats.errors++;
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Send packet
|
|
399
|
+
*/
|
|
400
|
+
async _sendPacket(packet, protocol = 'tcp') {
|
|
401
|
+
if (this.features.multiPath.enabled && this.features.multiPath.paths.length > 0) {
|
|
402
|
+
await this.features.multiPath.send(packet, protocol);
|
|
403
|
+
} else if (this.connection) {
|
|
404
|
+
if (protocol === 'tcp') {
|
|
405
|
+
await this.connection.sendTCP(packet);
|
|
406
|
+
} else {
|
|
407
|
+
await this.connection.sendUDP(packet);
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
throw new Error('No connection available');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Start keepalive mechanism
|
|
416
|
+
*/
|
|
417
|
+
_startKeepalive() {
|
|
418
|
+
this.keepaliveInterval = setInterval(() => {
|
|
419
|
+
if (this.connected && this.authenticated) {
|
|
420
|
+
const { packet } = this.protocol.createKeepalive(
|
|
421
|
+
this.sessionId,
|
|
422
|
+
this.sequence++
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
this._sendPacket(packet, 'tcp').catch(error => {
|
|
426
|
+
this.logger.error('Keepalive failed:', error.message);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}, 30000); // Every 30 seconds
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Stop keepalive mechanism
|
|
434
|
+
*/
|
|
435
|
+
_stopKeepalive() {
|
|
436
|
+
if (this.keepaliveInterval) {
|
|
437
|
+
clearInterval(this.keepaliveInterval);
|
|
438
|
+
this.keepaliveInterval = null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Handle disconnection
|
|
444
|
+
*/
|
|
445
|
+
_handleDisconnection() {
|
|
446
|
+
if (!this.connected) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
this.logger.info('Disconnected from server');
|
|
451
|
+
this.connected = false;
|
|
452
|
+
this.authenticated = false;
|
|
453
|
+
|
|
454
|
+
this._stopKeepalive();
|
|
455
|
+
|
|
456
|
+
this.emit('disconnected');
|
|
457
|
+
|
|
458
|
+
if (this.autoReconnect) {
|
|
459
|
+
this.logger.info('Attempting to reconnect...');
|
|
460
|
+
this.stats.reconnects++;
|
|
461
|
+
|
|
462
|
+
setTimeout(() => {
|
|
463
|
+
this.connect().catch(error => {
|
|
464
|
+
this.logger.error('Reconnection failed:', error.message);
|
|
465
|
+
});
|
|
466
|
+
}, 3000);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Disconnect from server
|
|
472
|
+
*/
|
|
473
|
+
async disconnect() {
|
|
474
|
+
if (!this.connected) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
this.logger.info('Disconnecting from server...');
|
|
479
|
+
|
|
480
|
+
try {
|
|
481
|
+
// Send close packet
|
|
482
|
+
const { packet } = this.protocol.createClose(
|
|
483
|
+
this.sessionId,
|
|
484
|
+
this.sequence++,
|
|
485
|
+
'Client disconnecting'
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
await this._sendPacket(packet, 'tcp');
|
|
489
|
+
} catch (error) {
|
|
490
|
+
// Ignore errors during disconnect
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
this._stopKeepalive();
|
|
494
|
+
|
|
495
|
+
if (this.features.multiPath.enabled) {
|
|
496
|
+
this.features.multiPath.closeAll();
|
|
497
|
+
} else if (this.connection) {
|
|
498
|
+
this.connection.disconnect();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
this.connected = false;
|
|
502
|
+
this.authenticated = false;
|
|
503
|
+
this.sessionId = null;
|
|
504
|
+
|
|
505
|
+
this.emit('disconnected');
|
|
506
|
+
this.logger.info('Disconnected');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Get statistics
|
|
511
|
+
*/
|
|
512
|
+
getStats() {
|
|
513
|
+
const uptime = this.stats.connectedAt
|
|
514
|
+
? Date.now() - this.stats.connectedAt
|
|
515
|
+
: 0;
|
|
516
|
+
|
|
517
|
+
const stats = {
|
|
518
|
+
...this.stats,
|
|
519
|
+
uptime,
|
|
520
|
+
connected: this.connected,
|
|
521
|
+
authenticated: this.authenticated
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// Add multi-path stats if enabled
|
|
525
|
+
if (this.features.multiPath.enabled) {
|
|
526
|
+
stats.multiPath = this.features.multiPath.getStats();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return stats;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Resolve DNS through VPN tunnel
|
|
534
|
+
*/
|
|
535
|
+
async resolveDNS(domain, type = 'A') {
|
|
536
|
+
if (this.features.dnsOverride.enabled) {
|
|
537
|
+
return await this.features.dnsOverride.resolve(domain, type);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
throw new Error('DNS override not enabled');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
module.exports = YakshaClient;
|