pending-dns 1.2.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/codeql/codeql-config.yml +11 -0
- package/.github/workflows/codeql.yml +52 -0
- package/.github/workflows/deploy.yml +16 -3
- package/.github/workflows/release.yaml +43 -0
- package/.github/workflows/test.yml +75 -0
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +16 -0
- package/CLAUDE.md +109 -0
- package/README.md +111 -9
- package/SECURITY.md +88 -0
- package/SECURITY.txt +27 -0
- package/bin/pending-dns.js +1 -1
- package/config/default.toml +43 -0
- package/config/test.toml +35 -0
- package/eslint.config.js +38 -0
- package/lib/api-server.js +198 -23
- package/lib/cached-resolver.js +5 -3
- package/lib/certs.js +12 -20
- package/lib/dns-handler.js +362 -32
- package/lib/dns-server.js +120 -43
- package/lib/dns-tcp-server.js +1 -1
- package/lib/dns-udp-server.js +1 -1
- package/lib/dnssec-wire.js +321 -0
- package/lib/dnssec.js +461 -0
- package/lib/lock.js +37 -0
- package/lib/logger.js +3 -0
- package/lib/public-server.js +20 -2
- package/lib/sentry.js +72 -0
- package/lib/tools.js +1 -1
- package/lib/zone-store.js +90 -7
- package/package.json +46 -33
- package/release-please-config.json +14 -0
- package/server.js +5 -24
- package/systemd/pending-dns.service +4 -4
- package/test/api.test.js +231 -0
- package/test/cached-resolver.test.js +57 -0
- package/test/certs.test.js +34 -0
- package/test/dns-handler.test.js +171 -0
- package/test/dns-server.test.js +162 -0
- package/test/dnssec-handler.test.js +550 -0
- package/test/dnssec-wire.test.js +163 -0
- package/test/dnssec.test.js +213 -0
- package/test/helpers.js +27 -0
- package/test/sentry.test.js +21 -0
- package/test/tools.test.js +48 -0
- package/test/zone-store.test.js +245 -0
- package/workers/api.js +3 -1
- package/workers/dns.js +2 -24
- package/workers/health.js +3 -26
- package/workers/public.js +3 -25
- package/.eslintrc +0 -14
- package/Gruntfile.js +0 -16
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
|
|
7
|
+
const { pem2jwk } = require('pem-jwk');
|
|
8
|
+
const certs = require('../lib/certs');
|
|
9
|
+
const { closeDb } = require('./helpers');
|
|
10
|
+
|
|
11
|
+
const { generateKey } = certs.testables;
|
|
12
|
+
|
|
13
|
+
test.after(async () => {
|
|
14
|
+
await closeDb();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('generateKey returns a PKCS#1 RSA private key PEM', async () => {
|
|
18
|
+
const pem = await generateKey(2048);
|
|
19
|
+
assert.match(pem, /^-----BEGIN RSA PRIVATE KEY-----/);
|
|
20
|
+
assert.match(pem, /-----END RSA PRIVATE KEY-----\s*$/);
|
|
21
|
+
|
|
22
|
+
// the key must be loadable by Node's crypto
|
|
23
|
+
const keyObject = crypto.createPrivateKey(pem);
|
|
24
|
+
assert.equal(keyObject.asymmetricKeyType, 'rsa');
|
|
25
|
+
assert.equal(keyObject.asymmetricKeyDetails.modulusLength, 2048);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('generated key converts to a JWK via pem-jwk (as the ACME flow needs)', async () => {
|
|
29
|
+
const pem = await generateKey(2048);
|
|
30
|
+
const jwk = pem2jwk(pem);
|
|
31
|
+
assert.equal(jwk.kty, 'RSA');
|
|
32
|
+
assert.ok(jwk.n, 'modulus present');
|
|
33
|
+
assert.ok(jwk.d, 'private exponent present');
|
|
34
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
const dns2 = require('dns2');
|
|
7
|
+
const Packet = dns2.Packet;
|
|
8
|
+
|
|
9
|
+
const dnsHandler = require('../lib/dns-handler');
|
|
10
|
+
const { zoneStore } = require('../lib/zone-store');
|
|
11
|
+
const { db, flushTestDb, closeDb } = require('./helpers');
|
|
12
|
+
|
|
13
|
+
const { formatTXTData, shuffle, filterUnhealthy, reversedTypes } = dnsHandler.testables;
|
|
14
|
+
|
|
15
|
+
test.after(async () => {
|
|
16
|
+
await closeDb();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Pure helpers
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
test('formatTXTData always returns an array of chunks', () => {
|
|
24
|
+
const short = formatTXTData('hello');
|
|
25
|
+
assert.ok(Array.isArray(short));
|
|
26
|
+
assert.deepEqual(short, ['hello']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('formatTXTData splits long values into <=255 byte chunks that recombine', () => {
|
|
30
|
+
const long = 'x'.repeat(600);
|
|
31
|
+
const chunks = formatTXTData(long);
|
|
32
|
+
assert.ok(Array.isArray(chunks));
|
|
33
|
+
assert.ok(chunks.length > 1);
|
|
34
|
+
for (const chunk of chunks) {
|
|
35
|
+
assert.ok(chunk.length <= 255);
|
|
36
|
+
}
|
|
37
|
+
assert.equal(chunks.join(''), long);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('formatTXTData coerces non-strings', () => {
|
|
41
|
+
assert.deepEqual(formatTXTData(undefined), ['']);
|
|
42
|
+
assert.deepEqual(formatTXTData(null), ['']);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('shuffle returns the same elements (a permutation)', () => {
|
|
46
|
+
const input = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
47
|
+
const out = shuffle(input.slice());
|
|
48
|
+
assert.equal(out.length, input.length);
|
|
49
|
+
assert.deepEqual(
|
|
50
|
+
out.slice().sort((a, b) => a - b),
|
|
51
|
+
input
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('filterUnhealthy drops unhealthy entries when at least one is healthy', () => {
|
|
56
|
+
const list = [
|
|
57
|
+
{ address: '1.1.1.1', health: { status: true } },
|
|
58
|
+
{ address: '2.2.2.2', health: { status: false } },
|
|
59
|
+
{ address: '3.3.3.3' } // no health info -> treated as healthy
|
|
60
|
+
];
|
|
61
|
+
const out = filterUnhealthy(list);
|
|
62
|
+
assert.deepEqual(
|
|
63
|
+
out.map(e => e.address),
|
|
64
|
+
['1.1.1.1', '3.3.3.3']
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('filterUnhealthy returns all entries when none are healthy', () => {
|
|
69
|
+
const list = [
|
|
70
|
+
{ address: '1.1.1.1', health: { status: false } },
|
|
71
|
+
{ address: '2.2.2.2', health: { status: false } }
|
|
72
|
+
];
|
|
73
|
+
const out = filterUnhealthy(list);
|
|
74
|
+
assert.equal(out.length, 2);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('reversedTypes maps numeric DNS types back to strings', () => {
|
|
78
|
+
assert.equal(reversedTypes.get(Packet.TYPE.A), 'A');
|
|
79
|
+
assert.equal(reversedTypes.get(Packet.TYPE.AAAA), 'AAAA');
|
|
80
|
+
assert.equal(reversedTypes.get(Packet.TYPE.MX), 'MX');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Handler integration (Redis backed)
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
const buildRequest = (name, type) => {
|
|
88
|
+
const req = new Packet({});
|
|
89
|
+
req.questions = [{ name, type: Packet.TYPE[type], class: Packet.CLASS.IN }];
|
|
90
|
+
req.source = { type: 'udp', address: '127.0.0.1', port: 5353 };
|
|
91
|
+
return req;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
test('dnsHandler answers an A query from stored records', async () => {
|
|
95
|
+
await flushTestDb();
|
|
96
|
+
await zoneStore.add('example.com', '', 'A', ['9.8.7.6']);
|
|
97
|
+
|
|
98
|
+
const response = await dnsHandler(buildRequest('example.com', 'A'));
|
|
99
|
+
const addresses = response.answers.filter(a => a.type === Packet.TYPE.A).map(a => a.address);
|
|
100
|
+
assert.ok(addresses.includes('9.8.7.6'));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('dnsHandler returns configured NS records when none are stored', async () => {
|
|
104
|
+
await flushTestDb();
|
|
105
|
+
const response = await dnsHandler(buildRequest('example.com', 'NS'));
|
|
106
|
+
const nsAnswers = response.answers.filter(a => a.type === Packet.TYPE.NS);
|
|
107
|
+
assert.ok(nsAnswers.length >= 1, 'should fall back to configured name servers');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('dnsHandler does not synthesise NS below the apex', async () => {
|
|
111
|
+
await flushTestDb();
|
|
112
|
+
// Adding any record registers the example.com zone.
|
|
113
|
+
await zoneStore.add('example.com', 'www', 'A', ['1.2.3.4']);
|
|
114
|
+
const response = await dnsHandler(buildRequest('sub.example.com', 'NS'));
|
|
115
|
+
assert.ok(!response.answers.some(a => a.type === Packet.TYPE.NS), 'a record-less below-apex name is not a delegation, so no NS is synthesised');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('dnsHandler synthesises a SOA record', async () => {
|
|
119
|
+
await flushTestDb();
|
|
120
|
+
const response = await dnsHandler(buildRequest('example.com', 'SOA'));
|
|
121
|
+
const soa = response.answers.find(a => a.type === Packet.TYPE.SOA);
|
|
122
|
+
assert.ok(soa, 'a SOA record should be returned');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('dnsHandler filters unhealthy AAAA records', async () => {
|
|
126
|
+
await flushTestDb();
|
|
127
|
+
// Two AAAA records with health checks; mark one unhealthy in the health store.
|
|
128
|
+
const healthyId = await zoneStore.add('example.com', '', 'AAAA', ['2001:db8::1', 'tcp://check:1']);
|
|
129
|
+
const unhealthyId = await zoneStore.add('example.com', '', 'AAAA', ['2001:db8::2', 'tcp://check:2']);
|
|
130
|
+
assert.ok(healthyId && unhealthyId);
|
|
131
|
+
|
|
132
|
+
// Health results are keyed by "<zone-name>:<record-id>"
|
|
133
|
+
const zoneName = zoneStore.domainToName('example.com');
|
|
134
|
+
await db.redisWrite.hset('d:health:r', `${zoneName}:${unhealthyId}`, JSON.stringify({ status: false }));
|
|
135
|
+
|
|
136
|
+
const response = await dnsHandler(buildRequest('example.com', 'AAAA'));
|
|
137
|
+
const aaaa = response.answers.filter(a => a.type === Packet.TYPE.AAAA);
|
|
138
|
+
assert.equal(aaaa.length, 1, 'the unhealthy AAAA record should be filtered out');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('dnsHandler answers a TLSA query with raw RDATA that round-trips on the wire', async () => {
|
|
142
|
+
await flushTestDb();
|
|
143
|
+
const certHex = '92003ba34942dc74152e2f2c408d29eca5a520e7f2e06bb944f4dca346baf63c';
|
|
144
|
+
await zoneStore.add('example.com', '_443._tcp.www', 'TLSA', [3, 1, 1, certHex]);
|
|
145
|
+
|
|
146
|
+
const req = new Packet({});
|
|
147
|
+
req.questions = [{ name: '_443._tcp.www.example.com', type: 52, class: Packet.CLASS.IN }];
|
|
148
|
+
req.source = { type: 'udp', address: '127.0.0.1', port: 5353 };
|
|
149
|
+
|
|
150
|
+
const response = await dnsHandler(req);
|
|
151
|
+
const tlsa = response.answers.find(a => a.type === 52);
|
|
152
|
+
assert.ok(tlsa, 'a TLSA answer should be returned');
|
|
153
|
+
|
|
154
|
+
// Serialize then reparse to confirm dns2 carries the raw RDATA intact.
|
|
155
|
+
const reparsed = Packet.parse(response.toBuffer());
|
|
156
|
+
const rr = reparsed.answers.find(a => a.type === 52);
|
|
157
|
+
assert.ok(rr, 'TLSA record survives wire serialization');
|
|
158
|
+
assert.equal(rr.data.toString('hex'), `030101${certHex}`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('dnsHandler resolves CNAME chains', async () => {
|
|
162
|
+
await flushTestDb();
|
|
163
|
+
await zoneStore.add('example.com', 'alias', 'CNAME', ['example.com']);
|
|
164
|
+
await zoneStore.add('example.com', '', 'A', ['4.4.4.4']);
|
|
165
|
+
|
|
166
|
+
const response = await dnsHandler(buildRequest('alias.example.com', 'A'));
|
|
167
|
+
const types = response.answers.map(a => a.type);
|
|
168
|
+
assert.ok(types.includes(Packet.TYPE.CNAME), 'should include the CNAME answer');
|
|
169
|
+
const addresses = response.answers.filter(a => a.type === Packet.TYPE.A).map(a => a.address);
|
|
170
|
+
assert.ok(addresses.includes('4.4.4.4'), 'should follow the CNAME to the A record');
|
|
171
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const { Resolver } = require('node:dns').promises;
|
|
6
|
+
|
|
7
|
+
const dns2 = require('dns2');
|
|
8
|
+
const Packet = dns2.Packet;
|
|
9
|
+
|
|
10
|
+
const initDnsServer = require('../lib/dns-server');
|
|
11
|
+
const { parseEdns, finalizeResponse } = initDnsServer.testables;
|
|
12
|
+
const { zoneStore } = require('../lib/zone-store');
|
|
13
|
+
const { config, flushTestDb, closeDb } = require('./helpers');
|
|
14
|
+
|
|
15
|
+
const EDNS = Packet.TYPE.EDNS;
|
|
16
|
+
|
|
17
|
+
let servers;
|
|
18
|
+
let resolver;
|
|
19
|
+
|
|
20
|
+
test.before(async () => {
|
|
21
|
+
await flushTestDb();
|
|
22
|
+
|
|
23
|
+
servers = await initDnsServer();
|
|
24
|
+
|
|
25
|
+
resolver = new Resolver();
|
|
26
|
+
resolver.setServers([`127.0.0.1:${config.dns.port}`]);
|
|
27
|
+
|
|
28
|
+
await zoneStore.add('example.com', '', 'A', ['9.8.7.6']);
|
|
29
|
+
await zoneStore.add('example.com', 'host', 'AAAA', ['2001:db8::1']);
|
|
30
|
+
await zoneStore.add('example.com', 'txt', 'TXT', ['hello world']);
|
|
31
|
+
// a value longer than a single 255-byte DNS character-string, but still
|
|
32
|
+
// small enough to fit in a 512-byte UDP response
|
|
33
|
+
await zoneStore.add('example.com', 'long', 'TXT', ['y'.repeat(300)]);
|
|
34
|
+
await zoneStore.add('example.com', 'mail', 'MX', ['mx.example.com', 10]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test.after(async () => {
|
|
38
|
+
if (servers) {
|
|
39
|
+
servers.udpServer.close();
|
|
40
|
+
await new Promise(resolve => servers.tcpServer.close(resolve));
|
|
41
|
+
}
|
|
42
|
+
await closeDb();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('resolves A records over the wire', async () => {
|
|
46
|
+
const addrs = await resolver.resolve4('example.com');
|
|
47
|
+
assert.deepEqual(addrs, ['9.8.7.6']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('resolves AAAA records over the wire', async () => {
|
|
51
|
+
const addrs = await resolver.resolve6('host.example.com');
|
|
52
|
+
assert.ok(addrs.includes('2001:db8::1'));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('resolves a TXT record over the wire', async () => {
|
|
56
|
+
const txt = await resolver.resolveTxt('txt.example.com');
|
|
57
|
+
const flat = txt.map(chunks => chunks.join(''));
|
|
58
|
+
assert.ok(flat.includes('hello world'));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('resolves a long, multi-chunk TXT record over the wire', async () => {
|
|
62
|
+
const txt = await resolver.resolveTxt('long.example.com');
|
|
63
|
+
const flat = txt.map(chunks => chunks.join(''));
|
|
64
|
+
assert.ok(flat.includes('y'.repeat(300)));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('resolves MX records over the wire', async () => {
|
|
68
|
+
const mx = await resolver.resolveMx('mail.example.com');
|
|
69
|
+
assert.ok(mx.some(rec => rec.exchange === 'mx.example.com' && rec.priority === 10));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('returns configured NS records over the wire', async () => {
|
|
73
|
+
const ns = await resolver.resolveNs('example.com');
|
|
74
|
+
assert.ok(Array.isArray(ns) && ns.length >= 1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// EDNS / OPT handling (pure, no network)
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
const mkResponse = answers => {
|
|
82
|
+
const p = new Packet({});
|
|
83
|
+
p.header = new Packet.Header({ id: 1, qr: 1, aa: 1 });
|
|
84
|
+
p.questions = [{ name: 'example.com', type: Packet.TYPE.A, class: Packet.CLASS.IN }];
|
|
85
|
+
p.answers = answers;
|
|
86
|
+
return p;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
test('parseEdns reads the DO bit and payload size from an OPT record', () => {
|
|
90
|
+
// eslint-disable-next-line new-cap
|
|
91
|
+
const withOpt = { additionals: [Packet.Resource.EDNS([], { udpPayloadSize: 1232, doFlag: true })] };
|
|
92
|
+
const edns = parseEdns(withOpt);
|
|
93
|
+
assert.equal(edns.hasOpt, true);
|
|
94
|
+
assert.equal(edns.doFlag, true);
|
|
95
|
+
assert.equal(edns.udpPayloadSize, 1232);
|
|
96
|
+
|
|
97
|
+
const noOpt = parseEdns({ additionals: [] });
|
|
98
|
+
assert.equal(noOpt.hasOpt, false);
|
|
99
|
+
assert.equal(noOpt.doFlag, false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('finalizeResponse adds an OPT to EDNS replies and drops leaked additionals', () => {
|
|
103
|
+
const response = mkResponse([{ name: 'example.com', type: Packet.TYPE.A, class: Packet.CLASS.IN, ttl: 300, address: '1.2.3.4' }]);
|
|
104
|
+
// simulate a leaked inbound OPT plus stray additional
|
|
105
|
+
response.additionals = [
|
|
106
|
+
// eslint-disable-next-line new-cap
|
|
107
|
+
Packet.Resource.EDNS([], { udpPayloadSize: 4096, doFlag: true }),
|
|
108
|
+
{ name: 'x', type: Packet.TYPE.A, class: 1, ttl: 1, address: '9.9.9.9' }
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const out = finalizeResponse(response, { hasOpt: true, doFlag: true, udpPayloadSize: 4096 }, 'tcp');
|
|
112
|
+
assert.equal(out.additionals.length, 1);
|
|
113
|
+
assert.equal(out.additionals[0].type, EDNS);
|
|
114
|
+
assert.equal(out.additionals[0].doFlag, true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('finalizeResponse omits OPT when the query had none', () => {
|
|
118
|
+
const response = mkResponse([{ name: 'example.com', type: Packet.TYPE.A, class: Packet.CLASS.IN, ttl: 300, address: '1.2.3.4' }]);
|
|
119
|
+
const out = finalizeResponse(response, { hasOpt: false, doFlag: false, udpPayloadSize: 512 }, 'tcp');
|
|
120
|
+
assert.deepEqual(out.additionals, []);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('finalizeResponse truncates oversized UDP responses with TC set', () => {
|
|
124
|
+
// Many large TXT answers easily exceed the 512-byte floor.
|
|
125
|
+
const answers = [];
|
|
126
|
+
for (let i = 0; i < 20; i++) {
|
|
127
|
+
answers.push({ name: 'example.com', type: Packet.TYPE.TXT, class: Packet.CLASS.IN, ttl: 300, data: ['z'.repeat(200)] });
|
|
128
|
+
}
|
|
129
|
+
const response = mkResponse(answers);
|
|
130
|
+
const out = finalizeResponse(response, { hasOpt: true, doFlag: true, udpPayloadSize: 512 }, 'udp');
|
|
131
|
+
// truncated path returns a serialized Buffer (TC=1, empty body, our OPT)
|
|
132
|
+
assert.ok(Buffer.isBuffer(out));
|
|
133
|
+
const reparsed = Packet.parse(out);
|
|
134
|
+
assert.equal(reparsed.header.tc, 1);
|
|
135
|
+
assert.equal(reparsed.answers.length, 0);
|
|
136
|
+
assert.ok(reparsed.additionals.some(r => r.type === EDNS));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('finalizeResponse caps UDP at our configured size, not the requestor advertised max', () => {
|
|
140
|
+
// A response between our 1232 cap and the 4096 ceiling: a resolver advertising
|
|
141
|
+
// 4096 must still get TC=1, because we never emit a datagram larger than our
|
|
142
|
+
// configured udpPayloadSize (anti-fragmentation), regardless of the advertised max.
|
|
143
|
+
const answers = [];
|
|
144
|
+
for (let i = 0; i < 10; i++) {
|
|
145
|
+
answers.push({ name: 'example.com', type: Packet.TYPE.TXT, class: Packet.CLASS.IN, ttl: 300, data: ['z'.repeat(200)] });
|
|
146
|
+
}
|
|
147
|
+
const response = mkResponse(answers);
|
|
148
|
+
const out = finalizeResponse(response, { hasOpt: true, doFlag: true, udpPayloadSize: 4096 }, 'udp');
|
|
149
|
+
assert.ok(Buffer.isBuffer(out));
|
|
150
|
+
const reparsed = Packet.parse(out);
|
|
151
|
+
assert.equal(reparsed.header.tc, 1, 'response above our configured cap is truncated even when 4096 is advertised');
|
|
152
|
+
assert.equal(reparsed.answers.length, 0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('finalizeResponse returns a serialized buffer for UDP responses that fit', () => {
|
|
156
|
+
const response = mkResponse([{ name: 'example.com', type: Packet.TYPE.A, class: Packet.CLASS.IN, ttl: 300, address: '1.2.3.4' }]);
|
|
157
|
+
const out = finalizeResponse(response, { hasOpt: true, doFlag: false, udpPayloadSize: 1232 }, 'udp');
|
|
158
|
+
assert.ok(Buffer.isBuffer(out));
|
|
159
|
+
// reparse to confirm the OPT made it onto the wire
|
|
160
|
+
const reparsed = Packet.parse(out);
|
|
161
|
+
assert.ok(reparsed.additionals.some(r => r.type === EDNS));
|
|
162
|
+
});
|