diff --git a/node.gyp b/node.gyp index daca13cba2be6a..a6d1a27def5dab 100644 --- a/node.gyp +++ b/node.gyp @@ -596,6 +596,7 @@ 'src/node_process_methods.cc', 'src/node_process_object.cc', 'src/node_serdes.cc', + 'src/node_sockaddr.cc', 'src/node_stat_watcher.cc', 'src/node_symbols.cc', 'src/node_task_queue.cc', @@ -686,6 +687,8 @@ 'src/node_process.h', 'src/node_revert.h', 'src/node_root_certs.h', + 'src/node_sockaddr.h', + 'src/node_sockaddr-inl.h', 'src/node_stat_watcher.h', 'src/node_union_bytes.h', 'src/node_url.h', @@ -1154,6 +1157,7 @@ 'test/cctest/test_linked_binding.cc', 'test/cctest/test_per_process.cc', 'test/cctest/test_platform.cc', + 'test/cctest/test_sockaddr.cc', 'test/cctest/test_traced_value.cc', 'test/cctest/test_util.cc', 'test/cctest/test_url.cc', diff --git a/src/node_sockaddr-inl.h b/src/node_sockaddr-inl.h new file mode 100644 index 00000000000000..a9d0ed061a126c --- /dev/null +++ b/src/node_sockaddr-inl.h @@ -0,0 +1,170 @@ +#ifndef SRC_NODE_SOCKADDR_INL_H_ +#define SRC_NODE_SOCKADDR_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include "node_internals.h" +#include "node_sockaddr.h" +#include "util-inl.h" + +#include + +namespace node { + +static constexpr uint32_t kLabelMask = 0xFFFFF; + +inline void hash_combine(size_t* seed) { } + +template +inline void hash_combine(size_t* seed, const T& value, Args... rest) { + *seed ^= std::hash{}(value) + 0x9e3779b9 + (*seed << 6) + (*seed >> 2); + hash_combine(seed, rest...); +} + +bool SocketAddress::is_numeric_host(const char* hostname) { + return is_numeric_host(hostname, AF_INET) || + is_numeric_host(hostname, AF_INET6); +} + +bool SocketAddress::is_numeric_host(const char* hostname, int family) { + in6_addr dst; + return inet_pton(family, hostname, &dst) == 1; +} + +int SocketAddress::GetPort(const sockaddr* addr) { + CHECK(addr->sa_family == AF_INET || addr->sa_family == AF_INET6); + return ntohs(addr->sa_family == AF_INET ? + reinterpret_cast(addr)->sin_port : + reinterpret_cast(addr)->sin6_port); +} + +int SocketAddress::GetPort(const sockaddr_storage* addr) { + return GetPort(reinterpret_cast(addr)); +} + +std::string SocketAddress::GetAddress(const sockaddr* addr) { + CHECK(addr->sa_family == AF_INET || addr->sa_family == AF_INET6); + char host[INET6_ADDRSTRLEN]; + const void* src = addr->sa_family == AF_INET ? + static_cast( + &(reinterpret_cast(addr)->sin_addr)) : + static_cast( + &(reinterpret_cast(addr)->sin6_addr)); + uv_inet_ntop(addr->sa_family, src, host, INET6_ADDRSTRLEN); + return std::string(host); +} + +std::string SocketAddress::GetAddress(const sockaddr_storage* addr) { + return GetAddress(reinterpret_cast(addr)); +} + +size_t SocketAddress::GetLength(const sockaddr* addr) { + return addr->sa_family == AF_INET ? + sizeof(sockaddr_in) : sizeof(sockaddr_in6); +} + +size_t SocketAddress::GetLength(const sockaddr_storage* addr) { + return GetLength(reinterpret_cast(addr)); +} + +SocketAddress::SocketAddress(const sockaddr* addr) { + memcpy(&address_, addr, GetLength(addr)); +} + +SocketAddress::SocketAddress(const SocketAddress& addr) { + memcpy(&address_, &addr.address_, addr.length()); +} + +SocketAddress& SocketAddress::operator=(const sockaddr* addr) { + memcpy(&address_, addr, GetLength(addr)); + return *this; +} + +SocketAddress& SocketAddress::operator=(const SocketAddress& addr) { + memcpy(&address_, &addr.address_, addr.length()); + return *this; +} + +const sockaddr& SocketAddress::operator*() const { + return *this->data(); +} + +const sockaddr* SocketAddress::operator->() const { + return this->data(); +} + +size_t SocketAddress::length() const { + return GetLength(&address_); +} + +const sockaddr* SocketAddress::data() const { + return reinterpret_cast(&address_); +} + +const uint8_t* SocketAddress::raw() const { + return reinterpret_cast(&address_); +} + +sockaddr* SocketAddress::storage() { + return reinterpret_cast(&address_); +} + +int SocketAddress::family() const { + return address_.ss_family; +} + +std::string SocketAddress::address() const { + return GetAddress(&address_); +} + +int SocketAddress::port() const { + return GetPort(&address_); +} + +uint32_t SocketAddress::flow_label() const { + if (family() != AF_INET6) + return 0; + const sockaddr_in6* in = reinterpret_cast(data()); + return in->sin6_flowinfo; +} + +void SocketAddress::set_flow_label(uint32_t label) { + if (family() != AF_INET6) + return; + CHECK_LE(label, kLabelMask); + sockaddr_in6* in = reinterpret_cast(&address_); + in->sin6_flowinfo = label; +} + +std::string SocketAddress::ToString() const { + if (family() != AF_INET && family() != AF_INET6) return ""; + return (family() == AF_INET6 ? + std::string("[") + address() + "]:" : + address() + ":") + + std::to_string(port()); +} + +void SocketAddress::Update(uint8_t* data, size_t len) { + CHECK_LE(len, sizeof(address_)); + memcpy(&address_, data, len); +} + +v8::Local SocketAddress::ToJS( + Environment* env, + v8::Local info) const { + return AddressToJS(env, data(), info); +} + +bool SocketAddress::operator==(const SocketAddress& other) const { + if (family() != other.family()) return false; + return memcmp(raw(), other.raw(), length()) == 0; +} + +bool SocketAddress::operator!=(const SocketAddress& other) const { + return !(*this == other); +} +} // namespace node + +#endif // NODE_WANT_INTERNALS +#endif // SRC_NODE_SOCKADDR_INL_H_ diff --git a/src/node_sockaddr.cc b/src/node_sockaddr.cc new file mode 100644 index 00000000000000..74fe123529abbd --- /dev/null +++ b/src/node_sockaddr.cc @@ -0,0 +1,95 @@ +#include "node_sockaddr-inl.h" // NOLINT(build/include) +#include "uv.h" + +namespace node { + +namespace { +template +SocketAddress FromUVHandle(F fn, const T& handle) { + SocketAddress addr; + int len = sizeof(sockaddr_storage); + if (fn(&handle, addr.storage(), &len) == 0) + CHECK_EQ(static_cast(len), addr.length()); + else + addr.storage()->sa_family = 0; + return addr; +} +} // namespace + +bool SocketAddress::ToSockAddr( + int32_t family, + const char* host, + uint32_t port, + sockaddr_storage* addr) { + switch (family) { + case AF_INET: + return uv_ip4_addr( + host, + port, + reinterpret_cast(addr)) == 0; + case AF_INET6: + return uv_ip6_addr( + host, + port, + reinterpret_cast(addr)) == 0; + default: + UNREACHABLE(); + } +} + +bool SocketAddress::New( + const char* host, + uint32_t port, + SocketAddress* addr) { + return New(AF_INET, host, port, addr) || New(AF_INET6, host, port, addr); +} + +bool SocketAddress::New( + int32_t family, + const char* host, + uint32_t port, + SocketAddress* addr) { + return ToSockAddr(family, host, port, + reinterpret_cast(addr->storage())); +} + +size_t SocketAddress::Hash::operator()(const SocketAddress& addr) const { + size_t hash = 0; + switch (addr.family()) { + case AF_INET: { + const sockaddr_in* ipv4 = + reinterpret_cast(addr.raw()); + hash_combine(&hash, ipv4->sin_port, ipv4->sin_addr.s_addr); + break; + } + case AF_INET6: { + const sockaddr_in6* ipv6 = + reinterpret_cast(addr.raw()); + const uint64_t* a = + reinterpret_cast(&ipv6->sin6_addr); + hash_combine(&hash, ipv6->sin6_port, a[0], a[1]); + break; + } + default: + UNREACHABLE(); + } + return hash; +} + +SocketAddress SocketAddress::FromSockName(const uv_tcp_t& handle) { + return FromUVHandle(uv_tcp_getsockname, handle); +} + +SocketAddress SocketAddress::FromSockName(const uv_udp_t& handle) { + return FromUVHandle(uv_udp_getsockname, handle); +} + +SocketAddress SocketAddress::FromPeerName(const uv_tcp_t& handle) { + return FromUVHandle(uv_tcp_getpeername, handle); +} + +SocketAddress SocketAddress::FromPeerName(const uv_udp_t& handle) { + return FromUVHandle(uv_udp_getpeername, handle); +} + +} // namespace node diff --git a/src/node_sockaddr.h b/src/node_sockaddr.h new file mode 100644 index 00000000000000..2e3ae09ce3bb8d --- /dev/null +++ b/src/node_sockaddr.h @@ -0,0 +1,122 @@ +#ifndef SRC_NODE_SOCKADDR_H_ +#define SRC_NODE_SOCKADDR_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "env.h" +#include "memory_tracker.h" +#include "node.h" +#include "uv.h" +#include "v8.h" + +#include +#include + +namespace node { + +class SocketAddress : public MemoryRetainer { + public: + struct Hash { + size_t operator()(const SocketAddress& addr) 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); + + // 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; + + // 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); + + 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::Local ToJS( + Environment* env, + v8::Local obj = v8::Local()) const; + + inline std::string ToString() const; + + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SocketAddress) + SET_SELF_SIZE(SocketAddress) + + template + using Map = std::unordered_map; + + private: + sockaddr_storage address_; +}; + +} // namespace node + +#endif // NOE_WANT_INTERNALS + +#endif // SRC_NODE_SOCKADDR_H_ diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index d714cfa902d5f6..770782ee9cf38e 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -22,6 +22,7 @@ #include "udp_wrap.h" #include "env-inl.h" #include "node_buffer.h" +#include "node_sockaddr-inl.h" #include "handle_wrap.h" #include "req_wrap-inl.h" #include "util-inl.h" @@ -629,12 +630,12 @@ AsyncWrap* UDPWrap::GetAsyncWrap() { return this; } -int UDPWrap::GetPeerName(sockaddr* name, int* namelen) { - return uv_udp_getpeername(&handle_, name, namelen); +SocketAddress UDPWrap::GetPeerName() { + return SocketAddress::FromPeerName(handle_); } -int UDPWrap::GetSockName(sockaddr* name, int* namelen) { - return uv_udp_getsockname(&handle_, name, namelen); +SocketAddress UDPWrap::GetSockName() { + return SocketAddress::FromSockName(handle_); } void UDPWrapBase::RecvStart(const FunctionCallbackInfo& args) { diff --git a/src/udp_wrap.h b/src/udp_wrap.h index 7afd9784b0ac9e..6fed1d2dfea810 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -26,6 +26,7 @@ #include "handle_wrap.h" #include "req_wrap.h" +#include "node_sockaddr.h" #include "uv.h" #include "v8.h" @@ -95,11 +96,8 @@ class UDPWrapBase { size_t nbufs, const sockaddr* addr) = 0; - // Stores the sockaddr for the peer in `name`. - virtual int GetPeerName(sockaddr* name, int* namelen) = 0; - - // Stores the sockaddr for the local socket in `name`. - virtual int GetSockName(sockaddr* name, int* namelen) = 0; + virtual SocketAddress GetPeerName() = 0; + virtual SocketAddress GetSockName() = 0; // Returns an AsyncWrap object with the same lifetime as this object. virtual AsyncWrap* GetAsyncWrap() = 0; @@ -168,8 +166,10 @@ class UDPWrap final : public HandleWrap, ssize_t Send(uv_buf_t* bufs, size_t nbufs, const sockaddr* addr) override; - int GetPeerName(sockaddr* name, int* namelen) override; - int GetSockName(sockaddr* name, int* namelen) override; + + SocketAddress GetPeerName() override; + SocketAddress GetSockName() override; + AsyncWrap* GetAsyncWrap() override; static v8::MaybeLocal Instantiate(Environment* env, diff --git a/test/cctest/test_sockaddr.cc b/test/cctest/test_sockaddr.cc new file mode 100644 index 00000000000000..8c23463f11d2d1 --- /dev/null +++ b/test/cctest/test_sockaddr.cc @@ -0,0 +1,57 @@ +#include "node_sockaddr-inl.h" +#include "gtest/gtest.h" + +using node::SocketAddress; + +TEST(SocketAddress, SocketAddress) { + CHECK(SocketAddress::is_numeric_host("123.123.123.123")); + CHECK(!SocketAddress::is_numeric_host("localhost")); + + sockaddr_storage storage; + sockaddr_storage storage2; + SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage); + SocketAddress::ToSockAddr(AF_INET, "1.1.1.1", 80, &storage2); + + SocketAddress addr(reinterpret_cast(&storage)); + SocketAddress addr2(reinterpret_cast(&storage2)); + + CHECK_EQ(addr.length(), sizeof(sockaddr_in)); + CHECK_EQ(addr.family(), AF_INET); + CHECK_EQ(addr.address(), "123.123.123.123"); + CHECK_EQ(addr.port(), 443); + + addr.set_flow_label(12345); + CHECK_EQ(addr.flow_label(), 0); + + CHECK_NE(addr, addr2); + CHECK_EQ(addr, addr); + + CHECK_EQ(SocketAddress::Hash()(addr), SocketAddress::Hash()(addr)); + CHECK_NE(SocketAddress::Hash()(addr), SocketAddress::Hash()(addr2)); + + addr.Update(reinterpret_cast(&storage2), sizeof(sockaddr_in)); + CHECK_EQ(addr.length(), sizeof(sockaddr_in)); + CHECK_EQ(addr.family(), AF_INET); + CHECK_EQ(addr.address(), "1.1.1.1"); + CHECK_EQ(addr.port(), 80); + + SocketAddress::Map map; + map[addr]++; + map[addr]++; + CHECK_EQ(map[addr], 2); +} + +TEST(SocketAddress, SocketAddressIPv6) { + sockaddr_storage storage; + SocketAddress::ToSockAddr(AF_INET6, "::1", 443, &storage); + + SocketAddress addr(reinterpret_cast(&storage)); + + CHECK_EQ(addr.length(), sizeof(sockaddr_in6)); + CHECK_EQ(addr.family(), AF_INET6); + CHECK_EQ(addr.address(), "::1"); + CHECK_EQ(addr.port(), 443); + + addr.set_flow_label(12345); + CHECK_EQ(addr.flow_label(), 12345); +}