1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23const common = require('../common'); 24const dnstools = require('../common/dns'); 25const assert = require('assert'); 26 27const dns = require('dns'); 28const dnsPromises = dns.promises; 29const dgram = require('dgram'); 30 31const existing = dns.getServers(); 32assert(existing.length > 0); 33 34// Verify that setServers() handles arrays with holes and other oddities 35{ 36 const servers = []; 37 38 servers[0] = '127.0.0.1'; 39 servers[2] = '0.0.0.0'; 40 dns.setServers(servers); 41 42 assert.deepStrictEqual(dns.getServers(), ['127.0.0.1', '0.0.0.0']); 43} 44 45{ 46 const servers = ['127.0.0.1', '192.168.1.1']; 47 48 servers[3] = '127.1.0.1'; 49 servers[4] = '127.1.0.1'; 50 servers[5] = '127.1.1.1'; 51 52 Object.defineProperty(servers, 2, { 53 enumerable: true, 54 get: () => { 55 servers.length = 3; 56 return '0.0.0.0'; 57 } 58 }); 59 60 dns.setServers(servers); 61 assert.deepStrictEqual(dns.getServers(), [ 62 '127.0.0.1', 63 '192.168.1.1', 64 '0.0.0.0', 65 ]); 66} 67 68{ 69 // Various invalidities, all of which should throw a clean error. 70 const invalidServers = [ 71 ' ', 72 '\n', 73 '\0', 74 '1'.repeat(3 * 4), 75 // Check for REDOS issues. 76 ':'.repeat(100000), 77 '['.repeat(100000), 78 '['.repeat(100000) + ']'.repeat(100000) + 'a', 79 ]; 80 invalidServers.forEach((serv) => { 81 assert.throws( 82 () => { 83 dns.setServers([serv]); 84 }, 85 { 86 name: 'TypeError', 87 code: 'ERR_INVALID_IP_ADDRESS' 88 } 89 ); 90 }); 91} 92 93const goog = [ 94 '8.8.8.8', 95 '8.8.4.4', 96]; 97dns.setServers(goog); 98assert.deepStrictEqual(dns.getServers(), goog); 99assert.throws(() => dns.setServers(['foobar']), { 100 code: 'ERR_INVALID_IP_ADDRESS', 101 name: 'TypeError', 102 message: 'Invalid IP address: foobar' 103}); 104assert.throws(() => dns.setServers(['127.0.0.1:va']), { 105 code: 'ERR_INVALID_IP_ADDRESS', 106 name: 'TypeError', 107 message: 'Invalid IP address: 127.0.0.1:va' 108}); 109assert.deepStrictEqual(dns.getServers(), goog); 110 111const goog6 = [ 112 '2001:4860:4860::8888', 113 '2001:4860:4860::8844', 114]; 115dns.setServers(goog6); 116assert.deepStrictEqual(dns.getServers(), goog6); 117 118goog6.push('4.4.4.4'); 119dns.setServers(goog6); 120assert.deepStrictEqual(dns.getServers(), goog6); 121 122const ports = [ 123 '4.4.4.4:53', 124 '[2001:4860:4860::8888]:53', 125 '103.238.225.181:666', 126 '[fe80::483a:5aff:fee6:1f04]:666', 127 '[fe80::483a:5aff:fee6:1f04]', 128]; 129const portsExpected = [ 130 '4.4.4.4', 131 '2001:4860:4860::8888', 132 '103.238.225.181:666', 133 '[fe80::483a:5aff:fee6:1f04]:666', 134 'fe80::483a:5aff:fee6:1f04', 135]; 136dns.setServers(ports); 137assert.deepStrictEqual(dns.getServers(), portsExpected); 138 139dns.setServers([]); 140assert.deepStrictEqual(dns.getServers(), []); 141 142{ 143 const errObj = { 144 code: 'ERR_INVALID_ARG_TYPE', 145 name: 'TypeError', 146 message: 'The "rrtype" argument must be of type string. ' + 147 'Received an instance of Array' 148 }; 149 assert.throws(() => { 150 dns.resolve('example.com', [], common.mustNotCall()); 151 }, errObj); 152 assert.throws(() => { 153 dnsPromises.resolve('example.com', []); 154 }, errObj); 155} 156{ 157 const errObj = { 158 code: 'ERR_INVALID_ARG_TYPE', 159 name: 'TypeError', 160 message: 'The "name" argument must be of type string. ' + 161 'Received undefined' 162 }; 163 assert.throws(() => { 164 dnsPromises.resolve(); 165 }, errObj); 166} 167 168// dns.lookup should accept only falsey and string values 169{ 170 const errorReg = { 171 code: 'ERR_INVALID_ARG_TYPE', 172 name: 'TypeError', 173 message: /^The "hostname" argument must be of type string\. Received .*/ 174 }; 175 176 assert.throws(() => dns.lookup({}, common.mustNotCall()), errorReg); 177 178 assert.throws(() => dns.lookup([], common.mustNotCall()), errorReg); 179 180 assert.throws(() => dns.lookup(true, common.mustNotCall()), errorReg); 181 182 assert.throws(() => dns.lookup(1, common.mustNotCall()), errorReg); 183 184 assert.throws(() => dns.lookup(common.mustNotCall(), common.mustNotCall()), 185 errorReg); 186 187 assert.throws(() => dnsPromises.lookup({}), errorReg); 188 assert.throws(() => dnsPromises.lookup([]), errorReg); 189 assert.throws(() => dnsPromises.lookup(true), errorReg); 190 assert.throws(() => dnsPromises.lookup(1), errorReg); 191 assert.throws(() => dnsPromises.lookup(common.mustNotCall()), errorReg); 192} 193 194// dns.lookup should accept falsey values 195{ 196 const checkCallback = (err, address, family) => { 197 assert.ifError(err); 198 assert.strictEqual(address, null); 199 assert.strictEqual(family, 4); 200 }; 201 202 ['', null, undefined, 0, NaN].forEach(async (value) => { 203 const res = await dnsPromises.lookup(value); 204 assert.deepStrictEqual(res, { address: null, family: 4 }); 205 dns.lookup(value, common.mustCall(checkCallback)); 206 }); 207} 208 209{ 210 // Make sure that dns.lookup throws if hints does not represent a valid flag. 211 // (dns.V4MAPPED | dns.ADDRCONFIG | dns.ALL) + 1 is invalid because: 212 // - it's different from dns.V4MAPPED and dns.ADDRCONFIG and dns.ALL. 213 // - it's different from any subset of them bitwise ored. 214 // - it's different from 0. 215 // - it's an odd number different than 1, and thus is invalid, because 216 // flags are either === 1 or even. 217 const hints = (dns.V4MAPPED | dns.ADDRCONFIG | dns.ALL) + 1; 218 const err = { 219 code: 'ERR_INVALID_ARG_VALUE', 220 name: 'TypeError', 221 message: /The argument 'hints' is invalid\. Received \d+/ 222 }; 223 224 assert.throws(() => { 225 dnsPromises.lookup('nodejs.org', { hints }); 226 }, err); 227 assert.throws(() => { 228 dns.lookup('nodejs.org', { hints }, common.mustNotCall()); 229 }, err); 230} 231 232assert.throws(() => dns.lookup('nodejs.org'), { 233 code: 'ERR_INVALID_ARG_TYPE', 234 name: 'TypeError' 235}); 236 237assert.throws(() => dns.lookup('nodejs.org', 4), { 238 code: 'ERR_INVALID_ARG_TYPE', 239 name: 'TypeError' 240}); 241 242assert.throws(() => dns.lookup('', { 243 family: 'nodejs.org', 244 hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL, 245}), { 246 code: 'ERR_INVALID_ARG_TYPE', 247 name: 'TypeError' 248}); 249 250dns.lookup('', { family: 4, hints: 0 }, common.mustCall()); 251 252dns.lookup('', { 253 family: 6, 254 hints: dns.ADDRCONFIG 255}, common.mustCall()); 256 257dns.lookup('', { hints: dns.V4MAPPED }, common.mustCall()); 258 259dns.lookup('', { 260 hints: dns.ADDRCONFIG | dns.V4MAPPED 261}, common.mustCall()); 262 263dns.lookup('', { 264 hints: dns.ALL 265}, common.mustCall()); 266 267dns.lookup('', { 268 hints: dns.V4MAPPED | dns.ALL 269}, common.mustCall()); 270 271dns.lookup('', { 272 hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL 273}, common.mustCall()); 274 275dns.lookup('', { 276 hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL, 277 family: 'IPv4' 278}, common.mustCall()); 279 280dns.lookup('', { 281 hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL, 282 family: 'IPv6' 283}, common.mustCall()); 284 285(async function() { 286 await dnsPromises.lookup('', { family: 4, hints: 0 }); 287 await dnsPromises.lookup('', { family: 6, hints: dns.ADDRCONFIG }); 288 await dnsPromises.lookup('', { hints: dns.V4MAPPED }); 289 await dnsPromises.lookup('', { hints: dns.ADDRCONFIG | dns.V4MAPPED }); 290 await dnsPromises.lookup('', { hints: dns.ALL }); 291 await dnsPromises.lookup('', { hints: dns.V4MAPPED | dns.ALL }); 292 await dnsPromises.lookup('', { 293 hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL 294 }); 295 await dnsPromises.lookup('', { verbatim: true }); 296})().then(common.mustCall()); 297 298{ 299 const err = { 300 code: 'ERR_MISSING_ARGS', 301 name: 'TypeError', 302 message: 'The "address", "port", and "callback" arguments must be ' + 303 'specified' 304 }; 305 306 assert.throws(() => dns.lookupService('0.0.0.0'), err); 307 err.message = 'The "address" and "port" arguments must be specified'; 308 assert.throws(() => dnsPromises.lookupService('0.0.0.0'), err); 309} 310 311{ 312 const invalidAddress = 'fasdfdsaf'; 313 const err = { 314 code: 'ERR_INVALID_ARG_VALUE', 315 name: 'TypeError', 316 message: `The argument 'address' is invalid. Received '${invalidAddress}'` 317 }; 318 319 assert.throws(() => { 320 dnsPromises.lookupService(invalidAddress, 0); 321 }, err); 322 323 assert.throws(() => { 324 dns.lookupService(invalidAddress, 0, common.mustNotCall()); 325 }, err); 326} 327 328const portErr = (port) => { 329 const err = { 330 code: 'ERR_SOCKET_BAD_PORT', 331 name: 'RangeError' 332 }; 333 334 assert.throws(() => { 335 dnsPromises.lookupService('0.0.0.0', port); 336 }, err); 337 338 assert.throws(() => { 339 dns.lookupService('0.0.0.0', port, common.mustNotCall()); 340 }, err); 341}; 342[null, undefined, 65538, 'test', NaN, Infinity, Symbol(), 0n, true, false, '', () => {}, {}].forEach(portErr); 343 344assert.throws(() => { 345 dns.lookupService('0.0.0.0', 80, null); 346}, { 347 code: 'ERR_INVALID_ARG_TYPE', 348 name: 'TypeError' 349}); 350 351{ 352 dns.resolveMx('foo.onion', function(err) { 353 assert.strictEqual(err.code, 'ENOTFOUND'); 354 assert.strictEqual(err.syscall, 'queryMx'); 355 assert.strictEqual(err.hostname, 'foo.onion'); 356 assert.strictEqual(err.message, 'queryMx ENOTFOUND foo.onion'); 357 }); 358} 359 360{ 361 const cases = [ 362 { method: 'resolveAny', 363 answers: [ 364 { type: 'A', address: '1.2.3.4', ttl: 3333333333 }, 365 { type: 'AAAA', address: '::42', ttl: 3333333333 }, 366 { type: 'MX', priority: 42, exchange: 'foobar.com', ttl: 3333333333 }, 367 { type: 'NS', value: 'foobar.org', ttl: 3333333333 }, 368 { type: 'PTR', value: 'baz.org', ttl: 3333333333 }, 369 { 370 type: 'SOA', 371 nsname: 'ns1.example.com', 372 hostmaster: 'admin.example.com', 373 serial: 3210987654, 374 refresh: 900, 375 retry: 900, 376 expire: 1800, 377 minttl: 3333333333 378 }, 379 ] }, 380 381 { method: 'resolve4', 382 options: { ttl: true }, 383 answers: [ { type: 'A', address: '1.2.3.4', ttl: 3333333333 } ] }, 384 385 { method: 'resolve6', 386 options: { ttl: true }, 387 answers: [ { type: 'AAAA', address: '::42', ttl: 3333333333 } ] }, 388 389 { method: 'resolveSoa', 390 answers: [ 391 { 392 type: 'SOA', 393 nsname: 'ns1.example.com', 394 hostmaster: 'admin.example.com', 395 serial: 3210987654, 396 refresh: 900, 397 retry: 900, 398 expire: 1800, 399 minttl: 3333333333 400 }, 401 ] }, 402 ]; 403 404 const server = dgram.createSocket('udp4'); 405 406 server.on('message', common.mustCall((msg, { address, port }) => { 407 const parsed = dnstools.parseDNSPacket(msg); 408 const domain = parsed.questions[0].domain; 409 assert.strictEqual(domain, 'example.org'); 410 411 server.send(dnstools.writeDNSPacket({ 412 id: parsed.id, 413 questions: parsed.questions, 414 answers: cases[0].answers.map( 415 (answer) => Object.assign({ domain }, answer) 416 ), 417 }), port, address); 418 }, cases.length * 2)); 419 420 server.bind(0, common.mustCall(() => { 421 const address = server.address(); 422 dns.setServers([`127.0.0.1:${address.port}`]); 423 424 function validateResults(res) { 425 if (!Array.isArray(res)) 426 res = [res]; 427 428 assert.deepStrictEqual(res.map(tweakEntry), 429 cases[0].answers.map(tweakEntry)); 430 } 431 432 function tweakEntry(r) { 433 const ret = { ...r }; 434 435 const { method } = cases[0]; 436 437 // TTL values are only provided for A and AAAA entries. 438 if (!['A', 'AAAA'].includes(ret.type) && !/^resolve(4|6)?$/.test(method)) 439 delete ret.ttl; 440 441 if (method !== 'resolveAny') 442 delete ret.type; 443 444 return ret; 445 } 446 447 (async function nextCase() { 448 if (cases.length === 0) 449 return server.close(); 450 451 const { method, options } = cases[0]; 452 453 validateResults(await dnsPromises[method]('example.org', options)); 454 455 dns[method]('example.org', options, common.mustSucceed((res) => { 456 validateResults(res); 457 cases.shift(); 458 nextCase(); 459 })); 460 })().then(common.mustCall()); 461 462 })); 463} 464