summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSatya Rohith <me@satyarohith.com>2024-06-18 16:16:13 +0530
committerGitHub <noreply@github.com>2024-06-18 10:46:13 +0000
commit8c4b33db0d05181a0e5538bddaf063144724c938 (patch)
tree368ced63a6ac484db822212d9d332d92bd3466ed
parent4b83ce8acabaf868d47bf764fce18ce5450fd314 (diff)
feat(ext/node): add BlockList & SocketAddress classes (#24229)
Closes https://github.com/denoland/deno/issues/24059
-rw-r--r--Cargo.lock10
-rw-r--r--ext/node/Cargo.toml1
-rw-r--r--ext/node/lib.rs10
-rw-r--r--ext/node/ops/blocklist.rs290
-rw-r--r--ext/node/ops/mod.rs1
-rw-r--r--ext/node/polyfills/internal/blocklist.mjs227
-rw-r--r--ext/node/polyfills/internal/errors.ts14
-rw-r--r--ext/node/polyfills/net.ts6
-rw-r--r--tests/node_compat/config.jsonc2
-rw-r--r--tests/node_compat/runner/TODO.md1
-rw-r--r--tests/node_compat/test/parallel/test-blocklist.js291
-rw-r--r--tests/unit_node/net_test.ts6
12 files changed, 845 insertions, 14 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9ba6698b3..fd5fa22c3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1701,6 +1701,7 @@ dependencies = [
"http 1.1.0",
"idna 0.3.0",
"indexmap",
+ "ipnetwork",
"k256",
"lazy-regex",
"libc",
@@ -3572,6 +3573,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
+name = "ipnetwork"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "is-docker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index 83ce49060..ecb618e48 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -41,6 +41,7 @@ home = "0.5.9"
http.workspace = true
idna = "0.3.0"
indexmap.workspace = true
+ipnetwork = "0.20.0"
k256 = "0.13.1"
lazy-regex.workspace = true
libc.workspace = true
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index d05434b88..7654607d7 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -230,6 +230,15 @@ deno_core::extension!(deno_node,
deps = [ deno_io, deno_fs ],
parameters = [P: NodePermissions],
ops = [
+ ops::blocklist::op_socket_address_parse,
+ ops::blocklist::op_socket_address_get_serialization,
+
+ ops::blocklist::op_blocklist_new,
+ ops::blocklist::op_blocklist_add_address,
+ ops::blocklist::op_blocklist_add_range,
+ ops::blocklist::op_blocklist_add_subnet,
+ ops::blocklist::op_blocklist_check,
+
ops::buffer::op_is_ascii,
ops::buffer::op_is_utf8,
ops::crypto::op_node_create_decipheriv,
@@ -489,6 +498,7 @@ deno_core::extension!(deno_node,
"internal_binding/uv.ts",
"internal/assert.mjs",
"internal/async_hooks.ts",
+ "internal/blocklist.mjs",
"internal/buffer.mjs",
"internal/child_process.ts",
"internal/cli_table.ts",
diff --git a/ext/node/ops/blocklist.rs b/ext/node/ops/blocklist.rs
new file mode 100644
index 000000000..ce32c14ba
--- /dev/null
+++ b/ext/node/ops/blocklist.rs
@@ -0,0 +1,290 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use std::cell::RefCell;
+use std::collections::HashSet;
+use std::net::IpAddr;
+use std::net::Ipv4Addr;
+use std::net::Ipv6Addr;
+use std::net::SocketAddr;
+
+use deno_core::anyhow::anyhow;
+use deno_core::anyhow::bail;
+use deno_core::error::AnyError;
+use deno_core::op2;
+use deno_core::OpState;
+
+use ipnetwork::IpNetwork;
+use ipnetwork::Ipv4Network;
+use ipnetwork::Ipv6Network;
+use serde::Serialize;
+
+pub struct BlockListResource {
+ blocklist: RefCell<BlockList>,
+}
+
+#[derive(Serialize)]
+struct SocketAddressSerialization(String, String);
+
+#[op2(fast)]
+pub fn op_socket_address_parse(
+ state: &mut OpState,
+ #[string] addr: &str,
+ #[smi] port: u16,
+ #[string] family: &str,
+) -> Result<bool, AnyError> {
+ let ip = addr.parse::<IpAddr>()?;
+ let parsed: SocketAddr = SocketAddr::new(ip, port);
+ let parsed_ip_str = parsed.ip().to_string();
+ let family_correct = family.eq_ignore_ascii_case("ipv4") && parsed.is_ipv4()
+ || family.eq_ignore_ascii_case("ipv6") && parsed.is_ipv6();
+
+ if family_correct {
+ let family_is_lowercase = family[..3].chars().all(char::is_lowercase);
+ if family_is_lowercase && parsed_ip_str == addr {
+ Ok(true)
+ } else {
+ state.put::<SocketAddressSerialization>(SocketAddressSerialization(
+ parsed_ip_str,
+ family.to_lowercase(),
+ ));
+ Ok(false)
+ }
+ } else {
+ Err(anyhow!("Invalid address"))
+ }
+}
+
+#[op2]
+#[serde]
+pub fn op_socket_address_get_serialization(
+ state: &mut OpState,
+) -> Result<SocketAddressSerialization, AnyError> {
+ Ok(state.take::<SocketAddressSerialization>())
+}
+
+#[op2]
+#[cppgc]
+pub fn op_blocklist_new() -> BlockListResource {
+ let blocklist = BlockList::new();
+ BlockListResource {
+ blocklist: RefCell::new(blocklist),
+ }
+}
+
+#[op2(fast)]
+pub fn op_blocklist_add_address(
+ #[cppgc] wrap: &BlockListResource,
+ #[string] addr: &str,
+) -> Result<(), AnyError> {
+ wrap.blocklist.borrow_mut().add_address(addr)
+}
+
+#[op2(fast)]
+pub fn op_blocklist_add_range(
+ #[cppgc] wrap: &BlockListResource,
+ #[string] start: &str,
+ #[string] end: &str,
+) -> Result<bool, AnyError> {
+ wrap.blocklist.borrow_mut().add_range(start, end)
+}
+
+#[op2(fast)]
+pub fn op_blocklist_add_subnet(
+ #[cppgc] wrap: &BlockListResource,
+ #[string] addr: &str,
+ #[smi] prefix: u8,
+) -> Result<(), AnyError> {
+ wrap.blocklist.borrow_mut().add_subnet(addr, prefix)
+}
+
+#[op2(fast)]
+pub fn op_blocklist_check(
+ #[cppgc] wrap: &BlockListResource,
+ #[string] addr: &str,
+ #[string] r#type: &str,
+) -> Result<bool, AnyError> {
+ wrap.blocklist.borrow().check(addr, r#type)
+}
+
+struct BlockList {
+ rules: HashSet<IpNetwork>,
+}
+
+impl BlockList {
+ pub fn new() -> Self {
+ BlockList {
+ rules: HashSet::new(),
+ }
+ }
+
+ fn map_addr_add_network(&mut self, addr: IpAddr, prefix: Option<u8>) {
+ match addr {
+ IpAddr::V4(addr) => {
+ self.rules.insert(IpNetwork::V4(
+ Ipv4Network::new(addr, prefix.unwrap_or(32)).unwrap(),
+ ));
+ self.rules.insert(IpNetwork::V6(
+ Ipv6Network::new(addr.to_ipv6_mapped(), prefix.unwrap_or(128))
+ .unwrap(),
+ ));
+ }
+ IpAddr::V6(addr) => {
+ if let Some(ipv4_mapped) = addr.to_ipv4_mapped() {
+ self.rules.insert(IpNetwork::V4(
+ Ipv4Network::new(ipv4_mapped, prefix.unwrap_or(32)).unwrap(),
+ ));
+ }
+ self.rules.insert(IpNetwork::V6(
+ Ipv6Network::new(addr, prefix.unwrap_or(128)).unwrap(),
+ ));
+ }
+ };
+ }
+
+ pub fn add_address(&mut self, address: &str) -> Result<(), AnyError> {
+ let ip: IpAddr = address.parse()?;
+ self.map_addr_add_network(ip, None);
+ Ok(())
+ }
+
+ pub fn add_range(
+ &mut self,
+ start: &str,
+ end: &str,
+ ) -> Result<bool, AnyError> {
+ let start_ip: IpAddr = start.parse()?;
+ let end_ip: IpAddr = end.parse()?;
+
+ match (start_ip, end_ip) {
+ (IpAddr::V4(start), IpAddr::V4(end)) => {
+ let start_u32: u32 = start.into();
+ let end_u32: u32 = end.into();
+ if end_u32 < start_u32 {
+ // Indicates invalid range.
+ return Ok(false);
+ }
+ for ip in start_u32..=end_u32 {
+ let addr: Ipv4Addr = ip.into();
+ self.map_addr_add_network(IpAddr::V4(addr), None);
+ }
+ }
+ (IpAddr::V6(start), IpAddr::V6(end)) => {
+ let start_u128: u128 = start.into();
+ let end_u128: u128 = end.into();
+ if end_u128 < start_u128 {
+ // Indicates invalid range.
+ return Ok(false);
+ }
+ for ip in start_u128..=end_u128 {
+ let addr: Ipv6Addr = ip.into();
+ self.map_addr_add_network(IpAddr::V6(addr), None);
+ }
+ }
+ _ => bail!("IP version mismatch between start and end addresses"),
+ }
+ Ok(true)
+ }
+
+ pub fn add_subnet(&mut self, addr: &str, prefix: u8) -> Result<(), AnyError> {
+ let ip: IpAddr = addr.parse()?;
+ self.map_addr_add_network(ip, Some(prefix));
+ Ok(())
+ }
+
+ pub fn check(&self, addr: &str, r#type: &str) -> Result<bool, AnyError> {
+ let addr: IpAddr = addr.parse()?;
+ let family = r#type.to_lowercase();
+ if family == "ipv4" && addr.is_ipv4() || family == "ipv6" && addr.is_ipv6()
+ {
+ Ok(self.rules.iter().any(|net| net.contains(addr)))
+ } else {
+ Err(anyhow!("Invalid address"))
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_add_address() {
+ // Single IPv4 address
+ let mut block_list = BlockList::new();
+ block_list.add_address("192.168.0.1").unwrap();
+ assert!(block_list.check("192.168.0.1", "ipv4").unwrap());
+ assert!(block_list.check("::ffff:c0a8:1", "ipv6").unwrap());
+
+ // Single IPv6 address
+ let mut block_list = BlockList::new();
+ block_list.add_address("2001:db8::1").unwrap();
+ assert!(block_list.check("2001:db8::1", "ipv6").unwrap());
+ assert!(!block_list.check("192.168.0.1", "ipv4").unwrap());
+ }
+
+ #[test]
+ fn test_add_range() {
+ // IPv4 range
+ let mut block_list = BlockList::new();
+ block_list.add_range("192.168.0.1", "192.168.0.3").unwrap();
+ assert!(block_list.check("192.168.0.1", "ipv4").unwrap());
+ assert!(block_list.check("192.168.0.2", "ipv4").unwrap());
+ assert!(block_list.check("192.168.0.3", "ipv4").unwrap());
+ assert!(block_list.check("::ffff:c0a8:1", "ipv6").unwrap());
+
+ // IPv6 range
+ let mut block_list = BlockList::new();
+ block_list.add_range("2001:db8::1", "2001:db8::3").unwrap();
+ assert!(block_list.check("2001:db8::1", "ipv6").unwrap());
+ assert!(block_list.check("2001:db8::2", "ipv6").unwrap());
+ assert!(block_list.check("2001:db8::3", "ipv6").unwrap());
+ assert!(!block_list.check("192.168.0.1", "ipv4").unwrap());
+ }
+
+ #[test]
+ fn test_add_subnet() {
+ // IPv4 subnet
+ let mut block_list = BlockList::new();
+ block_list.add_subnet("192.168.0.0", 24).unwrap();
+ assert!(block_list.check("192.168.0.1", "ipv4").unwrap());
+ assert!(block_list.check("192.168.0.255", "ipv4").unwrap());
+ assert!(block_list.check("::ffff:c0a8:0", "ipv6").unwrap());
+
+ // IPv6 subnet
+ let mut block_list = BlockList::new();
+ block_list.add_subnet("2001:db8::", 64).unwrap();
+ assert!(block_list.check("2001:db8::1", "ipv6").unwrap());
+ assert!(block_list.check("2001:db8::ffff", "ipv6").unwrap());
+ assert!(!block_list.check("192.168.0.1", "ipv4").unwrap());
+ }
+
+ #[test]
+ fn test_check() {
+ // Check IPv4 presence
+ let mut block_list = BlockList::new();
+ block_list.add_address("192.168.0.1").unwrap();
+ assert!(block_list.check("192.168.0.1", "ipv4").unwrap());
+
+ // Check IPv6 presence
+ let mut block_list = BlockList::new();
+ block_list.add_address("2001:db8::1").unwrap();
+ assert!(block_list.check("2001:db8::1", "ipv6").unwrap());
+
+ // Check IPv4 not present
+ let block_list = BlockList::new();
+ assert!(!block_list.check("192.168.0.1", "ipv4").unwrap());
+
+ // Check IPv6 not present
+ let block_list = BlockList::new();
+ assert!(!block_list.check("2001:db8::1", "ipv6").unwrap());
+
+ // Check invalid IP version
+ let block_list = BlockList::new();
+ assert!(block_list.check("192.168.0.1", "ipv6").is_err());
+
+ // Check invalid type
+ let mut block_list = BlockList::new();
+ block_list.add_address("192.168.0.1").unwrap();
+ assert!(block_list.check("192.168.0.1", "invalid_type").is_err());
+ }
+}
diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs
index ae703e3f3..b51e23ac8 100644
--- a/ext/node/ops/mod.rs
+++ b/ext/node/ops/mod.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+pub mod blocklist;
pub mod buffer;
pub mod crypto;
pub mod fs;
diff --git a/ext/node/polyfills/internal/blocklist.mjs b/ext/node/polyfills/internal/blocklist.mjs
new file mode 100644
index 000000000..a9aba03b6
--- /dev/null
+++ b/ext/node/polyfills/internal/blocklist.mjs
@@ -0,0 +1,227 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+
+import { primordials } from "ext:core/mod.js";
+import {
+ op_blocklist_add_address,
+ op_blocklist_add_range,
+ op_blocklist_add_subnet,
+ op_blocklist_check,
+ op_blocklist_new,
+ op_socket_address_get_serialization,
+ op_socket_address_parse,
+} from "ext:core/ops";
+
+import {
+ validateInt32,
+ validateObject,
+ validatePort,
+ validateString,
+ validateUint32,
+} from "ext:deno_node/internal/validators.mjs";
+import { ERR_INVALID_ARG_VALUE } from "ext:deno_node/internal/errors.ts";
+import { customInspectSymbol } from "ext:deno_node/internal/util.mjs";
+import { inspect } from "ext:deno_node/internal/util/inspect.mjs";
+
+const { Symbol } = primordials;
+
+const internalBlockList = Symbol("blocklist");
+
+class BlockList {
+ constructor() {
+ this[internalBlockList] = op_blocklist_new();
+ }
+
+ [customInspectSymbol](depth, options) {
+ if (depth < 0) {
+ return this;
+ }
+
+ const opts = {
+ ...options,
+ depth: options.depth == null ? null : options.depth - 1,
+ };
+
+ return `BlockList ${
+ inspect({
+ rules: [], // TODO(satyarohith): provide the actual rules
+ }, opts)
+ }`;
+ }
+
+ addAddress(address, family = "ipv4") {
+ if (!SocketAddress.isSocketAddress(address)) {
+ validateString(address, "address");
+ validateString(family, "family");
+ new SocketAddress({
+ address,
+ family,
+ });
+ } else {
+ address = address.address;
+ }
+ op_blocklist_add_address(this[internalBlockList], address);
+ }
+
+ addRange(start, end, family = "ipv4") {
+ if (!SocketAddress.isSocketAddress(start)) {
+ validateString(start, "start");
+ validateString(family, "family");
+ new SocketAddress({
+ address: start,
+ family,
+ });
+ } else {
+ start = start.address;
+ }
+ if (!SocketAddress.isSocketAddress(end)) {
+ validateString(end, "end");
+ validateString(family, "family");
+ new SocketAddress({
+ address: end,
+ family,
+ });
+ } else {
+ end = end.address;
+ }
+ const ret = op_blocklist_add_range(this[internalBlockList], start, end);
+ if (ret === false) {
+ throw new ERR_INVALID_ARG_VALUE("start", start, "must come before end");
+ }
+ }
+
+ addSubnet(network, prefix, family = "ipv4") {
+ if (!SocketAddress.isSocketAddress(network)) {
+ validateString(network, "network");
+ validateString(family, "family");
+ new SocketAddress({
+ address: network,
+ family,
+ });
+ } else {
+ network = network.address;
+ family = network.family;
+ }
+ switch (family) {
+ case "ipv4":
+ validateInt32(prefix, "prefix", 0, 32);
+ break;
+ case "ipv6":
+ validateInt32(prefix, "prefix", 0, 128);
+ break;
+ }
+ op_blocklist_add_subnet(this[internalBlockList], network, prefix);
+ }
+
+ check(address, family = "ipv4") {
+ if (!SocketAddress.isSocketAddress(address)) {
+ validateString(address, "address");
+ validateString(family, "family");
+ try {
+ new SocketAddress({
+ address,
+ family,
+ });
+ } catch {
+ // Ignore the error. If it's not a valid address, return false.
+ return false;
+ }
+ } else {
+ family = address.family;
+ address = address.address;
+ }
+ try {
+ return op_blocklist_check(this[internalBlockList], address, family);
+ } catch (_) {
+ // Node API expects false as return value if the address is invalid.
+ // Example: `blocklist.check("1.1.1.1", "ipv6")` should return false.
+ return false;
+ }
+ }
+
+ get rules() {
+ // TODO(satyarohith): return the actual rules
+ return [];
+ }
+}
+
+const kDetail = Symbol("kDetail");
+
+class SocketAddress {
+ static isSocketAddress(value) {
+ return value?.[kDetail] !== undefined;
+ }
+
+ constructor(options = kEmptyObject) {
+ validateObject(options, "options");
+ let { family = "ipv4" } = options;
+ const {
+ address = (family === "ipv4" ? "127.0.0.1" : "::"),
+ port = 0,
+ flowlabel = 0,
+ } = options;
+
+ if (typeof family?.toLowerCase === "function") {
+ // deno-lint-ignore prefer-primordials
+ family = family.toLowerCase();
+ }
+ switch (family) {
+ case "ipv4":
+ break;
+ case "ipv6":
+ break;
+ default:
+ throw new ERR_INVALID_ARG_VALUE("options.family", options.family);
+ }
+
+ validateString(address, "options.address");
+ validatePort(port, "options.port");
+ validateUint32(flowlabel, "options.flowlabel", false);
+
+ this[kDetail] = {
+ address,
+ port,
+ family,
+ flowlabel,
+ };
+ const useInput = op_socket_address_parse(
+ address,
+ port,
+ family,
+ );
+ if (!useInput) {
+ const { 0: address_, 1: family_ } = op_socket_address_get_serialization();
+ this[kDetail].address = address_;
+ this[kDetail].family = family_;
+ }
+ }
+
+ get address() {
+ return this[kDetail].address;
+ }
+
+ get port() {
+ return this[kDetail].port;
+ }
+
+ get family() {
+ return this[kDetail].family;
+ }
+
+ get flowlabel() {
+ // TODO(satyarohith): Implement this in Rust.
+ // The flow label can be changed internally.
+ return this[kDetail].flowlabel;
+ }
+
+ toJSON() {
+ return {
+ address: this.address,
+ port: this.port,
+ family: this.family,
+ flowlabel: this.flowlabel,
+ };
+ }
+}
+
+export { BlockList, SocketAddress };
diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts
index cb4119411..6529e9894 100644
--- a/ext/node/polyfills/internal/errors.ts
+++ b/ext/node/polyfills/internal/errors.ts
@@ -667,9 +667,7 @@ function invalidArgTypeHelper(input: any) {
return ` Received type ${typeof input} (${inspected})`;
}
-export class ERR_OUT_OF_RANGE extends RangeError {
- code = "ERR_OUT_OF_RANGE";
-
+export class ERR_OUT_OF_RANGE extends NodeRangeError {
constructor(
str: string,
range: string,
@@ -694,15 +692,7 @@ export class ERR_OUT_OF_RANGE extends RangeError {
}
msg += ` It must be ${range}. Received ${received}`;
- super(msg);
-
- const { name } = this;
- // Add the error code to the name to include it in the stack trace.
- this.name = `${name} [${this.code}]`;
- // Access the stack to generate the error message including the error code from the name.
- this.stack;
- // Reset the name to the actual name.
- this.name = name;
+ super("ERR_OUT_OF_RANGE", msg);
}
}
diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts
index 66b7735d9..6625ce7b5 100644
--- a/ext/node/polyfills/net.ts
+++ b/ext/node/polyfills/net.ts
@@ -24,6 +24,8 @@
// deno-lint-ignore-file prefer-primordials
import { notImplemented } from "ext:deno_node/_utils.ts";
+import { BlockList, SocketAddress } from "ext:deno_node/internal/blocklist.mjs";
+
import { EventEmitter } from "node:events";
import {
isIP,
@@ -2472,7 +2474,7 @@ export function createServer(
return new Server(options, connectionListener);
}
-export { isIP, isIPv4, isIPv6 };
+export { BlockList, isIP, isIPv4, isIPv6, SocketAddress };
export default {
_createServerHandle,
@@ -2480,6 +2482,8 @@ export default {
isIP,
isIPv4,
isIPv6,
+ BlockList,
+ SocketAddress,
connect,
createConnection,
createServer,
diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc
index 27cf6afb8..6a61c4e63 100644
--- a/tests/node_compat/config.jsonc
+++ b/tests/node_compat/config.jsonc
@@ -19,6 +19,7 @@
],
"parallel": [
"test-assert.js",
+ "test-blocklist.js",
"test-buffer-alloc.js",
"test-buffer-arraybuffer.js",
"test-buffer-from.js",
@@ -162,6 +163,7 @@
"test-assert-strict-exists.js",
"test-assert.js",
"test-bad-unicode.js",
+ "test-blocklist.js",
"test-btoa-atob.js",
"test-buffer-alloc.js",
"test-buffer-arraybuffer.js",
diff --git a/tests/node_compat/runner/TODO.md b/tests/node_compat/runner/TODO.md
index e24c82b75..4f397cc21 100644
--- a/tests/node_compat/runner/TODO.md
+++ b/tests/node_compat/runner/TODO.md
@@ -222,7 +222,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
- [parallel/test-blob-file-backed.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blob-file-backed.js)
- [parallel/test-blob.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blob.js)
- [parallel/test-blocklist-clone.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blocklist-clone.js)
-- [parallel/test-blocklist.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-blocklist.js)
- [parallel/test-bootstrap-modules.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-bootstrap-modules.js)
- [parallel/test-broadcastchannel-custom-inspect.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-broadcastchannel-custom-inspect.js)
- [parallel/test-buffer-backing-arraybuffer.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-buffer-backing-arraybuffer.js)
diff --git a/tests/node_compat/test/parallel/test-blocklist.js b/tests/node_compat/test/parallel/test-blocklist.js
new file mode 100644
index 000000000..fd63f51ff
--- /dev/null
+++ b/tests/node_compat/test/parallel/test-blocklist.js
@@ -0,0 +1,291 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+require('../common');
+
+const {
+ BlockList,
+ SocketAddress,
+} = require('net');
+const assert = require('assert');
+const util = require('util');
+
+{
+ const blockList = new BlockList();
+
+ [1, [], {}, null, 1n, undefined, null].forEach((i) => {
+ assert.throws(() => blockList.addAddress(i), {
+ code: 'ERR_INVALID_ARG_TYPE'
+ });
+ });
+
+ [1, [], {}, null, 1n, null].forEach((i) => {
+ assert.throws(() => blockList.addAddress('1.1.1.1', i), {
+ code: 'ERR_INVALID_ARG_TYPE'
+ });
+ });
+
+ assert.throws(() => blockList.addAddress('1.1.1.1', 'foo'), {
+ code: 'ERR_INVALID_ARG_VALUE'
+ });
+
+ [1, [], {}, null, 1n, undefined, null].forEach((i) => {
+ assert.throws(() => blockList.addRange(i), {
+ code: 'ERR_INVALID_ARG_TYPE'
+ });
+ assert.throws(() => blockList.addRange('1.1.1.1', i), {
+ code: 'ERR_INVALID_ARG_TYPE'
+ });
+ });
+
+ [1, [], {}, null, 1n, null].forEach((i) => {
+ assert.throws(() => blockList.addRange('1.1.1.1', '1.1.1.2', i), {
+ code: 'ERR_INVALID_ARG_TYPE'
+ });
+ });
+
+ assert.throws(() => blockList.addRange('1.1.1.1', '1.1.1.2', 'foo'), {
+ code: 'ERR_INVALID_ARG_VALUE'
+ });
+}
+
+{
+ const blockList = new BlockList();
+ blockList.addAddress('1.1.1.1');
+ blockList.addAddress('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17', 'ipv6');
+ blockList.addAddress('::ffff:1.1.1.2', 'ipv6');
+
+ assert(blockList.check('1.1.1.1'));
+ assert(!blockList.check('1.1.1.1', 'ipv6'));
+ assert(!blockList.check('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17'));
+ assert(blockList.check('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17', 'ipv6'));
+
+ assert(blockList.check('::ffff:1.1.1.1', 'ipv6'));
+ assert(blockList.check('::ffff:1.1.1.1', 'IPV6'));
+
+ assert(blockList.check('1.1.1.2'));
+
+ assert(!blockList.check('1.2.3.4'));
+ assert(!blockList.check('::1', 'ipv6'));
+}
+
+{
+ const blockList = new BlockList();
+ const sa1 = new SocketAddress({ address: '1.1.1.1' });
+ const sa2 = new SocketAddress({
+ address: '8592:757c:efae:4e45:fb5d:d62a:0d00:8e17',
+ family: 'ipv6'
+ });
+ const sa3 = new SocketAddress({ address: '1.1.1.2' });
+
+ blockList.addAddress(sa1);
+ blockList.addAddress(sa2);
+ blockList.addAddress('::ffff:1.1.1.2', 'ipv6');
+
+ assert(blockList.check('1.1.1.1'));
+ assert(blockList.check(sa1));
+ assert(!blockList.check('1.1.1.1', 'ipv6'));
+ assert(!blockList.check('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17'));
+ assert(blockList.check('8592:757c:efae:4e45:fb5d:d62a:0d00:8e17', 'ipv6'));
+ assert(blockList.check(sa2));
+
+ assert(blockList.check('::ffff:1.1.1.1', 'ipv6'));
+ assert(blockList.check('::ffff:1.1.1.1', 'IPV6'));
+
+ assert(blockList.check('1.1.1.2'));
+ assert(blockList.check(sa3));
+
+ assert(!blockList.check('1.2.3.4'));
+ assert(!blockList.check('::1', 'ipv6'));
+}
+
+{
+ const blockList = new BlockList();
+ blockList.addRange('1.1.1.1', '1.1.1.10');
+ blockList.addRange('::1', '::f', 'ipv6');
+
+ assert(!blockList.check('1.1.1.0'));
+ for (let n = 1; n <= 10; n++)
+ assert(blockList.check(`1.1.1.${n}`));
+ assert(!blockList.check('1.1.1.11'));
+
+ assert(!blockList.check('::0', 'ipv6'));
+ for (let n = 0x1; n <= 0xf; n++) {
+ assert(blockList.check(`::${n.toString(16)}`, 'ipv6'),
+ `::${n.toString(16)} check failed`);
+ }
+ assert(!blockList.check('::10', 'ipv6'));
+}
+
+{
+ const blockList = new BlockList();
+ const sa1 = new SocketAddress({ address: '1.1.1.1' });
+ const sa2 = new SocketAddress({ address: '1.1.1.10' });
+ const sa3 = new SocketAddress({ address: '::1', family: 'ipv6' });
+ const sa4 = new SocketAddress({ address: '::f', family: 'ipv6' });
+
+ blockList.addRange(sa1, sa2);
+ blockList.addRange(sa3, sa4);
+
+ assert(!blockList.check('1.1.1.0'));
+ for (let n = 1; n <= 10; n++)
+ assert(blockList.check(`1.1.1.${n}`));
+ assert(!blockList.check('1.1.1.11'));
+
+ assert(!blockList.check('::0', 'ipv6'));
+ for (let n = 0x1; n <= 0xf; n++) {
+ assert(blockList.check(`::${n.toString(16)}`, 'ipv6'),
+ `::${n.toString(16)} check failed`);
+ }
+ assert(!blockList.check('::10', 'ipv6'));
+}
+
+{
+ const blockList = new BlockList();
+ blockList.addSubnet('1.1.1.0', 16);
+ blockList.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6');
+
+ assert(blockList.check('1.1.0.1'));
+ assert(blockList.check('1.1.1.1'));
+ assert(!blockList.check('1.2.0.1'));
+ assert(blockList.check('::ffff:1.1.0.1', 'ipv6'));
+
+ assert(blockList.check('8592:757c:efae:4e45:f::', 'ipv6'));
+ assert(blockList.check('8592:757c:efae:4e45::f', 'ipv6'));
+ assert(!blockList.check('8592:757c:efae:4f45::f', 'ipv6'));
+}
+
+{
+ const blockList = new BlockList();
+ const sa1 = new SocketAddress({ address: '1.1.1.0' });
+ const sa2 = new SocketAddress({ address: '1.1.1.1' });
+ blockList.addSubnet(sa1, 16);
+ blockList.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6');
+
+ assert(blockList.check('1.1.0.1'));
+ assert(blockList.check(sa2));
+ assert(!blockList.check('1.2.0.1'));
+ assert(blockList.check('::ffff:1.1.0.1', 'ipv6'));
+
+ assert(blockList.check('8592:757c:efae:4e45:f::', 'ipv6'));
+ assert(blockList.check('8592:757c:efae:4e45::f', 'ipv6'));
+ assert(!blockList.check('8592:757c:efae:4f45::f', 'ipv6'));
+}
+
+{
+ const blockList = new BlockList();
+ blockList.addAddress('1.1.1.1');
+ blockList.addRange('10.0.0.1', '10.0.0.10');
+ blockList.addSubnet('8592:757c:efae:4e45::', 64, 'IpV6'); // Case insensitive
+
+ // const rulesCheck = [
+ // 'Subnet: IPv6 8592:757c:efae:4e45::/64',
+ // 'Range: IPv4 10.0.0.1-10.0.0.10',
+ // 'Address: IPv4 1.1.1.1',
+ // ];
+ // assert.deepStrictEqual(blockList.rules, rulesCheck);
+
+ assert(blockList.check('1.1.1.1'));
+ assert(blockList.check('10.0.0.5'));
+ assert(blockList.check('::ffff:10.0.0.5', 'ipv6'));
+ assert(blockList.check('8592:757c:efae:4e45::f', 'ipv6'));
+
+ assert(!blockList.check('123.123.123.123'));
+ assert(!blockList.check('8592:757c:efaf:4e45:fb5d:d62a:0d00:8e17', 'ipv6'));
+ assert(!blockList.check('::ffff:123.123.123.123', 'ipv6'));
+}
+
+{
+ // This test validates boundaries of non-aligned CIDR bit prefixes
+ const blockList = new BlockList();
+ blockList.addSubnet('10.0.0.0', 27);
+ blockList.addSubnet('8592:757c:efaf::', 51, 'ipv6');
+
+ for (let n = 0; n <= 31; n++)
+ assert(blockList.check(`10.0.0.${n}`));
+ assert(!blockList.check('10.0.0.32'));
+
+ assert(blockList.check('8592:757c:efaf:0:0:0:0:0', 'ipv6'));
+ assert(blockList.check('8592:757c:efaf:1fff:ffff:ffff:ffff:ffff', 'ipv6'));
+ assert(!blockList.check('8592:757c:efaf:2fff:ffff:ffff:ffff:ffff', 'ipv6'));
+}
+
+{
+ // Regression test for https://github.com/nodejs/node/issues/39074
+ const blockList = new BlockList();
+
+ blockList.addRange('10.0.0.2', '10.0.0.10');
+
+ // IPv4 checks against IPv4 range.
+ assert(blockList.check('10.0.0.2'));
+ assert(blockList.check('10.0.0.10'));
+ assert(!blockList.check('192.168.0.3'));
+ assert(!blockList.check('2.2.2.2'));
+ assert(!blockList.check('255.255.255.255'));
+
+ // IPv6 checks against IPv4 range.
+ assert(blockList.check('::ffff:0a00:0002', 'ipv6'));
+ assert(blockList.check('::ffff:0a00:000a', 'ipv6'));
+ assert(!blockList.check('::ffff:c0a8:0003', 'ipv6'));
+ assert(!blockList.check('::ffff:0202:0202', 'ipv6'));
+ assert(!blockList.check('::ffff:ffff:ffff', 'ipv6'));
+}
+
+{
+ const blockList = new BlockList();
+ assert.throws(() => blockList.addRange('1.1.1.2', '1.1.1.1'), /ERR_INVALID_ARG_VALUE/);
+}
+
+{
+ const blockList = new BlockList();
+ assert.throws(() => blockList.addSubnet(1), /ERR_INVALID_ARG_TYPE/);
+ assert.throws(() => blockList.addSubnet('1.1.1.1', ''),
+ /ERR_INVALID_ARG_TYPE/);
+ assert.throws(() => blockList.addSubnet('1.1.1.1', NaN), /ERR_OUT_OF_RANGE/);
+ assert.throws(() => blockList.addSubnet('', 1, 1), /ERR_INVALID_ARG_TYPE/);
+ assert.throws(() => blockList.addSubnet('', 1, ''), /ERR_INVALID_ARG_VALUE/);
+
+ assert.throws(() => blockList.addSubnet('1.1.1.1', -1, 'ipv4'),
+ /ERR_OUT_OF_RANGE/);
+ assert.throws(() => blockList.addSubnet('1.1.1.1', 33, 'ipv4'),
+ /ERR_OUT_OF_RANGE/);
+
+ assert.throws(() => blockList.addSubnet('::', -1, 'ipv6'),
+ /ERR_OUT_OF_RANGE/);
+ assert.throws(() => blockList.addSubnet('::', 129, 'ipv6'),
+ /ERR_OUT_OF_RANGE/);
+}
+
+{
+ const blockList = new BlockList();
+ assert.throws(() => blockList.check(1), /ERR_INVALID_ARG_TYPE/);
+ assert.throws(() => blockList.check('', 1), /ERR_INVALID_ARG_TYPE/);
+}
+
+{
+ const blockList = new BlockList();
+ const ret = util.inspect(blockList, { depth: -1 });
+ assert.strictEqual(ret, '[BlockList]');
+}
+
+{
+ const blockList = new BlockList();
+ const ret = util.inspect(blockList, { depth: null });
+ assert(ret.includes('rules: []'));
+}
+
+{
+ // Test for https://github.com/nodejs/node/issues/43360
+ const blocklist = new BlockList();
+ blocklist.addSubnet('1.1.1.1', 32, 'ipv4');
+
+ assert(blocklist.check('1.1.1.1'));
+ assert(!blocklist.check('1.1.1.2'));
+ assert(!blocklist.check('2.3.4.5'));
+}
diff --git a/tests/unit_node/net_test.ts b/tests/unit_node/net_test.ts
index e08b24c02..89a9fb6ba 100644
--- a/tests/unit_node/net_test.ts
+++ b/tests/unit_node/net_test.ts
@@ -200,3 +200,9 @@ Deno.test("[node/net] multiple Sockets should get correct server data", async ()
assertEquals(sockets[i].events, [`${i}`.repeat(3), `${i}`.repeat(3)]);
}
});
+
+Deno.test("[node/net] BlockList doesn't leak resources", () => {
+ const blockList = new net.BlockList();
+ blockList.addAddress("1.1.1.1");
+ assert(blockList.check("1.1.1.1"));
+});