blob: 458bbf40c6f76b4e71c2b846dc9beeb54538151a [file] [log] [blame]
// Copyright 2022 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 <net/if.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <algorithm>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include "constants.h"
namespace {
struct SendIpv6PacketInfoSuccessTestCase {
std::string test_name;
std::optional<std::string> send_local_addr_str;
std::optional<std::string> send_local_if_str;
std::string expected_recv_addr_str;
};
class SendIpv6PacketInfoSuccessTest
: public testing::TestWithParam<SendIpv6PacketInfoSuccessTestCase> {};
TEST_P(SendIpv6PacketInfoSuccessTest, SendAndRecv) {
fbl::unique_fd s;
ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(s.get(), SOL_IPV6, IPV6_RECVPKTINFO, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
const sockaddr_in6 bind_addr{
.sin6_family = AF_INET6,
};
ASSERT_EQ(bind(s.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)), 0)
<< strerror(errno);
sockaddr_in6 to_addr = {
.sin6_family = AF_INET6,
.sin6_port = htons(kServerPort),
};
ASSERT_EQ(inet_pton(to_addr.sin6_family, kServerIpv6Addr, &to_addr.sin6_addr), 1);
const auto [test_name, send_local_addr_str, send_local_if_str, expected_recv_addr_str] =
GetParam();
in6_addr expected_recv_addr;
ASSERT_EQ(inet_pton(AF_INET6, expected_recv_addr_str.c_str(), &expected_recv_addr), 1);
in6_pktinfo send_pktinfo = {};
if (send_local_addr_str.has_value()) {
ASSERT_EQ(inet_pton(AF_INET6, send_local_addr_str->c_str(), &send_pktinfo.ipi6_addr), 1);
}
if (send_local_if_str.has_value()) {
send_pktinfo.ipi6_ifindex = if_nametoindex(send_local_if_str->c_str());
}
char send_buf[] = "hello";
iovec send_iovec = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
char send_control[CMSG_SPACE(sizeof(send_pktinfo)) + 1];
msghdr send_msghdr = {
.msg_name = &to_addr,
.msg_namelen = sizeof(to_addr),
.msg_iov = &send_iovec,
.msg_iovlen = 1,
.msg_control = send_control,
.msg_controllen = sizeof(send_control),
};
cmsghdr* cmsg = CMSG_FIRSTHDR(&send_msghdr);
ASSERT_NE(cmsg, nullptr);
cmsg->cmsg_len = CMSG_LEN(sizeof(send_pktinfo));
cmsg->cmsg_level = SOL_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
memcpy(CMSG_DATA(cmsg), &send_pktinfo, sizeof(send_pktinfo));
ASSERT_EQ(sendmsg(s.get(), &send_msghdr, 0), static_cast<ssize_t>(sizeof(send_buf)))
<< strerror(errno);
constexpr char kExpectedRecvBuf[] = "Response: hello";
in6_pktinfo recv_pktinfo;
char recv_buf[sizeof(kExpectedRecvBuf) + 1];
iovec recv_iovec = {
.iov_base = recv_buf,
.iov_len = sizeof(recv_buf),
};
char recv_control[CMSG_SPACE(sizeof(recv_pktinfo)) + 1];
msghdr recv_msghdr = {
.msg_iov = &recv_iovec,
.msg_iovlen = 1,
.msg_control = recv_control,
.msg_controllen = sizeof(recv_control),
};
ASSERT_EQ(recvmsg(s.get(), &recv_msghdr, 0), static_cast<ssize_t>(sizeof(kExpectedRecvBuf)))
<< strerror(errno);
EXPECT_EQ(std::string_view(kExpectedRecvBuf, sizeof(kExpectedRecvBuf)),
std::string_view(recv_buf, sizeof(kExpectedRecvBuf)));
cmsg = CMSG_FIRSTHDR(&recv_msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(recv_pktinfo)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_PKTINFO);
memcpy(&recv_pktinfo, CMSG_DATA(cmsg), sizeof(recv_pktinfo));
EXPECT_EQ(memcmp(&recv_pktinfo.ipi6_addr, &expected_recv_addr, sizeof(expected_recv_addr)), 0);
}
INSTANTIATE_TEST_SUITE_P(SocketTests, SendIpv6PacketInfoSuccessTest,
testing::Values(
SendIpv6PacketInfoSuccessTestCase{
.test_name = "NIC1 local address",
.send_local_addr_str = kClientIpv6Addr1,
.expected_recv_addr_str = kClientIpv6Addr1,
},
SendIpv6PacketInfoSuccessTestCase{
.test_name = "NIC1 local interface",
.send_local_if_str = kClientNic1Name,
.expected_recv_addr_str = kClientIpv6Addr1,
},
SendIpv6PacketInfoSuccessTestCase{
.test_name = "NIC1 local address and interface",
.send_local_addr_str = kClientIpv6Addr1,
.send_local_if_str = kClientNic1Name,
.expected_recv_addr_str = kClientIpv6Addr1,
},
SendIpv6PacketInfoSuccessTestCase{
.test_name = "NIC2 local address",
.send_local_addr_str = kClientIpv6Addr2,
.expected_recv_addr_str = kClientIpv6Addr2,
},
SendIpv6PacketInfoSuccessTestCase{
.test_name = "NIC2 local interface",
.send_local_if_str = kClientNic2Name,
.expected_recv_addr_str = kClientIpv6Addr2,
},
SendIpv6PacketInfoSuccessTestCase{
.test_name = "NIC2 local address and interface",
.send_local_addr_str = kClientIpv6Addr2,
.send_local_if_str = kClientNic2Name,
.expected_recv_addr_str = kClientIpv6Addr2,
}),
[](const testing::TestParamInfo<SendIpv6PacketInfoSuccessTestCase>& info) {
std::string test_name(info.param.test_name);
std::replace(test_name.begin(), test_name.end(), ' ', '_');
return test_name;
});
struct SendIpv6PacketInfoFailureTestCase {
std::string test_name;
std::string bind_addr_str;
std::optional<std::string> bind_to_device;
std::optional<std::string> send_local_addr_str;
std::optional<std::string> send_local_if_str;
ssize_t expected_errno;
};
class SendIpv6PacketInfoFailureTest
: public testing::TestWithParam<SendIpv6PacketInfoFailureTestCase> {};
TEST_P(SendIpv6PacketInfoFailureTest, CheckError) {
fbl::unique_fd s;
ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
const auto [test_name, bind_addr_str, bind_to_device, send_local_addr_str, send_local_if_str,
expected_errno] = GetParam();
if (bind_to_device.has_value()) {
ASSERT_EQ(setsockopt(s.get(), SOL_SOCKET, SO_BINDTODEVICE, bind_to_device->c_str(),
static_cast<socklen_t>(bind_to_device->size())),
0)
<< strerror(errno);
}
sockaddr_in6 bind_addr{
.sin6_family = AF_INET6,
};
ASSERT_EQ(inet_pton(bind_addr.sin6_family, bind_addr_str.c_str(), &bind_addr.sin6_addr), 1);
ASSERT_EQ(bind(s.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)), 0)
<< strerror(errno);
sockaddr_in6 to_addr = {
.sin6_family = AF_INET6,
.sin6_port = htons(kServerPort),
};
ASSERT_EQ(inet_pton(to_addr.sin6_family, kServerIpv6Addr, &to_addr.sin6_addr), 1);
in6_pktinfo send_pktinfo = {};
if (send_local_addr_str.has_value()) {
ASSERT_EQ(inet_pton(AF_INET6, send_local_addr_str->c_str(), &send_pktinfo.ipi6_addr), 1);
}
if (send_local_if_str.has_value()) {
send_pktinfo.ipi6_ifindex = if_nametoindex(send_local_if_str->c_str());
}
char send_buf[] = "hello";
iovec send_iovec = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
char send_control[CMSG_SPACE(sizeof(send_pktinfo)) + 1];
msghdr send_msghdr = {
.msg_name = &to_addr,
.msg_namelen = sizeof(to_addr),
.msg_iov = &send_iovec,
.msg_iovlen = 1,
.msg_control = send_control,
.msg_controllen = sizeof(send_control),
};
cmsghdr* cmsg = CMSG_FIRSTHDR(&send_msghdr);
ASSERT_NE(cmsg, nullptr);
cmsg->cmsg_len = CMSG_LEN(sizeof(send_pktinfo));
cmsg->cmsg_level = SOL_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
memcpy(CMSG_DATA(cmsg), &send_pktinfo, sizeof(send_pktinfo));
ASSERT_EQ(sendmsg(s.get(), &send_msghdr, 0), -1);
EXPECT_EQ(errno, expected_errno) << strerror(errno);
}
constexpr char kIpv6UnspecifiedAddr[] = "::";
INSTANTIATE_TEST_SUITE_P(SocketTests, SendIpv6PacketInfoFailureTest,
testing::Values(
SendIpv6PacketInfoFailureTestCase{
.test_name = "Local interface and bound device mismatch",
.bind_addr_str = kIpv6UnspecifiedAddr,
.bind_to_device = kClientNic2Name,
.send_local_if_str = kClientNic1Name,
.expected_errno = EHOSTUNREACH,
},
SendIpv6PacketInfoFailureTestCase{
.test_name = "Local address and bound device mismatch",
.bind_addr_str = kIpv6UnspecifiedAddr,
.bind_to_device = kClientNic2Name,
.send_local_addr_str = kClientIpv6Addr1,
.expected_errno = EADDRNOTAVAIL,
},
SendIpv6PacketInfoFailureTestCase{
.test_name = "Local addr and interface mismatch",
.bind_addr_str = kIpv6UnspecifiedAddr,
.send_local_addr_str = kClientIpv6Addr2,
.send_local_if_str = kClientNic1Name,
.expected_errno = EADDRNOTAVAIL,
},
SendIpv6PacketInfoFailureTestCase{
.test_name = "Bound address and local interface mismatch",
.bind_addr_str = kClientIpv6Addr1,
.send_local_if_str = kClientNic2Name,
.expected_errno = EADDRNOTAVAIL}),
[](const testing::TestParamInfo<SendIpv6PacketInfoFailureTestCase>& info) {
std::string test_name(info.param.test_name);
std::replace(test_name.begin(), test_name.end(), ' ', '_');
return test_name;
});
} // namespace