mirror of
https://github.com/denoland/deno.git
synced 2024-12-27 17:49:08 -05:00
d59599fc18
partially unblocks #25470 This PR aligns the resolution of `localhost` hostname to Node.js behavior. In Node.js `dns.lookup("localhost", (_, addr) => console.log(addr))` prints ipv6 address `::1`, but it prints ipv4 address `127.0.0.1` in Deno. That difference causes some errors in the work of enabling `createConnection` option in `http.request` (#25470). This PR fixes the issue by aligning `dns.lookup` behavior to Node.js. This PR also changes the following behaviors (resolving TODOs): - `http.createServer` now listens on ipv6 address `[::]` by default on linux/mac - `net.createServer` now listens on ipv6 address `[::]` by default on linux/mac These changes are also alignments to Node.js behaviors.
449 lines
11 KiB
TypeScript
449 lines
11 KiB
TypeScript
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
|
// deno-lint-ignore-file prefer-primordials
|
|
|
|
import { getOptionValue } from "ext:deno_node/internal/options.ts";
|
|
import { emitWarning } from "node:process";
|
|
import {
|
|
AI_ADDRCONFIG,
|
|
AI_ALL,
|
|
AI_V4MAPPED,
|
|
} from "ext:deno_node/internal_binding/ares.ts";
|
|
import {
|
|
ChannelWrap,
|
|
strerror,
|
|
} from "ext:deno_node/internal_binding/cares_wrap.ts";
|
|
import {
|
|
ERR_DNS_SET_SERVERS_FAILED,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_INVALID_IP_ADDRESS,
|
|
} from "ext:deno_node/internal/errors.ts";
|
|
import type { ErrnoException } from "ext:deno_node/internal/errors.ts";
|
|
import {
|
|
validateArray,
|
|
validateInt32,
|
|
validateOneOf,
|
|
validateString,
|
|
} from "ext:deno_node/internal/validators.mjs";
|
|
import { isIP } from "ext:deno_node/internal/net.ts";
|
|
|
|
export interface LookupOptions {
|
|
family?: number | undefined;
|
|
hints?: number | undefined;
|
|
all?: boolean | undefined;
|
|
verbatim?: boolean | undefined;
|
|
}
|
|
|
|
export interface LookupOneOptions extends LookupOptions {
|
|
all?: false | undefined;
|
|
}
|
|
|
|
export interface LookupAllOptions extends LookupOptions {
|
|
all: true;
|
|
}
|
|
|
|
export interface LookupAddress {
|
|
address: string | null;
|
|
family: number;
|
|
}
|
|
|
|
export function isLookupOptions(
|
|
options: unknown,
|
|
): options is LookupOptions | undefined {
|
|
return typeof options === "object" || typeof options === "undefined";
|
|
}
|
|
|
|
export function isLookupCallback(
|
|
options: unknown,
|
|
): options is (...args: unknown[]) => void {
|
|
return typeof options === "function";
|
|
}
|
|
|
|
export function isFamily(options: unknown): options is number {
|
|
return typeof options === "number";
|
|
}
|
|
|
|
export interface ResolveOptions {
|
|
ttl?: boolean;
|
|
}
|
|
|
|
export interface ResolveWithTtlOptions extends ResolveOptions {
|
|
ttl: true;
|
|
}
|
|
|
|
export interface RecordWithTtl {
|
|
address: string;
|
|
ttl: number;
|
|
}
|
|
|
|
export interface AnyARecord extends RecordWithTtl {
|
|
type: "A";
|
|
}
|
|
|
|
export interface AnyAaaaRecord extends RecordWithTtl {
|
|
type: "AAAA";
|
|
}
|
|
|
|
export interface CaaRecord {
|
|
critial: number;
|
|
issue?: string | undefined;
|
|
issuewild?: string | undefined;
|
|
iodef?: string | undefined;
|
|
contactemail?: string | undefined;
|
|
contactphone?: string | undefined;
|
|
}
|
|
|
|
export interface MxRecord {
|
|
priority: number;
|
|
exchange: string;
|
|
}
|
|
|
|
export interface AnyMxRecord extends MxRecord {
|
|
type: "MX";
|
|
}
|
|
|
|
export interface NaptrRecord {
|
|
flags: string;
|
|
service: string;
|
|
regexp: string;
|
|
replacement: string;
|
|
order: number;
|
|
preference: number;
|
|
}
|
|
|
|
export interface AnyNaptrRecord extends NaptrRecord {
|
|
type: "NAPTR";
|
|
}
|
|
|
|
export interface SoaRecord {
|
|
nsname: string;
|
|
hostmaster: string;
|
|
serial: number;
|
|
refresh: number;
|
|
retry: number;
|
|
expire: number;
|
|
minttl: number;
|
|
}
|
|
|
|
export interface AnySoaRecord extends SoaRecord {
|
|
type: "SOA";
|
|
}
|
|
|
|
export interface SrvRecord {
|
|
priority: number;
|
|
weight: number;
|
|
port: number;
|
|
name: string;
|
|
}
|
|
|
|
export interface AnySrvRecord extends SrvRecord {
|
|
type: "SRV";
|
|
}
|
|
|
|
export interface AnyTxtRecord {
|
|
type: "TXT";
|
|
entries: string[];
|
|
}
|
|
|
|
export interface AnyNsRecord {
|
|
type: "NS";
|
|
value: string;
|
|
}
|
|
|
|
export interface AnyPtrRecord {
|
|
type: "PTR";
|
|
value: string;
|
|
}
|
|
|
|
export interface AnyCnameRecord {
|
|
type: "CNAME";
|
|
value: string;
|
|
}
|
|
|
|
export type AnyRecord =
|
|
| AnyARecord
|
|
| AnyAaaaRecord
|
|
| AnyCnameRecord
|
|
| AnyMxRecord
|
|
| AnyNaptrRecord
|
|
| AnyNsRecord
|
|
| AnyPtrRecord
|
|
| AnySoaRecord
|
|
| AnySrvRecord
|
|
| AnyTxtRecord;
|
|
|
|
export type Records =
|
|
| string[]
|
|
| AnyRecord[]
|
|
| MxRecord[]
|
|
| NaptrRecord[]
|
|
| SoaRecord
|
|
| SrvRecord[]
|
|
| string[];
|
|
|
|
export type ResolveCallback = (
|
|
err: ErrnoException | null,
|
|
addresses: Records,
|
|
) => void;
|
|
|
|
export function isResolveCallback(
|
|
callback: unknown,
|
|
): callback is ResolveCallback {
|
|
return typeof callback === "function";
|
|
}
|
|
|
|
const IANA_DNS_PORT = 53;
|
|
const IPv6RE = /^\[([^[\]]*)\]/;
|
|
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
|
|
|
|
export function validateTimeout(options?: { timeout?: number }) {
|
|
const { timeout = -1 } = { ...options };
|
|
validateInt32(timeout, "options.timeout", -1, 2 ** 31 - 1);
|
|
return timeout;
|
|
}
|
|
|
|
export function validateTries(options?: { tries?: number }) {
|
|
const { tries = 4 } = { ...options };
|
|
validateInt32(tries, "options.tries", 1, 2 ** 31 - 1);
|
|
return tries;
|
|
}
|
|
|
|
export interface ResolverOptions {
|
|
timeout?: number | undefined;
|
|
/**
|
|
* @default 4
|
|
*/
|
|
tries?: number;
|
|
}
|
|
|
|
/**
|
|
* An independent resolver for DNS requests.
|
|
*
|
|
* Creating a new resolver uses the default server settings. Setting
|
|
* the servers used for a resolver using `resolver.setServers()` does not affect
|
|
* other resolvers:
|
|
*
|
|
* ```js
|
|
* const { Resolver } = require('dns');
|
|
* const resolver = new Resolver();
|
|
* resolver.setServers(['4.4.4.4']);
|
|
*
|
|
* // This request will use the server at 4.4.4.4, independent of global settings.
|
|
* resolver.resolve4('example.org', (err, addresses) => {
|
|
* // ...
|
|
* });
|
|
* ```
|
|
*
|
|
* The following methods from the `dns` module are available:
|
|
*
|
|
* - `resolver.getServers()`
|
|
* - `resolver.resolve()`
|
|
* - `resolver.resolve4()`
|
|
* - `resolver.resolve6()`
|
|
* - `resolver.resolveAny()`
|
|
* - `resolver.resolveCaa()`
|
|
* - `resolver.resolveCname()`
|
|
* - `resolver.resolveMx()`
|
|
* - `resolver.resolveNaptr()`
|
|
* - `resolver.resolveNs()`
|
|
* - `resolver.resolvePtr()`
|
|
* - `resolver.resolveSoa()`
|
|
* - `resolver.resolveSrv()`
|
|
* - `resolver.resolveTxt()`
|
|
* - `resolver.reverse()`
|
|
* - `resolver.setServers()`
|
|
*/
|
|
export class Resolver {
|
|
_handle!: ChannelWrap;
|
|
|
|
constructor(options?: ResolverOptions) {
|
|
const timeout = validateTimeout(options);
|
|
const tries = validateTries(options);
|
|
this._handle = new ChannelWrap(timeout, tries);
|
|
}
|
|
|
|
cancel() {
|
|
this._handle.cancel();
|
|
}
|
|
|
|
getServers(): string[] {
|
|
return this._handle.getServers().map((val: [string, number]) => {
|
|
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: ReadonlyArray<string>) {
|
|
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 orig = this._handle.getServers();
|
|
const newSet: [number, string, number][] = [];
|
|
|
|
servers.forEach((serv, index) => {
|
|
validateString(serv, `servers[${index}]`);
|
|
let ipVersion = isIP(serv);
|
|
|
|
if (ipVersion !== 0) {
|
|
return newSet.push([ipVersion, serv, IANA_DNS_PORT]);
|
|
}
|
|
|
|
const match = serv.match(IPv6RE);
|
|
|
|
// Check for an IPv6 in brackets.
|
|
if (match) {
|
|
ipVersion = isIP(match[1]);
|
|
|
|
if (ipVersion !== 0) {
|
|
const port = Number.parseInt(serv.replace(addrSplitRE, "$2")) ||
|
|
IANA_DNS_PORT;
|
|
|
|
return newSet.push([ipVersion, match[1], port]);
|
|
}
|
|
}
|
|
|
|
// addr::port
|
|
const addrSplitMatch = serv.match(addrSplitRE);
|
|
|
|
if (addrSplitMatch) {
|
|
const hostIP = addrSplitMatch[1];
|
|
const port = addrSplitMatch[2] || `${IANA_DNS_PORT}`;
|
|
|
|
ipVersion = isIP(hostIP);
|
|
|
|
if (ipVersion !== 0) {
|
|
return newSet.push([ipVersion, hostIP, Number.parseInt(port)]);
|
|
}
|
|
}
|
|
|
|
throw new ERR_INVALID_IP_ADDRESS(serv);
|
|
});
|
|
|
|
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.join(","));
|
|
const err = strerror(errorNumber);
|
|
|
|
throw new ERR_DNS_SET_SERVERS_FAILED(err, servers.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The resolver instance will send its requests from the specified IP address.
|
|
* This allows programs to specify outbound interfaces when used on multi-homed
|
|
* systems.
|
|
*
|
|
* If a v4 or v6 address is not specified, it is set to the default, and the
|
|
* operating system will choose a local address automatically.
|
|
*
|
|
* The resolver will use the v4 local address when making requests to IPv4 DNS
|
|
* servers, and the v6 local address when making requests to IPv6 DNS servers.
|
|
* The `rrtype` of resolution requests has no impact on the local address used.
|
|
*
|
|
* @param [ipv4='0.0.0.0'] A string representation of an IPv4 address.
|
|
* @param [ipv6='::0'] A string representation of an IPv6 address.
|
|
*/
|
|
setLocalAddress(ipv4: string, ipv6?: string) {
|
|
validateString(ipv4, "ipv4");
|
|
|
|
if (ipv6 !== undefined) {
|
|
validateString(ipv6, "ipv6");
|
|
}
|
|
|
|
this._handle.setLocalAddress(ipv4, ipv6);
|
|
}
|
|
}
|
|
|
|
let defaultResolver = new Resolver();
|
|
|
|
export function getDefaultResolver(): Resolver {
|
|
return defaultResolver;
|
|
}
|
|
|
|
export function setDefaultResolver<T extends Resolver>(resolver: T) {
|
|
defaultResolver = resolver;
|
|
}
|
|
|
|
export function validateHints(hints: number) {
|
|
if ((hints & ~(AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED)) !== 0) {
|
|
throw new ERR_INVALID_ARG_VALUE("hints", hints, "is invalid");
|
|
}
|
|
}
|
|
|
|
let invalidHostnameWarningEmitted = false;
|
|
|
|
export function emitInvalidHostnameWarning(hostname: string) {
|
|
if (invalidHostnameWarningEmitted) {
|
|
return;
|
|
}
|
|
|
|
invalidHostnameWarningEmitted = true;
|
|
|
|
emitWarning(
|
|
`The provided hostname "${hostname}" is not a valid ` +
|
|
"hostname, and is supported in the dns module solely for compatibility.",
|
|
"DeprecationWarning",
|
|
"DEP0118",
|
|
);
|
|
}
|
|
|
|
let dnsOrder = getOptionValue("--dns-result-order") || "verbatim";
|
|
|
|
export function getDefaultVerbatim() {
|
|
return dnsOrder !== "ipv4first";
|
|
}
|
|
|
|
/**
|
|
* Set the default value of `verbatim` in `lookup` and `dnsPromises.lookup()`.
|
|
* The value could be:
|
|
*
|
|
* - `ipv4first`: sets default `verbatim` `false`.
|
|
* - `verbatim`: sets default `verbatim` `true`.
|
|
*
|
|
* The default is `ipv4first` and `setDefaultResultOrder` have higher
|
|
* priority than `--dns-result-order`. When using `worker threads`,
|
|
* `setDefaultResultOrder` from the main thread won't affect the default
|
|
* dns orders in workers.
|
|
*
|
|
* @param order must be `'ipv4first'` or `'verbatim'`.
|
|
*/
|
|
export function setDefaultResultOrder(order: "ipv4first" | "verbatim") {
|
|
validateOneOf(order, "dnsOrder", ["verbatim", "ipv4first"]);
|
|
dnsOrder = order;
|
|
}
|
|
|
|
export function defaultResolverSetServers(servers: string[]) {
|
|
const resolver = new Resolver();
|
|
|
|
resolver.setServers(servers);
|
|
setDefaultResolver(resolver);
|
|
}
|