Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: support blocklist in net.connect #56075

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
23 changes: 23 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 @@ -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 {{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

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));
}
Loading