Commit bc6e1ad8f9a for nodejs

commit bc6e1ad8f9aa620bc20b8771509d1fcb26d7ee23
Author: Yagiz Nizipli <yagiz@nizipli.com>
Date:   Fri Sep 25 10:39:01 2026 -0400

    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: https://github.com/nodejs/node/pull/66166
    Reviewed-By: James M Snell <jasnell@gmail.com>
    Reviewed-By: Tim Perry <pimterry@gmail.com>

diff --git a/lib/internal/blocklist.js b/lib/internal/blocklist.js
index 9436078eaa2..b4d6fe90512 100644
--- a/lib/internal/blocklist.js
+++ b/lib/internal/blocklist.js
@@ -315,8 +315,17 @@ class BlockList {
       validateString(family, 'family');
       // Fast path: pass the string directly to C++ which does
       // inet_pton + Apply() without allocating a JS SocketAddress wrapper.
-      const af = StringPrototypeToLowerCase(family) === 'ipv4' ?
-        AF_INET : AF_INET6;
+      // The documented values are 'ipv4' / 'ipv6' (and the IPvX aliases).
+      // Avoid toLowerCase on that hot path.
+      let af;
+      if (family === 'ipv4' || family === 'IPv4') {
+        af = AF_INET;
+      } else if (family === 'ipv6' || family === 'IPv6') {
+        af = AF_INET6;
+      } else {
+        af = StringPrototypeToLowerCase(family) === 'ipv4' ?
+          AF_INET : AF_INET6;
+      }
       return this[kHandle].checkString(address, af);
     }
     return Boolean(this[kHandle].check(address[kSocketAddressHandle]));
diff --git a/src/node_sockaddr.cc b/src/node_sockaddr.cc
index 562bb614600..3f903f548e1 100644
--- a/src/node_sockaddr.cc
+++ b/src/node_sockaddr.cc
@@ -8,6 +8,9 @@
 #include "node_hash.h"
 #include "node_sockaddr-inl.h"  // NOLINT(build/include_inline)
 #include "uv.h"
+#include "v8-fast-api-calls.h"
+
+#include <cstring>

 #include <memory>
 #include <string>
@@ -18,6 +21,8 @@ namespace node {
 using v8::Array;
 using v8::CFunction;
 using v8::Context;
+using v8::FastApiCallbackOptions;
+using v8::FastOneByteString;
 using v8::FunctionCallbackInfo;
 using v8::FunctionTemplate;
 using v8::Int32;
@@ -1032,6 +1037,53 @@ void SocketAddressBlockListWrap::CheckString(
   args.GetReturnValue().Set(wrap->blocklist_->Apply(addr));
 }

+bool SocketAddressBlockListWrap::FastCheckString(
+    Local<Object> receiver,
+    const FastOneByteString& address,
+    int32_t family,
+    // NOLINTNEXTLINE(runtime/references) This is V8 api.
+    FastApiCallbackOptions& options) {
+  // FastOneByteString is not NUL-terminated. Copy onto the stack
+  // before any other work: a GC would invalidate `address`.
+  //
+  // uv_ip6_addr only needs the address part (≤39 chars) plus an
+  // optional %zone. A zone longer than UV_IF_NAMESIZE is unknown
+  // (scope_id 0), same as omitting it. Keep that behavior when the
+  // full string does not fit so Fast API and CheckString agree.
+  constexpr size_t kMax = INET6_ADDRSTRLEN + UV_IF_NAMESIZE;
+  char buf[kMax];
+  if (address.length < kMax) {
+    memcpy(buf, address.data, address.length);
+    buf[address.length] = '\0';
+  } else if (family == AF_INET6) {
+    // uv_ip4_addr rejects %zone. Only IPv6 may drop an overlong zone
+    // (unknown zone → scope_id 0, same as uv_ip6_addr).
+    // uv_ip6_addr copies the address part into a 40-byte buffer
+    // (39 chars + NUL). A 40+ char prefix (mixed notation) is
+    // truncated and fails inet_pton — do not keep the full prefix.
+    const char* percent =
+        static_cast<const char*>(memchr(address.data, '%', address.length));
+    if (percent == nullptr) return false;
+    const size_t addr_len = static_cast<size_t>(percent - address.data);
+    if (addr_len >= 40) return false;
+    memcpy(buf, address.data, addr_len);
+    buf[addr_len] = '\0';
+  } else {
+    return false;
+  }
+  USE(options);
+
+  TRACK_V8_FAST_API_CALL("blocklist.checkString");
+  SocketAddressBlockListWrap* wrap =
+      FromJSObject<SocketAddressBlockListWrap>(receiver);
+  SocketAddress addr;
+  if (!SocketAddress::New(family, buf, 0, &addr)) return false;
+  return wrap->blocklist_->Apply(addr);
+}
+
+CFunction SocketAddressBlockListWrap::fast_check_string_(
+    CFunction::Make(&SocketAddressBlockListWrap::FastCheckString));
+
 void SocketAddressBlockListWrap::GetRules(
     const FunctionCallbackInfo<Value>& args) {
   Environment* env = Environment::GetCurrent(args);
@@ -1087,7 +1139,11 @@ Local<FunctionTemplate> SocketAddressBlockListWrap::GetConstructorTemplate(
     SetProtoMethod(isolate, tmpl, "removeSubnet", RemoveSubnet);
     SetFastMethod(
         isolate, tmpl->PrototypeTemplate(), "check", Check, &fast_check_);
-    SetProtoMethod(isolate, tmpl, "checkString", CheckString);
+    SetFastMethod(isolate,
+                  tmpl->PrototypeTemplate(),
+                  "checkString",
+                  CheckString,
+                  &fast_check_string_);
     SetProtoMethod(isolate, tmpl, "getRules", GetRules);
     SetProtoMethodNoSideEffect(isolate, tmpl, "getSize", GetSize);
     SetProtoMethod(isolate, tmpl, "clear", Clear);
diff --git a/src/node_sockaddr.h b/src/node_sockaddr.h
index ef4155110db..dccfc798fa4 100644
--- a/src/node_sockaddr.h
+++ b/src/node_sockaddr.h
@@ -9,6 +9,7 @@
 #include "node.h"
 #include "node_worker.h"
 #include "uv.h"
+#include "v8-fast-api-calls.h"
 #include "v8.h"

 #include <compare>
@@ -418,6 +419,12 @@ class SocketAddressBlockListWrap : public BaseObject {
   static bool FastCheck(v8::Local<v8::Object> receiver,
                         v8::Local<v8::Object> addr_obj);
   static void CheckString(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static bool FastCheckString(
+      v8::Local<v8::Object> receiver,
+      const v8::FastOneByteString& address,
+      int32_t family,
+      // NOLINTNEXTLINE(runtime/references) This is V8 api.
+      v8::FastApiCallbackOptions& options);
   static void GetRules(const v8::FunctionCallbackInfo<v8::Value>& args);
   static void GetSize(const v8::FunctionCallbackInfo<v8::Value>& args);
   static void Clear(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -465,6 +472,7 @@ class SocketAddressBlockListWrap : public BaseObject {
  private:
   std::shared_ptr<SocketAddressBlockList> blocklist_;
   static v8::CFunction fast_check_;
+  static v8::CFunction fast_check_string_;
 };

 }  // namespace node
diff --git a/test/parallel/test-blocklist-fast-api.js b/test/parallel/test-blocklist-fast-api.js
index 3c59ad361d1..c0d9db2d4c7 100644
--- a/test/parallel/test-blocklist-fast-api.js
+++ b/test/parallel/test-blocklist-fast-api.js
@@ -6,15 +6,14 @@ const assert = require('assert');
 const { BlockList } = require('net');
 const { internalBinding } = require('internal/test/binding');

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

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

+function checkString(address) {
+  return handle.checkString(address, AF_INET);
+}
+
 eval('%PrepareFunctionForOptimization(testFastCheck)');
 testFastCheck();
 eval('%OptimizeFunctionOnNextCall(testFastCheck)');
 testFastCheck();

+eval('%PrepareFunctionForOptimization(checkString)');
+assert.strictEqual(checkString('1.1.1.1'), true);
+eval('%OptimizeFunctionOnNextCall(checkString)');
+assert.strictEqual(checkString('1.1.1.1'), true);
+assert.strictEqual(checkString('2.2.2.2'), false);
+assert.strictEqual(checkString('10.0.0.5'), true);
+
 if (common.isDebug) {
   const { getV8FastApiCallCount } = internalBinding('debug');
   assert.strictEqual(getV8FastApiCallCount('blocklist.check'), 3);
+  assert.strictEqual(getV8FastApiCallCount('blocklist.checkString'), 3);
 }
diff --git a/test/parallel/test-blocklist.js b/test/parallel/test-blocklist.js
index f1fc42f6304..b0fe4303bfb 100644
--- a/test/parallel/test-blocklist.js
+++ b/test/parallel/test-blocklist.js
@@ -868,6 +868,29 @@ const util = require('util');
   assert.strictEqual(blockList.check('not_valid_ipv6', 'ipv6'), false);
 }

+// uv_ip6_addr ignores an unknown/overlong zone (scope_id 0). A Fast API
+// stack limit must not turn that into a miss after JIT.
+{
+  const blockList = new BlockList();
+  blockList.addAddress('fe80::1', 'ipv6');
+  const longZone = `fe80::1%${'z'.repeat(200)}`;
+  assert.strictEqual(blockList.check('fe80::1', 'ipv6'), true);
+  assert.strictEqual(blockList.check(longZone, 'ipv6'), true);
+  assert.strictEqual(blockList.check('x'.repeat(200), 'ipv6'), false);
+
+  // uv_ip4_addr rejects zone suffixes. The Fast API overflow path
+  // must not strip %zone and treat this as 1.1.1.1.
+  blockList.addAddress('1.1.1.1');
+  assert.strictEqual(blockList.check(`1.1.1.1%${'z'.repeat(200)}`), false);
+
+  // Mixed-notation IPv6 is 45 chars. uv_ip6_addr truncates the
+  // address part to 39 when a zone is present, so this is a miss.
+  const mixed = 'ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255';
+  blockList.addAddress(mixed, 'ipv6');
+  assert.strictEqual(blockList.check(mixed, 'ipv6'), true);
+  assert.strictEqual(blockList.check(`${mixed}%${'z'.repeat(200)}`, 'ipv6'), false);
+}
+
 // check() family parameter is case-insensitive.
 {
   const blockList = new BlockList();