Skip to content
Open
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
146 changes: 146 additions & 0 deletions benchmark/net/net-blocklist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
'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);
}
188 changes: 169 additions & 19 deletions doc/api/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,47 @@ added:

Adds a rule to block the given IP address.

### `blockList.addAddresses(addresses[, type])`

<!-- YAML
added: REPLACEME
-->

* `addresses` {string\[]|net.SocketAddress\[]} An array of IPv4 or IPv6
addresses.
* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default:** `'ipv4'`.

Adds multiple address rules to the block list in a single operation.
This is more efficient than calling `blockList.addAddress()` repeatedly
when adding a large number of individual addresses, as the addresses
are inserted under a single internal lock acquisition.

### `blockList.addCIDR(cidr)`

<!-- YAML
added: REPLACEME
-->

* `cidr` {string} An IPv4 or IPv6 subnet in CIDR notation (e.g.
`'10.0.0.0/8'` or `'2001:db8::/32'`).

Adds a subnet rule using CIDR notation. The address family is automatically
detected from the address (IPv6 if the address contains `':'`, IPv4
otherwise). This is equivalent to calling `blockList.addSubnet()` with
the parsed network address, prefix length, and family.

### `blockList.addCIDRs(cidrs)`

<!-- YAML
added: REPLACEME
-->

* `cidrs` {string\[]} An array of IPv4 or IPv6 subnets in CIDR notation.

Adds multiple subnet rules using CIDR notation in a single call. The address
family for each entry is automatically detected. This is equivalent to
calling `blockList.addCIDR()` for each element of the array.

### `blockList.addRange(start, end[, type])`

<!-- YAML
Expand Down Expand Up @@ -158,28 +199,13 @@ console.log(blockList.check('::ffff:7b7b:7b7b', 'ipv6')); // Prints: true
console.log(blockList.check('::ffff:123.123.123.123', 'ipv6')); // Prints: true
```

### `blockList.rules`

<!-- YAML
added:
- v15.0.0
- v14.18.0
-->

* Type: {string\[]}

The list of rules added to the blocklist.

### `BlockList.isBlockList(value)`
### `blockList.clear()`

<!-- YAML
added:
- v23.4.0
- v22.13.0
<!--
added: REPLACEME
-->

* `value` {any} Any JS value
* Returns `true` if the `value` is a `net.BlockList`.
Clears all rules from the `BlockList`.

### `blockList.fromJSON(value)`

Expand All @@ -205,6 +231,130 @@ blockList.fromJSON(JSON.stringify(data));

* `value` Blocklist.rules

### `BlockList.isBlockList(value)`

<!-- YAML
added:
- v23.4.0
- v22.13.0
-->

* `value` {any} Any JS value
* Returns `true` if the `value` is a `net.BlockList`.

### `BlockList.PRIVATE_RANGES`

<!-- YAML
added: REPLACEME
-->

* Type: {string\[]}

A frozen array of CIDR strings representing private, loopback, and link-local
IP address ranges. This can be passed to `blockList.addCIDRs()` to quickly
populate a blocklist with all non-routable address ranges.

The included ranges are:

* `10.0.0.0/8` — RFC 1918 private IPv4
* `172.16.0.0/12` — RFC 1918 private IPv4
* `192.168.0.0/16` — RFC 1918 private IPv4
* `127.0.0.0/8` — IPv4 loopback
* `::1/128` — IPv6 loopback
* `169.254.0.0/16` — IPv4 link-local
* `fe80::/10` — IPv6 link-local
* `fc00::/7` — IPv6 unique local (ULA)

```js
const blockList = new net.BlockList();
blockList.addCIDRs(net.BlockList.PRIVATE_RANGES);

console.log(blockList.check('10.0.0.1')); // Prints: true
console.log(blockList.check('127.0.0.1')); // Prints: true
console.log(blockList.check('8.8.8.8')); // Prints: false
```

### `blockList.removeAddress(address[, type])`

<!-- YAML
added: REPLACEME
-->

* `address` {string|net.SocketAddress} An IPv4 or IPv6 address.
* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default:** `'ipv4'`.

Removes a rule that was previously added with `blockList.addAddress()`. The
address must match exactly the value used when the rule was added. If the
specified address does not exist, this is a no-op.

### `blockList.removeCIDR(cidr)`

<!-- YAML
added: REPLACEME
-->

* `cidr` {string} An IPv4 or IPv6 subnet in CIDR notation (e.g.
`'10.0.0.0/8'` or `'2001:db8::/32'`).

Removes a subnet rule using CIDR notation. The address family is automatically
detected from the address. This is equivalent to calling
`blockList.removeSubnet()` with the parsed network address, prefix length,
and family. If the specified subnet does not exist, this is a no-op.

### `blockList.removeRange(start, end[, type])`

<!-- YAML
added: REPLACEME
-->

* `start` {string|net.SocketAddress} The starting IPv4 or IPv6 address in the
range.
* `end` {string|net.SocketAddress} The ending IPv4 or IPv6 address in the range.
* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default:** `'ipv4'`.

Removes a rule that was previously added with `blockList.addRange()`. The `start`
and `end` addresses must match exactly the values used when the rule was added.
If the specified range does not exist, this is a no-op.

### `blockList.removeSubnet(net, prefix[, type])`

<!-- YAML
added: REPLACEME
-->

* `net` {string|net.SocketAddress} The network IPv4 or IPv6 address.
* `prefix` {number} The number of CIDR prefix bits. For IPv4, this
must be a value between `0` and `32`. For IPv6, this must be between
`0` and `128`.
* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default:** `'ipv4'`.

Removes a rule that was previously added with `blockList.addSubnet()`. The
network address and prefix must match exactly the values used when the rule was
added. If the specified subnet does not exist, this is a no-op.

### `blockList.rules`

<!-- YAML
added:
- v15.0.0
- v14.18.0
-->

* Type: {string\[]}

The list of rules added to the blocklist.

### `blockList.size`

<!-- YAML
added: REPLACEME
-->

* Type: {number}

The number of rules in the blocklist. This is equivalent to
`blockList.rules.length` but does not allocate the rules array.

### `blockList.toJSON()`

> Stability: 1.2 - Release candidate
Expand Down
Loading
Loading