Skip to content

Commit 6568ab5

Browse files
committed
net: support blocklist in net.connect
1 parent 4cf6fab commit 6568ab5

File tree

5 files changed

+102
-0
lines changed

5 files changed

+102
-0
lines changed

doc/api/errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,6 +2155,12 @@ An attempt was made to open an IPC communication channel with a synchronously
21552155
forked Node.js process. See the documentation for the [`child_process`][] module
21562156
for more information.
21572157

2158+
<a id="ERR_IP_BLOCKED"></a>
2159+
2160+
### `ERR_IP_BLOCKED`
2161+
2162+
IP is blocked by `net.BlockList`.
2163+
21582164
<a id="ERR_LOADER_CHAIN_INCOMPLETE"></a>
21592165

21602166
### `ERR_LOADER_CHAIN_INCOMPLETE`

doc/api/net.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,8 @@ For TCP connections, available `options` are:
10691069
* `noDelay` {boolean} If set to `true`, it disables the use of Nagle's algorithm
10701070
immediately after the socket is established. **Default:** `false`.
10711071
* `port` {number} Required. Port the socket should connect to.
1072+
* `blockList` {net.BlockList} `blockList` can be used for disabling outbound
1073+
access to specific IP addresses, IP ranges, or IP subnets.
10721074

10731075
For [IPC][] connections, available `options` are:
10741076

lib/internal/errors.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,9 @@ E('ERR_IPC_CHANNEL_CLOSED', 'Channel closed', Error);
15511551
E('ERR_IPC_DISCONNECTED', 'IPC channel is already disconnected', Error);
15521552
E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe', Error);
15531553
E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks', Error);
1554+
E('ERR_IP_BLOCKED', function(ip) {
1555+
return `IP(${ip}) is blocked by net.BlockList`;
1556+
}, Error);
15541557
E(
15551558
'ERR_LOADER_CHAIN_INCOMPLETE',
15561559
'"%s" did not call the next hook in its chain and did not' +

lib/net.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const {
105105
ERR_INVALID_FD_TYPE,
106106
ERR_INVALID_HANDLE_TYPE,
107107
ERR_INVALID_IP_ADDRESS,
108+
ERR_IP_BLOCKED,
108109
ERR_MISSING_ARGS,
109110
ERR_SERVER_ALREADY_LISTEN,
110111
ERR_SERVER_NOT_RUNNING,
@@ -204,6 +205,10 @@ function isPipeName(s) {
204205
return typeof s === 'string' && toNumber(s) === false;
205206
}
206207

208+
function isBlockList(obj) {
209+
return obj instanceof module.exports.BlockList;
210+
}
211+
207212
/**
208213
* Creates a new TCP or IPC server
209214
* @param {{
@@ -510,6 +515,12 @@ function Socket(options) {
510515
// Used after `.destroy()`
511516
this[kBytesRead] = 0;
512517
this[kBytesWritten] = 0;
518+
if (options.blockList) {
519+
if (!isBlockList(options.blockList)) {
520+
throw new ERR_INVALID_ARG_TYPE('options.blockList', 'net.BlockList', options.blockList);
521+
}
522+
this.blockList = options.blockList;
523+
}
513524
}
514525
ObjectSetPrototypeOf(Socket.prototype, stream.Duplex.prototype);
515526
ObjectSetPrototypeOf(Socket, stream.Duplex);
@@ -1073,6 +1084,10 @@ function internalConnect(
10731084
self.emit('connectionAttempt', address, port, addressType);
10741085

10751086
if (addressType === 6 || addressType === 4) {
1087+
if (self.blockList?.check(address, `ipv${addressType}`)) {
1088+
self.destroy(new ERR_IP_BLOCKED(address));
1089+
return;
1090+
}
10761091
const req = new TCPConnectWrap();
10771092
req.oncomplete = afterConnect;
10781093
req.address = address;
@@ -1162,6 +1177,14 @@ function internalConnectMultiple(context, canceled) {
11621177
}
11631178
}
11641179

1180+
if (self.blockList?.check(address, `ipv${addressType}`)) {
1181+
const ex = new ERR_IP_BLOCKED(address);
1182+
ArrayPrototypePush(context.errors, ex);
1183+
self.emit('connectionAttemptFailed', address, port, addressType, ex);
1184+
internalConnectMultiple(context);
1185+
return;
1186+
}
1187+
11651188
debug('connect/multiple: attempting to connect to %s:%d (addressType: %d)', address, port, addressType);
11661189
self.emit('connectionAttempt', address, port, addressType);
11671190

test/parallel/test-net-blocklist.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const net = require('net');
5+
const assert = require('assert');
6+
7+
const blockList = new net.BlockList();
8+
blockList.addAddress('127.0.0.1');
9+
blockList.addAddress('127.0.0.2');
10+
11+
function check(err) {
12+
assert.ok(err.code === 'ERR_IP_BLOCKED', err);
13+
}
14+
15+
// Connect without calling dns.lookup
16+
{
17+
const socket = net.connect({
18+
port: 9999,
19+
host: '127.0.0.1',
20+
blockList,
21+
});
22+
socket.on('error', common.mustCall(check));
23+
}
24+
25+
// Connect with single IP returned by dns.lookup
26+
{
27+
const socket = net.connect({
28+
port: 9999,
29+
host: 'localhost',
30+
blockList,
31+
lookup: function(_, __, cb) {
32+
cb(null, '127.0.0.1', 4);
33+
},
34+
autoSelectFamily: false,
35+
});
36+
37+
socket.on('error', common.mustCall(check));
38+
}
39+
40+
// Connect with autoSelectFamily and single IP
41+
{
42+
const socket = net.connect({
43+
port: 9999,
44+
host: 'localhost',
45+
blockList,
46+
lookup: function(_, __, cb) {
47+
cb(null, [{ address: '127.0.0.1', family: 4 }]);
48+
},
49+
autoSelectFamily: true,
50+
});
51+
52+
socket.on('error', common.mustCall(check));
53+
}
54+
55+
// Connect with autoSelectFamily and multiple IPs
56+
{
57+
const socket = net.connect({
58+
port: 9999,
59+
host: 'localhost',
60+
blockList,
61+
lookup: function(_, __, cb) {
62+
cb(null, [{ address: '127.0.0.1', family: 4 }, { address: '127.0.0.2', family: 4 }]);
63+
},
64+
autoSelectFamily: true,
65+
});
66+
67+
socket.on('error', common.mustCall(check));
68+
}

0 commit comments

Comments
 (0)