nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
355 lines (303 loc) • 9.89 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/dns/utils.js
import { codes as __codes__ } from "nstdlib/lib/internal/errors";
import { isIP } from "nstdlib/lib/internal/net";
import { getOptionValue } from "nstdlib/lib/internal/options";
import {
validateArray,
validateInt32,
validateOneOf,
validateString,
} from "nstdlib/lib/internal/validators";
import { namespace as __namespace__ } from "nstdlib/lib/internal/v8/startup_snapshot";
const {
ERR_DNS_SET_SERVERS_FAILED,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_IP_ADDRESS,
} = __codes__;
let binding;
function lazyBinding() {
binding ??= require("binding/cares_wrap");
return binding;
}
const IANA_DNS_PORT = 53;
const IPv6RE = /^\[([^[\]]*)\]/;
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
const { addSerializeCallback, addDeserializeCallback, isBuildingSnapshot } =
__namespace__;
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 kSetServersInteral = 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 Array.prototype.map.call(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 = [];
Array.prototype.forEach.call(servers, (serv, index) => {
validateString(serv, `servers[${index}]`);
let ipVersion = isIP(serv);
if (ipVersion !== 0)
return Array.prototype.push.call(newSet, [
ipVersion,
serv,
IANA_DNS_PORT,
]);
const match = RegExp.prototype.exec.call(IPv6RE, serv);
// Check for an IPv6 in brackets.
if (match) {
ipVersion = isIP(match[1]);
if (ipVersion !== 0) {
const port =
Number.parseInt(
RegExp.prototype[Symbol.replace].call(addrSplitRE, serv, "$2"),
) || IANA_DNS_PORT;
return Array.prototype.push.call(newSet, [ipVersion, match[1], port]);
}
}
// addr::port
const addrSplitMatch = RegExp.prototype.exec.call(addrSplitRE, serv);
if (addrSplitMatch) {
const hostIP = addrSplitMatch[1];
const port = addrSplitMatch[2] || IANA_DNS_PORT;
ipVersion = isIP(hostIP);
if (ipVersion !== 0) {
return Array.prototype.push.call(newSet, [
ipVersion,
hostIP,
Number.parseInt(port),
]);
}
}
throw new ERR_INVALID_IP_ADDRESS(serv);
});
this[kSetServersInteral](newSet, servers);
}
[kSetServersInteral](newSet, servers) {
const orig = Array.prototype.map.call(
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[kSetServersInteral](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",
"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();
Array.prototype.forEach.call(resolverKeys, (key) => {
target[key] = Function.prototype.bind.call(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.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",
};
export { bindDefaultResolver };
export { getDefaultResolver };
export { setDefaultResolver };
export { validateHints };
export { validateTimeout };
export { validateTries };
export { emitInvalidHostnameWarning };
export { getDefaultResultOrder };
export { setDefaultResultOrder };
export { errorCodes };
export { createResolverClass };
export { initializeDns };