Skip to content
File

Blob: src/workerd/api/node/tests/dns-nodejs-test.js

javascript574 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4// Copyright Joyent and Node contributors. All rights reserved. MIT license.
5import dns from 'node:dns';
6import dnsPromises from 'node:dns/promises';
7import { strictEqual, ok, deepStrictEqual, throws } from 'node:assert';
8import { inspect } from 'node:util';
9 
10// Taken from Node.js
11// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/common/internet.js
12const addresses = {
13 // A generic host that has registered common DNS records,
14 // supports both IPv4 and IPv6, and provides basic HTTP/HTTPS services
15 INET_HOST: 'nodejs.org',
16 // A host that provides IPv4 services
17 INET4_HOST: 'nodejs.org',
18 // A host that provides IPv6 services
19 INET6_HOST: 'nodejs.org',
20 // An accessible IPv4 IP,
21 // defaults to the Google Public DNS IPv4 address
22 INET4_IP: '8.8.8.8',
23 // An accessible IPv6 IP,
24 // defaults to the Google Public DNS IPv6 address
25 INET6_IP: '2001:4860:4860::8888',
26 // An invalid host that cannot be resolved
27 // See https://tools.ietf.org/html/rfc2606#section-2
28 INVALID_HOST: 'something.invalid',
29 // A host with MX records registered
30 MX_HOST: 'nodejs.org',
31 // On some systems, .invalid returns a server failure/try again rather than
32 // record not found. Use this to guarantee record not found.
33 NOT_FOUND: 'come.on.fhqwhgads.test',
34 // A host with SRV records registered
35 SRV_HOST: '_caldav._tcp.google.com',
36 // A host with PTR records registered
37 PTR_HOST: '8.8.8.8.in-addr.arpa',
38 // A host with NAPTR records registered
39 NAPTR_HOST: 'sip2sip.info',
40 // A host with SOA records registered
41 SOA_HOST: 'nodejs.org',
42 // A host with CAA record registered
43 CAA_HOST: 'google.com',
44 // A host with CNAME records registered
45 CNAME_HOST: 'blog.nodejs.org',
46 // A host with NS records registered
47 NS_HOST: 'nodejs.org',
48 // A host with TXT records registered
49 TXT_HOST: 'nodejs.org',
50 // An accessible IPv4 DNS server
51 DNS4_SERVER: '8.8.8.8',
52 // An accessible IPv4 DNS server
53 DNS6_SERVER: '2001:4860:4860::8888',
54};
55 
56export const functionsExist = {
57 async test() {
58 const syncFns = [
59 'lookup',
60 'lookupService',
61 'resolve',
62 'resolve4',
63 'resolve6',
64 'resolveAny',
65 'resolveCname',
66 'resolveCaa',
67 'resolveMx',
68 'resolveNaptr',
69 'resolveNs',
70 'resolvePtr',
71 'resolveSoa',
72 'resolveSrv',
73 'setDefaultResultOrder',
74 'getDefaultResultOrder',
75 'setServers',
76 ];
77 
78 for (const fn of syncFns) {
79 strictEqual(typeof dns[fn], 'function');
80 }
81 
82 ok(dns.promises !== undefined);
83 },
84};
85 
86export const errorCodesExist = {
87 async test() {
88 strictEqual(typeof dns.NODATA, 'string');
89 strictEqual(typeof dnsPromises.NODATA, 'string');
90 },
91};
92 
93// Tests are taken from
94// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L483
95export const resolveTxt = {
96 async test() {
97 function validateResult(result) {
98 ok(
99 result.length > 0,
100 `Result length should be greater than 0 but got ${inspect(result)}`
101 );
102 ok(Array.isArray(result[0]));
103 ok(
104 result.some((record) => record[0].startsWith('v=spf1')),
105 `Expected SPF record but got ${inspect(result[0])}`
106 );
107 }
108 
109 validateResult(await dnsPromises.resolveTxt(addresses.TXT_HOST));
110 
111 {
112 // Callback API
113 const { promise, resolve, reject } = Promise.withResolvers();
114 dns.resolveTxt(addresses.TXT_HOST, (error, results) => {
115 if (error) {
116 reject(error);
117 return;
118 }
119 validateResult(results);
120 resolve();
121 });
122 await promise;
123 }
124 },
125};
126 
127// Tests are taken from
128// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L402
129export const resolveCaa = {
130 async test() {
131 function validateResult(result) {
132 ok(Array.isArray(result), `expected array, got ${inspect(result)}`);
133 strictEqual(result.length, 1);
134 strictEqual(typeof result[0].critical, 'number');
135 strictEqual(result[0].critical, 0);
136 strictEqual(result[0].issue, 'pki.goog');
137 }
138 
139 validateResult(await dnsPromises.resolveCaa(addresses.CAA_HOST));
140 },
141};
142 
143// Tests are taken from
144// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L142
145export const resolveMx = {
146 async test() {
147 function validateResult(result) {
148 ok(result.length > 0);
149 
150 for (const item of result) {
151 strictEqual(typeof item, 'object');
152 ok(item.exchange);
153 strictEqual(typeof item.exchange, 'string');
154 strictEqual(typeof item.priority, 'number');
155 }
156 }
157 validateResult(await dnsPromises.resolveMx(addresses.MX_HOST));
158 },
159};
160 
161// Tests are taken from
162// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L442
163export const resolveCname = {
164 async test() {
165 function validateResult(result) {
166 ok(result.length > 0);
167 
168 for (const item of result) {
169 ok(item);
170 strictEqual(typeof item, 'string');
171 }
172 }
173 
174 validateResult(await dnsPromises.resolveCname(addresses.CNAME_HOST));
175 },
176};
177 
178// Tests are taken from
179// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L184
180export const resolveNs = {
181 async test() {
182 function validateResult(result) {
183 ok(result.length > 0);
184 
185 for (const item of result) {
186 ok(item);
187 strictEqual(typeof item, 'string');
188 }
189 }
190 
191 validateResult(await dnsPromises.resolveNs(addresses.NS_HOST));
192 },
193};
194 
195// Tests are taken from
196// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L268
197export const resolvePtr = {
198 async test() {
199 function validateResult(result) {
200 ok(result.length > 0);
201 
202 for (const item of result) {
203 ok(item);
204 strictEqual(typeof item, 'string');
205 }
206 }
207 
208 validateResult(await dnsPromises.resolvePtr(addresses.PTR_HOST));
209 },
210};
211 
212// Tests are taken from
213// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L224
214export const resolveSrv = {
215 async test() {
216 function validateResult(result) {
217 ok(result.length > 0);
218 
219 for (const item of result) {
220 strictEqual(typeof item, 'object');
221 ok(item.name);
222 strictEqual(typeof item.name, 'string');
223 strictEqual(typeof item.port, 'number');
224 strictEqual(typeof item.priority, 'number');
225 strictEqual(typeof item.weight, 'number');
226 }
227 }
228 
229 validateResult(await dnsPromises.resolveSrv(addresses.SRV_HOST));
230 },
231};
232 
233// Tests are taken from
234// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L353
235export const resolveSoa = {
236 async test() {
237 function validateResult(result) {
238 strictEqual(typeof result, 'object');
239 strictEqual(typeof result.nsname, 'string');
240 ok(result.nsname.length > 0);
241 strictEqual(typeof result.hostmaster, 'string');
242 ok(result.hostmaster.length > 0);
243 strictEqual(typeof result.serial, 'number');
244 ok(result.serial > 0 && result.serial < 4294967295);
245 strictEqual(typeof result.refresh, 'number');
246 ok(result.refresh > 0 && result.refresh < 2147483647);
247 strictEqual(typeof result.retry, 'number');
248 ok(result.retry > 0 && result.retry < 2147483647);
249 strictEqual(typeof result.expire, 'number');
250 ok(result.expire > 0 && result.expire < 2147483647);
251 strictEqual(typeof result.minttl, 'number');
252 ok(result.minttl >= 0 && result.minttl < 2147483647);
253 }
254 
255 validateResult(await dnsPromises.resolveSoa(addresses.SOA_HOST));
256 },
257};
258 
259// Tests are taken from
260// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L308
261export const resolveNaptr = {
262 async test() {
263 function validateResult(result) {
264 ok(result.length > 0);
265 
266 for (const item of result) {
267 strictEqual(typeof item, 'object');
268 strictEqual(typeof item.flags, 'string');
269 strictEqual(typeof item.service, 'string');
270 strictEqual(typeof item.regexp, 'string');
271 strictEqual(typeof item.replacement, 'string');
272 strictEqual(typeof item.order, 'number');
273 strictEqual(typeof item.preference, 'number');
274 }
275 }
276 
277 validateResult(await dnsPromises.resolveNaptr(addresses.NAPTR_HOST));
278 },
279};
280 
281export const resolve4 = {
282 async test() {
283 function validateResult(result) {
284 ok(result.length > 0);
285 
286 for (const item of result) {
287 strictEqual(typeof item, 'string');
288 // TODO(soon): Validate IPv4
289 // ok(isIPv4(item));
290 }
291 }
292 
293 validateResult(await dnsPromises.resolve4(addresses.INET4_HOST));
294 },
295};
296 
297// Tests are taken from
298// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L86
299export const resolve4TTL = {
300 async test() {
301 function validateResult(result) {
302 ok(result.length > 0);
303 
304 for (const item of result) {
305 strictEqual(typeof item, 'object');
306 strictEqual(typeof item.ttl, 'number');
307 strictEqual(typeof item.address, 'string');
308 ok(item.ttl >= 0);
309 // TODO(soon): Validate IPv4
310 // ok(isIPv4(item.address));
311 }
312 }
313 
314 validateResult(
315 await dnsPromises.resolve4(addresses.INET4_HOST, {
316 ttl: true,
317 })
318 );
319 },
320};
321 
322export const resolve6 = {
323 async test() {
324 function validateResult(result) {
325 ok(result.length > 0);
326 
327 for (const item of result) {
328 strictEqual(typeof item, 'string');
329 // TODO(soon): Validate IPv6
330 // ok(isIPv6(item));
331 }
332 }
333 
334 validateResult(await dnsPromises.resolve6(addresses.INET6_HOST));
335 },
336};
337 
338// Tests are taken from
339// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L114
340export const resolve6TTL = {
341 async test() {
342 function validateResult(result) {
343 ok(result.length > 0);
344 
345 for (const item of result) {
346 strictEqual(typeof item, 'object');
347 strictEqual(typeof item.ttl, 'number');
348 strictEqual(typeof item.address, 'string');
349 ok(item.ttl >= 0);
350 // TODO(soon): Validate IPv6
351 // ok(isIPv6(item.address));
352 }
353 }
354 
355 validateResult(
356 await dnsPromises.resolve6(addresses.INET6_HOST, {
357 ttl: true,
358 })
359 );
360 },
361};
362 
363export const getServers = {
364 async test() {
365 deepStrictEqual(await dnsPromises.getServers(), [
366 '1.1.1.1',
367 '2606:4700:4700::1111',
368 '1.0.0.1',
369 '2606:4700:4700::1001',
370 ]);
371 },
372};
373 
374// Regression: dns.lookup() with all:true and family:0 must return family:6 for
375// IPv6 addresses (was returning family:4 due to copy-paste bug).
376export const lookupAllFamilyZeroIPv6Family = {
377 async test() {
378 const results = await dnsPromises.lookup(addresses.INET_HOST, {
379 all: true,
380 family: 0,
381 });
382 ok(Array.isArray(results), 'expected array of addresses');
383 ok(results.length > 0, 'expected at least one address');
384 
385 for (const entry of results) {
386 strictEqual(typeof entry.address, 'string');
387 ok(
388 entry.family === 4 || entry.family === 6,
389 `family must be 4 or 6, got ${entry.family}`
390 );
391 }
392 
393 // If any IPv6 addresses are returned, they must have family:6
394 const ipv6Entries = results.filter((e) => e.address.includes(':'));
395 for (const entry of ipv6Entries) {
396 strictEqual(
397 entry.family,
398 6,
399 `IPv6 address ${entry.address} should have family:6 but got family:${entry.family}`
400 );
401 }
402 
403 // If any IPv4 addresses are returned, they must have family:4
404 const ipv4Entries = results.filter((e) => !e.address.includes(':'));
405 for (const entry of ipv4Entries) {
406 strictEqual(
407 entry.family,
408 4,
409 `IPv4 address ${entry.address} should have family:4 but got family:${entry.family}`
410 );
411 }
412 },
413};
414 
415// Regression: dns.lookup() with default family (0) and all:false must resolve
416// hosts that only have A records (was only querying AAAA, failing for IPv4-only).
417export const lookupDefaultFamilyResolvesIPv4 = {
418 async test() {
419 // Default options (family:0, all:false) — should return an address
420 const result = await dnsPromises.lookup(addresses.INET4_HOST);
421 ok(result != null, 'expected a result');
422 strictEqual(typeof result.address, 'string');
423 ok(result.address.length > 0, 'expected non-empty address');
424 ok(
425 result.family === 4 || result.family === 6,
426 `family must be 4 or 6, got ${result.family}`
427 );
428 
429 // Also verify via callback API
430 const { promise, resolve, reject } = Promise.withResolvers();
431 dns.lookup(addresses.INET4_HOST, (error, address, family) => {
432 if (error) {
433 reject(error);
434 return;
435 }
436 strictEqual(typeof address, 'string');
437 ok(address.length > 0, 'expected non-empty address from callback');
438 ok(family === 4 || family === 6, `family must be 4 or 6, got ${family}`);
439 resolve();
440 });
441 await promise;
442 },
443};
444 
445// Regression: Resolver.resolvePtr must exist (was misspelled as 'esolvePtr').
446export const resolverResolvePtrExists = {
447 async test() {
448 const resolver = new dnsPromises.Resolver();
449 strictEqual(
450 typeof resolver.resolvePtr,
451 'function',
452 'Resolver.resolvePtr should be a function'
453 );
454 // Actually call it to verify it works end-to-end
455 const result = await resolver.resolvePtr(addresses.PTR_HOST);
456 ok(Array.isArray(result), 'expected array result');
457 ok(result.length > 0, 'expected at least one PTR record');
458 for (const item of result) {
459 strictEqual(typeof item, 'string');
460 }
461 },
462};
463 
464// Tests are taken from
465// https://github.com/nodejs/node/blob/3153c8333e3a8f2015b795642def4d81ec7cd7b3/test/parallel/test-dns-lookup.js
466export const testDnsLookup = {
467 async test() {
468 {
469 const err = {
470 code: 'ERR_INVALID_ARG_TYPE',
471 name: 'TypeError',
472 };
473 
474 throws(() => dns.lookup(1, {}), err);
475 throws(() => dnsPromises.lookup(1, {}), err);
476 }
477 
478 throws(
479 () => {
480 dns.lookup(false, 'cb');
481 },
482 {
483 code: 'ERR_INVALID_ARG_TYPE',
484 name: 'TypeError',
485 }
486 );
487 
488 throws(
489 () => {
490 dns.lookup(false, 'options', 'cb');
491 },
492 {
493 code: 'ERR_INVALID_ARG_TYPE',
494 name: 'TypeError',
495 }
496 );
497 
498 {
499 const family = 20;
500 const err = {
501 code: 'ERR_INVALID_ARG_VALUE',
502 name: 'TypeError',
503 message: `The property 'options.family' must be one of: 0, 4, 6. Received ${family}`,
504 };
505 const options = {
506 family,
507 all: false,
508 };
509 
510 throws(() => {
511 dnsPromises.lookup(false, options);
512 }, err);
513 throws(() => {
514 dns.lookup(false, options, () => {});
515 }, err);
516 }
517 
518 [1, 0n, 1n, '', '0', Symbol(), true, false, {}, [], () => {}].forEach(
519 (family) => {
520 const err = { code: 'ERR_INVALID_ARG_VALUE' };
521 const options = { family };
522 throws(() => {
523 dnsPromises.lookup(false, options);
524 }, err);
525 throws(() => {
526 dns.lookup(false, options, () => {});
527 }, err);
528 }
529 );
530 [0n, 1n, '', '0', Symbol(), true, false].forEach((family) => {
531 const err = { code: 'ERR_INVALID_ARG_TYPE' };
532 throws(() => {
533 dnsPromises.lookup(false, family);
534 }, err);
535 throws(() => {
536 dns.lookup(false, family, () => {});
537 }, err);
538 });
539 
540 [0, 1, 0n, 1n, '', '0', Symbol(), {}, [], () => {}].forEach((all) => {
541 const err = { code: 'ERR_INVALID_ARG_TYPE' };
542 const options = { all };
543 throws(() => {
544 dnsPromises.lookup(false, options);
545 }, err);
546 throws(() => {
547 dns.lookup(false, options, () => {});
548 }, err);
549 });
550 
551 [0, 1, 0n, 1n, '', '0', Symbol(), {}, [], () => {}].forEach((verbatim) => {
552 const err = { code: 'ERR_INVALID_ARG_TYPE' };
553 const options = { verbatim };
554 throws(() => {
555 dnsPromises.lookup(false, options);
556 }, err);
557 throws(() => {
558 dns.lookup(false, options, () => {});
559 }, err);
560 });
561 
562 [0, 1, 0n, 1n, '', '0', Symbol(), {}, [], () => {}].forEach((order) => {
563 const err = { code: 'ERR_INVALID_ARG_VALUE' };
564 const options = { order };
565 throws(() => {
566 dnsPromises.lookup(false, options);
567 }, err);
568 throws(() => {
569 dns.lookup(false, options, () => {});
570 }, err);
571 });
572 },
573};