@opensecurity/zonzon-core 0.1.1 → 0.1.3

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.
Files changed (71) hide show
  1. package/dist/audit.d.ts +10 -0
  2. package/dist/audit.js +39 -0
  3. package/dist/cache-layer.test.d.ts +1 -0
  4. package/dist/cache-layer.test.js +205 -0
  5. package/dist/cache-multi-question.test.d.ts +1 -0
  6. package/dist/cache-multi-question.test.js +187 -0
  7. package/dist/dns-handler.d.ts +27 -0
  8. package/dist/dns-handler.js +323 -0
  9. package/dist/dns-service.d.ts +45 -0
  10. package/dist/dns-service.js +546 -0
  11. package/dist/dns-service.test.d.ts +1 -0
  12. package/dist/dns-service.test.js +306 -0
  13. package/dist/dns-wireformat.test.d.ts +1 -0
  14. package/dist/dns-wireformat.test.js +669 -0
  15. package/dist/firewall.d.ts +9 -0
  16. package/dist/firewall.js +62 -0
  17. package/dist/http-body-forwarding-integration.test.d.ts +1 -0
  18. package/dist/http-body-forwarding-integration.test.js +318 -0
  19. package/dist/http-body-forwarding.test.d.ts +1 -0
  20. package/dist/http-body-forwarding.test.js +84 -0
  21. package/dist/http-handler.d.ts +21 -0
  22. package/dist/http-handler.js +429 -0
  23. package/dist/http-proxy.d.ts +14 -0
  24. package/dist/http-proxy.js +135 -0
  25. package/dist/http-proxy.test.d.ts +1 -0
  26. package/dist/http-proxy.test.js +375 -0
  27. package/{src/index.ts → dist/index.d.ts} +1 -1
  28. package/dist/index.js +10 -0
  29. package/dist/rate-limiter.d.ts +11 -0
  30. package/dist/rate-limiter.js +33 -0
  31. package/dist/rate-limiter.test.d.ts +1 -0
  32. package/dist/rate-limiter.test.js +149 -0
  33. package/dist/schema.d.ts +12 -0
  34. package/dist/schema.js +124 -0
  35. package/dist/schema.test.d.ts +1 -0
  36. package/dist/schema.test.js +586 -0
  37. package/dist/sni-proxy.d.ts +12 -0
  38. package/dist/sni-proxy.js +141 -0
  39. package/dist/srv-record.test.d.ts +1 -0
  40. package/dist/srv-record.test.js +186 -0
  41. package/dist/tcp-connection-limit.test.d.ts +1 -0
  42. package/dist/tcp-connection-limit.test.js +89 -0
  43. package/dist/types.d.ts +145 -0
  44. package/dist/types.js +34 -0
  45. package/dist/wildcard-matching.test.d.ts +1 -0
  46. package/dist/wildcard-matching.test.js +162 -0
  47. package/package.json +4 -1
  48. package/src/audit.ts +0 -43
  49. package/src/cache-layer.test.ts +0 -236
  50. package/src/cache-multi-question.test.ts +0 -263
  51. package/src/dns-handler.ts +0 -355
  52. package/src/dns-service.test.ts +0 -371
  53. package/src/dns-service.ts +0 -655
  54. package/src/dns-wireformat.test.ts +0 -771
  55. package/src/env.d.ts +0 -1
  56. package/src/firewall.ts +0 -66
  57. package/src/http-body-forwarding-integration.test.ts +0 -357
  58. package/src/http-body-forwarding.test.ts +0 -101
  59. package/src/http-handler.ts +0 -489
  60. package/src/http-proxy.test.ts +0 -440
  61. package/src/http-proxy.ts +0 -148
  62. package/src/rate-limiter.test.ts +0 -144
  63. package/src/rate-limiter.ts +0 -50
  64. package/src/schema.test.ts +0 -685
  65. package/src/schema.ts +0 -137
  66. package/src/sni-proxy.ts +0 -164
  67. package/src/srv-record.test.ts +0 -211
  68. package/src/tcp-connection-limit.test.ts +0 -110
  69. package/src/types.ts +0 -168
  70. package/src/wildcard-matching.test.ts +0 -196
  71. package/tsconfig.json +0 -9
@@ -1,655 +0,0 @@
1
- import { ServerConfig, DNS_TYPES, DNS_CLASSES, RESPONSE_FLAGS, DNS_RCODE } from "./types.js";
2
- import { audit } from "./audit.js";
3
- import { firewallEngine } from "./firewall.js";
4
-
5
- export class DnsWireFormat {
6
- public buf: Buffer;
7
- public offset: number = 0;
8
-
9
- constructor(buffer?: Buffer) {
10
- this.buf = buffer || Buffer.alloc(4096);
11
- }
12
-
13
- writeUint16(val: number): void {
14
- this.buf.writeUInt16BE(val, this.offset);
15
- this.offset += 2;
16
- }
17
-
18
- writeUint8(val: number): void {
19
- this.buf.writeUInt8(val, this.offset);
20
- this.offset += 1;
21
- }
22
-
23
- writeBytes(data: Uint8Array): void {
24
- Buffer.from(data).copy(this.buf, this.offset);
25
- this.offset += data.length;
26
- }
27
-
28
- writeDomainName(name: string): void {
29
- const labels = name.toLowerCase().split(".");
30
- for (const label of labels) {
31
- if (label.length === 0) continue;
32
- this.writeUint8(label.length);
33
- this.writeBytes(Buffer.from(label));
34
- }
35
- this.writeUint8(0);
36
- }
37
-
38
- readUint16(): number {
39
- const val = this.buf.readUInt16BE(this.offset);
40
- this.offset += 2;
41
- return val;
42
- }
43
-
44
- readUint8(): number {
45
- const val = this.buf.readUInt8(this.offset);
46
- this.offset += 1;
47
- return val;
48
- }
49
-
50
- readDomainName(): string {
51
- const labels: string[] = [];
52
- let jumped = false;
53
- let jumpOffset = -1;
54
- let jumps = 0;
55
- const MAX_JUMPS = 5;
56
-
57
- while (true) {
58
- if (jumped && jumpOffset >= 0) {
59
- this.offset = jumpOffset;
60
- jumped = false;
61
- jumpOffset = -1;
62
- }
63
-
64
- if (this.offset >= this.buf.length) {
65
- return labels.join(".");
66
- }
67
-
68
- const len = this.readUint8();
69
- if (len === 0) break;
70
-
71
- if ((len & 0xc0) === 0xc0) {
72
- jumps++;
73
- if (jumps > MAX_JUMPS) {
74
- return labels.join(".");
75
- }
76
- if (this.offset >= this.buf.length) {
77
- return labels.join(".");
78
- }
79
- jumpOffset = ((len & 0x3f) << 8) | this.readUint8();
80
- jumped = true;
81
- continue;
82
- }
83
-
84
- if (len > 63 || this.offset + len > this.buf.length) {
85
- return labels.join(".");
86
- }
87
-
88
- const labelBytes = new Uint8Array(len);
89
- for (let i = 0; i < len; i++) {
90
- labelBytes[i] = this.readUint8();
91
- }
92
- labels.push(new TextDecoder().decode(labelBytes));
93
- }
94
-
95
- return labels.join(".");
96
- }
97
-
98
- readUint32(): number {
99
- const val = this.buf.readUInt32BE(this.offset);
100
- this.offset += 4;
101
- return val;
102
- }
103
-
104
- finish(): Buffer {
105
- return this.buf.subarray(0, this.offset);
106
- }
107
-
108
- writeUint32(val: number): void {
109
- this.buf.writeUInt32BE(val, this.offset);
110
- this.offset += 4;
111
- }
112
- }
113
-
114
- function encodeARecord(address: string): Buffer {
115
- const parts = address.split(".");
116
- return Buffer.from(parts.map((p) => Number(p)));
117
- }
118
-
119
- function encodeAAAARecord(address: string): Buffer {
120
- let expanded = address;
121
- if (expanded.includes("::")) {
122
- const [left, right] = expanded.split("::");
123
- const leftParts = left ? left.split(":") : [];
124
- const rightParts = right ? right.split(":") : [];
125
- const missing = 8 - leftParts.length - rightParts.length;
126
- const parts: string[] = [...leftParts];
127
- for (let i = 0; i < missing; i++) parts.push("0");
128
- parts.push(...rightParts);
129
- expanded = parts.map((p) => p.padStart(4, "0")).join(":");
130
- }
131
- const segments = expanded.split(":");
132
- return Buffer.concat(segments.map((seg) => {
133
- const val = parseInt(seg, 16);
134
- return Buffer.from([Math.floor(val / 256), val % 256]);
135
- }));
136
- }
137
-
138
- function encodeCNAME(target: string): Buffer {
139
- const encoder = new DnsWireFormat();
140
- encoder.writeDomainName(target);
141
- return encoder.finish();
142
- }
143
-
144
- function encodeTXT(data: string[]): Buffer {
145
- const encoder = new DnsWireFormat();
146
- for (const txt of data) {
147
- const bytes = new TextEncoder().encode(txt);
148
- encoder.writeUint8(bytes.length);
149
- encoder.writeBytes(bytes);
150
- }
151
- return encoder.finish();
152
- }
153
-
154
- function encodeMX(priority: number, exchange: string): Buffer {
155
- const encoder = new DnsWireFormat();
156
- encoder.writeUint16(priority);
157
- encoder.writeDomainName(exchange);
158
- return encoder.finish();
159
- }
160
-
161
- function encodeNS(target: string): Buffer {
162
- const encoder = new DnsWireFormat();
163
- encoder.writeDomainName(target);
164
- return encoder.finish();
165
- }
166
-
167
- function encodeSRV(priority: number, weight: number, port: number, target: string): Buffer {
168
- const encoder = new DnsWireFormat();
169
- encoder.writeUint16(priority);
170
- encoder.writeUint16(weight);
171
- encoder.writeUint16(port);
172
- encoder.writeDomainName(target);
173
- return encoder.finish();
174
- }
175
-
176
- function encodePTR(target: string): Buffer {
177
- const encoder = new DnsWireFormat();
178
- encoder.writeDomainName(target);
179
- return encoder.finish();
180
- }
181
-
182
- interface ParsedQuestion {
183
- name: string;
184
- type: number;
185
- nextOffset: number;
186
- }
187
-
188
- function parseQuestion(query: Buffer, baseOffset: number): ParsedQuestion | null {
189
- const decoder = new DnsWireFormat();
190
- decoder.buf = query;
191
- decoder.offset = baseOffset;
192
-
193
- const name = decoder.readDomainName();
194
- if (decoder.offset > query.length - 4) return null;
195
-
196
- const type = decoder.readUint16();
197
- const dclass = decoder.readUint16();
198
-
199
- if (dclass !== DNS_CLASSES.IN && dclass !== 255) return null;
200
-
201
- return { name, type, nextOffset: decoder.offset };
202
- }
203
-
204
- export function extractQuestions(query: Buffer): ParsedQuestion[] {
205
- const qdcount = query.readUInt16BE(4);
206
- const questions: ParsedQuestion[] = [];
207
- let offset = 12;
208
-
209
- for (let i = 0; i < qdcount; i++) {
210
- const question = parseQuestion(query, offset);
211
- if (!question) break;
212
- questions.push(question);
213
- offset = question.nextOffset;
214
- }
215
-
216
- return questions;
217
- }
218
-
219
- function buildResponsePacket(
220
- id: number,
221
- flags: number,
222
- rcode: number,
223
- qdcount: number,
224
- questions: ParsedQuestion[],
225
- answerBuffers: Buffer[]
226
- ): Buffer {
227
- const encoder = new DnsWireFormat();
228
- encoder.buf = Buffer.alloc(4096);
229
-
230
- const combinedFlags = (flags & ~0x8000) | RESPONSE_FLAGS.QR | RESPONSE_FLAGS.AA | rcode;
231
-
232
- encoder.writeUint16(id);
233
- encoder.writeUint16(combinedFlags);
234
- encoder.writeUint16(qdcount);
235
-
236
- const answerCountOffset = encoder.offset;
237
- encoder.buf.writeUInt16BE(0, answerCountOffset);
238
- encoder.offset += 2;
239
- encoder.writeUint16(0);
240
- encoder.writeUint16(0);
241
-
242
- for (const q of questions) {
243
- encoder.writeDomainName(q.name);
244
- encoder.writeUint16(q.type);
245
- encoder.writeUint16(DNS_CLASSES.IN);
246
- }
247
-
248
- let ancount = 0;
249
- for (const answer of answerBuffers) {
250
- if (answer.length < 12) continue;
251
-
252
- const ansDecoder = new DnsWireFormat();
253
- ansDecoder.buf = answer;
254
- ansDecoder.offset = 0;
255
-
256
- const recordType = ansDecoder.readUint16();
257
- const recordClass = ansDecoder.readUint16();
258
- const ttl = ansDecoder.readUint32();
259
- const rdlength = ansDecoder.readUint16();
260
-
261
- encoder.writeUint8(0xc0);
262
- encoder.writeUint8(0x0c);
263
- encoder.writeUint16(recordType);
264
- encoder.writeUint16(recordClass);
265
- encoder.writeUint32(ttl);
266
- encoder.writeUint16(rdlength);
267
-
268
- const rdata = new Uint8Array(ansDecoder.buf.buffer, ansDecoder.buf.byteOffset + ansDecoder.offset, rdlength);
269
- encoder.writeBytes(rdata);
270
- ancount++;
271
- }
272
-
273
- encoder.buf.writeUInt16BE(ancount, answerCountOffset);
274
-
275
- return encoder.finish();
276
- }
277
-
278
- function buildNoErrorResponse(id: number, flags: number, questions: ParsedQuestion[]): Buffer {
279
- const encoder = new DnsWireFormat();
280
- encoder.buf = Buffer.alloc(4096);
281
-
282
- const combinedFlags = (flags & ~0x8000) | RESPONSE_FLAGS.QR | RESPONSE_FLAGS.AA | DNS_RCODE.NOERROR;
283
- const qdcount = questions.length;
284
-
285
- encoder.writeUint16(id);
286
- encoder.writeUint16(combinedFlags);
287
- encoder.writeUint16(qdcount);
288
-
289
- const answerOffsetPlaceholder = encoder.offset;
290
- encoder.buf.writeUInt16BE(0, encoder.offset);
291
- encoder.offset += 2;
292
- encoder.writeUint16(0);
293
- encoder.writeUint16(0);
294
-
295
- for (const q of questions) {
296
- encoder.writeDomainName(q.name);
297
- encoder.writeUint16(q.type);
298
- encoder.writeUint16(DNS_CLASSES.IN);
299
- }
300
-
301
- encoder.buf.writeUInt16BE(0, answerOffsetPlaceholder);
302
- return encoder.finish();
303
- }
304
-
305
- type HostConfig = import("./types.js").HostConfig;
306
- type DnsRecord = import("./types.js").DnsRecord;
307
-
308
- interface CacheEntry {
309
- response: Buffer;
310
- insertedAt: number;
311
- ttlMs: number;
312
- }
313
-
314
- export class DevDnsServer {
315
- private config: ServerConfig;
316
- private cacheMap = new Map<string, CacheEntry>();
317
- private cacheOrder: string[] = [];
318
- private dnsCacheMaxSize: number;
319
- private dnsCacheTtlMs: number;
320
-
321
- constructor(config: ServerConfig) {
322
- this.config = config;
323
- this.dnsCacheMaxSize = config.dnsCacheMaxSize ?? 1024;
324
- this.dnsCacheTtlMs = config.dnsCacheTtlMs ?? 0;
325
- }
326
-
327
- private normalizeHost(name: string): string {
328
- return name.toLowerCase().replace(/\.$/, "");
329
- }
330
-
331
- private findHostConfig(normalizedName: string): HostConfig | undefined {
332
- for (const [key, value] of Object.entries(this.config.hosts)) {
333
- if (key !== "*" && !key.startsWith("*.") && key.toLowerCase() === normalizedName) {
334
- return value;
335
- }
336
- }
337
-
338
- const labels = normalizedName.split(".");
339
- for (let i = 0; i < labels.length; i++) {
340
- const suffix = labels.slice(i).join(".");
341
- const wildcardKey = "*." + suffix;
342
- if (this.config.hosts[wildcardKey]) {
343
- return this.config.hosts[wildcardKey];
344
- }
345
- }
346
-
347
- if (this.config.hosts["*"]) {
348
- return this.config.hosts["*"];
349
- }
350
-
351
- return undefined;
352
- }
353
-
354
- private generateCacheKey(questions: ParsedQuestion[]): string {
355
- return questions.map((q) => `${q.name}:${q.type}`).join("|");
356
- }
357
-
358
- private evictCacheEntry(): void {
359
- if (this.cacheOrder.length === 0) return;
360
- const oldestKey = this.cacheOrder.shift()!;
361
- this.cacheMap.delete(oldestKey);
362
- }
363
-
364
- private getFromCache(key: string): CacheEntry | null {
365
- const entry = this.cacheMap.get(key);
366
- if (!entry) return null;
367
-
368
- if (this.dnsCacheTtlMs > 0 && Date.now() - entry.insertedAt >= this.dnsCacheTtlMs) {
369
- this.cacheMap.delete(key);
370
- const idx = this.cacheOrder.indexOf(key);
371
- if (idx >= 0) this.cacheOrder.splice(idx, 1);
372
- return null;
373
- }
374
-
375
- const idx = this.cacheOrder.indexOf(key);
376
- if (idx >= 0) this.cacheOrder.splice(idx, 1);
377
- this.cacheOrder.push(key);
378
-
379
- const remainingTtlMs = Math.max(0, this.dnsCacheTtlMs - (Date.now() - entry.insertedAt));
380
- entry.ttlMs = remainingTtlMs;
381
-
382
- return entry;
383
- }
384
-
385
- private addToCache(key: string, response: Buffer): void {
386
- if (this.dnsCacheTtlMs <= 0) return;
387
-
388
- while (this.cacheOrder.length >= this.dnsCacheMaxSize) {
389
- this.evictCacheEntry();
390
- }
391
-
392
- const remainingSeconds = Math.max(1, Math.floor(this.dnsCacheTtlMs / 1000));
393
- const modifiedResponse = this.rewriteResponseTtl(response, remainingSeconds);
394
-
395
- const entry: CacheEntry = {
396
- response: modifiedResponse,
397
- insertedAt: Date.now(),
398
- ttlMs: this.dnsCacheTtlMs,
399
- };
400
-
401
- this.cacheMap.set(key, entry);
402
- const idx = this.cacheOrder.indexOf(key);
403
- if (idx >= 0) this.cacheOrder.splice(idx, 1);
404
- this.cacheOrder.push(key);
405
- }
406
-
407
- private skipQuestionSection(buffer: Buffer, offset: number): number {
408
- const savedOffset = offset;
409
- while (offset < buffer.length) {
410
- const len = buffer[offset];
411
- if (len === 0) { offset++; break; }
412
- if ((len & 0xc0) === 0xc0) {
413
- if (offset + 1 >= buffer.length) return savedOffset;
414
- offset += 2;
415
- break;
416
- }
417
- if (offset + 1 + len > buffer.length) return savedOffset;
418
- offset += 1 + len;
419
- }
420
- if (offset + 4 > buffer.length) return savedOffset;
421
- return offset + 4;
422
- }
423
-
424
- private rewriteResponseTtl(response: Buffer, newTtl: number): Buffer {
425
- if (response.length < 12) return response;
426
-
427
- const result = Buffer.alloc(response.length);
428
- response.copy(result, 0);
429
-
430
- const qdcount = response.readUInt16BE(4);
431
- let offset = 12;
432
-
433
- for (let i = 0; i < qdcount; i++) {
434
- const nextOffset = this.skipQuestionSection(response, offset);
435
- if (nextOffset === offset) break;
436
- offset = nextOffset;
437
- }
438
-
439
- const ancount = response.readUInt16BE(6);
440
- for (let i = 0; i < ancount && offset + 12 <= response.length; i++) {
441
- offset += 2;
442
- offset += 2;
443
- offset += 2;
444
- const ttlOffset = offset;
445
- result.writeUInt32BE(newTtl, ttlOffset);
446
- offset += 4;
447
- const rdlength = response.readUInt16BE(offset);
448
- offset += 2 + rdlength;
449
- }
450
-
451
- return result;
452
- }
453
-
454
- private buildSingleAnswer(record: DnsRecord): Buffer {
455
- let rdata: Buffer = Buffer.alloc(0);
456
-
457
- switch (record.type) {
458
- case "A":
459
- rdata = encodeARecord((record as { type: "A"; address: string }).address);
460
- break;
461
- case "AAAA":
462
- rdata = encodeAAAARecord((record as { type: "AAAA"; address: string }).address);
463
- break;
464
- case "CNAME":
465
- rdata = encodeCNAME((record as { type: "CNAME"; target: string }).target);
466
- break;
467
- case "TXT":
468
- rdata = encodeTXT((record as { type: "TXT"; data: string[] }).data);
469
- break;
470
- case "MX":
471
- rdata = encodeMX((record as { type: "MX"; priority: number; exchange: string }).priority, (record as { type: "MX"; priority: number; exchange: string }).exchange);
472
- break;
473
- case "NS":
474
- rdata = encodeNS((record as { type: "NS"; target: string }).target);
475
- break;
476
- case "SRV": {
477
- const srv = record as { type: "SRV"; priority: number; weight: number; port: number; target: string };
478
- rdata = encodeSRV(srv.priority, srv.weight, srv.port, srv.target);
479
- break;
480
- }
481
- }
482
-
483
- const entry = Buffer.alloc(10 + rdata.length);
484
- let pos = 0;
485
- entry.writeUInt16BE(this.toTypeNumber(record.type), pos); pos += 2;
486
- entry.writeUInt16BE(DNS_CLASSES.IN, pos); pos += 2;
487
- entry.writeUInt32BE(300, pos); pos += 4;
488
- entry.writeUInt16BE(rdata.length, pos); pos += 2;
489
- rdata.copy(entry, pos);
490
-
491
- return entry;
492
- }
493
-
494
- private buildAnswers(hostConfig: HostConfig, recordType: number): Buffer[] {
495
- const answers: Buffer[] = [];
496
- for (const record of hostConfig.records) {
497
- if (this.toTypeNumber(record.type) === recordType) {
498
- answers.push(this.buildSingleAnswer(record));
499
- }
500
- }
501
-
502
- return answers;
503
- }
504
-
505
- public generateErrorResponse(query: Buffer, rcode: number): Buffer {
506
- if (query.length < 12) return Buffer.alloc(0);
507
- const id = query.readUInt16BE(0);
508
- const flags = query.readUInt16BE(2);
509
- const questions = extractQuestions(query);
510
-
511
- const encoder = new DnsWireFormat();
512
- encoder.buf = Buffer.alloc(4096);
513
- const combinedFlags = (flags & ~0x8000) | RESPONSE_FLAGS.QR | RESPONSE_FLAGS.AA | rcode;
514
-
515
- encoder.writeUint16(id);
516
- encoder.writeUint16(combinedFlags);
517
- encoder.writeUint16(questions.length);
518
-
519
- const answerOffsetPlaceholder = encoder.offset;
520
- encoder.buf.writeUInt16BE(0, encoder.offset);
521
- encoder.offset += 2;
522
- encoder.writeUint16(0);
523
- encoder.writeUint16(0);
524
-
525
- for (const q of questions) {
526
- encoder.writeDomainName(q.name);
527
- encoder.writeUint16(q.type);
528
- encoder.writeUint16(DNS_CLASSES.IN);
529
- }
530
-
531
- encoder.buf.writeUInt16BE(0, answerOffsetPlaceholder);
532
- return encoder.finish();
533
- }
534
-
535
- resolve(query: Buffer, sourceIp: string = "system"): Buffer | null {
536
- if (query.length < 12) {
537
- return Buffer.alloc(0);
538
- }
539
-
540
- const id = query.readUInt16BE(0);
541
- const flags = query.readUInt16BE(2);
542
-
543
- if ((flags & 0x8000) !== 0) {
544
- return Buffer.alloc(0);
545
- }
546
-
547
- if ((flags & 0x0100) === 0) {
548
- return Buffer.alloc(0);
549
- }
550
-
551
- const qdcount = query.readUInt16BE(4);
552
- const ancount = query.readUInt16BE(6);
553
- const nscount = query.readUInt16BE(8);
554
-
555
- if (qdcount === 0 || ancount > 0 || nscount > 0) {
556
- return Buffer.alloc(0);
557
- }
558
-
559
- const questions = extractQuestions(query);
560
-
561
- const cacheKey = this.generateCacheKey(questions);
562
- const cached = this.getFromCache(cacheKey);
563
- if (cached) {
564
- const cachedRcode = cached.response.length >= 4 ? cached.response.readUInt16BE(2) & 0xf : 0;
565
- audit.dns(sourceIp, questions, cachedRcode, true);
566
- return cached.response;
567
- }
568
-
569
- if (questions.length === 0) {
570
- return this.generateErrorResponse(query, DNS_RCODE.NXDOMAIN);
571
- }
572
-
573
- const answers: Buffer[] = [];
574
- let hasAnyMatch = false;
575
- let allQuestionsUnknown = true;
576
-
577
- for (const question of questions) {
578
- const normalizedName = this.normalizeHost(question.name);
579
- let hostConfig = this.findHostConfig(normalizedName);
580
-
581
- if (!hostConfig) continue;
582
-
583
- allQuestionsUnknown = false;
584
-
585
- const recordAnswers = this.buildAnswers(hostConfig, question.type);
586
- if (recordAnswers.length > 0) {
587
- answers.push(...recordAnswers);
588
- hasAnyMatch = true;
589
- }
590
- }
591
-
592
- if (!hasAnyMatch && allQuestionsUnknown) {
593
- let allowed = true;
594
- for (const q of questions) {
595
- if (firewallEngine.evaluateDomain(q.name, this.config.firewall) === "DENY") {
596
- allowed = false;
597
- break;
598
- }
599
- }
600
-
601
- if (!allowed) {
602
- audit.firewall(sourceIp, questions.map(q => q.name).join(", "), "DENY", "Domain Blocked");
603
- return this.generateErrorResponse(query, DNS_RCODE.REFUSED);
604
- }
605
-
606
- if (!this.config.fallbackDns) {
607
- const response = this.generateErrorResponse(query, DNS_RCODE.NXDOMAIN);
608
- this.addToCache(cacheKey, response);
609
- audit.dns(sourceIp, questions, DNS_RCODE.NXDOMAIN, false);
610
- return response;
611
- }
612
-
613
- return null;
614
- }
615
-
616
- if (answers.length > 0) {
617
- const response = buildResponsePacket(id, flags, DNS_RCODE.NOERROR, questions.length, questions, answers);
618
- this.addToCache(cacheKey, response);
619
- audit.dns(sourceIp, questions, DNS_RCODE.NOERROR, false);
620
- return response;
621
- }
622
-
623
- if (!allQuestionsUnknown) {
624
- const response = buildNoErrorResponse(id, flags, questions);
625
- this.addToCache(cacheKey, response);
626
- audit.dns(sourceIp, questions, DNS_RCODE.NOERROR, false);
627
- return response;
628
- }
629
-
630
- return null;
631
- }
632
-
633
- hasRecord(name: string, type: number): boolean {
634
- const normalizedName = this.normalizeHost(name);
635
- const hostConfig = this.findHostConfig(normalizedName);
636
- if (!hostConfig) return false;
637
-
638
- return hostConfig.records.some((r) => this.toTypeNumber(r.type) === type);
639
- }
640
-
641
- private toTypeNumber(recordType: string): number {
642
- const upper = recordType.toUpperCase();
643
- switch (upper) {
644
- case "A": return DNS_TYPES.A;
645
- case "AAAA": return DNS_TYPES.AAAA;
646
- case "CNAME": return DNS_TYPES.CNAME;
647
- case "TXT": return DNS_TYPES.TXT;
648
- case "MX": return DNS_TYPES.MX;
649
- case "NS": return DNS_TYPES.NS;
650
- case "SRV": return DNS_TYPES.SRV;
651
- case "PTR": return DNS_TYPES.PTR;
652
- default: return 0;
653
- }
654
- }
655
- }