feat(ext/node): add BlockList & SocketAddress classes (#24229)

Closes https://github.com/denoland/deno/issues/24059
This commit is contained in:
Satya Rohith 2024-06-18 16:16:13 +05:30 committed by GitHub
parent 4b83ce8aca
commit 8c4b33db0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 845 additions and 14 deletions

10
Cargo.lock generated
View file

@ -1701,6 +1701,7 @@ dependencies = [
"http 1.1.0", "http 1.1.0",
"idna 0.3.0", "idna 0.3.0",
"indexmap", "indexmap",
"ipnetwork",
"k256", "k256",
"lazy-regex", "lazy-regex",
"libc", "libc",
@ -3571,6 +3572,15 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "ipnetwork"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "is-docker" name = "is-docker"
version = "0.2.0" version = "0.2.0"

View file

@ -41,6 +41,7 @@ home = "0.5.9"
http.workspace = true http.workspace = true
idna = "0.3.0" idna = "0.3.0"
indexmap.workspace = true indexmap.workspace = true
ipnetwork = "0.20.0"
k256 = "0.13.1" k256 = "0.13.1"
lazy-regex.workspace = true lazy-regex.workspace = true
libc.workspace = true libc.workspace = true

View file

@ -230,6 +230,15 @@ deno_core::extension!(deno_node,
deps = [ deno_io, deno_fs ], deps = [ deno_io, deno_fs ],
parameters = [P: NodePermissions], parameters = [P: NodePermissions],
ops = [ 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_ascii,
ops::buffer::op_is_utf8, ops::buffer::op_is_utf8,
ops::crypto::op_node_create_decipheriv, ops::crypto::op_node_create_decipheriv,
@ -489,6 +498,7 @@ deno_core::extension!(deno_node,
"internal_binding/uv.ts", "internal_binding/uv.ts",
"internal/assert.mjs", "internal/assert.mjs",
"internal/async_hooks.ts", "internal/async_hooks.ts",
"internal/blocklist.mjs",
"internal/buffer.mjs", "internal/buffer.mjs",
"internal/child_process.ts", "internal/child_process.ts",
"internal/cli_table.ts", "internal/cli_table.ts",

290
ext/node/ops/blocklist.rs Normal file
View file

@ -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());
}
}

View file

@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
pub mod blocklist;
pub mod buffer; pub mod buffer;
pub mod crypto; pub mod crypto;
pub mod fs; pub mod fs;

View file

@ -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 };

View file

@ -667,9 +667,7 @@ function invalidArgTypeHelper(input: any) {
return ` Received type ${typeof input} (${inspected})`; return ` Received type ${typeof input} (${inspected})`;
} }
export class ERR_OUT_OF_RANGE extends RangeError { export class ERR_OUT_OF_RANGE extends NodeRangeError {
code = "ERR_OUT_OF_RANGE";
constructor( constructor(
str: string, str: string,
range: string, range: string,
@ -694,15 +692,7 @@ export class ERR_OUT_OF_RANGE extends RangeError {
} }
msg += ` It must be ${range}. Received ${received}`; msg += ` It must be ${range}. Received ${received}`;
super(msg); super("ERR_OUT_OF_RANGE", 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;
} }
} }

View file

@ -24,6 +24,8 @@
// deno-lint-ignore-file prefer-primordials // deno-lint-ignore-file prefer-primordials
import { notImplemented } from "ext:deno_node/_utils.ts"; import { notImplemented } from "ext:deno_node/_utils.ts";
import { BlockList, SocketAddress } from "ext:deno_node/internal/blocklist.mjs";
import { EventEmitter } from "node:events"; import { EventEmitter } from "node:events";
import { import {
isIP, isIP,
@ -2472,7 +2474,7 @@ export function createServer(
return new Server(options, connectionListener); return new Server(options, connectionListener);
} }
export { isIP, isIPv4, isIPv6 }; export { BlockList, isIP, isIPv4, isIPv6, SocketAddress };
export default { export default {
_createServerHandle, _createServerHandle,
@ -2480,6 +2482,8 @@ export default {
isIP, isIP,
isIPv4, isIPv4,
isIPv6, isIPv6,
BlockList,
SocketAddress,
connect, connect,
createConnection, createConnection,
createServer, createServer,

View file

@ -19,6 +19,7 @@
], ],
"parallel": [ "parallel": [
"test-assert.js", "test-assert.js",
"test-blocklist.js",
"test-buffer-alloc.js", "test-buffer-alloc.js",
"test-buffer-arraybuffer.js", "test-buffer-arraybuffer.js",
"test-buffer-from.js", "test-buffer-from.js",
@ -162,6 +163,7 @@
"test-assert-strict-exists.js", "test-assert-strict-exists.js",
"test-assert.js", "test-assert.js",
"test-bad-unicode.js", "test-bad-unicode.js",
"test-blocklist.js",
"test-btoa-atob.js", "test-btoa-atob.js",
"test-buffer-alloc.js", "test-buffer-alloc.js",
"test-buffer-arraybuffer.js", "test-buffer-arraybuffer.js",

View file

@ -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-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-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-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-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-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) - [parallel/test-buffer-backing-arraybuffer.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-buffer-backing-arraybuffer.js)

View file

@ -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'));
}

View file

@ -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)]); 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"));
});