Skip to content

net: make multiple improvements to net.BlockList - #64974

Open
jasnell wants to merge 18 commits into
nodejs:mainfrom
jasnell:jasnell/blocklist-improvements
Open

net: make multiple improvements to net.BlockList#64974
jasnell wants to merge 18 commits into
nodejs:mainfrom
jasnell:jasnell/blocklist-improvements

Conversation

@jasnell

@jasnell jasnell commented Aug 3, 2026

Copy link
Copy Markdown
Member

New APIs:

  • addCIDR(cidr): Parse CIDR string, auto-detect family
  • addCIDRs(cidrs): Batch CIDR add
  • addAddresses(addresses): Batch address add
  • removeAddress(address): Remove exact address rule
  • removeRange(start, end): Remove range rule
  • removeSubnet(net, prefix): Remove subnet rule (+ trie cleanup)
  • removeCIDR(cidr): Remove subnet by CIDR string
  • clear(): Remove all rules
  • size: Rule count without allocating rules array
  • BlockList.PRIVATE_RANGES: Array of RFC 1918 + loopback + link-local + ULA CIDRs

Performance improvements:

  • Constant-time address lookup
  • Radix trie for subnet lookup
  • String check fast path
  • V8 fast api for checks
  • Batch insert
  • R/W locking

Adds a benchmark and more tests.

  • Addresses
    • 10 rules ... 9x perf improvement
    • 100 rules ... 13x perf improvement
    • 1000 rules ... 58x perf improvement
    • 10000 rules ... 1186x perf improvement
  • Subnets
    • 10 rules ... 5x perf improvement
    • 100 rules ... 6x perf improvement
    • 1000 rules ... 24x perf improvement
    • 10000 rules ... 418x perf improvement

Essentially went from O(n) to mostly O(1) for most cases.

Signed-off-by: James M Snell jasnell@gmail.com

jasnell added 16 commits August 2, 2026 20:58
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Take O(1) fast-path when possible

Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode/Claude
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode/Opus
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: OpenCode/Opus
@jasnell
jasnell requested review from mcollina and pimterry August 3, 2026 06:20
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/net
  • @nodejs/performance

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Aug 3, 2026
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.26271% with 79 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.23%. Comparing base (f43086d) to head (e3055ea).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
src/node_sockaddr.cc 76.25% 24 Missing and 52 partials ⚠️
lib/internal/blocklist.js 97.93% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #64974      +/-   ##
==========================================
- Coverage   90.27%   90.23%   -0.05%     
==========================================
  Files         762      762              
  Lines      247515   247941     +426     
  Branches    46683    46793     +110     
==========================================
+ Hits       223447   223730     +283     
- Misses      15485    15601     +116     
- Partials     8583     8610      +27     
Files with missing lines Coverage Δ
src/node_sockaddr.h 51.28% <100.00%> (+13.04%) ⬆️
lib/internal/blocklist.js 93.37% <97.93%> (+1.98%) ⬆️
src/node_sockaddr.cc 62.36% <76.25%> (-7.64%) ⬇️

... and 32 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread lib/internal/blocklist.js
Comment thread lib/internal/blocklist.js
Comment thread lib/internal/blocklist.js
Comment thread lib/internal/blocklist.js
Comment thread lib/internal/blocklist.js
Comment thread lib/internal/blocklist.js
Comment thread lib/internal/blocklist.js
jasnell and others added 2 commits August 3, 2026 09:58
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
@jasnell
jasnell force-pushed the jasnell/blocklist-improvements branch from 390b7fa to 5623bfb Compare August 3, 2026 17:04
Comment thread src/node_sockaddr.cc
}

template <typename Callback>
void SocketAddressBlockList::SubnetTrie::Walk(Callback cb) const {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both Walk and WalkImpl are dead code, these aren't used anywhere.

Comment thread src/node_sockaddr.cc
if (address.family() == AF_INET) {
if (ipv4_subnets_.Lookup(bytes, bits)) return true;
// Also check IPv6 trie for ::ffff:x.x.x.x subnets.
uint8_t mapped[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're mirroring IPv4 & IPv6 handling in both directions, and we could save some work by just doing one. We duplicate writes (changing anything IPv4-related writes to both ranges) and we duplicate reads (all checks read both ranges). Surely there's a way we can just duplicate the write side and only check once read-side?

Comment thread src/node_sockaddr.cc
if (prefix >= 96 && memcmp(bytes, v4mapped, 12) == 0) {
ipv4_subnets_.Remove(bytes + 12, prefix - 96);
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal logic ignores overlaps, which results in a selection of fun ways to get this state out of sync. One example:

bl.addSubnet('10.0.0.0', 8);
bl.addSubnet('10.1.0.0', 16);
bl.removeSubnet('10.0.0.0', 8);

bl.rules // Lists 10.1.0.0./16
bl.check('10.1.2.3') // False - not blocked

This is a bit tricky to fix. If you have rule A & B that overlap, and you remove rule A, you can't clear all of A's range from the trie - you have to clear only the part that didn't overlap. Or you clear everything, but then re-apply B? Fun.

Comment thread lib/internal/blocklist.js
// Fast path: pass the string directly to C++ which does
// inet_pton + Apply() without allocating a JS SocketAddress wrapper.
const af = family === 'ipv4' ? AF_INET : AF_INET6;
return this[kHandle].checkString(address, af);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This no longer does normalizes family which might catch some people out:

> bl.addAddress('10.0.0.1')
> bl.check('10.0.0.1', 'IPv4')
false
> bl.check('10.0.0.1', 'ipv4')
true

Both return true on main.

function testFastCheck() {
assert(blockList.check('1.1.1.1'));
assert(!blockList.check('2.2.2.2'));
assert(blockList.check('10.0.0.5'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is supposed to be testing SocketAddressBlockListWrap::FastCheck, is that right? It looks like it doesn't actually test that, because that path is only for SocketAddress args and this all passes strings.

Comment thread lib/internal/blocklist.js
}
for (let i = 0; i < cidrs.length; i++) {
validateString(cidrs[i], `cidrs[${i}]`);
this.addCIDR(cidrs[i]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to validate all in advance, and then apply them all, so that if an exception is thrown then nothing is applied instead of half-applying the array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants