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 /* 211 * Make sure that dns.lookup throws if hints does not represent a valid flag. 212 * (dns.V4MAPPED | dns.ADDRCONFIG | dns.ALL) + 1 is invalid because: 213 * - it's different from dns.V4MAPPED and dns.ADDRCONFIG and dns.ALL. 214 * - it's different from any subset of them bitwise ored. 215 * - it's different from 0. 216 * - it's an odd number different than 1, and thus is invalid, because 217 * flags are either === 1 or even. 218 */ 219 const hints = (dns.V4MAPPED | dns.ADDRCONFIG | dns.ALL) + 1; 220 const err = { 221 code: 'ERR_INVALID_OPT_VALUE', 222 name: 'TypeError', 223 message: /The value "\d+" is invalid for option "hints"/ 224 }; 225 226 assert.throws(() => { 227 dnsPromises.lookup('nodejs.org', { hints }); 228 }, err); 229 assert.throws(() => { 230 dns.lookup('nodejs.org', { hints }, common.mustNotCall()); 231 }, err); 232} 233 234assert.throws(() => dns.lookup('nodejs.org'), { 235 code: 'ERR_INVALID_CALLBACK', 236 name: 'TypeError' 237}); 238 239assert.throws(() => dns.lookup('nodejs.org', 4), { 240 code: 'ERR_INVALID_CALLBACK', 241 name: 'TypeError' 242}); 243 244dns.lookup('', { family: 4, hints: 0 }, common.mustCall()); 245 246dns.lookup('', { 247 family: 6, 248 hints: dns.ADDRCONFIG 249}, common.mustCall()); 250 251dns.lookup('', { hints: dns.V4MAPPED }, common.mustCall()); 252 253dns.lookup('', { 254 hints: dns.ADDRCONFIG | dns.V4MAPPED 255}, common.mustCall()); 256 257dns.lookup('', { 258 hints: dns.ALL 259}, common.mustCall()); 260 261dns.lookup('', { 262 hints: dns.V4MAPPED | dns.ALL 263}, common.mustCall()); 264 265dns.lookup('', { 266 hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL 267}, common.mustCall()); 268 269(async function() { 270 await dnsPromises.lookup('', { family: 4, hints: 0 }); 271 await dnsPromises.lookup('', { family: 6, hints: dns.ADDRCONFIG }); 272 await dnsPromises.lookup('', { hints: dns.V4MAPPED }); 273 await dnsPromises.lookup('', { hints: dns.ADDRCONFIG | dns.V4MAPPED }); 274 await dnsPromises.lookup('', { hints: dns.ALL }); 275 await dnsPromises.lookup('', { hints: dns.V4MAPPED | dns.ALL }); 276 await dnsPromises.lookup('', { 277 hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL 278 }); 279})(); 280 281{ 282 const err = { 283 code: 'ERR_MISSING_ARGS', 284 name: 'TypeError', 285 message: 'The "address", "port", and "callback" arguments must be ' + 286 'specified' 287 }; 288 289 assert.throws(() => dns.lookupService('0.0.0.0'), err); 290 err.message = 'The "address" and "port" arguments must be specified'; 291 assert.throws(() => dnsPromises.lookupService('0.0.0.0'), err); 292} 293 294{ 295 const invalidAddress = 'fasdfdsaf'; 296 const err = { 297 code: 'ERR_INVALID_OPT_VALUE', 298 name: 'TypeError', 299 message: `The value "${invalidAddress}" is invalid for option "address"` 300 }; 301 302 assert.throws(() => { 303 dnsPromises.lookupService(invalidAddress, 0); 304 }, err); 305 306 assert.throws(() => { 307 dns.lookupService(invalidAddress, 0, common.mustNotCall()); 308 }, err); 309} 310 311const portErr = (port) => { 312 const err = { 313 code: 'ERR_SOCKET_BAD_PORT', 314 message: 315 `Port should be >= 0 and < 65536. Received ${port}.`, 316 name: 'RangeError' 317 }; 318 319 assert.throws(() => { 320 dnsPromises.lookupService('0.0.0.0', port); 321 }, err); 322 323 assert.throws(() => { 324 dns.lookupService('0.0.0.0', port, common.mustNotCall()); 325 }, err); 326}; 327portErr(null); 328portErr(undefined); 329portErr(65538); 330portErr('test'); 331 332assert.throws(() => { 333 dns.lookupService('0.0.0.0', 80, null); 334}, { 335 code: 'ERR_INVALID_CALLBACK', 336 name: 'TypeError' 337}); 338 339{ 340 dns.resolveMx('foo.onion', function(err) { 341 assert.deepStrictEqual(err.errno, 'ENOTFOUND'); 342 assert.deepStrictEqual(err.code, 'ENOTFOUND'); 343 assert.deepStrictEqual(err.syscall, 'queryMx'); 344 assert.deepStrictEqual(err.hostname, 'foo.onion'); 345 assert.deepStrictEqual(err.message, 'queryMx ENOTFOUND foo.onion'); 346 }); 347} 348 349{ 350 const cases = [ 351 { method: 'resolveAny', 352 answers: [ 353 { type: 'A', address: '1.2.3.4', ttl: 3333333333 }, 354 { type: 'AAAA', address: '::42', ttl: 3333333333 }, 355 { type: 'MX', priority: 42, exchange: 'foobar.com', ttl: 3333333333 }, 356 { type: 'NS', value: 'foobar.org', ttl: 3333333333 }, 357 { type: 'PTR', value: 'baz.org', ttl: 3333333333 }, 358 { 359 type: 'SOA', 360 nsname: 'ns1.example.com', 361 hostmaster: 'admin.example.com', 362 serial: 3210987654, 363 refresh: 900, 364 retry: 900, 365 expire: 1800, 366 minttl: 3333333333 367 }, 368 ] 369 }, 370 371 { method: 'resolve4', 372 options: { ttl: true }, 373 answers: [ { type: 'A', address: '1.2.3.4', ttl: 3333333333 } ] 374 }, 375 376 { method: 'resolve6', 377 options: { ttl: true }, 378 answers: [ { type: 'AAAA', address: '::42', ttl: 3333333333 } ] 379 }, 380 381 { method: 'resolveSoa', 382 answers: [ 383 { 384 type: 'SOA', 385 nsname: 'ns1.example.com', 386 hostmaster: 'admin.example.com', 387 serial: 3210987654, 388 refresh: 900, 389 retry: 900, 390 expire: 1800, 391 minttl: 3333333333 392 } 393 ] 394 }, 395 ]; 396 397 const server = dgram.createSocket('udp4'); 398 399 server.on('message', common.mustCall((msg, { address, port }) => { 400 const parsed = dnstools.parseDNSPacket(msg); 401 const domain = parsed.questions[0].domain; 402 assert.strictEqual(domain, 'example.org'); 403 404 server.send(dnstools.writeDNSPacket({ 405 id: parsed.id, 406 questions: parsed.questions, 407 answers: cases[0].answers.map( 408 (answer) => Object.assign({ domain }, answer) 409 ), 410 }), port, address); 411 }, cases.length * 2)); 412 413 server.bind(0, common.mustCall(() => { 414 const address = server.address(); 415 dns.setServers([`127.0.0.1:${address.port}`]); 416 417 function validateResults(res) { 418 if (!Array.isArray(res)) 419 res = [res]; 420 421 assert.deepStrictEqual(res.map(tweakEntry), 422 cases[0].answers.map(tweakEntry)); 423 } 424 425 function tweakEntry(r) { 426 const ret = { ...r }; 427 428 const { method } = cases[0]; 429 430 // TTL values are only provided for A and AAAA entries. 431 if (!['A', 'AAAA'].includes(ret.type) && !/^resolve(4|6)?$/.test(method)) 432 delete ret.ttl; 433 434 if (method !== 'resolveAny') 435 delete ret.type; 436 437 return ret; 438 } 439 440 (async function nextCase() { 441 if (cases.length === 0) 442 return server.close(); 443 444 const { method, options } = cases[0]; 445 446 validateResults(await dnsPromises[method]('example.org', options)); 447 448 dns[method]('example.org', options, common.mustCall((err, res) => { 449 assert.ifError(err); 450 validateResults(res); 451 cases.shift(); 452 nextCase(); 453 })); 454 })(); 455 456 })); 457} 458