UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

355 lines (303 loc) 9.89 kB
// 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 };