diff --git a/doc/api/dgram.md b/doc/api/dgram.md index 2243b6abdea9bc..4d2ef8dea164f9 100644 --- a/doc/api/dgram.md +++ b/doc/api/dgram.md @@ -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} diff --git a/lib/dgram.js b/lib/dgram.js index 74dd83889990ca..4bf3c90362c5b0 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -55,6 +55,7 @@ const { _createSocketHandle, newHandle, } = require('internal/dgram'); +const { isIP } = require('internal/net'); const { isInt32, validateAbortSignal, @@ -104,6 +105,8 @@ function Socket(type, listener) { let lookup; let recvBufferSize; let sendBufferSize; + let receiveBlockList; + let sendBlockList; let options; if (type !== null && typeof type === 'object') { @@ -112,6 +115,14 @@ function Socket(type, listener) { lookup = options.lookup; recvBufferSize = options.recvBufferSize; sendBufferSize = options.sendBufferSize; + // TODO: validate the params + // https://github.com/nodejs/node/pull/56078 + if (options.receiveBlockList) { + receiveBlockList = options.receiveBlockList; + } + if (options.sendBlockList) { + sendBlockList = options.sendBlockList; + } } const handle = newHandle(type, lookup); @@ -134,6 +145,8 @@ function Socket(type, listener) { ipv6Only: options?.ipv6Only, recvBufferSize, sendBufferSize, + receiveBlockList, + sendBlockList, }; if (options?.signal !== undefined) { @@ -432,7 +445,10 @@ 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)}`)) { + // TODO + ex = new ERR_SOCKET_DGRAM_IS_CONNECTED(); + } if (!ex) { const err = state.handle.connect(ip, port); if (err) { @@ -696,6 +712,14 @@ function doSend(ex, self, ip, list, address, port, callback) { return; } + if (port && state.sendBlockList?.check(ip, `ipv${isIP(ip)}`)) { + if (callback) { + // TODO + process.nextTick(callback, new ERR_SOCKET_DGRAM_IS_CONNECTED()); + } + return; + } + const req = new SendWrap(); req.list = list; // Keep reference alive. req.address = address; @@ -944,6 +968,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); } diff --git a/test/parallel/test-dgram-blocklist.js b/test/parallel/test-dgram-blocklist.js new file mode 100644 index 00000000000000..28d2b22de254f0 --- /dev/null +++ b/test/parallel/test-dgram-blocklist.js @@ -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); + 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); + 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(); + })); + })); +}