Browse Source

dns: add resolveAny support

`dns.resolveAny` and `dns.resolve` with `"ANY"` has the similar behavior
like `$ dig <domain> any` and returns an array with several types of
records.

`dns.resolveAny` parses the result packet by several rules in turn.

Supported types:

* A
* AAAA
* CNAME
* MX
* NAPTR
* NS
* PTR
* SOA
* SRV
* TXT

Fixes: https://github.com/nodejs/node/issues/2848
PR-URL: https://github.com/nodejs/node/pull/13137
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
v6
XadillaX 8 years ago
committed by Anna Henningsen
parent
commit
27de36926b
No known key found for this signature in database GPG Key ID: D8B9F5AEAE84E4CF
  1. 47
      doc/api/dns.md
  2. 2
      lib/dns.js
  3. 889
      src/cares_wrap.cc
  4. 13
      src/env.h
  5. 198
      test/internet/test-dns-any.js

47
doc/api/dns.md

@ -197,6 +197,7 @@ records. The type and structure of individual results varies based on `rrtype`:
| `'SOA'` | start of authority records | {Object} | [`dns.resolveSoa()`][] |
| `'SRV'` | service records | {Object} | [`dns.resolveSrv()`][] |
| `'TXT'` | text records | {string} | [`dns.resolveTxt()`][] |
| `'ANY'` | any records | {Object} | [`dns.resolveAny()`][] |
On error, `err` is an [`Error`][] object, where `err.code` is one of the
[DNS error codes](#dns_error_codes).
@ -417,6 +418,51 @@ is a two-dimensional array of the text records available for `hostname` (e.g.,
one record. Depending on the use case, these could be either joined together or
treated separately.
## dns.resolveAny(hostname, callback)
- `hostname` {string}
- `callback` {Function}
- `err` {Error}
- `ret` {Object[][]}
Uses the DNS protocol to resolve all records (also known as `ANY` or `*` query).
The `ret` argument passed to the `callback` function will be an array containing
various types of records. Each object has a property `type` that indicates the
type of the current record. And depending on the `type`, additional properties
will be present on the object:
| Type | Properties |
|------|------------|
| `"A"` | `address` / `ttl` |
| `"AAAA"` | `address` / `ttl` |
| `"CNAME"` | `value` |
| `"MX"` | Refer to [`dns.resolveMx()`][] |
| `"NAPTR"` | Refer to [`dns.resolveNaptr()`][] |
| `"NS"` | `value` |
| `"PTR"` | `value` |
| `"SOA"` | Refer to [`dns.resolveSoa()`][] |
| `"SRV"` | Refer to [`dns.resolveSrv()`][] |
| `"TXT"` | This type of record contains an array property called `entries` which refers to [`dns.resolveTxt()`][], eg. `{ entries: ['...'], type: 'TXT' }` |
Here is a example of the `ret` object passed to the callback:
<!-- eslint-disable -->
```js
[ { type: 'A', address: '127.0.0.1', ttl: 299 },
{ type: 'CNAME', value: 'example.com' },
{ type: 'MX', exchange: 'alt4.aspmx.l.example.com', priority: 50 },
{ type: 'NS', value: 'ns1.example.com', type: 'NS' },
{ type: 'TXT', entries: [ 'v=spf1 include:_spf.example.com ~all' ] },
{ type: 'SOA',
nsname: 'ns1.example.com',
hostmaster: 'admin.example.com',
serial: 156696742,
refresh: 900,
retry: 900,
expire: 1800,
minttl: 60 } ]
```
## dns.reverse(ip, callback)
<!-- YAML
added: v0.1.16
@ -531,6 +577,7 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
[`dns.resolveSoa()`]: #dns_dns_resolvesoa_hostname_callback
[`dns.resolveSrv()`]: #dns_dns_resolvesrv_hostname_callback
[`dns.resolveTxt()`]: #dns_dns_resolvetxt_hostname_callback
[`dns.resolveAny()`]: #dns_dns_resolveany_hostname_callback
[DNS error codes]: #dns_error_codes
[Implementation considerations section]: #dns_implementation_considerations
[supported `getaddrinfo` flags]: #dns_supported_getaddrinfo_flags

2
lib/dns.js

@ -268,6 +268,7 @@ function resolver(bindingName) {
var resolveMap = Object.create(null);
resolveMap.ANY = resolver('queryAny');
resolveMap.A = resolver('queryA');
resolveMap.AAAA = resolver('queryAaaa');
resolveMap.CNAME = resolver('queryCname');
@ -349,6 +350,7 @@ module.exports = {
getServers,
setServers,
resolve,
resolveAny: resolveMap.ANY,
resolve4: resolveMap.A,
resolve6: resolveMap.AAAA,
resolveCname: resolveMap.CNAME,

889
src/cares_wrap.cc

File diff suppressed because it is too large

13
src/env.h

@ -105,6 +105,16 @@ namespace node {
V(dest_string, "dest") \
V(detached_string, "detached") \
V(disposed_string, "_disposed") \
V(dns_a_string, "A") \
V(dns_aaaa_string, "AAAA") \
V(dns_cname_string, "CNAME") \
V(dns_mx_string, "MX") \
V(dns_naptr_string, "NAPTR") \
V(dns_ns_string, "NS") \
V(dns_ptr_string, "PTR") \
V(dns_soa_string, "SOA") \
V(dns_srv_string, "SRV") \
V(dns_txt_string, "TXT") \
V(domain_string, "domain") \
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
V(exchange_string, "exchange") \
@ -113,6 +123,7 @@ namespace node {
V(irq_string, "irq") \
V(encoding_string, "encoding") \
V(enter_string, "enter") \
V(entries_string, "entries") \
V(env_pairs_string, "envPairs") \
V(errno_string, "errno") \
V(error_string, "error") \
@ -151,6 +162,7 @@ namespace node {
V(issuer_string, "issuer") \
V(issuercert_string, "issuerCertificate") \
V(kill_signal_string, "killSignal") \
V(length_string, "length") \
V(mac_string, "mac") \
V(max_buffer_string, "maxBuffer") \
V(message_string, "message") \
@ -231,6 +243,7 @@ namespace node {
V(timeout_string, "timeout") \
V(times_string, "times") \
V(tls_ticket_string, "tlsTicket") \
V(ttl_string, "ttl") \
V(type_string, "type") \
V(uid_string, "uid") \
V(unknown_string, "<unknown>") \

198
test/internet/test-dns-any.js

@ -0,0 +1,198 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const dns = require('dns');
const net = require('net');
let running = false;
const queue = [];
const isIPv4 = net.isIPv4;
const isIPv6 = net.isIPv6;
dns.setServers([ '8.8.8.8', '8.8.4.4' ]);
function checkWrap(req) {
assert.ok(typeof req === 'object');
}
const checkers = {
checkA(r) {
assert.ok(isIPv4(r.address));
assert.strictEqual(typeof r.ttl, 'number');
assert.strictEqual(r.type, 'A');
},
checkAAAA(r) {
assert.ok(isIPv6(r.address));
assert.strictEqual(typeof r.ttl, 'number');
assert.strictEqual(r.type, 'AAAA');
},
checkCNAME(r) {
assert.ok(r.value);
assert.strictEqual(typeof r.value, 'string');
assert.strictEqual(r.type, 'CNAME');
},
checkMX(r) {
assert.strictEqual(typeof r.exchange, 'string');
assert.strictEqual(typeof r.priority, 'number');
assert.strictEqual(r.type, 'MX');
},
checkNAPTR(r) {
assert.strictEqual(typeof r.flags, 'string');
assert.strictEqual(typeof r.service, 'string');
assert.strictEqual(typeof r.regexp, 'string');
assert.strictEqual(typeof r.replacement, 'string');
assert.strictEqual(typeof r.order, 'number');
assert.strictEqual(typeof r.preference, 'number');
assert.strictEqual(r.type, 'NAPTR');
},
checkNS(r) {
assert.strictEqual(typeof r.value, 'string');
assert.strictEqual(r.type, 'NS');
},
checkPTR(r) {
assert.strictEqual(typeof r.value, 'string');
assert.strictEqual(r.type, 'PTR');
},
checkTXT(r) {
assert.ok(Array.isArray(r.entries));
assert.ok(r.entries.length > 0);
r.entries.forEach((txt) => {
assert.strictEqual(txt.indexOf('v=spf1'), 0);
});
assert.strictEqual(r.type, 'TXT');
},
checkSOA(r) {
assert.strictEqual(typeof r.nsname, 'string');
assert.strictEqual(typeof r.hostmaster, 'string');
assert.strictEqual(typeof r.serial, 'number');
assert.strictEqual(typeof r.refresh, 'number');
assert.strictEqual(typeof r.retry, 'number');
assert.strictEqual(typeof r.expire, 'number');
assert.strictEqual(typeof r.minttl, 'number');
assert.strictEqual(r.type, 'SOA');
},
checkSRV(r) {
assert.strictEqual(typeof r.name, 'string');
assert.strictEqual(typeof r.port, 'number');
assert.strictEqual(typeof r.priority, 'number');
assert.strictEqual(typeof r.weight, 'number');
assert.strictEqual(r.type, 'SRV');
}
};
function TEST(f) {
function next() {
const f = queue.shift();
if (f) {
running = true;
f(done);
}
}
function done() {
running = false;
process.nextTick(next);
}
queue.push(f);
if (!running) {
next();
}
}
TEST(function test_google(done) {
const req = dns.resolve(
'google.com',
'ANY',
common.mustCall(function(err, ret) {
assert.ifError(err);
assert.ok(Array.isArray(ret));
assert.ok(ret.length > 0);
/* current google.com has A / AAAA / MX / NS / TXT and SOA records */
const types = {};
ret.forEach((obj) => {
types[obj.type] = true;
checkers[`check${obj.type}`](obj);
});
assert.ok(
types.A && types.AAAA && types.MX &&
types.NS && types.TXT && types.SOA);
done();
}));
checkWrap(req);
});
TEST(function test_sip2sip_for_naptr(done) {
const req = dns.resolve(
'sip2sip.info',
'ANY',
common.mustCall(function(err, ret) {
assert.ifError(err);
assert.ok(Array.isArray(ret));
assert.ok(ret.length > 0);
/* current sip2sip.info has A / NS / NAPTR and SOA records */
const types = {};
ret.forEach((obj) => {
types[obj.type] = true;
checkers[`check${obj.type}`](obj);
});
assert.ok(types.A && types.NS && types.NAPTR && types.SOA);
done();
}));
checkWrap(req);
});
TEST(function test_google_for_cname_and_srv(done) {
const req = dns.resolve(
'_jabber._tcp.google.com',
'ANY',
common.mustCall(function(err, ret) {
assert.ifError(err);
assert.ok(Array.isArray(ret));
assert.ok(ret.length > 0);
const types = {};
ret.forEach((obj) => {
types[obj.type] = true;
checkers[`check${obj.type}`](obj);
});
assert.ok(types.SRV);
done();
}));
checkWrap(req);
});
TEST(function test_ptr(done) {
const req = dns.resolve(
'8.8.8.8.in-addr.arpa',
'ANY',
common.mustCall(function(err, ret) {
assert.ifError(err);
assert.ok(Array.isArray(ret));
assert.ok(ret.length > 0);
/* current 8.8.8.8.in-addr.arpa has PTR record */
const types = {};
ret.forEach((obj) => {
types[obj.type] = true;
checkers[`check${obj.type}`](obj);
});
assert.ok(types.PTR);
done();
}));
checkWrap(req);
});
Loading…
Cancel
Save