@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.
@@ -0,0 +1,551 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Yaksha VPN Server
5
+ * High-performance VPN server with advanced features
6
+ */
7
+
8
+ const net = require('net');
9
+ const dgram = require('dgram');
10
+ const EventEmitter = require('events');
11
+ const crypto = require('crypto');
12
+
13
+ const Protocol = require('../core/protocol');
14
+ const Encryption = require('../core/encryption');
15
+ const Authentication = require('../security/auth');
16
+ const SecurityLevels = require('../security/levels');
17
+ const Config = require('../utils/config');
18
+ const Logger = require('../utils/logger');
19
+
20
+ class YakshaServer extends EventEmitter {
21
+ constructor(options = {}) {
22
+ super();
23
+
24
+ // Configuration
25
+ this.config = new Config(options);
26
+ this.port = options.port || 8443;
27
+ this.host = options.host || '0.0.0.0';
28
+ this.securityLevel = options.securityLevel || 'medium';
29
+ this.maxConnections = options.maxConnections || 10000;
30
+ this.authMethod = options.authMethod || 'token';
31
+ this.customSecurityConfig = options.customSecurityConfig || null;
32
+
33
+ // Security configuration
34
+ if (this.securityLevel === 'custom' && this.customSecurityConfig) {
35
+ this.securityConfig = SecurityLevels.createCustomConfig(
36
+ this.customSecurityConfig,
37
+ this.customSecurityConfig.baseLevel || 'medium'
38
+ );
39
+ } else {
40
+ this.securityConfig = SecurityLevels.getConfig(this.securityLevel);
41
+ }
42
+
43
+ // Core components
44
+ this.protocol = new Protocol({
45
+ maxPayloadSize: options.maxPayloadSize || 65535,
46
+ enablePadding: this.securityConfig.obfuscation !== 'none'
47
+ });
48
+
49
+ this.auth = new Authentication(this.authMethod, {
50
+ maxAttempts: 10,
51
+ lockoutDuration: 300000,
52
+ sessionTimeout: 3600000
53
+ });
54
+
55
+ this.logger = new Logger({
56
+ prefix: 'Server',
57
+ level: options.logLevel || 'info'
58
+ });
59
+
60
+ // Server state
61
+ this.tcpServer = null;
62
+ this.udpSocket = null;
63
+ this.clients = new Map(); // sessionId -> client info
64
+ this.running = false;
65
+
66
+ // Statistics
67
+ this.stats = {
68
+ startTime: null,
69
+ connections: 0,
70
+ activeConnections: 0,
71
+ bytesReceived: 0,
72
+ bytesSent: 0,
73
+ packetsReceived: 0,
74
+ packetsSent: 0,
75
+ errors: 0
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Start the VPN server
81
+ */
82
+ async start() {
83
+ if (this.running) {
84
+ throw new Error('Server is already running');
85
+ }
86
+
87
+ this.logger.info(`Starting Yaksha VPN Server v1.1.0`);
88
+ this.logger.info(`Security Level: ${this.securityLevel.toUpperCase()}`);
89
+ this.logger.info(`Port: ${this.port}`);
90
+
91
+ try {
92
+ // Start TCP server (control channel)
93
+ await this._startTCPServer();
94
+
95
+ // Start UDP socket (data channel)
96
+ await this._startUDPSocket();
97
+
98
+ this.running = true;
99
+ this.stats.startTime = Date.now();
100
+
101
+ this.emit('listening', {
102
+ host: this.host,
103
+ port: this.port
104
+ });
105
+
106
+ this.logger.info(`Server listening on ${this.host}:${this.port}`);
107
+ } catch (error) {
108
+ this.logger.error('Failed to start server:', error.message);
109
+ await this.stop();
110
+ throw error;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Start TCP server for control channel
116
+ */
117
+ _startTCPServer() {
118
+ return new Promise((resolve, reject) => {
119
+ this.tcpServer = net.createServer((socket) => {
120
+ this._handleNewConnection(socket);
121
+ });
122
+
123
+ this.tcpServer.on('error', (error) => {
124
+ this.logger.error('TCP server error:', error.message);
125
+ this.stats.errors++;
126
+ this.emit('error', error);
127
+ reject(error);
128
+ });
129
+
130
+ this.tcpServer.listen(this.port, this.host, () => {
131
+ this.logger.debug('TCP server started');
132
+ resolve();
133
+ });
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Start UDP socket for data channel
139
+ */
140
+ _startUDPSocket() {
141
+ return new Promise((resolve, reject) => {
142
+ this.udpSocket = dgram.createSocket('udp4');
143
+
144
+ this.udpSocket.on('message', (data, rinfo) => {
145
+ this._handleUDPData(data, rinfo);
146
+ });
147
+
148
+ this.udpSocket.on('error', (error) => {
149
+ this.logger.error('UDP socket error:', error.message);
150
+ this.stats.errors++;
151
+ this.emit('error', error);
152
+ });
153
+
154
+ this.udpSocket.bind(this.port, this.host, () => {
155
+ this.logger.debug('UDP socket started');
156
+ resolve();
157
+ });
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Handle new TCP connection
163
+ */
164
+ _handleNewConnection(socket) {
165
+ const clientAddress = `${socket.remoteAddress}:${socket.remotePort}`;
166
+ this.logger.info(`New connection from ${clientAddress}`);
167
+
168
+ // Check connection limit
169
+ if (this.clients.size >= this.maxConnections) {
170
+ this.logger.warn(`Connection limit reached, rejecting ${clientAddress}`);
171
+ socket.end();
172
+ return;
173
+ }
174
+
175
+ // Create client session
176
+ const sessionId = crypto.randomInt(1, 0xFFFFFFFF);
177
+ const client = {
178
+ sessionId,
179
+ socket,
180
+ address: socket.remoteAddress,
181
+ port: socket.remotePort,
182
+ authenticated: false,
183
+ encryption: new Encryption(this.securityLevel, {
184
+ customConfig: this.securityLevel === 'custom' ? this.securityConfig : null
185
+ }),
186
+ sequence: 0,
187
+ receivedSequence: 0,
188
+ connectedAt: Date.now(),
189
+ lastActivity: Date.now()
190
+ };
191
+
192
+ // Generate encryption keys
193
+ client.encryption.generateKeys();
194
+
195
+ this.clients.set(sessionId, client);
196
+ this.stats.connections++;
197
+ this.stats.activeConnections++;
198
+
199
+ // Set up socket handlers
200
+ let buffer = Buffer.alloc(0);
201
+
202
+ socket.on('data', (data) => {
203
+ buffer = Buffer.concat([buffer, data]);
204
+
205
+ try {
206
+ while (buffer.length >= 16) { // Minimum header size
207
+ const result = this._handleTCPData(buffer, client);
208
+
209
+ if (result.bytesProcessed === 0) {
210
+ break; // Need more data
211
+ }
212
+
213
+ buffer = buffer.slice(result.bytesProcessed);
214
+ }
215
+ } catch (error) {
216
+ this.logger.error(`Error processing data from ${clientAddress}:`, error.message);
217
+ this.stats.errors++;
218
+ }
219
+ });
220
+
221
+ socket.on('error', (error) => {
222
+ this.logger.error(`Socket error for ${clientAddress}:`, error.message);
223
+ this.stats.errors++;
224
+ });
225
+
226
+ socket.on('close', () => {
227
+ this.logger.info(`Connection closed: ${clientAddress}`);
228
+ this.clients.delete(sessionId);
229
+ this.stats.activeConnections--;
230
+ this.emit('disconnection', sessionId, clientAddress);
231
+ });
232
+
233
+ // Send handshake initiation
234
+ this._sendHandshakeResponse(client);
235
+
236
+ this.emit('connection', sessionId, clientAddress);
237
+ }
238
+
239
+ /**
240
+ * Handle TCP data
241
+ */
242
+ _handleTCPData(buffer, client) {
243
+ try {
244
+ // Parse packet
245
+ const { header, payload, totalSize } = this.protocol.deserialize(buffer);
246
+
247
+ this.stats.packetsReceived++;
248
+ this.stats.bytesReceived += totalSize;
249
+ client.lastActivity = Date.now();
250
+
251
+ // Handle different packet types
252
+ switch (header.type) {
253
+ case Protocol.PACKET_TYPES.HANDSHAKE:
254
+ this._handleHandshake(payload, client);
255
+ break;
256
+
257
+ case Protocol.PACKET_TYPES.DATA:
258
+ this._handleData(payload, client);
259
+ break;
260
+
261
+ case Protocol.PACKET_TYPES.KEEPALIVE:
262
+ this._handleKeepalive(payload, client);
263
+ break;
264
+
265
+ case Protocol.PACKET_TYPES.CLOSE:
266
+ this._handleClose(payload, client);
267
+ break;
268
+
269
+ default:
270
+ this.logger.warn(`Unknown packet type: ${header.type}`);
271
+ }
272
+
273
+ return { bytesProcessed: totalSize };
274
+ } catch (error) {
275
+ // Not enough data or parse error
276
+ return { bytesProcessed: 0 };
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Handle UDP data
282
+ */
283
+ _handleUDPData(data, rinfo) {
284
+ try {
285
+ // Parse packet to extract session ID
286
+ const header = this.protocol.parseHeader(data);
287
+ const client = this.clients.get(header.sessionId);
288
+
289
+ if (!client || !client.authenticated) {
290
+ this.logger.warn(`UDP packet from unauthenticated session: ${header.sessionId}`);
291
+ return;
292
+ }
293
+
294
+ // Process packet
295
+ const { payload } = this.protocol.deserialize(data);
296
+
297
+ this.stats.packetsReceived++;
298
+ this.stats.bytesReceived += data.length;
299
+ client.lastActivity = Date.now();
300
+
301
+ // Handle data packet
302
+ this._handleData(payload, client, 'udp');
303
+
304
+ } catch (error) {
305
+ this.logger.error('Error processing UDP data:', error.message);
306
+ this.stats.errors++;
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Send handshake response
312
+ */
313
+ _sendHandshakeResponse(client) {
314
+ // Send server public key for key exchange
315
+ const handshakeData = JSON.stringify({
316
+ version: '1.1.0',
317
+ sessionId: client.sessionId,
318
+ publicKey: client.encryption.keys.publicKey
319
+ ? client.encryption.keys.publicKey.toString('base64')
320
+ : null,
321
+ securityLevel: this.securityLevel,
322
+ features: this.securityConfig
323
+ });
324
+
325
+ const { packet } = this.protocol.createHandshake(
326
+ client.sessionId,
327
+ client.sequence++,
328
+ Buffer.from(handshakeData, 'utf8')
329
+ );
330
+
331
+ client.socket.write(packet);
332
+ this.stats.packetsSent++;
333
+ this.stats.bytesSent += packet.length;
334
+ }
335
+
336
+ /**
337
+ * Handle handshake
338
+ */
339
+ _handleHandshake(payload, client) {
340
+ try {
341
+ const handshake = JSON.parse(payload.toString('utf8'));
342
+
343
+ // Perform key exchange if client provided public key
344
+ if (handshake.clientPublicKey) {
345
+ const clientPublicKey = Buffer.from(handshake.clientPublicKey, 'base64');
346
+ client.encryption.keyExchange(clientPublicKey);
347
+ }
348
+
349
+ // Authenticate client
350
+ if (handshake.credentials) {
351
+ const authResult = this._authenticateClient(handshake.credentials, client);
352
+
353
+ if (authResult.success) {
354
+ client.authenticated = true;
355
+ this.logger.info(`Client authenticated: session ${client.sessionId}`);
356
+
357
+ // Send authentication success
358
+ this._sendAuthResponse(client, true);
359
+ } else {
360
+ this.logger.warn(`Authentication failed for session ${client.sessionId}`);
361
+ this._sendAuthResponse(client, false, authResult.reason);
362
+
363
+ // Close connection after failed auth
364
+ setTimeout(() => client.socket.end(), 1000);
365
+ }
366
+ }
367
+
368
+ } catch (error) {
369
+ this.logger.error('Error handling handshake:', error.message);
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Authenticate client
375
+ */
376
+ _authenticateClient(credentials, client) {
377
+ // This is a simplified implementation
378
+ // In production, use proper authentication based on auth method
379
+
380
+ switch (this.authMethod) {
381
+ case 'password':
382
+ return this.auth.authenticatePassword(credentials.username, credentials.password);
383
+
384
+ case 'token':
385
+ return this.auth.authenticateToken(credentials.identifier, credentials.token);
386
+
387
+ case 'certificate':
388
+ return this.auth.authenticateCertificate(
389
+ credentials.identifier,
390
+ credentials.certificate,
391
+ credentials.totpToken
392
+ );
393
+
394
+ default:
395
+ return { success: false, reason: 'Unknown auth method' };
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Send authentication response
401
+ */
402
+ _sendAuthResponse(client, success, reason = '') {
403
+ const response = JSON.stringify({ success, reason });
404
+ const { packet } = this.protocol.createData(
405
+ client.sessionId,
406
+ client.sequence++,
407
+ Buffer.from(response, 'utf8')
408
+ );
409
+
410
+ client.socket.write(packet);
411
+ this.stats.packetsSent++;
412
+ this.stats.bytesSent += packet.length;
413
+ }
414
+
415
+ /**
416
+ * Handle data packet
417
+ */
418
+ _handleData(payload, client, protocol = 'tcp') {
419
+ if (!client.authenticated) {
420
+ this.logger.warn(`Data from unauthenticated client: session ${client.sessionId}`);
421
+ return;
422
+ }
423
+
424
+ // Decrypt data if encrypted
425
+ try {
426
+ // In production, decrypt payload here
427
+ // const decrypted = client.encryption.decrypt(...);
428
+
429
+ this.emit('data', {
430
+ sessionId: client.sessionId,
431
+ data: payload,
432
+ protocol
433
+ });
434
+
435
+ } catch (error) {
436
+ this.logger.error('Error handling data:', error.message);
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Handle keepalive
442
+ */
443
+ _handleKeepalive(payload, client) {
444
+ // Respond with keepalive ACK
445
+ const { packet } = this.protocol.createKeepalive(
446
+ client.sessionId,
447
+ client.sequence++
448
+ );
449
+
450
+ client.socket.write(packet);
451
+ this.stats.packetsSent++;
452
+ }
453
+
454
+ /**
455
+ * Handle close
456
+ */
457
+ _handleClose(payload, client) {
458
+ const reason = payload.toString('utf8');
459
+ this.logger.info(`Client closing connection: session ${client.sessionId}, reason: ${reason}`);
460
+ client.socket.end();
461
+ }
462
+
463
+ /**
464
+ * Send data to client
465
+ */
466
+ sendToClient(sessionId, data, protocol = 'tcp') {
467
+ const client = this.clients.get(sessionId);
468
+
469
+ if (!client || !client.authenticated) {
470
+ throw new Error('Client not found or not authenticated');
471
+ }
472
+
473
+ // Encrypt data if needed
474
+ // const encrypted = client.encryption.encrypt(data);
475
+
476
+ const { packet } = this.protocol.createData(
477
+ client.sessionId,
478
+ client.sequence++,
479
+ data
480
+ );
481
+
482
+ if (protocol === 'tcp') {
483
+ client.socket.write(packet);
484
+ } else {
485
+ this.udpSocket.send(packet, client.port, client.address);
486
+ }
487
+
488
+ this.stats.packetsSent++;
489
+ this.stats.bytesSent += packet.length;
490
+ }
491
+
492
+ /**
493
+ * Get server statistics
494
+ */
495
+ getStats() {
496
+ const uptime = this.stats.startTime
497
+ ? Date.now() - this.stats.startTime
498
+ : 0;
499
+
500
+ return {
501
+ ...this.stats,
502
+ uptime,
503
+ clients: this.clients.size
504
+ };
505
+ }
506
+
507
+ /**
508
+ * Stop the server
509
+ */
510
+ async stop() {
511
+ if (!this.running) {
512
+ return;
513
+ }
514
+
515
+ this.logger.info('Stopping server...');
516
+ this.running = false;
517
+
518
+ // Close all client connections
519
+ for (const [sessionId, client] of this.clients.entries()) {
520
+ try {
521
+ const { packet } = this.protocol.createClose(
522
+ client.sessionId,
523
+ client.sequence++,
524
+ 'Server shutting down'
525
+ );
526
+ client.socket.write(packet);
527
+ client.socket.end();
528
+ } catch (error) {
529
+ // Ignore errors during shutdown
530
+ }
531
+ }
532
+
533
+ this.clients.clear();
534
+
535
+ // Close servers
536
+ if (this.tcpServer) {
537
+ await new Promise(resolve => this.tcpServer.close(resolve));
538
+ this.tcpServer = null;
539
+ }
540
+
541
+ if (this.udpSocket) {
542
+ await new Promise(resolve => this.udpSocket.close(resolve));
543
+ this.udpSocket = null;
544
+ }
545
+
546
+ this.emit('stopped');
547
+ this.logger.info('Server stopped');
548
+ }
549
+ }
550
+
551
+ module.exports = YakshaServer;
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Yaksha Buffer Pool - Zero-copy buffer management
5
+ * Pre-allocates buffers to reduce GC pressure and improve performance
6
+ */
7
+
8
+ class BufferPool {
9
+ constructor(options = {}) {
10
+ this.sizes = options.sizes || [64, 256, 1024, 4096, 16384, 65536];
11
+ this.maxPoolSize = options.maxPoolSize || 1000;
12
+ this.pools = new Map();
13
+
14
+ // Initialize pools for each size
15
+ for (const size of this.sizes) {
16
+ this.pools.set(size, []);
17
+ }
18
+
19
+ this.stats = {
20
+ allocations: 0,
21
+ deallocations: 0,
22
+ hits: 0,
23
+ misses: 0
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Find the best pool size for requested size
29
+ */
30
+ _findPoolSize(requestedSize) {
31
+ for (const size of this.sizes) {
32
+ if (size >= requestedSize) {
33
+ return size;
34
+ }
35
+ }
36
+ return null; // Too large for pooling
37
+ }
38
+
39
+ /**
40
+ * Acquire a buffer from the pool
41
+ * @param {number} size - Requested buffer size
42
+ * @param {boolean} unsafe - Use Buffer.allocUnsafe for performance (default: false)
43
+ * @returns {Buffer}
44
+ */
45
+ acquire(size, unsafe = false) {
46
+ this.stats.allocations++;
47
+
48
+ const poolSize = this._findPoolSize(size);
49
+
50
+ // If size is too large or pooling disabled, allocate directly
51
+ if (!poolSize) {
52
+ this.stats.misses++;
53
+ return unsafe ? Buffer.allocUnsafe(size) : Buffer.alloc(size);
54
+ }
55
+
56
+ const pool = this.pools.get(poolSize);
57
+
58
+ // Try to reuse from pool
59
+ if (pool.length > 0) {
60
+ this.stats.hits++;
61
+ const buffer = pool.pop();
62
+ // Return a slice of the exact requested size
63
+ return buffer.slice(0, size);
64
+ }
65
+
66
+ // Pool empty, allocate new buffer
67
+ this.stats.misses++;
68
+ return unsafe ? Buffer.allocUnsafe(poolSize) : Buffer.alloc(poolSize);
69
+ }
70
+
71
+ /**
72
+ * Release a buffer back to the pool
73
+ * @param {Buffer} buffer - Buffer to release
74
+ */
75
+ release(buffer) {
76
+ if (!Buffer.isBuffer(buffer)) {
77
+ return;
78
+ }
79
+
80
+ this.stats.deallocations++;
81
+
82
+ const size = buffer.length;
83
+ const poolSize = this._findPoolSize(size);
84
+
85
+ // Don't pool if size doesn't match or pool is full
86
+ if (!poolSize) {
87
+ return;
88
+ }
89
+
90
+ const pool = this.pools.get(poolSize);
91
+
92
+ if (pool.length < this.maxPoolSize) {
93
+ // Expand buffer back to pool size if it was sliced
94
+ const poolBuffer = buffer.length === poolSize
95
+ ? buffer
96
+ : Buffer.allocUnsafe(poolSize);
97
+
98
+ if (buffer.length !== poolSize) {
99
+ buffer.copy(poolBuffer);
100
+ }
101
+
102
+ pool.push(poolBuffer);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Clear all pools
108
+ */
109
+ clear() {
110
+ for (const pool of this.pools.values()) {
111
+ pool.length = 0;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Get pool statistics
117
+ */
118
+ getStats() {
119
+ const poolStats = {};
120
+ for (const [size, pool] of this.pools.entries()) {
121
+ poolStats[`${size}B`] = pool.length;
122
+ }
123
+
124
+ return {
125
+ ...this.stats,
126
+ hitRate: this.stats.allocations > 0
127
+ ? (this.stats.hits / this.stats.allocations * 100).toFixed(2) + '%'
128
+ : '0%',
129
+ pools: poolStats
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Reset statistics
135
+ */
136
+ resetStats() {
137
+ this.stats = {
138
+ allocations: 0,
139
+ deallocations: 0,
140
+ hits: 0,
141
+ misses: 0
142
+ };
143
+ }
144
+ }
145
+
146
+ // Global buffer pool instance
147
+ const globalPool = new BufferPool();
148
+
149
+ module.exports = BufferPool;
150
+ module.exports.globalPool = globalPool;