mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
Signed-off-by: Antoine du Hamel <duhamelantoine1995@gmail.com> PR-URL: https://github.com/nodejs/node/pull/67002 Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1020 lines
31 KiB
JavaScript
1020 lines
31 KiB
JavaScript
'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 = [
|
|
'Address: IPv4 1.1.1.1',
|
|
'Subnet: IPv6 8592:757c:efae:4e45::/64',
|
|
'Range: IPv4 10.0.0.1-10.0.0.10',
|
|
];
|
|
assert.deepStrictEqual(blockList.rules.sort(), rulesCheck.sort());
|
|
|
|
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'));
|
|
}
|
|
|
|
{
|
|
assert(BlockList.isBlockList(new BlockList()));
|
|
assert(!BlockList.isBlockList({}));
|
|
}
|
|
|
|
{
|
|
// Test that adding the same address twice does not create duplicate rules.
|
|
// Previously, the second add would orphan the first rule in the internal
|
|
// list while overwriting its index entry, making it unreachable for removal
|
|
// but still evaluated during checks.
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('1.1.1.1');
|
|
blockList.addAddress('1.1.1.1');
|
|
|
|
// Should have exactly one rule, not two.
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
assert(blockList.check('1.1.1.1'));
|
|
}
|
|
|
|
{
|
|
// Test clear() removes all rules.
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('1.1.1.1');
|
|
blockList.addRange('10.0.0.1', '10.0.0.10');
|
|
blockList.addSubnet('192.168.0.0', 16);
|
|
|
|
assert.strictEqual(blockList.rules.length, 3);
|
|
assert(blockList.check('1.1.1.1'));
|
|
assert(blockList.check('10.0.0.5'));
|
|
assert(blockList.check('192.168.1.1'));
|
|
|
|
blockList.clear();
|
|
|
|
assert.strictEqual(blockList.rules.length, 0);
|
|
assert(!blockList.check('1.1.1.1'));
|
|
assert(!blockList.check('10.0.0.5'));
|
|
assert(!blockList.check('192.168.1.1'));
|
|
|
|
// Can add new rules after clearing.
|
|
blockList.addAddress('2.2.2.2');
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
assert(blockList.check('2.2.2.2'));
|
|
assert(!blockList.check('1.1.1.1'));
|
|
}
|
|
|
|
{
|
|
// addAddresses() validation: non-array argument throws.
|
|
const blockList0 = new BlockList();
|
|
assert.throws(() => blockList0.addAddresses('not-an-array'), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => blockList0.addAddresses(123), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
}
|
|
|
|
{
|
|
// addAddresses() and addCIDRs() must throw the same errors for non-array
|
|
// input regardless of how the checks are implemented internally.
|
|
const blockList = new BlockList();
|
|
for (const [value, received] of [
|
|
['x', "type string ('x')"],
|
|
[123, 'type number (123)'],
|
|
[{}, 'an instance of Object'],
|
|
[null, 'null'],
|
|
[undefined, 'undefined'],
|
|
[1n, 'type bigint (1n)'],
|
|
[true, 'type boolean (true)'],
|
|
]) {
|
|
assert.throws(() => blockList.addAddresses(value), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: 'The "addresses" argument must be an instance of Array. ' +
|
|
`Received ${received}`,
|
|
});
|
|
assert.throws(() => blockList.addCIDRs(value), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: 'The "cidrs" argument must be an instance of Array. ' +
|
|
`Received ${received}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
{
|
|
// Test addAddresses() batch insert.
|
|
const blockList = new BlockList();
|
|
blockList.addAddresses(['1.1.1.1', '2.2.2.2', '3.3.3.3']);
|
|
|
|
assert(blockList.check('1.1.1.1'));
|
|
assert(blockList.check('2.2.2.2'));
|
|
assert(blockList.check('3.3.3.3'));
|
|
assert(!blockList.check('4.4.4.4'));
|
|
assert.strictEqual(blockList.rules.length, 3);
|
|
|
|
// Cross-family works with batch insert.
|
|
assert(blockList.check('::ffff:1.1.1.1', 'ipv6'));
|
|
|
|
// Batch with SocketAddress objects.
|
|
const blockList2 = new BlockList();
|
|
const sa1 = new SocketAddress({ address: '10.0.0.1' });
|
|
const sa2 = new SocketAddress({ address: '10.0.0.2' });
|
|
blockList2.addAddresses([sa1, sa2]);
|
|
assert(blockList2.check('10.0.0.1'));
|
|
assert(blockList2.check('10.0.0.2'));
|
|
assert(!blockList2.check('10.0.0.3'));
|
|
|
|
// IPv6 batch.
|
|
const blockList3 = new BlockList();
|
|
blockList3.addAddresses(['::1', '::2'], 'ipv6');
|
|
assert(blockList3.check('::1', 'ipv6'));
|
|
assert(blockList3.check('::2', 'ipv6'));
|
|
assert(!blockList3.check('::3', 'ipv6'));
|
|
}
|
|
|
|
// Test exporting and importing the rule list to/from JSON
|
|
{
|
|
const ruleList = [
|
|
'Address: IPv4 10.0.0.5',
|
|
'Address: IPv6 ::',
|
|
'Subnet: IPv4 192.168.1.0/24',
|
|
'Subnet: IPv6 8592:757c:efae:4e45::/64',
|
|
];
|
|
|
|
const test2 = new BlockList();
|
|
const test3 = new BlockList();
|
|
const test4 = new BlockList();
|
|
const test5 = new BlockList();
|
|
|
|
const bl = new BlockList();
|
|
bl.addAddress('10.0.0.5');
|
|
bl.addAddress('::', 'ipv6');
|
|
bl.addSubnet('192.168.1.0', 24);
|
|
bl.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6');
|
|
|
|
// Test invalid inputs (input to fromJSON must be an array of
|
|
// string rules or a serialized json string of an array of
|
|
// string rules.
|
|
[
|
|
1, null, Symbol(), [1, 2, 3], '123', [Symbol()], new Map(),
|
|
].forEach((i) => {
|
|
assert.throws(() => test2.fromJSON(i), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
});
|
|
|
|
// Invalid rules are ignored.
|
|
test2.fromJSON(['1', '2', '3']);
|
|
assert.deepStrictEqual(test2.rules, []);
|
|
|
|
// Direct output from toJSON method works
|
|
test2.fromJSON(bl.toJSON());
|
|
assert.deepStrictEqual(test2.rules.sort(), ruleList);
|
|
|
|
// JSON stringified output works
|
|
test3.fromJSON(JSON.stringify(bl));
|
|
assert.deepStrictEqual(test3.rules.sort(), ruleList);
|
|
|
|
// A raw array works
|
|
test4.fromJSON(ruleList);
|
|
assert.deepStrictEqual(test4.rules.sort(), ruleList);
|
|
|
|
// Individual rules work
|
|
ruleList.forEach((item) => {
|
|
test5.fromJSON([item]);
|
|
});
|
|
assert.deepStrictEqual(test5.rules.sort(), ruleList);
|
|
|
|
// Each of the created blocklists should handle the checks identically.
|
|
[
|
|
['10.0.0.5', 'ipv4', true],
|
|
['10.0.0.6', 'ipv4', false],
|
|
['::', 'ipv6', true],
|
|
['::1', 'ipv6', false],
|
|
['192.168.1.0', 'ipv4', true],
|
|
['193.168.1.0', 'ipv4', false],
|
|
['8592:757c:efae:4e45::', 'ipv6', true],
|
|
['1111:1111:1111:1111::', 'ipv6', false],
|
|
].forEach((i) => {
|
|
assert.strictEqual(bl.check(i[0], i[1]), i[2]);
|
|
assert.strictEqual(test2.check(i[0], i[1]), i[2]);
|
|
assert.strictEqual(test3.check(i[0], i[1]), i[2]);
|
|
assert.strictEqual(test4.check(i[0], i[1]), i[2]);
|
|
assert.strictEqual(test5.check(i[0], i[1]), i[2]);
|
|
});
|
|
}
|
|
|
|
// removeRange: basic removal
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addRange('10.0.0.1', '10.0.0.100');
|
|
blockList.addRange('192.168.1.1', '192.168.1.50');
|
|
assert(blockList.check('10.0.0.50'));
|
|
assert(blockList.check('192.168.1.25'));
|
|
|
|
blockList.removeRange('10.0.0.1', '10.0.0.100');
|
|
assert(!blockList.check('10.0.0.50'));
|
|
assert(blockList.check('192.168.1.25'));
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
}
|
|
|
|
// removeRange: non-existent range is a no-op
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addRange('10.0.0.1', '10.0.0.10');
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
blockList.removeRange('99.99.99.1', '99.99.99.10');
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
assert(blockList.check('10.0.0.5'));
|
|
}
|
|
|
|
// removeRange: IPv6 range
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addRange('2001:db8::1', '2001:db8::ff', 'ipv6');
|
|
assert(blockList.check('2001:db8::50', 'ipv6'));
|
|
|
|
blockList.removeRange('2001:db8::1', '2001:db8::ff', 'ipv6');
|
|
assert(!blockList.check('2001:db8::50', 'ipv6'));
|
|
assert.strictEqual(blockList.rules.length, 0);
|
|
}
|
|
|
|
// removeRange: with SocketAddress objects
|
|
{
|
|
const blockList = new BlockList();
|
|
const start = new SocketAddress({ address: '10.0.0.1' });
|
|
const end = new SocketAddress({ address: '10.0.0.10' });
|
|
blockList.addRange(start, end);
|
|
assert(blockList.check('10.0.0.5'));
|
|
|
|
blockList.removeRange(start, end);
|
|
assert(!blockList.check('10.0.0.5'));
|
|
}
|
|
|
|
// removeSubnet: basic IPv4 removal
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addSubnet('10.0.0.0', 8);
|
|
blockList.addSubnet('192.168.0.0', 16);
|
|
assert(blockList.check('10.1.2.3'));
|
|
assert(blockList.check('192.168.5.5'));
|
|
|
|
blockList.removeSubnet('10.0.0.0', 8);
|
|
assert(!blockList.check('10.1.2.3'));
|
|
assert(blockList.check('192.168.5.5'));
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
}
|
|
|
|
// removeSubnet: IPv6
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addSubnet('2001:db8::', 32, 'ipv6');
|
|
assert(blockList.check('2001:db8::1', 'ipv6'));
|
|
|
|
blockList.removeSubnet('2001:db8::', 32, 'ipv6');
|
|
assert(!blockList.check('2001:db8::1', 'ipv6'));
|
|
assert.strictEqual(blockList.rules.length, 0);
|
|
}
|
|
|
|
// removeSubnet: cross-family cleanup
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addSubnet('10.0.0.0', 8);
|
|
assert(blockList.check('::ffff:10.0.0.1', 'ipv6'));
|
|
|
|
blockList.removeSubnet('10.0.0.0', 8);
|
|
assert(!blockList.check('10.0.0.1'));
|
|
assert(!blockList.check('::ffff:10.0.0.1', 'ipv6'));
|
|
}
|
|
|
|
// removeSubnet: non-existent subnet is a no-op
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addSubnet('10.0.0.0', 8);
|
|
blockList.removeSubnet('172.16.0.0', 12);
|
|
assert(blockList.check('10.1.2.3'));
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
}
|
|
|
|
// removeSubnet: with SocketAddress objects
|
|
{
|
|
const blockList = new BlockList();
|
|
const net = new SocketAddress({ address: '10.0.0.0' });
|
|
blockList.addSubnet(net, 8);
|
|
assert(blockList.check('10.1.2.3'));
|
|
|
|
blockList.removeSubnet(net, 8);
|
|
assert(!blockList.check('10.1.2.3'));
|
|
}
|
|
|
|
// removeRange/removeSubnet don't affect other rule types
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('1.1.1.1');
|
|
blockList.addRange('10.0.0.1', '10.0.0.100');
|
|
blockList.addSubnet('192.168.0.0', 16);
|
|
|
|
blockList.removeRange('10.0.0.1', '10.0.0.100');
|
|
blockList.removeSubnet('192.168.0.0', 16);
|
|
|
|
// Address rule should still work
|
|
assert(blockList.check('1.1.1.1'));
|
|
assert(!blockList.check('10.0.0.50'));
|
|
assert(!blockList.check('192.168.1.1'));
|
|
}
|
|
|
|
// addCIDR: IPv4
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDR('10.0.0.0/8');
|
|
blockList.addCIDR('192.168.1.0/24');
|
|
assert(blockList.check('10.1.2.3'));
|
|
assert(blockList.check('192.168.1.50'));
|
|
assert(!blockList.check('192.168.2.1'));
|
|
assert(!blockList.check('11.0.0.1'));
|
|
}
|
|
|
|
// addCIDR: IPv6 auto-detected
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDR('2001:db8::/32');
|
|
assert(blockList.check('2001:db8::1', 'ipv6'));
|
|
assert(blockList.check('2001:db8:ffff::1', 'ipv6'));
|
|
assert(!blockList.check('2001:db9::1', 'ipv6'));
|
|
}
|
|
|
|
// addCIDR: cross-family
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDR('10.0.0.0/8');
|
|
assert(blockList.check('::ffff:10.0.0.1', 'ipv6'));
|
|
}
|
|
|
|
// addCIDR: validation errors
|
|
{
|
|
const blockList = new BlockList();
|
|
assert.throws(() => blockList.addCIDR('10.0.0.0'), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => blockList.addCIDR('10.0.0.0/abc'), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
assert.throws(() => blockList.addCIDR(123), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => blockList.addCIDR('10.0.0.0/'), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
}
|
|
|
|
// removeCIDR: basic
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDR('10.0.0.0/8');
|
|
blockList.addCIDR('192.168.0.0/16');
|
|
assert(blockList.check('10.1.2.3'));
|
|
|
|
blockList.removeCIDR('10.0.0.0/8');
|
|
assert(!blockList.check('10.1.2.3'));
|
|
assert(blockList.check('192.168.1.1'));
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
}
|
|
|
|
// removeCIDR: IPv6
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDR('2001:db8::/32');
|
|
assert(blockList.check('2001:db8::1', 'ipv6'));
|
|
|
|
blockList.removeCIDR('2001:db8::/32');
|
|
assert(!blockList.check('2001:db8::1', 'ipv6'));
|
|
assert.strictEqual(blockList.rules.length, 0);
|
|
}
|
|
|
|
// removeCIDR: non-existent is a no-op
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDR('10.0.0.0/8');
|
|
blockList.removeCIDR('172.16.0.0/12');
|
|
assert(blockList.check('10.1.2.3'));
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
}
|
|
|
|
// addCIDR interoperates with removeSubnet, and vice versa
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDR('10.0.0.0/8');
|
|
blockList.removeSubnet('10.0.0.0', 8);
|
|
assert(!blockList.check('10.1.2.3'));
|
|
|
|
blockList.addSubnet('192.168.0.0', 16);
|
|
blockList.removeCIDR('192.168.0.0/16');
|
|
assert(!blockList.check('192.168.1.1'));
|
|
}
|
|
|
|
// removeAddress: basic
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('1.1.1.1');
|
|
blockList.addAddress('2.2.2.2');
|
|
assert(blockList.check('1.1.1.1'));
|
|
|
|
blockList.removeAddress('1.1.1.1');
|
|
assert(!blockList.check('1.1.1.1'));
|
|
assert(blockList.check('2.2.2.2'));
|
|
}
|
|
|
|
// removeAddress: cross-family cleanup
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('3.3.3.3');
|
|
assert(blockList.check('::ffff:3.3.3.3', 'ipv6'));
|
|
|
|
blockList.removeAddress('3.3.3.3');
|
|
assert(!blockList.check('3.3.3.3'));
|
|
assert(!blockList.check('::ffff:3.3.3.3', 'ipv6'));
|
|
}
|
|
|
|
// removeAddress: IPv6
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('::1', 'ipv6');
|
|
assert(blockList.check('::1', 'ipv6'));
|
|
|
|
blockList.removeAddress('::1', 'ipv6');
|
|
assert(!blockList.check('::1', 'ipv6'));
|
|
}
|
|
|
|
// removeAddress: non-existent is a no-op
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('1.1.1.1');
|
|
blockList.removeAddress('9.9.9.9');
|
|
assert(blockList.check('1.1.1.1'));
|
|
}
|
|
|
|
// removeAddress: with SocketAddress object
|
|
{
|
|
const blockList = new BlockList();
|
|
const addr = new SocketAddress({ address: '5.5.5.5' });
|
|
blockList.addAddress(addr);
|
|
assert(blockList.check('5.5.5.5'));
|
|
|
|
blockList.removeAddress(addr);
|
|
assert(!blockList.check('5.5.5.5'));
|
|
}
|
|
|
|
// addCIDRs: batch
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDRs(['10.0.0.0/8', '192.168.0.0/16', '2001:db8::/32']);
|
|
assert(blockList.check('10.1.2.3'));
|
|
assert(blockList.check('192.168.1.1'));
|
|
assert(blockList.check('2001:db8::1', 'ipv6'));
|
|
assert(!blockList.check('11.0.0.1'));
|
|
assert.strictEqual(blockList.rules.length, 3);
|
|
}
|
|
|
|
// addCIDRs: validation
|
|
{
|
|
const blockList = new BlockList();
|
|
assert.throws(() => blockList.addCIDRs('not-an-array'), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => blockList.addCIDRs([123]), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
});
|
|
assert.throws(() => blockList.addCIDRs(['10.0.0.0']), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
}
|
|
|
|
// addCIDRs: empty array is a no-op
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDRs([]);
|
|
assert.strictEqual(blockList.size, 0);
|
|
}
|
|
|
|
// addCIDRs: invalid entry mid-array does not half-apply
|
|
{
|
|
const blockList = new BlockList();
|
|
assert.throws(() => blockList.addCIDRs(['10.0.0.0/8', 'bad', '1.1.1.0/24']), {
|
|
code: 'ERR_INVALID_ARG_VALUE',
|
|
});
|
|
// Nothing should have been applied.
|
|
assert.strictEqual(blockList.size, 0);
|
|
assert(!blockList.check('10.0.0.1'));
|
|
}
|
|
|
|
// size: tracks all rule types
|
|
{
|
|
const blockList = new BlockList();
|
|
assert.strictEqual(blockList.size, 0);
|
|
|
|
blockList.addAddress('1.1.1.1');
|
|
assert.strictEqual(blockList.size, 1);
|
|
|
|
blockList.addRange('10.0.0.1', '10.0.0.10');
|
|
assert.strictEqual(blockList.size, 2);
|
|
|
|
blockList.addSubnet('192.168.0.0', 16);
|
|
assert.strictEqual(blockList.size, 3);
|
|
|
|
// Matches rules.length
|
|
assert.strictEqual(blockList.size, blockList.rules.length);
|
|
|
|
blockList.removeAddress('1.1.1.1');
|
|
assert.strictEqual(blockList.size, 2);
|
|
|
|
blockList.removeRange('10.0.0.1', '10.0.0.10');
|
|
assert.strictEqual(blockList.size, 1);
|
|
|
|
blockList.removeSubnet('192.168.0.0', 16);
|
|
assert.strictEqual(blockList.size, 0);
|
|
|
|
// After clear
|
|
blockList.addAddress('5.5.5.5');
|
|
blockList.clear();
|
|
assert.strictEqual(blockList.size, 0);
|
|
}
|
|
|
|
// size: duplicate addAddress does not double-count
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('1.1.1.1');
|
|
blockList.addAddress('1.1.1.1');
|
|
assert.strictEqual(blockList.size, 1);
|
|
}
|
|
|
|
// PRIVATE_RANGES: is a frozen array of CIDR strings
|
|
{
|
|
assert(Array.isArray(BlockList.PRIVATE_RANGES));
|
|
assert(Object.isFrozen(BlockList.PRIVATE_RANGES));
|
|
assert(BlockList.PRIVATE_RANGES.length > 0);
|
|
for (const cidr of BlockList.PRIVATE_RANGES) {
|
|
assert.strictEqual(typeof cidr, 'string');
|
|
assert(cidr.includes('/'));
|
|
}
|
|
}
|
|
|
|
// PRIVATE_RANGES: covers expected addresses
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addCIDRs(BlockList.PRIVATE_RANGES);
|
|
|
|
// IPv4 private (RFC 1918)
|
|
assert(blockList.check('10.0.0.1'));
|
|
assert(blockList.check('10.255.255.255'));
|
|
assert(blockList.check('172.16.0.1'));
|
|
assert(blockList.check('172.31.255.255'));
|
|
assert(blockList.check('192.168.0.1'));
|
|
assert(blockList.check('192.168.255.255'));
|
|
|
|
// Loopback
|
|
assert(blockList.check('127.0.0.1'));
|
|
assert(blockList.check('127.255.255.255'));
|
|
assert(blockList.check('::1', 'ipv6'));
|
|
|
|
// Link-local
|
|
assert(blockList.check('169.254.0.1'));
|
|
assert(blockList.check('fe80::1', 'ipv6'));
|
|
|
|
// ULA
|
|
assert(blockList.check('fc00::1', 'ipv6'));
|
|
assert(blockList.check('fd00::1', 'ipv6'));
|
|
|
|
// Public addresses should not match
|
|
assert(!blockList.check('8.8.8.8'));
|
|
assert(!blockList.check('1.1.1.1'));
|
|
assert(!blockList.check('203.0.113.1'));
|
|
assert(!blockList.check('2001:db8::1', 'ipv6'));
|
|
}
|
|
|
|
// check() with invalid address string returns false (exercises checkString
|
|
// error path in C++ — SocketAddress::New fails, returns false).
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('1.1.1.1');
|
|
assert.strictEqual(blockList.check('not_a_valid_ip'), false);
|
|
assert.strictEqual(blockList.check('', 'ipv4'), false);
|
|
assert.strictEqual(blockList.check('999.999.999.999'), false);
|
|
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'),
|
|
// This returns true as of libuv 1.53.0 (there are no 0.53.0 version)
|
|
(([major, minor]) => major > 1 || minor > 52)(process.versions.uv.split('.')));
|
|
}
|
|
|
|
// check() family parameter is case-insensitive.
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addAddress('10.0.0.1');
|
|
blockList.addAddress('::1', 'ipv6');
|
|
|
|
assert(blockList.check('10.0.0.1', 'ipv4'));
|
|
assert(blockList.check('10.0.0.1', 'IPv4'));
|
|
assert(blockList.check('10.0.0.1', 'IPV4'));
|
|
assert(blockList.check('::1', 'ipv6'));
|
|
assert(blockList.check('::1', 'IPv6'));
|
|
assert(blockList.check('::1', 'IPV6'));
|
|
}
|
|
|
|
// SocketAddress constructor with invalid address throws ERR_INVALID_ADDRESS.
|
|
{
|
|
assert.throws(() => new SocketAddress({ address: 'not_a_valid_ip' }), {
|
|
code: 'ERR_INVALID_ADDRESS',
|
|
});
|
|
assert.throws(
|
|
() => new SocketAddress({ address: 'not_valid', family: 'ipv6' }), {
|
|
code: 'ERR_INVALID_ADDRESS',
|
|
});
|
|
}
|
|
|
|
// check() with SocketAddress objects across family boundaries.
|
|
{
|
|
const blockList = new BlockList();
|
|
const ipv4 = new SocketAddress({ address: '10.0.0.1' });
|
|
const mapped = new SocketAddress({
|
|
address: '::ffff:10.0.0.1',
|
|
family: 'ipv6',
|
|
});
|
|
|
|
blockList.addAddress(ipv4);
|
|
|
|
// Check with SocketAddress objects (exercises the check() -> C++ fast path).
|
|
assert(blockList.check(ipv4));
|
|
assert(blockList.check(mapped));
|
|
|
|
blockList.removeAddress(ipv4);
|
|
assert(!blockList.check(ipv4));
|
|
assert(!blockList.check(mapped));
|
|
}
|
|
|
|
// Subnet with IPv4-mapped IPv6 network.
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addSubnet('::ffff:10.0.0.0', 120, 'ipv6');
|
|
|
|
// IPv4-mapped IPv6 within the subnet should match.
|
|
assert(blockList.check('::ffff:10.0.0.5', 'ipv6'));
|
|
// The plain IPv4 form should also match (cross-family trie lookup).
|
|
assert(blockList.check('10.0.0.5'));
|
|
// Outside the subnet.
|
|
assert(!blockList.check('10.0.1.0'));
|
|
}
|
|
|
|
// Range with IPv6 addresses.
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addRange('::1', '::ff', 'ipv6');
|
|
assert(blockList.check('::1', 'ipv6'));
|
|
assert(blockList.check('::a0', 'ipv6'));
|
|
assert(blockList.check('::ff', 'ipv6'));
|
|
assert(!blockList.check('::100', 'ipv6'));
|
|
assert(!blockList.check('::0', 'ipv6'));
|
|
|
|
blockList.removeRange('::1', '::ff', 'ipv6');
|
|
assert(!blockList.check('::a0', 'ipv6'));
|
|
}
|
|
|
|
// Removing a broader subnet must restore subsumed narrower subnets.
|
|
{
|
|
const blockList = new BlockList();
|
|
blockList.addSubnet('10.0.0.0', 8); // /8 subsumes /16 in the trie
|
|
blockList.addSubnet('10.1.0.0', 16);
|
|
assert(blockList.check('10.1.2.3'));
|
|
|
|
blockList.removeSubnet('10.0.0.0', 8);
|
|
|
|
// /16 must still work after /8 is removed.
|
|
assert(blockList.check('10.1.2.3'));
|
|
// Address outside /16 but inside old /8 should no longer match.
|
|
assert(!blockList.check('10.2.0.1'));
|
|
assert.strictEqual(blockList.rules.length, 1);
|
|
}
|
|
|
|
{
|
|
// IPv4-mapped / mixed-notation IPv6 rules must survive a toJSON/fromJSON
|
|
// round-trip. toJSON() emits these addresses in mixed dotted-quad form
|
|
// (e.g. "::ffff:192.0.2.128"), so the rule parser must accept the "." used
|
|
// in that notation.
|
|
const bl = new BlockList();
|
|
bl.addAddress('::ffff:192.0.2.128', 'ipv6');
|
|
bl.addSubnet('::ffff:10.0.0.0', 120, 'ipv6');
|
|
bl.addRange('::ffff:172.16.0.1', '::ffff:172.16.0.10', 'ipv6');
|
|
|
|
const expected = [
|
|
'Address: IPv6 ::ffff:192.0.2.128',
|
|
'Range: IPv6 ::ffff:172.16.0.1-::ffff:172.16.0.10',
|
|
'Subnet: IPv6 ::ffff:10.0.0.0/120',
|
|
];
|
|
assert.deepStrictEqual(bl.toJSON().sort(), expected);
|
|
|
|
const restored = new BlockList();
|
|
restored.fromJSON(bl.toJSON());
|
|
|
|
// The restored rules must be identical (lossless round-trip).
|
|
assert.deepStrictEqual(restored.toJSON().sort(), expected);
|
|
|
|
// And it must still match the same addresses as the original.
|
|
[
|
|
['::ffff:192.0.2.128', true], // the single address
|
|
['::ffff:192.0.2.129', false],
|
|
['::ffff:10.0.0.50', true], // inside the subnet
|
|
['::ffff:10.0.1.0', false],
|
|
['::ffff:172.16.0.5', true], // inside the range
|
|
['::ffff:172.16.0.20', false],
|
|
].forEach(([address, result]) => {
|
|
assert.strictEqual(bl.check(address, 'ipv6'), result);
|
|
assert.strictEqual(restored.check(address, 'ipv6'), result);
|
|
});
|
|
}
|