FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

net: improve performance of net.BlockList · nodejs/node@3d7d277 · GitHub

/ node Public

Commit 3d7d277

Browse files
authored andcommitted
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: #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>
1 parent e9327d1 commit 3d7d277

8 files changed

Lines changed: 2061 additions & 127 deletions

File tree

‎benchmark/net/net-blocklist.js‎

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { BlockList, SocketAddress } = require('net');
5+
6+
const hasAddAddresses = typeof BlockList.prototype.addAddresses === 'function';
7+
8+
const operations = ['check', 'checkWithSocketAddress', 'addAddress'];
9+
if (hasAddAddresses) {
10+
operations.push('addAddresses');
11+
}
12+
13+
const bench = common.createBenchmark(main, {
14+
n: [1e6],
15+
ruleCount: [10, 100, 1000, 10000],
16+
ruleType: ['address', 'subnet', 'mixed'],
17+
checkResult: ['hit', 'miss'],
18+
operation: operations,
19+
}, {
20+
combinationFilter({ operation, ruleCount, ruleType }) {
21+
// addAddress and addAddresses only need address rules, not subnets.
22+
if ((operation === 'addAddress' || operation === 'addAddresses') &&
23+
ruleType !== 'address') {
24+
return false;
25+
}
26+
return true;
27+
},
28+
});
29+
30+
function generateIPv4(index) {
31+
return `${(index >>> 24) & 0xff}.${(index >>> 16) & 0xff}.` +
32+
`${(index >>> 8) & 0xff}.${index & 0xff}`;
33+
}
34+
35+
function buildBlockList(ruleCount, ruleType) {
36+
const blockList = new BlockList();
37+
38+
if (ruleType === 'address' || ruleType === 'mixed') {
39+
const addressCount = ruleType === 'mixed' ?
40+
Math.floor(ruleCount / 2) : ruleCount;
41+
const addresses = [];
42+
for (let i = 0; i < addressCount; i++) {
43+
// Start from 10.0.0.1 to avoid 0.0.0.0
44+
addresses.push(generateIPv4(0x0a000001 + i));
45+
}
46+
if (hasAddAddresses) {
47+
blockList.addAddresses(addresses);
48+
} else {
49+
for (const addr of addresses) {
50+
blockList.addAddress(addr);
51+
}
52+
}
53+
}
54+
55+
if (ruleType === 'subnet' || ruleType === 'mixed') {
56+
const subnetCount = ruleType === 'mixed' ?
57+
Math.floor(ruleCount / 2) : ruleCount;
58+
for (let i = 0; i < subnetCount; i++) {
59+
// Use distinct /24 subnets: 172.i.j.0/24
60+
const second = (i >>> 8) & 0xff;
61+
const third = i & 0xff;
62+
blockList.addSubnet(`172.${second}.${third}.0`, 24);
63+
}
64+
}
65+
66+
return blockList;
67+
}
68+
69+
function main({ n, ruleCount, ruleType, checkResult, operation }) {
70+
if (operation === 'check') {
71+
benchCheck(n, ruleCount, ruleType, checkResult);
72+
} else if (operation === 'checkWithSocketAddress') {
73+
benchCheckWithSocketAddress(n, ruleCount, ruleType, checkResult);
74+
} else if (operation === 'addAddress') {
75+
benchAddAddress(n, ruleCount);
76+
} else if (operation === 'addAddresses') {
77+
benchAddAddresses(n, ruleCount);
78+
}
79+
}
80+
81+
// Benchmark check() with string addresses (the common JS API path).
82+
function benchCheck(n, ruleCount, ruleType, checkResult) {
83+
const blockList = buildBlockList(ruleCount, ruleType);
84+
85+
// For 'hit', use an address that's in the list.
86+
// For 'miss', use an address that's not in the list.
87+
const address = checkResult === 'hit' ? '10.0.0.1' : '192.168.255.255';
88+
89+
bench.start();
90+
for (let i = 0; i < n; i++) {
91+
blockList.check(address);
92+
}
93+
bench.end(n);
94+
}
95+
96+
// Benchmark check() with pre-created SocketAddress objects
97+
// (avoids measuring SocketAddress construction overhead).
98+
function benchCheckWithSocketAddress(n, ruleCount, ruleType, checkResult) {
99+
const blockList = buildBlockList(ruleCount, ruleType);
100+
101+
const address = checkResult === 'hit' ? '10.0.0.1' : '192.168.255.255';
102+
const sa = new SocketAddress({ address });
103+
104+
bench.start();
105+
for (let i = 0; i < n; i++) {
106+
blockList.check(sa);
107+
}
108+
bench.end(n);
109+
}
110+
111+
// Benchmark single addAddress() calls (one lock acquire per call).
112+
function benchAddAddress(n, ruleCount) {
113+
// Scale n down for large rule counts to keep runtime reasonable.
114+
const iterations = Math.min(n, ruleCount * 100);
115+
116+
const addresses = [];
117+
for (let i = 0; i < ruleCount; i++) {
118+
addresses.push(generateIPv4(0x0a000001 + i));
119+
}
120+
121+
bench.start();
122+
for (let i = 0; i < iterations; i++) {
123+
const blockList = new BlockList();
124+
for (let j = 0; j < addresses.length; j++) {
125+
blockList.addAddress(addresses[j]);
126+
}
127+
}
128+
bench.end(iterations);
129+
}
130+
131+
// Benchmark batch addAddresses() (one lock acquire per batch).
132+
function benchAddAddresses(n, ruleCount) {
133+
const iterations = Math.min(n, ruleCount * 100);
134+
135+
const addresses = [];
136+
for (let i = 0; i < ruleCount; i++) {
137+
addresses.push(generateIPv4(0x0a000001 + i));
138+
}
139+
140+
bench.start();
141+
for (let i = 0; i < iterations; i++) {
142+
const blockList = new BlockList();
143+
blockList.addAddresses(addresses);
144+
}
145+
bench.end(iterations);
146+
}

‎doc/api/net.md‎

Lines changed: 169 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,47 @@ added:
9696

9797
Adds a rule to block the given IP address.
9898

99+
### `blockList.addAddresses(addresses[, type])`
100+
101+
<!-- YAML
102+
added: REPLACEME
103+
-->
104+
105+
* `addresses` {string\[]|net.SocketAddress\[]} An array of IPv4 or IPv6
106+
addresses.
107+
* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default:** `'ipv4'`.
108+
109+
Adds multiple address rules to the block list in a single operation.
110+
This is more efficient than calling `blockList.addAddress()` repeatedly
111+
when adding a large number of individual addresses, as the addresses
112+
are inserted under a single internal lock acquisition.
113+
114+
### `blockList.addCIDR(cidr)`
115+
116+
<!-- YAML
117+
added: REPLACEME
118+
-->
119+
120+
* `cidr` {string} An IPv4 or IPv6 subnet in CIDR notation (e.g.
121+
`'10.0.0.0/8'` or `'2001:db8::/32'`).
122+
123+
Adds a subnet rule using CIDR notation. The address family is automatically
124+
detected from the address (IPv6 if the address contains `':'`, IPv4
125+
otherwise). This is equivalent to calling `blockList.addSubnet()` with
126+
the parsed network address, prefix length, and family.
127+
128+
### `blockList.addCIDRs(cidrs)`
129+
130+
<!-- YAML
131+
added: REPLACEME
132+
-->
133+
134+
* `cidrs` {string\[]} An array of IPv4 or IPv6 subnets in CIDR notation.
135+
136+
Adds multiple subnet rules using CIDR notation in a single call. The address
137+
family for each entry is automatically detected. This is equivalent to
138+
calling `blockList.addCIDR()` for each element of the array.
139+
99140
### `blockList.addRange(start, end[, type])`
100141

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

161-
### `blockList.rules`
202+
### `blockList.clear()`
162203

163-
<!-- YAML
164-
added:
165-
- v15.0.0
166-
- v14.18.0
204+
<!--
205+
added: REPLACEME
167206
-->
168207

169-
* Type: {string\[]}
170-
171-
The list of rules added to the blocklist.
172-
173-
### `BlockList.isBlockList(value)`
174-
175-
<!-- YAML
176-
added:
177-
- v23.4.0
178-
- v22.13.0
179-
-->
180-
181-
* `value` {any} Any JS value
182-
* Returns `true` if the `value` is a `net.BlockList`.
208+
Clears all rules from the `BlockList`.
183209

184210
### `blockList.fromJSON(value)`
185211

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

206232
* `value` Blocklist.rules
207233

234+
### `BlockList.isBlockList(value)`
235+
236+
<!-- YAML
237+
added:
238+
- v23.4.0
239+
- v22.13.0
240+
-->
241+
242+
* `value` {any} Any JS value
243+
* Returns `true` if the `value` is a `net.BlockList`.
244+
245+
### `BlockList.PRIVATE_RANGES`
246+
247+
<!-- YAML
248+
added: REPLACEME
249+
-->
250+
251+
* Type: {string\[]}
252+
253+
A frozen array of CIDR strings representing private, loopback, and link-local
254+
IP address ranges. This can be passed to `blockList.addCIDRs()` to quickly
255+
populate a blocklist with all non-routable address ranges.
256+
257+
The included ranges are:
258+
259+
* `10.0.0.0/8` — RFC 1918 private IPv4
260+
* `172.16.0.0/12` — RFC 1918 private IPv4
261+
* `192.168.0.0/16` — RFC 1918 private IPv4
262+
* `127.0.0.0/8` — IPv4 loopback
263+
* `::1/128` — IPv6 loopback
264+
* `169.254.0.0/16` — IPv4 link-local
265+
* `fe80::/10` — IPv6 link-local
266+
* `fc00::/7` — IPv6 unique local (ULA)
267+
268+
```js
269+
const blockList = new net.BlockList();
270+
blockList.addCIDRs(net.BlockList.PRIVATE_RANGES);
271+
272+
console.log(blockList.check('10.0.0.1')); // Prints: true
273+
console.log(blockList.check('127.0.0.1')); // Prints: true
274+
console.log(blockList.check('8.8.8.8')); // Prints: false
275+
```
276+
277+
### `blockList.removeAddress(address[, type])`
278+
279+
<!-- YAML
280+
added: REPLACEME
281+
-->
282+
283+
* `address` {string|net.SocketAddress} An IPv4 or IPv6 address.
284+
* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default:** `'ipv4'`.
285+
286+
Removes a rule that was previously added with `blockList.addAddress()`. The
287+
address must match exactly the value used when the rule was added. If the
288+
specified address does not exist, this is a no-op.
289+
290+
### `blockList.removeCIDR(cidr)`
291+
292+
<!-- YAML
293+
added: REPLACEME
294+
-->
295+
296+
* `cidr` {string} An IPv4 or IPv6 subnet in CIDR notation (e.g.
297+
`'10.0.0.0/8'` or `'2001:db8::/32'`).
298+
299+
Removes a subnet rule using CIDR notation. The address family is automatically
300+
detected from the address. This is equivalent to calling
301+
`blockList.removeSubnet()` with the parsed network address, prefix length,
302+
and family. If the specified subnet does not exist, this is a no-op.
303+
304+
### `blockList.removeRange(start, end[, type])`
305+
306+
<!-- YAML
307+
added: REPLACEME
308+
-->
309+
310+
* `start` {string|net.SocketAddress} The starting IPv4 or IPv6 address in the
311+
range.
312+
* `end` {string|net.SocketAddress} The ending IPv4 or IPv6 address in the range.
313+
* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default:** `'ipv4'`.
314+
315+
Removes a rule that was previously added with `blockList.addRange()`. The `start`
316+
and `end` addresses must match exactly the values used when the rule was added.
317+
If the specified range does not exist, this is a no-op.
318+
319+
### `blockList.removeSubnet(net, prefix[, type])`
320+
321+
<!-- YAML
322+
added: REPLACEME
323+
-->
324+
325+
* `net` {string|net.SocketAddress} The network IPv4 or IPv6 address.
326+
* `prefix` {number} The number of CIDR prefix bits. For IPv4, this
327+
must be a value between `0` and `32`. For IPv6, this must be between
328+
`0` and `128`.
329+
* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default:** `'ipv4'`.
330+
331+
Removes a rule that was previously added with `blockList.addSubnet()`. The
332+
network address and prefix must match exactly the values used when the rule was
333+
added. If the specified subnet does not exist, this is a no-op.
334+
335+
### `blockList.rules`
336+
337+
<!-- YAML
338+
added:
339+
- v15.0.0
340+
- v14.18.0
341+
-->
342+
343+
* Type: {string\[]}
344+
345+
The list of rules added to the blocklist.
346+
347+
### `blockList.size`
348+
349+
<!-- YAML
350+
added: REPLACEME
351+
-->
352+
353+
* Type: {number}
354+
355+
The number of rules in the blocklist. This is equivalent to
356+
`blockList.rules.length` but does not allocate the rules array.
357+
208358
### `blockList.toJSON()`
209359

210360
> Stability: 1.2 - Release candidate

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL