@opensecurity/zonzon-core 0.1.2 → 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,771 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "assert";
3
- import { DevDnsServer } from "./dns-service.js";
4
- import { ServerConfig, DNS_TYPES, DNS_RCODE } from "./types.js";
5
-
6
- function buildQuery(name: string, type: number): Buffer {
7
- const encoder = new (class {
8
- buf = Buffer.alloc(256);
9
- offset = 0;
10
-
11
- writeUint16(v: number) {
12
- this.buf.writeUInt16BE(v, this.offset);
13
- this.offset += 2;
14
- }
15
-
16
- writeUint8(v: number) {
17
- this.buf.writeUInt8(v, this.offset);
18
- this.offset += 1;
19
- }
20
-
21
- writeDomainName(nm: string) {
22
- for (const label of nm.split(".")) {
23
- if (label.length === 0) continue;
24
- this.writeUint8(label.length);
25
- Buffer.from(label).copy(this.buf, this.offset);
26
- this.offset += label.length;
27
- }
28
- this.writeUint8(0);
29
- }
30
-
31
- finish(): Buffer {
32
- return this.buf.subarray(0, this.offset);
33
- }
34
- })();
35
-
36
- encoder.writeUint16(0x1234);
37
- encoder.writeUint16(0x0100);
38
- encoder.writeUint16(1);
39
- encoder.writeUint16(0);
40
- encoder.writeUint16(0);
41
- encoder.writeUint16(0);
42
-
43
- encoder.writeDomainName(name);
44
- encoder.writeUint16(type);
45
- encoder.writeUint16(1);
46
-
47
- return encoder.finish();
48
- }
49
-
50
- function parseResponseFlags(buf: Buffer): { qr: number; rcode: number; id: number } {
51
- const id = buf.readUInt16BE(0);
52
- const flags = buf.readUInt16BE(2);
53
- const qr = (flags >> 15) & 0x1;
54
- const rcode = flags & 0xf;
55
- return { id, qr, rcode };
56
- }
57
-
58
- function getAnswerCount(buf: Buffer): number {
59
- return buf.readUInt16BE(6);
60
- }
61
-
62
- describe("DevDnsServer - NS Record Tests", () => {
63
- let server: DevDnsServer;
64
-
65
- it("returns NS record when configured", () => {
66
- const config: ServerConfig = {
67
- port: 53,
68
- hosts: {
69
- "ns.loop": {
70
- records: [{ type: "NS", target: "ns1.ns.loop" }],
71
- },
72
- },
73
- };
74
-
75
- server = new DevDnsServer(config);
76
- const queryBuffer = buildQuery("ns.loop", DNS_TYPES.NS);
77
- const response = server.resolve(queryBuffer)!;
78
-
79
- assert.ok(response.length > 0);
80
- const { qr, rcode } = parseResponseFlags(response);
81
- assert.strictEqual(qr, 1);
82
- assert.strictEqual(rcode, DNS_RCODE.NOERROR);
83
-
84
- const ancount = getAnswerCount(response);
85
- assert.ok(ancount >= 1);
86
- });
87
-
88
- it("does not return NS record for A-type query on NS-configured host", () => {
89
- const config: ServerConfig = {
90
- port: 53,
91
- hosts: {
92
- "ns-only.loop": {
93
- records: [{ type: "NS", target: "ns1.ns-only.loop" }],
94
- },
95
- },
96
- };
97
-
98
- server = new DevDnsServer(config);
99
- const queryBuffer = buildQuery("ns-only.loop", DNS_TYPES.A);
100
- const response = server.resolve(queryBuffer)!;
101
-
102
- assert.ok(response.length > 0);
103
- const { rcode } = parseResponseFlags(response);
104
- assert.strictEqual(rcode, DNS_RCODE.NOERROR);
105
- assert.strictEqual(getAnswerCount(response), 0);
106
- });
107
- });
108
-
109
- describe("DevDnsServer - PTR Record Tests", () => {
110
- let server: DevDnsServer;
111
-
112
- it("returns PTR record when configured", () => {
113
- const config: ServerConfig = {
114
- port: 53,
115
- hosts: {
116
- "1.0.0.127.in-addr.arpa": {
117
- records: [{ type: "PTR", target: "localhost" }],
118
- },
119
- },
120
- };
121
-
122
- server = new DevDnsServer(config);
123
- const queryBuffer = buildQuery("1.0.0.127.in-addr.arpa", DNS_TYPES.PTR);
124
- const response = server.resolve(queryBuffer)!;
125
-
126
- assert.ok(response.length > 0);
127
- const { qr, rcode } = parseResponseFlags(response);
128
- assert.strictEqual(qr, 1);
129
- assert.strictEqual(rcode, DNS_RCODE.NOERROR);
130
- });
131
-
132
- it("handles PTR with IP reversal lookup name", () => {
133
- const config: ServerConfig = {
134
- port: 53,
135
- hosts: {
136
- "20.0.168.192.in-addr.arpa": {
137
- records: [{ type: "PTR", target: "host.example.com" }],
138
- },
139
- },
140
- };
141
-
142
- server = new DevDnsServer(config);
143
- const queryBuffer = buildQuery("20.0.168.192.in-addr.arpa", DNS_TYPES.PTR);
144
- const response = server.resolve(queryBuffer)!;
145
-
146
- assert.ok(response.length > 0);
147
- const { rcode } = parseResponseFlags(response);
148
- assert.strictEqual(rcode, DNS_RCODE.NOERROR);
149
- });
150
- });
151
-
152
- describe("DevDnsServer - Multi-Question Queries", () => {
153
- let server: DevDnsServer;
154
-
155
- it("returns answers for all matching questions in a multi-question query", () => {
156
- const config: ServerConfig = {
157
- port: 53,
158
- hosts: {
159
- "host1.loop": { records: [{ type: "A", address: "10.0.0.1" }] },
160
- "host2.loop": { records: [{ type: "A", address: "10.0.0.2" }] },
161
- },
162
- };
163
-
164
- server = new DevDnsServer(config);
165
-
166
- const encoder = new (class {
167
- buf = Buffer.alloc(512);
168
- offset = 0;
169
-
170
- writeUint16(v: number) {
171
- this.buf.writeUInt16BE(v, this.offset);
172
- this.offset += 2;
173
- }
174
-
175
- writeUint8(v: number) {
176
- this.buf.writeUInt8(v, this.offset);
177
- this.offset += 1;
178
- }
179
-
180
- writeDomainName(nm: string) {
181
- for (const label of nm.split(".")) {
182
- if (label.length === 0) continue;
183
- this.writeUint8(label.length);
184
- Buffer.from(label).copy(this.buf, this.offset);
185
- this.offset += label.length;
186
- }
187
- this.writeUint8(0);
188
- }
189
-
190
- finish(): Buffer {
191
- return this.buf.subarray(0, this.offset);
192
- }
193
- })();
194
-
195
- encoder.writeUint16(0xabcd);
196
- encoder.writeUint16(0x0100);
197
- encoder.writeUint16(2);
198
- encoder.writeUint16(0);
199
- encoder.writeUint16(0);
200
- encoder.writeUint16(0);
201
-
202
- encoder.writeDomainName("host1.loop");
203
- encoder.writeUint16(DNS_TYPES.A);
204
- encoder.writeUint16(1);
205
-
206
- encoder.writeDomainName("host2.loop");
207
- encoder.writeUint16(DNS_TYPES.A);
208
- encoder.writeUint16(1);
209
-
210
- const response = server.resolve(encoder.finish())!;
211
- assert.ok(response.length > 0);
212
- const { qr, rcode } = parseResponseFlags(response);
213
- assert.strictEqual(qr, 1);
214
- assert.strictEqual(rcode, DNS_RCODE.NOERROR);
215
- const ancount = getAnswerCount(response);
216
- assert.ok(ancount >= 1);
217
- });
218
-
219
- it("returns NXDOMAIN when all questions in multi-query are unknown", () => {
220
- const config: ServerConfig = {
221
- port: 53,
222
- hosts: {},
223
- };
224
-
225
- server = new DevDnsServer(config);
226
-
227
- const encoder = new (class {
228
- buf = Buffer.alloc(512);
229
- offset = 0;
230
-
231
- writeUint16(v: number) { this.buf.writeUInt16BE(v, this.offset); this.offset += 2; }
232
- writeUint8(v: number) { this.buf.writeUInt8(v, this.offset); this.offset += 1; }
233
- writeDomainName(nm: string) { for (const label of nm.split(".")) { if (label.length === 0) continue; this.writeUint8(label.length); Buffer.from(label).copy(this.buf, this.offset); this.offset += label.length; } this.writeUint8(0); }
234
- finish(): Buffer { return this.buf.subarray(0, this.offset); }
235
- })();
236
-
237
- encoder.writeUint16(0xffff);
238
- encoder.writeUint16(0x0100);
239
- encoder.writeUint16(2);
240
- encoder.writeUint16(0);
241
- encoder.writeUint16(0);
242
- encoder.writeUint16(0);
243
-
244
- encoder.writeDomainName("unknown1.loop");
245
- encoder.writeUint16(DNS_TYPES.A);
246
- encoder.writeUint16(1);
247
-
248
- encoder.writeDomainName("unknown2.loop");
249
- encoder.writeUint16(DNS_TYPES.A);
250
- encoder.writeUint16(1);
251
-
252
- const response = server.resolve(encoder.finish())!;
253
- assert.ok(response.length > 0);
254
- const { rcode } = parseResponseFlags(response);
255
- assert.strictEqual(rcode, DNS_RCODE.NXDOMAIN);
256
- });
257
- });
258
-
259
- describe("DevDnsServer - Oversized Packet Protection", () => {
260
- let server: DevDnsServer;
261
-
262
- it("handles oversized queries without crashing", () => {
263
- const config: ServerConfig = {
264
- port: 53,
265
- hosts: {
266
- "test.loop": { records: [{ type: "A", address: "127.0.0.1" }] },
267
- },
268
- };
269
-
270
- server = new DevDnsServer(config);
271
-
272
- const hugeQuery = Buffer.alloc(65535, 0x41);
273
-
274
- hugeQuery.writeUInt16BE(0x9999, 0);
275
- hugeQuery.writeUInt16BE(0x0100, 2);
276
- hugeQuery.writeUInt16BE(1, 4);
277
-
278
- const response = server.resolve(hugeQuery)!;
279
- assert.ok(response.length === 0 || response.length >= 12);
280
- });
281
-
282
- it("handles queries with maximum label length (63 chars)", () => {
283
- const longLabel = "a".repeat(63);
284
- const config: ServerConfig = {
285
- port: 53,
286
- hosts: {
287
- [`${longLabel}.loop`]: { records: [{ type: "A", address: "127.0.0.1" }] },
288
- },
289
- };
290
-
291
- server = new DevDnsServer(config);
292
- const queryBuffer = buildQuery(`${longLabel}.loop`, DNS_TYPES.A);
293
- const response = server.resolve(queryBuffer)!;
294
-
295
- assert.ok(response.length > 0);
296
- const { rcode } = parseResponseFlags(response);
297
- assert.strictEqual(rcode, DNS_RCODE.NOERROR);
298
- });
299
-
300
- it("handles very long domain names without crashing", () => {
301
- const config: ServerConfig = {
302
- port: 53,
303
- hosts: {},
304
- };
305
-
306
- server = new DevDnsServer(config);
307
-
308
- const encoder = new (class {
309
- buf = Buffer.alloc(65535);
310
- offset = 0;
311
-
312
- writeUint16(v: number) { this.buf.writeUInt16BE(v, this.offset); this.offset += 2; }
313
- writeUint8(v: number) { this.buf.writeUInt8(v, this.offset); this.offset += 1; }
314
- writeDomainName(nm: string) { for (const label of nm.split(".")) { if (label.length === 0) continue; this.writeUint8(label.length); Buffer.from(label).copy(this.buf, this.offset); this.offset += label.length; } this.writeUint8(0); }
315
- finish(): Buffer { return this.buf.subarray(0, this.offset); }
316
- })();
317
-
318
- encoder.writeUint16(0xaaaa);
319
- encoder.writeUint16(0x0100);
320
- encoder.writeUint16(1);
321
- encoder.writeUint16(0);
322
- encoder.writeUint16(0);
323
- encoder.writeUint16(0);
324
-
325
- const longDomain = "a".repeat(100) + ".b".repeat(100) + ".c".repeat(100);
326
- encoder.writeDomainName(longDomain);
327
- encoder.writeUint16(DNS_TYPES.A);
328
- encoder.writeUint16(1);
329
-
330
- const response = server.resolve(encoder.finish())!;
331
- assert.ok(response.length === 0 || response.length >= 12);
332
- });
333
- });
334
-
335
- describe("DevDnsServer - hasRecord()", () => {
336
- let server: DevDnsServer;
337
-
338
- it("returns true for existing record type", () => {
339
- const config: ServerConfig = {
340
- port: 53,
341
- hosts: {
342
- "test.loop": { records: [{ type: "A", address: "127.0.0.1" }] },
343
- },
344
- };
345
-
346
- server = new DevDnsServer(config);
347
- assert.strictEqual(server.hasRecord("test.loop", DNS_TYPES.A), true);
348
- });
349
-
350
- it("returns false for non-existing record type on existing host", () => {
351
- const config: ServerConfig = {
352
- port: 53,
353
- hosts: {
354
- "test.loop": { records: [{ type: "A", address: "127.0.0.1" }] },
355
- },
356
- };
357
-
358
- server = new DevDnsServer(config);
359
- assert.strictEqual(server.hasRecord("test.loop", DNS_TYPES.NS), false);
360
- });
361
-
362
- it("returns false for unknown host", () => {
363
- const config: ServerConfig = {
364
- port: 53,
365
- hosts: {
366
- "test.loop": { records: [{ type: "A", address: "127.0.0.1" }] },
367
- },
368
- };
369
-
370
- server = new DevDnsServer(config);
371
- assert.strictEqual(server.hasRecord("unknown.loop", DNS_TYPES.A), false);
372
- });
373
-
374
- it("is case-insensitive for hostname matching", () => {
375
- const config: ServerConfig = {
376
- port: 53,
377
- hosts: {
378
- "Test.Loop": { records: [{ type: "A", address: "127.0.0.1" }] },
379
- },
380
- };
381
-
382
- server = new DevDnsServer(config);
383
- assert.strictEqual(server.hasRecord("test.loop", DNS_TYPES.A), true);
384
- assert.strictEqual(server.hasRecord("TEST.LOOP", DNS_TYPES.A), true);
385
- });
386
-
387
- it("handles trailing dot in hostname for hasRecord", () => {
388
- const config: ServerConfig = {
389
- port: 53,
390
- hosts: {
391
- "test.loop": { records: [{ type: "A", address: "127.0.0.1" }] },
392
- },
393
- };
394
-
395
- server = new DevDnsServer(config);
396
- assert.strictEqual(server.hasRecord("test.loop.", DNS_TYPES.A), true);
397
- });
398
-
399
- it("returns false for empty records array host", () => {
400
- const config: ServerConfig = {
401
- port: 53,
402
- hosts: {
403
- "empty.loop": { records: [] },
404
- },
405
- };
406
-
407
- server = new DevDnsServer(config);
408
- assert.strictEqual(server.hasRecord("empty.loop", DNS_TYPES.A), false);
409
- });
410
-
411
- it("returns correct type number for all record types", () => {
412
- const config: ServerConfig = {
413
- port: 53,
414
- hosts: {},
415
- };
416
-
417
- server = new DevDnsServer(config);
418
- assert.strictEqual(server.hasRecord("x", DNS_TYPES.A), false);
419
- assert.strictEqual(server.hasRecord("x", DNS_TYPES.NS), false);
420
- assert.strictEqual(server.hasRecord("x", DNS_TYPES.CNAME), false);
421
- assert.strictEqual(server.hasRecord("x", DNS_TYPES.TXT), false);
422
- assert.strictEqual(server.hasRecord("x", DNS_TYPES.MX), false);
423
- assert.strictEqual(server.hasRecord("x", DNS_TYPES.PTR), false);
424
- assert.strictEqual(server.hasRecord("x", DNS_TYPES.AAAA), false);
425
- assert.strictEqual(server.hasRecord("x", DNS_TYPES.SRV), false);
426
- });
427
- });
428
-
429
- describe("DevDnsServer - Mixed Resolution Scenarios", () => {
430
- let server: DevDnsServer;
431
-
432
- it("returns NOERROR with answers when one host has matching records and another is unknown", () => {
433
- const config: ServerConfig = {
434
- port: 53,
435
- hosts: {
436
- "known.loop": { records: [{ type: "A", address: "10.0.0.1" }] },
437
- },
438
- };
439
-
440
- server = new DevDnsServer(config);
441
-
442
- const encoder = new (class {
443
- buf = Buffer.alloc(512);
444
- offset = 0;
445
- writeUint16(v: number) { this.buf.writeUInt16BE(v, this.offset); this.offset += 2; }
446
- writeUint8(v: number) { this.buf.writeUInt8(v, this.offset); this.offset += 1; }
447
- writeDomainName(nm: string) { for (const label of nm.split(".")) { if (label.length === 0) continue; this.writeUint8(label.length); Buffer.from(label).copy(this.buf, this.offset); this.offset += label.length; } this.writeUint8(0); }
448
- finish(): Buffer { return this.buf.subarray(0, this.offset); }
449
- })();
450
-
451
- encoder.writeUint16(0xdead);
452
- encoder.writeUint16(0x0100);
453
- encoder.writeUint16(2);
454
- encoder.writeUint16(0);
455
- encoder.writeUint16(0);
456
- encoder.writeUint16(0);
457
-
458
- encoder.writeDomainName("known.loop");
459
- encoder.writeUint16(DNS_TYPES.A);
460
- encoder.writeUint16(1);
461
-
462
- encoder.writeDomainName("unknown.loop");
463
- encoder.writeUint16(DNS_TYPES.A);
464
- encoder.writeUint16(1);
465
-
466
- const response = server.resolve(encoder.finish())!;
467
- assert.ok(response.length > 0);
468
- const { qr } = parseResponseFlags(response);
469
- assert.strictEqual(qr, 1);
470
- });
471
-
472
- it("returns NXDOMAIN when host exists but wrong record type requested", () => {
473
- const config: ServerConfig = {
474
- port: 53,
475
- hosts: {
476
- "only-a.loop": { records: [{ type: "A", address: "10.0.0.1" }] },
477
- },
478
- };
479
-
480
- server = new DevDnsServer(config);
481
- const queryBuffer = buildQuery("only-a.loop", DNS_TYPES.MX);
482
- const response = server.resolve(queryBuffer)!;
483
-
484
- assert.ok(response.length > 0);
485
- const { rcode } = parseResponseFlags(response);
486
- assert.strictEqual(rcode, DNS_RCODE.NOERROR);
487
- assert.strictEqual(getAnswerCount(response), 0);
488
- });
489
-
490
- it("handles CNAME + A records returning both", () => {
491
- const config: ServerConfig = {
492
- port: 53,
493
- hosts: {
494
- "alias.loop": {
495
- records: [
496
- { type: "CNAME", target: "target.loop" },
497
- { type: "A", address: "127.0.0.1" },
498
- ],
499
- },
500
- },
501
- };
502
-
503
- server = new DevDnsServer(config);
504
-
505
- const cnameQuery = buildQuery("alias.loop", DNS_TYPES.CNAME);
506
- const cnameResponse = server.resolve(cnameQuery)!;
507
- assert.ok(cnameResponse.length > 0);
508
- assert.strictEqual(getAnswerCount(cnameResponse), 1);
509
-
510
- const aQuery = buildQuery("alias.loop", DNS_TYPES.A);
511
- const aResponse = server.resolve(aQuery)!;
512
- assert.ok(aResponse.length > 0);
513
- assert.strictEqual(getAnswerCount(aResponse), 1);
514
- });
515
- });
516
-
517
- describe("DevDnsServer - Response ID Preservation", () => {
518
- let server: DevDnsServer;
519
-
520
- it("preserves query ID in response for different ID values", () => {
521
- const config: ServerConfig = {
522
- port: 53,
523
- hosts: {
524
- "id-test.loop": { records: [{ type: "A", address: "127.0.0.1" }] },
525
- },
526
- };
527
-
528
- server = new DevDnsServer(config);
529
-
530
- for (const testId of [0x0001, 0x00FF, 0x8000, 0xFFFF, 0xABCD, 0x0000]) {
531
- const encoder = new (class {
532
- buf = Buffer.alloc(256);
533
- offset = 0;
534
- writeUint16(v: number) { this.buf.writeUInt16BE(v, this.offset); this.offset += 2; }
535
- writeUint8(v: number) { this.buf.writeUInt8(v, this.offset); this.offset += 1; }
536
- writeDomainName(nm: string) { for (const label of nm.split(".")) { if (label.length === 0) continue; this.writeUint8(label.length); Buffer.from(label).copy(this.buf, this.offset); this.offset += label.length; } this.writeUint8(0); }
537
- finish(): Buffer { return this.buf.subarray(0, this.offset); }
538
- })();
539
-
540
- encoder.writeUint16(testId);
541
- encoder.writeUint16(0x0100);
542
- encoder.writeUint16(1);
543
- encoder.writeUint16(0);
544
- encoder.writeUint16(0);
545
- encoder.writeUint16(0);
546
-
547
- encoder.writeDomainName("id-test.loop");
548
- encoder.writeUint16(DNS_TYPES.A);
549
- encoder.writeUint16(1);
550
-
551
- const response = server.resolve(encoder.finish())!;
552
- assert.ok(response.length > 0);
553
- const { id } = parseResponseFlags(response);
554
- assert.strictEqual(id, testId);
555
- }
556
- });
557
-
558
- it("preserves NXDOMAIN response ID", () => {
559
- const config: ServerConfig = {
560
- port: 53,
561
- hosts: {},
562
- };
563
-
564
- server = new DevDnsServer(config);
565
- const encoder = new (class {
566
- buf = Buffer.alloc(256);
567
- offset = 0;
568
- writeUint16(v: number) { this.buf.writeUInt16BE(v, this.offset); this.offset += 2; }
569
- writeUint8(v: number) { this.buf.writeUInt8(v, this.offset); this.offset += 1; }
570
- writeDomainName(nm: string) { for (const label of nm.split(".")) { if (label.length === 0) continue; this.writeUint8(label.length); Buffer.from(label).copy(this.buf, this.offset); this.offset += label.length; } this.writeUint8(0); }
571
- finish(): Buffer { return this.buf.subarray(0, this.offset); }
572
- })();
573
-
574
- encoder.writeUint16(0xCafe);
575
- encoder.writeUint16(0x0100);
576
- encoder.writeUint16(1);
577
- encoder.writeUint16(0);
578
- encoder.writeUint16(0);
579
- encoder.writeUint16(0);
580
-
581
- encoder.writeDomainName("ghost.loop");
582
- encoder.writeUint16(DNS_TYPES.A);
583
- encoder.writeUint16(1);
584
-
585
- const response = server.resolve(encoder.finish())!;
586
- assert.strictEqual(parseResponseFlags(response).id, 0xCafe);
587
- });
588
- });
589
-
590
- describe("DevDnsServer - Edge Case DNS Queries", () => {
591
- let server: DevDnsServer;
592
-
593
- it("handles query with exactly header size buffer (12 bytes)", () => {
594
- const config: ServerConfig = {
595
- port: 53,
596
- hosts: {},
597
- };
598
-
599
- server = new DevDnsServer(config);
600
-
601
- const minimalQuery = Buffer.alloc(12);
602
- minimalQuery.writeUInt16BE(0x1111, 0);
603
- minimalQuery.writeUInt16BE(0x0100, 2);
604
- minimalQuery.writeUInt16BE(0, 4);
605
-
606
- const response = server.resolve(minimalQuery)!;
607
- assert.strictEqual(response.length, 0);
608
- });
609
-
610
- it("handles single-byte query after header", () => {
611
- const config: ServerConfig = {
612
- port: 53,
613
- hosts: {},
614
- };
615
-
616
- server = new DevDnsServer(config);
617
-
618
- const truncatedQuery = Buffer.alloc(13);
619
- truncatedQuery.writeUInt16BE(0x2222, 0);
620
- truncatedQuery.writeUInt16BE(0x0100, 2);
621
- truncatedQuery.writeUInt16BE(1, 4);
622
-
623
- const response = server.resolve(truncatedQuery)!;
624
- assert.ok(response.length === 0 || response.length >= 12);
625
- });
626
-
627
- it("handles DNS compression pointer at offset 0 without hanging", () => {
628
- const config: ServerConfig = {
629
- port: 53,
630
- hosts: {},
631
- };
632
-
633
- server = new DevDnsServer(config);
634
-
635
- const maliciousQuery = Buffer.alloc(20);
636
- maliciousQuery.writeUInt16BE(0x3333, 0);
637
- maliciousQuery.writeUInt16BE(0x0100, 2);
638
- maliciousQuery.writeUInt16BE(1, 4);
639
- maliciousQuery[12] = 0xC0;
640
- maliciousQuery[13] = 0x00;
641
- maliciousQuery.writeUInt16BE(DNS_TYPES.A, 14);
642
- maliciousQuery.writeUInt16BE(1, 16);
643
-
644
- const response = server.resolve(maliciousQuery)!;
645
- assert.ok(response.length === 0 || response.length >= 12);
646
- if (response.length > 0) {
647
- const { qr } = parseResponseFlags(response);
648
- assert.strictEqual(qr, 1);
649
- }
650
- });
651
-
652
- it("handles query with only empty labels", () => {
653
- const config: ServerConfig = {
654
- port: 53,
655
- hosts: {},
656
- };
657
-
658
- server = new DevDnsServer(config);
659
-
660
- const rootQuery = Buffer.alloc(17);
661
- rootQuery.writeUInt16BE(0x4444, 0);
662
- rootQuery.writeUInt16BE(0x0100, 2);
663
- rootQuery.writeUInt16BE(1, 4);
664
- rootQuery[12] = 0;
665
- rootQuery.writeUInt16BE(DNS_TYPES.A, 13);
666
- rootQuery.writeUInt16BE(1, 15);
667
-
668
- const response = server.resolve(rootQuery)!;
669
- assert.ok(response.length === 0 || response.length >= 12);
670
- });
671
- });
672
-
673
- describe("DevDnsServer - SRV Record Edge Cases", () => {
674
- let server: DevDnsServer;
675
-
676
- it("handles SRV record with priority 0", () => {
677
- const config: ServerConfig = {
678
- port: 53,
679
- hosts: {
680
- "srv-zero.loop": {
681
- records: [{ type: "SRV", priority: 0, weight: 0, port: 80, target: "target.srv-zero.loop" }],
682
- },
683
- },
684
- };
685
-
686
- server = new DevDnsServer(config);
687
- const response = server.resolve(buildQuery("srv-zero.loop", DNS_TYPES.SRV))!;
688
- assert.ok(response.length > 0);
689
- assert.strictEqual(getAnswerCount(response), 1);
690
- });
691
-
692
- it("handles SRV record with maximum port (65535)", () => {
693
- const config: ServerConfig = {
694
- port: 53,
695
- hosts: {
696
- "srv-max.loop": {
697
- records: [{ type: "SRV", priority: 0, weight: 65535, port: 65535, target: "target.srv-max.loop" }],
698
- },
699
- },
700
- };
701
-
702
- server = new DevDnsServer(config);
703
- const response = server.resolve(buildQuery("srv-max.loop", DNS_TYPES.SRV))!;
704
- assert.ok(response.length > 0);
705
- assert.strictEqual(getAnswerCount(response), 1);
706
- });
707
-
708
- it("does not return SRV for A-type query on SRV-only host", () => {
709
- const config: ServerConfig = {
710
- port: 53,
711
- hosts: {
712
- "srv-only.loop": {
713
- records: [{ type: "SRV", priority: 10, weight: 5, port: 8080, target: "target.srv-only.loop" }],
714
- },
715
- },
716
- };
717
-
718
- server = new DevDnsServer(config);
719
- const response = server.resolve(buildQuery("srv-only.loop", DNS_TYPES.A))!;
720
- assert.ok(response.length > 0);
721
- const { rcode } = parseResponseFlags(response);
722
- assert.strictEqual(rcode, DNS_RCODE.NOERROR);
723
- assert.strictEqual(getAnswerCount(response), 0);
724
- });
725
- });
726
-
727
- describe("DevDnsServer - AAAA Record Formats", () => {
728
- let server: DevDnsServer;
729
-
730
- it("handles full IPv6 address (8 groups)", () => {
731
- const config: ServerConfig = {
732
- port: 53,
733
- hosts: {
734
- "full-ipv6.loop": { records: [{ type: "AAAA", address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" }] },
735
- },
736
- };
737
-
738
- server = new DevDnsServer(config);
739
- const response = server.resolve(buildQuery("full-ipv6.loop", DNS_TYPES.AAAA))!;
740
- assert.ok(response.length > 0);
741
- assert.strictEqual(getAnswerCount(response), 1);
742
- });
743
-
744
- it("handles :: shorthand IPv6 address", () => {
745
- const config: ServerConfig = {
746
- port: 53,
747
- hosts: {
748
- "shorthand.loop": { records: [{ type: "AAAA", address: "::ffff:192.168.1.1" }] },
749
- },
750
- };
751
-
752
- server = new DevDnsServer(config);
753
- const response = server.resolve(buildQuery("shorthand.loop", DNS_TYPES.AAAA))!;
754
- assert.ok(response.length > 0);
755
- assert.strictEqual(getAnswerCount(response), 1);
756
- });
757
-
758
- it("handles ::1 loopback IPv6", () => {
759
- const config: ServerConfig = {
760
- port: 53,
761
- hosts: {
762
- "loopback.loop": { records: [{ type: "AAAA", address: "::1" }] },
763
- },
764
- };
765
-
766
- server = new DevDnsServer(config);
767
- const response = server.resolve(buildQuery("loopback.loop", DNS_TYPES.AAAA))!;
768
- assert.ok(response.length > 0);
769
- assert.strictEqual(getAnswerCount(response), 1);
770
- });
771
- });