@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,268 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Yaksha Protocol Handler
5
+ * Custom protocol for packet parsing and handling
6
+ */
7
+
8
+ const crypto = require('crypto');
9
+ const { globalPool } = require('../utils/buffer-pool');
10
+
11
+ // Packet types
12
+ const PACKET_TYPES = {
13
+ HANDSHAKE: 0x01,
14
+ DATA: 0x02,
15
+ ACK: 0x03,
16
+ CLOSE: 0x04,
17
+ KEEPALIVE: 0x05
18
+ };
19
+
20
+ // Protocol version
21
+ const PROTOCOL_VERSION = 0x01;
22
+
23
+ // Header size (fixed 16 bytes)
24
+ const HEADER_SIZE = 16;
25
+
26
+ class Protocol {
27
+ constructor(options = {}) {
28
+ this.version = PROTOCOL_VERSION;
29
+ this.maxPayloadSize = options.maxPayloadSize || 65535;
30
+ this.enablePadding = options.enablePadding !== false;
31
+ }
32
+
33
+ /**
34
+ * Create a packet header
35
+ * Header structure (16 bytes):
36
+ * - Version (1 byte)
37
+ * - Type (1 byte)
38
+ * - Length (2 bytes) - payload length
39
+ * - Session ID (4 bytes)
40
+ * - Sequence (4 bytes)
41
+ * - Checksum (4 bytes)
42
+ */
43
+ createHeader(type, payloadLength, sessionId, sequence) {
44
+ const header = globalPool.acquire(HEADER_SIZE, true);
45
+
46
+ // Version
47
+ header.writeUInt8(this.version, 0);
48
+
49
+ // Type
50
+ header.writeUInt8(type, 1);
51
+
52
+ // Length (uint16, max 65535)
53
+ header.writeUInt16BE(payloadLength, 2);
54
+
55
+ // Session ID (uint32)
56
+ header.writeUInt32BE(sessionId, 4);
57
+
58
+ // Sequence (uint32)
59
+ header.writeUInt32BE(sequence, 8);
60
+
61
+ // Checksum placeholder (will be filled later)
62
+ header.writeUInt32BE(0, 12);
63
+
64
+ return header;
65
+ }
66
+
67
+ /**
68
+ * Calculate CRC32 checksum
69
+ */
70
+ calculateChecksum(data) {
71
+ // Simple CRC32 implementation
72
+ let crc = 0xFFFFFFFF;
73
+ for (let i = 0; i < data.length; i++) {
74
+ crc ^= data[i];
75
+ for (let j = 0; j < 8; j++) {
76
+ crc = (crc >>> 1) ^ (0xEDB88320 & -(crc & 1));
77
+ }
78
+ }
79
+ return (crc ^ 0xFFFFFFFF) >>> 0;
80
+ }
81
+
82
+ /**
83
+ * Add random padding for traffic obfuscation
84
+ */
85
+ addPadding(data, maxPaddingSize = 255) {
86
+ if (!this.enablePadding) {
87
+ return { data, paddingSize: 0 };
88
+ }
89
+
90
+ const paddingSize = crypto.randomInt(0, Math.min(maxPaddingSize + 1, 256));
91
+ if (paddingSize === 0) {
92
+ return { data, paddingSize: 0 };
93
+ }
94
+
95
+ const padding = crypto.randomBytes(paddingSize);
96
+ const paddedData = Buffer.concat([data, padding]);
97
+
98
+ return { data: paddedData, paddingSize };
99
+ }
100
+
101
+ /**
102
+ * Remove padding from data
103
+ */
104
+ removePadding(data, paddingSize) {
105
+ if (paddingSize === 0) {
106
+ return data;
107
+ }
108
+ return data.slice(0, -paddingSize);
109
+ }
110
+
111
+ /**
112
+ * Serialize packet
113
+ */
114
+ serialize(type, payload, sessionId, sequence) {
115
+ // Add padding to payload
116
+ const { data: paddedPayload, paddingSize } = this.addPadding(payload);
117
+
118
+ // Create header
119
+ const header = this.createHeader(type, paddedPayload.length, sessionId, sequence);
120
+
121
+ // Combine header and payload
122
+ const packet = Buffer.concat([header, paddedPayload]);
123
+
124
+ // Calculate and update checksum (checksum covers header + payload)
125
+ const checksum = this.calculateChecksum(packet);
126
+ packet.writeUInt32BE(checksum, 12);
127
+
128
+ return { packet, paddingSize };
129
+ }
130
+
131
+ /**
132
+ * Parse packet header
133
+ */
134
+ parseHeader(buffer) {
135
+ if (buffer.length < HEADER_SIZE) {
136
+ throw new Error('Buffer too small for header');
137
+ }
138
+
139
+ const version = buffer.readUInt8(0);
140
+ const type = buffer.readUInt8(1);
141
+ const length = buffer.readUInt16BE(2);
142
+ const sessionId = buffer.readUInt32BE(4);
143
+ const sequence = buffer.readUInt32BE(8);
144
+ const checksum = buffer.readUInt32BE(12);
145
+
146
+ // Validate version
147
+ if (version !== this.version) {
148
+ throw new Error(`Unsupported protocol version: ${version}`);
149
+ }
150
+
151
+ // Validate type
152
+ if (!Object.values(PACKET_TYPES).includes(type)) {
153
+ throw new Error(`Invalid packet type: ${type}`);
154
+ }
155
+
156
+ // Validate length
157
+ if (length > this.maxPayloadSize) {
158
+ throw new Error(`Payload too large: ${length}`);
159
+ }
160
+
161
+ return {
162
+ version,
163
+ type,
164
+ length,
165
+ sessionId,
166
+ sequence,
167
+ checksum
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Deserialize packet
173
+ */
174
+ deserialize(buffer, paddingSize = 0) {
175
+ // Parse header
176
+ const header = this.parseHeader(buffer);
177
+
178
+ // Check if we have complete packet
179
+ const expectedSize = HEADER_SIZE + header.length;
180
+ if (buffer.length < expectedSize) {
181
+ throw new Error(`Incomplete packet: expected ${expectedSize}, got ${buffer.length}`);
182
+ }
183
+
184
+ // Verify checksum
185
+ const storedChecksum = header.checksum;
186
+
187
+ // Create a copy of packet and zero out checksum field for verification
188
+ const packetCopy = Buffer.from(buffer.slice(0, expectedSize));
189
+ packetCopy.writeUInt32BE(0, 12);
190
+
191
+ const calculatedChecksum = this.calculateChecksum(packetCopy);
192
+
193
+ if (storedChecksum !== calculatedChecksum) {
194
+ throw new Error('Checksum verification failed');
195
+ }
196
+
197
+ // Extract payload
198
+ let payload = buffer.slice(HEADER_SIZE, expectedSize);
199
+
200
+ // Remove padding if present
201
+ payload = this.removePadding(payload, paddingSize);
202
+
203
+ return {
204
+ header,
205
+ payload,
206
+ totalSize: expectedSize
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Create HANDSHAKE packet
212
+ */
213
+ createHandshake(sessionId, sequence, data) {
214
+ return this.serialize(PACKET_TYPES.HANDSHAKE, data, sessionId, sequence);
215
+ }
216
+
217
+ /**
218
+ * Create DATA packet
219
+ */
220
+ createData(sessionId, sequence, data) {
221
+ return this.serialize(PACKET_TYPES.DATA, data, sessionId, sequence);
222
+ }
223
+
224
+ /**
225
+ * Create ACK packet
226
+ */
227
+ createAck(sessionId, sequence, ackSequence) {
228
+ const ackBuffer = Buffer.allocUnsafe(4);
229
+ ackBuffer.writeUInt32BE(ackSequence, 0);
230
+ return this.serialize(PACKET_TYPES.ACK, ackBuffer, sessionId, sequence);
231
+ }
232
+
233
+ /**
234
+ * Create CLOSE packet
235
+ */
236
+ createClose(sessionId, sequence, reason = '') {
237
+ const reasonBuffer = Buffer.from(reason, 'utf8');
238
+ return this.serialize(PACKET_TYPES.CLOSE, reasonBuffer, sessionId, sequence);
239
+ }
240
+
241
+ /**
242
+ * Create KEEPALIVE packet
243
+ */
244
+ createKeepalive(sessionId, sequence) {
245
+ const timestamp = Buffer.allocUnsafe(8);
246
+ timestamp.writeBigUInt64BE(BigInt(Date.now()), 0);
247
+ return this.serialize(PACKET_TYPES.KEEPALIVE, timestamp, sessionId, sequence);
248
+ }
249
+
250
+ /**
251
+ * Get packet type name
252
+ */
253
+ getTypeName(type) {
254
+ const typeNames = {
255
+ [PACKET_TYPES.HANDSHAKE]: 'HANDSHAKE',
256
+ [PACKET_TYPES.DATA]: 'DATA',
257
+ [PACKET_TYPES.ACK]: 'ACK',
258
+ [PACKET_TYPES.CLOSE]: 'CLOSE',
259
+ [PACKET_TYPES.KEEPALIVE]: 'KEEPALIVE'
260
+ };
261
+ return typeNames[type] || 'UNKNOWN';
262
+ }
263
+ }
264
+
265
+ module.exports = Protocol;
266
+ module.exports.PACKET_TYPES = PACKET_TYPES;
267
+ module.exports.HEADER_SIZE = HEADER_SIZE;
268
+ module.exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
@@ -0,0 +1,403 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Yaksha DNS Override and Tunneling
5
+ * Prevent DNS leaks by tunneling all DNS queries through VPN
6
+ */
7
+
8
+ const dns = require('dns');
9
+ const dgram = require('dgram');
10
+ const EventEmitter = require('events');
11
+ const Logger = require('../utils/logger');
12
+
13
+ // DNS providers
14
+ const DNS_PROVIDERS = {
15
+ CLOUDFLARE: { primary: '1.1.1.1', secondary: '1.0.0.1', doh: 'https://cloudflare-dns.com/dns-query' },
16
+ GOOGLE: { primary: '8.8.8.8', secondary: '8.8.4.4', doh: 'https://dns.google/dns-query' },
17
+ QUAD9: { primary: '9.9.9.9', secondary: '149.112.112.112', doh: 'https://dns.quad9.net/dns-query' },
18
+ OPENDNS: { primary: '208.67.222.222', secondary: '208.67.220.220' }
19
+ };
20
+
21
+ class DNSOverride extends EventEmitter {
22
+ constructor(securityLevel = 'medium', options = {}) {
23
+ super();
24
+
25
+ this.securityLevel = securityLevel;
26
+ this.mode = this._selectMode(securityLevel);
27
+ this.provider = options.provider || 'CLOUDFLARE';
28
+ this.customResolver = options.customResolver || null;
29
+ this.enabled = options.enabled !== false;
30
+ this.cacheTTL = options.cacheTTL || 300000; // 5 minutes
31
+
32
+ this.cache = new Map(); // domain -> { records, timestamp }
33
+ this.logger = new Logger({ prefix: 'DNS' });
34
+ }
35
+
36
+ /**
37
+ * Select DNS mode based on security level
38
+ */
39
+ _selectMode(level) {
40
+ switch (level) {
41
+ case 'low':
42
+ return 'override';
43
+ case 'medium':
44
+ return 'doh';
45
+ case 'high':
46
+ return 'dot-dnssec';
47
+ default:
48
+ return 'doh';
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Get DNS provider configuration
54
+ */
55
+ _getProvider() {
56
+ if (this.customResolver) {
57
+ return this.customResolver;
58
+ }
59
+
60
+ return DNS_PROVIDERS[this.provider] || DNS_PROVIDERS.CLOUDFLARE;
61
+ }
62
+
63
+ /**
64
+ * Check DNS cache
65
+ */
66
+ _checkCache(domain) {
67
+ if (!this.cache.has(domain)) {
68
+ return null;
69
+ }
70
+
71
+ const cached = this.cache.get(domain);
72
+ const now = Date.now();
73
+
74
+ // Check if cache expired
75
+ if (now - cached.timestamp > this.cacheTTL) {
76
+ this.cache.delete(domain);
77
+ return null;
78
+ }
79
+
80
+ return cached.records;
81
+ }
82
+
83
+ /**
84
+ * Update DNS cache
85
+ */
86
+ _updateCache(domain, records) {
87
+ this.cache.set(domain, {
88
+ records,
89
+ timestamp: Date.now()
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Basic DNS override (replace system DNS)
95
+ */
96
+ async _resolveOverride(domain, type = 'A') {
97
+ const provider = this._getProvider();
98
+
99
+ return new Promise((resolve, reject) => {
100
+ const resolver = new dns.Resolver();
101
+ resolver.setServers([provider.primary, provider.secondary]);
102
+
103
+ const method = type === 'A' ? 'resolve4' :
104
+ type === 'AAAA' ? 'resolve6' :
105
+ type === 'MX' ? 'resolveMx' :
106
+ type === 'TXT' ? 'resolveTxt' :
107
+ 'resolve4';
108
+
109
+ resolver[method](domain, (error, records) => {
110
+ if (error) {
111
+ reject(error);
112
+ } else {
113
+ resolve(records);
114
+ }
115
+ });
116
+ });
117
+ }
118
+
119
+ /**
120
+ * DNS over HTTPS (DoH)
121
+ */
122
+ async _resolveDoH(domain, type = 'A') {
123
+ const provider = this._getProvider();
124
+
125
+ if (!provider.doh) {
126
+ throw new Error('Provider does not support DoH');
127
+ }
128
+
129
+ const https = require('https');
130
+ const url = `${provider.doh}?name=${encodeURIComponent(domain)}&type=${type}`;
131
+
132
+ return new Promise((resolve, reject) => {
133
+ https.get(url, {
134
+ headers: {
135
+ 'Accept': 'application/dns-json'
136
+ }
137
+ }, (res) => {
138
+ let data = '';
139
+
140
+ res.on('data', chunk => {
141
+ data += chunk;
142
+ });
143
+
144
+ res.on('end', () => {
145
+ try {
146
+ const response = JSON.parse(data);
147
+
148
+ if (response.Status !== 0) {
149
+ reject(new Error(`DNS query failed: ${response.Status}`));
150
+ return;
151
+ }
152
+
153
+ const records = response.Answer
154
+ ? response.Answer.map(answer => answer.data)
155
+ : [];
156
+
157
+ resolve(records);
158
+ } catch (error) {
159
+ reject(error);
160
+ }
161
+ });
162
+ }).on('error', reject);
163
+ });
164
+ }
165
+
166
+ /**
167
+ * DNS over TLS (DoT) with DNSSEC
168
+ */
169
+ async _resolveDoT(domain, type = 'A') {
170
+ // Simplified DoT implementation
171
+ // In production, use a proper DoT library
172
+
173
+ const tls = require('tls');
174
+ const provider = this._getProvider();
175
+
176
+ return new Promise((resolve, reject) => {
177
+ const socket = tls.connect({
178
+ host: provider.primary,
179
+ port: 853,
180
+ servername: provider.primary
181
+ }, () => {
182
+ // Build DNS query packet (simplified)
183
+ // In production, use proper DNS packet formatting
184
+
185
+ const query = Buffer.from([
186
+ 0x00, 0x1c, // Length
187
+ 0xaa, 0xaa, // Transaction ID
188
+ 0x01, 0x00, // Flags: standard query
189
+ 0x00, 0x01, // Questions: 1
190
+ 0x00, 0x00, // Answer RRs
191
+ 0x00, 0x00, // Authority RRs
192
+ 0x00, 0x00, // Additional RRs
193
+ // Question section would follow
194
+ ]);
195
+
196
+ socket.write(query);
197
+ });
198
+
199
+ socket.on('data', (data) => {
200
+ // Parse DNS response (simplified)
201
+ // For this implementation, fall back to DoH
202
+ socket.end();
203
+ resolve(this._resolveDoH(domain, type));
204
+ });
205
+
206
+ socket.on('error', reject);
207
+
208
+ socket.setTimeout(5000);
209
+ socket.on('timeout', () => {
210
+ socket.destroy();
211
+ reject(new Error('DNS query timeout'));
212
+ });
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Resolve domain name
218
+ */
219
+ async resolve(domain, type = 'A') {
220
+ if (!this.enabled) {
221
+ // Use system DNS
222
+ return new Promise((resolve, reject) => {
223
+ dns.resolve(domain, type, (error, records) => {
224
+ if (error) reject(error);
225
+ else resolve(records);
226
+ });
227
+ });
228
+ }
229
+
230
+ // Check cache
231
+ const cacheKey = `${domain}:${type}`;
232
+ const cached = this._checkCache(cacheKey);
233
+
234
+ if (cached) {
235
+ this.logger.debug(`DNS cache hit for ${domain}`);
236
+ return cached;
237
+ }
238
+
239
+ this.logger.debug(`Resolving ${domain} (${type}) via ${this.mode}`);
240
+
241
+ try {
242
+ let records;
243
+
244
+ switch (this.mode) {
245
+ case 'override':
246
+ records = await this._resolveOverride(domain, type);
247
+ break;
248
+
249
+ case 'doh':
250
+ records = await this._resolveDoH(domain, type);
251
+ break;
252
+
253
+ case 'dot-dnssec':
254
+ records = await this._resolveDoT(domain, type);
255
+ break;
256
+
257
+ default:
258
+ records = await this._resolveDoH(domain, type);
259
+ }
260
+
261
+ // Update cache
262
+ this._updateCache(cacheKey, records);
263
+
264
+ this.emit('resolved', domain, type, records);
265
+ return records;
266
+
267
+ } catch (error) {
268
+ this.logger.error(`DNS resolution failed for ${domain}:`, error.message);
269
+ this.emit('error', domain, error);
270
+ throw error;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Reverse DNS lookup
276
+ */
277
+ async reverse(ip) {
278
+ if (!this.enabled) {
279
+ return new Promise((resolve, reject) => {
280
+ dns.reverse(ip, (error, hostnames) => {
281
+ if (error) reject(error);
282
+ else resolve(hostnames);
283
+ });
284
+ });
285
+ }
286
+
287
+ // Check cache
288
+ const cached = this._checkCache(`reverse:${ip}`);
289
+ if (cached) {
290
+ return cached;
291
+ }
292
+
293
+ try {
294
+ const hostnames = await this._resolveDoH(ip, 'PTR');
295
+ this._updateCache(`reverse:${ip}`, hostnames);
296
+ return hostnames;
297
+ } catch (error) {
298
+ this.logger.error(`Reverse DNS lookup failed for ${ip}:`, error.message);
299
+ throw error;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Lookup (resolve A or AAAA records)
305
+ */
306
+ async lookup(domain, options = {}) {
307
+ const family = options.family || 4;
308
+ const type = family === 6 ? 'AAAA' : 'A';
309
+
310
+ try {
311
+ const records = await this.resolve(domain, type);
312
+
313
+ if (records.length === 0) {
314
+ throw new Error(`No ${type} records found for ${domain}`);
315
+ }
316
+
317
+ // Return first record
318
+ return {
319
+ address: records[0],
320
+ family
321
+ };
322
+ } catch (error) {
323
+ if (family === 4) {
324
+ // Try IPv6 as fallback
325
+ try {
326
+ const ipv6Records = await this.resolve(domain, 'AAAA');
327
+ if (ipv6Records.length > 0) {
328
+ return {
329
+ address: ipv6Records[0],
330
+ family: 6
331
+ };
332
+ }
333
+ } catch (ipv6Error) {
334
+ // Ignore IPv6 error
335
+ }
336
+ }
337
+ throw error;
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Clear DNS cache
343
+ */
344
+ clearCache() {
345
+ this.cache.clear();
346
+ this.logger.info('DNS cache cleared');
347
+ }
348
+
349
+ /**
350
+ * Get cache statistics
351
+ */
352
+ getCacheStats() {
353
+ const now = Date.now();
354
+ let valid = 0;
355
+ let expired = 0;
356
+
357
+ for (const [key, entry] of this.cache.entries()) {
358
+ if (now - entry.timestamp > this.cacheTTL) {
359
+ expired++;
360
+ } else {
361
+ valid++;
362
+ }
363
+ }
364
+
365
+ return {
366
+ total: this.cache.size,
367
+ valid,
368
+ expired,
369
+ ttl: this.cacheTTL
370
+ };
371
+ }
372
+
373
+ /**
374
+ * Enable DNS override
375
+ */
376
+ enable() {
377
+ this.enabled = true;
378
+ this.logger.info('DNS override enabled');
379
+ }
380
+
381
+ /**
382
+ * Disable DNS override
383
+ */
384
+ disable() {
385
+ this.enabled = false;
386
+ this.logger.info('DNS override disabled');
387
+ }
388
+
389
+ /**
390
+ * Get statistics
391
+ */
392
+ getStats() {
393
+ return {
394
+ enabled: this.enabled,
395
+ mode: this.mode,
396
+ provider: this.provider,
397
+ cache: this.getCacheStats()
398
+ };
399
+ }
400
+ }
401
+
402
+ module.exports = DNSOverride;
403
+ module.exports.DNS_PROVIDERS = DNS_PROVIDERS;