@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,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;
|