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

dgram: support blocklist in udp #56087

Merged
merged 1 commit into from
Dec 15, 2024
Merged
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
7 changes: 7 additions & 0 deletions doc/api/dgram.md
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,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 @@ -41,6 +41,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 @@ -55,6 +56,7 @@ const {
_createSocketHandle,
newHandle,
} = require('internal/dgram');
const { isIP } = require('internal/net');
const {
isInt32,
validateAbortSignal,
Expand Down Expand Up @@ -99,12 +101,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 @@ -119,6 +127,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 @@ -141,6 +161,8 @@ function Socket(type, listener) {
ipv6Only: options?.ipv6Only,
recvBufferSize,
sendBufferSize,
receiveBlockList,
sendBlockList,
};

if (options?.signal !== undefined) {
Expand Down Expand Up @@ -439,7 +461,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 @@ -703,6 +727,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 @@ -951,6 +982,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();
}));
}));
}
Loading