| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/dns/address_info.h" |
| |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <array> |
| #include <memory> |
| |
| #include "base/check_op.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/sys_byteorder.h" |
| #include "build/build_config.h" |
| #include "net/base/address_list.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/sys_addrinfo.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| class MockAddrInfoGetter : public AddrInfoGetter { |
| public: |
| std::unique_ptr<addrinfo, FreeAddrInfoFunc> getaddrinfo( |
| const std::string& host, |
| const addrinfo* hints, |
| int* out_os_error, |
| handles::NetworkHandle network) override; |
| |
| private: |
| struct IpAndPort { |
| struct Ip { |
| uint8_t a; |
| uint8_t b; |
| uint8_t c; |
| uint8_t d; |
| }; |
| Ip ip; |
| int port; |
| }; |
| |
| // Initialises `addr` and `ai` from `ip_and_port`, `canonical_name` and |
| // `ai_next`. |
| static void InitializeAddrinfo(const IpAndPort& ip_and_port, |
| char* canonical_name, |
| addrinfo* ai_next, |
| sockaddr_in* addr, |
| addrinfo* ai); |
| |
| // Allocates and initialises an addrinfo structure containing the ip addresses |
| // and ports from `ipp` and the name `canonical_name`. This function is |
| // designed to be used within getaddrinfo(), which returns a raw pointer even |
| // though it transfers ownership. So this function does the same. Since |
| // addrinfo is a C-style variable-sized structure it cannot be allocated with |
| // new. It is allocated with malloc() instead, so it must be freed with |
| // free(). |
| template <size_t N> |
| static std::unique_ptr<addrinfo, FreeAddrInfoFunc> MakeAddrInfoList( |
| const IpAndPort (&ipp)[N], |
| base::StringPiece canonical_name); |
| |
| static std::unique_ptr<addrinfo, FreeAddrInfoFunc> MakeAddrInfo( |
| IpAndPort ipp, |
| base::StringPiece canonical_name); |
| }; |
| |
| template <size_t N> |
| std::unique_ptr<addrinfo, FreeAddrInfoFunc> |
| MockAddrInfoGetter::MakeAddrInfoList(const IpAndPort (&ipp)[N], |
| base::StringPiece canonical_name) { |
| struct Buffer { |
| addrinfo ai[N]; |
| sockaddr_in addr[N]; |
| char canonical_name[256]; |
| }; |
| |
| CHECK_LE(canonical_name.size(), 255u); |
| |
| Buffer* const buffer = new Buffer(); |
| memset(buffer, 0x0, sizeof(Buffer)); |
| |
| // At least one trailing nul byte on buffer->canonical_name was added by |
| // memset() above. |
| memcpy(buffer->canonical_name, canonical_name.data(), canonical_name.size()); |
| |
| for (size_t i = 0; i < N; ++i) { |
| InitializeAddrinfo(ipp[i], buffer->canonical_name, |
| i + 1 < N ? buffer->ai + i + 1 : nullptr, |
| buffer->addr + i, buffer->ai + i); |
| } |
| |
| return {reinterpret_cast<addrinfo*>(buffer), |
| [](addrinfo* ai) { delete reinterpret_cast<Buffer*>(ai); }}; |
| } |
| |
| std::unique_ptr<addrinfo, FreeAddrInfoFunc> MockAddrInfoGetter::MakeAddrInfo( |
| IpAndPort ipp, |
| base::StringPiece canonical_name) { |
| return MakeAddrInfoList({ipp}, canonical_name); |
| } |
| |
| void MockAddrInfoGetter::InitializeAddrinfo(const IpAndPort& ip_and_port, |
| char* canonical_name, |
| addrinfo* ai_next, |
| sockaddr_in* addr, |
| addrinfo* ai) { |
| const uint8_t ip[4] = {ip_and_port.ip.a, ip_and_port.ip.b, ip_and_port.ip.c, |
| ip_and_port.ip.d}; |
| memcpy(&addr->sin_addr, ip, 4); |
| addr->sin_family = AF_INET; |
| addr->sin_port = |
| base::HostToNet16(base::checked_cast<uint16_t>(ip_and_port.port)); |
| |
| ai->ai_family = AF_INET; |
| ai->ai_socktype = SOCK_STREAM; |
| ai->ai_addrlen = sizeof(sockaddr_in); |
| ai->ai_addr = reinterpret_cast<sockaddr*>(addr); |
| ai->ai_canonname = |
| reinterpret_cast<decltype(ai->ai_canonname)>(canonical_name); |
| if (ai_next) |
| ai->ai_next = ai_next; |
| } |
| |
| std::unique_ptr<addrinfo, FreeAddrInfoFunc> MockAddrInfoGetter::getaddrinfo( |
| const std::string& host, |
| const addrinfo* /* hints */, |
| int* out_os_error, |
| handles::NetworkHandle) { |
| // Presume success |
| *out_os_error = 0; |
| |
| if (host == std::string("canonical.bar.com")) |
| return MakeAddrInfo({{1, 2, 3, 4}, 80}, "canonical.bar.com"); |
| else if (host == "iteration.test") |
| return MakeAddrInfoList({{{10, 20, 30, 40}, 80}, |
| {{11, 21, 31, 41}, 81}, |
| {{12, 22, 32, 42}, 82}}, |
| "iteration.test"); |
| else if (host == "alllocalhost.com") |
| return MakeAddrInfoList( |
| {{{127, 0, 0, 1}, 80}, {{127, 0, 0, 2}, 80}, {{127, 0, 0, 3}, 80}}, |
| "alllocalhost.com"); |
| else if (host == "not.alllocalhost.com") |
| return MakeAddrInfoList( |
| {{{128, 0, 0, 1}, 80}, {{127, 0, 0, 2}, 80}, {{127, 0, 0, 3}, 80}}, |
| "not.alllocalhost.com"); |
| else if (host == "www.example.com") |
| return MakeAddrInfo({{8, 8, 8, 8}, 80}, "www.example.com"); |
| |
| // Failure |
| *out_os_error = 1; |
| |
| return {nullptr, [](addrinfo*) {}}; |
| } |
| |
| std::unique_ptr<addrinfo> MakeHints(AddressFamily address_family, |
| HostResolverFlags host_resolver_flags) { |
| auto hints = std::make_unique<addrinfo>(); |
| *hints = {0}; |
| |
| switch (address_family) { |
| case ADDRESS_FAMILY_IPV4: |
| hints->ai_family = AF_INET; |
| break; |
| case ADDRESS_FAMILY_IPV6: |
| hints->ai_family = AF_INET6; |
| break; |
| case ADDRESS_FAMILY_UNSPECIFIED: |
| hints->ai_family = AF_UNSPEC; |
| break; |
| } |
| |
| if (host_resolver_flags & HOST_RESOLVER_CANONNAME) |
| hints->ai_flags |= AI_CANONNAME; |
| |
| hints->ai_socktype = SOCK_STREAM; |
| |
| return hints; |
| } |
| |
| TEST(AddressInfoTest, Failure) { |
| auto getter = std::make_unique<MockAddrInfoGetter>(); |
| auto [ai, err, os_error] = AddressInfo::Get( |
| "failure.com", *MakeHints(ADDRESS_FAMILY_IPV4, HOST_RESOLVER_CANONNAME), |
| std::move(getter)); |
| |
| EXPECT_FALSE(ai); |
| EXPECT_NE(err, OK); |
| EXPECT_NE(os_error, 0); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| // Note: this test is descriptive, not prescriptive. |
| TEST(AddressInfoTest, FailureWin) { |
| auto getter = std::make_unique<MockAddrInfoGetter>(); |
| auto [ai, err, os_error] = AddressInfo::Get( |
| "failure.com", *MakeHints(ADDRESS_FAMILY_IPV4, HOST_RESOLVER_CANONNAME), |
| std::move(getter)); |
| |
| EXPECT_FALSE(ai); |
| EXPECT_EQ(err, ERR_NAME_RESOLUTION_FAILED); |
| EXPECT_NE(os_error, 0); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Note: this test is descriptive, not prescriptive. |
| TEST(AddressInfoTest, FailureAndroid) { |
| auto getter = std::make_unique<MockAddrInfoGetter>(); |
| auto [ai, err, os_error] = AddressInfo::Get( |
| "failure.com", *MakeHints(ADDRESS_FAMILY_IPV4, HOST_RESOLVER_CANONNAME), |
| std::move(getter)); |
| |
| EXPECT_FALSE(ai); |
| EXPECT_EQ(err, ERR_NAME_NOT_RESOLVED); |
| EXPECT_NE(os_error, 0); |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| TEST(AddressInfoTest, Canonical) { |
| auto [ai, err, os_error] = |
| AddressInfo::Get("canonical.bar.com", |
| *MakeHints(ADDRESS_FAMILY_IPV4, HOST_RESOLVER_CANONNAME), |
| std::make_unique<MockAddrInfoGetter>()); |
| |
| EXPECT_TRUE(ai); |
| EXPECT_EQ(err, OK); |
| EXPECT_EQ(os_error, 0); |
| EXPECT_THAT(ai->GetCanonicalName(), |
| absl::optional<std::string>("canonical.bar.com")); |
| } |
| |
| TEST(AddressInfoTest, Iteration) { |
| auto [ai, err, os_error] = |
| AddressInfo::Get("iteration.test", |
| *MakeHints(ADDRESS_FAMILY_IPV4, HOST_RESOLVER_CANONNAME), |
| std::make_unique<MockAddrInfoGetter>()); |
| |
| EXPECT_TRUE(ai); |
| EXPECT_EQ(err, OK); |
| EXPECT_EQ(os_error, 0); |
| |
| { |
| int count = 0; |
| for (const auto& addr_info : *ai) { |
| const sockaddr_in* addr = |
| reinterpret_cast<sockaddr_in*>(addr_info.ai_addr); |
| EXPECT_EQ(base::HostToNet16(addr->sin_port) % 10, count % 10); |
| ++count; |
| } |
| |
| EXPECT_EQ(count, 3); |
| } |
| |
| { |
| int count = 0; |
| for (auto&& aii : ai.value()) { |
| const sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(aii.ai_addr); |
| EXPECT_EQ(base::HostToNet16(addr->sin_port) % 10, count % 10); |
| ++count; |
| } |
| |
| EXPECT_EQ(count, 3); |
| } |
| } |
| |
| TEST(AddressInfoTest, IsAllLocalhostOfOneFamily) { |
| auto [ai, err, os_error] = |
| AddressInfo::Get("alllocalhost.com", |
| *MakeHints(ADDRESS_FAMILY_IPV4, HOST_RESOLVER_CANONNAME), |
| std::make_unique<MockAddrInfoGetter>()); |
| |
| EXPECT_TRUE(ai); |
| EXPECT_EQ(err, OK); |
| EXPECT_EQ(os_error, 0); |
| EXPECT_TRUE(ai->IsAllLocalhostOfOneFamily()); |
| } |
| |
| TEST(AddressInfoTest, IsAllLocalhostOfOneFamilyFalse) { |
| auto [ai, err, os_error] = |
| AddressInfo::Get("not.alllocalhost.com", |
| *MakeHints(ADDRESS_FAMILY_IPV4, HOST_RESOLVER_CANONNAME), |
| std::make_unique<MockAddrInfoGetter>()); |
| |
| EXPECT_TRUE(ai); |
| EXPECT_EQ(err, OK); |
| EXPECT_EQ(os_error, 0); |
| EXPECT_FALSE(ai->IsAllLocalhostOfOneFamily()); |
| } |
| |
| TEST(AddressInfoTest, CreateAddressList) { |
| auto [ai, err, os_error] = |
| AddressInfo::Get("www.example.com", |
| *MakeHints(ADDRESS_FAMILY_IPV4, HOST_RESOLVER_CANONNAME), |
| std::make_unique<MockAddrInfoGetter>()); |
| |
| EXPECT_TRUE(ai); |
| EXPECT_EQ(err, OK); |
| EXPECT_EQ(os_error, 0); |
| |
| AddressList list = ai->CreateAddressList(); |
| |
| // Verify one result. |
| ASSERT_EQ(1u, list.size()); |
| ASSERT_EQ(ADDRESS_FAMILY_IPV4, list[0].GetFamily()); |
| |
| // Check if operator= works. |
| AddressList copy; |
| copy = list; |
| ASSERT_EQ(1u, copy.size()); |
| } |
| |
| } // namespace |
| } // namespace net |