blob: 149bf50a595afbb17faa07e6a25a2de3d81797a2 [file] [log] [blame] [edit]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <arpa/inet.h>
#include <errno.h>
#include <lib/fit/defer.h>
#include <lib/fit/result.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <zircon/lookup.h>
#include <gtest/gtest.h>
struct GetAddrInfoRequest {
const char* node;
const char* service;
const addrinfo hints;
};
struct GetAddrInfoResponse {
const in_port_t port;
const char* address;
const size_t count;
};
class GetAddrInfoTest : public testing::TestWithParam<
std::tuple<GetAddrInfoRequest, fit::result<int, GetAddrInfoResponse>>> {
};
std::string to_string(const GetAddrInfoTest::ParamType& param) {
const auto& [request, expectation] = param;
std::ostringstream ss;
ss << request.service;
switch (request.hints.ai_socktype) {
case SOCK_STREAM: {
ss << "Stream";
} break;
case SOCK_DGRAM: {
ss << "Datagram";
} break;
default:
ss << "Socktype" << request.hints.ai_socktype;
}
if (expectation.is_error()) {
ss << "Fails"; // Can't use gai_strerror here because the returned string contains spaces.
} else {
const GetAddrInfoResponse& response = expectation.value();
ss << "Succeeds"
<< "With" << response.count << "Record";
if (response.count != 1) {
ss << "s";
}
}
return ss.str();
}
TEST_P(GetAddrInfoTest, Basic) {
const auto& [request, expectation] = GetParam();
addrinfo* result;
{
const int ret = getaddrinfo(request.node, request.service, &request.hints, &result);
if (expectation.is_error()) {
ASSERT_EQ(ret, expectation.error_value())
<< "got: " << gai_strerror(ret) << "want: " << gai_strerror(expectation.error_value());
return;
}
ASSERT_EQ(ret, 0) << gai_strerror(ret);
}
auto cleanup = fit::defer([result]() { freeaddrinfo(result); });
const GetAddrInfoResponse& response = expectation.value();
size_t i = 0;
for (addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
const addrinfo& r = *rp;
EXPECT_EQ(r.ai_flags, request.hints.ai_flags);
EXPECT_EQ(r.ai_family, request.hints.ai_family);
EXPECT_EQ(r.ai_socktype, request.hints.ai_socktype);
switch (request.hints.ai_socktype) {
case SOCK_STREAM: {
EXPECT_EQ(r.ai_protocol, IPPROTO_TCP);
} break;
case SOCK_DGRAM: {
EXPECT_EQ(r.ai_protocol, IPPROTO_UDP);
} break;
default:
FAIL() << "unexpected ai_socktype: " << request.hints.ai_socktype;
}
switch (request.hints.ai_family) {
case AF_INET: {
ASSERT_EQ(r.ai_addrlen, sizeof(sockaddr_in));
const auto& addr = *reinterpret_cast<const sockaddr_in*>(r.ai_addr);
EXPECT_EQ(addr.sin_family, request.hints.ai_family);
EXPECT_EQ(ntohs(addr.sin_port), response.port);
char buf[INET_ADDRSTRLEN];
ASSERT_NE(inet_ntop(r.ai_family, &addr.sin_addr, buf, sizeof(buf)), nullptr)
<< strerror(errno);
EXPECT_STREQ(buf, response.address);
} break;
case AF_INET6: {
ASSERT_EQ(r.ai_addrlen, sizeof(sockaddr_in6));
const auto& addr = *reinterpret_cast<const sockaddr_in6*>(r.ai_addr);
EXPECT_EQ(addr.sin6_family, request.hints.ai_family);
EXPECT_EQ(ntohs(addr.sin6_port), response.port);
char buf[INET6_ADDRSTRLEN];
ASSERT_NE(inet_ntop(r.ai_family, &addr.sin6_addr, buf, sizeof(buf)), nullptr)
<< strerror(errno);
EXPECT_STREQ(buf, response.address);
} break;
default:
FAIL() << "unexpected ai_family: " << request.hints.ai_family;
}
EXPECT_STREQ(r.ai_canonname, request.node);
i++;
}
EXPECT_EQ(i, response.count);
}
INSTANTIATE_TEST_SUITE_P(
Basic, GetAddrInfoTest,
testing::Values(std::make_tuple(GetAddrInfoRequest{.node = "example.com",
.service = "http",
.hints =
{
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
}},
fit::ok(GetAddrInfoResponse{
.port = 80,
.address = "192.0.2.1",
.count = 1,
})),
std::make_tuple(GetAddrInfoRequest{.node = "example.com",
.service = "ntp",
.hints =
{
.ai_family = AF_INET6,
.ai_socktype = SOCK_DGRAM,
}},
fit::ok(GetAddrInfoResponse{
.port = 123,
.address = "2001:db8::1",
.count = 1,
})),
std::make_tuple(GetAddrInfoRequest{.node = "lotsofrecords.com",
.service = "http",
.hints =
{
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
}},
fit::ok(GetAddrInfoResponse{
.port = 80,
.address = "192.0.2.1",
.count = MAXADDRS,
})),
std::make_tuple(GetAddrInfoRequest{.node = "google.com",
.service = "http",
.hints =
{
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
}},
fit::error(EAI_NONAME))),
[](const testing::TestParamInfo<GetAddrInfoTest::ParamType>& info) {
return to_string(info.param);
});