Skip to content

Commit 8e6556f

Browse files
committed
dgram: support blocklist in udp
1 parent 0547dcf commit 8e6556f

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

doc/api/dgram.md

+7
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,13 @@ changes:
959959
* `sendBufferSize` {number} Sets the `SO_SNDBUF` socket value.
960960
* `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][].
961961
* `signal` {AbortSignal} An AbortSignal that may be used to close a socket.
962+
* `receiveBlockList` {net.BlockList} `receiveBlockList` can be used for discarding
963+
inbound datagram to specific IP addresses, IP ranges, or IP subnets. This does not
964+
work if the server is behind a reverse proxy, NAT, etc. because the address
965+
checked against the blocklist is the address of the proxy, or the one
966+
specified by the NAT.
967+
* `sendBlockList` {net.BlockList} `sendBlockList` can be used for disabling outbound
968+
access to specific IP addresses, IP ranges, or IP subnets.
962969
* `callback` {Function} Attached as a listener for `'message'` events. Optional.
963970
* Returns: {dgram.Socket}
964971

lib/dgram.js

+36-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const {
4141
ERR_BUFFER_OUT_OF_BOUNDS,
4242
ERR_INVALID_ARG_TYPE,
4343
ERR_INVALID_FD_TYPE,
44+
ERR_IP_BLOCKED,
4445
ERR_MISSING_ARGS,
4546
ERR_SOCKET_ALREADY_BOUND,
4647
ERR_SOCKET_BAD_BUFFER_SIZE,
@@ -55,6 +56,7 @@ const {
5556
_createSocketHandle,
5657
newHandle,
5758
} = require('internal/dgram');
59+
const { isIP } = require('internal/net');
5860
const {
5961
isInt32,
6062
validateAbortSignal,
@@ -99,12 +101,18 @@ let _cluster = null;
99101
function lazyLoadCluster() {
100102
return _cluster ??= require('cluster');
101103
}
104+
let _blockList = null;
105+
function lazyLoadBlockList() {
106+
return _blockList ??= require('internal/blocklist').BlockList;
107+
}
102108

103109
function Socket(type, listener) {
104110
FunctionPrototypeCall(EventEmitter, this);
105111
let lookup;
106112
let recvBufferSize;
107113
let sendBufferSize;
114+
let receiveBlockList;
115+
let sendBlockList;
108116

109117
let options;
110118
if (type !== null && typeof type === 'object') {
@@ -119,6 +127,18 @@ function Socket(type, listener) {
119127
}
120128
recvBufferSize = options.recvBufferSize;
121129
sendBufferSize = options.sendBufferSize;
130+
if (options.receiveBlockList) {
131+
if (!lazyLoadBlockList().isBlockList(options.receiveBlockList)) {
132+
throw new ERR_INVALID_ARG_TYPE('options.receiveBlockList', 'net.BlockList', options.receiveBlockList);
133+
}
134+
receiveBlockList = options.receiveBlockList;
135+
}
136+
if (options.sendBlockList) {
137+
if (!lazyLoadBlockList().isBlockList(options.sendBlockList)) {
138+
throw new ERR_INVALID_ARG_TYPE('options.sendBlockList', 'net.BlockList', options.sendBlockList);
139+
}
140+
sendBlockList = options.sendBlockList;
141+
}
122142
}
123143

124144
const handle = newHandle(type, lookup);
@@ -141,6 +161,8 @@ function Socket(type, listener) {
141161
ipv6Only: options?.ipv6Only,
142162
recvBufferSize,
143163
sendBufferSize,
164+
receiveBlockList,
165+
sendBlockList,
144166
};
145167

146168
if (options?.signal !== undefined) {
@@ -439,7 +461,9 @@ function doConnect(ex, self, ip, address, port, callback) {
439461
const state = self[kStateSymbol];
440462
if (!state.handle)
441463
return;
442-
464+
if (!ex && state.sendBlockList?.check(ip, `ipv${isIP(ip)}`)) {
465+
ex = new ERR_IP_BLOCKED(ip);
466+
}
443467
if (!ex) {
444468
const err = state.handle.connect(ip, port);
445469
if (err) {
@@ -703,6 +727,13 @@ function doSend(ex, self, ip, list, address, port, callback) {
703727
return;
704728
}
705729

730+
if (ip && state.sendBlockList?.check(ip, `ipv${isIP(ip)}`)) {
731+
if (callback) {
732+
process.nextTick(callback, new ERR_IP_BLOCKED(ip));
733+
}
734+
return;
735+
}
736+
706737
const req = new SendWrap();
707738
req.list = list; // Keep reference alive.
708739
req.address = address;
@@ -951,6 +982,10 @@ function onMessage(nread, handle, buf, rinfo) {
951982
if (nread < 0) {
952983
return self.emit('error', new ErrnoException(nread, 'recvmsg'));
953984
}
985+
if (self[kStateSymbol]?.receiveBlockList?.check(rinfo.address,
986+
rinfo.family?.toLocaleLowerCase())) {
987+
return;
988+
}
954989
rinfo.size = buf.length; // compatibility
955990
self.emit('message', buf, rinfo);
956991
}

test/parallel/test-dgram-blocklist.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const dgram = require('dgram');
5+
const net = require('net');
6+
7+
{
8+
const blockList = new net.BlockList();
9+
blockList.addAddress(common.localhostIPv4);
10+
11+
const connectSocket = dgram.createSocket({ type: 'udp4', sendBlockList: blockList });
12+
connectSocket.connect(9999, common.localhostIPv4, common.mustCall((err) => {
13+
assert.ok(err.code === 'ERR_IP_BLOCKED', err);
14+
connectSocket.close();
15+
}));
16+
}
17+
18+
{
19+
const blockList = new net.BlockList();
20+
blockList.addAddress(common.localhostIPv4);
21+
const sendSocket = dgram.createSocket({ type: 'udp4', sendBlockList: blockList });
22+
sendSocket.send('hello', 9999, common.localhostIPv4, common.mustCall((err) => {
23+
assert.ok(err.code === 'ERR_IP_BLOCKED', err);
24+
sendSocket.close();
25+
}));
26+
}
27+
28+
{
29+
const blockList = new net.BlockList();
30+
blockList.addAddress(common.localhostIPv4);
31+
const receiveSocket = dgram.createSocket({ type: 'udp4', receiveBlockList: blockList });
32+
// Hack to close the socket
33+
const check = blockList.check;
34+
blockList.check = function() {
35+
process.nextTick(() => {
36+
receiveSocket.close();
37+
});
38+
return check.apply(this, arguments);
39+
};
40+
receiveSocket.on('message', common.mustNotCall());
41+
receiveSocket.bind(0, common.localhostIPv4, common.mustCall(() => {
42+
const addressInfo = receiveSocket.address();
43+
const client = dgram.createSocket('udp4');
44+
client.send('hello', addressInfo.port, addressInfo.address, common.mustCall((err) => {
45+
assert.ok(!err);
46+
client.close();
47+
}));
48+
}));
49+
}

0 commit comments

Comments
 (0)