Skip to content

Commit 354c2cc

Browse files
anonrigaduh95
authored andcommitted
net: speed up BlockList.check for strings
Skip toLowerCase() for the documented ipv4/ipv6 families and add a V8 Fast API for one-byte address strings. The FastOneByteString callback takes FastApiCallbackOptions so V8 can use the fast path reliably (required for string-shaped Fast API arguments). Overlong IPv6 zone ids are parsed like uv_ip6_addr (unknown zone is scope_id 0; address parts 40+ chars stay a miss). IPv4 zone suffixes stay a miss, matching uv_ip4_addr. Assisted-by: a closed-source coding agent Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com> PR-URL: #66166 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com>
1 parent 2fd51fd commit 354c2cc

5 files changed

Lines changed: 114 additions & 7 deletions

File tree

‎lib/internal/blocklist.js‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,17 @@ class BlockList {
315315
validateString(family, 'family');
316316
// Fast path: pass the string directly to C++ which does
317317
// inet_pton + Apply() without allocating a JS SocketAddress wrapper.
318-
const af = StringPrototypeToLowerCase(family) === 'ipv4' ?
319-
AF_INET : AF_INET6;
318+
// The documented values are 'ipv4' / 'ipv6' (and the IPvX aliases).
319+
// Avoid toLowerCase on that hot path.
320+
let af;
321+
if (family === 'ipv4' || family === 'IPv4') {
322+
af = AF_INET;
323+
} else if (family === 'ipv6' || family === 'IPv6') {
324+
af = AF_INET6;
325+
} else {
326+
af = StringPrototypeToLowerCase(family) === 'ipv4' ?
327+
AF_INET : AF_INET6;
328+
}
320329
return this[kHandle].checkString(address, af);
321330
}
322331
return Boolean(this[kHandle].check(address[kSocketAddressHandle]));

‎src/node_sockaddr.cc‎

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#include "node_hash.h"
99
#include "node_sockaddr-inl.h" // NOLINT(build/include_inline)
1010
#include "uv.h"
11+
#include "v8-fast-api-calls.h"
12+
13+
#include <cstring>
1114

1215
#include <memory>
1316
#include <string>
@@ -18,6 +21,8 @@ namespace node {
1821
using v8::Array;
1922
using v8::CFunction;
2023
using v8::Context;
24+
using v8::FastApiCallbackOptions;
25+
using v8::FastOneByteString;
2126
using v8::FunctionCallbackInfo;
2227
using v8::FunctionTemplate;
2328
using v8::Int32;
@@ -1032,6 +1037,53 @@ void SocketAddressBlockListWrap::CheckString(
10321037
args.GetReturnValue().Set(wrap->blocklist_->Apply(addr));
10331038
}
10341039

1040+
bool SocketAddressBlockListWrap::FastCheckString(
1041+
Local<Object> receiver,
1042+
const FastOneByteString& address,
1043+
int32_t family,
1044+
// NOLINTNEXTLINE(runtime/references) This is V8 api.
1045+
FastApiCallbackOptions& options) {
1046+
// FastOneByteString is not NUL-terminated. Copy onto the stack
1047+
// before any other work: a GC would invalidate `address`.
1048+
//
1049+
// uv_ip6_addr only needs the address part (≤39 chars) plus an
1050+
// optional %zone. A zone longer than UV_IF_NAMESIZE is unknown
1051+
// (scope_id 0), same as omitting it. Keep that behavior when the
1052+
// full string does not fit so Fast API and CheckString agree.
1053+
constexpr size_t kMax = INET6_ADDRSTRLEN + UV_IF_NAMESIZE;
1054+
char buf[kMax];
1055+
if (address.length < kMax) {
1056+
memcpy(buf, address.data, address.length);
1057+
buf[address.length] = '\0';
1058+
} else if (family == AF_INET6) {
1059+
// uv_ip4_addr rejects %zone. Only IPv6 may drop an overlong zone
1060+
// (unknown zone → scope_id 0, same as uv_ip6_addr).
1061+
// uv_ip6_addr copies the address part into a 40-byte buffer
1062+
// (39 chars + NUL). A 40+ char prefix (mixed notation) is
1063+
// truncated and fails inet_pton — do not keep the full prefix.
1064+
const char* percent =
1065+
static_cast<const char*>(memchr(address.data, '%', address.length));
1066+
if (percent == nullptr) return false;
1067+
const size_t addr_len = static_cast<size_t>(percent - address.data);
1068+
if (addr_len >= 40) return false;
1069+
memcpy(buf, address.data, addr_len);
1070+
buf[addr_len] = '\0';
1071+
} else {
1072+
return false;
1073+
}
1074+
USE(options);
1075+
1076+
TRACK_V8_FAST_API_CALL("blocklist.checkString");
1077+
SocketAddressBlockListWrap* wrap =
1078+
FromJSObject<SocketAddressBlockListWrap>(receiver);
1079+
SocketAddress addr;
1080+
if (!SocketAddress::New(family, buf, 0, &addr)) return false;
1081+
return wrap->blocklist_->Apply(addr);
1082+
}
1083+
1084+
CFunction SocketAddressBlockListWrap::fast_check_string_(
1085+
CFunction::Make(&SocketAddressBlockListWrap::FastCheckString));
1086+
10351087
void SocketAddressBlockListWrap::GetRules(
10361088
const FunctionCallbackInfo<Value>& args) {
10371089
Environment* env = Environment::GetCurrent(args);
@@ -1087,7 +1139,11 @@ Local<FunctionTemplate> SocketAddressBlockListWrap::GetConstructorTemplate(
10871139
SetProtoMethod(isolate, tmpl, "removeSubnet", RemoveSubnet);
10881140
SetFastMethod(
10891141
isolate, tmpl->PrototypeTemplate(), "check", Check, &fast_check_);
1090-
SetProtoMethod(isolate, tmpl, "checkString", CheckString);
1142+
SetFastMethod(isolate,
1143+
tmpl->PrototypeTemplate(),
1144+
"checkString",
1145+
CheckString,
1146+
&fast_check_string_);
10911147
SetProtoMethod(isolate, tmpl, "getRules", GetRules);
10921148
SetProtoMethodNoSideEffect(isolate, tmpl, "getSize", GetSize);
10931149
SetProtoMethod(isolate, tmpl, "clear", Clear);

‎src/node_sockaddr.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "node.h"
1010
#include "node_worker.h"
1111
#include "uv.h"
12+
#include "v8-fast-api-calls.h"
1213
#include "v8.h"
1314

1415
#include <compare>
@@ -418,6 +419,12 @@ class SocketAddressBlockListWrap : public BaseObject {
418419
static bool FastCheck(v8::Local<v8::Object> receiver,
419420
v8::Local<v8::Object> addr_obj);
420421
static void CheckString(const v8::FunctionCallbackInfo<v8::Value>& args);
422+
static bool FastCheckString(
423+
v8::Local<v8::Object> receiver,
424+
const v8::FastOneByteString& address,
425+
int32_t family,
426+
// NOLINTNEXTLINE(runtime/references) This is V8 api.
427+
v8::FastApiCallbackOptions& options);
421428
static void GetRules(const v8::FunctionCallbackInfo<v8::Value>& args);
422429
static void GetSize(const v8::FunctionCallbackInfo<v8::Value>& args);
423430
static void Clear(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -465,6 +472,7 @@ class SocketAddressBlockListWrap : public BaseObject {
465472
private:
466473
std::shared_ptr<SocketAddressBlockList> blocklist_;
467474
static v8::CFunction fast_check_;
475+
static v8::CFunction fast_check_string_;
468476
};
469477

470478
} // namespace node

‎test/parallel/test-blocklist-fast-api.js‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ const assert = require('assert');
66
const { BlockList } = require('net');
77
const { internalBinding } = require('internal/test/binding');
88

9-
// The fast API is on the native check() method which takes a
10-
// SocketAddressBase object. The JS BlockList.prototype.check() routes
11-
// string arguments to checkString() which has no fast API, so we need
12-
// to use SocketAddress objects to exercise the fast API path.
9+
// The native check() method takes a SocketAddressBase object.
10+
// String arguments go through checkString(), which also has a fast API.
1311
const { kHandle: kBlockListHandle } = require('internal/blocklist');
1412
const {
1513
SocketAddress,
1614
kHandle: kSocketAddressHandle,
1715
} = require('internal/socketaddress');
16+
const { AF_INET } = internalBinding('block_list');
1817

1918
const blockList = new BlockList();
2019
blockList.addAddress('1.1.1.1');
@@ -31,12 +30,24 @@ function testFastCheck() {
3130
assert.strictEqual(handle.check(addr3), true);
3231
}
3332

33+
function checkString(address) {
34+
return handle.checkString(address, AF_INET);
35+
}
36+
3437
eval('%PrepareFunctionForOptimization(testFastCheck)');
3538
testFastCheck();
3639
eval('%OptimizeFunctionOnNextCall(testFastCheck)');
3740
testFastCheck();
3841

42+
eval('%PrepareFunctionForOptimization(checkString)');
43+
assert.strictEqual(checkString('1.1.1.1'), true);
44+
eval('%OptimizeFunctionOnNextCall(checkString)');
45+
assert.strictEqual(checkString('1.1.1.1'), true);
46+
assert.strictEqual(checkString('2.2.2.2'), false);
47+
assert.strictEqual(checkString('10.0.0.5'), true);
48+
3949
if (common.isDebug) {
4050
const { getV8FastApiCallCount } = internalBinding('debug');
4151
assert.strictEqual(getV8FastApiCallCount('blocklist.check'), 3);
52+
assert.strictEqual(getV8FastApiCallCount('blocklist.checkString'), 3);
4253
}

‎test/parallel/test-blocklist.js‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,29 @@ const util = require('util');
868868
assert.strictEqual(blockList.check('not_valid_ipv6', 'ipv6'), false);
869869
}
870870

871+
// uv_ip6_addr ignores an unknown/overlong zone (scope_id 0). A Fast API
872+
// stack limit must not turn that into a miss after JIT.
873+
{
874+
const blockList = new BlockList();
875+
blockList.addAddress('fe80::1', 'ipv6');
876+
const longZone = `fe80::1%${'z'.repeat(200)}`;
877+
assert.strictEqual(blockList.check('fe80::1', 'ipv6'), true);
878+
assert.strictEqual(blockList.check(longZone, 'ipv6'), true);
879+
assert.strictEqual(blockList.check('x'.repeat(200), 'ipv6'), false);
880+
881+
// uv_ip4_addr rejects zone suffixes. The Fast API overflow path
882+
// must not strip %zone and treat this as 1.1.1.1.
883+
blockList.addAddress('1.1.1.1');
884+
assert.strictEqual(blockList.check(`1.1.1.1%${'z'.repeat(200)}`), false);
885+
886+
// Mixed-notation IPv6 is 45 chars. uv_ip6_addr truncates the
887+
// address part to 39 when a zone is present, so this is a miss.
888+
const mixed = 'ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255';
889+
blockList.addAddress(mixed, 'ipv6');
890+
assert.strictEqual(blockList.check(mixed, 'ipv6'), true);
891+
assert.strictEqual(blockList.check(`${mixed}%${'z'.repeat(200)}`, 'ipv6'), false);
892+
}
893+
871894
// check() family parameter is case-insensitive.
872895
{
873896
const blockList = new BlockList();

0 commit comments

Comments
 (0)