Files
James M Snell 27d6cfa9b2 net: improve performance of net.BlockList
* fix duplicate address insertion in SocketAddressBlockList
* fix BlockList rule listing order to match apply
* add minor bound check in BlockList
* improve performance of BlockList apply
* eliminating shared_ptr
* check fast api path
* add clear method to BlockList
* general storage improvements to BlockList
* use shared locks for BlockList reads
* add bulk address adding to BlockList
* add BlockList benchmark
* add remove range/subnet to BlockList
* add cidr notation parsing to BlockList
* add additional apis to BlockList
* add private subnet presets to BlockList

Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: OpenCode/Opus
PR-URL: https://github.com/nodejs/node/pull/64974
Reviewed-By: Tim Perry <pimterry@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
2026-08-06 05:50:42 -07:00

147 lines
4.2 KiB
JavaScript

'use strict';
const common = require('../common.js');
const { BlockList, SocketAddress } = require('net');
const hasAddAddresses = typeof BlockList.prototype.addAddresses === 'function';
const operations = ['check', 'checkWithSocketAddress', 'addAddress'];
if (hasAddAddresses) {
operations.push('addAddresses');
}
const bench = common.createBenchmark(main, {
n: [1e6],
ruleCount: [10, 100, 1000, 10000],
ruleType: ['address', 'subnet', 'mixed'],
checkResult: ['hit', 'miss'],
operation: operations,
}, {
combinationFilter({ operation, ruleCount, ruleType }) {
// addAddress and addAddresses only need address rules, not subnets.
if ((operation === 'addAddress' || operation === 'addAddresses') &&
ruleType !== 'address') {
return false;
}
return true;
},
});
function generateIPv4(index) {
return `${(index >>> 24) & 0xff}.${(index >>> 16) & 0xff}.` +
`${(index >>> 8) & 0xff}.${index & 0xff}`;
}
function buildBlockList(ruleCount, ruleType) {
const blockList = new BlockList();
if (ruleType === 'address' || ruleType === 'mixed') {
const addressCount = ruleType === 'mixed' ?
Math.floor(ruleCount / 2) : ruleCount;
const addresses = [];
for (let i = 0; i < addressCount; i++) {
// Start from 10.0.0.1 to avoid 0.0.0.0
addresses.push(generateIPv4(0x0a000001 + i));
}
if (hasAddAddresses) {
blockList.addAddresses(addresses);
} else {
for (const addr of addresses) {
blockList.addAddress(addr);
}
}
}
if (ruleType === 'subnet' || ruleType === 'mixed') {
const subnetCount = ruleType === 'mixed' ?
Math.floor(ruleCount / 2) : ruleCount;
for (let i = 0; i < subnetCount; i++) {
// Use distinct /24 subnets: 172.i.j.0/24
const second = (i >>> 8) & 0xff;
const third = i & 0xff;
blockList.addSubnet(`172.${second}.${third}.0`, 24);
}
}
return blockList;
}
function main({ n, ruleCount, ruleType, checkResult, operation }) {
if (operation === 'check') {
benchCheck(n, ruleCount, ruleType, checkResult);
} else if (operation === 'checkWithSocketAddress') {
benchCheckWithSocketAddress(n, ruleCount, ruleType, checkResult);
} else if (operation === 'addAddress') {
benchAddAddress(n, ruleCount);
} else if (operation === 'addAddresses') {
benchAddAddresses(n, ruleCount);
}
}
// Benchmark check() with string addresses (the common JS API path).
function benchCheck(n, ruleCount, ruleType, checkResult) {
const blockList = buildBlockList(ruleCount, ruleType);
// For 'hit', use an address that's in the list.
// For 'miss', use an address that's not in the list.
const address = checkResult === 'hit' ? '10.0.0.1' : '192.168.255.255';
bench.start();
for (let i = 0; i < n; i++) {
blockList.check(address);
}
bench.end(n);
}
// Benchmark check() with pre-created SocketAddress objects
// (avoids measuring SocketAddress construction overhead).
function benchCheckWithSocketAddress(n, ruleCount, ruleType, checkResult) {
const blockList = buildBlockList(ruleCount, ruleType);
const address = checkResult === 'hit' ? '10.0.0.1' : '192.168.255.255';
const sa = new SocketAddress({ address });
bench.start();
for (let i = 0; i < n; i++) {
blockList.check(sa);
}
bench.end(n);
}
// Benchmark single addAddress() calls (one lock acquire per call).
function benchAddAddress(n, ruleCount) {
// Scale n down for large rule counts to keep runtime reasonable.
const iterations = Math.min(n, ruleCount * 100);
const addresses = [];
for (let i = 0; i < ruleCount; i++) {
addresses.push(generateIPv4(0x0a000001 + i));
}
bench.start();
for (let i = 0; i < iterations; i++) {
const blockList = new BlockList();
for (let j = 0; j < addresses.length; j++) {
blockList.addAddress(addresses[j]);
}
}
bench.end(iterations);
}
// Benchmark batch addAddresses() (one lock acquire per batch).
function benchAddAddresses(n, ruleCount) {
const iterations = Math.min(n, ruleCount * 100);
const addresses = [];
for (let i = 0; i < ruleCount; i++) {
addresses.push(generateIPv4(0x0a000001 + i));
}
bench.start();
for (let i = 0; i < iterations; i++) {
const blockList = new BlockList();
blockList.addAddresses(addresses);
}
bench.end(iterations);
}