mirror of
https://github.com/nodejs/node.git
synced 2025-05-06 00:30:06 +00:00

PR-URL: https://github.com/nodejs/node/pull/52983 Refs: https://github.com/nodejs/node/issues/39569 Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
360 lines
9.5 KiB
JavaScript
360 lines
9.5 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayPrototypeForEach,
|
|
ArrayPrototypeMap,
|
|
ArrayPrototypePush,
|
|
FunctionPrototypeBind,
|
|
NumberParseInt,
|
|
RegExpPrototypeExec,
|
|
RegExpPrototypeSymbolReplace,
|
|
Symbol,
|
|
} = primordials;
|
|
|
|
const {
|
|
codes: {
|
|
ERR_DNS_SET_SERVERS_FAILED,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_INVALID_IP_ADDRESS,
|
|
},
|
|
} = require('internal/errors');
|
|
const { isIP } = require('internal/net');
|
|
const { getOptionValue } = require('internal/options');
|
|
const {
|
|
validateArray,
|
|
validateInt32,
|
|
validateOneOf,
|
|
validateString,
|
|
} = require('internal/validators');
|
|
let binding;
|
|
function lazyBinding() {
|
|
binding ??= internalBinding('cares_wrap');
|
|
return binding;
|
|
}
|
|
const IANA_DNS_PORT = 53;
|
|
const IPv6RE = /^\[([^[\]]*)\]/;
|
|
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
|
|
|
|
const {
|
|
namespace: {
|
|
addSerializeCallback,
|
|
addDeserializeCallback,
|
|
isBuildingSnapshot,
|
|
},
|
|
} = require('internal/v8/startup_snapshot');
|
|
|
|
function validateTimeout(options) {
|
|
const { timeout = -1 } = { ...options };
|
|
validateInt32(timeout, 'options.timeout', -1);
|
|
return timeout;
|
|
}
|
|
|
|
function validateTries(options) {
|
|
const { tries = 4 } = { ...options };
|
|
validateInt32(tries, 'options.tries', 1);
|
|
return tries;
|
|
}
|
|
|
|
const kSerializeResolver = Symbol('dns:resolver:serialize');
|
|
const kDeserializeResolver = Symbol('dns:resolver:deserialize');
|
|
const kSnapshotStates = Symbol('dns:resolver:config');
|
|
const kInitializeHandle = Symbol('dns:resolver:initializeHandle');
|
|
const kSetServersInternal = Symbol('dns:resolver:setServers');
|
|
|
|
// Resolver instances correspond 1:1 to c-ares channels.
|
|
|
|
class ResolverBase {
|
|
constructor(options = undefined) {
|
|
const timeout = validateTimeout(options);
|
|
const tries = validateTries(options);
|
|
// If we are building snapshot, save the states of the resolver along
|
|
// the way.
|
|
if (isBuildingSnapshot()) {
|
|
this[kSnapshotStates] = { timeout, tries };
|
|
}
|
|
this[kInitializeHandle](timeout, tries);
|
|
}
|
|
|
|
[kInitializeHandle](timeout, tries) {
|
|
const { ChannelWrap } = lazyBinding();
|
|
this._handle = new ChannelWrap(timeout, tries);
|
|
}
|
|
|
|
cancel() {
|
|
this._handle.cancel();
|
|
}
|
|
|
|
getServers() {
|
|
return ArrayPrototypeMap(this._handle.getServers() || [], (val) => {
|
|
if (!val[1] || val[1] === IANA_DNS_PORT)
|
|
return val[0];
|
|
|
|
const host = isIP(val[0]) === 6 ? `[${val[0]}]` : val[0];
|
|
return `${host}:${val[1]}`;
|
|
});
|
|
}
|
|
|
|
setServers(servers) {
|
|
validateArray(servers, 'servers');
|
|
|
|
// Cache the original servers because in the event of an error while
|
|
// setting the servers, c-ares won't have any servers available for
|
|
// resolution.
|
|
const newSet = [];
|
|
ArrayPrototypeForEach(servers, (serv, index) => {
|
|
validateString(serv, `servers[${index}]`);
|
|
let ipVersion = isIP(serv);
|
|
|
|
if (ipVersion !== 0)
|
|
return ArrayPrototypePush(newSet, [ipVersion, serv, IANA_DNS_PORT]);
|
|
|
|
const match = RegExpPrototypeExec(IPv6RE, serv);
|
|
|
|
// Check for an IPv6 in brackets.
|
|
if (match) {
|
|
ipVersion = isIP(match[1]);
|
|
|
|
if (ipVersion !== 0) {
|
|
const port = NumberParseInt(
|
|
RegExpPrototypeSymbolReplace(addrSplitRE, serv, '$2')) || IANA_DNS_PORT;
|
|
return ArrayPrototypePush(newSet, [ipVersion, match[1], port]);
|
|
}
|
|
}
|
|
|
|
// addr::port
|
|
const addrSplitMatch = RegExpPrototypeExec(addrSplitRE, serv);
|
|
|
|
if (addrSplitMatch) {
|
|
const hostIP = addrSplitMatch[1];
|
|
const port = addrSplitMatch[2] || IANA_DNS_PORT;
|
|
|
|
ipVersion = isIP(hostIP);
|
|
|
|
if (ipVersion !== 0) {
|
|
return ArrayPrototypePush(
|
|
newSet, [ipVersion, hostIP, NumberParseInt(port)]);
|
|
}
|
|
}
|
|
|
|
throw new ERR_INVALID_IP_ADDRESS(serv);
|
|
});
|
|
|
|
this[kSetServersInternal](newSet, servers);
|
|
}
|
|
|
|
[kSetServersInternal](newSet, servers) {
|
|
const orig = ArrayPrototypeMap(this._handle.getServers() || [], (val) => {
|
|
val.unshift(isIP(val[0]));
|
|
return val;
|
|
});
|
|
const errorNumber = this._handle.setServers(newSet);
|
|
|
|
if (errorNumber !== 0) {
|
|
// Reset the servers to the old servers, because ares probably unset them.
|
|
this._handle.setServers(orig);
|
|
const { strerror } = lazyBinding();
|
|
const err = strerror(errorNumber);
|
|
throw new ERR_DNS_SET_SERVERS_FAILED(err, servers);
|
|
}
|
|
|
|
if (isBuildingSnapshot()) {
|
|
this[kSnapshotStates].servers = newSet;
|
|
}
|
|
}
|
|
|
|
|
|
setLocalAddress(ipv4, ipv6) {
|
|
validateString(ipv4, 'ipv4');
|
|
|
|
if (ipv6 !== undefined) {
|
|
validateString(ipv6, 'ipv6');
|
|
}
|
|
|
|
this._handle.setLocalAddress(ipv4, ipv6);
|
|
|
|
if (isBuildingSnapshot()) {
|
|
this[kSnapshotStates].localAddress = { ipv4, ipv6 };
|
|
}
|
|
}
|
|
|
|
// TODO(joyeecheung): consider exposing this if custom DNS resolvers
|
|
// end up being useful for snapshot users.
|
|
[kSerializeResolver]() {
|
|
this._handle = null; // We'll restore it during deserialization.
|
|
addDeserializeCallback(function deserializeResolver(resolver) {
|
|
resolver[kDeserializeResolver]();
|
|
}, this);
|
|
}
|
|
|
|
[kDeserializeResolver]() {
|
|
const { timeout, tries, localAddress, servers } = this[kSnapshotStates];
|
|
this[kInitializeHandle](timeout, tries);
|
|
if (localAddress) {
|
|
const { ipv4, ipv6 } = localAddress;
|
|
this._handle.setLocalAddress(ipv4, ipv6);
|
|
}
|
|
if (servers) {
|
|
this[kSetServersInternal](servers, servers);
|
|
}
|
|
}
|
|
}
|
|
|
|
let defaultResolver;
|
|
let dnsOrder;
|
|
|
|
function initializeDns() {
|
|
const orderFromCLI = getOptionValue('--dns-result-order');
|
|
if (!orderFromCLI) {
|
|
dnsOrder ??= 'verbatim';
|
|
} else {
|
|
// Allow the deserialized application to override order from CLI.
|
|
validateOneOf(orderFromCLI, '--dns-result-order', ['verbatim', 'ipv4first', 'ipv6first']);
|
|
dnsOrder = orderFromCLI;
|
|
}
|
|
|
|
if (!isBuildingSnapshot()) {
|
|
return;
|
|
}
|
|
|
|
addSerializeCallback(() => {
|
|
defaultResolver?.[kSerializeResolver]();
|
|
});
|
|
}
|
|
|
|
const resolverKeys = [
|
|
'getServers',
|
|
'resolve',
|
|
'resolve4',
|
|
'resolve6',
|
|
'resolveAny',
|
|
'resolveCaa',
|
|
'resolveCname',
|
|
'resolveMx',
|
|
'resolveNaptr',
|
|
'resolveNs',
|
|
'resolvePtr',
|
|
'resolveSoa',
|
|
'resolveSrv',
|
|
'resolveTlsa',
|
|
'resolveTxt',
|
|
'reverse',
|
|
];
|
|
|
|
function getDefaultResolver() {
|
|
// We do this here instead of pre-execution so that the default resolver is
|
|
// only ever created when the user loads any dns module.
|
|
if (defaultResolver === undefined) {
|
|
defaultResolver = new ResolverBase();
|
|
}
|
|
return defaultResolver;
|
|
}
|
|
|
|
function setDefaultResolver(resolver) {
|
|
defaultResolver = resolver;
|
|
}
|
|
|
|
function bindDefaultResolver(target, source) {
|
|
const defaultResolver = getDefaultResolver();
|
|
ArrayPrototypeForEach(resolverKeys, (key) => {
|
|
target[key] = FunctionPrototypeBind(source[key], defaultResolver);
|
|
});
|
|
}
|
|
|
|
function validateHints(hints) {
|
|
const { AI_ADDRCONFIG, AI_ALL, AI_V4MAPPED } = lazyBinding();
|
|
if ((hints & ~(AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED)) !== 0) {
|
|
throw new ERR_INVALID_ARG_VALUE('hints', hints);
|
|
}
|
|
}
|
|
|
|
let invalidHostnameWarningEmitted = false;
|
|
function emitInvalidHostnameWarning(hostname) {
|
|
if (!invalidHostnameWarningEmitted) {
|
|
process.emitWarning(
|
|
`The provided hostname "${hostname}" is not a valid ` +
|
|
'hostname, and is supported in the dns module solely for compatibility.',
|
|
'DeprecationWarning',
|
|
'DEP0118',
|
|
);
|
|
invalidHostnameWarningEmitted = true;
|
|
}
|
|
}
|
|
|
|
function setDefaultResultOrder(value) {
|
|
validateOneOf(value, 'dnsOrder', ['verbatim', 'ipv4first', 'ipv6first']);
|
|
dnsOrder = value;
|
|
}
|
|
|
|
function getDefaultResultOrder() {
|
|
return dnsOrder;
|
|
}
|
|
|
|
function createResolverClass(resolver) {
|
|
const resolveMap = { __proto__: null };
|
|
|
|
class Resolver extends ResolverBase {}
|
|
|
|
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
|
|
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
|
|
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
|
|
Resolver.prototype.resolveCaa = resolveMap.CAA = resolver('queryCaa');
|
|
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
|
|
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
|
|
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
|
|
Resolver.prototype.resolveTlsa = resolveMap.TLSA = resolver('queryTlsa');
|
|
Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt');
|
|
Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv');
|
|
Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr');
|
|
Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
|
|
Resolver.prototype.resolveSoa = resolveMap.SOA = resolver('querySoa');
|
|
Resolver.prototype.reverse = resolver('getHostByAddr');
|
|
|
|
return {
|
|
resolveMap,
|
|
Resolver,
|
|
};
|
|
}
|
|
|
|
// ERROR CODES
|
|
const errorCodes = {
|
|
NODATA: 'ENODATA',
|
|
FORMERR: 'EFORMERR',
|
|
SERVFAIL: 'ESERVFAIL',
|
|
NOTFOUND: 'ENOTFOUND',
|
|
NOTIMP: 'ENOTIMP',
|
|
REFUSED: 'EREFUSED',
|
|
BADQUERY: 'EBADQUERY',
|
|
BADNAME: 'EBADNAME',
|
|
BADFAMILY: 'EBADFAMILY',
|
|
BADRESP: 'EBADRESP',
|
|
CONNREFUSED: 'ECONNREFUSED',
|
|
TIMEOUT: 'ETIMEOUT',
|
|
EOF: 'EOF',
|
|
FILE: 'EFILE',
|
|
NOMEM: 'ENOMEM',
|
|
DESTRUCTION: 'EDESTRUCTION',
|
|
BADSTR: 'EBADSTR',
|
|
BADFLAGS: 'EBADFLAGS',
|
|
NONAME: 'ENONAME',
|
|
BADHINTS: 'EBADHINTS',
|
|
NOTINITIALIZED: 'ENOTINITIALIZED',
|
|
LOADIPHLPAPI: 'ELOADIPHLPAPI',
|
|
ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS',
|
|
CANCELLED: 'ECANCELLED',
|
|
};
|
|
|
|
module.exports = {
|
|
bindDefaultResolver,
|
|
getDefaultResolver,
|
|
setDefaultResolver,
|
|
validateHints,
|
|
validateTimeout,
|
|
validateTries,
|
|
emitInvalidHostnameWarning,
|
|
getDefaultResultOrder,
|
|
setDefaultResultOrder,
|
|
errorCodes,
|
|
createResolverClass,
|
|
initializeDns,
|
|
};
|