#ifndef SRC_NODE_SOCKADDR_H_
#define SRC_NODE_SOCKADDR_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "node.h"
#include "node_worker.h"
#include "uv.h"
#include "v8-fast-api-calls.h"
#include "v8.h"
#include <compare>
#include <list>
#include <memory>
#include <string>
#include <unordered_map>
namespace node {
class Environment;
class SocketAddress : public MemoryRetainer {
public:
struct Hash {
size_t operator()(const SocketAddress& addr) const;
};
// Compares what identifies a peer: family, port, address, and scope_id for
// IPv6. operator== memcmps the whole sockaddr, so it also distinguishes
// sin6_flowinfo, which is a QoS label a peer may vary between datagrams,
// and sin_zero, which is padding. Pair this with Hash where two datagrams
// from one peer have to reach one entry.
struct Equal {
bool operator()(const SocketAddress& a, const SocketAddress& b) const;
};
// Hashes and compares only the IP address, ignoring the port.
// Useful for per-host connection counting where clients from
// the same IP but different ports should be treated as one host.
struct IpHash {
size_t operator()(const SocketAddress& addr) const;
};
struct IpEqual {
bool operator()(const SocketAddress& a, const SocketAddress& b) const;
};
inline bool operator==(const SocketAddress& other) const;
inline bool operator!=(const SocketAddress& other) const;
inline std::partial_ordering 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);
// Returns true if converting {family, host, port} to *addr succeeded.
static bool ToSockAddr(int32_t family,
const char* host,
uint32_t port,
sockaddr_storage* addr);
// Returns true if converting {family, host, port} to *addr succeeded.
static bool New(int32_t family,
const char* host,
uint32_t port,
SocketAddress* addr);
static bool New(const char* host, uint32_t port, SocketAddress* addr);
// Returns the port for an IPv4 or IPv6 address.
inline static int GetPort(const sockaddr* addr);
inline static int GetPort(const sockaddr_storage* addr);
// Returns the numeric host as a string for an IPv4 or IPv6 address.
inline static std::string GetAddress(const sockaddr* addr);
inline static std::string GetAddress(const sockaddr_storage* addr);
// Returns the struct length for an IPv4, IPv6 or UNIX domain.
inline static size_t GetLength(const sockaddr* addr);
inline static size_t GetLength(const sockaddr_storage* addr);
SocketAddress() = default;
inline explicit SocketAddress(const sockaddr* addr);
inline SocketAddress(const SocketAddress& addr);
inline SocketAddress& operator=(const sockaddr* other);
inline SocketAddress& operator=(const SocketAddress& other);
inline const sockaddr& operator*() const;
inline const sockaddr* operator->() const;
inline const sockaddr* data() const;
inline const uint8_t* raw() const;
inline sockaddr* storage();
inline size_t length() const;
inline int family() const;
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 address in IPv6 notation)
bool is_match(const SocketAddress& other) const;
// Compares this SocketAddress to the given other SocketAddress.
std::partial_ordering 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.
inline uint32_t flow_label() const;
// If the SocketAddress is an IPv6 address, sets the
// current value of the IPv6 flow label. If not an
// IPv6 address, set_flow_label is a non-op. It
// is important to note that the flow label,
// while represented as an uint32_t, the flow
// label is strictly limited to 20 bits, and
// this will assert if any value larger than
// 20-bits is specified.
inline void set_flow_label(uint32_t label = 0);
inline void Update(uint8_t* data, size_t len);
inline void Update(const sockaddr* data, size_t len);
static SocketAddress FromSockName(const uv_udp_t& handle);
static SocketAddress FromSockName(const uv_tcp_t& handle);
static SocketAddress FromPeerName(const uv_udp_t& handle);
static SocketAddress FromPeerName(const uv_tcp_t& handle);
inline v8::MaybeLocal<v8::Object> ToJS(
Environment* env,
v8::Local<v8::Object> obj = v8::Local<v8::Object>()) const;
inline std::string ToString() const;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(SocketAddress)
SET_SELF_SIZE(SocketAddress)
template <typename T>
using Map = std::unordered_map<SocketAddress, T, Hash>;
// As Map, but keyed on what identifies a peer rather than on every byte of
// the sockaddr. Separate from Map because Map is what SocketAddressLRU
// uses, and QUIC's address validation counts entries in one of those.
template <typename T>
using PeerMap = std::unordered_map<SocketAddress, T, Hash, Equal>;
template <typename T>
using IpMap = std::unordered_map<SocketAddress, T, IpHash, IpEqual>;
private:
sockaddr_storage address_;
};
class SocketAddressBase : public BaseObject {
public:
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static BaseObjectPtr<SocketAddressBase> Create(
Environment* env, std::shared_ptr<SocketAddress> address);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Detail(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LegacyDetail(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetFlowLabel(const v8::FunctionCallbackInfo<v8::Value>& args);
SocketAddressBase(Environment* env,
v8::Local<v8::Object> wrap,
std::shared_ptr<SocketAddress> address);
inline const std::shared_ptr<SocketAddress>& address() const {
return address_;
}
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SocketAddressBase)
SET_SELF_SIZE(SocketAddressBase)
BaseObject::TransferMode GetTransferMode() const override {
return TransferMode::kCloneable;
}
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
class TransferData : public worker::TransferData {
public:
inline explicit TransferData(const SocketAddressBase* wrap)
: address_(wrap->address_) {}
inline explicit TransferData(std::shared_ptr<SocketAddress> address)
: address_(std::move(address)) {}
BaseObjectPtr<BaseObject> Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) override;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SocketAddressBase::TransferData)
SET_SELF_SIZE(TransferData)
private:
std::shared_ptr<SocketAddress> address_;
};
private:
std::shared_ptr<SocketAddress> address_;
};
template <typename T>
class SocketAddressLRU : public MemoryRetainer {
public:
using Type = typename T::Type;
inline explicit SocketAddressLRU(size_t max_size);
// If the item already exists, returns a reference to
// the existing item, adjusting items position in the
// LRU. If the item does not exist, emplaces the item
// and returns the new item. The caller provides a
// timestamp to avoid redundant uv_hrtime() calls.
Type* Upsert(const SocketAddress& address, uint64_t now);
// Returns a reference to the item if it exists, or
// nullptr. The position in the LRU is not modified.
Type* Peek(const SocketAddress& address) const;
size_t size() const { return map_.size(); }
size_t max_size() const { return max_size_; }
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SocketAddressLRU)
SET_SELF_SIZE(SocketAddressLRU)
private:
using Pair = std::pair<SocketAddress, Type>;
using Iterator = typename std::list<Pair>::iterator;
void CheckExpired(uint64_t now);
std::list<Pair> list_;
SocketAddress::Map<Iterator> map_;
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 AddSocketAddresses(const SocketAddress* addresses, size_t count);
void RemoveSocketAddress(const SocketAddress& address);
void AddSocketAddressRange(const SocketAddress& start,
const SocketAddress& end);
void RemoveSocketAddressRange(const SocketAddress& start,
const SocketAddress& end);
void AddSocketAddressMask(const SocketAddress& address, int prefix);
void RemoveSocketAddressMask(const SocketAddress& address, int prefix);
bool Apply(const SocketAddress& address);
void Clear();
size_t size() const {
return address_count_ + rules_.size() + subnet_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 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)
// A compressed radix trie for O(prefix_length) subnet lookups.
// Each node has two children (bit 0, bit 1). A node marked
// terminal means all addresses matching the prefix up to that
// depth are blocked. On insert, if a new prefix is shorter than
// or equal to an existing one, the subtree is pruned (the shorter
// prefix subsumes all longer ones). On lookup, we walk the bits
// of the address and return true as soon as we hit a terminal node.
class SubnetTrie {
public:
SubnetTrie() = default;
~SubnetTrie() = default;
// Insert a subnet (network address bytes, prefix length in bits).
// If a broader prefix already exists, the insert is a no-op.
// If this prefix is broader than existing children, they are pruned.
void Insert(const uint8_t* address_bytes, int prefix_length);
// Returns true if the given address falls within any inserted subnet.
bool Lookup(const uint8_t* address_bytes, int address_bits) const;
// Remove all entries.
void Clear();
bool empty() const { return root_ == nullptr; }
size_t size() const { return count_; }
private:
struct Node {
std::unique_ptr<Node> children[2];
bool terminal = false;
};
std::unique_ptr<Node> root_;
size_t count_ = 0;
};
private:
// Lock-free implementation used by both AddSocketAddress and
// AddSocketAddresses. Caller must hold the write lock.
void AddSocketAddressImpl(const SocketAddress& address);
bool ListRules(Environment* env, v8::LocalVector<v8::Value>* vec);
std::shared_ptr<SocketAddressBlockList> parent_;
// Range rules only. Scanned linearly by Apply().
std::list<std::unique_ptr<Rule>> rules_;
// Exact address rules. Keyed by IP only (port-insensitive) so that
// Apply() can perform O(1) lookups regardless of the port on the
// checked address. Not included in rules_ to avoid redundant scanning.
SocketAddress::IpMap<SocketAddress> address_rules_;
// User-visible address count (not inflated by cross-family dual-insert).
size_t address_count_ = 0;
// Subnet/mask rules stored in radix tries for O(prefix_length) lookup.
// Separate tries for IPv4 (max 32-bit depth) and IPv6 (max 128-bit).
SubnetTrie ipv4_subnets_;
SubnetTrie ipv6_subnets_;
// Subnet metadata kept for ListRules serialization only.
std::list<std::unique_ptr<SocketAddressMaskRule>> subnet_rules_;
// RwLock allows concurrent Apply() calls (shared/read lock) while
// mutations (Add*/Remove*/Clear) take an exclusive/write lock.
mutable RwLock mutex_;
};
class SocketAddressBlockListWrap : public BaseObject {
public:
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static void Initialize(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv);
static BaseObjectPtr<SocketAddressBlockListWrap> New(Environment* env);
static BaseObjectPtr<SocketAddressBlockListWrap> New(
Environment* env, std::shared_ptr<SocketAddressBlockList> blocklist);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddAddress(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddAddresses(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 RemoveAddress(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RemoveRange(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RemoveSubnet(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Check(const v8::FunctionCallbackInfo<v8::Value>& args);
static bool FastCheck(v8::Local<v8::Object> receiver,
v8::Local<v8::Object> addr_obj);
static void CheckString(const v8::FunctionCallbackInfo<v8::Value>& args);
static bool FastCheckString(
v8::Local<v8::Object> receiver,
const v8::FastOneByteString& address,
int32_t family,
// NOLINTNEXTLINE(runtime/references) This is V8 api.
v8::FastApiCallbackOptions& options);
static void GetRules(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetSize(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Clear(const v8::FunctionCallbackInfo<v8::Value>& args);
SocketAddressBlockListWrap(Environment* env,
v8::Local<v8::Object> wrap,
std::shared_ptr<SocketAddressBlockList> blocklist =
std::make_shared<SocketAddressBlockList>());
inline const std::shared_ptr<SocketAddressBlockList>& blocklist() const {
return blocklist_;
}
void MemoryInfo(node::MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
SET_SELF_SIZE(SocketAddressBlockListWrap)
BaseObject::TransferMode GetTransferMode() const override {
return TransferMode::kCloneable;
}
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
class TransferData : public worker::TransferData {
public:
inline explicit TransferData(const SocketAddressBlockListWrap* wrap)
: blocklist_(wrap->blocklist_) {}
inline explicit TransferData(
std::shared_ptr<SocketAddressBlockList> blocklist)
: blocklist_(std::move(blocklist)) {}
BaseObjectPtr<BaseObject> Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) override;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap::TransferData)
SET_SELF_SIZE(TransferData)
private:
std::shared_ptr<SocketAddressBlockList> blocklist_;
};
private:
std::shared_ptr<SocketAddressBlockList> blocklist_;
static v8::CFunction fast_check_;
static v8::CFunction fast_check_string_;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_SOCKADDR_H_