diff --git a/doc/api/errors.md b/doc/api/errors.md
index f6bf2dc4814c88..c4efb44df7be3f 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2155,6 +2155,12 @@ An attempt was made to open an IPC communication channel with a synchronously
forked Node.js process. See the documentation for the [`child_process`][] module
for more information.
+
+
+### `ERR_IP_BLOCKED`
+
+IP is blocked by `net.BlockList`.
+
### `ERR_LOADER_CHAIN_INCOMPLETE`
diff --git a/doc/api/net.md b/doc/api/net.md
index 7d8a27bf0b1769..e4580d0fe8bfe2 100644
--- a/doc/api/net.md
+++ b/doc/api/net.md
@@ -1069,6 +1069,8 @@ For TCP connections, available `options` are:
* `noDelay` {boolean} If set to `true`, it disables the use of Nagle's algorithm
immediately after the socket is established. **Default:** `false`.
* `port` {number} Required. Port the socket should connect to.
+* `blocklist` {net.BlockList} `blocklist` can be used for disabling outbound
+ access to specific IP addresses, IP ranges, or IP subnets.
For [IPC][] connections, available `options` are:
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 214bed12e62d19..d990f8d5a106aa 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1551,6 +1551,9 @@ E('ERR_IPC_CHANNEL_CLOSED', 'Channel closed', Error);
E('ERR_IPC_DISCONNECTED', 'IPC channel is already disconnected', Error);
E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe', Error);
E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks', Error);
+E('ERR_IP_BLOCKED', function(ip) {
+ return `IP(${ip}) is blocked by net.BlockList`;
+}, Error);
E(
'ERR_LOADER_CHAIN_INCOMPLETE',
'"%s" did not call the next hook in its chain and did not' +
diff --git a/lib/net.js b/lib/net.js
index 52085df6ba0574..d53934a3d62db8 100644
--- a/lib/net.js
+++ b/lib/net.js
@@ -105,6 +105,7 @@ const {
ERR_INVALID_FD_TYPE,
ERR_INVALID_HANDLE_TYPE,
ERR_INVALID_IP_ADDRESS,
+ ERR_IP_BLOCKED,
ERR_MISSING_ARGS,
ERR_SERVER_ALREADY_LISTEN,
ERR_SERVER_NOT_RUNNING,
@@ -204,6 +205,10 @@ function isPipeName(s) {
return typeof s === 'string' && toNumber(s) === false;
}
+function isBlockList(obj) {
+ return obj instanceof module.exports.BlockList;
+}
+
/**
* Creates a new TCP or IPC server
* @param {{
@@ -510,6 +515,12 @@ function Socket(options) {
// Used after `.destroy()`
this[kBytesRead] = 0;
this[kBytesWritten] = 0;
+ if (options.blocklist) {
+ if (!isBlockList(options.blocklist)) {
+ throw new ERR_INVALID_ARG_TYPE('options.blocklist', 'net.BlockList', options.blocklist);
+ }
+ this.blocklist = options.blocklist;
+ }
}
ObjectSetPrototypeOf(Socket.prototype, stream.Duplex.prototype);
ObjectSetPrototypeOf(Socket, stream.Duplex);
@@ -1073,6 +1084,10 @@ function internalConnect(
self.emit('connectionAttempt', address, port, addressType);
if (addressType === 6 || addressType === 4) {
+ if (self.blocklist?.check(address, `ipv${addressType}`)) {
+ self.destroy(new ERR_IP_BLOCKED(address));
+ return;
+ }
const req = new TCPConnectWrap();
req.oncomplete = afterConnect;
req.address = address;
@@ -1162,6 +1177,14 @@ function internalConnectMultiple(context, canceled) {
}
}
+ if (self.blocklist?.check(address, `ipv${addressType}`)) {
+ const ex = new ERR_IP_BLOCKED(address);
+ ArrayPrototypePush(context.errors, ex);
+ self.emit('connectionAttemptFailed', address, port, addressType, ex);
+ internalConnectMultiple(context);
+ return;
+ }
+
debug('connect/multiple: attempting to connect to %s:%d (addressType: %d)', address, port, addressType);
self.emit('connectionAttempt', address, port, addressType);
diff --git a/test/parallel/test-net-blocklist.js b/test/parallel/test-net-blocklist.js
new file mode 100644
index 00000000000000..a525420bd770e7
--- /dev/null
+++ b/test/parallel/test-net-blocklist.js
@@ -0,0 +1,68 @@
+'use strict';
+
+const common = require('../common');
+const net = require('net');
+const assert = require('assert');
+
+const blocklist = new net.BlockList();
+blocklist.addAddress('127.0.0.1');
+blocklist.addAddress('127.0.0.2');
+
+function check(err) {
+ assert.ok(err.code === 'ERR_IP_BLOCKED', err);
+}
+
+// Connect without calling dns.lookup
+{
+ const socket = net.connect({
+ port: 9999,
+ host: '127.0.0.1',
+ blocklist,
+ });
+ socket.on('error', common.mustCall(check));
+}
+
+// Connect with single IP returned by dns.lookup
+{
+ const socket = net.connect({
+ port: 9999,
+ host: 'localhost',
+ blocklist,
+ lookup: function(_, __, cb) {
+ cb(null, '127.0.0.1', 4);
+ },
+ autoSelectFamily: false,
+ });
+
+ socket.on('error', common.mustCall(check));
+}
+
+// Connect with autoSelectFamily and single IP
+{
+ const socket = net.connect({
+ port: 9999,
+ host: 'localhost',
+ blocklist,
+ lookup: function(_, __, cb) {
+ cb(null, [{ address: '127.0.0.1', family: 4 }]);
+ },
+ autoSelectFamily: true,
+ });
+
+ socket.on('error', common.mustCall(check));
+}
+
+// Connect with autoSelectFamily and multiple IPs
+{
+ const socket = net.connect({
+ port: 9999,
+ host: 'localhost',
+ blocklist,
+ lookup: function(_, __, cb) {
+ cb(null, [{ address: '127.0.0.1', family: 4 }, { address: '127.0.0.2', family: 4 }]);
+ },
+ autoSelectFamily: true,
+ });
+
+ socket.on('error', common.mustCall(check));
+}