Skip to content

Commit

Permalink
net: support blocklist in net.connect
Browse files Browse the repository at this point in the history
  • Loading branch information
theanarkh committed Nov 29, 2024
1 parent 4cf6fab commit 176a993
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 0 deletions.
6 changes: 6 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a id="ERR_IP_BLOCKED"></a>

### `ERR_IP_BLOCKED`

IP is blocked by `net.BlockList`.

<a id="ERR_LOADER_CHAIN_INCOMPLETE"></a>

### `ERR_LOADER_CHAIN_INCOMPLETE`
Expand Down
2 changes: 2 additions & 0 deletions doc/api/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' +
Expand Down
20 changes: 20 additions & 0 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -510,6 +511,13 @@ function Socket(options) {
// Used after `.destroy()`
this[kBytesRead] = 0;
this[kBytesWritten] = 0;
if (options.blockList) {
if (options.blockList instanceof module.exports.BlockList) {
this.blockList = options.blockList;
} else {
throw new ERR_INVALID_ARG_TYPE('options.blockList', 'net.BlockList', options.blockList);
}
}
}
ObjectSetPrototypeOf(Socket.prototype, stream.Duplex.prototype);
ObjectSetPrototypeOf(Socket, stream.Duplex);
Expand Down Expand Up @@ -1073,6 +1081,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;
Expand Down Expand Up @@ -1162,6 +1174,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);

Expand Down
68 changes: 68 additions & 0 deletions test/parallel/test-net-blocklist.js
Original file line number Diff line number Diff line change
@@ -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));
}

0 comments on commit 176a993

Please sign in to comment.