@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
@@ -0,0 +1,10 @@
1
+ export declare class AuditLogger {
2
+ private isTestEnv;
3
+ private sanitize;
4
+ dns(ip: string, questions: any[], rcode: number, cached?: boolean): void;
5
+ firewall(ip: string, target: string, action: "ALLOW" | "DENY", detail?: string): void;
6
+ http(ip: string, method: string, host: string, path: string, status: number, target?: string): void;
7
+ system(msg: string): void;
8
+ error(msg: string): void;
9
+ }
10
+ export declare const audit: AuditLogger;
package/dist/audit.js ADDED
@@ -0,0 +1,39 @@
1
+ import { DNS_TYPES } from "./types.js";
2
+ const REVERSE_DNS_TYPES = Object.fromEntries(Object.entries(DNS_TYPES).map(([k, v]) => [v, k]));
3
+ export class AuditLogger {
4
+ isTestEnv = process.argv.includes("--test") || process.env.NODE_ENV === "test";
5
+ sanitize(input) {
6
+ return String(input || "").replace(/[\r\n\t]/g, " ").replace(/[^\x20-\x7E]/g, "?");
7
+ }
8
+ dns(ip, questions, rcode, cached = false) {
9
+ if (this.isTestEnv)
10
+ return;
11
+ const codeMap = { 0: "Found (NOERROR)", 3: "Not Found (NXDOMAIN)", 5: "Blocked by Firewall (REFUSED)" };
12
+ const prefix = cached ? "[Cached] " : "";
13
+ questions.forEach(q => {
14
+ console.log(`[DNS] ${this.sanitize(ip)} | ${prefix}${REVERSE_DNS_TYPES[q.type] || q.type} ${this.sanitize(q.name)} -> ${codeMap[rcode] || rcode}`);
15
+ });
16
+ }
17
+ firewall(ip, target, action, detail = "") {
18
+ if (this.isTestEnv)
19
+ return;
20
+ const color = action === "ALLOW" ? "\x1b[32mALLOW\x1b[0m" : "\x1b[31mDENY\x1b[0m";
21
+ console.log(`[FIREWALL] ${this.sanitize(ip)} | ${color} | ${this.sanitize(target)} ${detail ? `(${this.sanitize(detail)})` : ""}`);
22
+ }
23
+ http(ip, method, host, path, status, target = "") {
24
+ if (this.isTestEnv)
25
+ return;
26
+ console.log(`[HTTP] ${this.sanitize(ip)} | Returned Status ${status} | ${this.sanitize(method)} ${this.sanitize(host)}${this.sanitize(path)} ${target ? `-> ${this.sanitize(target)}` : ""}`);
27
+ }
28
+ system(msg) {
29
+ if (this.isTestEnv)
30
+ return;
31
+ console.log(`[SYSTEM] ${this.sanitize(msg)}`);
32
+ }
33
+ error(msg) {
34
+ if (this.isTestEnv)
35
+ return;
36
+ console.error(`[ERROR] \x1b[31m${this.sanitize(msg)}\x1b[0m`);
37
+ }
38
+ }
39
+ export const audit = new AuditLogger();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,205 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "assert";
3
+ import { DevDnsServer } from "./dns-service.js";
4
+ import { DNS_TYPES } from "./types.js";
5
+ function buildQuery(name, type) {
6
+ const encoder = new (class {
7
+ buf = Buffer.alloc(256);
8
+ offset = 0;
9
+ writeUint16(v) {
10
+ this.buf.writeUInt16BE(v, this.offset);
11
+ this.offset += 2;
12
+ }
13
+ writeUint8(v) {
14
+ this.buf.writeUInt8(v, this.offset);
15
+ this.offset += 1;
16
+ }
17
+ writeDomainName(nm) {
18
+ for (const label of nm.split(".")) {
19
+ if (label.length === 0)
20
+ continue;
21
+ this.writeUint8(label.length);
22
+ Buffer.from(label).copy(this.buf, this.offset);
23
+ this.offset += label.length;
24
+ }
25
+ this.writeUint8(0);
26
+ }
27
+ finish() {
28
+ return this.buf.subarray(0, this.offset);
29
+ }
30
+ })();
31
+ encoder.writeUint16(0x5678);
32
+ encoder.writeUint16(0x0100);
33
+ encoder.writeUint16(1);
34
+ encoder.writeUint16(0);
35
+ encoder.writeUint16(0);
36
+ encoder.writeUint16(0);
37
+ encoder.writeDomainName(name);
38
+ encoder.writeUint16(type);
39
+ encoder.writeUint16(1);
40
+ return encoder.finish();
41
+ }
42
+ function parseResponseFlags(buf) {
43
+ const flags = buf.readUInt16BE(2);
44
+ const qr = (flags >> 15) & 0x1;
45
+ const rcode = flags & 0xf;
46
+ return { qr, rcode };
47
+ }
48
+ function getTtlFromResponse(buf) {
49
+ const ancount = buf.readUInt16BE(6);
50
+ if (ancount === 0)
51
+ return null;
52
+ let offset = 12;
53
+ while (offset < buf.length && buf[offset] !== 0) {
54
+ const len = buf[offset];
55
+ if ((len & 0xc0) === 0xc0)
56
+ break;
57
+ offset += len + 1;
58
+ }
59
+ offset += 5;
60
+ if (offset + 4 > buf.length)
61
+ return null;
62
+ return buf.readUInt32BE(offset);
63
+ }
64
+ describe("DevDnsServer - Cache Layer", () => {
65
+ let server;
66
+ it("caches responses when cache is enabled with TTL", async () => {
67
+ const config = {
68
+ port: 53,
69
+ dnsCacheMaxSize: 100,
70
+ dnsCacheTtlMs: 5000,
71
+ hosts: {
72
+ "cached.loop": { records: [{ type: "A", address: "192.168.1.1" }] },
73
+ },
74
+ };
75
+ server = new DevDnsServer(config);
76
+ const queryBuffer = buildQuery("cached.loop", DNS_TYPES.A);
77
+ const firstResponse = server.resolve(queryBuffer);
78
+ assert.ok(firstResponse.length > 0);
79
+ const ttl1 = getTtlFromResponse(firstResponse);
80
+ assert.ok(ttl1 !== null && ttl1 >= 5);
81
+ await new Promise((resolve) => setTimeout(resolve, 200));
82
+ const secondResponse = server.resolve(queryBuffer);
83
+ assert.ok(secondResponse.length > 0);
84
+ const { qr: qr1, rcode: rc1 } = parseResponseFlags(firstResponse);
85
+ const { qr: qr2, rcode: rc2 } = parseResponseFlags(secondResponse);
86
+ assert.strictEqual(qr1, qr2);
87
+ assert.strictEqual(rc1, rc2);
88
+ const ttl2 = getTtlFromResponse(secondResponse);
89
+ assert.ok(ttl2 !== null);
90
+ assert.ok(ttl2 <= ttl1);
91
+ });
92
+ it("returns expired cache entries as stale", () => {
93
+ const config = {
94
+ port: 53,
95
+ dnsCacheMaxSize: 100,
96
+ dnsCacheTtlMs: 50,
97
+ hosts: {
98
+ "short-lived.loop": { records: [{ type: "A", address: "10.0.0.1" }] },
99
+ },
100
+ };
101
+ server = new DevDnsServer(config);
102
+ const queryBuffer = buildQuery("short-lived.loop", DNS_TYPES.A);
103
+ server.resolve(queryBuffer);
104
+ return new Promise((resolve) => {
105
+ setTimeout(() => {
106
+ const expiredResponse = server.resolve(queryBuffer);
107
+ assert.ok(expiredResponse.length > 0);
108
+ const { rcode } = parseResponseFlags(expiredResponse);
109
+ assert.strictEqual(rcode, 0);
110
+ resolve();
111
+ }, 100);
112
+ });
113
+ });
114
+ it("enforces cache size limit by evicting oldest entries", () => {
115
+ const config = {
116
+ port: 53,
117
+ dnsCacheMaxSize: 3,
118
+ dnsCacheTtlMs: 10000,
119
+ hosts: {
120
+ "one.loop": { records: [{ type: "A", address: "1.1.1.1" }] },
121
+ "two.loop": { records: [{ type: "A", address: "2.2.2.2" }] },
122
+ "three.loop": { records: [{ type: "A", address: "3.3.3.3" }] },
123
+ "four.loop": { records: [{ type: "A", address: "4.4.4.4" }] },
124
+ },
125
+ };
126
+ server = new DevDnsServer(config);
127
+ server.resolve(buildQuery("one.loop", DNS_TYPES.A));
128
+ server.resolve(buildQuery("two.loop", DNS_TYPES.A));
129
+ server.resolve(buildQuery("three.loop", DNS_TYPES.A));
130
+ server.resolve(buildQuery("four.loop", DNS_TYPES.A));
131
+ const r1 = server.resolve(buildQuery("one.loop", DNS_TYPES.A));
132
+ const r4 = server.resolve(buildQuery("four.loop", DNS_TYPES.A));
133
+ assert.ok(r1.length > 0 || r4.length > 0);
134
+ });
135
+ it("does not cache when cache is disabled (zero TTL)", () => {
136
+ const config = {
137
+ port: 53,
138
+ dnsCacheMaxSize: 100,
139
+ dnsCacheTtlMs: 0,
140
+ hosts: {
141
+ "no-cache.loop": { records: [{ type: "A", address: "9.9.9.9" }] },
142
+ },
143
+ };
144
+ server = new DevDnsServer(config);
145
+ const queryBuffer = buildQuery("no-cache.loop", DNS_TYPES.A);
146
+ const resp1 = server.resolve(queryBuffer);
147
+ assert.ok(resp1.length > 0);
148
+ const resp2 = server.resolve(queryBuffer);
149
+ assert.ok(resp2.length > 0);
150
+ });
151
+ it("returns NXDOMAIN for unknown hosts without caching the negative result when cache is enabled", () => {
152
+ const config = {
153
+ port: 53,
154
+ dnsCacheMaxSize: 100,
155
+ dnsCacheTtlMs: 2000,
156
+ hosts: {},
157
+ };
158
+ server = new DevDnsServer(config);
159
+ const queryBuffer = buildQuery("nonexistent.loop", DNS_TYPES.A);
160
+ const response = server.resolve(queryBuffer);
161
+ assert.ok(response.length > 0);
162
+ const { rcode } = parseResponseFlags(response);
163
+ assert.strictEqual(rcode, 3);
164
+ });
165
+ it("caches multi-question queries separately per question", () => {
166
+ const config = {
167
+ port: 53,
168
+ dnsCacheMaxSize: 100,
169
+ dnsCacheTtlMs: 5000,
170
+ hosts: {
171
+ "a.loop": { records: [{ type: "A", address: "1.1.1.1" }] },
172
+ "b.loop": { records: [{ type: "A", address: "2.2.2.2" }] },
173
+ },
174
+ };
175
+ server = new DevDnsServer(config);
176
+ const encoder = new (class {
177
+ buf = Buffer.alloc(512);
178
+ offset = 0;
179
+ writeUint16(v) { this.buf.writeUInt16BE(v, this.offset); this.offset += 2; }
180
+ writeUint8(v) { this.buf.writeUInt8(v, this.offset); this.offset += 1; }
181
+ writeDomainName(nm) { for (const label of nm.split(".")) {
182
+ if (label.length === 0)
183
+ continue;
184
+ this.writeUint8(label.length);
185
+ Buffer.from(label).copy(this.buf, this.offset);
186
+ this.offset += label.length;
187
+ } this.writeUint8(0); }
188
+ finish() { return this.buf.subarray(0, this.offset); }
189
+ })();
190
+ encoder.writeUint16(0xAAAA);
191
+ encoder.writeUint16(0x0100);
192
+ encoder.writeUint16(2);
193
+ encoder.writeUint16(0);
194
+ encoder.writeUint16(0);
195
+ encoder.writeUint16(0);
196
+ encoder.writeDomainName("a.loop");
197
+ encoder.writeUint16(DNS_TYPES.A);
198
+ encoder.writeUint16(1);
199
+ encoder.writeDomainName("b.loop");
200
+ encoder.writeUint16(DNS_TYPES.A);
201
+ encoder.writeUint16(1);
202
+ const response = server.resolve(encoder.finish());
203
+ assert.ok(response.length > 0);
204
+ });
205
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,187 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "assert";
3
+ import { DevDnsServer } from "./dns-service.js";
4
+ import { DNS_TYPES, DNS_CLASSES } from "./types.js";
5
+ describe("Multi-Question TTL Cache Rewriting", () => {
6
+ function buildMultiQuestionQuery(names, types) {
7
+ const encoder = new (class {
8
+ buf = Buffer.alloc(1024);
9
+ offset = 0;
10
+ writeUint16(v) {
11
+ this.buf.writeUInt16BE(v, this.offset);
12
+ this.offset += 2;
13
+ }
14
+ writeUint8(v) {
15
+ this.buf.writeUInt8(v, this.offset);
16
+ this.offset += 1;
17
+ }
18
+ writeDomainName(nm) {
19
+ for (const label of nm.split(".")) {
20
+ if (label.length === 0)
21
+ continue;
22
+ this.writeUint8(label.length);
23
+ Buffer.from(label).copy(this.buf, this.offset);
24
+ this.offset += label.length;
25
+ }
26
+ this.writeUint8(0);
27
+ }
28
+ finish() {
29
+ return this.buf.subarray(0, this.offset);
30
+ }
31
+ })();
32
+ encoder.writeUint16(0xCAFE);
33
+ encoder.writeUint16(0x0100);
34
+ encoder.writeUint16(names.length);
35
+ encoder.writeUint16(0);
36
+ encoder.writeUint16(0);
37
+ encoder.writeUint16(0);
38
+ for (let i = 0; i < names.length; i++) {
39
+ encoder.writeDomainName(names[i]);
40
+ encoder.writeUint16(types[i] || DNS_TYPES.A);
41
+ encoder.writeUint16(DNS_CLASSES.IN);
42
+ }
43
+ return encoder.finish();
44
+ }
45
+ function extractAnswerTtls(response) {
46
+ const ttls = [];
47
+ const qdcount = response.readUInt16BE(4);
48
+ let offset = 12;
49
+ for (let i = 0; i < qdcount && offset < response.length; i++) {
50
+ while (offset < response.length) {
51
+ const len = response[offset];
52
+ if (len === 0) {
53
+ offset++;
54
+ break;
55
+ }
56
+ if ((len & 0xc0) === 0xc0) {
57
+ offset += 2;
58
+ break;
59
+ }
60
+ offset += 1 + len;
61
+ }
62
+ offset += 4;
63
+ }
64
+ const ancount = response.readUInt16BE(6);
65
+ for (let i = 0; i < ancount && offset + 8 <= response.length; i++) {
66
+ offset += 2;
67
+ offset += 2;
68
+ offset += 2;
69
+ ttls.push(response.readUInt32BE(offset));
70
+ offset += 4;
71
+ const rdlength = response.readUInt16BE(offset);
72
+ offset += 2 + rdlength;
73
+ }
74
+ return ttls;
75
+ }
76
+ it("rewrites TTL for all answer entries in a cached multi-question response", async () => {
77
+ const config = {
78
+ port: 53,
79
+ dnsCacheMaxSize: 100,
80
+ dnsCacheTtlMs: 10000,
81
+ hosts: {
82
+ "alpha.loop": { records: [{ type: "A", address: "1.1.1.1" }] },
83
+ "beta.loop": { records: [{ type: "A", address: "2.2.2.2" }] },
84
+ },
85
+ };
86
+ const server = new DevDnsServer(config);
87
+ const query = buildMultiQuestionQuery(["alpha.loop", "beta.loop"], [DNS_TYPES.A, DNS_TYPES.A]);
88
+ const response1 = server.resolve(query);
89
+ assert.ok(response1.length > 0);
90
+ const ttlsAfterFirst = extractAnswerTtls(response1);
91
+ assert.strictEqual(ttlsAfterFirst.length, 2);
92
+ assert.ok(ttlsAfterFirst.every((t) => t > 0));
93
+ await new Promise((resolve) => setTimeout(resolve, 200));
94
+ const response2 = server.resolve(query);
95
+ assert.ok(response2.length > 0);
96
+ const ttlsAfterCacheHit = extractAnswerTtls(response2);
97
+ assert.strictEqual(ttlsAfterCacheHit.length, 2);
98
+ const originalTotalTtl = ttlsAfterFirst.reduce((a, b) => a + b, 0);
99
+ const cachedTotalTtl = ttlsAfterCacheHit.reduce((a, b) => a + b, 0);
100
+ assert.ok(cachedTotalTtl < originalTotalTtl);
101
+ assert.ok(ttlsAfterCacheHit.every((t) => t >= 0));
102
+ });
103
+ it("handles three-question multi-query without TTL corruption", async () => {
104
+ const config = {
105
+ port: 53,
106
+ dnsCacheMaxSize: 100,
107
+ dnsCacheTtlMs: 10000,
108
+ hosts: {
109
+ "one.loop": { records: [{ type: "A", address: "1.0.0.1" }] },
110
+ "two.loop": { records: [{ type: "A", address: "1.0.0.2" }] },
111
+ "three.loop": { records: [{ type: "A", address: "1.0.0.3" }] },
112
+ },
113
+ };
114
+ const server = new DevDnsServer(config);
115
+ const query = buildMultiQuestionQuery(["one.loop", "two.loop", "three.loop"], [DNS_TYPES.A, DNS_TYPES.A, DNS_TYPES.A]);
116
+ const response1 = server.resolve(query);
117
+ assert.ok(response1.length > 0);
118
+ const ttlsAfterFirst = extractAnswerTtls(response1);
119
+ assert.strictEqual(ttlsAfterFirst.length, 3);
120
+ await new Promise((resolve) => setTimeout(resolve, 300));
121
+ const response2 = server.resolve(query);
122
+ assert.ok(response2.length > 0);
123
+ const ttlsAfterCache = extractAnswerTtls(response2);
124
+ assert.strictEqual(ttlsAfterCache.length, 3);
125
+ const maxTtl = config.dnsCacheTtlMs / 1000;
126
+ for (const ttl of ttlsAfterCache) {
127
+ assert.ok(ttl >= 0 && ttl <= maxTtl);
128
+ }
129
+ });
130
+ it("does not corrupt question data when rewriting TTL in multi-question response", async () => {
131
+ const config = {
132
+ port: 53,
133
+ dnsCacheMaxSize: 100,
134
+ dnsCacheTtlMs: 10000,
135
+ hosts: {
136
+ "a.loop": { records: [{ type: "A", address: "1.2.3.4" }] },
137
+ "b.loop": { records: [{ type: "A", address: "5.6.7.8" }] },
138
+ },
139
+ };
140
+ const server = new DevDnsServer(config);
141
+ const query = buildMultiQuestionQuery(["a.loop", "b.loop"], [DNS_TYPES.A, DNS_TYPES.A]);
142
+ const originalResponse = server.resolve(query);
143
+ assert.ok(originalResponse.length > 0);
144
+ const originalBytes = Buffer.from(originalResponse);
145
+ await new Promise((resolve) => setTimeout(resolve, 150));
146
+ const cachedResponse = server.resolve(query);
147
+ assert.ok(cachedResponse.length > 0);
148
+ assert.strictEqual(cachedResponse.readUInt16BE(6), originalResponse.readUInt16BE(6));
149
+ assert.strictEqual(cachedResponse.length, originalBytes.length);
150
+ const origQdcount = originalResponse.readUInt16BE(4);
151
+ const cacheQdcount = cachedResponse.readUInt16BE(4);
152
+ assert.strictEqual(origQdcount, cacheQdcount);
153
+ const origAncount = originalResponse.readUInt16BE(6);
154
+ const cacheAncount = cachedResponse.readUInt16BE(6);
155
+ assert.strictEqual(origAncount, cacheAncount);
156
+ const origTtls = extractAnswerTtls(originalResponse);
157
+ const cacheTtls = extractAnswerTtls(cachedResponse);
158
+ for (let i = 0; i < origTtls.length; i++) {
159
+ assert.ok(cacheTtls[i] <= origTtls[i]);
160
+ }
161
+ });
162
+ it("handles multi-question with mismatched record types", async () => {
163
+ const config = {
164
+ port: 53,
165
+ dnsCacheMaxSize: 100,
166
+ dnsCacheTtlMs: 10000,
167
+ hosts: {
168
+ "a.loop": { records: [{ type: "A", address: "1.2.3.4" }] },
169
+ "b.loop": { records: [{ type: "AAAA", address: "::1" }] },
170
+ },
171
+ };
172
+ const server = new DevDnsServer(config);
173
+ const query = buildMultiQuestionQuery(["a.loop", "b.loop"], [DNS_TYPES.A, DNS_TYPES.AAAA]);
174
+ const response1 = server.resolve(query);
175
+ assert.ok(response1.length > 0);
176
+ const ttlsFirst = extractAnswerTtls(response1);
177
+ assert.strictEqual(ttlsFirst.length, 2);
178
+ await new Promise((resolve) => setTimeout(resolve, 100));
179
+ const response2 = server.resolve(query);
180
+ assert.ok(response2.length > 0);
181
+ const ttlsCache = extractAnswerTtls(response2);
182
+ assert.strictEqual(ttlsCache.length, 2);
183
+ for (const ttl of ttlsCache) {
184
+ assert.ok(ttl >= 0);
185
+ }
186
+ });
187
+ });
@@ -0,0 +1,27 @@
1
+ import { DevDnsServer } from "./dns-service.js";
2
+ import { ServerConfig } from "./types.js";
3
+ export declare class DnsHandler {
4
+ private server;
5
+ private udpServer;
6
+ private tcpServer;
7
+ private port;
8
+ private fallbackDns;
9
+ private config;
10
+ private activeTcpConnections;
11
+ private maxTcpConnections;
12
+ private tcpIdleTimeoutMs;
13
+ private rateLimiter;
14
+ constructor(server: DevDnsServer, config: ServerConfig);
15
+ start(): Promise<void>;
16
+ stop(): Promise<void>;
17
+ private startUdp;
18
+ private startTcp;
19
+ private isRateLimited;
20
+ private parseResolvedIpv4s;
21
+ private forwardUdpQuery;
22
+ private handleUdpMessage;
23
+ private removeTcpConnection;
24
+ private forwardTcpQuery;
25
+ private handleTcpConnection;
26
+ getPort(): number;
27
+ }