From 25d2ebf8fc7677b2b4f65390a9169cc94a2eeb37 Mon Sep 17 00:00:00 2001 From: Yat Ho Date: Sat, 22 Nov 2025 08:09:38 +0800 Subject: [PATCH] refactor: overhaul `tr_address` special address checks (#7818) * refactor: rewrite is_martian_addr() with tr_address methods - Fix broken check for IPv4 multicast address in is_martian_address() * refactor: rewrite is_global_unicast_address() - Rewrite using new tr_address methods - Add missing IPv4 loopback check - Follow RFC 4291 IPv6 global unicast definition - Fix and update existing tests * chore: reorganise methods and add comments * fix: check for teredo and 6to4 * test: tests for new methods --- libtransmission/announcer-udp.cc | 2 +- libtransmission/ip-cache.cc | 4 +- libtransmission/net.cc | 194 ++----- libtransmission/net.h | 156 +++++- libtransmission/peer-msgs.cc | 8 +- libtransmission/session.cc | 3 +- libtransmission/tr-udp.cc | 2 +- tests/libtransmission/ip-cache-test.cc | 22 +- tests/libtransmission/net-test.cc | 722 ++++++++++++++++++++++++- 9 files changed, 920 insertions(+), 193 deletions(-) diff --git a/libtransmission/announcer-udp.cc b/libtransmission/announcer-udp.cc index 61ef7b570..183294442 100644 --- a/libtransmission/announcer-udp.cc +++ b/libtransmission/announcer-udp.cc @@ -551,7 +551,7 @@ private: // N.B. getaddrinfo() will return IPv4-mapped addresses by default on macOS auto socket_address = tr_socket_address::from_sockaddr(info->ai_addr); - if (!socket_address || socket_address->address().is_ipv4_mapped_address()) + if (!socket_address || socket_address->address().is_ipv6_ipv4_mapped()) { logdbg( log_name(), diff --git a/libtransmission/ip-cache.cc b/libtransmission/ip-cache.cc index 3c0f3d27d..9ca9176ee 100644 --- a/libtransmission/ip-cache.cc +++ b/libtransmission/ip-cache.cc @@ -125,7 +125,7 @@ namespace global_source_ip_helpers // In order for address selection to work right, // this should be a global unicast address, not Teredo or 6to4 - TR_ASSERT(dst_addr && dst_addr->is_global_unicast_address()); + TR_ASSERT(dst_addr && dst_addr->is_global_unicast() && !dst_addr->is_ipv6_teredo() && !dst_addr->is_ipv6_6to4()); if (dst_addr) { @@ -214,7 +214,7 @@ tr_address tr_ip_cache::bind_addr(tr_address_type type) const noexcept bool tr_ip_cache::set_global_addr(tr_address const& addr_new) noexcept { - if (addr_new.is_global_unicast_address()) + if (addr_new.is_global_unicast()) { auto const lock = std::scoped_lock{ global_addr_mutex_[addr_new.type] }; if (auto& addr = global_addr_[addr_new.type]; addr != addr_new) diff --git a/libtransmission/net.cc b/libtransmission/net.cc index 9c8faa984..00f70f9a7 100644 --- a/libtransmission/net.cc +++ b/libtransmission/net.cc @@ -447,32 +447,10 @@ namespace is_valid_for_peers_helpers and is covered under the same license as third-party/dht/dht.c. */ [[nodiscard]] auto is_martian_addr(tr_address const& addr, tr_peer_from from) { - static auto constexpr Zeroes = std::array{}; auto const loopback_allowed = from == TR_PEER_FROM_INCOMING || from == TR_PEER_FROM_LPD || from == TR_PEER_FROM_RESUME; - - switch (addr.type) - { - case TR_AF_INET: - { - auto const* const address = reinterpret_cast(&addr.addr.addr4); - return address[0] == 0 || // 0.x.x.x - (!loopback_allowed && address[0] == 127) || // 127.x.x.x - (address[0] & 0xE0) == 0xE0; // multicast address - } - - case TR_AF_INET6: - { - auto const* const address = reinterpret_cast(&addr.addr.addr6); - return address[0] == 0xFF || // multicast address - (std::memcmp(address, std::data(Zeroes), 15) == 0 && - (address[15] == 0 || // :: - (!loopback_allowed && address[15] == 1)) // ::1 - ); - } - - default: - return true; - } + return addr.is_ipv4_current_network() || addr.is_ipv6_unspecified() || + (!loopback_allowed && (addr.is_ipv4_loopback() || addr.is_ipv6_loopback())) || addr.is_ipv4_multicast() || + addr.is_ipv6_multicast(); } } // namespace is_valid_for_peers_helpers @@ -670,143 +648,43 @@ int tr_address::compare(tr_address const& that) const noexcept // <=> } // https://en.wikipedia.org/wiki/Reserved_IP_addresses -[[nodiscard]] bool tr_address::is_global_unicast_address() const noexcept +// +// https://www.rfc-editor.org/rfc/rfc4291.html#section-2.4 +// address type Binary prefix IPv6 notation Section +// ------------ ------------- ------------- ------- +// Unspecified 00...0 (128 bits) ::/128 2.5.2 +// Loopback 00...1 (128 bits) ::1/128 2.5.3 +// Multicast 11111111 FF00::/8 2.7 +// Link-Local unicast 1111111010 FE80::/10 2.5.6 +// Global Unicast (everything else) +[[nodiscard]] bool tr_address::is_global_unicast() const noexcept { - if (is_ipv4()) - { - auto const* const a = reinterpret_cast(&addr.addr4.s_addr); - - // [0.0.0.0–0.255.255.255] - // Current network. - if (a[0] == 0) - { - return false; - } - - // [10.0.0.0 – 10.255.255.255] - // Used for local communications within a private network. - if (a[0] == 10) - { - return false; - } - - // [100.64.0.0–100.127.255.255] - // Shared address space for communications between a service provider - // and its subscribers when using a carrier-grade NAT. - if ((a[0] == 100) && (64 <= a[1] && a[1] <= 127)) - { - return false; - } - - // [169.254.0.0–169.254.255.255] - // Used for link-local addresses[5] between two hosts on a single link - // when no IP address is otherwise specified, such as would have - // normally been retrieved from a DHCP server. - if (a[0] == 169 && a[1] == 254) - { - return false; - } - - // [172.16.0.0–172.31.255.255] - // Used for local communications within a private network. - if ((a[0] == 172) && (16 <= a[1] && a[1] <= 31)) - { - return false; - } - - // [192.0.0.0–192.0.0.255] - // IETF Protocol Assignments. - if (a[0] == 192 && a[1] == 0 && a[2] == 0) - { - return false; - } - - // [192.0.2.0–192.0.2.255] - // Assigned as TEST-NET-1, documentation and examples. - if (a[0] == 192 && a[1] == 0 && a[2] == 2) - { - return false; - } - - // [192.88.99.0–192.88.99.255] - // Reserved. Formerly used for IPv6 to IPv4 relay. - if (a[0] == 192 && a[1] == 88 && a[2] == 99) - { - return false; - } - - // [192.168.0.0–192.168.255.255] - // Used for local communications within a private network. - if (a[0] == 192 && a[1] == 168) - { - return false; - } - - // [198.18.0.0–198.19.255.255] - // Used for benchmark testing of inter-network communications - // between two separate subnets. - if (a[0] == 198 && (18 <= a[1] && a[1] <= 19)) - { - return false; - } - - // [198.51.100.0–198.51.100.255] - // Assigned as TEST-NET-2, documentation and examples. - if (a[0] == 198 && a[1] == 51 && a[2] == 100) - { - return false; - } - - // [203.0.113.0–203.0.113.255] - // Assigned as TEST-NET-3, documentation and examples. - if (a[0] == 203 && a[1] == 0 && a[2] == 113) - { - return false; - } - - // [224.0.0.0–239.255.255.255] - // In use for IP multicast. (Former Class D network.) - if (224 <= a[0] && a[0] <= 230) - { - return false; - } - - // [233.252.0.0-233.252.0.255] - // Assigned as MCAST-TEST-NET, documentation and examples. - if (a[0] == 233 && a[1] == 252 && a[2] == 0) - { - return false; - } - - // [240.0.0.0–255.255.255.254] - // Reserved for future use. (Former Class E network.) - // [255.255.255.255] - // Reserved for the "limited broadcast" destination address. - if (240 <= a[0]) - { - return false; - } - - return true; - } - - if (is_ipv6()) - { - auto const* const a = addr.addr6.s6_addr; - - // TODO: 2000::/3 is commonly used for global unicast but technically - // other spaces would be allowable too, so we should test those here. - // See RFC 4291 in the Section 2.4 listing global unicast as everything - // that's not link-local, multicast, loopback, or unspecified. - return (a[0] & 0xE0) == 0x20; - } - - return false; + return !is_ipv4_current_network() && // + !is_ipv4_10_private() && // + !is_ipv4_carrier_grade_nat() && // + !is_ipv4_loopback() && // + !is_ipv4_link_local() && // + !is_ipv4_172_private() && // + !is_ipv4_ietf_protocol_assignment() && // + !is_ipv4_test_net_1() && // + !is_ipv4_6to4_relay() && // + !is_ipv4_192_168_private() && // + !is_ipv4_benchmark() && // + !is_ipv4_test_net_2() && // + !is_ipv4_test_net_3() && // + !is_ipv4_multicast() && // + !is_ipv4_mcast_test_net() && // + !is_ipv4_reserved_class_e() && // + !is_ipv4_limited_broadcast() && // + !is_ipv6_unspecified() && // + !is_ipv6_loopback() && // + !is_ipv6_multicast() && // + !is_ipv6_link_local(); } std::optional tr_address::from_ipv4_mapped() const noexcept { - if (!is_ipv4_mapped_address()) + if (!is_ipv6_ipv4_mapped()) { return {}; } @@ -825,7 +703,7 @@ bool tr_socket_address::is_valid_for_peers(tr_peer_from from) const noexcept { using namespace is_valid_for_peers_helpers; - return is_valid() && !std::empty(port_) && !address_.is_ipv6_link_local_address() && !address_.is_ipv4_mapped_address() && + return is_valid() && !std::empty(port_) && !address_.is_ipv6_link_local() && !address_.is_ipv6_ipv4_mapped() && !is_martian_addr(address_, from); } diff --git a/libtransmission/net.h b/libtransmission/net.h index fd6d68d8d..aae949ca0 100644 --- a/libtransmission/net.h +++ b/libtransmission/net.h @@ -245,28 +245,170 @@ struct tr_address // --- - [[nodiscard]] bool is_global_unicast_address() const noexcept; + [[nodiscard]] bool is_global_unicast() const noexcept; - [[nodiscard]] constexpr bool is_ipv4_mapped_address() const noexcept + // 0.0.0.0/8 + [[nodiscard]] constexpr bool is_ipv4_current_network() const noexcept { - return is_ipv6() && IN6_IS_ADDR_V4MAPPED(&addr.addr6); + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 0U; } - [[nodiscard]] constexpr bool is_ipv6_link_local_address() const noexcept + // 10.0.0.0/8 + [[nodiscard]] constexpr bool is_ipv4_10_private() const noexcept { - return is_ipv6() && IN6_IS_ADDR_LINKLOCAL(&addr.addr6); + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 10U; } - [[nodiscard]] constexpr bool is_ipv4_loopback_address() const noexcept + // 100.64.0.0/10 + [[nodiscard]] constexpr bool is_ipv4_carrier_grade_nat() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 100U && + (reinterpret_cast(&addr.addr4.s_addr)[1] & 0xC0U) == 64U; + } + + // 127.0.0.0/8 + [[nodiscard]] constexpr bool is_ipv4_loopback() const noexcept { return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 127U; } - [[nodiscard]] constexpr bool is_ipv6_loopback_address() const noexcept + // 169.254.0.0/16 + [[nodiscard]] constexpr bool is_ipv4_link_local() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 169U && + reinterpret_cast(&addr.addr4.s_addr)[1] == 254U; + } + + // 172.16.0.0/12 + [[nodiscard]] constexpr bool is_ipv4_172_private() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 172U && + (reinterpret_cast(&addr.addr4.s_addr)[1] & 0xF0U) == 16U; + } + + // 192.0.0.0/24 + [[nodiscard]] constexpr bool is_ipv4_ietf_protocol_assignment() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 192U && + reinterpret_cast(&addr.addr4.s_addr)[1] == 0U && + reinterpret_cast(&addr.addr4.s_addr)[2] == 0U; + } + + // 192.0.2.0/24 + [[nodiscard]] constexpr bool is_ipv4_test_net_1() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 192U && + reinterpret_cast(&addr.addr4.s_addr)[1] == 0U && + reinterpret_cast(&addr.addr4.s_addr)[2] == 2U; + } + + // 192.88.99.0/24 + [[nodiscard]] constexpr bool is_ipv4_6to4_relay() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 192U && + reinterpret_cast(&addr.addr4.s_addr)[1] == 88U && + reinterpret_cast(&addr.addr4.s_addr)[2] == 99U; + } + + // 192.168.0.0/16 + [[nodiscard]] constexpr bool is_ipv4_192_168_private() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 192U && + reinterpret_cast(&addr.addr4.s_addr)[1] == 168U; + } + + // 198.18.0.0/15 + [[nodiscard]] constexpr bool is_ipv4_benchmark() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 198U && + (reinterpret_cast(&addr.addr4.s_addr)[1] & 0xFEU) == 18U; + } + + // 198.51.100.0/24 + [[nodiscard]] constexpr bool is_ipv4_test_net_2() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 198U && + reinterpret_cast(&addr.addr4.s_addr)[1] == 51U && + reinterpret_cast(&addr.addr4.s_addr)[2] == 100U; + } + + // 203.0.113.0/24 + [[nodiscard]] constexpr bool is_ipv4_test_net_3() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 203U && + reinterpret_cast(&addr.addr4.s_addr)[1] == 0U && + reinterpret_cast(&addr.addr4.s_addr)[2] == 113U; + } + + // 224.0.0.0/4 + [[nodiscard]] constexpr bool is_ipv4_multicast() const noexcept + { + return is_ipv4() && (reinterpret_cast(&addr.addr4.s_addr)[0] & 0xF0U) == 224U; + } + + // 233.252.0.0/24 + [[nodiscard]] constexpr bool is_ipv4_mcast_test_net() const noexcept + { + return is_ipv4() && reinterpret_cast(&addr.addr4.s_addr)[0] == 233U && + reinterpret_cast(&addr.addr4.s_addr)[1] == 252U && + reinterpret_cast(&addr.addr4.s_addr)[2] == 0U; + } + + // 240.0.0.0/4 -255.255.255.255/32 + [[nodiscard]] constexpr bool is_ipv4_reserved_class_e() const noexcept + { + return is_ipv4() && !is_ipv4_limited_broadcast() && + (reinterpret_cast(&addr.addr4.s_addr)[0] & 0xF0U) == 240U; + } + + // 255.255.255.255/32 + [[nodiscard]] constexpr bool is_ipv4_limited_broadcast() const noexcept + { + return is_ipv4() && addr.addr4.s_addr == 0xFFFFFFFFU; + } + + // ::/128 + [[nodiscard]] constexpr bool is_ipv6_unspecified() const noexcept + { + return is_ipv6() && IN6_IS_ADDR_UNSPECIFIED(&addr.addr6); + } + + // ::1/128 + [[nodiscard]] constexpr bool is_ipv6_loopback() const noexcept { return is_ipv6() && IN6_IS_ADDR_LOOPBACK(&addr.addr6); } + // ::ffff:0:0/96 + [[nodiscard]] constexpr bool is_ipv6_ipv4_mapped() const noexcept + { + return is_ipv6() && IN6_IS_ADDR_V4MAPPED(&addr.addr6); + } + + // 2001::/32 + [[nodiscard]] constexpr bool is_ipv6_teredo() const noexcept + { + return is_ipv6() && reinterpret_cast(&addr.addr6)[0] == htonl(0x20010000U); + } + + // 2002::/16 + [[nodiscard]] constexpr bool is_ipv6_6to4() const noexcept + { + return is_ipv6() && reinterpret_cast(&addr.addr6)[0] == htons(0x2002U); + } + + // fe80::/64 from fe80::/10 + [[nodiscard]] constexpr bool is_ipv6_link_local() const noexcept + { + return is_ipv6() && IN6_IS_ADDR_LINKLOCAL(&addr.addr6); + } + + // ff00::/8 + [[nodiscard]] constexpr bool is_ipv6_multicast() const noexcept + { + return is_ipv6() && IN6_IS_ADDR_MULTICAST(&addr.addr6); + } + [[nodiscard]] std::optional from_ipv4_mapped() const noexcept; tr_address_type type = NUM_TR_AF_INET_TYPES; diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index 286712e7a..7b471fc25 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -1140,15 +1140,15 @@ void tr_peerMsgsImpl::send_ltep_handshake() // If connecting to global peer, then use global address // Otherwise we are connecting to local peer, use bind address directly - if (auto const addr = io_->address().is_global_unicast_address() ? session->global_address(TR_AF_INET) : - session->bind_address(TR_AF_INET); + if (auto const addr = io_->address().is_global_unicast() ? session->global_address(TR_AF_INET) : + session->bind_address(TR_AF_INET); addr && !addr->is_any()) { TR_ASSERT(addr->is_ipv4()); tr_variantDictAddRaw(&val, TR_KEY_ipv4, &addr->addr.addr4, sizeof(addr->addr.addr4)); } - if (auto const addr = io_->address().is_global_unicast_address() ? session->global_address(TR_AF_INET6) : - session->bind_address(TR_AF_INET6); + if (auto const addr = io_->address().is_global_unicast() ? session->global_address(TR_AF_INET6) : + session->bind_address(TR_AF_INET6); addr && !addr->is_any()) { TR_ASSERT(addr->is_ipv6()); diff --git a/libtransmission/session.cc b/libtransmission/session.cc index 572c4225a..fe62d036d 100644 --- a/libtransmission/session.cc +++ b/libtransmission/session.cc @@ -448,8 +448,7 @@ tr_address tr_session::bind_address(tr_address_type type) const noexcept // otherwise, if we can determine which one to use via global_source_address(ipv6) magic, use it. // otherwise, use any_ipv6 (::). auto const source_addr = source_address(type); - auto const default_addr = source_addr && source_addr->is_global_unicast_address() ? *source_addr : - tr_address::any(TR_AF_INET6); + auto const default_addr = source_addr && source_addr->is_global_unicast() ? *source_addr : tr_address::any(TR_AF_INET6); return tr_address::from_string(settings_.bind_address_ipv6).value_or(default_addr); } diff --git a/libtransmission/tr-udp.cc b/libtransmission/tr-udp.cc index a25d0a607..ece2a0ace 100644 --- a/libtransmission/tr-udp.cc +++ b/libtransmission/tr-udp.cc @@ -284,7 +284,7 @@ void tr_session::tr_udp_core::sendto(void const* buf, size_t buflen, struct sock return; } else if ( - addrport && !addrport->address().is_ipv4_loopback_address() && !addrport->address().is_ipv6_loopback_address() && + addrport && !addrport->address().is_ipv4_loopback() && !addrport->address().is_ipv6_loopback() && !session_.source_address(tr_af_to_ip_protocol(to->sa_family))) { // don't try to send if we don't have a route in this IP protocol diff --git a/tests/libtransmission/ip-cache-test.cc b/tests/libtransmission/ip-cache-test.cc index 72f572002..d7f89be0d 100644 --- a/tests/libtransmission/ip-cache-test.cc +++ b/tests/libtransmission/ip-cache-test.cc @@ -141,7 +141,7 @@ TEST_F(IPCacheTest, setGlobalAddr) static auto constexpr AddrStr = std::array{ "8.8.8.8"sv, "192.168.133.133"sv, "172.16.241.133"sv, "2001:1890:1112:1::20"sv, "fd12:3456:789a:1::1"sv, }; - static auto constexpr AddrTests = std::array{ true, false, false, true, false }; + static auto constexpr AddrTests = std::array{ true, false, false, true, true }; static_assert(TR_AF_INET == 0); static_assert(TR_AF_INET6 == 1); static_assert(NUM_TR_AF_INET_TYPES == 2); @@ -154,7 +154,7 @@ TEST_F(IPCacheTest, setGlobalAddr) { auto const addr = tr_address::from_string(AddrStr[i]); ASSERT_TRUE(addr.has_value()); - EXPECT_EQ(ip_cache_->set_global_addr(*addr), AddrTests[i]); + EXPECT_EQ(ip_cache_->set_global_addr(*addr), AddrTests[i]) << AddrStr[i]; if (auto const val = ip_cache_->global_addr(addr->type); val && AddrTests[i]) { EXPECT_EQ(val->display_name(), AddrStr[i]); @@ -213,11 +213,13 @@ TEST_F(IPCacheTest, globalSourceIPv6) TEST_F(IPCacheTest, onResponseIPQuery) { static auto constexpr AddrStr = std::array{ - "8.8.8.8"sv, "192.168.133.133"sv, "172.16.241.133"sv, "2001:1890:1112:1::20"sv, "fd12:3456:789a:1::1"sv, - "91.121.74.28"sv, "2001:1890:1112:1::20"sv + "8.8.8.8"sv, "192.168.133.133"sv, "172.16.241.133"sv, "2001:1890:1112:1::20"sv, "fd12:3456:789a:1::1"sv, + "91.121.74.28"sv, "2001:1890:1112:1::20"sv, + }; + static auto constexpr AddrTests = std::array{ + std::array{ true, false, false, false, false, true, false /* IPv4 */ }, + std::array{ false, false, false, true, true, false, true /* IPv6 */ }, }; - static auto constexpr AddrTests = std::array{ std::array{ true, false, false, false, false, true, false /* IPv4 */ }, - std::array{ false, false, false, true, false, false, true /* IPv6 */ } }; static_assert(TR_AF_INET == 0); static_assert(TR_AF_INET6 == 1); static_assert(NUM_TR_AF_INET_TYPES == 2); @@ -228,8 +230,9 @@ TEST_F(IPCacheTest, onResponseIPQuery) { void fetch(tr_web::FetchOptions&& options) override // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) { - auto response = tr_web::FetchResponse{ http_code, std::string{ AddrStr[k_] }, std::string{}, true, - false, options.done_func_user_data }; + auto response = tr_web::FetchResponse{ + http_code, std::string{ AddrStr[k_] }, std::string{}, true, false, options.done_func_user_data, + }; options.done_func(response); } @@ -256,7 +259,8 @@ TEST_F(IPCacheTest, onResponseIPQuery) ip_cache_->update_global_addr(type); auto const global_addr = ip_cache_->global_addr(type); - EXPECT_EQ(!!global_addr, j == 200 /* HTTP_OK */ && AddrTests[i][k]); + EXPECT_EQ(!!global_addr, j == 200 /* HTTP_OK */ && AddrTests[i][k]) + << "i = " << i << ", j = "sv << j << ", addr = "sv << AddrStr[k]; if (global_addr) { EXPECT_EQ(global_addr->display_name(), AddrStr[k]); diff --git a/tests/libtransmission/net-test.cc b/tests/libtransmission/net-test.cc index 278d1f03c..4cf94b907 100644 --- a/tests/libtransmission/net-test.cc +++ b/tests/libtransmission/net-test.cc @@ -5,7 +5,6 @@ #include #include -#include #include // std::byte, size_t #include #include @@ -32,8 +31,7 @@ TEST_F(NetTest, conversionsIPv4) static auto constexpr AddrStr = "127.0.0.1"sv; auto addr = tr_address::from_string(AddrStr); - EXPECT_TRUE(addr.has_value()); - assert(addr.has_value()); + ASSERT_TRUE(addr.has_value()); EXPECT_EQ(AddrStr, addr->display_name()); auto [ss, sslen] = tr_socket_address::to_sockaddr(*addr, Port); @@ -191,23 +189,729 @@ TEST_F(NetTest, isGlobalUnicastAddress) { "100.64.0.0"sv, false }, { "100.128.0.0"sv, true }, { "126.0.0.0"sv, true }, - { "127.0.0.0"sv, true }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, true }, { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, { "169.255.0.0"sv, true }, { "223.0.0.0"sv, true }, { "224.0.0.0"sv, false }, - { "0:0:0:0:0:0:0:1", false }, - { "2001:0:0eab:dead::a0:abcd:4e", true }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, true }, } }; for (auto const& [presentation, expected] : Tests) { auto const address = tr_address::from_string(presentation); - EXPECT_TRUE(address.has_value()); - assert(address.has_value()); - EXPECT_EQ(expected, address->is_global_unicast_address()) << presentation; + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_global_unicast()) << presentation; + } +} + +TEST_F(NetTest, isIPv4CurrentNetwork) +{ + static auto constexpr Tests = std::array, 19>{ { + { "0.0.0.0"sv, true }, + { "0.25.37.132"sv, true }, + { "0.255.255.255"sv, true }, + { "1.0.0.0"sv, false }, + { "10.0.0.0"sv, false }, + { "10.255.0.0"sv, false }, + { "10.255.0.255"sv, false }, + { "100.64.0.0"sv, false }, + { "100.128.0.0"sv, false }, + { "126.0.0.0"sv, false }, + { "127.0.0.0"sv, false }, + { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, + { "169.254.255.255"sv, false }, + { "169.255.0.0"sv, false }, + { "223.0.0.0"sv, false }, + { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_current_network()) << presentation; + } +} + +TEST_F(NetTest, isIPv4And10Private) +{ + static auto constexpr Tests = std::array, 18>{ { + { "0.0.0.0"sv, false }, + { "9.255.255.255"sv, false }, + { "10.0.0.0"sv, true }, + { "10.255.0.0"sv, true }, + { "10.255.0.255"sv, true }, + { "10.255.255.255"sv, true }, + { "11.0.0.0"sv, false }, + { "100.128.0.0"sv, false }, + { "126.0.0.0"sv, false }, + { "127.0.0.0"sv, false }, + { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, + { "169.254.255.255"sv, false }, + { "169.255.0.0"sv, false }, + { "223.0.0.0"sv, false }, + { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_10_private()) << presentation; + } +} + +TEST_F(NetTest, isIPv4CarrierGradeNAT) +{ + static auto constexpr Tests = std::array, 19>{ { + { "0.0.0.0"sv, false }, + { "1.0.0.0"sv, false }, + { "10.0.0.0"sv, false }, + { "10.255.0.0"sv, false }, + { "100.63.255.255"sv, false }, + { "100.64.0.0"sv, true }, + { "100.100.32.0"sv, true }, + { "100.127.255.255"sv, true }, + { "100.128.0.0"sv, false }, + { "126.0.0.0"sv, false }, + { "127.0.0.0"sv, false }, + { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, + { "169.254.255.255"sv, false }, + { "169.255.0.0"sv, false }, + { "223.0.0.0"sv, false }, + { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_carrier_grade_nat()) << presentation; + } +} + +TEST_F(NetTest, isIPv4Loopback) +{ + static auto constexpr Tests = std::array, 19>{ { + { "0.0.0.0"sv, false }, + { "1.0.0.0"sv, false }, + { "10.0.0.0"sv, false }, + { "10.255.0.0"sv, false }, + { "10.255.0.255"sv, false }, + { "100.64.0.0"sv, false }, + { "100.128.0.0"sv, false }, + { "126.255.255.255"sv, false }, + { "127.0.0.0"sv, true }, + { "127.12.12.57"sv, true }, + { "127.255.255.255"sv, true }, + { "128.0.0.0"sv, false }, + { "169.254.0.0"sv, false }, + { "169.254.255.255"sv, false }, + { "169.255.0.0"sv, false }, + { "223.0.0.0"sv, false }, + { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_loopback()) << presentation; + } +} + +TEST_F(NetTest, isIPv4LinkLocal) +{ + static auto constexpr Tests = std::array, 18>{ { + { "0.0.0.0"sv, false }, + { "1.0.0.0"sv, false }, + { "10.0.0.0"sv, false }, + { "10.255.0.0"sv, false }, + { "10.255.0.255"sv, false }, + { "100.64.0.0"sv, false }, + { "100.128.0.0"sv, false }, + { "126.0.0.0"sv, false }, + { "127.0.0.0"sv, false }, + { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, true }, + { "169.254.235.12"sv, true }, + { "169.254.255.255"sv, true }, + { "169.255.0.0"sv, false }, + { "223.0.0.0"sv, false }, + { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_link_local()) << presentation; + } +} + +TEST_F(NetTest, isIPv4And172Private) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.0"sv, false }, { "10.255.0.255"sv, false }, + { "100.64.0.0"sv, false }, { "100.128.0.0"sv, false }, + { "126.0.0.0"sv, false }, { "127.0.0.0"sv, false }, + { "169.253.255.255"sv, false }, { "169.254.0.0"sv, false }, + { "169.254.255.255"sv, false }, { "172.15.255.255"sv, false }, + { "172.16.0.0"sv, true }, { "172.17.78.245"sv, true }, + { "172.31.255.255"sv, true }, { "172.32.0.0"sv, false }, + { "223.0.0.0"sv, false }, { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_172_private()) << presentation; + } +} + +TEST_F(NetTest, isIPv4IetfProtocolAssignment) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "191.255.255.255"sv, false }, + { "192.0.0.0"sv, true }, { "192.0.0.14"sv, true }, + { "192.0.0.255"sv, true }, { "192.0.1.0"sv, false }, + { "223.0.0.0"sv, false }, { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_ietf_protocol_assignment()) << presentation; + } +} + +TEST_F(NetTest, isIPv4TestNet1) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "192.0.1.255"sv, false }, + { "192.0.2.0"sv, true }, { "192.0.2.14"sv, true }, + { "192.0.2.225"sv, true }, { "192.0.3.0"sv, false }, + { "223.0.0.0"sv, false }, { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_test_net_1()) << presentation; + } +} + +TEST_F(NetTest, isIPv4And6to4Relay) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "192.88.98.255"sv, false }, + { "192.88.99.0"sv, true }, { "192.88.99.14"sv, true }, + { "192.88.99.225"sv, true }, { "192.88.100.0"sv, false }, + { "223.0.0.0"sv, false }, { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_6to4_relay()) << presentation; + } +} + +TEST_F(NetTest, isIPv4And192Private) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "192.167.255.255"sv, false }, + { "192.168.0.0"sv, true }, { "192.168.99.14"sv, true }, + { "192.168.255.225"sv, true }, { "192.169.0.0"sv, false }, + { "223.0.0.0"sv, false }, { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_192_168_private()) << presentation; + } +} + +TEST_F(NetTest, isIPv4Benchmark) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "198.17.255.255"sv, false }, + { "198.18.0.0"sv, true }, { "198.19.99.14"sv, true }, + { "198.19.255.225"sv, true }, { "198.20.0.0"sv, false }, + { "223.0.0.0"sv, false }, { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_benchmark()) << presentation; + } +} + +TEST_F(NetTest, isIPv4TestNet2) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "198.51.99.255"sv, false }, + { "198.51.100.0"sv, true }, { "198.51.100.45"sv, true }, + { "198.51.100.255"sv, true }, { "198.51.101.0"sv, false }, + { "223.0.0.0"sv, false }, { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_test_net_2()) << presentation; + } +} + +TEST_F(NetTest, isIPv4TestNet3) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "203.0.112.255"sv, false }, + { "203.0.113.0"sv, true }, { "203.0.113.45"sv, true }, + { "203.0.113.255"sv, true }, { "203.0.114.0"sv, false }, + { "223.0.0.0"sv, false }, { "224.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_test_net_3()) << presentation; + } +} + +TEST_F(NetTest, isIPv4Multicast) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "203.0.113.255"sv, false }, + { "203.0.114.0"sv, false }, { "223.255.255.255"sv, false }, + { "224.0.0.0"sv, true }, { "230.124.45.18"sv, true }, + { "239.255.255.255"sv, true }, { "240.0.0.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_multicast()) << presentation; + } +} + +TEST_F(NetTest, isIPv4McastTestNet) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "203.0.113.255"sv, false }, + { "203.0.114.0"sv, false }, { "233.251.255.255"sv, false }, + { "233.252.0.0"sv, true }, { "233.252.0.18"sv, true }, + { "233.252.0.255"sv, true }, { "233.252.1.0"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_mcast_test_net()) << presentation; + } +} + +TEST_F(NetTest, isIPv4ReservedClassE) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "203.0.113.255"sv, false }, + { "203.0.114.0"sv, false }, { "239.255.255.255"sv, false }, + { "240.0.0.0"sv, true }, { "247.252.0.18"sv, true }, + { "255.255.255.254"sv, true }, { "255.255.255.255"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_reserved_class_e()) << presentation; + } +} + +TEST_F(NetTest, isIPv4LimitedBroadcast) +{ + static auto constexpr Tests = std::array, 20>{ { + { "0.0.0.0"sv, false }, { "10.0.0.0"sv, false }, + { "10.255.0.255"sv, false }, { "100.64.0.0"sv, false }, + { "127.0.0.0"sv, false }, { "169.253.255.255"sv, false }, + { "169.254.0.0"sv, false }, { "169.254.255.255"sv, false }, + { "172.16.0.0"sv, false }, { "172.17.78.245"sv, false }, + { "172.31.255.255"sv, false }, { "203.0.113.255"sv, false }, + { "203.0.114.0"sv, false }, { "239.255.255.255"sv, false }, + { "240.0.0.0"sv, false }, { "247.252.0.18"sv, false }, + { "255.255.255.254"sv, false }, { "255.255.255.255"sv, true }, + { "0:0:0:0:0:0:0:1"sv, false }, { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv4_limited_broadcast()) << presentation; + } +} + +TEST_F(NetTest, isIPv6Unspecified) +{ + static auto constexpr Tests = std::array, 26>{ { + { "0.0.0.0"sv, false }, + { "169.254.0.0"sv, false }, + { "::"sv, true }, + { "0:0:0:0:0:0:0:0"sv, true }, + { "::1"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "0:0:0:0:0:0:0:2"sv, false }, + { "::fffe:ffff:ffff"sv, false }, + { "::ffff:0:0"sv, false }, + { "::ffff:255.255.255.255"sv, false }, + { "::1:0:0:0"sv, false }, + { "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001::"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + { "2001:0:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001:1::"sv, false }, + { "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2002::"sv, false }, + { "2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2003::"sv, false }, + { "fe80::"sv, false }, + { "fe80::1234:5678:9876:5432"sv, false }, + { "fe80::ffff:ffff:ffff:ffff"sv, false }, + { "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "ff00::"sv, false }, + { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv6_unspecified()) << presentation; + } +} + +TEST_F(NetTest, isIPv6Loopback) +{ + static auto constexpr Tests = std::array, 26>{ { + { "0.0.0.0"sv, false }, + { "169.254.0.0"sv, false }, + { "::"sv, false }, + { "0:0:0:0:0:0:0:0"sv, false }, + { "::1"sv, true }, + { "0:0:0:0:0:0:0:1"sv, true }, + { "0:0:0:0:0:0:0:2"sv, false }, + { "::fffe:ffff:ffff"sv, false }, + { "::ffff:0:0"sv, false }, + { "::ffff:255.255.255.255"sv, false }, + { "::1:0:0:0"sv, false }, + { "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001::"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + { "2001:0:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001:1::"sv, false }, + { "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2002::"sv, false }, + { "2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2003::"sv, false }, + { "fe80::"sv, false }, + { "fe80::1234:5678:9876:5432"sv, false }, + { "fe80::ffff:ffff:ffff:ffff"sv, false }, + { "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "ff00::"sv, false }, + { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv6_loopback()) << presentation; + } +} + +TEST_F(NetTest, isIPv6IPv4Mapped) +{ + static auto constexpr Tests = std::array, 26>{ { + { "0.0.0.0"sv, false }, + { "169.254.0.0"sv, false }, + { "::"sv, false }, + { "0:0:0:0:0:0:0:0"sv, false }, + { "::1"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "0:0:0:0:0:0:0:2"sv, false }, + { "::fffe:ffff:ffff"sv, false }, + { "::ffff:0:0"sv, true }, + { "::ffff:255.255.255.255"sv, true }, + { "::1:0:0:0"sv, false }, + { "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001::"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + { "2001:0:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001:1::"sv, false }, + { "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2002::"sv, false }, + { "2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2003::"sv, false }, + { "fe80::"sv, false }, + { "fe80::1234:5678:9876:5432"sv, false }, + { "fe80::ffff:ffff:ffff:ffff"sv, false }, + { "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "ff00::"sv, false }, + { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv6_ipv4_mapped()) << presentation; + } +} + +TEST_F(NetTest, isIPv6Teredo) +{ + static auto constexpr Tests = std::array, 26>{ { + { "0.0.0.0"sv, false }, + { "169.254.0.0"sv, false }, + { "::"sv, false }, + { "0:0:0:0:0:0:0:0"sv, false }, + { "::1"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "0:0:0:0:0:0:0:2"sv, false }, + { "::fffe:ffff:ffff"sv, false }, + { "::ffff:0:0"sv, false }, + { "::ffff:255.255.255.255"sv, false }, + { "::1:0:0:0"sv, false }, + { "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001::"sv, true }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, true }, + { "2001:0:ffff:ffff:ffff:ffff:ffff:ffff"sv, true }, + { "2001:1::"sv, false }, + { "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2002::"sv, false }, + { "2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2003::"sv, false }, + { "fe80::"sv, false }, + { "fe80::1234:5678:9876:5432"sv, false }, + { "fe80::ffff:ffff:ffff:ffff"sv, false }, + { "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "ff00::"sv, false }, + { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv6_teredo()) << presentation; + } +} + +TEST_F(NetTest, isIPv6And6to4) +{ + static auto constexpr Tests = std::array, 26>{ { + { "0.0.0.0"sv, false }, + { "169.254.0.0"sv, false }, + { "::"sv, false }, + { "0:0:0:0:0:0:0:0"sv, false }, + { "::1"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "0:0:0:0:0:0:0:2"sv, false }, + { "::fffe:ffff:ffff"sv, false }, + { "::ffff:0:0"sv, false }, + { "::ffff:255.255.255.255"sv, false }, + { "::1:0:0:0"sv, false }, + { "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001::"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + { "2001:0:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001:1::"sv, false }, + { "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2002::"sv, true }, + { "2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, true }, + { "2003::"sv, false }, + { "fe80::"sv, false }, + { "fe80::1234:5678:9876:5432"sv, false }, + { "fe80::ffff:ffff:ffff:ffff"sv, false }, + { "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "ff00::"sv, false }, + { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv6_6to4()) << presentation; + } +} + +TEST_F(NetTest, isIPv6LinkLocal) +{ + static auto constexpr Tests = std::array, 26>{ { + { "0.0.0.0"sv, false }, + { "169.254.0.0"sv, false }, + { "::"sv, false }, + { "0:0:0:0:0:0:0:0"sv, false }, + { "::1"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "0:0:0:0:0:0:0:2"sv, false }, + { "::fffe:ffff:ffff"sv, false }, + { "::ffff:0:0"sv, false }, + { "::ffff:255.255.255.255"sv, false }, + { "::1:0:0:0"sv, false }, + { "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001::"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + { "2001:0:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001:1::"sv, false }, + { "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2002::"sv, false }, + { "2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2003::"sv, false }, + { "fe80::"sv, true }, + { "fe80::1234:5678:9876:5432"sv, true }, + { "fe80::ffff:ffff:ffff:ffff"sv, true }, + { "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "ff00::"sv, false }, + { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv6_link_local()) << presentation; + } +} + +TEST_F(NetTest, isIPv6Multicast) +{ + static auto constexpr Tests = std::array, 26>{ { + { "0.0.0.0"sv, false }, + { "169.254.0.0"sv, false }, + { "::"sv, false }, + { "0:0:0:0:0:0:0:0"sv, false }, + { "::1"sv, false }, + { "0:0:0:0:0:0:0:1"sv, false }, + { "0:0:0:0:0:0:0:2"sv, false }, + { "::fffe:ffff:ffff"sv, false }, + { "::ffff:0:0"sv, false }, + { "::ffff:255.255.255.255"sv, false }, + { "::1:0:0:0"sv, false }, + { "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001::"sv, false }, + { "2001:0:0eab:dead::a0:abcd:4e"sv, false }, + { "2001:0:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2001:1::"sv, false }, + { "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2002::"sv, false }, + { "2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "2003::"sv, false }, + { "fe80::"sv, false }, + { "fe80::1234:5678:9876:5432"sv, false }, + { "fe80::ffff:ffff:ffff:ffff"sv, false }, + { "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, false }, + { "ff00::"sv, true }, + { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"sv, true }, + } }; + + for (auto const& [presentation, expected] : Tests) + { + auto const address = tr_address::from_string(presentation); + ASSERT_TRUE(address.has_value()); + EXPECT_EQ(expected, address->is_ipv6_multicast()) << presentation; } }