// MODULES: ../impl/IPEndpoint.cpp ../impl/IPAddressV4.cpp // ../impl/IPAddressV6.cpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace beast::IP { //------------------------------------------------------------------------------ class IPEndpoint_test : public unit_test::Suite { public: void shouldParseAddrV4(std::string const& s, std::uint32_t value, std::string const& normal = "") { boost::system::error_code ec; Address const result{boost::asio::ip::make_address(s, ec)}; if (!BEAST_EXPECTS(!ec, ec.message())) return; if (!BEAST_EXPECTS(result.is_v4(), s + " not v4")) return; if (!BEAST_EXPECTS(result.to_v4().to_uint() == value, s + " value mismatch")) return; BEAST_EXPECTS(result.to_string() == (normal.empty() ? s : normal), s + " as string"); } void failParseAddr(std::string const& s) { boost::system::error_code ec; auto a = boost::asio::ip::make_address(s, ec); BEAST_EXPECTS(ec, s + " parses as " + a.to_string()); } void testAddressV4() { testcase("AddressV4"); BEAST_EXPECT(AddressV4{}.to_uint() == 0); BEAST_EXPECT(isUnspecified(AddressV4{})); BEAST_EXPECT(AddressV4{0x01020304}.to_uint() == 0x01020304); { AddressV4::bytes_type const d = {{1, 2, 3, 4}}; BEAST_EXPECT(AddressV4{d}.to_uint() == 0x01020304); unexpected(isUnspecified(AddressV4{d})); } AddressV4 const v1{1}; BEAST_EXPECT(AddressV4{v1}.to_uint() == 1); { AddressV4 v; v = v1; BEAST_EXPECT(v.to_uint() == v1.to_uint()); } { AddressV4 v; auto d = v.to_bytes(); d[0] = 1; d[1] = 2; d[2] = 3; d[3] = 4; v = AddressV4{d}; BEAST_EXPECT(v.to_uint() == 0x01020304); } BEAST_EXPECT(AddressV4(0x01020304).to_string() == "1.2.3.4"); shouldParseAddrV4("1.2.3.4", 0x01020304); shouldParseAddrV4("255.255.255.255", 0xffffffff); shouldParseAddrV4("0.0.0.0", 0); failParseAddr("."); failParseAddr(".."); failParseAddr("..."); failParseAddr("...."); #if BOOST_OS_WINDOWS // WINDOWS bug in asio - I don't think these should parse // at all, and in-fact they do not on mac/linux shouldParseAddrV4("1", 0x00000001, "0.0.0.1"); shouldParseAddrV4("1.2", 0x01000002, "1.0.0.2"); shouldParseAddrV4("1.2.3", 0x01020003, "1.2.0.3"); #else failParseAddr("1"); failParseAddr("1.2"); failParseAddr("1.2.3"); #endif failParseAddr("1."); failParseAddr("1.2."); failParseAddr("1.2.3."); failParseAddr("256.0.0.0"); failParseAddr("-1.2.3.4"); } void testAddressV4Proxy() { testcase("AddressV4::Bytes"); AddressV4::bytes_type const d1 = {{10, 0, 0, 1}}; AddressV4 v4{d1}; BEAST_EXPECT(v4.to_bytes()[0] == 10); BEAST_EXPECT(v4.to_bytes()[1] == 0); BEAST_EXPECT(v4.to_bytes()[2] == 0); BEAST_EXPECT(v4.to_bytes()[3] == 1); BEAST_EXPECT((~((0xff) << 16)) == 0xff00ffff); auto d2 = v4.to_bytes(); d2[1] = 10; v4 = AddressV4{d2}; BEAST_EXPECT(v4.to_bytes()[0] == 10); BEAST_EXPECT(v4.to_bytes()[1] == 10); BEAST_EXPECT(v4.to_bytes()[2] == 0); BEAST_EXPECT(v4.to_bytes()[3] == 1); } //-------------------------------------------------------------------------- void testAddress() { testcase("Address"); boost::system::error_code ec; Address const result{boost::asio::ip::make_address("1.2.3.4", ec)}; AddressV4::bytes_type const d = {{1, 2, 3, 4}}; BEAST_EXPECT(!ec); BEAST_EXPECT(result.is_v4() && result.to_v4() == AddressV4{d}); } //-------------------------------------------------------------------------- void shouldParseEPV4( std::string const& s, AddressV4::bytes_type const& value, std::uint16_t p, std::string const& normal = "") { auto const result = Endpoint::fromStringChecked(s); if (BEAST_EXPECT(result); !result.has_value()) return; if (!BEAST_EXPECT(result->address().is_v4())) return; if (!BEAST_EXPECT(result->address().to_v4() == AddressV4{value})) return; BEAST_EXPECT(result->port() == p); BEAST_EXPECT(to_string(*result) == (normal.empty() ? s : normal)); } void shouldParseEPV6( std::string const& s, AddressV6::bytes_type const& value, std::uint16_t p, std::string const& normal = "") { auto result = Endpoint::fromStringChecked(s); if (BEAST_EXPECT(result); !result.has_value()) return; if (!BEAST_EXPECT(result->address().is_v6())) return; if (!BEAST_EXPECT(result->address().to_v6() == AddressV6{value})) return; BEAST_EXPECT(result->port() == p); BEAST_EXPECT(to_string(*result) == (normal.empty() ? s : normal)); } void failParseEP(std::string s) { auto a1 = Endpoint::fromString(s); BEAST_EXPECTS(isUnspecified(a1), s + " parses as " + a1.toString()); auto a2 = Endpoint::fromString(s); BEAST_EXPECTS(isUnspecified(a2), s + " parses as " + a2.toString()); boost::replace_last(s, ":", " "); auto a3 = Endpoint::fromString(s); BEAST_EXPECTS(isUnspecified(a3), s + " parses as " + a3.toString()); } void testEndpoint() { testcase("Endpoint"); shouldParseEPV4("1.2.3.4", {{1, 2, 3, 4}}, 0); shouldParseEPV4("1.2.3.4:5", {{1, 2, 3, 4}}, 5); shouldParseEPV4("1.2.3.4 5", {{1, 2, 3, 4}}, 5, "1.2.3.4:5"); // leading, trailing space shouldParseEPV4(" 1.2.3.4:5", {{1, 2, 3, 4}}, 5, "1.2.3.4:5"); shouldParseEPV4("1.2.3.4:5 ", {{1, 2, 3, 4}}, 5, "1.2.3.4:5"); shouldParseEPV4("1.2.3.4 ", {{1, 2, 3, 4}}, 0, "1.2.3.4"); shouldParseEPV4(" 1.2.3.4", {{1, 2, 3, 4}}, 0, "1.2.3.4"); shouldParseEPV6( "2001:db8:a0b:12f0::1", {{32, 01, 13, 184, 10, 11, 18, 240, 0, 0, 0, 0, 0, 0, 0, 1}}, 0); shouldParseEPV6( "[2001:db8:a0b:12f0::1]:8", {{32, 01, 13, 184, 10, 11, 18, 240, 0, 0, 0, 0, 0, 0, 0, 1}}, 8); shouldParseEPV6( "[2001:2002:2003:2004:2005:2006:2007:2008]:65535", {{32, 1, 32, 2, 32, 3, 32, 4, 32, 5, 32, 6, 32, 7, 32, 8}}, 65535); shouldParseEPV6( "2001:2002:2003:2004:2005:2006:2007:2008 65535", {{32, 1, 32, 2, 32, 3, 32, 4, 32, 5, 32, 6, 32, 7, 32, 8}}, 65535, "[2001:2002:2003:2004:2005:2006:2007:2008]:65535"); Endpoint ep; AddressV4::bytes_type d = {{127, 0, 0, 1}}; ep = Endpoint(AddressV4{d}, 80); BEAST_EXPECT(!isUnspecified(ep)); BEAST_EXPECT(!isPublic(ep)); BEAST_EXPECT(isPrivate(ep)); BEAST_EXPECT(!isMulticast(ep)); BEAST_EXPECT(isLoopback(ep)); BEAST_EXPECT(to_string(ep) == "127.0.0.1:80"); // same address as v4 mapped in ipv6 ep = Endpoint( boost::asio::ip::make_address_v6(boost::asio::ip::v4_mapped, AddressV4{d}), 80); BEAST_EXPECT(!isUnspecified(ep)); BEAST_EXPECT(!isPublic(ep)); BEAST_EXPECT(isPrivate(ep)); BEAST_EXPECT(!isMulticast(ep)); BEAST_EXPECT(!isLoopback(ep)); // mapped loopback is not a loopback BEAST_EXPECTS(to_string(ep) == "[::ffff:127.0.0.1]:80", to_string(ep)); d = {{10, 0, 0, 1}}; ep = Endpoint(AddressV4{d}); BEAST_EXPECT(getClass(ep.toV4()) == 'A'); BEAST_EXPECT(!isUnspecified(ep)); BEAST_EXPECT(!isPublic(ep)); BEAST_EXPECT(isPrivate(ep)); BEAST_EXPECT(!isMulticast(ep)); BEAST_EXPECT(!isLoopback(ep)); BEAST_EXPECT(to_string(ep) == "10.0.0.1"); // same address as v4 mapped in ipv6 ep = Endpoint(boost::asio::ip::make_address_v6(boost::asio::ip::v4_mapped, AddressV4{d})); BEAST_EXPECT( getClass(boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, ep.toV6())) == 'A'); BEAST_EXPECT(!isUnspecified(ep)); BEAST_EXPECT(!isPublic(ep)); BEAST_EXPECT(isPrivate(ep)); BEAST_EXPECT(!isMulticast(ep)); BEAST_EXPECT(!isLoopback(ep)); BEAST_EXPECTS(to_string(ep) == "::ffff:10.0.0.1", to_string(ep)); d = {{166, 78, 151, 147}}; ep = Endpoint(AddressV4{d}); BEAST_EXPECT(!isUnspecified(ep)); BEAST_EXPECT(isPublic(ep)); BEAST_EXPECT(!isPrivate(ep)); BEAST_EXPECT(!isMulticast(ep)); BEAST_EXPECT(!isLoopback(ep)); BEAST_EXPECT(to_string(ep) == "166.78.151.147"); // same address as v4 mapped in ipv6 ep = Endpoint(boost::asio::ip::make_address_v6(boost::asio::ip::v4_mapped, AddressV4{d})); BEAST_EXPECT(!isUnspecified(ep)); BEAST_EXPECT(isPublic(ep)); BEAST_EXPECT(!isPrivate(ep)); BEAST_EXPECT(!isMulticast(ep)); BEAST_EXPECT(!isLoopback(ep)); BEAST_EXPECTS(to_string(ep) == "::ffff:166.78.151.147", to_string(ep)); // a private IPv6 AddressV6::bytes_type const d2 = {{253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; ep = Endpoint(AddressV6{d2}); BEAST_EXPECT(!isUnspecified(ep)); BEAST_EXPECT(!isPublic(ep)); BEAST_EXPECT(isPrivate(ep)); BEAST_EXPECT(!isMulticast(ep)); BEAST_EXPECT(!isLoopback(ep)); BEAST_EXPECTS(to_string(ep) == "fd00::1", to_string(ep)); { ep = Endpoint::fromString("192.0.2.112"); BEAST_EXPECT(!isUnspecified(ep)); BEAST_EXPECT(ep == Endpoint::fromString("192.0.2.112")); auto const ep1 = Endpoint::fromString("192.0.2.112:2016"); BEAST_EXPECT(!isUnspecified(ep1)); BEAST_EXPECT(ep.address() == ep1.address()); BEAST_EXPECT(ep1.port() == 2016); auto const ep2 = Endpoint::fromString("192.0.2.112:2016"); BEAST_EXPECT(!isUnspecified(ep2)); BEAST_EXPECT(ep.address() == ep2.address()); BEAST_EXPECT(ep2.port() == 2016); BEAST_EXPECT(ep1 == ep2); auto const ep3 = Endpoint::fromString("192.0.2.112 2016"); BEAST_EXPECT(!isUnspecified(ep3)); BEAST_EXPECT(ep.address() == ep3.address()); BEAST_EXPECT(ep3.port() == 2016); BEAST_EXPECT(ep2 == ep3); auto const ep4 = Endpoint::fromString("192.0.2.112 2016"); BEAST_EXPECT(!isUnspecified(ep4)); BEAST_EXPECT(ep.address() == ep4.address()); BEAST_EXPECT(ep4.port() == 2016); BEAST_EXPECT(ep3 == ep4); BEAST_EXPECT(to_string(ep1) == to_string(ep2)); BEAST_EXPECT(to_string(ep1) == to_string(ep3)); BEAST_EXPECT(to_string(ep1) == to_string(ep4)); } { ep = Endpoint::fromString("[::]:2017"); BEAST_EXPECT(isUnspecified(ep)); BEAST_EXPECT(ep.port() == 2017); BEAST_EXPECT(ep.address() == AddressV6{}); } // Failures: failParseEP("192.0.2.112:port"); failParseEP("ip:port"); failParseEP(""); failParseEP("1.2.3.256"); #if BOOST_OS_WINDOWS // windows asio bugs...false positives shouldParseEPV4("255", {{0, 0, 0, 255}}, 0, "0.0.0.255"); shouldParseEPV4("512", {{0, 0, 2, 0}}, 0, "0.0.2.0"); shouldParseEPV4("1.2.3:80", {{1, 2, 0, 3}}, 80, "1.2.0.3:80"); #else failParseEP("255"); failParseEP("512"); failParseEP("1.2.3:80"); #endif failParseEP("1.2.3.4:65536"); failParseEP("1.2.3.4:89119"); failParseEP("1.2.3:89119"); failParseEP("[::1]:89119"); failParseEP("[::az]:1"); failParseEP("[1234:5678:90ab:cdef:1234:5678:90ab:cdef:1111]:1"); failParseEP("[1234:5678:90ab:cdef:1234:5678:90ab:cdef:1111]:12345"); failParseEP("abcdef:12345"); failParseEP("[abcdef]:12345"); failParseEP("foo.org 12345"); // test with hashed container std::unordered_set eps; static constexpr auto kItems{100}; float maxLf{0}; for (auto i = 0; i < kItems; ++i) { eps.insert(randomEP(xrpl::randInt(0, 1) == 1)); maxLf = std::max(maxLf, eps.load_factor()); } BEAST_EXPECT(eps.bucket_count() >= kItems); BEAST_EXPECT(maxLf > 0.90); } //-------------------------------------------------------------------------- template bool parse(std::string const& text, T& t) { std::istringstream stream{text}; stream >> t; return !stream.fail(); } template void shouldPass(std::string const& text, std::string const& normal = "") { using namespace std::literals; T t; BEAST_EXPECT(parse(text, t)); BEAST_EXPECTS( to_string(t) == (normal.empty() ? text : normal), "string mismatch for "s + text); } template void shouldFail(std::string const& text) { T t; unexpected(parse(text, t), text + " should not parse"); } template void testParse(char const* name) { testcase(name); shouldPass("0.0.0.0"); shouldPass("192.168.0.1"); shouldPass("168.127.149.132"); shouldPass("168.127.149.132:80"); shouldPass("168.127.149.132:54321"); shouldPass("2001:db8:a0b:12f0::1"); shouldPass("[2001:db8:a0b:12f0::1]:8"); shouldPass("2001:db8:a0b:12f0::1 8", "[2001:db8:a0b:12f0::1]:8"); shouldPass("[::1]:8"); shouldPass("[2001:2002:2003:2004:2005:2006:2007:2008]:65535"); shouldFail("1.2.3.256"); shouldFail(""); #if BOOST_OS_WINDOWS // windows asio bugs...false positives shouldPass("512", "0.0.2.0"); shouldPass("255", "0.0.0.255"); shouldPass("1.2.3:80", "1.2.0.3:80"); #else shouldFail("512"); shouldFail("255"); shouldFail("1.2.3:80"); #endif shouldFail("1.2.3:65536"); shouldFail("1.2.3:72131"); shouldFail("[::1]:89119"); shouldFail("[::az]:1"); shouldFail("[1234:5678:90ab:cdef:1234:5678:90ab:cdef:1111]:1"); shouldFail("[1234:5678:90ab:cdef:1234:5678:90ab:cdef:1111]:12345"); } void run() override { testAddressV4(); testAddressV4Proxy(); testAddress(); testEndpoint(); testParse("Parse Endpoint"); } }; BEAST_DEFINE_TESTSUITE(IPEndpoint, beast, beast); } // namespace beast::IP