Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
dns: switch to getdns
  • Loading branch information
devsnek committed Apr 16, 2021
commit 2bb19bdce12e63eea99727e7bc8b355bcf0c6470
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
/src/cares_wrap.cc @nodejs/net
/src/connect_wrap.* @nodejs/net
/src/connection_wrap.* @nodejs/net
/src/node_dns.cc @nodejs/net
/src/node_dns.h @nodejs/net
/src/node_sockaddr* @nodejs/net
/src/tcp_wrap.* @nodejs/net
/src/udp_wrap.* @nodejs/net
Expand Down
3 changes: 0 additions & 3 deletions doc/api/dns.md
Original file line number Diff line number Diff line change
Expand Up @@ -651,9 +651,6 @@ dns.setServers([

An error will be thrown if an invalid address is provided.

The `dns.setServers()` method must not be called while a DNS query is in
progress.

The [`dns.setServers()`][] method affects only [`dns.resolve()`][],
`dns.resolve*()` and [`dns.reverse()`][] (and specifically *not*
[`dns.lookup()`][]).
Expand Down
31 changes: 15 additions & 16 deletions lib/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const {
ReflectApply,
} = primordials;

const cares = internalBinding('cares_wrap');
const { toASCII } = require('internal/idna');
const { isIP } = require('internal/net');
const { customPromisifyArgs } = require('internal/util');
Expand All @@ -41,7 +40,17 @@ const {
Resolver,
validateHints,
emitInvalidHostnameWarning,
AI_ADDRCONFIG,
AI_ALL,
AI_V4MAPPED,
} = require('internal/dns/utils');
const {
QueryReqWrap,
GetAddrInfoReqWrap,
GetNameInfoReqWrap,
getaddrinfo,
getnameinfo,
} = require('internal/dns/compat');
const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
Expand All @@ -54,12 +63,6 @@ const {
validateOneOf,
} = require('internal/validators');

const {
GetAddrInfoReqWrap,
GetNameInfoReqWrap,
QueryReqWrap,
} = cares;

const dnsException = errors.dnsException;

let promises = null; // Lazy loaded
Expand All @@ -71,7 +74,6 @@ function onlookup(err, addresses) {
this.callback(null, addresses[0], this.family || isIP(addresses[0]));
}


function onlookupall(err, addresses) {
if (err) {
return this.callback(dnsException(err, 'getaddrinfo', this.hostname));
Expand All @@ -89,7 +91,6 @@ function onlookupall(err, addresses) {
this.callback(null, addresses);
}


// Easy DNS A/AAAA look up
// lookup(hostname, [options,] callback)
function lookup(hostname, options, callback) {
Expand Down Expand Up @@ -150,7 +151,7 @@ function lookup(hostname, options, callback) {
req.hostname = hostname;
req.oncomplete = all ? onlookupall : onlookup;

const err = cares.getaddrinfo(
const err = getaddrinfo(
req, toASCII(hostname), family, hints, verbatim
);
if (err) {
Expand All @@ -163,7 +164,6 @@ function lookup(hostname, options, callback) {
ObjectDefineProperty(lookup, customPromisifyArgs,
{ value: ['address', 'family'], enumerable: false });


function onlookupservice(err, hostname, service) {
if (err)
return this.callback(dnsException(err, 'getnameinfo', this.hostname));
Expand Down Expand Up @@ -191,15 +191,14 @@ function lookupService(address, port, callback) {
req.port = port;
req.oncomplete = onlookupservice;

const err = cares.getnameinfo(req, address, port);
const err = getnameinfo(req, address, port);
if (err) throw dnsException(err, 'getnameinfo', address);
return req;
}

ObjectDefineProperty(lookupService, customPromisifyArgs,
{ value: ['hostname', 'service'], enumerable: false });


function onresolve(err, result, ttls) {
if (ttls && this.ttl)
result = ArrayPrototypeMap(
Expand Down Expand Up @@ -289,9 +288,9 @@ module.exports = {
setServers: defaultResolverSetServers,

// uv_getaddrinfo flags
ADDRCONFIG: cares.AI_ADDRCONFIG,
ALL: cares.AI_ALL,
V4MAPPED: cares.AI_V4MAPPED,
ADDRCONFIG: AI_ADDRCONFIG,
ALL: AI_ALL,
V4MAPPED: AI_V4MAPPED,

// ERROR CODES
NODATA: 'ENODATA',
Expand Down
249 changes: 249 additions & 0 deletions lib/internal/dns/compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
'use strict';

const {
ArrayPrototypeFilter,
ArrayPrototypeMap,
ArrayPrototypeSort,
JSONParse,
StringPrototypeReplace,
Symbol,
} = primordials;

const {
DNSWrap,
GetNameInfoReqWrap,
getnameinfo,
} = internalBinding('dns');
const { Buffer } = require('buffer');
const { dnsException } = require('internal/errors');
const { AsyncResource } = require('async_hooks');

const kResource = Symbol('kResource');
const kOnComplete = Symbol('kOnComplete');

class ReqWrap {
constructor(name) {
this[kResource] = new AsyncResource(name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the ReqWrap itself just extend from AsyncResource?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could, though that would add the AsyncResource api to the req wrap, which is returned from all the various api calls to the user.

this[kOnComplete] = undefined;
}

set oncomplete(f) {
this[kOnComplete] = function(...args) {
try {
return this[kResource].runInAsyncScope(f, this, ...args);
} finally {
this[kResource].emitDestroy();
}
};
}

get oncomplete() {
return this[kOnComplete];
}
}

class QueryReqWrap extends ReqWrap {
constructor() {
super('QUERYWRAP');
}
}

class GetAddrInfoReqWrap extends ReqWrap {
constructor() {
super('GETADDRINFOREQWRAP');
}
}

function unwrapBindata(s, isFQDN = false) {
if (typeof s !== 'string') {
s = Buffer.from(s).toString();
}
if (isFQDN) {
s = StringPrototypeReplace(s, /\.$/, '');
}
return s;
}

const DNS_MAP_FNS = {};
const DNS_MAP_TYPES = {};
[
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
['Any', 255, (answer, req) => {
let mapped = DNS_MAP_FNS[answer.type](answer, { ttl: true });
if (typeof mapped === 'string') {
mapped = { value: mapped };
} else if (DNS_MAP_TYPES[answer.type] === 'TXT') {
mapped = { entries: mapped };
}
mapped.type = DNS_MAP_TYPES[answer.type];
return mapped;
}],
['A', 1, (answer, req) => {
if (req.ttl) {
return {
ttl: answer.ttl,
address: unwrapBindata(answer.rdata.ipv4_address, true),
};
}
return unwrapBindata(answer.rdata.ipv4_address);
}],
['Aaaa', 28, (answer, req) => {
if (req.ttl) {
return {
ttl: answer.ttl,
address: unwrapBindata(answer.rdata.ipv6_address, true),
};
}
return unwrapBindata(answer.rdata.ipv6_address);
}],
['Caa', 257, (answer) => {
return {
critical: answer.rdata.flags,
[unwrapBindata(answer.rdata.tag)]: unwrapBindata(answer.rdata.value),
};
}],
['Cname', 5, (answer) => {
return answer.rdata.cname;
}],
['Mx', 15, (answer) => {
return {
priority: answer.rdata.preference,
exchange: unwrapBindata(answer.rdata.exchange, true),
};
}],
['Ns', 2, (answer) => {
return unwrapBindata(answer.rdata.nsdname, true);
}],
['Txt', 16, (answer) => {
return ArrayPrototypeMap(
answer.rdata.txt_strings,
(s) => unwrapBindata(s, true));
}],
['Srv', 33, (answer) => {
return {
priority: answer.rdata.priority,
weight: answer.rdata.weight,
port: answer.rdata.port,
name: unwrapBindata(answer.rdata.target),
};
}],
['Ptr', 12, (answer) => {
return unwrapBindata(answer.rdata.ptrdname, true);
}],
['Naptr', 35, (answer) => {
return {
order: answer.rdata.order,
preference: answer.rdata.preference,
flags: answer.rdata.flags,
service: answer.rdata.service,
regexp: unwrapBindata(answer.rdata.regexp),
replacement: unwrapBindata(answer.rdata.replacement),
};
}],
['Soa', 6, (answer) => {
return {
nsname: unwrapBindata(answer.rdata.mname, true),
hostmaster: unwrapBindata(answer.rdata.rname, true),
serial: answer.rdata.serial,
refresh: answer.rdata.refresh,
retry: answer.rdata.retry,
expire: answer.rdata.expire,
minttl: answer.rdata.minimum,
};
}],
].forEach(({ 0: name, 1: rr, 2: mapfn }) => {
DNS_MAP_TYPES[rr] = name.toUpperCase();
DNS_MAP_FNS[rr] = mapfn;
const bindingName = `query${name}`;
DNSWrap.prototype[bindingName] = function(req, hostname) {
this.getGeneral(hostname, rr)
.then((json) => {
const data = JSONParse(json);
let mapped;
if (name === 'Txt') {
mapped = ArrayPrototypeFilter(
ArrayPrototypeMap(
ArrayPrototypeFilter(
data.replies_tree[0].answer,
(a) => a.type === 16),
(a) => mapfn(a, req)),
(a) => a.length > 0);
} else {
mapped = ArrayPrototypeMap(
data.replies_tree[0].answer,
(a) => mapfn(a, req),
);
}
if (name === 'Soa') {
mapped = mapped[0];
}
return mapped;
}, (e) => {
throw dnsException(e, bindingName, name);
})
.then((v) => {
req.oncomplete(null, v);
}, (e) => {
req.oncomplete(e);
});
};
});

DNSWrap.prototype.getHostByAddr = function(req, address) {
this.getHostnames(address)
.then((v) => {
req.oncomplete(null, v);
})
.catch((e) => {
req.oncomplete(dnsException(e, 'getHostByAddr', address));
});
};

let implicitResolver;
function getImplicitResolver() {
if (!implicitResolver) {
implicitResolver = new DNSWrap(-1);
}
return implicitResolver;
}

function getaddrinfo(req, hostname, family, hints, verbatim) {
getImplicitResolver()
.getAddresses(hostname)
.then((json) => {
const data = JSONParse(json);
let addresses = data.just_address_answers;
if (!verbatim) {
ArrayPrototypeSort(addresses, (a, b) => {
if (a.address_type === b.address_type) {
return 0;
}
if (a.address_type === 'IPv4' && b.address_type === 'IPv6') {
return -1;
}
return 1;
});
}
if (family !== 0) {
const type = { 4: 'IPv4', 6: 'IPv6' }[family];
addresses = ArrayPrototypeFilter(
addresses, (a) => a.address_type === type);
}
return ArrayPrototypeMap(addresses, (a) => a.address_data);
}, (e) => {
throw dnsException(e, 'getaddrinfo', hostname);
})
.then((v) => {
req.oncomplete(null, v);
}, (e) => {
req.oncomplete(e);
});
}

module.exports = {
QueryReqWrap,
GetAddrInfoReqWrap,
GetNameInfoReqWrap,
getaddrinfo,
getnameinfo,
};
Loading