@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.
- package/dist/audit.d.ts +10 -0
- package/dist/audit.js +39 -0
- package/dist/cache-layer.test.d.ts +1 -0
- package/dist/cache-layer.test.js +205 -0
- package/dist/cache-multi-question.test.d.ts +1 -0
- package/dist/cache-multi-question.test.js +187 -0
- package/dist/dns-handler.d.ts +27 -0
- package/dist/dns-handler.js +323 -0
- package/dist/dns-service.d.ts +45 -0
- package/dist/dns-service.js +546 -0
- package/dist/dns-service.test.d.ts +1 -0
- package/dist/dns-service.test.js +306 -0
- package/dist/dns-wireformat.test.d.ts +1 -0
- package/dist/dns-wireformat.test.js +669 -0
- package/dist/firewall.d.ts +9 -0
- package/dist/firewall.js +62 -0
- package/dist/http-body-forwarding-integration.test.d.ts +1 -0
- package/dist/http-body-forwarding-integration.test.js +318 -0
- package/dist/http-body-forwarding.test.d.ts +1 -0
- package/dist/http-body-forwarding.test.js +84 -0
- package/dist/http-handler.d.ts +21 -0
- package/dist/http-handler.js +429 -0
- package/dist/http-proxy.d.ts +14 -0
- package/dist/http-proxy.js +135 -0
- package/dist/http-proxy.test.d.ts +1 -0
- package/dist/http-proxy.test.js +375 -0
- package/{src/index.ts → dist/index.d.ts} +1 -1
- package/dist/index.js +10 -0
- package/dist/rate-limiter.d.ts +11 -0
- package/dist/rate-limiter.js +33 -0
- package/dist/rate-limiter.test.d.ts +1 -0
- package/dist/rate-limiter.test.js +149 -0
- package/dist/schema.d.ts +12 -0
- package/dist/schema.js +124 -0
- package/dist/schema.test.d.ts +1 -0
- package/dist/schema.test.js +586 -0
- package/dist/sni-proxy.d.ts +12 -0
- package/dist/sni-proxy.js +141 -0
- package/dist/srv-record.test.d.ts +1 -0
- package/dist/srv-record.test.js +186 -0
- package/dist/tcp-connection-limit.test.d.ts +1 -0
- package/dist/tcp-connection-limit.test.js +89 -0
- package/dist/types.d.ts +145 -0
- package/dist/types.js +34 -0
- package/dist/wildcard-matching.test.d.ts +1 -0
- package/dist/wildcard-matching.test.js +162 -0
- package/package.json +4 -1
- package/src/audit.ts +0 -43
- package/src/cache-layer.test.ts +0 -236
- package/src/cache-multi-question.test.ts +0 -263
- package/src/dns-handler.ts +0 -355
- package/src/dns-service.test.ts +0 -371
- package/src/dns-service.ts +0 -655
- package/src/dns-wireformat.test.ts +0 -771
- package/src/env.d.ts +0 -1
- package/src/firewall.ts +0 -66
- package/src/http-body-forwarding-integration.test.ts +0 -357
- package/src/http-body-forwarding.test.ts +0 -101
- package/src/http-handler.ts +0 -489
- package/src/http-proxy.test.ts +0 -440
- package/src/http-proxy.ts +0 -148
- package/src/rate-limiter.test.ts +0 -144
- package/src/rate-limiter.ts +0 -50
- package/src/schema.test.ts +0 -685
- package/src/schema.ts +0 -137
- package/src/sni-proxy.ts +0 -164
- package/src/srv-record.test.ts +0 -211
- package/src/tcp-connection-limit.test.ts +0 -110
- package/src/types.ts +0 -168
- package/src/wildcard-matching.test.ts +0 -196
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,162 @@
|
|
|
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(0xBEEF);
|
|
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 getAnswerCount(buf) {
|
|
43
|
+
return buf.readUInt16BE(6);
|
|
44
|
+
}
|
|
45
|
+
describe("DevDnsServer - Wildcard Host Matching", () => {
|
|
46
|
+
let server;
|
|
47
|
+
it("resolves subdomains under a wildcard pattern *.loop", () => {
|
|
48
|
+
const config = {
|
|
49
|
+
port: 53,
|
|
50
|
+
dnsCacheMaxSize: 100,
|
|
51
|
+
dnsCacheTtlMs: 0,
|
|
52
|
+
hosts: {
|
|
53
|
+
"*.loop": { records: [{ type: "A", address: "192.168.1.1" }] },
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
server = new DevDnsServer(config);
|
|
57
|
+
const appResp = server.resolve(buildQuery("app.loop", DNS_TYPES.A));
|
|
58
|
+
assert.ok(appResp.length > 0);
|
|
59
|
+
assert.strictEqual(getAnswerCount(appResp), 1);
|
|
60
|
+
const deepResp = server.resolve(buildQuery("deep.sub.loop", DNS_TYPES.A));
|
|
61
|
+
assert.ok(deepResp.length > 0);
|
|
62
|
+
assert.strictEqual(getAnswerCount(deepResp), 1);
|
|
63
|
+
});
|
|
64
|
+
it("resolves wildcard with multiple record types", () => {
|
|
65
|
+
const config = {
|
|
66
|
+
port: 53,
|
|
67
|
+
dnsCacheMaxSize: 100,
|
|
68
|
+
dnsCacheTtlMs: 0,
|
|
69
|
+
hosts: {
|
|
70
|
+
"*.dev": {
|
|
71
|
+
records: [
|
|
72
|
+
{ type: "A", address: "10.0.0.1" },
|
|
73
|
+
{ type: "AAAA", address: "::1" },
|
|
74
|
+
{ type: "TXT", data: ["v=dev"] },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
server = new DevDnsServer(config);
|
|
80
|
+
const aResp = server.resolve(buildQuery("random.dev", DNS_TYPES.A));
|
|
81
|
+
assert.ok(aResp.length > 0);
|
|
82
|
+
assert.strictEqual(getAnswerCount(aResp), 1);
|
|
83
|
+
const aaaaResp = server.resolve(buildQuery("another.dev", DNS_TYPES.AAAA));
|
|
84
|
+
assert.ok(aaaaResp.length > 0);
|
|
85
|
+
assert.strictEqual(getAnswerCount(aaaaResp), 1);
|
|
86
|
+
const txtResp = server.resolve(buildQuery("foo.dev", DNS_TYPES.TXT));
|
|
87
|
+
assert.ok(txtResp.length > 0);
|
|
88
|
+
assert.strictEqual(getAnswerCount(txtResp), 1);
|
|
89
|
+
});
|
|
90
|
+
it("exact hostname takes priority over wildcard", () => {
|
|
91
|
+
const config = {
|
|
92
|
+
port: 53,
|
|
93
|
+
dnsCacheMaxSize: 100,
|
|
94
|
+
dnsCacheTtlMs: 0,
|
|
95
|
+
hosts: {
|
|
96
|
+
"*.loop": { records: [{ type: "A", address: "192.168.1.1" }] },
|
|
97
|
+
"exact.loop": { records: [{ type: "A", address: "10.0.0.100" }] },
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
server = new DevDnsServer(config);
|
|
101
|
+
const exactResp = server.resolve(buildQuery("exact.loop", DNS_TYPES.A));
|
|
102
|
+
assert.ok(exactResp.length > 0);
|
|
103
|
+
assert.strictEqual(getAnswerCount(exactResp), 1);
|
|
104
|
+
const wildResp = server.resolve(buildQuery("other.loop", DNS_TYPES.A));
|
|
105
|
+
assert.ok(wildResp.length > 0);
|
|
106
|
+
assert.strictEqual(getAnswerCount(wildResp), 1);
|
|
107
|
+
});
|
|
108
|
+
it("rejects bare wildcard pattern query (no subdomain)", () => {
|
|
109
|
+
const config = {
|
|
110
|
+
port: 53,
|
|
111
|
+
dnsCacheMaxSize: 100,
|
|
112
|
+
dnsCacheTtlMs: 0,
|
|
113
|
+
hosts: {
|
|
114
|
+
"*.loop": { records: [{ type: "A", address: "192.168.1.1" }] },
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
server = new DevDnsServer(config);
|
|
118
|
+
const bareResp = server.resolve(buildQuery("*.loop", DNS_TYPES.A));
|
|
119
|
+
assert.ok(bareResp.length > 0);
|
|
120
|
+
});
|
|
121
|
+
it("handles deeply nested wildcard matches", () => {
|
|
122
|
+
const config = {
|
|
123
|
+
port: 53,
|
|
124
|
+
dnsCacheMaxSize: 100,
|
|
125
|
+
dnsCacheTtlMs: 0,
|
|
126
|
+
hosts: {
|
|
127
|
+
"*.com": { records: [{ type: "A", address: "8.8.8.8" }] },
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
server = new DevDnsServer(config);
|
|
131
|
+
const deepResp = server.resolve(buildQuery("a.b.c.d.com", DNS_TYPES.A));
|
|
132
|
+
assert.ok(deepResp.length > 0);
|
|
133
|
+
assert.strictEqual(getAnswerCount(deepResp), 1);
|
|
134
|
+
});
|
|
135
|
+
it("is case-insensitive for wildcard matching", () => {
|
|
136
|
+
const config = {
|
|
137
|
+
port: 53,
|
|
138
|
+
dnsCacheMaxSize: 100,
|
|
139
|
+
dnsCacheTtlMs: 0,
|
|
140
|
+
hosts: {
|
|
141
|
+
"*.example": { records: [{ type: "A", address: "1.2.3.4" }] },
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
server = new DevDnsServer(config);
|
|
145
|
+
const upperResp = server.resolve(buildQuery("APP.EXAMPLE", DNS_TYPES.A));
|
|
146
|
+
assert.ok(upperResp.length > 0);
|
|
147
|
+
assert.strictEqual(getAnswerCount(upperResp), 1);
|
|
148
|
+
});
|
|
149
|
+
it("wildcard only matches one label prefix", () => {
|
|
150
|
+
const config = {
|
|
151
|
+
port: 53,
|
|
152
|
+
dnsCacheMaxSize: 100,
|
|
153
|
+
dnsCacheTtlMs: 0,
|
|
154
|
+
hosts: {
|
|
155
|
+
"*.loop": { records: [{ type: "A", address: "5.5.5.5" }] },
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
server = new DevDnsServer(config);
|
|
159
|
+
const oneLabelResp = server.resolve(buildQuery("x.loop", DNS_TYPES.A));
|
|
160
|
+
assert.ok(oneLabelResp.length > 0);
|
|
161
|
+
});
|
|
162
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensecurity/zonzon-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "core routing, dns, and firewall engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Lucian BLETAN <neuraluc@gmail.com>",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
25
28
|
"exports": {
|
|
26
29
|
".": {
|
|
27
30
|
"import": "./dist/index.js",
|
package/src/audit.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { DNS_TYPES } from "./types.js";
|
|
2
|
-
|
|
3
|
-
const REVERSE_DNS_TYPES = Object.fromEntries(Object.entries(DNS_TYPES).map(([k, v]) => [v, k]));
|
|
4
|
-
|
|
5
|
-
export class AuditLogger {
|
|
6
|
-
private isTestEnv = process.argv.includes("--test") || process.env.NODE_ENV === "test";
|
|
7
|
-
|
|
8
|
-
private sanitize(input: any): string {
|
|
9
|
-
return String(input || "").replace(/[\r\n\t]/g, " ").replace(/[^\x20-\x7E]/g, "?");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
public dns(ip: string, questions: any[], rcode: number, cached: boolean = false): void {
|
|
13
|
-
if (this.isTestEnv) return;
|
|
14
|
-
const codeMap: any = { 0: "Found (NOERROR)", 3: "Not Found (NXDOMAIN)", 5: "Blocked by Firewall (REFUSED)" };
|
|
15
|
-
const prefix = cached ? "[Cached] " : "";
|
|
16
|
-
questions.forEach(q => {
|
|
17
|
-
console.log(`[DNS] ${this.sanitize(ip)} | ${prefix}${REVERSE_DNS_TYPES[q.type] || q.type} ${this.sanitize(q.name)} -> ${codeMap[rcode] || rcode}`);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public firewall(ip: string, target: string, action: "ALLOW" | "DENY", detail: string = "") {
|
|
22
|
-
if (this.isTestEnv) return;
|
|
23
|
-
const color = action === "ALLOW" ? "\x1b[32mALLOW\x1b[0m" : "\x1b[31mDENY\x1b[0m";
|
|
24
|
-
console.log(`[FIREWALL] ${this.sanitize(ip)} | ${color} | ${this.sanitize(target)} ${detail ? `(${this.sanitize(detail)})` : ""}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public http(ip: string, method: string, host: string, path: string, status: number, target: string = "") {
|
|
28
|
-
if (this.isTestEnv) return;
|
|
29
|
-
console.log(`[HTTP] ${this.sanitize(ip)} | Returned Status ${status} | ${this.sanitize(method)} ${this.sanitize(host)}${this.sanitize(path)} ${target ? `-> ${this.sanitize(target)}` : ""}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
public system(msg: string) {
|
|
33
|
-
if (this.isTestEnv) return;
|
|
34
|
-
console.log(`[SYSTEM] ${this.sanitize(msg)}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public error(msg: string) {
|
|
38
|
-
if (this.isTestEnv) return;
|
|
39
|
-
console.error(`[ERROR] \x1b[31m${this.sanitize(msg)}\x1b[0m`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const audit = new AuditLogger();
|
package/src/cache-layer.test.ts
DELETED
|
@@ -1,236 +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 } 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(0x5678);
|
|
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 } {
|
|
51
|
-
const flags = buf.readUInt16BE(2);
|
|
52
|
-
const qr = (flags >> 15) & 0x1;
|
|
53
|
-
const rcode = flags & 0xf;
|
|
54
|
-
return { qr, rcode };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function getTtlFromResponse(buf: Buffer): number | null {
|
|
58
|
-
const ancount = buf.readUInt16BE(6);
|
|
59
|
-
if (ancount === 0) return null;
|
|
60
|
-
|
|
61
|
-
let offset = 12;
|
|
62
|
-
while (offset < buf.length && buf[offset] !== 0) {
|
|
63
|
-
const len = buf[offset];
|
|
64
|
-
if ((len & 0xc0) === 0xc0) break;
|
|
65
|
-
offset += len + 1;
|
|
66
|
-
}
|
|
67
|
-
offset += 5;
|
|
68
|
-
|
|
69
|
-
if (offset + 4 > buf.length) return null;
|
|
70
|
-
return buf.readUInt32BE(offset);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
describe("DevDnsServer - Cache Layer", () => {
|
|
74
|
-
let server: DevDnsServer;
|
|
75
|
-
|
|
76
|
-
it("caches responses when cache is enabled with TTL", async () => {
|
|
77
|
-
const config: ServerConfig & { dnsCacheMaxSize?: number; dnsCacheTtlMs?: number } = {
|
|
78
|
-
port: 53,
|
|
79
|
-
dnsCacheMaxSize: 100,
|
|
80
|
-
dnsCacheTtlMs: 5000,
|
|
81
|
-
hosts: {
|
|
82
|
-
"cached.loop": { records: [{ type: "A", address: "192.168.1.1" }] },
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
server = new DevDnsServer(config);
|
|
87
|
-
const queryBuffer = buildQuery("cached.loop", DNS_TYPES.A);
|
|
88
|
-
|
|
89
|
-
const firstResponse = server.resolve(queryBuffer)!;
|
|
90
|
-
assert.ok(firstResponse.length > 0);
|
|
91
|
-
const ttl1 = getTtlFromResponse(firstResponse);
|
|
92
|
-
assert.ok(ttl1 !== null && ttl1 >= 5);
|
|
93
|
-
|
|
94
|
-
await new Promise<void>((resolve) => setTimeout(resolve, 200));
|
|
95
|
-
const secondResponse = server.resolve(queryBuffer)!;
|
|
96
|
-
assert.ok(secondResponse.length > 0);
|
|
97
|
-
|
|
98
|
-
const { qr: qr1, rcode: rc1 } = parseResponseFlags(firstResponse);
|
|
99
|
-
const { qr: qr2, rcode: rc2 } = parseResponseFlags(secondResponse);
|
|
100
|
-
assert.strictEqual(qr1, qr2);
|
|
101
|
-
assert.strictEqual(rc1, rc2);
|
|
102
|
-
|
|
103
|
-
const ttl2 = getTtlFromResponse(secondResponse);
|
|
104
|
-
assert.ok(ttl2 !== null);
|
|
105
|
-
assert.ok(ttl2 <= ttl1!);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("returns expired cache entries as stale", () => {
|
|
109
|
-
const config: ServerConfig & { dnsCacheMaxSize?: number; dnsCacheTtlMs?: number } = {
|
|
110
|
-
port: 53,
|
|
111
|
-
dnsCacheMaxSize: 100,
|
|
112
|
-
dnsCacheTtlMs: 50,
|
|
113
|
-
hosts: {
|
|
114
|
-
"short-lived.loop": { records: [{ type: "A", address: "10.0.0.1" }] },
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
server = new DevDnsServer(config);
|
|
119
|
-
const queryBuffer = buildQuery("short-lived.loop", DNS_TYPES.A);
|
|
120
|
-
|
|
121
|
-
server.resolve(queryBuffer);
|
|
122
|
-
|
|
123
|
-
return new Promise<void>((resolve) => {
|
|
124
|
-
setTimeout(() => {
|
|
125
|
-
const expiredResponse = server.resolve(queryBuffer)!;
|
|
126
|
-
assert.ok(expiredResponse.length > 0);
|
|
127
|
-
const { rcode } = parseResponseFlags(expiredResponse);
|
|
128
|
-
assert.strictEqual(rcode, 0);
|
|
129
|
-
resolve();
|
|
130
|
-
}, 100);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("enforces cache size limit by evicting oldest entries", () => {
|
|
135
|
-
const config: ServerConfig & { dnsCacheMaxSize?: number; dnsCacheTtlMs?: number } = {
|
|
136
|
-
port: 53,
|
|
137
|
-
dnsCacheMaxSize: 3,
|
|
138
|
-
dnsCacheTtlMs: 10000,
|
|
139
|
-
hosts: {
|
|
140
|
-
"one.loop": { records: [{ type: "A", address: "1.1.1.1" }] },
|
|
141
|
-
"two.loop": { records: [{ type: "A", address: "2.2.2.2" }] },
|
|
142
|
-
"three.loop": { records: [{ type: "A", address: "3.3.3.3" }] },
|
|
143
|
-
"four.loop": { records: [{ type: "A", address: "4.4.4.4" }] },
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
server = new DevDnsServer(config);
|
|
148
|
-
|
|
149
|
-
server.resolve(buildQuery("one.loop", DNS_TYPES.A));
|
|
150
|
-
server.resolve(buildQuery("two.loop", DNS_TYPES.A));
|
|
151
|
-
server.resolve(buildQuery("three.loop", DNS_TYPES.A));
|
|
152
|
-
|
|
153
|
-
server.resolve(buildQuery("four.loop", DNS_TYPES.A));
|
|
154
|
-
|
|
155
|
-
const r1 = server.resolve(buildQuery("one.loop", DNS_TYPES.A))!;
|
|
156
|
-
const r4 = server.resolve(buildQuery("four.loop", DNS_TYPES.A))!;
|
|
157
|
-
|
|
158
|
-
assert.ok(r1.length > 0 || r4.length > 0);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("does not cache when cache is disabled (zero TTL)", () => {
|
|
162
|
-
const config: ServerConfig & { dnsCacheMaxSize?: number; dnsCacheTtlMs?: number } = {
|
|
163
|
-
port: 53,
|
|
164
|
-
dnsCacheMaxSize: 100,
|
|
165
|
-
dnsCacheTtlMs: 0,
|
|
166
|
-
hosts: {
|
|
167
|
-
"no-cache.loop": { records: [{ type: "A", address: "9.9.9.9" }] },
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
server = new DevDnsServer(config);
|
|
172
|
-
const queryBuffer = buildQuery("no-cache.loop", DNS_TYPES.A);
|
|
173
|
-
|
|
174
|
-
const resp1 = server.resolve(queryBuffer)!;
|
|
175
|
-
assert.ok(resp1.length > 0);
|
|
176
|
-
|
|
177
|
-
const resp2 = server.resolve(queryBuffer)!;
|
|
178
|
-
assert.ok(resp2.length > 0);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("returns NXDOMAIN for unknown hosts without caching the negative result when cache is enabled", () => {
|
|
182
|
-
const config: ServerConfig & { dnsCacheMaxSize?: number; dnsCacheTtlMs?: number } = {
|
|
183
|
-
port: 53,
|
|
184
|
-
dnsCacheMaxSize: 100,
|
|
185
|
-
dnsCacheTtlMs: 2000,
|
|
186
|
-
hosts: {},
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
server = new DevDnsServer(config);
|
|
190
|
-
const queryBuffer = buildQuery("nonexistent.loop", DNS_TYPES.A);
|
|
191
|
-
|
|
192
|
-
const response = server.resolve(queryBuffer)!;
|
|
193
|
-
assert.ok(response.length > 0);
|
|
194
|
-
const { rcode } = parseResponseFlags(response);
|
|
195
|
-
assert.strictEqual(rcode, 3);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("caches multi-question queries separately per question", () => {
|
|
199
|
-
const config: ServerConfig & { dnsCacheMaxSize?: number; dnsCacheTtlMs?: number } = {
|
|
200
|
-
port: 53,
|
|
201
|
-
dnsCacheMaxSize: 100,
|
|
202
|
-
dnsCacheTtlMs: 5000,
|
|
203
|
-
hosts: {
|
|
204
|
-
"a.loop": { records: [{ type: "A", address: "1.1.1.1" }] },
|
|
205
|
-
"b.loop": { records: [{ type: "A", address: "2.2.2.2" }] },
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
server = new DevDnsServer(config);
|
|
210
|
-
|
|
211
|
-
const encoder = new (class {
|
|
212
|
-
buf = Buffer.alloc(512);
|
|
213
|
-
offset = 0;
|
|
214
|
-
writeUint16(v: number) { this.buf.writeUInt16BE(v, this.offset); this.offset += 2; }
|
|
215
|
-
writeUint8(v: number) { this.buf.writeUInt8(v, this.offset); this.offset += 1; }
|
|
216
|
-
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); }
|
|
217
|
-
finish(): Buffer { return this.buf.subarray(0, this.offset); }
|
|
218
|
-
})();
|
|
219
|
-
|
|
220
|
-
encoder.writeUint16(0xAAAA);
|
|
221
|
-
encoder.writeUint16(0x0100);
|
|
222
|
-
encoder.writeUint16(2);
|
|
223
|
-
encoder.writeUint16(0);
|
|
224
|
-
encoder.writeUint16(0);
|
|
225
|
-
encoder.writeUint16(0);
|
|
226
|
-
encoder.writeDomainName("a.loop");
|
|
227
|
-
encoder.writeUint16(DNS_TYPES.A);
|
|
228
|
-
encoder.writeUint16(1);
|
|
229
|
-
encoder.writeDomainName("b.loop");
|
|
230
|
-
encoder.writeUint16(DNS_TYPES.A);
|
|
231
|
-
encoder.writeUint16(1);
|
|
232
|
-
|
|
233
|
-
const response = server.resolve(encoder.finish())!;
|
|
234
|
-
assert.ok(response.length > 0);
|
|
235
|
-
});
|
|
236
|
-
});
|