Skip to content

Commit

Permalink
dgram: support blocklist in udp
Browse files Browse the repository at this point in the history
PR-URL: #56087
Reviewed-By: Luigi Pinca <[email protected]>
  • Loading branch information
theanarkh authored and ruyadorno committed Dec 21, 2024
1 parent 28d3ce3 commit 2c406a5
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 1 deletion.
7 changes: 7 additions & 0 deletions doc/api/dgram.md
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,13 @@ changes:
* `sendBufferSize` {number} Sets the `SO_SNDBUF` socket value.
* `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][].
* `signal` {AbortSignal} An AbortSignal that may be used to close a socket.
* `receiveBlockList` {net.BlockList} `receiveBlockList` can be used for discarding
inbound datagram to specific IP addresses, IP ranges, or IP subnets. This does not
work if the server is behind a reverse proxy, NAT, etc. because the address
checked against the blocklist is the address of the proxy, or the one
specified by the NAT.
* `sendBlockList` {net.BlockList} `sendBlockList` can be used for disabling outbound
access to specific IP addresses, IP ranges, or IP subnets.
* `callback` {Function} Attached as a listener for `'message'` events. Optional.
* Returns: {dgram.Socket}

Expand Down
37 changes: 36 additions & 1 deletion lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const {
ERR_BUFFER_OUT_OF_BOUNDS,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_FD_TYPE,
ERR_IP_BLOCKED,
ERR_MISSING_ARGS,
ERR_SOCKET_ALREADY_BOUND,
ERR_SOCKET_BAD_BUFFER_SIZE,
Expand All @@ -53,6 +54,7 @@ const {
_createSocketHandle,
newHandle,
} = require('internal/dgram');
const { isIP } = require('internal/net');
const {
isInt32,
validateAbortSignal,
Expand Down Expand Up @@ -97,12 +99,18 @@ let _cluster = null;
function lazyLoadCluster() {
return _cluster ??= require('cluster');
}
let _blockList = null;
function lazyLoadBlockList() {
return _blockList ??= require('internal/blocklist').BlockList;
}

function Socket(type, listener) {
FunctionPrototypeCall(EventEmitter, this);
let lookup;
let recvBufferSize;
let sendBufferSize;
let receiveBlockList;
let sendBlockList;

let options;
if (type !== null && typeof type === 'object') {
Expand All @@ -117,6 +125,18 @@ function Socket(type, listener) {
}
recvBufferSize = options.recvBufferSize;
sendBufferSize = options.sendBufferSize;
if (options.receiveBlockList) {
if (!lazyLoadBlockList().isBlockList(options.receiveBlockList)) {
throw new ERR_INVALID_ARG_TYPE('options.receiveBlockList', 'net.BlockList', options.receiveBlockList);
}
receiveBlockList = options.receiveBlockList;
}
if (options.sendBlockList) {
if (!lazyLoadBlockList().isBlockList(options.sendBlockList)) {
throw new ERR_INVALID_ARG_TYPE('options.sendBlockList', 'net.BlockList', options.sendBlockList);
}
sendBlockList = options.sendBlockList;
}
}

const handle = newHandle(type, lookup);
Expand All @@ -139,6 +159,8 @@ function Socket(type, listener) {
ipv6Only: options?.ipv6Only,
recvBufferSize,
sendBufferSize,
receiveBlockList,
sendBlockList,
};

if (options?.signal !== undefined) {
Expand Down Expand Up @@ -437,7 +459,9 @@ function doConnect(ex, self, ip, address, port, callback) {
const state = self[kStateSymbol];
if (!state.handle)
return;

if (!ex && state.sendBlockList?.check(ip, `ipv${isIP(ip)}`)) {
ex = new ERR_IP_BLOCKED(ip);
}
if (!ex) {
const err = state.handle.connect(ip, port);
if (err) {
Expand Down Expand Up @@ -701,6 +725,13 @@ function doSend(ex, self, ip, list, address, port, callback) {
return;
}

if (ip && state.sendBlockList?.check(ip, `ipv${isIP(ip)}`)) {
if (callback) {
process.nextTick(callback, new ERR_IP_BLOCKED(ip));
}
return;
}

const req = new SendWrap();
req.list = list; // Keep reference alive.
req.address = address;
Expand Down Expand Up @@ -949,6 +980,10 @@ function onMessage(nread, handle, buf, rinfo) {
if (nread < 0) {
return self.emit('error', new ErrnoException(nread, 'recvmsg'));
}
if (self[kStateSymbol]?.receiveBlockList?.check(rinfo.address,
rinfo.family?.toLocaleLowerCase())) {
return;
}
rinfo.size = buf.length; // compatibility
self.emit('message', buf, rinfo);
}
Expand Down
49 changes: 49 additions & 0 deletions test/parallel/test-dgram-blocklist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const dgram = require('dgram');
const net = require('net');

{
const blockList = new net.BlockList();
blockList.addAddress(common.localhostIPv4);

const connectSocket = dgram.createSocket({ type: 'udp4', sendBlockList: blockList });
connectSocket.connect(9999, common.localhostIPv4, common.mustCall((err) => {
assert.ok(err.code === 'ERR_IP_BLOCKED', err);
connectSocket.close();
}));
}

{
const blockList = new net.BlockList();
blockList.addAddress(common.localhostIPv4);
const sendSocket = dgram.createSocket({ type: 'udp4', sendBlockList: blockList });
sendSocket.send('hello', 9999, common.localhostIPv4, common.mustCall((err) => {
assert.ok(err.code === 'ERR_IP_BLOCKED', err);
sendSocket.close();
}));
}

{
const blockList = new net.BlockList();
blockList.addAddress(common.localhostIPv4);
const receiveSocket = dgram.createSocket({ type: 'udp4', receiveBlockList: blockList });
// Hack to close the socket
const check = blockList.check;
blockList.check = function() {
process.nextTick(() => {
receiveSocket.close();
});
return check.apply(this, arguments);
};
receiveSocket.on('message', common.mustNotCall());
receiveSocket.bind(0, common.localhostIPv4, common.mustCall(() => {
const addressInfo = receiveSocket.address();
const client = dgram.createSocket('udp4');
client.send('hello', addressInfo.port, addressInfo.address, common.mustCall((err) => {
assert.ok(!err);
client.close();
}));
}));
}

0 comments on commit 2c406a5

Please sign in to comment.