Validate untyped listen ports and give net errors Node's system properties (#2619)

This commit is contained in:
Chris Tate
2026-10-30 07:18:37 -05:00
committed by GitHub
17 changed files with 450 additions and 23 deletions
+2
View File
@@ -178,6 +178,8 @@ declare module "net" {
getConnections(callback: (error: Error | null, count: number) => void): Server;
/* Node answers the server itself (`return this` chaining). */
listen(port: number, callback?: () => void): Server;
/* No port: an arbitrary unused one. */
listen(callback?: () => void): Server;
/* The positional bind address (the options form's host in argument
* spelling). */
listen(port: number, host: string, callback?: () => void): Server;
@@ -1488,6 +1488,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
// the client request forms have non-generic shapes — special-cased.
"net.serverPort": "scr_net_server_port",
"net.serverListening": "scr_net_server_listening",
"net.listenPortDyn": "scr_net_listen_port_dyn",
"net.sockSetRef": "scr_net_sock_set_ref",
"net.serverSetRef": "scr_net_server_set_ref",
"net.serverOff": "scr_net_server_off",
@@ -1531,11 +1531,11 @@ function lowerNetServerMethodCall(
return { kind: "libCall", fn, args: [receiver], type: VOID, loc };
}
if (name === "listen") {
if (args.length < 1 || args.length > 3) {
if (args.length > 3) {
lowerer.noLowering(
`listen with ${args.length} arguments`,
call,
"the supported form is listen(port[, host][, callback]) — port 0 binds an ephemeral port",
"the supported form is listen([port][, host][, callback]) — port 0 binds an ephemeral port",
);
}
// Node answers the server (`return this` chaining — `const s =
@@ -1549,6 +1549,90 @@ function lowerNetServerMethodCall(
return receiverReturningCall(lowerer, fn, callArgs, NETSERVER_T, loc);
};
const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T);
// listen() and listen(callback): Node binds an arbitrary unused port
// (normalizeArgs takes a function first argument as the callback).
if (args.length === 0) return listenResult("net.listen", [receiver, numLit(0, loc)]);
const first = lowerer.mapTypeOf(lowerer.typeOf(args[0]!));
// An untyped first argument: a checked-dynamic value, a JavaScript value
// read into an untyped slot, or a checker `any`.
const untyped =
args.length > 0 &&
(first?.kind === "dyn" ||
first?.kind === "jsval" ||
(lowerer.typeOf(args[0]!).flags & ts.TypeFlags.Any) !== 0);
if (args.length === 1 && first?.kind === "func") {
const { cb } = lowerCallbackArg(
lowerer,
args[0]!,
"listen callbacks",
0,
() => false,
"use ()",
[],
);
return listenResult("net.listenCb", [receiver, numLit(0, loc), cb]);
}
// An untyped first argument takes its port from net.listenPortDyn,
// Node's normalizeArgs and validatePort at run time: 0 for a function,
// undefined or null, the number of a port number or numeric string,
// ERR_SOCKET_BAD_PORT for a bad port, and the fence for an options
// object or a pipe path, which have no lowering from untyped values.
const portOfUntyped = (value: IrExpr): IrExpr => ({
kind: "libCall",
fn: "net.listenPortDyn",
args: [
value,
ladderFenceExpr(
lowerer,
"listen with a runtime options object or pipe path",
args[0]!,
"pass the port as a number, the path as a string, or the options as an object literal",
),
],
type: F64,
loc,
});
if (args.length === 1 && untyped) {
// An untyped single argument: a function is the callback on an
// arbitrary port; any other value is the port.
const server = lowerer.declareHiddenLocal("%listenServer", NETSERVER_T);
const arg = lowerer.declareHiddenLocal("%listenArg", DYN);
const serverRef = (): IrExpr => varRef(server.id, NETSERVER_T, loc);
const argRef = (): IrExpr => varRef(arg.id, DYN, loc);
const listenStmt = (fn: IrLibFn, callArgs: IrExpr[]): IrStmt => ({
kind: "exprStmt",
expr: { kind: "libCall", fn, args: callArgs, type: VOID, loc },
loc,
});
return {
kind: "seqExpr",
stmts: [
{ kind: "varDecl", localId: server.id, init: receiver, loc },
{
kind: "varDecl",
localId: arg.id,
init: lowerer.coerceToExpected(lowerer.lowerExpr(args[0]!), DYN),
loc,
},
{
kind: "if",
cond: { kind: "dynTest", test: "function", value: argRef(), type: BOOL, loc },
then: [
listenStmt("net.listenCb", [
serverRef(),
numLit(0, loc),
{ kind: "dynCheck", value: argRef(), type: funcOf([], VOID), loc },
]),
],
else_: [listenStmt("net.listen", [serverRef(), portOfUntyped(argRef())])],
loc,
},
],
result: serverRef(),
type: NETSERVER_T,
loc,
};
}
// The options-object form — listen({ port, host?, ipv6Only?, reusePort? }[, cb]),
// the portless listenOnProxyInterface shape: host binds that ONE
// address (an IP literal — the runtime has no resolver here; absent
@@ -1762,7 +1846,9 @@ function lowerNetServerMethodCall(
);
return listenResult("net.listenPathCb", [receiver, path, cb]);
}
const port = lowerer.lowerExprExpecting(args[0]!, F64);
const port = untyped
? portOfUntyped(lowerer.coerceToExpected(lowerer.lowerExpr(args[0]!), DYN))
: lowerer.lowerExprExpecting(args[0]!, F64);
// The optional middle host — listen(port, '127.0.0.1'[, cb]): a
// string-typed second argument is the bind address (the net.connect
// disambiguation), routed through the options-form entry.
@@ -992,6 +992,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"net.connectCb",
"net.connectPath",
"net.connectPathCb",
// listen's port from a checked-dynamic first argument: ERR_SOCKET_BAD_PORT,
// or the fence for forms without a lowering.
"net.listenPortDyn",
// resetAndDestroy on a TLS socket throws ERR_INVALID_HANDLE_TYPE.
"net.sockResetAndDestroy",
// SocketAddress's option checks and parse()'s input check.
@@ -1711,6 +1711,7 @@ function buildLibFnSignatures() {
* compiler-rendered fence — ALWAYS THROWS (the error.nodeThrow
* polymorphic-result carve-out). May-throw seed. */
"net.connectOptsChk": { argTypes: [DYN, STRING], result: VOID },
"net.listenPortDyn": { argTypes: [DYN, STRING], result: F64 },
"net.connectCb": {
argTypes: [F64, STRING, { kind: "func", params: [], ret: VOID }],
result: NETSOCKET_T,
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/http-server-listen-forms.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/http-server-listen-untyped.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/net-error-system-properties.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/net-listen-untyped-arguments.cjs"
],
"diags": []
}
+171 -20
View File
@@ -1,6 +1,7 @@
#include "scr_platform.h"
#include "scr_runtime.h"
#include "scr_system_errors.h"
#include <errno.h>
#include <limits.h>
@@ -413,6 +414,95 @@ void scr_net_fire_err_this(ScrNetLs *l, ScrStr *msg, void *self, ScrDynHandleTag
void scr_net_fire_err(ScrNetLs *l, ScrStr *msg) { scr_net_fire_err_this(l, msg, NULL, 0); }
/* The libuv errno of an error code name (Node's err.errno), 0 for a name
* outside libuv's map. */
static double scr_net_uv_errno(const char *code, size_t len) {
#define SCR_NET_UV_ERRNO(name, text) \
if (len == sizeof #name - 1 && memcmp(code, #name, len) == 0) return SCR_NODE_UV__##name;
SCR_NODE_UV_ERRNO_MAP(SCR_NET_UV_ERRNO)
#undef SCR_NET_UV_ERRNO
return 0;
}
/* The libuv message of an error code name, or NULL. */
static const char *scr_net_uv_text(const char *code, size_t len) {
#define SCR_NET_UV_TEXT(name, text) \
if (len == sizeof #name - 1 && memcmp(code, #name, len) == 0) return text;
SCR_NODE_UV_ERRNO_MAP(SCR_NET_UV_TEXT)
#undef SCR_NET_UV_TEXT
return NULL;
}
/* Node's system error for a net failure message this unit writes:
* "<syscall> <CODE>[ <detail>]" (errnoException, exceptionWithHostPort) or
* "<syscall> <CODE>: <libuv message>[ <detail>]" (uvExceptionWithHostPort).
* The error carries the code, the libuv errno and the syscall; for
* connect, listen and bind, the detail is the address, as "<address>:<port>"
* with a port, and a listen on a pipe path has port -1. NULL for any other
* message; +1 otherwise. */
static ScrError *scr_net_system_error(ScrStr *msg) {
const char *data = msg->data;
const char *end = data + msg->len;
const char *space = memchr(data, ' ', msg->len);
if (space == NULL || space == data) return NULL;
size_t syscall_len = (size_t)(space - data);
const char *code = space + 1;
const char *code_end = code;
while (code_end < end && *code_end != ' ' && *code_end != ':') code_end++;
size_t code_len = (size_t)(code_end - code);
if (code_len < 2 || code[0] != 'E') return NULL;
for (size_t i = 0; i < code_len; i++) {
char c = code[i];
if (!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) return NULL;
}
double uv = scr_net_uv_errno(code, code_len);
if (uv == 0) return NULL;
const char *detail = code_end;
if (detail < end && *detail == ':') {
/* the libuv form: skip ": <message>" */
const char *text = scr_net_uv_text(code, code_len);
size_t text_len = text != NULL ? strlen(text) : 0;
if (text == NULL || (size_t)(end - detail) < 2 + text_len ||
memcmp(detail + 2, text, text_len) != 0)
return NULL;
detail += 2 + text_len;
}
if (detail < end && *detail == ' ') detail++;
ScrError *e = scr_error_new(SCR_ERR_ERROR, msg);
char code_buf[32];
size_t n = code_len < sizeof code_buf - 1 ? code_len : sizeof code_buf - 1;
memcpy(code_buf, code, n);
code_buf[n] = '\0';
scr_error_set_code(e, code_buf);
e->system_errno = uv;
e->system_call = scr_str_new(data, syscall_len);
bool with_address = (syscall_len == 7 && memcmp(data, "connect", 7) == 0) ||
(syscall_len == 6 && memcmp(data, "listen", 6) == 0) ||
(syscall_len == 4 && memcmp(data, "bind", 4) == 0);
if (with_address && detail < end) {
const char *colon = NULL;
for (const char *q = detail; q < end; q++)
if (*q == ':') colon = q;
bool digits = colon != NULL && colon + 1 < end;
for (const char *q = colon != NULL ? colon + 1 : end; q < end; q++)
if (*q < '0' || *q > '9') digits = false;
const char *address_end = digits ? colon : end;
ScrStr *address = scr_str_new(detail, (size_t)(address_end - detail));
scr_error_set_prop(e, "address", 7, scr_dyn_new_str(address));
/* a pipe listen: Node's listenInCluster passes port -1 with the path */
bool pipe_listen = false;
if (!digits && syscall_len == 6) {
unsigned char ip[16];
pipe_listen = inet_pton(AF_INET, address->data, ip) != 1 &&
inet_pton(AF_INET6, address->data, ip) != 1;
}
scr_str_release(address);
if (digits) scr_error_set_prop(e, "port", 4, scr_dyn_new_num(strtod(colon + 1, NULL)));
else if (pipe_listen) scr_error_set_prop(e, "port", 4, scr_dyn_new_num(-1));
}
return e;
}
/* ── the handles ─────────────────────────────────────────────────────── */
enum { SCR_NET_K_SERVER = 1, SCR_NET_K_SOCKET = 2 };
@@ -3075,7 +3165,7 @@ void scr_net_connect_opts_chk(const ScrDyn *opts, const ScrStr *fence) {
char detail[64], msg[160];
const char *d = scr_dyn_specific_type(port, detail, sizeof detail);
int len = snprintf(msg, sizeof msg,
"options.port should be >= 0 and < 65536. Received %s", d);
"Port should be >= 0 and < 65536. Received %s.", d);
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_SOCKET_BAD_PORT");
return;
}
@@ -3098,6 +3188,60 @@ void scr_net_connect_opts_chk(const ScrDyn *opts, const ScrStr *fence) {
scr_throw_lowering_fence(fence);
}
/* The port of listen(arg[, ...]) for a checked-dynamic first argument, as
* Node's Server.prototype.listen normalizes it: a function (listen(cb)),
* undefined and null bind an ephemeral port (0); a number, or a string
* whose Number() is >= 0, passes validatePort and answers its integer.
* Other values take forms that have no lowering here (an options object, a
* pipe path, and the ERR_INVALID_ARG_VALUE kinds) and throw the
* compiler-rendered fence. */
/* Node's validatePort over listen's port value: 1 with *out set for a
* function, undefined or null (0) or a valid port; 0 with ERR_SOCKET_BAD_PORT
* pending for a bad port; -1 for a value that is no port form here (an
* options object, a pipe path, and the ERR_INVALID_ARG_VALUE kinds). */
static int scr_net_listen_port_check(const ScrDyn *v, double *out) {
*out = 0;
if (v == NULL || v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL ||
v->kind == SCR_DYN_FUNC)
return 1;
double n;
bool ok;
if (v->kind == SCR_DYN_NUM) {
n = v->v.num;
ok = n >= 0 && n <= 65535 && trunc(n) == n;
} else if (v->kind == SCR_DYN_STR) {
ScrStr *ps = scr_str_retain(v->v.str);
n = scr_string_to_number(ps);
if (!(n >= 0)) { /* Node's isPipeName: a path */
scr_str_release(ps);
return -1;
}
ScrStr *trimmed = scr_str_trim(ps);
ok = trimmed->len > 0 && n <= 65535 && trunc(n) == n;
scr_str_release(trimmed);
scr_str_release(ps);
} else {
return -1;
}
if (!ok) {
char detail[64], msg[160];
const char *d = scr_dyn_specific_type(v, detail, sizeof detail);
int len = snprintf(msg, sizeof msg,
"options.port should be >= 0 and < 65536. Received %s.", d);
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, len < 0 ? 0 : (size_t)len,
"ERR_SOCKET_BAD_PORT");
return 0;
}
*out = n;
return 1;
}
double scr_net_listen_port_dyn(const ScrDyn *v, const ScrStr *fence) {
double port;
if (scr_net_listen_port_check(v, &port) < 0) scr_throw_lowering_fence(fence);
return port;
}
/* A dial the caller starts LATER (the http agent's maxSockets queue):
* the socket exists, registers, and buffers writes as "connecting" —
* scr_net_sock_dial_start runs the actual dial when a slot frees. */
@@ -3290,6 +3434,8 @@ void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/,
err = errno;
}
#endif
/* libuv's uv_pipe_bind reports ENOENT as EACCES, as Windows does */
if (err == ENOENT) err = EACCES;
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "listen ");
@@ -6060,8 +6206,10 @@ static bool scr_net_sweep_sock(ScrNetSocket *sock) {
if (sock->pending_code != NULL) {
coded = scr_error_new(SCR_ERR_ERROR, msg);
coded->code = scr_str_new(sock->pending_code, strlen(sock->pending_code));
scr_event_error_payload = coded;
} else {
coded = scr_net_system_error(msg);
}
if (coded != NULL) scr_event_error_payload = coded;
scr_net_fire_err_this(&sock->err_ls, msg, sock, SCR_DYNH_NET_SOCKET);
scr_event_error_payload = saved;
if (coded != NULL) scr_error_release(coded);
@@ -6126,7 +6274,12 @@ static void scr_net_sweep(void) {
ScrStr *msg = srv->pending_err;
srv->pending_err = NULL;
ScrAlsCtx *prev = scr_als_dispatch_enter(srv->listen_ctx);
ScrError *system = scr_net_system_error(msg);
ScrError *saved = scr_event_error_payload;
if (system != NULL) scr_event_error_payload = system;
scr_net_fire_err_this(&srv->err_ls, msg, srv, SCR_DYNH_NET_SERVER);
scr_event_error_payload = saved;
if (system != NULL) scr_error_release(system);
scr_als_dispatch_leave(prev);
scr_str_release(msg);
/* A failed listen SETTLES the handle: listeners drop (an error
@@ -6875,29 +7028,27 @@ static ScrDyn *scr_net_dynh_srv_invoke(void *h, ScrDyn *self, const char *method
return scr_dyn_retain(self);
}
if (strcmp(method, "listen") == 0) {
/* listen([port][, host][, cb]) — the static lowering's shapes over
* dyn arguments; the options-object form throws the 'not supported yet'
/* listen([port][, host][, backlog][, cb]) and listen(path[, cb]) over
* dyn arguments, as Node's normalizeArgs reads them: the last argument
* is the callback when it is a function, a first argument that is a
* function, undefined or null binds an unused port, a string whose
* Number() is not >= 0 is a pipe path, and a string second argument is
* the host. The options-object form throws the 'not supported yet'
* Error. */
const ScrDyn *first = argc > 0 ? args[0] : NULL;
const ScrDyn *cb = argc > 0 && args[argc - 1]->kind == SCR_DYN_FUNC ? args[argc - 1] : NULL;
double port = 0;
ScrStr *host = NULL;
const ScrDyn *cb = NULL;
size_t i = 0;
if (i < argc && args[i]->kind == SCR_DYN_NUM) { port = args[i]->v.num; i++; }
else if (i < argc && args[i]->kind == SCR_DYN_STR && args[i]->v.str->len > 0 &&
args[i]->v.str->data[0] >= '0' && args[i]->v.str->data[0] <= '9') {
/* Node coerces a numeric port string */
port = strtod(args[i]->v.str->data, NULL);
i++;
} else if (i < argc && args[i]->kind == SCR_DYN_OBJ) {
int form = scr_net_listen_port_check(first, &port);
if (form == 0) return NULL;
if (form < 0 && first->kind == SCR_DYN_STR) {
scr_net_listen_path(s, first->v.str, cb ? scr_dyn_listener_closure0(cb) : NULL);
return scr_dyn_retain(self);
}
if (form < 0) {
scr_net_dynh_srv_unsupported("listen", "the options-object form on a dynamic value is not modeled (use listen(port[, host][, cb]))");
return NULL;
}
if (i < argc && args[i]->kind == SCR_DYN_STR) { host = args[i]->v.str; i++; }
if (i < argc && args[i]->kind == SCR_DYN_FUNC) { cb = args[i]; i++; }
if (i < argc && args[i]->kind != SCR_DYN_UNDEF && args[i]->kind != SCR_DYN_NULL) {
scr_net_dynh_srv_unsupported("listen", "unrecognized arguments (the modeled shape is listen(port[, host][, cb]))");
return NULL;
}
ScrStr *host = argc > 1 && args[1]->kind == SCR_DYN_STR ? args[1]->v.str : NULL;
ScrClosure *clo = cb ? scr_dyn_listener_closure0(cb) : NULL;
if (host) scr_net_listen_opts(s, port, host, false, clo);
else scr_net_listen(s, port, clo);
+5
View File
@@ -229,6 +229,11 @@ ScrNetSocket *scr_net_connect_attempt(double port, ScrStr *host /*borrowed*/, co
* autoSelectFamily, attempt budget), then the compiler-rendered fence —
* always leaves an exception pending. Borrowed. */
void scr_net_connect_opts_chk(const ScrDyn *opts, const ScrStr *fence);
/* The port of listen(arg[, ...]) for a checked-dynamic first argument
* (Node's normalizeArgs and validatePort): 0 for a function, undefined or
* null; ERR_SOCKET_BAD_PORT pending for a bad port; the fence pending for
* an options object or a pipe path. Borrowed. */
double scr_net_listen_port_dyn(const ScrDyn *v, const ScrStr *fence);
/* connect with a caller lookup (net.connect({ ..., lookup })): invokes
* lookup(hostname, options, answer-closure) synchronously; answer_fn is
* the emitted per-shape thunk that decodes the answer down to
+24
View File
@@ -0,0 +1,24 @@
// server.listen() and server.listen(callback) bind an arbitrary unused port,
// as Node's normalizeArgs takes a function first argument as the callback.
import * as http from "node:http";
import type { AddressInfo } from "node:net";
const first = http.createServer((req, res) => res.end("first"));
first.listen(() => {
const port = (first.address() as AddressInfo).port;
console.log("callback form port > 0", port > 0);
http.get({ host: "127.0.0.1", port }, (res) => {
res.setEncoding("utf8");
res.on("data", (chunk: string) => console.log("body", chunk));
res.on("end", () => {
first.close();
const second = http.createServer((req2, res2) => res2.end("second"));
second.on("listening", () => {
const port2 = (second.address() as AddressInfo).port;
console.log("no-argument form port > 0", port2 > 0, port2 !== port);
second.close();
});
second.listen();
});
});
});
@@ -0,0 +1,24 @@
// server.listen(value) with an untyped value: a function is the 'listening'
// callback on an arbitrary unused port, as in Node; a number is the port.
'use strict';
const http = require('http');
function wrap(fn) {
return function (...args) {
return fn.apply(this, args);
};
}
const server = http.createServer((req, res) => res.end('ok'));
server.listen(wrap(() => {
const port = server.address().port;
console.log('port > 0', port > 0);
server.close(() => {
const other = http.createServer();
const chosen = [0][0];
other.listen(chosen, wrap(() => {
console.log('numeric port > 0', other.address().port > 0);
other.close();
}));
});
}));
@@ -0,0 +1,52 @@
// net 'error' events carry Node's system error properties: errno, code
// and syscall, and for listen and connect the address and port (a Unix
// socket's path, with port -1 for listen). A reset connection's read error
// carries errno, code and syscall only.
"use strict";
const net = require("net");
function describe(label, err, port) {
const hide = (text) => (port === undefined ? text : text.replace(String(port), "<port>"));
console.log(
label,
JSON.stringify({
message: hide(err.message),
code: err.code,
errno: err.errno,
syscall: err.syscall,
address: err.address,
port: port !== undefined && err.port === port ? "<port>" : err.port,
}),
);
}
const taken = net.createServer();
taken.listen(0, "127.0.0.1", () => {
const port = taken.address().port;
const second = net.createServer();
second.on("error", (err) => {
describe("listen in use", err, port);
const unix = net.createServer();
unix.on("error", (err2) => {
describe("listen path", err2);
const client = net.connect(1, "127.0.0.1");
client.on("error", (err3) => {
describe("connect refused", err3);
const resetting = net.createServer((socket) => {
socket.on("error", (err4) => {
describe("read reset", err4);
resetting.close();
taken.close();
});
});
resetting.listen(0, "127.0.0.1", () => {
const victim = net.connect(resetting.address().port, "127.0.0.1", () => {
setTimeout(() => victim.resetAndDestroy(), 20);
});
});
});
});
unix.listen("/nonexistent-dir/sock");
});
second.listen(port, "127.0.0.1");
});
@@ -0,0 +1,52 @@
// server.listen with untyped arguments follows Node's normalizeArgs and
// validatePort: a function is the 'listening' callback on an unused port,
// undefined and null take an unused port, a numeric string is a port, and
// a port out of range or not an integer throws ERR_SOCKET_BAD_PORT, as a
// bad port in a connect options object checked at run time does.
"use strict";
const net = require("net");
// Values read from JSON are untyped, as a test helper's results are.
const raw = JSON.parse(
'{"none":null,"zero":0,"text":"0","bad":[-1,65536,1.5,""," ","70000"]}',
);
const wrap = (fn) => (...args) => fn(...args);
const forms = [
["callback only", (server, done) => server.listen(wrap(done))],
["undefined port", (server, done) => server.listen(raw.missing, done)],
["null port", (server, done) => server.listen(raw.none, done)],
["numeric string", (server, done) => server.listen(raw.text, done)],
["host and callback", (server, done) => server.listen(raw.zero, "127.0.0.1", done)],
];
function next(i) {
if (i === forms.length) {
for (const bad of raw.bad) {
const server = net.createServer();
try {
server.listen(bad);
console.log(JSON.stringify(bad), "listened");
server.close();
} catch (err) {
console.log(JSON.stringify(bad), err.name, err.code, err.message);
}
}
// A connect options object with a computed key is checked at run time.
const key = ["po", "rt"].join("");
try {
net.connect({ [key]: -1 }).destroy();
console.log("connect", "connected");
} catch (err) {
console.log("connect", err.name, err.code, err.message);
}
return;
}
const [label, listen] = forms[i];
const server = net.createServer();
listen(server, () => {
const port = server.address().port;
console.log(label, typeof port, port > 0);
server.close(() => next(i + 1));
});
}
next(0);
@@ -0,0 +1 @@
{ "plain": 2000, "san": 3000 }
@@ -0,0 +1 @@
{ "plain": 2000, "san": 3000 }