mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
fix(ext/node): add autoSelectFamily option to net.createConnection (#26661)
This commit is contained in:
parent
90236d67c5
commit
c3c2b37966
9 changed files with 824 additions and 16 deletions
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { primordials } from "ext:core/mod.js";
|
import { primordials } from "ext:core/mod.js";
|
||||||
const { JSONStringify, SymbolFor } = primordials;
|
const { JSONStringify, SafeArrayIterator, SymbolFor } = primordials;
|
||||||
import { format, inspect } from "ext:deno_node/internal/util/inspect.mjs";
|
import { format, inspect } from "ext:deno_node/internal/util/inspect.mjs";
|
||||||
import { codes } from "ext:deno_node/internal/error_codes.ts";
|
import { codes } from "ext:deno_node/internal/error_codes.ts";
|
||||||
import {
|
import {
|
||||||
|
@ -1874,6 +1874,11 @@ export class ERR_SOCKET_CLOSED extends NodeError {
|
||||||
super("ERR_SOCKET_CLOSED", `Socket is closed`);
|
super("ERR_SOCKET_CLOSED", `Socket is closed`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class ERR_SOCKET_CONNECTION_TIMEOUT extends NodeError {
|
||||||
|
constructor() {
|
||||||
|
super("ERR_SOCKET_CONNECTION_TIMEOUT", `Socket connection timeout`);
|
||||||
|
}
|
||||||
|
}
|
||||||
export class ERR_SOCKET_DGRAM_IS_CONNECTED extends NodeError {
|
export class ERR_SOCKET_DGRAM_IS_CONNECTED extends NodeError {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("ERR_SOCKET_DGRAM_IS_CONNECTED", `Already connected`);
|
super("ERR_SOCKET_DGRAM_IS_CONNECTED", `Already connected`);
|
||||||
|
@ -2633,11 +2638,30 @@ export function aggregateTwoErrors(
|
||||||
}
|
}
|
||||||
return innerError || outerError;
|
return innerError || outerError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class NodeAggregateError extends AggregateError {
|
||||||
|
code: string;
|
||||||
|
constructor(errors, message) {
|
||||||
|
super(new SafeArrayIterator(errors), message);
|
||||||
|
this.code = errors[0]?.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
get [kIsNodeError]() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore adjacent-overload-signatures
|
||||||
|
get ["constructor"]() {
|
||||||
|
return AggregateError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
codes.ERR_IPC_CHANNEL_CLOSED = ERR_IPC_CHANNEL_CLOSED;
|
codes.ERR_IPC_CHANNEL_CLOSED = ERR_IPC_CHANNEL_CLOSED;
|
||||||
codes.ERR_INVALID_ARG_TYPE = ERR_INVALID_ARG_TYPE;
|
codes.ERR_INVALID_ARG_TYPE = ERR_INVALID_ARG_TYPE;
|
||||||
codes.ERR_INVALID_ARG_VALUE = ERR_INVALID_ARG_VALUE;
|
codes.ERR_INVALID_ARG_VALUE = ERR_INVALID_ARG_VALUE;
|
||||||
codes.ERR_OUT_OF_RANGE = ERR_OUT_OF_RANGE;
|
codes.ERR_OUT_OF_RANGE = ERR_OUT_OF_RANGE;
|
||||||
codes.ERR_SOCKET_BAD_PORT = ERR_SOCKET_BAD_PORT;
|
codes.ERR_SOCKET_BAD_PORT = ERR_SOCKET_BAD_PORT;
|
||||||
|
codes.ERR_SOCKET_CONNECTION_TIMEOUT = ERR_SOCKET_CONNECTION_TIMEOUT;
|
||||||
codes.ERR_BUFFER_OUT_OF_BOUNDS = ERR_BUFFER_OUT_OF_BOUNDS;
|
codes.ERR_BUFFER_OUT_OF_BOUNDS = ERR_BUFFER_OUT_OF_BOUNDS;
|
||||||
codes.ERR_UNKNOWN_ENCODING = ERR_UNKNOWN_ENCODING;
|
codes.ERR_UNKNOWN_ENCODING = ERR_UNKNOWN_ENCODING;
|
||||||
codes.ERR_PARSE_ARGS_INVALID_OPTION_VALUE = ERR_PARSE_ARGS_INVALID_OPTION_VALUE;
|
codes.ERR_PARSE_ARGS_INVALID_OPTION_VALUE = ERR_PARSE_ARGS_INVALID_OPTION_VALUE;
|
||||||
|
|
|
@ -95,4 +95,5 @@ export function makeSyncWrite(fd: number) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const kReinitializeHandle = Symbol("kReinitializeHandle");
|
||||||
export const normalizedArgsSymbol = Symbol("normalizedArgs");
|
export const normalizedArgsSymbol = Symbol("normalizedArgs");
|
||||||
|
|
|
@ -530,10 +530,12 @@ export function mapSysErrnoToUvErrno(sysErrno: number): number {
|
||||||
|
|
||||||
export const UV_EAI_MEMORY = codeMap.get("EAI_MEMORY")!;
|
export const UV_EAI_MEMORY = codeMap.get("EAI_MEMORY")!;
|
||||||
export const UV_EBADF = codeMap.get("EBADF")!;
|
export const UV_EBADF = codeMap.get("EBADF")!;
|
||||||
|
export const UV_ECANCELED = codeMap.get("ECANCELED")!;
|
||||||
export const UV_EEXIST = codeMap.get("EEXIST");
|
export const UV_EEXIST = codeMap.get("EEXIST");
|
||||||
export const UV_EINVAL = codeMap.get("EINVAL")!;
|
export const UV_EINVAL = codeMap.get("EINVAL")!;
|
||||||
export const UV_ENOENT = codeMap.get("ENOENT");
|
export const UV_ENOENT = codeMap.get("ENOENT");
|
||||||
export const UV_ENOTSOCK = codeMap.get("ENOTSOCK")!;
|
export const UV_ENOTSOCK = codeMap.get("ENOTSOCK")!;
|
||||||
|
export const UV_ETIMEDOUT = codeMap.get("ETIMEDOUT")!;
|
||||||
export const UV_UNKNOWN = codeMap.get("UNKNOWN")!;
|
export const UV_UNKNOWN = codeMap.get("UNKNOWN")!;
|
||||||
|
|
||||||
export function errname(errno: number): string {
|
export function errname(errno: number): string {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
isIP,
|
isIP,
|
||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
|
kReinitializeHandle,
|
||||||
normalizedArgsSymbol,
|
normalizedArgsSymbol,
|
||||||
} from "ext:deno_node/internal/net.ts";
|
} from "ext:deno_node/internal/net.ts";
|
||||||
import { Duplex } from "node:stream";
|
import { Duplex } from "node:stream";
|
||||||
|
@ -50,9 +51,11 @@ import {
|
||||||
ERR_SERVER_ALREADY_LISTEN,
|
ERR_SERVER_ALREADY_LISTEN,
|
||||||
ERR_SERVER_NOT_RUNNING,
|
ERR_SERVER_NOT_RUNNING,
|
||||||
ERR_SOCKET_CLOSED,
|
ERR_SOCKET_CLOSED,
|
||||||
|
ERR_SOCKET_CONNECTION_TIMEOUT,
|
||||||
errnoException,
|
errnoException,
|
||||||
exceptionWithHostPort,
|
exceptionWithHostPort,
|
||||||
genericNodeError,
|
genericNodeError,
|
||||||
|
NodeAggregateError,
|
||||||
uvExceptionWithHostPort,
|
uvExceptionWithHostPort,
|
||||||
} from "ext:deno_node/internal/errors.ts";
|
} from "ext:deno_node/internal/errors.ts";
|
||||||
import type { ErrnoException } from "ext:deno_node/internal/errors.ts";
|
import type { ErrnoException } from "ext:deno_node/internal/errors.ts";
|
||||||
|
@ -80,6 +83,7 @@ import { Buffer } from "node:buffer";
|
||||||
import type { LookupOneOptions } from "ext:deno_node/internal/dns/utils.ts";
|
import type { LookupOneOptions } from "ext:deno_node/internal/dns/utils.ts";
|
||||||
import {
|
import {
|
||||||
validateAbortSignal,
|
validateAbortSignal,
|
||||||
|
validateBoolean,
|
||||||
validateFunction,
|
validateFunction,
|
||||||
validateInt32,
|
validateInt32,
|
||||||
validateNumber,
|
validateNumber,
|
||||||
|
@ -100,13 +104,25 @@ import { ShutdownWrap } from "ext:deno_node/internal_binding/stream_wrap.ts";
|
||||||
import { assert } from "ext:deno_node/_util/asserts.ts";
|
import { assert } from "ext:deno_node/_util/asserts.ts";
|
||||||
import { isWindows } from "ext:deno_node/_util/os.ts";
|
import { isWindows } from "ext:deno_node/_util/os.ts";
|
||||||
import { ADDRCONFIG, lookup as dnsLookup } from "node:dns";
|
import { ADDRCONFIG, lookup as dnsLookup } from "node:dns";
|
||||||
import { codeMap } from "ext:deno_node/internal_binding/uv.ts";
|
import {
|
||||||
|
codeMap,
|
||||||
|
UV_ECANCELED,
|
||||||
|
UV_ETIMEDOUT,
|
||||||
|
} from "ext:deno_node/internal_binding/uv.ts";
|
||||||
import { guessHandleType } from "ext:deno_node/internal_binding/util.ts";
|
import { guessHandleType } from "ext:deno_node/internal_binding/util.ts";
|
||||||
import { debuglog } from "ext:deno_node/internal/util/debuglog.ts";
|
import { debuglog } from "ext:deno_node/internal/util/debuglog.ts";
|
||||||
import type { DuplexOptions } from "ext:deno_node/_stream.d.ts";
|
import type { DuplexOptions } from "ext:deno_node/_stream.d.ts";
|
||||||
import type { BufferEncoding } from "ext:deno_node/_global.d.ts";
|
import type { BufferEncoding } from "ext:deno_node/_global.d.ts";
|
||||||
import type { Abortable } from "ext:deno_node/_events.d.ts";
|
import type { Abortable } from "ext:deno_node/_events.d.ts";
|
||||||
import { channel } from "node:diagnostics_channel";
|
import { channel } from "node:diagnostics_channel";
|
||||||
|
import { primordials } from "ext:core/mod.js";
|
||||||
|
|
||||||
|
const {
|
||||||
|
ArrayPrototypeIncludes,
|
||||||
|
ArrayPrototypePush,
|
||||||
|
FunctionPrototypeBind,
|
||||||
|
MathMax,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
let debug = debuglog("net", (fn) => {
|
let debug = debuglog("net", (fn) => {
|
||||||
debug = fn;
|
debug = fn;
|
||||||
|
@ -120,6 +136,9 @@ const kBytesWritten = Symbol("kBytesWritten");
|
||||||
const DEFAULT_IPV4_ADDR = "0.0.0.0";
|
const DEFAULT_IPV4_ADDR = "0.0.0.0";
|
||||||
const DEFAULT_IPV6_ADDR = "::";
|
const DEFAULT_IPV6_ADDR = "::";
|
||||||
|
|
||||||
|
let autoSelectFamilyDefault = true;
|
||||||
|
let autoSelectFamilyAttemptTimeoutDefault = 250;
|
||||||
|
|
||||||
type Handle = TCP | Pipe;
|
type Handle = TCP | Pipe;
|
||||||
|
|
||||||
interface HandleOptions {
|
interface HandleOptions {
|
||||||
|
@ -214,6 +233,8 @@ interface TcpSocketConnectOptions extends ConnectOptions {
|
||||||
hints?: number;
|
hints?: number;
|
||||||
family?: number;
|
family?: number;
|
||||||
lookup?: LookupFunction;
|
lookup?: LookupFunction;
|
||||||
|
autoSelectFamily?: boolean | undefined;
|
||||||
|
autoSelectFamilyAttemptTimeout?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IpcSocketConnectOptions extends ConnectOptions {
|
interface IpcSocketConnectOptions extends ConnectOptions {
|
||||||
|
@ -316,12 +337,6 @@ export function _normalizeArgs(args: unknown[]): NormalizedArgs {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isTCPConnectWrap(
|
|
||||||
req: TCPConnectWrap | PipeConnectWrap,
|
|
||||||
): req is TCPConnectWrap {
|
|
||||||
return "localAddress" in req && "localPort" in req;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _afterConnect(
|
function _afterConnect(
|
||||||
status: number,
|
status: number,
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
|
@ -372,7 +387,7 @@ function _afterConnect(
|
||||||
socket.connecting = false;
|
socket.connecting = false;
|
||||||
let details;
|
let details;
|
||||||
|
|
||||||
if (_isTCPConnectWrap(req)) {
|
if (req.localAddress && req.localPort) {
|
||||||
details = req.localAddress + ":" + req.localPort;
|
details = req.localAddress + ":" + req.localPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +399,7 @@ function _afterConnect(
|
||||||
details,
|
details,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_isTCPConnectWrap(req)) {
|
if (details) {
|
||||||
ex.localAddress = req.localAddress;
|
ex.localAddress = req.localAddress;
|
||||||
ex.localPort = req.localPort;
|
ex.localPort = req.localPort;
|
||||||
}
|
}
|
||||||
|
@ -393,6 +408,107 @@ function _afterConnect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _createConnectionError(req, status) {
|
||||||
|
let details;
|
||||||
|
|
||||||
|
if (req.localAddress && req.localPort) {
|
||||||
|
details = req.localAddress + ":" + req.localPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ex = exceptionWithHostPort(
|
||||||
|
status,
|
||||||
|
"connect",
|
||||||
|
req.address,
|
||||||
|
req.port,
|
||||||
|
details,
|
||||||
|
);
|
||||||
|
if (details) {
|
||||||
|
ex.localAddress = req.localAddress;
|
||||||
|
ex.localPort = req.localPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _afterConnectMultiple(
|
||||||
|
context,
|
||||||
|
current,
|
||||||
|
status,
|
||||||
|
handle,
|
||||||
|
req,
|
||||||
|
readable,
|
||||||
|
writable,
|
||||||
|
) {
|
||||||
|
debug(
|
||||||
|
"connect/multiple: connection attempt to %s:%s completed with status %s",
|
||||||
|
req.address,
|
||||||
|
req.port,
|
||||||
|
status,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure another connection is not spawned
|
||||||
|
clearTimeout(context[kTimeout]);
|
||||||
|
|
||||||
|
// One of the connection has completed and correctly dispatched but after timeout, ignore this one
|
||||||
|
if (status === 0 && current !== context.current - 1) {
|
||||||
|
debug(
|
||||||
|
"connect/multiple: ignoring successful but timedout connection to %s:%s",
|
||||||
|
req.address,
|
||||||
|
req.port,
|
||||||
|
);
|
||||||
|
handle.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = context.socket;
|
||||||
|
|
||||||
|
// Some error occurred, add to the list of exceptions
|
||||||
|
if (status !== 0) {
|
||||||
|
const ex = _createConnectionError(req, status);
|
||||||
|
ArrayPrototypePush(context.errors, ex);
|
||||||
|
|
||||||
|
self.emit(
|
||||||
|
"connectionAttemptFailed",
|
||||||
|
req.address,
|
||||||
|
req.port,
|
||||||
|
req.addressType,
|
||||||
|
ex,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try the next address, unless we were aborted
|
||||||
|
if (context.socket.connecting) {
|
||||||
|
_internalConnectMultiple(context, status === UV_ECANCELED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_afterConnect(status, self._handle, req, readable, writable);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _internalConnectMultipleTimeout(context, req, handle) {
|
||||||
|
debug(
|
||||||
|
"connect/multiple: connection to %s:%s timed out",
|
||||||
|
req.address,
|
||||||
|
req.port,
|
||||||
|
);
|
||||||
|
context.socket.emit(
|
||||||
|
"connectionAttemptTimeout",
|
||||||
|
req.address,
|
||||||
|
req.port,
|
||||||
|
req.addressType,
|
||||||
|
);
|
||||||
|
|
||||||
|
req.oncomplete = undefined;
|
||||||
|
ArrayPrototypePush(context.errors, _createConnectionError(req, UV_ETIMEDOUT));
|
||||||
|
handle.close();
|
||||||
|
|
||||||
|
// Try the next address, unless we were aborted
|
||||||
|
if (context.socket.connecting) {
|
||||||
|
_internalConnectMultiple(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _checkBindError(err: number, port: number, handle: TCP) {
|
function _checkBindError(err: number, port: number, handle: TCP) {
|
||||||
// EADDRINUSE may not be reported until we call `listen()` or `connect()`.
|
// EADDRINUSE may not be reported until we call `listen()` or `connect()`.
|
||||||
// To complicate matters, a failed `bind()` followed by `listen()` or `connect()`
|
// To complicate matters, a failed `bind()` followed by `listen()` or `connect()`
|
||||||
|
@ -495,6 +611,131 @@ function _internalConnect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _internalConnectMultiple(context, canceled?: boolean) {
|
||||||
|
clearTimeout(context[kTimeout]);
|
||||||
|
const self = context.socket;
|
||||||
|
|
||||||
|
// We were requested to abort. Stop all operations
|
||||||
|
if (self._aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All connections have been tried without success, destroy with error
|
||||||
|
if (canceled || context.current === context.addresses.length) {
|
||||||
|
if (context.errors.length === 0) {
|
||||||
|
self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.destroy(new NodeAggregateError(context.errors));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(self.connecting);
|
||||||
|
|
||||||
|
const current = context.current++;
|
||||||
|
|
||||||
|
if (current > 0) {
|
||||||
|
self[kReinitializeHandle](new TCP(TCPConstants.SOCKET));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { localPort, port, flags } = context;
|
||||||
|
const { address, family: addressType } = context.addresses[current];
|
||||||
|
let localAddress;
|
||||||
|
let err;
|
||||||
|
|
||||||
|
if (localPort) {
|
||||||
|
if (addressType === 4) {
|
||||||
|
localAddress = DEFAULT_IPV4_ADDR;
|
||||||
|
err = self._handle.bind(localAddress, localPort);
|
||||||
|
} else { // addressType === 6
|
||||||
|
localAddress = DEFAULT_IPV6_ADDR;
|
||||||
|
err = self._handle.bind6(localAddress, localPort, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(
|
||||||
|
"connect/multiple: binding to localAddress: %s and localPort: %d (addressType: %d)",
|
||||||
|
localAddress,
|
||||||
|
localPort,
|
||||||
|
addressType,
|
||||||
|
);
|
||||||
|
|
||||||
|
err = _checkBindError(err, localPort, self._handle);
|
||||||
|
if (err) {
|
||||||
|
ArrayPrototypePush(
|
||||||
|
context.errors,
|
||||||
|
exceptionWithHostPort(err, "bind", localAddress, localPort),
|
||||||
|
);
|
||||||
|
_internalConnectMultiple(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(
|
||||||
|
"connect/multiple: attempting to connect to %s:%d (addressType: %d)",
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
addressType,
|
||||||
|
);
|
||||||
|
self.emit("connectionAttempt", address, port, addressType);
|
||||||
|
|
||||||
|
const req = new TCPConnectWrap();
|
||||||
|
req.oncomplete = FunctionPrototypeBind(
|
||||||
|
_afterConnectMultiple,
|
||||||
|
undefined,
|
||||||
|
context,
|
||||||
|
current,
|
||||||
|
);
|
||||||
|
req.address = address;
|
||||||
|
req.port = port;
|
||||||
|
req.localAddress = localAddress;
|
||||||
|
req.localPort = localPort;
|
||||||
|
req.addressType = addressType;
|
||||||
|
|
||||||
|
ArrayPrototypePush(
|
||||||
|
self.autoSelectFamilyAttemptedAddresses,
|
||||||
|
`${address}:${port}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addressType === 4) {
|
||||||
|
err = self._handle.connect(req, address, port);
|
||||||
|
} else {
|
||||||
|
err = self._handle.connect6(req, address, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
const sockname = self._getsockname();
|
||||||
|
let details;
|
||||||
|
|
||||||
|
if (sockname) {
|
||||||
|
details = sockname.address + ":" + sockname.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ex = exceptionWithHostPort(err, "connect", address, port, details);
|
||||||
|
ArrayPrototypePush(context.errors, ex);
|
||||||
|
|
||||||
|
self.emit("connectionAttemptFailed", address, port, addressType, ex);
|
||||||
|
_internalConnectMultiple(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current < context.addresses.length - 1) {
|
||||||
|
debug(
|
||||||
|
"connect/multiple: setting the attempt timeout to %d ms",
|
||||||
|
context.timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the attempt has not returned an error, start the connection timer
|
||||||
|
context[kTimeout] = setTimeout(
|
||||||
|
_internalConnectMultipleTimeout,
|
||||||
|
context.timeout,
|
||||||
|
context,
|
||||||
|
req,
|
||||||
|
self._handle,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Provide a better error message when we call end() as a result
|
// Provide a better error message when we call end() as a result
|
||||||
// of the other side sending a FIN. The standard "write after end"
|
// of the other side sending a FIN. The standard "write after end"
|
||||||
// is overly vague, and makes it seem like the user's code is to blame.
|
// is overly vague, and makes it seem like the user's code is to blame.
|
||||||
|
@ -597,7 +838,7 @@ function _lookupAndConnect(
|
||||||
) {
|
) {
|
||||||
const { localAddress, localPort } = options;
|
const { localAddress, localPort } = options;
|
||||||
const host = options.host || "localhost";
|
const host = options.host || "localhost";
|
||||||
let { port } = options;
|
let { port, autoSelectFamilyAttemptTimeout, autoSelectFamily } = options;
|
||||||
|
|
||||||
if (localAddress && !isIP(localAddress)) {
|
if (localAddress && !isIP(localAddress)) {
|
||||||
throw new ERR_INVALID_IP_ADDRESS(localAddress);
|
throw new ERR_INVALID_IP_ADDRESS(localAddress);
|
||||||
|
@ -621,6 +862,22 @@ function _lookupAndConnect(
|
||||||
|
|
||||||
port |= 0;
|
port |= 0;
|
||||||
|
|
||||||
|
if (autoSelectFamily != null) {
|
||||||
|
validateBoolean(autoSelectFamily, "options.autoSelectFamily");
|
||||||
|
} else {
|
||||||
|
autoSelectFamily = autoSelectFamilyDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoSelectFamilyAttemptTimeout !== undefined) {
|
||||||
|
validateInt32(autoSelectFamilyAttemptTimeout);
|
||||||
|
|
||||||
|
if (autoSelectFamilyAttemptTimeout < 10) {
|
||||||
|
autoSelectFamilyAttemptTimeout = 10;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
autoSelectFamilyAttemptTimeout = autoSelectFamilyAttemptTimeoutDefault;
|
||||||
|
}
|
||||||
|
|
||||||
// If host is an IP, skip performing a lookup
|
// If host is an IP, skip performing a lookup
|
||||||
const addressType = isIP(host);
|
const addressType = isIP(host);
|
||||||
if (addressType) {
|
if (addressType) {
|
||||||
|
@ -649,6 +906,7 @@ function _lookupAndConnect(
|
||||||
const dnsOpts = {
|
const dnsOpts = {
|
||||||
family: options.family,
|
family: options.family,
|
||||||
hints: options.hints || 0,
|
hints: options.hints || 0,
|
||||||
|
all: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -665,6 +923,31 @@ function _lookupAndConnect(
|
||||||
self._host = host;
|
self._host = host;
|
||||||
const lookup = options.lookup || dnsLookup;
|
const lookup = options.lookup || dnsLookup;
|
||||||
|
|
||||||
|
if (
|
||||||
|
dnsOpts.family !== 4 && dnsOpts.family !== 6 && !localAddress &&
|
||||||
|
autoSelectFamily
|
||||||
|
) {
|
||||||
|
debug("connect: autodetecting");
|
||||||
|
|
||||||
|
dnsOpts.all = true;
|
||||||
|
defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () {
|
||||||
|
_lookupAndConnectMultiple(
|
||||||
|
self,
|
||||||
|
asyncIdSymbol,
|
||||||
|
lookup,
|
||||||
|
host,
|
||||||
|
options,
|
||||||
|
dnsOpts,
|
||||||
|
port,
|
||||||
|
localAddress,
|
||||||
|
localPort,
|
||||||
|
autoSelectFamilyAttemptTimeout,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () {
|
defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () {
|
||||||
lookup(
|
lookup(
|
||||||
host,
|
host,
|
||||||
|
@ -719,6 +1002,143 @@ function _lookupAndConnect(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _lookupAndConnectMultiple(
|
||||||
|
self: Socket,
|
||||||
|
asyncIdSymbol: number,
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
lookup: any,
|
||||||
|
host: string,
|
||||||
|
options: TcpSocketConnectOptions,
|
||||||
|
dnsopts,
|
||||||
|
port: number,
|
||||||
|
localAddress: string,
|
||||||
|
localPort: number,
|
||||||
|
timeout: number | undefined,
|
||||||
|
) {
|
||||||
|
defaultTriggerAsyncIdScope(self[asyncIdSymbol], function emitLookup() {
|
||||||
|
lookup(host, dnsopts, function emitLookup(err, addresses) {
|
||||||
|
// It's possible we were destroyed while looking this up.
|
||||||
|
// XXX it would be great if we could cancel the promise returned by
|
||||||
|
// the look up.
|
||||||
|
if (!self.connecting) {
|
||||||
|
return;
|
||||||
|
} else if (err) {
|
||||||
|
self.emit("lookup", err, undefined, undefined, host);
|
||||||
|
|
||||||
|
// net.createConnection() creates a net.Socket object and immediately
|
||||||
|
// calls net.Socket.connect() on it (that's us). There are no event
|
||||||
|
// listeners registered yet so defer the error event to the next tick.
|
||||||
|
nextTick(_connectErrorNT, self, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter addresses by only keeping the one which are either IPv4 or IPV6.
|
||||||
|
// The first valid address determines which group has preference on the
|
||||||
|
// alternate family sorting which happens later.
|
||||||
|
const validAddresses = [[], []];
|
||||||
|
const validIps = [[], []];
|
||||||
|
let destinations;
|
||||||
|
for (let i = 0, l = addresses.length; i < l; i++) {
|
||||||
|
const address = addresses[i];
|
||||||
|
const { address: ip, family: addressType } = address;
|
||||||
|
self.emit("lookup", err, ip, addressType, host);
|
||||||
|
// It's possible we were destroyed while looking this up.
|
||||||
|
if (!self.connecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isIP(ip) && (addressType === 4 || addressType === 6)) {
|
||||||
|
destinations ||= addressType === 6 ? { 6: 0, 4: 1 } : { 4: 0, 6: 1 };
|
||||||
|
|
||||||
|
const destination = destinations[addressType];
|
||||||
|
|
||||||
|
// Only try an address once
|
||||||
|
if (!ArrayPrototypeIncludes(validIps[destination], ip)) {
|
||||||
|
ArrayPrototypePush(validAddresses[destination], address);
|
||||||
|
ArrayPrototypePush(validIps[destination], ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When no AAAA or A records are available, fail on the first one
|
||||||
|
if (!validAddresses[0].length && !validAddresses[1].length) {
|
||||||
|
const { address: firstIp, family: firstAddressType } = addresses[0];
|
||||||
|
|
||||||
|
if (!isIP(firstIp)) {
|
||||||
|
err = new ERR_INVALID_IP_ADDRESS(firstIp);
|
||||||
|
nextTick(_connectErrorNT, self, err);
|
||||||
|
} else if (firstAddressType !== 4 && firstAddressType !== 6) {
|
||||||
|
err = new ERR_INVALID_ADDRESS_FAMILY(
|
||||||
|
firstAddressType,
|
||||||
|
options.host,
|
||||||
|
options.port,
|
||||||
|
);
|
||||||
|
nextTick(_connectErrorNT, self, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort addresses alternating families
|
||||||
|
const toAttempt = [];
|
||||||
|
for (
|
||||||
|
let i = 0,
|
||||||
|
l = MathMax(validAddresses[0].length, validAddresses[1].length);
|
||||||
|
i < l;
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
if (i in validAddresses[0]) {
|
||||||
|
ArrayPrototypePush(toAttempt, validAddresses[0][i]);
|
||||||
|
}
|
||||||
|
if (i in validAddresses[1]) {
|
||||||
|
ArrayPrototypePush(toAttempt, validAddresses[1][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toAttempt.length === 1) {
|
||||||
|
debug(
|
||||||
|
"connect/multiple: only one address found, switching back to single connection",
|
||||||
|
);
|
||||||
|
const { address: ip, family: addressType } = toAttempt[0];
|
||||||
|
|
||||||
|
self._unrefTimer();
|
||||||
|
defaultTriggerAsyncIdScope(
|
||||||
|
self[asyncIdSymbol],
|
||||||
|
_internalConnect,
|
||||||
|
self,
|
||||||
|
ip,
|
||||||
|
port,
|
||||||
|
addressType,
|
||||||
|
localAddress,
|
||||||
|
localPort,
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.autoSelectFamilyAttemptedAddresses = [];
|
||||||
|
debug("connect/multiple: will try the following addresses", toAttempt);
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
socket: self,
|
||||||
|
addresses: toAttempt,
|
||||||
|
current: 0,
|
||||||
|
port,
|
||||||
|
localPort,
|
||||||
|
timeout,
|
||||||
|
[kTimeout]: null,
|
||||||
|
errors: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
self._unrefTimer();
|
||||||
|
defaultTriggerAsyncIdScope(
|
||||||
|
self[asyncIdSymbol],
|
||||||
|
_internalConnectMultiple,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function _afterShutdown(this: ShutdownWrap<TCP>) {
|
function _afterShutdown(this: ShutdownWrap<TCP>) {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
const self: any = this.handle[ownerSymbol];
|
const self: any = this.handle[ownerSymbol];
|
||||||
|
@ -777,6 +1197,7 @@ export class Socket extends Duplex {
|
||||||
_host: string | null = null;
|
_host: string | null = null;
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
_parent: any = null;
|
_parent: any = null;
|
||||||
|
autoSelectFamilyAttemptedAddresses: AddressInfo[] | undefined = undefined;
|
||||||
|
|
||||||
constructor(options: SocketOptions | number) {
|
constructor(options: SocketOptions | number) {
|
||||||
if (typeof options === "number") {
|
if (typeof options === "number") {
|
||||||
|
@ -1546,6 +1967,16 @@ export class Socket extends Duplex {
|
||||||
set _handle(v: Handle | null) {
|
set _handle(v: Handle | null) {
|
||||||
this[kHandle] = v;
|
this[kHandle] = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
[kReinitializeHandle](handle: any) {
|
||||||
|
this._handle?.close();
|
||||||
|
|
||||||
|
this._handle = handle;
|
||||||
|
this._handle[ownerSymbol] = this;
|
||||||
|
|
||||||
|
_initSocketHandle(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Stream = Socket;
|
export const Stream = Socket;
|
||||||
|
@ -1593,6 +2024,33 @@ export function connect(...args: unknown[]) {
|
||||||
|
|
||||||
export const createConnection = connect;
|
export const createConnection = connect;
|
||||||
|
|
||||||
|
/** https://docs.deno.com/api/node/net/#namespace_getdefaultautoselectfamily */
|
||||||
|
export function getDefaultAutoSelectFamily() {
|
||||||
|
return autoSelectFamilyDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** https://docs.deno.com/api/node/net/#namespace_setdefaultautoselectfamily */
|
||||||
|
export function setDefaultAutoSelectFamily(value: boolean) {
|
||||||
|
validateBoolean(value, "value");
|
||||||
|
autoSelectFamilyDefault = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** https://docs.deno.com/api/node/net/#namespace_getdefaultautoselectfamilyattempttimeout */
|
||||||
|
export function getDefaultAutoSelectFamilyAttemptTimeout() {
|
||||||
|
return autoSelectFamilyAttemptTimeoutDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** https://docs.deno.com/api/node/net/#namespace_setdefaultautoselectfamilyattempttimeout */
|
||||||
|
export function setDefaultAutoSelectFamilyAttemptTimeout(value: number) {
|
||||||
|
validateInt32(value, "value", 1);
|
||||||
|
|
||||||
|
if (value < 10) {
|
||||||
|
value = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoSelectFamilyAttemptTimeoutDefault = value;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ListenOptions extends Abortable {
|
export interface ListenOptions extends Abortable {
|
||||||
fd?: number;
|
fd?: number;
|
||||||
port?: number | undefined;
|
port?: number | undefined;
|
||||||
|
@ -2478,15 +2936,19 @@ export { BlockList, isIP, isIPv4, isIPv6, SocketAddress };
|
||||||
export default {
|
export default {
|
||||||
_createServerHandle,
|
_createServerHandle,
|
||||||
_normalizeArgs,
|
_normalizeArgs,
|
||||||
isIP,
|
|
||||||
isIPv4,
|
|
||||||
isIPv6,
|
|
||||||
BlockList,
|
BlockList,
|
||||||
SocketAddress,
|
|
||||||
connect,
|
connect,
|
||||||
createConnection,
|
createConnection,
|
||||||
createServer,
|
createServer,
|
||||||
|
getDefaultAutoSelectFamily,
|
||||||
|
getDefaultAutoSelectFamilyAttemptTimeout,
|
||||||
|
isIP,
|
||||||
|
isIPv4,
|
||||||
|
isIPv6,
|
||||||
Server,
|
Server,
|
||||||
|
setDefaultAutoSelectFamily,
|
||||||
|
setDefaultAutoSelectFamilyAttemptTimeout,
|
||||||
Socket,
|
Socket,
|
||||||
|
SocketAddress,
|
||||||
Stream,
|
Stream,
|
||||||
};
|
};
|
||||||
|
|
|
@ -77,6 +77,7 @@
|
||||||
"test-fs-rmdir-recursive.js",
|
"test-fs-rmdir-recursive.js",
|
||||||
"test-fs-write-file.js",
|
"test-fs-write-file.js",
|
||||||
"test-http-url.parse-https.request.js",
|
"test-http-url.parse-https.request.js",
|
||||||
|
"test-net-autoselectfamily.js",
|
||||||
"test-net-better-error-messages-path.js",
|
"test-net-better-error-messages-path.js",
|
||||||
"test-net-connect-buffer.js",
|
"test-net-connect-buffer.js",
|
||||||
"test-net-connect-buffer2.js",
|
"test-net-connect-buffer2.js",
|
||||||
|
@ -404,6 +405,7 @@
|
||||||
"test-http-url.parse-only-support-http-https-protocol.js",
|
"test-http-url.parse-only-support-http-https-protocol.js",
|
||||||
"test-icu-transcode.js",
|
"test-icu-transcode.js",
|
||||||
"test-net-access-byteswritten.js",
|
"test-net-access-byteswritten.js",
|
||||||
|
"test-net-autoselectfamily.js",
|
||||||
"test-net-better-error-messages-listen-path.js",
|
"test-net-better-error-messages-listen-path.js",
|
||||||
"test-net-better-error-messages-path.js",
|
"test-net-better-error-messages-path.js",
|
||||||
"test-net-better-error-messages-port-hostname.js",
|
"test-net-better-error-messages-port-hostname.js",
|
||||||
|
|
|
@ -1767,7 +1767,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
|
||||||
- [parallel/test-net-autoselectfamily-commandline-option.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-commandline-option.js)
|
- [parallel/test-net-autoselectfamily-commandline-option.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-commandline-option.js)
|
||||||
- [parallel/test-net-autoselectfamily-default.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-default.js)
|
- [parallel/test-net-autoselectfamily-default.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-default.js)
|
||||||
- [parallel/test-net-autoselectfamily-ipv4first.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-ipv4first.js)
|
- [parallel/test-net-autoselectfamily-ipv4first.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily-ipv4first.js)
|
||||||
- [parallel/test-net-autoselectfamily.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-autoselectfamily.js)
|
|
||||||
- [parallel/test-net-better-error-messages-listen.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-better-error-messages-listen.js)
|
- [parallel/test-net-better-error-messages-listen.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-better-error-messages-listen.js)
|
||||||
- [parallel/test-net-binary.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-binary.js)
|
- [parallel/test-net-binary.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-binary.js)
|
||||||
- [parallel/test-net-bind-twice.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-bind-twice.js)
|
- [parallel/test-net-bind-twice.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-net-bind-twice.js)
|
||||||
|
|
|
@ -473,6 +473,7 @@ const pwdCommand = isWindows ?
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allowGlobals,
|
allowGlobals,
|
||||||
|
defaultAutoSelectFamilyAttemptTimeout: 2500,
|
||||||
expectsError,
|
expectsError,
|
||||||
expectWarning,
|
expectWarning,
|
||||||
getArrayBufferViews,
|
getArrayBufferViews,
|
||||||
|
|
312
tests/node_compat/test/parallel/test-net-autoselectfamily.js
Normal file
312
tests/node_compat/test/parallel/test-net-autoselectfamily.js
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
// deno-fmt-ignore-file
|
||||||
|
// deno-lint-ignore-file
|
||||||
|
|
||||||
|
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||||
|
// Taken from Node 18.12.1
|
||||||
|
// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const dgram = require('dgram');
|
||||||
|
const { Resolver } = require('dns');
|
||||||
|
const { createConnection, createServer } = require('net');
|
||||||
|
|
||||||
|
// Test that happy eyeballs algorithm is properly implemented.
|
||||||
|
|
||||||
|
// Purposely not using setDefaultAutoSelectFamilyAttemptTimeout here to test the
|
||||||
|
// parameter is correctly used in options.
|
||||||
|
//
|
||||||
|
// Some of the machines in the CI need more time to establish connection
|
||||||
|
const autoSelectFamilyAttemptTimeout = common.defaultAutoSelectFamilyAttemptTimeout;
|
||||||
|
|
||||||
|
function _lookup(resolver, hostname, options, cb) {
|
||||||
|
resolver.resolve(hostname, 'ANY', (err, replies) => {
|
||||||
|
assert.notStrictEqual(options.family, 4);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hosts = replies
|
||||||
|
.map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }))
|
||||||
|
.sort((a, b) => b.family - a.family);
|
||||||
|
|
||||||
|
if (options.all === true) {
|
||||||
|
return cb(null, hosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb(null, hosts[0].address, hosts[0].family);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDnsServer(ipv6Addrs, ipv4Addrs, cb) {
|
||||||
|
if (!Array.isArray(ipv6Addrs)) {
|
||||||
|
ipv6Addrs = [ipv6Addrs];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(ipv4Addrs)) {
|
||||||
|
ipv4Addrs = [ipv4Addrs];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a DNS server which replies with a AAAA and a A record for the same host
|
||||||
|
const socket = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
// TODO(kt3k): We use common.mustCallAtLeast instead of common.mustCall
|
||||||
|
// because Deno sends multiple requests to the DNS server.
|
||||||
|
// This can be addressed if Deno.resolveDns supports ANY record type.
|
||||||
|
// See https://github.com/denoland/deno/issues/14492
|
||||||
|
socket.on('message', common.mustCallAtLeast((msg, { address, port }) => {
|
||||||
|
const parsed = parseDNSPacket(msg);
|
||||||
|
const domain = parsed.questions[0].domain;
|
||||||
|
assert.strictEqual(domain, 'example.org');
|
||||||
|
|
||||||
|
socket.send(writeDNSPacket({
|
||||||
|
id: parsed.id,
|
||||||
|
questions: parsed.questions,
|
||||||
|
answers: [
|
||||||
|
...ipv6Addrs.map((address) => ({ type: 'AAAA', address, ttl: 123, domain: 'example.org' })),
|
||||||
|
...ipv4Addrs.map((address) => ({ type: 'A', address, ttl: 123, domain: 'example.org' })),
|
||||||
|
]
|
||||||
|
}), port, address);
|
||||||
|
}));
|
||||||
|
|
||||||
|
socket.bind(0, () => {
|
||||||
|
const resolver = new Resolver();
|
||||||
|
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
|
||||||
|
|
||||||
|
cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that IPV4 is reached if IPV6 is not reachable
|
||||||
|
{
|
||||||
|
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||||
|
const ipv4Server = createServer((socket) => {
|
||||||
|
socket.on('data', common.mustCall(() => {
|
||||||
|
socket.write('response-ipv4');
|
||||||
|
socket.end();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||||
|
const port = ipv4Server.address().port;
|
||||||
|
|
||||||
|
const connection = createConnection({
|
||||||
|
host: 'example.org',
|
||||||
|
port: port,
|
||||||
|
lookup,
|
||||||
|
autoSelectFamily: true,
|
||||||
|
autoSelectFamilyAttemptTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = '';
|
||||||
|
connection.setEncoding('utf-8');
|
||||||
|
|
||||||
|
connection.on('ready', common.mustCall(() => {
|
||||||
|
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`, `127.0.0.1:${port}`]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
connection.on('data', (chunk) => {
|
||||||
|
response += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('end', common.mustCall(() => {
|
||||||
|
assert.strictEqual(response, 'response-ipv4');
|
||||||
|
ipv4Server.close();
|
||||||
|
dnsServer.close();
|
||||||
|
}));
|
||||||
|
|
||||||
|
connection.write('request');
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that only the last successful connection is established.
|
||||||
|
{
|
||||||
|
createDnsServer(
|
||||||
|
['2606:4700::6810:85e5', '2606:4700::6810:84e5', "::1"],
|
||||||
|
// TODO(kt3k): Comment out ipv4 addresses to make the test pass faster.
|
||||||
|
// Enable this when Deno.connect() call becomes cancellable.
|
||||||
|
// See https://github.com/denoland/deno/issues/26819
|
||||||
|
// ['104.20.22.46', '104.20.23.46', '127.0.0.1'],
|
||||||
|
['127.0.0.1'],
|
||||||
|
common.mustCall(function({ dnsServer, lookup }) {
|
||||||
|
const ipv4Server = createServer((socket) => {
|
||||||
|
socket.on('data', common.mustCall(() => {
|
||||||
|
socket.write('response-ipv4');
|
||||||
|
socket.end();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||||
|
const port = ipv4Server.address().port;
|
||||||
|
|
||||||
|
const connection = createConnection({
|
||||||
|
host: 'example.org',
|
||||||
|
port: port,
|
||||||
|
lookup,
|
||||||
|
autoSelectFamily: true,
|
||||||
|
autoSelectFamilyAttemptTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = '';
|
||||||
|
connection.setEncoding('utf-8');
|
||||||
|
|
||||||
|
connection.on('ready', common.mustCall(() => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
connection.autoSelectFamilyAttemptedAddresses,
|
||||||
|
[
|
||||||
|
`2606:4700::6810:85e5:${port}`,
|
||||||
|
`104.20.22.46:${port}`,
|
||||||
|
`2606:4700::6810:84e5:${port}`,
|
||||||
|
`104.20.23.46:${port}`,
|
||||||
|
`::1:${port}`,
|
||||||
|
`127.0.0.1:${port}`,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
connection.on('data', (chunk) => {
|
||||||
|
response += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('end', common.mustCall(() => {
|
||||||
|
assert.strictEqual(response, 'response-ipv4');
|
||||||
|
ipv4Server.close();
|
||||||
|
dnsServer.close();
|
||||||
|
}));
|
||||||
|
|
||||||
|
connection.write('request');
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that IPV4 is NOT reached if IPV6 is reachable
|
||||||
|
if (common.hasIPv6) {
|
||||||
|
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||||
|
const ipv4Server = createServer((socket) => {
|
||||||
|
socket.on('data', common.mustNotCall(() => {
|
||||||
|
socket.write('response-ipv4');
|
||||||
|
socket.end();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const ipv6Server = createServer((socket) => {
|
||||||
|
socket.on('data', common.mustCall(() => {
|
||||||
|
socket.write('response-ipv6');
|
||||||
|
socket.end();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||||
|
const port = ipv4Server.address().port;
|
||||||
|
|
||||||
|
ipv6Server.listen(port, '::1', common.mustCall(() => {
|
||||||
|
const connection = createConnection({
|
||||||
|
host: 'example.org',
|
||||||
|
port,
|
||||||
|
lookup,
|
||||||
|
autoSelectFamily: true,
|
||||||
|
autoSelectFamilyAttemptTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = '';
|
||||||
|
connection.setEncoding('utf-8');
|
||||||
|
|
||||||
|
connection.on('ready', common.mustCall(() => {
|
||||||
|
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
connection.on('data', (chunk) => {
|
||||||
|
response += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('end', common.mustCall(() => {
|
||||||
|
assert.strictEqual(response, 'response-ipv6');
|
||||||
|
ipv4Server.close();
|
||||||
|
ipv6Server.close();
|
||||||
|
dnsServer.close();
|
||||||
|
}));
|
||||||
|
|
||||||
|
connection.write('request');
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that when all errors are returned when no connections succeeded
|
||||||
|
{
|
||||||
|
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||||
|
const connection = createConnection({
|
||||||
|
host: 'example.org',
|
||||||
|
port: 10,
|
||||||
|
lookup,
|
||||||
|
autoSelectFamily: true,
|
||||||
|
autoSelectFamilyAttemptTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('ready', common.mustNotCall());
|
||||||
|
connection.on('error', common.mustCall((error) => {
|
||||||
|
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, ['::1:10', '127.0.0.1:10']);
|
||||||
|
assert.strictEqual(error.constructor.name, 'AggregateError');
|
||||||
|
assert.strictEqual(error.errors.length, 2);
|
||||||
|
|
||||||
|
const errors = error.errors.map((e) => e.message);
|
||||||
|
assert.ok(errors.includes('connect ECONNREFUSED 127.0.0.1:10'));
|
||||||
|
|
||||||
|
if (common.hasIPv6) {
|
||||||
|
assert.ok(errors.includes('connect ECONNREFUSED ::1:10'));
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.close();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the option can be disabled
|
||||||
|
{
|
||||||
|
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
|
||||||
|
const ipv4Server = createServer((socket) => {
|
||||||
|
socket.on('data', common.mustCall(() => {
|
||||||
|
socket.write('response-ipv4');
|
||||||
|
socket.end();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||||
|
const port = ipv4Server.address().port;
|
||||||
|
|
||||||
|
const connection = createConnection({
|
||||||
|
host: 'example.org',
|
||||||
|
port,
|
||||||
|
lookup,
|
||||||
|
autoSelectFamily: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('ready', common.mustNotCall());
|
||||||
|
connection.on('error', common.mustCall((error) => {
|
||||||
|
assert.strictEqual(connection.autoSelectFamilyAttemptedAddresses, undefined);
|
||||||
|
|
||||||
|
if (common.hasIPv6) {
|
||||||
|
assert.strictEqual(error.code, 'ECONNREFUSED');
|
||||||
|
assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`);
|
||||||
|
} else if (error.code === 'EAFNOSUPPORT') {
|
||||||
|
assert.strictEqual(error.message, `connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`);
|
||||||
|
} else if (error.code === 'EUNATCH') {
|
||||||
|
assert.strictEqual(error.message, `connect EUNATCH ::1:${port} - Local (:::0)`);
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(error.code, 'EADDRNOTAVAIL');
|
||||||
|
assert.strictEqual(error.message, `connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv4Server.close();
|
||||||
|
dnsServer.close();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
|
@ -10,6 +10,11 @@ import * as net from "node:net";
|
||||||
import { assert, assertEquals } from "@std/assert";
|
import { assert, assertEquals } from "@std/assert";
|
||||||
import { curlRequest } from "../unit/test_util.ts";
|
import { curlRequest } from "../unit/test_util.ts";
|
||||||
|
|
||||||
|
// Increase the timeout for the auto select family to avoid flakiness
|
||||||
|
net.setDefaultAutoSelectFamilyAttemptTimeout(
|
||||||
|
net.getDefaultAutoSelectFamilyAttemptTimeout() * 30,
|
||||||
|
);
|
||||||
|
|
||||||
for (const url of ["http://localhost:4246", "https://localhost:4247"]) {
|
for (const url of ["http://localhost:4246", "https://localhost:4247"]) {
|
||||||
Deno.test(`[node/http2 client] ${url}`, {
|
Deno.test(`[node/http2 client] ${url}`, {
|
||||||
ignore: Deno.build.os === "windows",
|
ignore: Deno.build.os === "windows",
|
||||||
|
|
Loading…
Reference in a new issue