Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/net.md80
-rw-r--r--lib/internal/blocklist.js115
-rw-r--r--lib/net.js6
-rw-r--r--node.gyp1
-rw-r--r--src/node_binding.cc1
-rw-r--r--src/node_sockaddr-inl.h28
-rw-r--r--src/node_sockaddr.cc592
-rw-r--r--src/node_sockaddr.h146
-rw-r--r--test/cctest/test_sockaddr.cc86
-rw-r--r--test/parallel/test-blocklist.js136
10 files changed, 1189 insertions, 2 deletions
diff --git a/doc/api/net.md b/doc/api/net.md
index 3bf9a68a8ab..72324760f88 100644
--- a/doc/api/net.md
+++ b/doc/api/net.md
@@ -55,6 +55,86 @@ net.createServer().listen(
path.join('\\\\?\\pipe', process.cwd(), 'myctl'));
```
+## Class: `net.BlockList`
+<!-- YAML
+added: REPLACEME
+-->
+
+The `BlockList` object can be used with some network APIs to specify rules for
+disabling inbound or outbound access to specific IP addresses, IP ranges, or
+IP subnets.
+
+### `blockList.addAddress(address[, type])`
+<!-- YAML
+added: REPLACEME
+-->
+
+* `address` {string} An IPv4 or IPv6 address.
+* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default**: `'ipv4'`.
+
+Adds a rule to block the given IP address.
+
+### `blockList.addRange(start, end[, type])`
+<!-- YAML
+added: REPLACEME
+-->
+
+* `start` {string} The starting IPv4 or IPv6 address in the range.
+* `end` {string} The ending IPv4 or IPv6 address in the range.
+* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default**: `'ipv4'`.
+
+Adds a rule to block a range of IP addresses from `start` (inclusive) to
+`end` (inclusive).
+
+### `blockList.addSubnet(net, prefix[, type])`
+<!-- YAML
+added: REPLACEME
+-->
+
+* `net` {string} The network IPv4 or IPv6 address.
+* `prefix` {number} The number of CIDR prefix bits. For IPv4, this
+ must be a value between `0` and `32`. For IPv6, this must be between
+ `0` and `128`.
+* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default**: `'ipv4'`.
+
+Adds a rule to block a range of IP addresses specified as a subnet mask.
+
+### `blockList.check(address[, type])`
+<!-- YAML
+added: REPLACEME
+-->
+
+* `address` {string} The IP address to check
+* `type` {string} Either `'ipv4'` or `'ipv6'`. **Default**: `'ipv4'`.
+* Returns: {boolean}
+
+Returns `true` if the given IP address matches any of the rules added to the
+`BlockList`.
+
+```js
+const blockList = new net.BlockList();
+blockList.addAddress('123.123.123.123');
+blockList.addRange('10.0.0.1', '10.0.0.10');
+blockList.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6');
+
+console.log(blockList.check('123.123.123.123')); // Prints: true
+console.log(blockList.check('10.0.0.3')); // Prints: true
+console.log(blockList.check('222.111.111.222')); // Prints: false
+
+// IPv6 notation for IPv4 addresses works:
+console.log(blockList.check('::ffff:7b7b:7b7b', 'ipv6')); // Prints: true
+console.log(blockList.check('::ffff:123.123.123.123', 'ipv6')); // Prints: true
+```
+
+### `blockList.rules`
+<!-- YAML
+added: REPLACEME
+-->
+
+* Type: {string[]}
+
+The list of rules added to the blocklist.
+
## Class: `net.Server`
<!-- YAML
added: v0.1.90
diff --git a/lib/internal/blocklist.js b/lib/internal/blocklist.js
new file mode 100644
index 00000000000..d4074ab41c2
--- /dev/null
+++ b/lib/internal/blocklist.js
@@ -0,0 +1,115 @@
+'use strict';
+
+const {
+ Boolean,
+ Symbol
+} = primordials;
+
+const {
+ BlockList: BlockListHandle,
+ AF_INET,
+ AF_INET6,
+} = internalBinding('block_list');
+
+const {
+ customInspectSymbol: kInspect,
+} = require('internal/util');
+const { inspect } = require('internal/util/inspect');
+
+const kHandle = Symbol('kHandle');
+const { owner_symbol } = internalBinding('symbols');
+
+const {
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_ARG_VALUE,
+ ERR_OUT_OF_RANGE,
+} = require('internal/errors').codes;
+
+class BlockList {
+ constructor() {
+ this[kHandle] = new BlockListHandle();
+ this[kHandle][owner_symbol] = this;
+ }
+
+ [kInspect](depth, options) {
+ if (depth < 0)
+ return this;
+
+ const opts = {
+ ...options,
+ depth: options.depth == null ? null : options.depth - 1
+ };
+
+ return `BlockList ${inspect({
+ rules: this.rules
+ }, opts)}`;
+ }
+
+ addAddress(address, family = 'ipv4') {
+ if (typeof address !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('address', 'string', address);
+ if (typeof family !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('family', 'string', family);
+ if (family !== 'ipv4' && family !== 'ipv6')
+ throw new ERR_INVALID_ARG_VALUE('family', family);
+ const type = family === 'ipv4' ? AF_INET : AF_INET6;
+ this[kHandle].addAddress(address, type);
+ }
+
+ addRange(start, end, family = 'ipv4') {
+ if (typeof start !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('start', 'string', start);
+ if (typeof end !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('end', 'string', end);
+ if (typeof family !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('family', 'string', family);
+ if (family !== 'ipv4' && family !== 'ipv6')
+ throw new ERR_INVALID_ARG_VALUE('family', family);
+ const type = family === 'ipv4' ? AF_INET : AF_INET6;
+ const ret = this[kHandle].addRange(start, end, type);
+ if (ret === false)
+ throw new ERR_INVALID_ARG_VALUE('start', start, 'must come before end');
+ }
+
+ addSubnet(network, prefix, family = 'ipv4') {
+ if (typeof network !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('network', 'string', network);
+ if (typeof prefix !== 'number')
+ throw new ERR_INVALID_ARG_TYPE('prefix', 'number', prefix);
+ if (typeof family !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('family', 'string', family);
+ let type;
+ switch (family) {
+ case 'ipv4':
+ type = AF_INET;
+ if (prefix < 0 || prefix > 32)
+ throw new ERR_OUT_OF_RANGE(prefix, '>= 0 and <= 32', prefix);
+ break;
+ case 'ipv6':
+ type = AF_INET6;
+ if (prefix < 0 || prefix > 128)
+ throw new ERR_OUT_OF_RANGE(prefix, '>= 0 and <= 128', prefix);
+ break;
+ default:
+ throw new ERR_INVALID_ARG_VALUE('family', family);
+ }
+ this[kHandle].addSubnet(network, type, prefix);
+ }
+
+ check(address, family = 'ipv4') {
+ if (typeof address !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('address', 'string', address);
+ if (typeof family !== 'string')
+ throw new ERR_INVALID_ARG_TYPE('family', 'string', family);
+ if (family !== 'ipv4' && family !== 'ipv6')
+ throw new ERR_INVALID_ARG_VALUE('family', family);
+ const type = family === 'ipv4' ? AF_INET : AF_INET6;
+ return Boolean(this[kHandle].check(address, type));
+ }
+
+ get rules() {
+ return this[kHandle].getRules();
+ }
+}
+
+module.exports = BlockList;
diff --git a/lib/net.js b/lib/net.js
index 54958d5912e..21b28c8978d 100644
--- a/lib/net.js
+++ b/lib/net.js
@@ -117,6 +117,7 @@ const {
// Lazy loaded to improve startup performance.
let cluster;
let dns;
+let BlockList;
const { clearTimeout } = require('timers');
const { kTimeout } = require('internal/timers');
@@ -1724,6 +1725,11 @@ module.exports = {
_createServerHandle: createServerHandle,
_normalizeArgs: normalizeArgs,
_setSimultaneousAccepts,
+ get BlockList() {
+ if (BlockList === undefined)
+ BlockList = require('internal/blocklist');
+ return BlockList;
+ },
connect,
createConnection: connect,
createServer,
diff --git a/node.gyp b/node.gyp
index 6dd9541cc55..72e06754210 100644
--- a/node.gyp
+++ b/node.gyp
@@ -106,6 +106,7 @@
'lib/internal/assert/assertion_error.js',
'lib/internal/assert/calltracker.js',
'lib/internal/async_hooks.js',
+ 'lib/internal/blocklist.js',
'lib/internal/buffer.js',
'lib/internal/cli_table.js',
'lib/internal/child_process.js',
diff --git a/src/node_binding.cc b/src/node_binding.cc
index 4dbf56b99a3..9890f9e7be9 100644
--- a/src/node_binding.cc
+++ b/src/node_binding.cc
@@ -44,6 +44,7 @@
// __attribute__((constructor)) like mechanism in GCC.
#define NODE_BUILTIN_STANDARD_MODULES(V) \
V(async_wrap) \
+ V(block_list) \
V(buffer) \
V(cares_wrap) \
V(config) \
diff --git a/src/node_sockaddr-inl.h b/src/node_sockaddr-inl.h
index c8b985aedda..e5d8985771e 100644
--- a/src/node_sockaddr-inl.h
+++ b/src/node_sockaddr-inl.h
@@ -4,6 +4,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node.h"
+#include "env-inl.h"
#include "node_internals.h"
#include "node_sockaddr.h"
#include "util-inl.h"
@@ -88,11 +89,11 @@ SocketAddress& SocketAddress::operator=(const SocketAddress& addr) {
}
const sockaddr& SocketAddress::operator*() const {
- return *this->data();
+ return *data();
}
const sockaddr* SocketAddress::operator->() const {
- return this->data();
+ return data();
}
size_t SocketAddress::length() const {
@@ -166,6 +167,24 @@ bool SocketAddress::operator!=(const SocketAddress& other) const {
return !(*this == other);
}
+bool SocketAddress::operator<(const SocketAddress& other) const {
+ return compare(other) == CompareResult::LESS_THAN;
+}
+
+bool SocketAddress::operator>(const SocketAddress& other) const {
+ return compare(other) == CompareResult::GREATER_THAN;
+}
+
+bool SocketAddress::operator<=(const SocketAddress& other) const {
+ CompareResult c = compare(other);
+ return c == CompareResult::NOT_COMPARABLE ? false :
+ c <= CompareResult::SAME;
+}
+
+bool SocketAddress::operator>=(const SocketAddress& other) const {
+ return compare(other) >= CompareResult::SAME;
+}
+
template <typename T>
SocketAddressLRU<T>::SocketAddressLRU(
size_t max_size)
@@ -231,6 +250,11 @@ typename T::Type* SocketAddressLRU<T>::Upsert(
return &map_[address]->second;
}
+v8::MaybeLocal<v8::Value> SocketAddressBlockList::Rule::ToV8String(
+ Environment* env) {
+ std::string str = ToString();
+ return ToV8Value(env->context(), str);
+}
} // namespace node
#endif // NODE_WANT_INTERNALS
diff --git a/src/node_sockaddr.cc b/src/node_sockaddr.cc
index 74fe123529a..8ba82ff6853 100644
--- a/src/node_sockaddr.cc
+++ b/src/node_sockaddr.cc
@@ -1,8 +1,25 @@
#include "node_sockaddr-inl.h" // NOLINT(build/include)
+#include "env-inl.h"
+#include "base_object-inl.h"
+#include "memory_tracker-inl.h"
#include "uv.h"
+#include <memory>
+#include <string>
+#include <vector>
+
namespace node {
+using v8::Array;
+using v8::Context;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Local;
+using v8::MaybeLocal;
+using v8::Object;
+using v8::String;
+using v8::Value;
+
namespace {
template <typename T, typename F>
SocketAddress FromUVHandle(F fn, const T& handle) {
@@ -92,4 +109,579 @@ SocketAddress SocketAddress::FromPeerName(const uv_udp_t& handle) {
return FromUVHandle(uv_udp_getpeername, handle);
}
+namespace {
+constexpr uint8_t mask[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
+
+bool is_match_ipv4(
+ const SocketAddress& one,
+ const SocketAddress& two) {
+ const sockaddr_in* one_in =
+ reinterpret_cast<const sockaddr_in*>(one.data());
+ const sockaddr_in* two_in =
+ reinterpret_cast<const sockaddr_in*>(two.data());
+ return memcmp(&one_in->sin_addr, &two_in->sin_addr, sizeof(uint32_t)) == 0;
+}
+
+bool is_match_ipv6(
+ const SocketAddress& one,
+ const SocketAddress& two) {
+ const sockaddr_in6* one_in =
+ reinterpret_cast<const sockaddr_in6*>(one.data());
+ const sockaddr_in6* two_in =
+ reinterpret_cast<const sockaddr_in6*>(two.data());
+ return memcmp(&one_in->sin6_addr, &two_in->sin6_addr, 16) == 0;
+}
+
+bool is_match_ipv4_ipv6(
+ const SocketAddress& ipv4,
+ const SocketAddress& ipv6) {
+ const sockaddr_in* check_ipv4 =
+ reinterpret_cast<const sockaddr_in*>(ipv4.data());
+ const sockaddr_in6* check_ipv6 =
+ reinterpret_cast<const sockaddr_in6*>(ipv6.data());
+
+ const uint8_t* ptr =
+ reinterpret_cast<const uint8_t*>(&check_ipv6->sin6_addr);
+
+ return memcmp(ptr, mask, sizeof(mask)) == 0 &&
+ memcmp(ptr + sizeof(mask),
+ &check_ipv4->sin_addr,
+ sizeof(uint32_t)) == 0;
+}
+
+SocketAddress::CompareResult compare_ipv4(
+ const SocketAddress& one,
+ const SocketAddress& two) {
+ const sockaddr_in* one_in =
+ reinterpret_cast<const sockaddr_in*>(one.data());
+ const sockaddr_in* two_in =
+ reinterpret_cast<const sockaddr_in*>(two.data());
+
+ if (one_in->sin_addr.s_addr < two_in->sin_addr.s_addr)
+ return SocketAddress::CompareResult::LESS_THAN;
+ else if (one_in->sin_addr.s_addr == two_in->sin_addr.s_addr)
+ return SocketAddress::CompareResult::SAME;
+ else
+ return SocketAddress::CompareResult::GREATER_THAN;
+}
+
+SocketAddress::CompareResult compare_ipv6(
+ const SocketAddress& one,
+ const SocketAddress& two) {
+ const sockaddr_in6* one_in =
+ reinterpret_cast<const sockaddr_in6*>(one.data());
+ const sockaddr_in6* two_in =
+ reinterpret_cast<const sockaddr_in6*>(two.data());
+ int ret = memcmp(&one_in->sin6_addr, &two_in->sin6_addr, 16);
+ if (ret < 0)
+ return SocketAddress::CompareResult::LESS_THAN;
+ else if (ret > 0)
+ return SocketAddress::CompareResult::GREATER_THAN;
+ return SocketAddress::CompareResult::SAME;
+}
+
+SocketAddress::CompareResult compare_ipv4_ipv6(
+ const SocketAddress& ipv4,
+ const SocketAddress& ipv6) {
+ const sockaddr_in* ipv4_in =
+ reinterpret_cast<const sockaddr_in*>(ipv4.data());
+ const sockaddr_in6 * ipv6_in =
+ reinterpret_cast<const sockaddr_in6*>(ipv6.data());
+
+ const uint8_t* ptr =
+ reinterpret_cast<const uint8_t*>(&ipv6_in->sin6_addr);
+
+ if (memcmp(ptr, mask, sizeof(mask)) != 0)
+ return SocketAddress::CompareResult::NOT_COMPARABLE;
+
+ int ret = memcmp(
+ &ipv4_in->sin_addr,
+ ptr + sizeof(mask),
+ sizeof(uint32_t));
+
+ if (ret < 0)
+ return SocketAddress::CompareResult::LESS_THAN;
+ else if (ret > 0)
+ return SocketAddress::CompareResult::GREATER_THAN;
+ return SocketAddress::CompareResult::SAME;
+}
+
+bool in_network_ipv4(
+ const SocketAddress& ip,
+ const SocketAddress& net,
+ int prefix) {
+ uint32_t mask = ((1 << prefix) - 1) << (32 - prefix);
+
+ const sockaddr_in* ip_in =
+ reinterpret_cast<const sockaddr_in*>(ip.data());
+ const sockaddr_in* net_in =
+ reinterpret_cast<const sockaddr_in*>(net.data());
+
+ return (htonl(ip_in->sin_addr.s_addr) & mask) ==
+ (htonl(net_in->sin_addr.s_addr) & mask);
+}
+
+bool in_network_ipv6(
+ const SocketAddress& ip,
+ const SocketAddress& net,
+ int prefix) {
+ // Special case, if prefix == 128, then just do a
+ // straight comparison.
+ if (prefix == 128)
+ return compare_ipv6(ip, net) == SocketAddress::CompareResult::SAME;
+
+ uint8_t r = prefix % 8;
+ int len = (prefix - r) / 8;
+ uint8_t mask = ((1 << r) - 1) << (8 - r);
+
+ const sockaddr_in6* ip_in =
+ reinterpret_cast<const sockaddr_in6*>(ip.data());
+ const sockaddr_in6* net_in =
+ reinterpret_cast<const sockaddr_in6*>(net.data());
+
+ if (memcmp(&ip_in->sin6_addr, &net_in->sin6_addr, len) != 0)
+ return false;
+
+ const uint8_t* p1 = reinterpret_cast<const uint8_t*>(
+ ip_in->sin6_addr.s6_addr);
+ const uint8_t* p2 = reinterpret_cast<const uint8_t*>(
+ net_in->sin6_addr.s6_addr);
+
+ return (p1[len] & mask) == (p2[len] & mask);
+}
+
+bool in_network_ipv4_ipv6(
+ const SocketAddress& ip,
+ const SocketAddress& net,
+ int prefix) {
+
+ if (prefix == 128)
+ return compare_ipv4_ipv6(ip, net) == SocketAddress::CompareResult::SAME;
+
+ uint8_t r = prefix % 8;
+ int len = (prefix - r) / 8;
+ uint8_t mask = ((1 << r) - 1) << (8 - r);
+
+ const sockaddr_in* ip_in =
+ reinterpret_cast<const sockaddr_in*>(ip.data());
+ const sockaddr_in6* net_in =
+ reinterpret_cast<const sockaddr_in6*>(net.data());
+
+ uint8_t ip_mask[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0};
+ uint8_t* ptr = ip_mask;
+ memcpy(ptr + 12, &ip_in->sin_addr, 4);
+
+ if (memcmp(ptr, &net_in->sin6_addr, len) != 0)
+ return false;
+
+ ptr += len;
+ const uint8_t* p2 = reinterpret_cast<const uint8_t*>(
+ net_in->sin6_addr.s6_addr);
+
+ return (ptr[0] & mask) == (p2[len] & mask);
+}
+
+bool in_network_ipv6_ipv4(
+ const SocketAddress& ip,
+ const SocketAddress& net,
+ int prefix) {
+ if (prefix == 32)
+ return compare_ipv4_ipv6(net, ip) == SocketAddress::CompareResult::SAME;
+
+ uint32_t m = ((1 << prefix) - 1) << (32 - prefix);
+
+ const sockaddr_in6* ip_in =
+ reinterpret_cast<const sockaddr_in6*>(ip.data());
+ const sockaddr_in* net_in =
+ reinterpret_cast<const sockaddr_in*>(net.data());
+
+ const uint8_t* ptr =
+ reinterpret_cast<const uint8_t*>(&ip_in->sin6_addr);
+
+ if (memcmp(ptr, mask, sizeof(mask)) != 0)
+ return false;
+
+ ptr += sizeof(mask);
+ uint32_t check = ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
+
+ return (check & m) == (htonl(net_in->sin_addr.s_addr) & m);
+}
+} // namespace
+
+// TODO(@jasnell): The implementations of is_match, compare, and
+// is_in_network have not been performance optimized and could
+// likely benefit from work on more performant approaches.
+
+bool SocketAddress::is_match(const SocketAddress& other) const {
+ switch (family()) {
+ case AF_INET:
+ switch (other.family()) {
+ case AF_INET: return is_match_ipv4(*this, other);
+ case AF_INET6: return is_match_ipv4_ipv6(*this, other);
+ }
+ break;
+ case AF_INET6:
+ switch (other.family()) {
+ case AF_INET: return is_match_ipv4_ipv6(other, *this);
+ case AF_INET6: return is_match_ipv6(*this, other);
+ }
+ break;
+ }
+ return false;
+}
+
+SocketAddress::CompareResult SocketAddress::compare(
+ const SocketAddress& other) const {
+ switch (family()) {
+ case AF_INET:
+ switch (other.family()) {
+ case AF_INET: return compare_ipv4(*this, other);
+ case AF_INET6: return compare_ipv4_ipv6(*this, other);
+ }
+ break;
+ case AF_INET6:
+ switch (other.family()) {
+ case AF_INET: {
+ CompareResult c = compare_ipv4_ipv6(other, *this);
+ switch (c) {
+ case SocketAddress::CompareResult::NOT_COMPARABLE:
+ // Fall through
+ case SocketAddress::CompareResult::SAME:
+ return c;
+ case SocketAddress::CompareResult::GREATER_THAN:
+ return SocketAddress::CompareResult::LESS_THAN;
+ case SocketAddress::CompareResult::LESS_THAN:
+ return SocketAddress::CompareResult::GREATER_THAN;
+ }
+ break;
+ }
+ case AF_INET6: return compare_ipv6(*this, other);
+ }
+ break;
+ }
+ return SocketAddress::CompareResult::NOT_COMPARABLE;
+}
+
+bool SocketAddress::is_in_network(
+ const SocketAddress& other,
+ int prefix) const {
+
+ switch (family()) {
+ case AF_INET:
+ switch (other.family()) {
+ case AF_INET: return in_network_ipv4(*this, other, prefix);
+ case AF_INET6: return in_network_ipv4_ipv6(*this, other, prefix);
+ }
+ break;
+ case AF_INET6:
+ switch (other.family()) {
+ case AF_INET: return in_network_ipv6_ipv4(*this, other, prefix);
+ case AF_INET6: return in_network_ipv6(*this, other, prefix);
+ }
+ break;
+ }
+
+ return false;
+}
+
+SocketAddressBlockList::SocketAddressBlockList(
+ std::shared_ptr<SocketAddressBlockList> parent)
+ : parent_(parent) {}
+
+void SocketAddressBlockList::AddSocketAddress(
+ const SocketAddress& address) {
+ std::unique_ptr<Rule> rule =
+ std::make_unique<SocketAddressRule>(address);
+ rules_.emplace_front(std::move(rule));
+ address_rules_[address] = rules_.begin();
+}
+
+void SocketAddressBlockList::RemoveSocketAddress(
+ const SocketAddress& address) {
+ auto it = address_rules_.find(address);
+ if (it != std::end(address_rules_)) {
+ rules_.erase(it->second);
+ address_rules_.erase(it);
+ }
+}
+
+void SocketAddressBlockList::AddSocketAddressRange(
+ const SocketAddress& start,
+ const SocketAddress& end) {
+ std::unique_ptr<Rule> rule =
+ std::make_unique<SocketAddressRangeRule>(start, end);
+ rules_.emplace_front(std::move(rule));
+}
+
+void SocketAddressBlockList::AddSocketAddressMask(
+ const SocketAddress& network,
+ int prefix) {
+ std::unique_ptr<Rule> rule =
+ std::make_unique<SocketAddressMaskRule>(network, prefix);
+ rules_.emplace_front(std::move(rule));
+}
+
+bool SocketAddressBlockList::Apply(const SocketAddress& address) {
+ for (const auto& rule : rules_) {
+ if (rule->Apply(address))
+ return true;
+ }
+ return parent_ ? parent_->Apply(address) : false;
+}
+
+SocketAddressBlockList::SocketAddressRule::SocketAddressRule(
+ const SocketAddress& address_)
+ : address(address_) {}
+
+SocketAddressBlockList::SocketAddressRangeRule::SocketAddressRangeRule(
+ const SocketAddress& start_,
+ const SocketAddress& end_)
+ : start(start_),
+ end(end_) {}
+
+SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
+ const SocketAddress& network_,
+ int prefix_)
+ : network(network_),
+ prefix(prefix_) {}
+
+bool SocketAddressBlockList::SocketAddressRule::Apply(
+ const SocketAddress& address) {
+ return this->address.is_match(address);
+}
+
+std::string SocketAddressBlockList::SocketAddressRule::ToString() {
+ std::string ret = "Address: ";
+ ret += address.family() == AF_INET ? "IPv4" : "IPv6";
+ ret += " ";
+ ret += address.address();
+ return ret;
+}
+
+bool SocketAddressBlockList::SocketAddressRangeRule::Apply(
+ const SocketAddress& address) {
+ return address >= start && address <= end;
+}
+
+std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
+ std::string ret = "Range: ";
+ ret += start.family() == AF_INET ? "IPv4" : "IPv6";
+ ret += " ";
+ ret += start.address();
+ ret += "-";
+ ret += end.address();
+ return ret;
+}
+
+bool SocketAddressBlockList::SocketAddressMaskRule::Apply(
+ const SocketAddress& address) {
+ return address.is_in_network(network, prefix);
+}
+
+std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
+ std::string ret = "Subnet: ";
+ ret += network.family() == AF_INET ? "IPv4" : "IPv6";
+ ret += " ";
+ ret += network.address();
+ ret += "/" + std::to_string(prefix);
+ return ret;
+}
+
+MaybeLocal<Array> SocketAddressBlockList::ListRules(Environment* env) {
+ std::vector<Local<Value>> rules;
+ for (const auto& rule : rules_) {
+ Local<Value> str;
+ if (!rule->ToV8String(env).ToLocal(&str))
+ return MaybeLocal<Array>();
+ rules.push_back(str);
+ }
+ return Array::New(env->isolate(), rules.data(), rules.size());
+}
+
+void SocketAddressBlockList::MemoryInfo(node::MemoryTracker* tracker) const {
+ tracker->TrackField("rules", rules_);
+}
+
+void SocketAddressBlockList::SocketAddressRule::MemoryInfo(
+ node::MemoryTracker* tracker) const {
+ tracker->TrackField("address", address);
+}
+
+void SocketAddressBlockList::SocketAddressRangeRule::MemoryInfo(
+ node::MemoryTracker* tracker) const {
+ tracker->TrackField("start", start);
+ tracker->TrackField("end", end);
+}
+
+void SocketAddressBlockList::SocketAddressMaskRule::MemoryInfo(
+ node::MemoryTracker* tracker) const {
+ tracker->TrackField("network", network);
+}
+
+SocketAddressBlockListWrap::SocketAddressBlockListWrap(
+ Environment* env, Local<Object> wrap)
+ : BaseObject(env, wrap) {
+ MakeWeak();
+}
+
+void SocketAddressBlockListWrap::New(
+ const FunctionCallbackInfo<Value>& args) {
+ CHECK(args.IsConstructCall());
+ Environment* env = Environment::GetCurrent(args);
+ new SocketAddressBlockListWrap(env, args.This());
+}
+
+void SocketAddressBlockListWrap::AddAddress(
+ const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ SocketAddressBlockListWrap* wrap;
+ ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
+
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsInt32());
+
+ sockaddr_storage address;
+ Utf8Value value(args.GetIsolate(), args[0]);
+ int32_t family;
+ if (!args[1]->Int32Value(env->context()).To(&family))
+ return;
+
+ if (!SocketAddress::ToSockAddr(family, *value, 0, &address))
+ return;
+
+ wrap->AddSocketAddress(
+ SocketAddress(reinterpret_cast<const sockaddr*>(&address)));
+
+ args.GetReturnValue().Set(true);
+}
+
+void SocketAddressBlockListWrap::AddRange(
+ const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ SocketAddressBlockListWrap* wrap;
+ ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
+
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsString());
+ CHECK(args[2]->IsInt32());
+
+ sockaddr_storage address[2];
+ Utf8Value start(args.GetIsolate(), args[0]);
+ Utf8Value end(args.GetIsolate(), args[1]);
+ int32_t family;
+ if (!args[2]->Int32Value(env->context()).To(&family))
+ return;
+
+ if (!SocketAddress::ToSockAddr(family, *start, 0, &address[0]) ||
+ !SocketAddress::ToSockAddr(family, *end, 0, &address[1])) {
+ return;
+ }
+
+ SocketAddress start_addr(reinterpret_cast<const sockaddr*>(&address[0]));
+ SocketAddress end_addr(reinterpret_cast<const sockaddr*>(&address[1]));
+
+ // Starting address must come before the end address
+ if (start_addr > end_addr)
+ return args.GetReturnValue().Set(false);
+
+ wrap->AddSocketAddressRange(start_addr, end_addr);
+
+ args.GetReturnValue().Set(true);
+}
+
+void SocketAddressBlockListWrap::AddSubnet(
+ const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ SocketAddressBlockListWrap* wrap;
+ ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
+
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsInt32());
+ CHECK(args[2]->IsInt32());
+
+ sockaddr_storage address;
+ Utf8Value network(args.GetIsolate(), args[0]);
+ int32_t family;
+ int32_t prefix;
+ if (!args[1]->Int32Value(env->context()).To(&family) ||
+ !args[2]->Int32Value(env->context()).To(&prefix)) {
+ return;
+ }
+
+ if (!SocketAddress::ToSockAddr(family, *network, 0, &address))
+ return;
+
+ CHECK_IMPLIES(family == AF_INET, prefix <= 32);
+ CHECK_IMPLIES(family == AF_INET6, prefix <= 128);
+ CHECK_GE(prefix, 0);
+
+ wrap->AddSocketAddressMask(
+ SocketAddress(reinterpret_cast<const sockaddr*>(&address)),
+ prefix);
+
+ args.GetReturnValue().Set(true);
+}
+
+void SocketAddressBlockListWrap::Check(
+ const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ SocketAddressBlockListWrap* wrap;
+ ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
+
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsInt32());
+
+ sockaddr_storage address;
+ Utf8Value value(args.GetIsolate(), args[0]);
+ int32_t family;
+ if (!args[1]->Int32Value(env->context()).To(&family))
+ return;
+
+ if (!SocketAddress::ToSockAddr(family, *value, 0, &address))
+ return;
+
+ args.GetReturnValue().Set(
+ wrap->Apply(SocketAddress(reinterpret_cast<const sockaddr*>(&address))));
+}
+
+void SocketAddressBlockListWrap::GetRules(
+ const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ SocketAddressBlockListWrap* wrap;
+ ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
+ Local<Array> rules;
+ if (wrap->ListRules(env).ToLocal(&rules))
+ args.GetReturnValue().Set(rules);
+}
+
+void SocketAddressBlockListWrap::Initialize(
+ Local<Object> target,
+ Local<Value> unused,
+ Local<Context> context,
+ void* priv) {
+ Environment* env = Environment::GetCurrent(context);
+
+ Local<String> name = FIXED_ONE_BYTE_STRING(env->isolate(), "BlockList");
+ Local<FunctionTemplate> t =
+ env->NewFunctionTemplate(SocketAddressBlockListWrap::New);
+ t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount);
+ t->SetClassName(name);
+
+ env->SetProtoMethod(t, "addAddress", SocketAddressBlockListWrap::AddAddress);
+ env->SetProtoMethod(t, "addRange", SocketAddressBlockListWrap::AddRange);
+ env->SetProtoMethod(t, "addSubnet", SocketAddressBlockListWrap::AddSubnet);
+ env->SetProtoMethod(t, "check", SocketAddressBlockListWrap::Check);
+ env->SetProtoMethod(t, "getRules", SocketAddressBlockListWrap::GetRules);
+
+ target->Set(env->context(), name,
+ t->GetFunction(env->context()).ToLocalChecked()).FromJust();
+
+ NODE_DEFINE_CONSTANT(target, AF_INET);
+ NODE_DEFINE_CONSTANT(target, AF_INET6);
+}
+
} // namespace node
+
+NODE_MODULE_CONTEXT_AWARE_INTERNAL(
+ block_list,
+ node::SocketAddressBlockListWrap::Initialize)
diff --git a/src/node_sockaddr.h b/src/node_sockaddr.h
index 5d20487f93d..f539cf6555f 100644
--- a/src/node_sockaddr.h
+++ b/src/node_sockaddr.h
@@ -3,11 +3,14 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+#include "env.h"
#include "memory_tracker.h"
+#include "base_object.h"
#include "node.h"
#include "uv.h"
#include "v8.h"
+#include <memory>
#include <string>
#include <list>
#include <unordered_map>
@@ -18,6 +21,13 @@ class Environment;
class SocketAddress : public MemoryRetainer {
public:
+ enum class CompareResult {
+ NOT_COMPARABLE = -2,
+ LESS_THAN,
+ SAME,
+ GREATER_THAN
+ };
+
struct Hash {
size_t operator()(const SocketAddress& addr) const;
};
@@ -25,6 +35,11 @@ class SocketAddress : public MemoryRetainer {
inline bool operator==(const SocketAddress& other) const;
inline bool operator!=(const SocketAddress& other) const;
+ inline bool operator<(const SocketAddress& other) const;
+ inline bool operator>(const SocketAddress& other) const;
+ inline bool operator<=(const SocketAddress& other) const;
+ inline bool operator>=(const SocketAddress& other) const;
+
inline static bool is_numeric_host(const char* hostname);
inline static bool is_numeric_host(const char* hostname, int family);
@@ -78,6 +93,20 @@ class SocketAddress : public MemoryRetainer {
inline std::string address() const;
inline int port() const;
+ // Returns true if the given other SocketAddress is a match
+ // for this one. The addresses are a match if:
+ // 1. They are the same family and match identically
+ // 2. They are different family but match semantically (
+ // for instance, an IPv4 addres in IPv6 notation)
+ bool is_match(const SocketAddress& other) const;
+
+ // Compares this SocketAddress to the given other SocketAddress.
+ CompareResult compare(const SocketAddress& other) const;
+
+ // Returns true if this SocketAddress is within the subnet
+ // identified by the given network address and CIDR prefix.
+ bool is_in_network(const SocketAddress& network, int prefix) const;
+
// If the SocketAddress is an IPv6 address, returns the
// current value of the IPv6 flow label, if set. Otherwise
// returns 0.
@@ -152,6 +181,123 @@ class SocketAddressLRU : public MemoryRetainer {
size_t max_size_;
};
+// A BlockList is used to evaluate whether a given
+// SocketAddress should be accepted for inbound or
+// outbound network activity.
+class SocketAddressBlockList : public MemoryRetainer {
+ public:
+ explicit SocketAddressBlockList(
+ std::shared_ptr<SocketAddressBlockList> parent = {});
+ ~SocketAddressBlockList() = default;
+
+ void AddSocketAddress(
+ const SocketAddress& address);
+
+ void RemoveSocketAddress(
+ const SocketAddress& address);
+
+ void AddSocketAddressRange(
+ const SocketAddress& start,
+ const SocketAddress& end);
+
+ void AddSocketAddressMask(
+ const SocketAddress& address,
+ int prefix);
+
+ bool Apply(const SocketAddress& address);
+
+ size_t size() const { return rules_.size(); }
+
+ v8::MaybeLocal<v8::Array> ListRules(Environment* env);
+
+ struct Rule : public MemoryRetainer {
+ virtual bool Apply(const SocketAddress& address) = 0;
+ inline v8::MaybeLocal<v8::Value> ToV8String(Environment* env);
+ virtual std::string ToString() = 0;
+ };
+
+ struct SocketAddressRule final : Rule {
+ SocketAddress address;
+
+ explicit SocketAddressRule(const SocketAddress& address);
+
+ bool Apply(const SocketAddress& address) override;
+ std::string ToString() override;
+
+ void MemoryInfo(node::MemoryTracker* tracker) const override;
+ SET_MEMORY_INFO_NAME(SocketAddressRule)
+ SET_SELF_SIZE(SocketAddressRule)
+ };
+
+ struct SocketAddressRangeRule final : Rule {
+ SocketAddress start;
+ SocketAddress end;
+
+ SocketAddressRangeRule(
+ const SocketAddress& start,
+ const SocketAddress& end);
+
+ bool Apply(const SocketAddress& address) override;
+ std::string ToString() override;
+
+ void MemoryInfo(node::MemoryTracker* tracker) const override;
+ SET_MEMORY_INFO_NAME(SocketAddressRangeRule)
+ SET_SELF_SIZE(SocketAddressRangeRule)
+ };
+
+ struct SocketAddressMaskRule final : Rule {
+ SocketAddress network;
+ int prefix;
+
+ SocketAddressMaskRule(
+ const SocketAddress& address,
+ int prefix);
+
+ bool Apply(const SocketAddress& address) override;
+ std::string ToString() override;
+
+ void MemoryInfo(node::MemoryTracker* tracker) const override;
+ SET_MEMORY_INFO_NAME(SocketAddressMaskRule)
+ SET_SELF_SIZE(SocketAddressMaskRule)
+ };
+
+ void MemoryInfo(node::MemoryTracker* tracker) const override;
+ SET_MEMORY_INFO_NAME(SocketAddressBlockList)
+ SET_SELF_SIZE(SocketAddressBlockList)
+
+ private:
+ std::shared_ptr<SocketAddressBlockList> parent_;
+ std::list<std::unique_ptr<Rule>> rules_;
+ SocketAddress::Map<std::list<std::unique_ptr<Rule>>::iterator> address_rules_;
+};
+
+class SocketAddressBlockListWrap :
+ public BaseObject,
+ public SocketAddressBlockList {
+ public:
+ static void Initialize(v8::Local<v8::Object> target,
+ v8::Local<v8::Value> unused,
+ v8::Local<v8::Context> context,
+ void* priv);
+
+ static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void AddAddress(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void AddRange(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void AddSubnet(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void Check(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void GetRules(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ SocketAddressBlockListWrap(
+ Environment* env,
+ v8::Local<v8::Object> wrap);
+
+ void MemoryInfo(node::MemoryTracker* tracker) const override {
+ SocketAddressBlockList::MemoryInfo(tracker);
+ }
+ SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
+ SET_SELF_SIZE(SocketAddressBlockListWrap)
+};
+
} // namespace node
#endif // NOE_WANT_INTERNALS
diff --git a/test/cctest/test_sockaddr.cc b/test/cctest/test_sockaddr.cc
index 9abcd8ba819..036dfae78a5 100644
--- a/test/cctest/test_sockaddr.cc
+++ b/test/cctest/test_sockaddr.cc
@@ -2,6 +2,7 @@
#include "gtest/gtest.h"
using node::SocketAddress;
+using node::SocketAddressBlockList;
using node::SocketAddressLRU;
TEST(SocketAddress, SocketAddress) {
@@ -84,6 +85,7 @@ TEST(SocketAddressLRU, SocketAddressLRU) {
SocketAddress::ToSockAddr(AF_INET, "123.123.123.125", 443, &storage[2]);
SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage[3]);
+
SocketAddress addr1(reinterpret_cast<const sockaddr*>(&storage[0]));
SocketAddress addr2(reinterpret_cast<const sockaddr*>(&storage[1]));
SocketAddress addr3(reinterpret_cast<const sockaddr*>(&storage[2]));
@@ -125,3 +127,87 @@ TEST(SocketAddressLRU, SocketAddressLRU) {
CHECK_NULL(lru.Peek(addr1));
CHECK_NULL(lru.Peek(addr2));
}
+
+TEST(SocketAddress, Comparison) {
+ sockaddr_storage storage[6];
+
+ SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &storage[0]);
+ SocketAddress::ToSockAddr(AF_INET, "10.0.0.2", 0, &storage[1]);
+ SocketAddress::ToSockAddr(AF_INET6, "::1", 0, &storage[2]);
+ SocketAddress::ToSockAddr(AF_INET6, "::2", 0, &storage[3]);
+ SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.1", 0, &storage[4]);
+ SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.2", 0, &storage[5]);
+
+ SocketAddress addr1(reinterpret_cast<const sockaddr*>(&storage[0]));
+ SocketAddress addr2(reinterpret_cast<const sockaddr*>(&storage[1]));
+ SocketAddress addr3(reinterpret_cast<const sockaddr*>(&storage[2]));
+ SocketAddress addr4(reinterpret_cast<const sockaddr*>(&storage[3]));
+ SocketAddress addr5(reinterpret_cast<const sockaddr*>(&storage[4]));
+ SocketAddress addr6(reinterpret_cast<const sockaddr*>(&storage[5]));
+
+ CHECK_EQ(addr1.compare(addr1), SocketAddress::CompareResult::SAME);
+ CHECK_EQ(addr1.compare(addr2), SocketAddress::CompareResult::LESS_THAN);
+ CHECK_EQ(addr2.compare(addr1), SocketAddress::CompareResult::GREATER_THAN);
+ CHECK(addr1 <= addr1);
+ CHECK(addr1 < addr2);
+ CHECK(addr1 <= addr2);
+ CHECK(addr2 >= addr2);
+ CHECK(addr2 > addr1);
+ CHECK(addr2 >= addr1);
+
+ CHECK_EQ(addr3.compare(addr3), SocketAddress::CompareResult::SAME);
+ CHECK_EQ(addr3.compare(addr4), SocketAddress::CompareResult::LESS_THAN);
+ CHECK_EQ(addr4.compare(addr3), SocketAddress::CompareResult::GREATER_THAN);
+ CHECK(addr3 <= addr3);
+ CHECK(addr3 < addr4);
+ CHECK(addr3 <= addr4);
+ CHECK(addr4 >= addr4);
+ CHECK(addr4 > addr3);
+ CHECK(addr4 >= addr3);
+
+ // Not comparable
+ CHECK_EQ(addr1.compare(addr3), SocketAddress::CompareResult::NOT_COMPARABLE);
+ CHECK_EQ(addr3.compare(addr1), SocketAddress::CompareResult::NOT_COMPARABLE);
+ CHECK(!(addr1 < addr3));
+ CHECK(!(addr1 > addr3));
+ CHECK(!(addr1 >= addr3));
+ CHECK(!(addr1 <= addr3));
+ CHECK(!(addr3 < addr1));
+ CHECK(!(addr3 > addr1));
+ CHECK(!(addr3 >= addr1));
+ CHECK(!(addr3 <= addr1));
+
+ // Comparable
+ CHECK_EQ(addr1.compare(addr5), SocketAddress::CompareResult::SAME);
+ CHECK_EQ(addr2.compare(addr6), SocketAddress::CompareResult::SAME);
+ CHECK_EQ(addr1.compare(addr6), SocketAddress::CompareResult::LESS_THAN);
+ CHECK_EQ(addr6.compare(addr1), SocketAddress::CompareResult::GREATER_THAN);
+ CHECK(addr1 <= addr5);
+ CHECK(addr1 <= addr6);
+ CHECK(addr1 < addr6);
+ CHECK(addr6 > addr1);
+ CHECK(addr6 >= addr1);
+ CHECK(addr2 >= addr6);
+ CHECK(addr2 >= addr5);
+}
+
+TEST(SocketAddressBlockList, Simple) {
+ SocketAddressBlockList bl;
+
+ sockaddr_storage storage[2];
+ SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &storage[0]);
+ SocketAddress::ToSockAddr(AF_INET, "10.0.0.2", 0, &storage[1]);
+ SocketAddress addr1(reinterpret_cast<const sockaddr*>(&storage[0]));
+ SocketAddress addr2(reinterpret_cast<const sockaddr*>(&storage[1]));
+
+ bl.AddSocketAddress(addr1);
+ bl.AddSocketAddress(addr2);
+
+ CHECK(bl.Apply(addr1));
+ CHECK(bl.Apply(addr2));
+
+ bl.RemoveSocketAddress(addr1);
+
+ CHECK(!bl.Apply(addr1));
+ CHECK(bl.Apply(addr2));
+}
diff --git a/test/parallel/test-blocklist.js b/test/parallel/test-blocklist.js
new file mode 100644
index 00000000000..cef90dc9a10
--- /dev/null
+++ b/test/parallel/test-blocklist.js
@@ -0,0 +1,136 @@
+'use strict';
+
+require('../common');
+
+const { BlockList } = require('net');
+const assert = require('assert');
+
+{
+ 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('1.1.1.2'));
+
+ 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();
+ 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();
+ blockList.addAddress('1.1.1.1');
+ blockList.addRange('10.0.0.1', '10.0.0.10');
+ blockList.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6');
+
+ 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);
+ console.log(blockList);
+
+ 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'));
+}