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