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>
This commit is contained in:
XadillaX 2017-05-21 18:02:33 +08:00 committed by Anna Henningsen
parent c4a61b3ee5
commit 27de36926b
No known key found for this signature in database
GPG Key ID: D8B9F5AEAE84E4CF
5 changed files with 973 additions and 172 deletions

View File

@ -197,6 +197,7 @@ records. The type and structure of individual results varies based on `rrtype`:
| `'SOA'` | start of authority records | {Object} | [`dns.resolveSoa()`][] | | `'SOA'` | start of authority records | {Object} | [`dns.resolveSoa()`][] |
| `'SRV'` | service records | {Object} | [`dns.resolveSrv()`][] | | `'SRV'` | service records | {Object} | [`dns.resolveSrv()`][] |
| `'TXT'` | text records | {string} | [`dns.resolveTxt()`][] | | `'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 On error, `err` is an [`Error`][] object, where `err.code` is one of the
[DNS error codes](#dns_error_codes). [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 one record. Depending on the use case, these could be either joined together or
treated separately. 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) ## dns.reverse(ip, callback)
<!-- YAML <!-- YAML
added: v0.1.16 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.resolveSoa()`]: #dns_dns_resolvesoa_hostname_callback
[`dns.resolveSrv()`]: #dns_dns_resolvesrv_hostname_callback [`dns.resolveSrv()`]: #dns_dns_resolvesrv_hostname_callback
[`dns.resolveTxt()`]: #dns_dns_resolvetxt_hostname_callback [`dns.resolveTxt()`]: #dns_dns_resolvetxt_hostname_callback
[`dns.resolveAny()`]: #dns_dns_resolveany_hostname_callback
[DNS error codes]: #dns_error_codes [DNS error codes]: #dns_error_codes
[Implementation considerations section]: #dns_implementation_considerations [Implementation considerations section]: #dns_implementation_considerations
[supported `getaddrinfo` flags]: #dns_supported_getaddrinfo_flags [supported `getaddrinfo` flags]: #dns_supported_getaddrinfo_flags

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -105,6 +105,16 @@ namespace node {
V(dest_string, "dest") \ V(dest_string, "dest") \
V(detached_string, "detached") \ V(detached_string, "detached") \
V(disposed_string, "_disposed") \ 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(domain_string, "domain") \
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \ V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
V(exchange_string, "exchange") \ V(exchange_string, "exchange") \
@ -113,6 +123,7 @@ namespace node {
V(irq_string, "irq") \ V(irq_string, "irq") \
V(encoding_string, "encoding") \ V(encoding_string, "encoding") \
V(enter_string, "enter") \ V(enter_string, "enter") \
V(entries_string, "entries") \
V(env_pairs_string, "envPairs") \ V(env_pairs_string, "envPairs") \
V(errno_string, "errno") \ V(errno_string, "errno") \
V(error_string, "error") \ V(error_string, "error") \
@ -151,6 +162,7 @@ namespace node {
V(issuer_string, "issuer") \ V(issuer_string, "issuer") \
V(issuercert_string, "issuerCertificate") \ V(issuercert_string, "issuerCertificate") \
V(kill_signal_string, "killSignal") \ V(kill_signal_string, "killSignal") \
V(length_string, "length") \
V(mac_string, "mac") \ V(mac_string, "mac") \
V(max_buffer_string, "maxBuffer") \ V(max_buffer_string, "maxBuffer") \
V(message_string, "message") \ V(message_string, "message") \
@ -231,6 +243,7 @@ namespace node {
V(timeout_string, "timeout") \ V(timeout_string, "timeout") \
V(times_string, "times") \ V(times_string, "times") \
V(tls_ticket_string, "tlsTicket") \ V(tls_ticket_string, "tlsTicket") \
V(ttl_string, "ttl") \
V(type_string, "type") \ V(type_string, "type") \
V(uid_string, "uid") \ V(uid_string, "uid") \
V(unknown_string, "<unknown>") \ V(unknown_string, "<unknown>") \

View File

@ -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);
});