blob: 7f33e76dea48828c977072a0f92510e1fd33adcb [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.
// Fuchsia's BSD socket tests ensure that fdio and Netstack together produce
// POSIX-like behavior. This module contains tests that exclusively test
// SOCK_DGRAM sockets.
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#include <sys/socket.h>
#include <array>
#include <future>
#include <latch>
#include <fbl/unaligned.h>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#if defined(__Fuchsia__)
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <zircon/status.h>
#include "src/connectivity/network/netstack/udp_serde/udp_serde.h"
#endif
#include "src/connectivity/network/tests/os.h"
#include "util.h"
#ifndef SO_BINDTOIFINDEX
// Our host toolchain's <sys/socket.h> appears not to define SO_BINDTOIFINDEX.
#define SO_BINDTOIFINDEX 62
#endif
// TODO(C++20): Remove this; std::chrono::duration defines operator<< in c++20. See
// https://en.cppreference.com/w/cpp/chrono/duration/operator_ltlt.
namespace std::chrono {
template <class Rep, class Period>
void PrintTo(const std::chrono::duration<Rep, Period>& duration, std::ostream* os) {
*os << std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() << "ns";
}
} // namespace std::chrono
namespace {
#if defined(__Fuchsia__)
// Saturates `recvfd`'s receive buffers by writing `sendbuf` to `sendfd` N times using `io_method`.
// `sendbuf` is resized and N picked to ensure that:
// - `recvfd`'s associated zircon socket contains `sizeof(kernel buf) - remainder` bytes, where
// 0 < `remainder` < payload size.
// - `recvfd`'s associated Netstack receive buffer contains `M * payload size` bytes, where
// `M` > 0.
// After completion, payloads read out of `recvfd` may be compared against `sendbuf`.
void FillRxBuffersLeavingRemainderInZirconSocket(const fbl::unique_fd& recvfd,
const fbl::unique_fd& sendfd,
const IOMethod& io_method,
std::vector<char>& sendbuf) {
// Start with the maximum datagram payload size, derived as:
// 65535 bytes (max IP packet size) - 20 bytes (IPv4 header) - 8 bytes (UDP header)
size_t payload_size = 65507;
size_t recv_capacity;
ASSERT_NO_FATAL_FAILURE(RxCapacity(recvfd.get(), recv_capacity));
zx_info_socket_t zx_socket_info;
ASSERT_NO_FATAL_FAILURE(ZxSocketInfoDgram(recvfd.get(), zx_socket_info));
// Pick a payload size which is less than the maximum datagram payload size and ensures
// that the zircon socket has a remainder, as described above.
payload_size = std::min(payload_size, zx_socket_info.rx_buf_max - kRxUdpPreludeSize);
while (payload_size > 0) {
size_t total_size = payload_size + kRxUdpPreludeSize;
if (zx_socket_info.rx_buf_max % total_size != 0) {
break;
}
--payload_size;
}
if (payload_size == 0) {
FAIL() << "couldn't find valid UDP payload size for which (zx_socket_info.rx_buf_max % "
"payload size) != 0";
}
// It's possible that the receiver's Netstack receive buffer will fill up even when its zircon
// socket still has space (because the shuttling routines have lagged). When this happens, the
// receiver will drop inbound packets; if enough packets are dropped, we might fail to fill up
// the zircon socket. To avoid this scenario, send significantly more than the receiver's total
// Rx capacity.
size_t payload_count = (2 * recv_capacity) / payload_size;
sendbuf = std::vector<char>(payload_size, 'a');
while (payload_count > 0) {
ASSERT_EQ(io_method.ExecuteIO(sendfd.get(), sendbuf.data(), sendbuf.size()),
ssize_t(sendbuf.size()))
<< strerror(errno);
--payload_count;
}
}
#endif // defined(__Fuchsia__)
void SetUpBoundAndConnectedDatagramSockets(const SocketDomain& domain, fbl::unique_fd& bindfd,
fbl::unique_fd& connectfd) {
ASSERT_TRUE(bindfd = fbl::unique_fd(socket(domain.Get(), SOCK_DGRAM, 0))) << strerror(errno);
auto [addr, addrlen] = LoopbackSockaddrAndSocklenForDomain(domain);
ASSERT_EQ(bind(bindfd.get(), reinterpret_cast<const sockaddr*>(&addr), addrlen), 0)
<< strerror(errno);
{
socklen_t bound_addrlen = addrlen;
ASSERT_EQ(getsockname(bindfd.get(), reinterpret_cast<sockaddr*>(&addr), &bound_addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, bound_addrlen);
}
ASSERT_TRUE(connectfd = fbl::unique_fd(socket(domain.Get(), SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(connect(connectfd.get(), reinterpret_cast<sockaddr*>(&addr), addrlen), 0)
<< strerror(errno);
}
void ExpectNoPollin(int fd) {
pollfd pfd = {
.fd = fd,
.events = POLLIN,
};
int n = poll(&pfd, 1, std::chrono::milliseconds(std::chrono::seconds(1)).count());
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 0);
}
template <typename T>
void SendWithCmsg(int sock, char* buf, size_t buf_size, int cmsg_level, int cmsg_type,
T cmsg_value) {
iovec iov = {
.iov_base = buf,
.iov_len = buf_size,
};
std::array<uint8_t, CMSG_SPACE(sizeof(cmsg_value))> control;
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control.data(),
.msg_controllen = CMSG_LEN(sizeof(cmsg_value)),
};
// Manually add control message.
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(cmsg, nullptr);
*cmsg = {
.cmsg_len = CMSG_LEN(sizeof(cmsg_value)),
.cmsg_level = cmsg_level,
.cmsg_type = cmsg_type,
};
memcpy(CMSG_DATA(cmsg), &cmsg_value, sizeof(cmsg_value));
// Verify that message doesn't extend past the data block.
uint8_t* data = CMSG_DATA(cmsg);
uint8_t* data_end = data + sizeof(cmsg_value);
uint8_t* msg_end = reinterpret_cast<uint8_t*>(cmsg) + cmsg->cmsg_len;
EXPECT_EQ(data_end, msg_end);
const ssize_t r = sendmsg(sock, &msg, 0);
ASSERT_NE(r, -1) << strerror(errno);
ASSERT_EQ(r, ssize_t(buf_size));
}
TEST(LocalhostTest, SendToZeroPort) {
sockaddr_in addr = LoopbackSockaddrV4(0);
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(sendto(fd.get(), nullptr, 0, 0, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)),
-1);
ASSERT_EQ(errno, EINVAL) << strerror(errno);
addr.sin_port = htons(1234);
ASSERT_EQ(sendto(fd.get(), nullptr, 0, 0, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)),
0)
<< strerror(errno);
}
TEST(LocalhostTest, DatagramSocketIgnoresMsgWaitAll) {
fbl::unique_fd recvfd;
ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0)))
<< strerror(errno);
sockaddr_in addr = LoopbackSockaddrV4(0);
ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
ASSERT_EQ(recvfrom(recvfd.get(), nullptr, 0, MSG_WAITALL, nullptr, nullptr), -1);
ASSERT_EQ(errno, EAGAIN) << strerror(errno);
EXPECT_EQ(close(recvfd.release()), 0) << strerror(errno);
}
TEST(LocalhostTest, DatagramSocketSendMsgNameLenTooBig) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in addr = {
.sin_family = AF_INET,
};
msghdr msg = {
.msg_name = &addr,
.msg_namelen = sizeof(sockaddr_storage) + 1,
};
ASSERT_EQ(sendmsg(fd.get(), &msg, 0), -1);
ASSERT_EQ(errno, EINVAL) << strerror(errno);
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
TEST(LocalhostTest, DatagramSocketAtOOBMark) {
fbl::unique_fd client;
ASSERT_TRUE(client = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
// sockatmark is not supported on datagram sockets on Linux or Fuchsia.
// It is on macOS.
EXPECT_EQ(sockatmark(client.get()), -1);
// This should be ENOTTY per POSIX:
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/sockatmark.html
EXPECT_EQ(errno, ENOTTY) << strerror(errno);
}
TEST(LocalhostTest, BindToDevice) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
{
// The default is that a socket is not bound to a device.
char get_dev[IFNAMSIZ] = {};
socklen_t get_dev_length = sizeof(get_dev);
EXPECT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, get_dev, &get_dev_length), 0)
<< strerror(errno);
EXPECT_EQ(get_dev_length, socklen_t(0));
EXPECT_STREQ(get_dev, "");
}
const char set_dev[IFNAMSIZ] = "lo\0blahblah";
// Bind to "lo" with null termination should work even if the size is too big.
ASSERT_EQ(setsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, set_dev, sizeof(set_dev)), 0)
<< strerror(errno);
const char set_dev_unknown[] = "loblahblahblah";
// Bind to "lo" without null termination but with accurate length should work.
{
int ret = setsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, set_dev_unknown, 2);
if (kIsFuchsia) {
EXPECT_EQ(ret, 0) << strerror(errno);
} else {
// We may get EPERM if we lack sufficient privileges.
EXPECT_TRUE(ret == 0 || errno == EPERM) << strerror(errno);
}
}
// Bind to unknown name should fail.
EXPECT_EQ(
setsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, set_dev_unknown, sizeof(set_dev_unknown)),
-1);
EXPECT_EQ(errno, ENODEV) << strerror(errno);
{
// Reading it back should work.
char get_dev[IFNAMSIZ] = {};
socklen_t get_dev_length = sizeof(get_dev);
EXPECT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, get_dev, &get_dev_length), 0)
<< strerror(errno);
EXPECT_EQ(get_dev_length, strlen(set_dev) + 1);
EXPECT_STREQ(get_dev, set_dev);
}
{
// Reading it back without enough space in the buffer should fail.
char get_dev[] = "";
socklen_t get_dev_length = sizeof(get_dev);
EXPECT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, get_dev, &get_dev_length), -1);
EXPECT_EQ(errno, EINVAL) << strerror(errno);
EXPECT_EQ(get_dev_length, sizeof(get_dev));
EXPECT_STREQ(get_dev, "");
}
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
TEST(LocalhostTest, BindToInterfaceIndex) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
{
// The default is that a socket is not bound to a device.
int32_t get_dev = 0;
socklen_t get_dev_length = sizeof(get_dev);
EXPECT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_BINDTOIFINDEX, &get_dev, &get_dev_length), 0)
<< strerror(errno);
EXPECT_EQ(get_dev_length, socklen_t(sizeof(get_dev)));
EXPECT_EQ(get_dev, 0);
}
// Bind to 1 should work (assuming loopback interface exists).
const int32_t set_dev = 1;
{
ASSERT_EQ(setsockopt(fd.get(), SOL_SOCKET, SO_BINDTOIFINDEX, &set_dev, sizeof(set_dev)), 0)
<< strerror(errno);
}
if (kIsFuchsia) {
// On Fuchsia, binding to unknown interface should fail.
const int32_t set_dev_unknown = 42;
EXPECT_EQ(setsockopt(fd.get(), SOL_SOCKET, SO_BINDTOIFINDEX, &set_dev_unknown,
sizeof(set_dev_unknown)),
-1);
EXPECT_EQ(errno, ENODEV) << strerror(errno);
}
{
// Reading it back should work.
int32_t get_dev;
socklen_t get_dev_length = sizeof(get_dev);
EXPECT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_BINDTOIFINDEX, &get_dev, &get_dev_length), 0)
<< strerror(errno);
EXPECT_EQ(get_dev_length, socklen_t(sizeof(get_dev)));
EXPECT_EQ(get_dev, set_dev);
}
// Once a bound device is set, unsetting it requires CAP_NET_RAW on Linux.
SKIP_IF_CANT_ACCESS_RAW_SOCKETS();
{
// Binding to 0 should unset the bound device.
const int32_t unset_dev = 0;
ASSERT_EQ(setsockopt(fd.get(), SOL_SOCKET, SO_BINDTOIFINDEX, &unset_dev, sizeof(unset_dev)), 0)
<< strerror(errno);
int32_t get_dev = 42;
socklen_t get_dev_length = sizeof(get_dev);
EXPECT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_BINDTOIFINDEX, &get_dev, &get_dev_length), 0)
<< strerror(errno);
EXPECT_EQ(get_dev_length, socklen_t(sizeof(get_dev)));
EXPECT_EQ(get_dev, unset_dev);
}
// Setting using a wider type for interface IDs happens to work, so it's good
// to have a test documenting this is the case. This is discouraged, though.
const uint64_t set_dev_wide = 1;
{
ASSERT_EQ(
setsockopt(fd.get(), SOL_SOCKET, SO_BINDTOIFINDEX, &set_dev_wide, sizeof(set_dev_wide)), 0)
<< strerror(errno);
}
// Reading using a wider type also happens to work, but is likewise
// discouraged.
{
uint64_t get_dev = 0;
socklen_t get_dev_length = sizeof(get_dev);
EXPECT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_BINDTOIFINDEX, &get_dev, &get_dev_length), 0)
<< strerror(errno);
EXPECT_EQ(get_dev_length, socklen_t(sizeof(uint32_t)));
EXPECT_EQ(get_dev, set_dev_wide);
}
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
TEST(LocalhostTest, IpAddMembershipAny) {
fbl::unique_fd s;
ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
ip_mreqn param = {
.imr_address =
{
.s_addr = htonl(INADDR_ANY),
},
.imr_ifindex = 1,
};
int n = inet_pton(AF_INET, "224.0.2.1", &param.imr_multiaddr.s_addr);
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 1);
ASSERT_EQ(setsockopt(s.get(), SOL_IP, IP_ADD_MEMBERSHIP, &param, sizeof(param)), 0)
<< strerror(errno);
EXPECT_EQ(close(s.release()), 0) << strerror(errno);
}
TEST(LocalhostTest, IpAddMembershipInvalidIface) {
fbl::unique_fd s;
ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
ip_mreqn param = {
.imr_address =
{
.s_addr = htonl(INADDR_ANY),
},
.imr_ifindex = -1,
};
int n = inet_pton(AF_INET, "224.0.2.1", &param.imr_multiaddr.s_addr);
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 1);
ASSERT_EQ(setsockopt(s.get(), SOL_IP, IP_ADD_MEMBERSHIP, &param, sizeof(param)), -1);
ASSERT_EQ(errno, ENODEV);
EXPECT_EQ(close(s.release()), 0) << strerror(errno);
}
TEST(LocalhostTest, ConnectAFMismatchINET) {
fbl::unique_fd s;
ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
sockaddr_in6 addr = LoopbackSockaddrV6(1337);
EXPECT_EQ(connect(s.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), -1);
EXPECT_EQ(errno, EAFNOSUPPORT) << strerror(errno);
EXPECT_EQ(close(s.release()), 0) << strerror(errno);
}
TEST(LocalhostTest, ConnectAFMismatchINET6) {
fbl::unique_fd s;
ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
sockaddr_in addr = LoopbackSockaddrV4(1337);
EXPECT_EQ(connect(s.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
EXPECT_EQ(close(s.release()), 0) << strerror(errno);
}
TEST(DatagramSocketTest, UnsupportedOps) {
fbl::unique_fd s;
ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
EXPECT_EQ(listen(s.get(), 0), -1);
EXPECT_EQ(errno, EOPNOTSUPP) << strerror(errno);
EXPECT_EQ(accept(s.get(), nullptr, nullptr), -1);
EXPECT_EQ(errno, EOPNOTSUPP) << strerror(errno);
}
class IOMethodTest : public testing::TestWithParam<IOMethod> {};
TEST_P(IOMethodTest, NullptrFaultDGRAM) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0)))
<< strerror(errno);
const sockaddr_in addr = LoopbackSockaddrV4(1235);
ASSERT_EQ(bind(fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
ASSERT_EQ(connect(fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
DoNullPtrIO(fd, fd, GetParam(), true);
}
INSTANTIATE_TEST_SUITE_P(IOMethodTests, IOMethodTest, testing::ValuesIn(kAllIOMethods),
[](const auto info) { return info.param.IOMethodToString(); });
class IOReadingMethodTest : public testing::TestWithParam<IOMethod> {};
TEST_P(IOReadingMethodTest, DatagramSocketErrorWhileBlocked) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
{
// Connect to an existing remote but on a port that is not being used.
sockaddr_in addr = LoopbackSockaddrV4(1337);
ASSERT_EQ(connect(fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
}
std::latch fut_started(1);
const auto fut = std::async(std::launch::async, [&, read_method = GetParam()]() {
fut_started.count_down();
char bytes[1];
// Block while waiting for data to be received.
ASSERT_EQ(read_method.ExecuteIO(fd.get(), bytes, sizeof(bytes)), -1);
ASSERT_EQ(errno, ECONNREFUSED) << strerror(errno);
});
fut_started.wait();
ASSERT_NO_FATAL_FAILURE(AssertBlocked(fut));
{
// Precondition sanity check: no pending events on the socket.
pollfd pfd = {
.fd = fd.get(),
};
int n = poll(&pfd, 1, 0);
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 0);
}
char bytes[1];
// Send a UDP packet to trigger a port unreachable response.
ASSERT_EQ(send(fd.get(), bytes, sizeof(bytes), 0), ssize_t(sizeof(bytes))) << strerror(errno);
// The blocking recv call should terminate with an error.
ASSERT_EQ(fut.wait_for(kDeprecatedTimeout), std::future_status::ready);
{
// Postcondition sanity check: no pending events on the socket, the POLLERR should've been
// cleared by the read_method call.
pollfd pfd = {
.fd = fd.get(),
};
int n = poll(&pfd, 1, 0);
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 0);
}
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
INSTANTIATE_TEST_SUITE_P(IOReadingMethodTests, IOReadingMethodTest,
testing::ValuesIn(kRecvIOMethods),
[](const testing::TestParamInfo<IOMethod>& info) {
return info.param.IOMethodToString();
});
class DatagramSocketErrBase {
protected:
static void SetUpSocket(fbl::unique_fd& fd, bool nonblocking) {
int flags = 0;
if (nonblocking) {
flags |= SOCK_NONBLOCK;
}
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM | flags, 0))) << strerror(errno);
ASSERT_NO_FATAL_FAILURE(BindLoopback(fd));
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(fd));
}
static void BindLoopback(const fbl::unique_fd& fd) {
{
sockaddr_in addr = LoopbackSockaddrV4(0);
ASSERT_EQ(bind(fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
}
}
static void ConnectTo(const fbl::unique_fd& send_fd, const fbl::unique_fd& fd) {
{
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(fd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(sockaddr_in));
ASSERT_EQ(connect(send_fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
}
}
static void PollForPollerr(const fbl::unique_fd& fd) {
pollfd pfd = {
.fd = fd.get(),
};
const int n = poll(&pfd, 1, std::chrono::milliseconds(kDeprecatedTimeout).count());
ASSERT_GE(n, 0) << strerror(errno);
EXPECT_EQ(n, 1);
EXPECT_EQ(pfd.revents & POLLERR, POLLERR);
}
static void TriggerICMPUnreachable(const fbl::unique_fd& fd) {
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachableNoPoll(fd));
ASSERT_NO_FATAL_FAILURE(PollForPollerr(fd));
}
static void SendToUnreachableAddr(const fbl::unique_fd& fd, bool connect) {
fbl::unique_fd other_fd;
ASSERT_NO_FATAL_FAILURE(SetUpSocket(other_fd, false));
if (connect) {
ASSERT_NO_FATAL_FAILURE(ConnectTo(fd, other_fd));
}
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(other_fd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
// Closing this socket ensures that `fd` ends up connected to an unbound port.
ASSERT_EQ(close(other_fd.release()), 0) << strerror(errno);
char bytes[1];
EXPECT_EQ(sendto(fd.get(), bytes, sizeof(bytes), 0, reinterpret_cast<const sockaddr*>(&addr),
addrlen),
ssize_t(sizeof(bytes)))
<< strerror(errno);
}
static void TriggerICMPUnreachableNoPoll(const fbl::unique_fd& fd) {
SendToUnreachableAddr(fd, true);
}
static void CheckNoPendingEvents(
const fbl::unique_fd& fd,
std::chrono::duration<int, std::chrono::milliseconds::period> timeout = {}) {
{
pollfd pfd = {
.fd = fd.get(),
.events = std::numeric_limits<decltype(pfd.events)>::max() &
~(POLLOUT | POLLWRNORM | POLLWRBAND),
};
const int n = poll(&pfd, 1, timeout.count());
ASSERT_GE(n, 0) << strerror(errno);
EXPECT_EQ(n, 0);
}
}
};
class DatagramSocketErrTest : public DatagramSocketErrBase, public testing::Test {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SetUpSocket(sendfd_, false));
ASSERT_NO_FATAL_FAILURE(SetUpSocket(recvfd_, false));
}
void TearDown() override {
EXPECT_EQ(close(sendfd_.release()), 0) << strerror(errno);
EXPECT_EQ(close(recvfd_.release()), 0) << strerror(errno);
}
const fbl::unique_fd& sendfd() const { return sendfd_; }
const fbl::unique_fd& recvfd() const { return recvfd_; }
private:
fbl::unique_fd sendfd_;
fbl::unique_fd recvfd_;
};
TEST_F(DatagramSocketErrTest, IcmpErrorsPropagatedDuringIOSpamSend) {
// Under the hood, Fuchsia sends datagram payloads using an asynchronous loop routine[1]
// that consumes errors surfaced by the networking library used internally by the Netstack.
// This test validates that those errors are correctly propagated to the client (rather than
// dropped on the floor) by triggering an ICMP error while the loop routine is processing a
// heavy load of outgoing payloads.
//
// The goal is to exercise and validate the following scenario:
//
// 1) Client sends payload on a socket
// 2) ICMP error arrives
// 3) Loop routine asynchronously enqueues payload into the Netstack and consumes
// the ICMP error
// 4) The error is propagated to the client and the payload is successfully sent
//
// [1]: https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0109_socket_datagram_socket
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(recvfd().get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(sockaddr_in));
// Trigger an ICMP error _without_ waiting for POLLERR. This makes it possible
// for a `send` below to enqueue a payload into the zircon socket before the error
// is signaled on the socket.
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachableNoPoll(sendfd()));
size_t total_errors = 0;
size_t total_sent = 0;
constexpr char buf[] = "b";
while (total_errors == 0 || total_sent == 0) {
ssize_t res =
sendto(sendfd().get(), buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&addr), addrlen);
if (res < 0) {
total_errors++;
EXPECT_EQ(errno, ECONNREFUSED);
} else {
total_sent++;
EXPECT_EQ(res, ssize_t(sizeof(buf)));
}
}
EXPECT_EQ(total_errors, static_cast<size_t>(1));
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(sendfd()));
// Expect that the loop routine successfully sent all outgoing packets in addition
// to returning the error.
for (size_t i = 0; i < total_sent; i++) {
char recv_buf[sizeof(buf) + 1];
EXPECT_EQ(read(recvfd().get(), recv_buf, sizeof(recv_buf)), ssize_t(sizeof(buf)))
<< strerror(errno);
EXPECT_EQ(std::string_view(recv_buf, sizeof(buf)), std::string_view(buf, sizeof(buf)));
}
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(recvfd()));
}
TEST_F(DatagramSocketErrTest, IcmpErrorsPropagatedDuringIOSpamRecv) {
// Under the hood, Fuchsia receives datagram payloads using an asynchronous loop routine[1]
// that consumes errors surfaced by the networking library used internally by the Netstack.
// This test validates that those errors are correctly propagated to the client (rather than
// dropped on the floor) by triggering an ICMP error while the loop routine is processing a
// heavy load of incoming payloads.
//
// The goal is to exercise and validate the following scenario:
//
// 1) ICMP error arrives on a socket
// 2) Payload arrives on a socket
// 3) Loop routine dequeues payload from the Netstack, consuming the ICMP error
// 4) Both the payload and the error are propagated to the client
//
// [1]: https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0109_socket_datagram_socket
ASSERT_NO_FATAL_FAILURE(ConnectTo(sendfd(), recvfd()));
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachable(recvfd()));
ASSERT_NO_FATAL_FAILURE(ConnectTo(recvfd(), sendfd()));
constexpr char buf[] = "b";
EXPECT_EQ(send(sendfd().get(), buf, sizeof(buf), 0), ssize_t(sizeof(buf))) << strerror(errno);
size_t total_errors = 0;
size_t total_received = 0;
char recv_buf[sizeof(buf) + 1];
while (total_errors == 0 || total_received == 0) {
ssize_t res = read(recvfd().get(), recv_buf, sizeof(recv_buf));
if (res < 0) {
total_errors++;
EXPECT_EQ(errno, ECONNREFUSED);
} else {
total_received++;
EXPECT_EQ(res, ssize_t(sizeof(buf)));
EXPECT_EQ(std::string_view(recv_buf, sizeof(buf)), std::string_view(buf, sizeof(buf)));
}
}
EXPECT_EQ(total_errors, static_cast<size_t>(1));
EXPECT_EQ(total_received, static_cast<size_t>(1));
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(recvfd()));
}
// Validate that ICMP errors can only be observed on datagram sockets when:
// (1) the socket is connected, AND
// (2) the error is triggered by a send to the connected address.
class IcmpErrorTest : public DatagramSocketErrBase, public testing::Test {
protected:
void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpSocket(fd_, false)); }
void TearDown() override { EXPECT_EQ(close(fd_.release()), 0) << strerror(errno); }
const fbl::unique_fd& fd() const { return fd_; }
private:
fbl::unique_fd fd_;
};
TEST_F(IcmpErrorTest, ErrObservableWhenConnectedSocketSendsToConnectedAddr) {
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachable(fd()));
char bytes[1];
EXPECT_EQ(send(fd().get(), bytes, sizeof(bytes), 0), -1);
EXPECT_EQ(errno, ECONNREFUSED) << strerror(errno);
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(fd()));
}
TEST_F(IcmpErrorTest, ErrNotObservableOnUnconnectedSocket) {
ASSERT_NO_FATAL_FAILURE(SendToUnreachableAddr(fd(), /*connect=*/false));
// Ensure that there is no error observable on the socket.
ASSERT_NO_FATAL_FAILURE(
CheckNoPendingEvents(fd(), std::chrono::milliseconds(kDeprecatedTimeout)));
}
TEST_F(IcmpErrorTest, ErrNotObservableWhenConnectedSocketSendsToUnconnectedAddr) {
fbl::unique_fd other_fd;
ASSERT_TRUE(other_fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_NO_FATAL_FAILURE(BindLoopback(other_fd));
ASSERT_NO_FATAL_FAILURE(ConnectTo(fd(), other_fd));
// Send to a different address than the one the socket is connected to.
ASSERT_NO_FATAL_FAILURE(SendToUnreachableAddr(fd(), /*connect=*/false));
// Ensure that there is no error observable on the socket.
ASSERT_NO_FATAL_FAILURE(
CheckNoPendingEvents(fd(), std::chrono::milliseconds(kDeprecatedTimeout)));
EXPECT_EQ(close(other_fd.release()), 0) << strerror(errno);
}
std::string nonBlockingToString(bool nonblocking) {
if (nonblocking) {
return "NonBlocking";
}
return "Blocking";
}
class DatagramSocketErrWithNonBlockingOptionTest : public DatagramSocketErrBase,
public testing::TestWithParam<bool> {};
TEST_P(DatagramSocketErrWithNonBlockingOptionTest, ClearsErrWithGetSockOpt) {
fbl::unique_fd fd;
ASSERT_NO_FATAL_FAILURE(SetUpSocket(fd, GetParam()));
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachable(fd));
// Clear error using `getsockopt`.
int err;
socklen_t optlen = sizeof(err);
ASSERT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), 0) << strerror(errno);
ASSERT_EQ(optlen, sizeof(err));
EXPECT_EQ(err, ECONNREFUSED) << strerror(err);
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(fd));
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
INSTANTIATE_TEST_SUITE_P(NetDatagramTest, DatagramSocketErrWithNonBlockingOptionTest,
testing::Values(false, true),
[](const testing::TestParamInfo<bool>& info) {
return nonBlockingToString(info.param);
});
using IOMethodNonBlockingOptionParams = std::tuple<IOMethod, bool>;
class DatagramSocketErrWithIOMethodBase : public DatagramSocketErrBase {
protected:
static void ExpectConnectionRefusedErr(const fbl::unique_fd& fd, const IOMethod& io_method) {
char bytes[1];
EXPECT_EQ(io_method.ExecuteIO(fd.get(), bytes, sizeof(bytes)), -1);
EXPECT_EQ(errno, ECONNREFUSED) << strerror(errno);
}
};
class DatagramSocketErrWithIOMethodNonBlockingOptionTest
: public DatagramSocketErrWithIOMethodBase,
public testing::TestWithParam<IOMethodNonBlockingOptionParams> {};
TEST_P(DatagramSocketErrWithIOMethodNonBlockingOptionTest, ClearsErrWithIO) {
fbl::unique_fd fd;
const auto& [io_method, nonblocking] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpSocket(fd, nonblocking));
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachable(fd));
ASSERT_NO_FATAL_FAILURE(ExpectConnectionRefusedErr(fd, io_method));
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(fd));
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
TEST_P(DatagramSocketErrWithIOMethodNonBlockingOptionTest,
ClearsErrWithIOAfterSendCacheInvalidated) {
// Datagram sockets using the Fast UDP protocol [1] use a single mechanism to
// 1) check for errors and 2) check the validity of elements in their cache.
// Here, we validate that signaled/sticky errors take precedence over cache
// errors.
//
// [1] https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0109_socket_datagram_socket
fbl::unique_fd fd;
const auto& [io_method, nonblocking] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpSocket(fd, nonblocking));
// Send to an unreachable port, which causes an ICMP error to be
// returned on the socket. In addition, it causes the socket to cache the
// destination address.
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachable(fd));
// Connecting the socket to a new destination invalidates the cached address.
ASSERT_NO_FATAL_FAILURE(ConnectTo(fd, fd));
// Expect socket I/O returns the received error.
ASSERT_NO_FATAL_FAILURE(ExpectConnectionRefusedErr(fd, io_method));
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(fd));
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
#if defined(__Fuchsia__)
TEST_P(DatagramSocketErrWithIOMethodNonBlockingOptionTest, ClearsErrWithIOAfterTransfer) {
fbl::unique_fd fd;
const auto& [io_method, nonblocking] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpSocket(fd, nonblocking));
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachable(fd));
// Create a second client from the existing file descriptor, and bind it to a
// new file descriptor. Now we have two file descriptors in the same process
// sharing a single netstack socket (and therefore zircon socket).
zx_handle_t handle;
zx_status_t status = fdio_fd_transfer(fd.release(), &handle);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
fbl::unique_fd second_fd;
status = fdio_fd_create(handle, second_fd.reset_and_get_address());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
// Expect that socket I/O returns the asynchronously received error. In the
// case of Tx I/O methods, this validates that such errors are returned even
// when the destination cache [1] is empty.
//
// [1] https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0109_socket_datagram_socket
ASSERT_NO_FATAL_FAILURE(ExpectConnectionRefusedErr(second_fd, io_method));
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(second_fd));
EXPECT_EQ(close(second_fd.release()), 0) << strerror(errno);
}
#endif
std::string IOMethodNonBlockingOptionParamsToString(
const testing::TestParamInfo<IOMethodNonBlockingOptionParams> info) {
auto const& [io_method, nonblocking] = info.param;
std::stringstream s;
s << nonBlockingToString(nonblocking);
s << io_method.IOMethodToString();
return s.str();
}
INSTANTIATE_TEST_SUITE_P(NetDatagramTest, DatagramSocketErrWithIOMethodNonBlockingOptionTest,
testing::Combine(testing::ValuesIn(kAllIOMethods),
testing::Values(false, true)),
IOMethodNonBlockingOptionParamsToString);
class DatagramSocketErrWithIOMethodAndReceivedDatagramBase
: public DatagramSocketErrWithIOMethodBase {
protected:
static void ExpectPollin(const fbl::unique_fd& fd) {
pollfd pfd = {
.fd = fd.get(),
.events = POLLIN,
};
const int n = poll(&pfd, 1, std::chrono::milliseconds(kDeprecatedTimeout).count());
ASSERT_GE(n, 0) << strerror(errno);
EXPECT_EQ(n, 1);
ASSERT_EQ(pfd.revents & POLLIN, POLLIN)
<< "expect pfd.revents contains POLLIN, found: " << pfd.revents;
}
};
class DatagramSocketErrWithIOMethodTest
: public DatagramSocketErrWithIOMethodAndReceivedDatagramBase,
public testing::TestWithParam<IOMethod> {};
TEST_P(DatagramSocketErrWithIOMethodTest, ClearsErrWithIOAfterDatagramReceived) {
fbl::unique_fd fd;
ASSERT_NO_FATAL_FAILURE(SetUpSocket(fd, false));
fbl::unique_fd send_fd;
ASSERT_TRUE(send_fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_NO_FATAL_FAILURE(ConnectTo(send_fd, fd));
// Send a datagram to `fd`.
constexpr char send_buf[] = "abc";
ASSERT_EQ(send(send_fd.get(), send_buf, sizeof(send_buf), 0), ssize_t(sizeof(send_buf)))
<< strerror(errno);
ASSERT_NO_FATAL_FAILURE(ExpectPollin(fd));
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachable(fd));
ASSERT_NO_FATAL_FAILURE(ExpectConnectionRefusedErr(fd, GetParam()));
// Now that the error has been consumed, consume the datagram.
char recv_buf[sizeof(send_buf) + 1];
ASSERT_EQ(read(fd.get(), recv_buf, sizeof(recv_buf)), ssize_t(sizeof(send_buf)))
<< strerror(errno);
EXPECT_EQ(std::string_view(recv_buf, sizeof(send_buf)),
std::string_view(send_buf, sizeof(send_buf)));
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(fd));
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
EXPECT_EQ(close(send_fd.release()), 0) << strerror(errno);
}
INSTANTIATE_TEST_SUITE_P(NetDatagramTest, DatagramSocketErrWithIOMethodTest,
testing::ValuesIn(kRecvIOMethods),
[](const testing::TestParamInfo<IOMethod>& info) {
return info.param.IOMethodToString();
});
using IOMethodCmsgCacheInvalidationParams = std::tuple<IOMethod, bool>;
class DatagramSocketErrWithIOMethodCmsgCacheInvalidationTest
: public DatagramSocketErrWithIOMethodAndReceivedDatagramBase,
public testing::TestWithParam<IOMethodCmsgCacheInvalidationParams> {};
TEST_P(DatagramSocketErrWithIOMethodCmsgCacheInvalidationTest, ClearsErrWithIOWithCmsgCache) {
// Datagram sockets using the Fast UDP protocol
// (https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0109_socket_datagram_socket)
// use a single mechanism to 1) check for errors and 2) check the validity of elements
// in their cache. Here, we validate that signaled/sticky errors take precedence
// over cache errors.
fbl::unique_fd fd;
const auto& [io_method, request_cmsg] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpSocket(fd, false));
fbl::unique_fd send_fd;
ASSERT_TRUE(send_fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_NO_FATAL_FAILURE(ConnectTo(send_fd, fd));
constexpr int kTtl = 42;
char send_buf[] = "abc";
ASSERT_EQ(setsockopt(send_fd.get(), SOL_IP, IP_TTL, &kTtl, sizeof(kTtl)), 0) << strerror(errno);
ASSERT_EQ(send(send_fd.get(), send_buf, sizeof(send_buf), 0), ssize_t(sizeof(send_buf)))
<< strerror(errno);
char control[CMSG_SPACE(sizeof(kTtl)) + 1];
char recv_buf[sizeof(send_buf) + 1];
iovec iovec = {
.iov_base = recv_buf,
.iov_len = sizeof(recv_buf),
};
msghdr msghdr = {
.msg_name = nullptr,
.msg_namelen = 0,
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
};
// Receive a datagram while providing space for control messages. This causes
// the socket to look up and cache the set of requested control messages.
EXPECT_EQ(recvmsg(fd.get(), &msghdr, 0), ssize_t(sizeof(send_buf))) << strerror(errno);
EXPECT_EQ(std::string_view(recv_buf, sizeof(send_buf)),
std::string_view(send_buf, sizeof(send_buf)));
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
ASSERT_EQ(send(send_fd.get(), send_buf, sizeof(send_buf), 0), ssize_t(sizeof(send_buf)))
<< strerror(errno);
ASSERT_NO_FATAL_FAILURE(ExpectPollin(fd));
// Send to an unreachable port, which causes an ICMP error to be
// returned on the socket.
ASSERT_NO_FATAL_FAILURE(TriggerICMPUnreachable(fd));
// Requesting a new cmsg invalidates the cache.
if (request_cmsg) {
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(fd.get(), SOL_IP, IP_RECVTTL, &kOne, sizeof(kOne)), 0) << strerror(errno);
}
// Expect socket I/O returns the received error.
ASSERT_NO_FATAL_FAILURE(ExpectConnectionRefusedErr(fd, io_method));
msghdr = {
.msg_name = nullptr,
.msg_namelen = 0,
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
};
EXPECT_EQ(recvmsg(fd.get(), &msghdr, 0), ssize_t(sizeof(send_buf))) << strerror(errno);
EXPECT_EQ(std::string_view(recv_buf, sizeof(send_buf)),
std::string_view(send_buf, sizeof(send_buf)));
// Expect that a cmsg is returned with the datagram iff it was previously requested.
if (request_cmsg) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(kTtl)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kTtl)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IP);
EXPECT_EQ(cmsg->cmsg_type, IP_TTL);
int recv_ttl;
memcpy(&recv_ttl, CMSG_DATA(cmsg), sizeof(recv_ttl));
EXPECT_EQ(recv_ttl, kTtl);
} else {
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
}
ASSERT_NO_FATAL_FAILURE(CheckNoPendingEvents(fd));
EXPECT_EQ(close(send_fd.release()), 0) << strerror(errno);
}
std::string IOMethodCmsgCacheInvalidationParamsToString(
const testing::TestParamInfo<IOMethodCmsgCacheInvalidationParams> info) {
auto const& [io_method, invalidate_cmsg_cache] = info.param;
std::stringstream s;
if (invalidate_cmsg_cache) {
s << "InvalidCmsgCache";
} else {
s << "ValidCmsgCache";
}
s << io_method.IOMethodToString();
return s.str();
}
INSTANTIATE_TEST_SUITE_P(NetDatagramTest, DatagramSocketErrWithIOMethodCmsgCacheInvalidationTest,
testing::Combine(testing::ValuesIn(kRecvIOMethods),
testing::Values(false, true)),
IOMethodCmsgCacheInvalidationParamsToString);
class DatagramSendTest : public testing::TestWithParam<IOMethod> {};
TEST_P(DatagramSendTest, SendToIPv4MappedIPv6FromIPv4) {
auto io_method = GetParam();
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in addr = LoopbackSockaddrV4(0);
ASSERT_EQ(bind(fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(fd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr));
sockaddr_in6 addr6 = MapIpv4SockaddrToIpv6Sockaddr(addr);
switch (io_method.Op()) {
case IOMethod::Op::SENDTO: {
ASSERT_EQ(
sendto(fd.get(), nullptr, 0, 0, reinterpret_cast<const sockaddr*>(&addr6), sizeof(addr6)),
-1);
ASSERT_EQ(errno, EAFNOSUPPORT) << strerror(errno);
break;
}
case IOMethod::Op::SENDMSG: {
msghdr msghdr = {
.msg_name = &addr6,
.msg_namelen = sizeof(addr6),
};
ASSERT_EQ(sendmsg(fd.get(), &msghdr, 0), -1);
ASSERT_EQ(errno, EAFNOSUPPORT) << strerror(errno);
break;
}
default: {
FAIL() << "unexpected test variant";
break;
}
}
}
TEST_P(DatagramSendTest, DatagramSend) {
auto io_method = GetParam();
fbl::unique_fd recvfd;
ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in addr = LoopbackSockaddrV4(0);
EXPECT_EQ(bind(recvfd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr);
EXPECT_EQ(getsockname(recvfd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
EXPECT_EQ(addrlen, sizeof(addr));
std::string msg = "hello";
char recvbuf[32] = {};
iovec iov = {
.iov_base = msg.data(),
.iov_len = msg.size(),
};
msghdr msghdr = {
.msg_name = &addr,
.msg_namelen = addrlen,
.msg_iov = &iov,
.msg_iovlen = 1,
};
fbl::unique_fd sendfd;
ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
switch (io_method.Op()) {
case IOMethod::Op::SENDTO: {
EXPECT_EQ(sendto(sendfd.get(), msg.data(), msg.size(), 0, reinterpret_cast<sockaddr*>(&addr),
addrlen),
ssize_t(msg.size()))
<< strerror(errno);
break;
}
case IOMethod::Op::SENDMSG: {
EXPECT_EQ(sendmsg(sendfd.get(), &msghdr, 0), ssize_t(msg.size())) << strerror(errno);
break;
}
default: {
FAIL() << "unexpected test variant";
break;
}
}
auto start = std::chrono::steady_clock::now();
EXPECT_EQ(asyncSocketRead(recvfd.get(), sendfd.get(), recvbuf, sizeof(recvbuf), 0,
SocketType::Dgram(), SocketDomain::IPv4(), kDeprecatedTimeout),
ssize_t(msg.size()));
auto success_rcv_duration = std::chrono::steady_clock::now() - start;
EXPECT_EQ(std::string_view(recvbuf, msg.size()), msg);
EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno);
// sendto/sendmsg on connected sockets does accept sockaddr input argument and
// also lets the dest sockaddr be overridden from what was passed for connect.
ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
EXPECT_EQ(connect(sendfd.get(), reinterpret_cast<sockaddr*>(&addr), addrlen), 0)
<< strerror(errno);
switch (io_method.Op()) {
case IOMethod::Op::SENDTO: {
EXPECT_EQ(sendto(sendfd.get(), msg.data(), msg.size(), 0, reinterpret_cast<sockaddr*>(&addr),
addrlen),
ssize_t(msg.size()))
<< strerror(errno);
break;
}
case IOMethod::Op::SENDMSG: {
EXPECT_EQ(sendmsg(sendfd.get(), &msghdr, 0), ssize_t(msg.size())) << strerror(errno);
break;
}
default: {
FAIL() << "unexpected test variant";
break;
}
}
EXPECT_EQ(asyncSocketRead(recvfd.get(), sendfd.get(), recvbuf, sizeof(recvbuf), 0,
SocketType::Dgram(), SocketDomain::IPv4(), kDeprecatedTimeout),
ssize_t(msg.size()));
EXPECT_EQ(std::string_view(recvbuf, msg.size()), msg);
// Test sending to an address that is different from what we're connected to.
//
// We connect to a port that was emphemerally assigned which may fall anywhere
// in [16000, UINT16_MAX] on gVisor's netstack-based platforms[1] or
// [32768, 60999] on Linux platforms[2]. Adding 1 to UINT16_MAX will overflow
// and result in a new port value of 0 so we always subtract by 1 as both
// platforms that this test runs on will assign a port that will not
// "underflow" when subtracting by 1 (as the port is always at least 1).
// Previously, we added by 1 and this resulted in a test flake on Fuchsia
// (gVisor netstack-based). See https://fxbug.dev/42165219 for more details.
//
// [1]:
// https://github.com/google/gvisor/blob/570ca571805d6939c4c24b6a88660eefaf558ae7/pkg/tcpip/ports/ports.go#L242
//
// [2]: default ip_local_port_range setting, as per
// https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
const uint16_t orig_sin_port = addr.sin_port;
addr.sin_port = htons(ntohs(orig_sin_port) - 1);
switch (io_method.Op()) {
case IOMethod::Op::SENDTO: {
EXPECT_EQ(sendto(sendfd.get(), msg.data(), msg.size(), 0, reinterpret_cast<sockaddr*>(&addr),
addrlen),
ssize_t(msg.size()))
<< strerror(errno);
break;
}
case IOMethod::Op::SENDMSG: {
EXPECT_EQ(sendmsg(sendfd.get(), &msghdr, 0), ssize_t(msg.size())) << strerror(errno);
break;
}
default: {
FAIL() << "unexpected test variant";
break;
}
}
// Expect blocked receiver and try to recover it by sending a packet to the
// original connected sockaddr.
addr.sin_port = orig_sin_port;
// As we expect failure, to keep the recv wait time minimal, we base it on the time taken for a
// successful recv.
EXPECT_EQ(asyncSocketRead(recvfd.get(), sendfd.get(), recvbuf, sizeof(recvbuf), 0,
SocketType::Dgram(), SocketDomain::IPv4(), success_rcv_duration * 10),
0);
EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno);
EXPECT_EQ(close(recvfd.release()), 0) << strerror(errno);
}
INSTANTIATE_TEST_SUITE_P(NetDatagramTest, DatagramSendTest,
testing::Values(IOMethod::Op::SENDTO, IOMethod::Op::SENDMSG),
[](const testing::TestParamInfo<IOMethod>& info) {
return info.param.IOMethodToString();
});
TEST(NetDatagramTest, DatagramConnectWrite) {
fbl::unique_fd recvfd;
ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in addr = LoopbackSockaddrV4(0);
ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
const char msg[] = "hello";
fbl::unique_fd sendfd;
ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(connect(sendfd.get(), reinterpret_cast<sockaddr*>(&addr), addrlen), 0)
<< strerror(errno);
ASSERT_EQ(write(sendfd.get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno);
EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno);
pollfd pfd = {
.fd = recvfd.get(),
.events = POLLIN,
};
int n = poll(&pfd, 1, std::chrono::milliseconds(kDeprecatedTimeout).count());
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 1);
char buf[sizeof(msg) + 1] = {};
ASSERT_EQ(read(recvfd.get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno);
ASSERT_STREQ(buf, msg);
EXPECT_EQ(close(recvfd.release()), 0) << strerror(errno);
}
TEST(NetDatagramTest, DatagramPartialRecv) {
fbl::unique_fd recvfd;
ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in addr = LoopbackSockaddrV4(0);
ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
constexpr std::string_view kTestMsg = "hello";
fbl::unique_fd sendfd;
ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
auto check_recv = [&sendfd, &recvfd, &kTestMsg, &addr, &addrlen](
size_t recv_buf_size, int flags, ssize_t expected_recvmsg_returnvalue,
int expected_msg_flags) {
char recv_buf[kTestMsg.size()];
iovec iov = {
.iov_base = recv_buf,
.iov_len = recv_buf_size,
};
// TODO(https://github.com/google/sanitizers/issues/1455): The size of this
// array should be 0 or 1, but ASAN's recvmsg interceptor incorrectly encodes
// that recvmsg writes [msg_name:][:msg_namelen'] (prime indicates value
// after recvmsg returns), while the actual behavior is that
// [msg_name:][:min(msg_namelen, msg_namelen'] is written.
uint8_t from[sizeof(addr) + 1];
msghdr msg = {
.msg_name = from,
.msg_namelen = sizeof(from),
.msg_iov = &iov,
.msg_iovlen = 1,
};
ASSERT_EQ(sendto(sendfd.get(), kTestMsg.data(), kTestMsg.size(), 0,
reinterpret_cast<sockaddr*>(&addr), addrlen),
ssize_t(kTestMsg.size()));
ASSERT_EQ(recvmsg(recvfd.get(), &msg, flags), expected_recvmsg_returnvalue);
ASSERT_EQ(msg.msg_namelen, sizeof(addr));
ASSERT_EQ(std::string_view(recv_buf, recv_buf_size), kTestMsg.substr(0, recv_buf_size));
ASSERT_EQ(msg.msg_flags, expected_msg_flags);
};
// Partial read returns partial length and `MSG_TRUNC`.
ASSERT_NO_FATAL_FAILURE(check_recv(kTestMsg.size() - 1, 0, kTestMsg.size() - 1, MSG_TRUNC));
// Partial read with `MSG_TRUNC` flags returns full message length and
// `MSG_TRUNC`.
ASSERT_NO_FATAL_FAILURE(
check_recv(kTestMsg.size() - 1, MSG_TRUNC, ssize_t(kTestMsg.size()), MSG_TRUNC));
// Full read always returns full length and no `MSG_TRUNC`.
ASSERT_NO_FATAL_FAILURE(check_recv(kTestMsg.size(), 0, ssize_t(kTestMsg.size()), 0));
ASSERT_NO_FATAL_FAILURE(check_recv(kTestMsg.size(), MSG_TRUNC, ssize_t(kTestMsg.size()), 0));
EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno);
EXPECT_EQ(close(recvfd.release()), 0) << strerror(errno);
}
TEST(NetDatagramTest, POLLOUT) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
pollfd pfd = {
.fd = fd.get(),
.events = POLLOUT,
};
int n = poll(&pfd, 1, std::chrono::milliseconds(kDeprecatedTimeout).count());
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 1);
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
// DatagramSendtoRecvfrom tests if UDP send automatically binds an ephemeral
// port where the receiver can responds to.
TEST(NetDatagramTest, DatagramSendtoRecvfrom) {
fbl::unique_fd recvfd;
ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in addr = LoopbackSockaddrV4(0);
ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr));
const char msg[] = "hello";
fbl::unique_fd sendfd;
ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(sendto(sendfd.get(), msg, sizeof(msg), 0, reinterpret_cast<sockaddr*>(&addr), addrlen),
ssize_t(sizeof(msg)))
<< strerror(errno);
char buf[sizeof(msg) + 1] = {};
sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
ASSERT_EQ(
recvfrom(recvfd.get(), buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&peer), &peerlen),
ssize_t(sizeof(msg)))
<< strerror(errno);
ASSERT_EQ(peerlen, sizeof(peer));
ASSERT_STREQ(msg, buf);
ASSERT_EQ(sendto(recvfd.get(), buf, sizeof(msg), 0, reinterpret_cast<sockaddr*>(&peer), peerlen),
ssize_t(sizeof(msg)))
<< strerror(errno);
ASSERT_EQ(
recvfrom(sendfd.get(), buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&peer), &peerlen),
ssize_t(sizeof(msg)))
<< strerror(errno);
ASSERT_EQ(peerlen, sizeof(peer));
ASSERT_STREQ(msg, buf);
char addrbuf[INET_ADDRSTRLEN], peerbuf[INET_ADDRSTRLEN];
const char* addrstr = inet_ntop(addr.sin_family, &addr.sin_addr, addrbuf, sizeof(addrbuf));
ASSERT_NE(addrstr, nullptr);
const char* peerstr = inet_ntop(peer.sin_family, &peer.sin_addr, peerbuf, sizeof(peerbuf));
ASSERT_NE(peerstr, nullptr);
ASSERT_STREQ(peerstr, addrstr);
EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno);
EXPECT_EQ(close(recvfd.release()), 0) << strerror(errno);
}
// DatagramSendtoRecvfromV6 tests if UDP send automatically binds an ephemeral
// port where the receiver can responds to.
TEST(NetDatagramTest, DatagramSendtoRecvfromV6) {
fbl::unique_fd recvfd;
ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in6 addr = LoopbackSockaddrV6(0);
ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr));
const char msg[] = "hello";
fbl::unique_fd sendfd;
ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(sendto(sendfd.get(), msg, sizeof(msg), 0, reinterpret_cast<sockaddr*>(&addr), addrlen),
ssize_t(sizeof(msg)))
<< strerror(errno);
char buf[sizeof(msg) + 1] = {};
sockaddr_in6 peer;
socklen_t peerlen = sizeof(peer);
ASSERT_EQ(
recvfrom(recvfd.get(), buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&peer), &peerlen),
ssize_t(sizeof(msg)))
<< strerror(errno);
ASSERT_EQ(peerlen, sizeof(peer));
ASSERT_STREQ(msg, buf);
ASSERT_EQ(sendto(recvfd.get(), buf, sizeof(msg), 0, reinterpret_cast<sockaddr*>(&peer), peerlen),
ssize_t(sizeof(msg)))
<< strerror(errno);
ASSERT_EQ(
recvfrom(sendfd.get(), buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&peer), &peerlen),
ssize_t(sizeof(msg)))
<< strerror(errno);
ASSERT_EQ(peerlen, sizeof(peer));
ASSERT_STREQ(msg, buf);
char addrbuf[INET6_ADDRSTRLEN], peerbuf[INET6_ADDRSTRLEN];
const char* addrstr = inet_ntop(addr.sin6_family, &addr.sin6_addr, addrbuf, sizeof(addrbuf));
ASSERT_NE(addrstr, nullptr);
const char* peerstr = inet_ntop(peer.sin6_family, &peer.sin6_addr, peerbuf, sizeof(peerbuf));
ASSERT_NE(peerstr, nullptr);
ASSERT_STREQ(peerstr, addrstr);
EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno);
EXPECT_EQ(close(recvfd.release()), 0) << strerror(errno);
}
TEST(NetDatagramTest, DatagramSendtoV4RecvfromV6) {
sockaddr_in addr4 = LoopbackSockaddrV4(0);
sockaddr_in6 addr6 = MapIpv4SockaddrToIpv6Sockaddr(addr4);
fbl::unique_fd recv_fd;
ASSERT_TRUE(recv_fd = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(bind(recv_fd.get(), reinterpret_cast<const sockaddr*>(&addr6), sizeof(addr6)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr6);
ASSERT_EQ(getsockname(recv_fd.get(), reinterpret_cast<sockaddr*>(&addr6), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr6));
fbl::unique_fd send_fd;
ASSERT_TRUE(send_fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
addr4.sin_port = addr6.sin6_port;
constexpr char send_buf[] = "abc";
ASSERT_EQ(sendto(send_fd.get(), send_buf, sizeof(send_buf), 0,
reinterpret_cast<sockaddr*>(&addr4), addrlen),
ssize_t(sizeof(send_buf)))
<< strerror(errno);
char recv_buf[sizeof(send_buf) + 1];
sockaddr_in6 peer;
socklen_t peerlen = sizeof(peer);
ASSERT_EQ(recvfrom(recv_fd.get(), recv_buf, sizeof(recv_buf), 0,
reinterpret_cast<sockaddr*>(&peer), &peerlen),
ssize_t(sizeof(send_buf)))
<< strerror(errno);
ASSERT_EQ(peerlen, sizeof(peer));
char addrbuf[INET6_ADDRSTRLEN];
char peerbuf[INET6_ADDRSTRLEN];
const char* addrstr = inet_ntop(addr6.sin6_family, &addr6.sin6_addr, addrbuf, sizeof(addrbuf));
const char* peerstr = inet_ntop(peer.sin6_family, &peer.sin6_addr, peerbuf, sizeof(peerbuf));
EXPECT_STREQ(peerstr, addrstr);
EXPECT_EQ(close(send_fd.release()), 0) << strerror(errno);
EXPECT_EQ(close(recv_fd.release()), 0) << strerror(errno);
}
TEST(NetDatagramTest, DatagramSendtoV6RecvfromV4) {
sockaddr_in addr = LoopbackSockaddrV4(0);
fbl::unique_fd recv_fd;
ASSERT_TRUE(recv_fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(bind(recv_fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(recv_fd.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr));
sockaddr_in6 addr6 = MapIpv4SockaddrToIpv6Sockaddr(addr);
fbl::unique_fd send_fd;
ASSERT_TRUE(send_fd = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
constexpr char send_buf[] = "abc";
ASSERT_EQ(sendto(send_fd.get(), send_buf, sizeof(send_buf), 0,
reinterpret_cast<sockaddr*>(&addr6), sizeof(addr6)),
ssize_t(sizeof(send_buf)))
<< strerror(errno);
char recv_buf[sizeof(send_buf) + 1];
sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
ASSERT_EQ(recvfrom(recv_fd.get(), recv_buf, sizeof(recv_buf), 0,
reinterpret_cast<sockaddr*>(&peer), &peerlen),
ssize_t(sizeof(send_buf)))
<< strerror(errno);
ASSERT_EQ(peerlen, sizeof(peer));
char addrbuf[INET_ADDRSTRLEN];
char peerbuf[INET_ADDRSTRLEN];
const char* addrstr = inet_ntop(addr.sin_family, &addr.sin_addr, addrbuf, sizeof(addrbuf));
const char* peerstr = inet_ntop(peer.sin_family, &peer.sin_addr, peerbuf, sizeof(peerbuf));
EXPECT_STREQ(peerstr, addrstr);
EXPECT_EQ(close(send_fd.release()), 0) << strerror(errno);
EXPECT_EQ(close(recv_fd.release()), 0) << strerror(errno);
}
TEST(NetDatagramTest, ConnectUnspecV4) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
sockaddr_in addr = {
.sin_family = AF_UNSPEC,
};
EXPECT_EQ(connect(fd.get(), reinterpret_cast<const sockaddr*>(&addr),
offsetof(sockaddr_in, sin_family) + sizeof(addr.sin_family)),
0)
<< strerror(errno);
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
TEST(NetDatagramTest, ConnectUnspecV6) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno);
sockaddr_in6 addr = {
.sin6_family = AF_UNSPEC,
};
EXPECT_EQ(connect(fd.get(), reinterpret_cast<const sockaddr*>(&addr),
offsetof(sockaddr_in6, sin6_family) + sizeof(addr.sin6_family)),
0)
<< strerror(errno);
EXPECT_EQ(close(fd.release()), 0) << strerror(errno);
}
TEST(IoctlTest, IoctlGetInterfaceFlags) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ifreq ifr_ntof = {};
{
constexpr char name[] = "lo";
memcpy(ifr_ntof.ifr_name, name, sizeof(name));
}
ASSERT_EQ(ioctl(fd.get(), SIOCGIFFLAGS, &ifr_ntof), 0) << strerror(errno);
const struct {
std::string name;
uint16_t bitmask;
bool value;
} flags[] = {
{
.name = "IFF_UP",
.bitmask = IFF_UP,
.value = true,
},
{
.name = "IFF_LOOPBACK",
.bitmask = IFF_LOOPBACK,
.value = true,
},
{
.name = "IFF_RUNNING",
.bitmask = IFF_RUNNING,
.value = true,
},
{
.name = "IFF_PROMISC",
.bitmask = IFF_PROMISC,
.value = false,
},
};
for (const auto& flag : flags) {
EXPECT_EQ(static_cast<bool>(ifr_ntof.ifr_flags & flag.bitmask), flag.value)
<< std::bitset<16>(ifr_ntof.ifr_flags) << ", " << std::bitset<16>(flag.bitmask);
}
// Don't check strict equality of `ifr_ntof.ifr_flags` with expected flag
// values, except on Fuchsia, because gVisor does not set all the interface
// flags that Linux does.
if (kIsFuchsia) {
uint16_t expected_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING | IFF_MULTICAST;
ASSERT_EQ(ifr_ntof.ifr_flags, expected_flags)
<< std::bitset<16>(ifr_ntof.ifr_flags) << ", " << std::bitset<16>(expected_flags);
}
}
TEST(IoctlTest, IoctlGetInterfaceAddressesNullIfConf) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(ioctl(fd.get(), SIOCGIFCONF, nullptr), -1);
ASSERT_EQ(errno, EFAULT) << strerror(errno);
}
TEST(IoctlTest, IoctlGetInterfaceAddressesPartialRecord) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
// Get the interface configuration information, but only pass an `ifc_len`
// large enough to hold a partial `struct ifreq`, and ensure that the buffer
// is not overwritten.
constexpr char kGarbage = 0xa;
ifreq ifr;
memset(&ifr, kGarbage, sizeof(ifr));
ifconf ifc = {};
ifc.ifc_len = sizeof(ifr) - 1;
ifc.ifc_req = &ifr;
ASSERT_EQ(ioctl(fd.get(), SIOCGIFCONF, &ifc), 0) << strerror(errno);
ASSERT_EQ(ifc.ifc_len, 0);
char* buffer = reinterpret_cast<char*>(&ifr);
for (size_t i = 0; i < sizeof(ifr); i++) {
EXPECT_EQ(buffer[i], kGarbage) << i;
}
}
TEST(NetDatagramTest, PingIpv4LoopbackAddresses) {
const char msg[] = "hello";
char addrbuf[INET_ADDRSTRLEN];
std::array<int, 5> sampleAddrOctets = {0, 1, 100, 200, 255};
for (auto i : sampleAddrOctets) {
for (auto j : sampleAddrOctets) {
for (auto k : sampleAddrOctets) {
// Skip the subnet and broadcast addresses.
if ((i == 0 && j == 0 && k == 0) || (i == 255 && j == 255 && k == 255)) {
continue;
}
// loopback_addr = 127.i.j.k
in_addr loopback_sin_addr = {
.s_addr = htonl((127 << 24) + (i << 16) + (j << 8) + k),
};
const char* loopback_addrstr =
inet_ntop(AF_INET, &loopback_sin_addr, addrbuf, sizeof(addrbuf));
ASSERT_NE(nullptr, loopback_addrstr);
fbl::unique_fd recvfd;
ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in rcv_addr = {
.sin_family = AF_INET,
.sin_addr = loopback_sin_addr,
};
ASSERT_EQ(
bind(recvfd.get(), reinterpret_cast<const sockaddr*>(&rcv_addr), sizeof(rcv_addr)), 0)
<< "recvaddr=" << loopback_addrstr << ": " << strerror(errno);
socklen_t rcv_addrlen = sizeof(rcv_addr);
ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<sockaddr*>(&rcv_addr), &rcv_addrlen),
0)
<< strerror(errno);
ASSERT_EQ(sizeof(rcv_addr), rcv_addrlen);
fbl::unique_fd sendfd;
ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in sendto_addr = {
.sin_family = AF_INET,
.sin_port = rcv_addr.sin_port,
.sin_addr = loopback_sin_addr,
};
ASSERT_EQ(sendto(sendfd.get(), msg, sizeof(msg), 0,
reinterpret_cast<sockaddr*>(&sendto_addr), sizeof(sendto_addr)),
ssize_t(sizeof(msg)))
<< "sendtoaddr=" << loopback_addrstr << ": " << strerror(errno);
EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno);
pollfd pfd = {
.fd = recvfd.get(),
.events = POLLIN,
};
int n = poll(&pfd, 1, std::chrono::milliseconds(kDeprecatedTimeout).count());
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 1);
char buf[sizeof(msg) + 1] = {};
ASSERT_EQ(read(recvfd.get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno);
ASSERT_STREQ(buf, msg);
EXPECT_EQ(close(recvfd.release()), 0) << strerror(errno);
}
}
}
}
#if defined(__Fuchsia__)
using DomainAndIOMethod = std::tuple<SocketDomain, IOMethod>;
std::string DomainAndIOMethodToString(const testing::TestParamInfo<DomainAndIOMethod>& info) {
auto const& [domain, io_method] = info.param;
std::ostringstream oss;
oss << socketDomainToString(domain);
oss << '_' << io_method.IOMethodToString();
return oss.str();
}
class IOSendingMethodTest : public testing::TestWithParam<DomainAndIOMethod> {};
TEST_P(IOSendingMethodTest, CloseTerminatesWithRxZirconSocketRemainder) {
// Fast datagram sockets on Fuchsia use multiple buffers to store inbound payloads,
// some of which are in Netstack memory and some of which are in kernel memory.
// Bytes are shuttled between these buffers using routines that spin until
// the socket is closed. Furthermore, `close()`ing a socket blocks until all
// of these routines exit.
//
// One edge case arises when the kernel buffers have free space, but not so much
// space that they can accept the next datagram payload. In this case, the netstack
// routines need to be smart enough to terminate rather than continually trying to
// enqueue the next payload in an infinite loop.
//
// This test verifies that behavior.
auto const& [domain, io_method] = GetParam();
fbl::unique_fd recvfd;
fbl::unique_fd sendfd;
ASSERT_NO_FATAL_FAILURE(SetUpBoundAndConnectedDatagramSockets(domain, recvfd, sendfd));
std::vector<char> buf;
ASSERT_NO_FATAL_FAILURE(
FillRxBuffersLeavingRemainderInZirconSocket(recvfd, sendfd, io_method, buf));
struct close_task {
const std::string name;
fbl::unique_fd fd;
std::optional<int> result;
std::thread action;
} close_tasks[] = {
{.name = "recvfd", .fd = std::move(recvfd)},
{.name = "sendfd", .fd = std::move(sendfd)},
};
std::latch done{std::size(close_tasks)};
for (close_task& task : close_tasks) {
task.action = std::thread([&task, &done]() {
task.result = close(task.fd.release());
done.count_down();
});
}
// Expect that both calls to `close()` return without blocking indefinitely.
const auto close_both = std::async(std::launch::async, [&done]() { done.wait(); });
ASSERT_EQ(close_both.wait_for(kDeprecatedTimeout), std::future_status::ready);
for (close_task& task : close_tasks) {
ASSERT_TRUE(task.result.has_value()) << " close(" << task.name << ") failed to terminate";
ASSERT_EQ(task.result.value(), 0) << " close(" << task.name << ") returned error";
}
for (close_task& task : close_tasks) {
task.action.join();
}
}
TEST_P(IOSendingMethodTest, ReadWithRxZirconSocketRemainder) {
// Fast datagram sockets on Fuchsia use multiple buffers to store inbound payloads,
// some of which are in Netstack memory and some of which are in kernel memory.
// Bytes are shuttled between these buffers using goroutines.
//
// One edge case arises when the kernel buffers have free space, but not so much
// space that they can accept the next datagram payload. In this case, the netstack
// routines wait until the kernel object can accept the entire payload, in an
// operation known as a threshold wait.
//
// This test exercises this scenario.
auto const& [domain, io_method] = GetParam();
fbl::unique_fd recvfd;
fbl::unique_fd sendfd;
ASSERT_NO_FATAL_FAILURE(SetUpBoundAndConnectedDatagramSockets(domain, recvfd, sendfd));
std::vector<char> buf;
ASSERT_NO_FATAL_FAILURE(
FillRxBuffersLeavingRemainderInZirconSocket(recvfd, sendfd, io_method, buf));
zx_info_socket_t zx_socket_info;
ASSERT_NO_FATAL_FAILURE(ZxSocketInfoDgram(recvfd.get(), zx_socket_info));
std::vector<char> recvbuf;
recvbuf.resize(buf.size() + 1);
// Read a number of bytes exceeding the capacity of the Rx kernel buffer. For these
// reads to succeed, the Rx routine must have successfully performed a threshold
// wait.
size_t bytes_read = 0;
while (bytes_read <= zx_socket_info.rx_buf_max) {
ASSERT_EQ(read(recvfd.get(), recvbuf.data(), recvbuf.size()), ssize_t(buf.size()))
<< strerror(errno);
EXPECT_EQ(std::string_view(recvbuf.data(), buf.size()),
std::string_view(buf.data(), buf.size()));
bytes_read += buf.size();
}
}
INSTANTIATE_TEST_SUITE_P(IOSendingMethodTests, IOSendingMethodTest,
testing::Combine(testing::Values(SocketDomain::IPv4(),
SocketDomain::IPv6()),
testing::ValuesIn(kSendIOMethods)),
DomainAndIOMethodToString);
#endif // defined(__Fuchsia__)
std::pair<sockaddr_storage, socklen_t> AnySockaddrAndSocklenForDomain(const SocketDomain& domain) {
sockaddr_storage addr{
.ss_family = domain.Get(),
};
switch (domain.which()) {
case SocketDomain::Which::IPv4: {
auto& sin = *reinterpret_cast<sockaddr_in*>(&addr);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
return std::make_pair(addr, sizeof(sin));
}
case SocketDomain::Which::IPv6: {
auto& sin6 = *reinterpret_cast<sockaddr_in6*>(&addr);
sin6.sin6_addr = IN6ADDR_ANY_INIT;
return std::make_pair(addr, sizeof(sin6));
}
}
}
class AllDomainTests : public testing::TestWithParam<SocketDomain> {};
TEST_P(AllDomainTests, SendToAnyAddr) {
auto const& domain = GetParam();
fbl::unique_fd recv_fd;
ASSERT_TRUE(recv_fd = fbl::unique_fd(socket(domain.Get(), SOCK_DGRAM, 0))) << strerror(errno);
auto [sock_addr, socklen] = AnySockaddrAndSocklenForDomain(domain);
ASSERT_EQ(bind(recv_fd.get(), reinterpret_cast<const sockaddr*>(&sock_addr), sizeof(sock_addr)),
0)
<< strerror(errno);
socklen_t addrlen = socklen;
ASSERT_EQ(getsockname(recv_fd.get(), reinterpret_cast<sockaddr*>(&sock_addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, socklen);
const char sendbuf[] = "hello";
fbl::unique_fd send_fd;
ASSERT_TRUE(send_fd = fbl::unique_fd(socket(domain.Get(), SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(sendto(send_fd.get(), sendbuf, sizeof(sendbuf), 0,
reinterpret_cast<sockaddr*>(&sock_addr), socklen),
ssize_t(sizeof(sendbuf)))
<< strerror(errno);
char recvbuf[sizeof(sendbuf) + 1];
ASSERT_EQ(read(recv_fd.get(), recvbuf, sizeof(recvbuf)), ssize_t(sizeof(sendbuf)))
<< strerror(errno);
ASSERT_STREQ(recvbuf, sendbuf);
}
INSTANTIATE_TEST_SUITE_P(AllDomainTests, AllDomainTests,
testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
[](const auto info) {
return std::string(socketDomainToString(info.param));
});
class NetDatagramSocketsTestBase {
protected:
void SetUpDatagramSockets(const SocketDomain& domain) {
ASSERT_NO_FATAL_FAILURE(SetUpBoundAndConnectedDatagramSockets(domain, bound_, connected_));
}
void TearDownDatagramSockets() {
EXPECT_EQ(close(connected_.release()), 0) << strerror(errno);
EXPECT_EQ(close(bound_.release()), 0) << strerror(errno);
}
const fbl::unique_fd& bound() const { return bound_; }
const fbl::unique_fd& connected() const { return connected_; }
private:
fbl::unique_fd bound_;
fbl::unique_fd connected_;
};
enum class SendZeroBytesTestCase {
NullBuffer,
ZeroBufferLen,
};
using DomainAndIOMethodAndSendZeroBytesTestCase =
std::tuple<SocketDomain, IOMethod, SendZeroBytesTestCase>;
std::string DomainAndIOMethodAndSendZeroBytesTestCaseToString(
const testing::TestParamInfo<DomainAndIOMethodAndSendZeroBytesTestCase>& info) {
auto const& [domain, io_method, test_case] = info.param;
std::ostringstream oss;
oss << socketDomainToString(domain);
oss << '_' << io_method.IOMethodToString();
switch (test_case) {
case SendZeroBytesTestCase::NullBuffer:
oss << '_' << "NullBuffer";
break;
case SendZeroBytesTestCase::ZeroBufferLen:
oss << '_' << "ZeroBufferLen";
break;
}
return oss.str();
}
class IOSendingZeroBytesMethodTest
: public NetDatagramSocketsTestBase,
public testing::TestWithParam<DomainAndIOMethodAndSendZeroBytesTestCase> {
void SetUp() override {
auto const& [domain, io_method, test_case] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(domain));
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(IOSendingZeroBytesMethodTest, ZeroLengthPayload) {
const auto& [domain, io_method, test_case] = GetParam();
char buf[1];
char* data;
switch (test_case) {
case SendZeroBytesTestCase::NullBuffer:
data = nullptr;
break;
case SendZeroBytesTestCase::ZeroBufferLen:
data = buf;
break;
}
EXPECT_EQ(io_method.ExecuteIO(connected().get(), data, 0), 0) << strerror(errno);
// TODO(https://fxbug.dev/42054549): Match Linux behavior when calling `writev` with zero length
// payloads.
if (!kIsFuchsia && io_method.Op() == IOMethod::Op::WRITEV) {
ASSERT_NO_FATAL_FAILURE(ExpectNoPollin(bound().get()));
return;
}
EXPECT_EQ(read(bound().get(), buf, sizeof(buf)), 0);
}
INSTANTIATE_TEST_SUITE_P(IOSendingZeroBytesMethodTests, IOSendingZeroBytesMethodTest,
testing::Combine(testing::Values(SocketDomain::IPv4(),
SocketDomain::IPv6()),
testing::ValuesIn(kSendIOMethods),
testing::Values(SendZeroBytesTestCase::NullBuffer,
SendZeroBytesTestCase::ZeroBufferLen)),
DomainAndIOMethodAndSendZeroBytesTestCaseToString);
using SendIOMethodTestCase = std::tuple<SocketDomain, IOMethod>;
std::string SendIOMethodTestCaseToString(const testing::TestParamInfo<SendIOMethodTestCase>& info) {
auto const& [domain, io_method] = info.param;
std::ostringstream oss;
oss << socketDomainToString(domain) << '_' << io_method.IOMethodToString();
return oss.str();
}
class SendIOMethodTest : public NetDatagramSocketsTestBase,
public testing::TestWithParam<SendIOMethodTestCase> {
void SetUp() override {
auto const& [domain, io_method] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(domain));
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(SendIOMethodTest, SendDatagramTooLarge) {
auto [domain, io_method] = GetParam();
std::vector<char> buf(1024, 'a');
EXPECT_EQ(io_method.ExecuteIO(connected().get(), buf.data(), buf.size()), ssize_t(buf.size()))
<< strerror(errno);
// The maximum IP packet size is 65535, so even ignoring the overhead of the
// IP and UDP headers, this payload is guaranteed to be too large.
buf.resize(65536, 'a');
EXPECT_EQ(io_method.ExecuteIO(connected().get(), buf.data(), buf.size()), -1);
EXPECT_EQ(errno, EMSGSIZE);
buf.resize(128 * 1024, 'a');
EXPECT_EQ(io_method.ExecuteIO(connected().get(), buf.data(), buf.size()), -1);
EXPECT_EQ(errno, EMSGSIZE);
}
INSTANTIATE_TEST_SUITE_P(SendIOMethodTests, SendIOMethodTest,
testing::Combine(testing::Values(SocketDomain::IPv4(),
SocketDomain::IPv6()),
testing::ValuesIn(kSendIOMethods)),
SendIOMethodTestCaseToString);
enum class SendZeroBytesVectorizedTestCase {
NullIovecPointer,
ZeroIovCnt,
};
using DomainAndVectorizedIOMethodAndSendZeroBytesVectorizedTestCase =
std::tuple<SocketDomain, VectorizedIOMethod, SendZeroBytesVectorizedTestCase>;
std::string DomainAndVectorizedIOMethodAndSendZeroBytesVectorizedTestCaseToString(
const testing::TestParamInfo<DomainAndVectorizedIOMethodAndSendZeroBytesVectorizedTestCase>&
info) {
auto const& [domain, io_method, test_case] = info.param;
std::ostringstream oss;
oss << socketDomainToString(domain);
oss << '_' << io_method.IOMethodToString();
switch (test_case) {
case SendZeroBytesVectorizedTestCase::NullIovecPointer:
oss << '_' << "NullIovecPointer";
break;
case SendZeroBytesVectorizedTestCase::ZeroIovCnt:
oss << '_' << "ZeroIovCnt";
break;
}
return oss.str();
}
class VectorizedIOSendingZeroBytesMethodTest
: public NetDatagramSocketsTestBase,
public testing::TestWithParam<DomainAndVectorizedIOMethodAndSendZeroBytesVectorizedTestCase> {
void SetUp() override {
auto const& [domain, io_method, test_case] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(domain));
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(VectorizedIOSendingZeroBytesMethodTest, ZeroLengthPayload) {
const auto& [domain, io_method, test_case] = GetParam();
char buf[1];
iovec* iov_ptr;
size_t iov_size;
std::vector<iovec> iovecs;
switch (test_case) {
case SendZeroBytesVectorizedTestCase::NullIovecPointer:
iov_ptr = nullptr;
iov_size = 0;
break;
case SendZeroBytesVectorizedTestCase::ZeroIovCnt:
iovecs.push_back({
.iov_base = buf,
.iov_len = sizeof(buf),
});
iov_ptr = iovecs.data();
iov_size = 0;
break;
}
EXPECT_EQ(io_method.ExecuteIO(connected().get(), iov_ptr, iov_size), 0) << strerror(errno);
// TODO(https://fxbug.dev/42054549): Match Linux behavior when calling `writev` with zero length
// payloads.
if (!kIsFuchsia && io_method.Op() == VectorizedIOMethod::Op::WRITEV) {
ASSERT_NO_FATAL_FAILURE(ExpectNoPollin(bound().get()));
return;
}
EXPECT_EQ(read(bound().get(), buf, sizeof(buf)), 0);
}
INSTANTIATE_TEST_SUITE_P(
VectorizedIOSendingZeroBytesMethodTests, VectorizedIOSendingZeroBytesMethodTest,
testing::Combine(testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
testing::Values(VectorizedIOMethod::Op::WRITEV,
VectorizedIOMethod::Op::SENDMSG),
testing::Values(SendZeroBytesVectorizedTestCase::NullIovecPointer,
SendZeroBytesVectorizedTestCase::ZeroIovCnt)),
DomainAndVectorizedIOMethodAndSendZeroBytesVectorizedTestCaseToString);
struct Cmsg {
Cmsg(int level, std::string level_str, int type, std::string type_str)
: level(level), level_str(level_str), type(type), type_str(type_str) {}
int level;
std::string level_str;
int type;
std::string type_str;
};
#define STRINGIFIED_CMSG(level, type) Cmsg(level, #level, type, #type)
struct CmsgSocketOption {
Cmsg cmsg;
socklen_t cmsg_size;
// The option and the control message always share the same level, so we only need the name of the
// option here.
int optname_to_enable_receive;
};
std::ostream& operator<<(std::ostream& oss, const CmsgSocketOption& cmsg_opt) {
return oss << cmsg_opt.cmsg.level_str << '_' << cmsg_opt.cmsg.type_str;
}
class NetDatagramSocketsCmsgTestBase : public NetDatagramSocketsTestBase {
protected:
template <typename F>
void ReceiveAndCheckMessage(const char* sent_buf, ssize_t sent_buf_len, void* control,
socklen_t control_len, F check) const {
ASSERT_NO_FATAL_FAILURE(
ReceiveAndCheckMessageBase(sent_buf, sent_buf_len, control, control_len, check));
}
template <typename F>
void ReceiveAndCheckMessageBase(const char* sent_buf, ssize_t sent_buf_len, void* control,
socklen_t control_len, F check) const {
char recv_buf[sent_buf_len + 1];
iovec iovec = {
.iov_base = recv_buf,
.iov_len = sizeof(recv_buf),
};
msghdr msghdr = {
.msg_name = nullptr,
.msg_namelen = 0,
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = control_len,
};
ASSERT_EQ(recvmsg(bound().get(), &msghdr, 0), ssize_t(sent_buf_len)) << strerror(errno);
ASSERT_EQ(memcmp(recv_buf, sent_buf, sent_buf_len), 0);
check(msghdr);
}
};
enum class EnableCmsgReceiveTime { AfterSocketSetup, BetweenSendAndRecv };
std::string_view enableCmsgReceiveTimeToString(EnableCmsgReceiveTime enable_cmsg_receive_time) {
switch (enable_cmsg_receive_time) {
case EnableCmsgReceiveTime::AfterSocketSetup:
return "AfterSocketSetup";
case EnableCmsgReceiveTime::BetweenSendAndRecv:
return "BetweenSendAndRecv";
}
}
using SocketDomainAndOptionAndEnableCmsgReceiveTime =
std::tuple<SocketDomain, CmsgSocketOption, EnableCmsgReceiveTime>;
std::string SocketDomainAndOptionAndEnableCmsgReceiveTimeToString(
const testing::TestParamInfo<SocketDomainAndOptionAndEnableCmsgReceiveTime>& info) {
auto const& [domain, cmsg_opt, enable_cmsg_receive_time] = info.param;
std::ostringstream oss;
oss << socketDomainToString(domain);
oss << '_' << cmsg_opt;
oss << '_' << enableCmsgReceiveTimeToString(enable_cmsg_receive_time);
return oss.str();
}
class NetDatagramSocketsCmsgRecvTestBase : public NetDatagramSocketsCmsgTestBase {
protected:
void SetUpDatagramSockets(const SocketDomain& domain,
EnableCmsgReceiveTime enable_cmsg_receive_time) {
enable_cmsg_receive_time_ = enable_cmsg_receive_time;
ASSERT_NO_FATAL_FAILURE(NetDatagramSocketsCmsgTestBase::SetUpDatagramSockets(domain));
if (enable_cmsg_receive_time_ == EnableCmsgReceiveTime::AfterSocketSetup) {
ASSERT_NO_FATAL_FAILURE(EnableReceivingCmsg());
}
}
virtual void EnableReceivingCmsg() const = 0;
template <typename F>
void ReceiveAndCheckMessage(const char* sent_buf, ssize_t sent_buf_len, void* control,
socklen_t control_len, F check) const {
if (enable_cmsg_receive_time_ == EnableCmsgReceiveTime::BetweenSendAndRecv) {
// Ensure the packet is ready to be read by the client when the
// control message is requested; this lets us test that control
// messages are applied to all subsequent incoming payloads.
pollfd pfd = {
.fd = bound().get(),
.events = POLLIN,
};
int n = poll(&pfd, 1, -1);
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(n, 1);
ASSERT_EQ(pfd.revents, POLLIN);
ASSERT_NO_FATAL_FAILURE(EnableReceivingCmsg());
}
ASSERT_NO_FATAL_FAILURE(
ReceiveAndCheckMessageBase(sent_buf, sent_buf_len, control, control_len, check));
}
template <typename F>
void SendAndCheckReceivedMessage(void* control, socklen_t control_len, F check) {
constexpr char send_buf[] = "hello";
ASSERT_EQ(send(connected().get(), send_buf, sizeof(send_buf), 0), ssize_t(sizeof(send_buf)))
<< strerror(errno);
ReceiveAndCheckMessage(send_buf, sizeof(send_buf), control, control_len, check);
}
private:
EnableCmsgReceiveTime enable_cmsg_receive_time_;
};
class NetDatagramSocketsCmsgRecvTest
: public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<SocketDomainAndOptionAndEnableCmsgReceiveTime> {
protected:
void SetUp() override {
auto const& [domain, cmsg_sockopt, enable_cmsg_receive_time] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(domain, enable_cmsg_receive_time));
}
void EnableReceivingCmsg() const override {
auto const& [domain, cmsg_sockopt, enable_cmsg_receive_time] = GetParam();
// Enable the specified socket option.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), cmsg_sockopt.cmsg.level,
cmsg_sockopt.optname_to_enable_receive, &kOne, sizeof(kOne)),
0)
<< strerror(errno);
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(NetDatagramSocketsCmsgRecvTest, NullPtrNoControlMessages) {
ASSERT_NO_FATAL_FAILURE(SendAndCheckReceivedMessage(nullptr, 1337, [](msghdr& msghdr) {
// The msg_controllen field should be reset when the control buffer is null, even when no
// control messages are enabled.
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgRecvTest, NullControlBuffer) {
ASSERT_NO_FATAL_FAILURE(SendAndCheckReceivedMessage(nullptr, 1337, [](msghdr& msghdr) {
// The msg_controllen field should be reset when the control buffer is null.
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgRecvTest, OneByteControlLength) {
char control[1];
ASSERT_NO_FATAL_FAILURE(SendAndCheckReceivedMessage(control, sizeof(control), [](msghdr& msghdr) {
// If there is not enough space to store the cmsghdr, then nothing is stored.
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgRecvTest, ZeroControlLength) {
char control[1];
ASSERT_NO_FATAL_FAILURE(SendAndCheckReceivedMessage(control, 0, [](msghdr& msghdr) {
// The msg_controllen field should remain zero when no messages were written.
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgRecvTest, FailureDoesNotResetControlLength) {
char recvbuf[1];
iovec iovec = {
.iov_base = recvbuf,
.iov_len = sizeof(recvbuf),
};
char control[1337];
msghdr msghdr = {
.msg_name = nullptr,
.msg_namelen = 0,
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
};
ASSERT_EQ(recvmsg(bound().get(), &msghdr, MSG_DONTWAIT), -1);
EXPECT_EQ(errno, EWOULDBLOCK) << strerror(errno);
// The msg_controllen field should be left unchanged when recvmsg() fails for any reason.
EXPECT_EQ(msghdr.msg_controllen, sizeof(control));
}
TEST_P(NetDatagramSocketsCmsgRecvTest, TruncatedMessageMinimumValidSize) {
// A control message can be truncated if there is at least enough space to store the cmsghdr.
char control[sizeof(cmsghdr)];
ASSERT_NO_FATAL_FAILURE(SendAndCheckReceivedMessage(control, sizeof(cmsghdr), [](msghdr& msghdr) {
if (kIsFuchsia) {
// TODO(https://fxbug.dev/42167124): Add support for truncated control messages (MSG_CTRUNC).
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
} else {
ASSERT_EQ(msghdr.msg_controllen, sizeof(control));
EXPECT_EQ(msghdr.msg_flags, MSG_CTRUNC);
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, sizeof(control));
auto const& cmsg_sockopt = std::get<1>(GetParam());
EXPECT_EQ(cmsg->cmsg_level, cmsg_sockopt.cmsg.level);
EXPECT_EQ(cmsg->cmsg_type, cmsg_sockopt.cmsg.type);
}
}));
}
TEST_P(NetDatagramSocketsCmsgRecvTest, TruncatedMessageByOneByte) {
auto const& cmsg_sockopt = std::get<1>(GetParam());
char control[CMSG_LEN(cmsg_sockopt.cmsg_size) - 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control, socklen_t(sizeof(control)), [&](msghdr& msghdr) {
if (kIsFuchsia) {
// TODO(https://fxbug.dev/42167124): Add support for truncated control messages (MSG_CTRUNC).
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
} else {
ASSERT_EQ(msghdr.msg_controllen, sizeof(control));
EXPECT_EQ(msghdr.msg_flags, MSG_CTRUNC);
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, sizeof(control));
EXPECT_EQ(cmsg->cmsg_level, cmsg_sockopt.cmsg.level);
EXPECT_EQ(cmsg->cmsg_type, cmsg_sockopt.cmsg.type);
}
}));
}
INSTANTIATE_TEST_SUITE_P(
NetDatagramSocketsCmsgRecvTests, NetDatagramSocketsCmsgRecvTest,
testing::Combine(testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
testing::Values(
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_SOCKET, SO_TIMESTAMP),
.cmsg_size = sizeof(timeval),
.optname_to_enable_receive = SO_TIMESTAMP,
},
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_SOCKET, SO_TIMESTAMPNS),
.cmsg_size = sizeof(timespec),
.optname_to_enable_receive = SO_TIMESTAMPNS,
}),
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv)),
SocketDomainAndOptionAndEnableCmsgReceiveTimeToString);
INSTANTIATE_TEST_SUITE_P(
NetDatagramSocketsCmsgRecvIPv4Tests, NetDatagramSocketsCmsgRecvTest,
testing::Combine(testing::Values(SocketDomain::IPv4()),
testing::Values(
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IP, IP_TOS),
.cmsg_size = sizeof(uint8_t),
.optname_to_enable_receive = IP_RECVTOS,
},
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IP, IP_TTL),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IP_RECVTTL,
}),
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv)),
SocketDomainAndOptionAndEnableCmsgReceiveTimeToString);
INSTANTIATE_TEST_SUITE_P(
NetDatagramSocketsCmsgRecvIPv6Tests, NetDatagramSocketsCmsgRecvTest,
testing::Combine(testing::Values(SocketDomain::IPv6()),
testing::Values(
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IPV6, IPV6_TCLASS),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IPV6_RECVTCLASS,
},
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IPV6, IPV6_HOPLIMIT),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IPV6_RECVHOPLIMIT,
},
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IPV6, IPV6_PKTINFO),
.cmsg_size = sizeof(in6_pktinfo),
.optname_to_enable_receive = IPV6_RECVPKTINFO,
}),
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv)),
SocketDomainAndOptionAndEnableCmsgReceiveTimeToString);
// Tests in this suite assume that control messages are requested after setup only. Create
// a new class that can be parameterized in order to fulfill this expectation.
class NetDatagramSocketsCmsgRequestOnSetupOnlyRecvTest : public NetDatagramSocketsCmsgRecvTest {};
TEST_P(NetDatagramSocketsCmsgRequestOnSetupOnlyRecvTest, DisableReceiveSocketOption) {
// The SetUp enables the receipt of the parametrized control message. Confirm that we initially
// receive the control message, and then check that disabling the receive option does exactly
// just that.
auto const& cmsg_sockopt = std::get<1>(GetParam());
{
char control[CMSG_SPACE(cmsg_sockopt.cmsg_size) + 1];
ASSERT_NO_FATAL_FAILURE(SendAndCheckReceivedMessage(
control, socklen_t(sizeof(control)), [cmsg_sockopt](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(cmsg_sockopt.cmsg_size));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_sockopt.cmsg_size));
EXPECT_EQ(cmsg->cmsg_level, cmsg_sockopt.cmsg.level);
EXPECT_EQ(cmsg->cmsg_type, cmsg_sockopt.cmsg.type);
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
constexpr int kZero = 0;
ASSERT_EQ(setsockopt(bound().get(), cmsg_sockopt.cmsg.level,
cmsg_sockopt.optname_to_enable_receive, &kZero, sizeof(kZero)),
0)
<< strerror(errno);
{
char control[CMSG_SPACE(cmsg_sockopt.cmsg_size) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control, socklen_t(sizeof(control)), [](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, 0u);
EXPECT_EQ(CMSG_FIRSTHDR(&msghdr), nullptr);
}));
}
}
INSTANTIATE_TEST_SUITE_P(
NetDatagramSocketsCmsgRequestOnSetupOnlyRecvTests,
NetDatagramSocketsCmsgRequestOnSetupOnlyRecvTest,
testing::Combine(testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
testing::Values(
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_SOCKET, SO_TIMESTAMP),
.cmsg_size = sizeof(timeval),
.optname_to_enable_receive = SO_TIMESTAMP,
},
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_SOCKET, SO_TIMESTAMPNS),
.cmsg_size = sizeof(timespec),
.optname_to_enable_receive = SO_TIMESTAMPNS,
}),
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup)),
SocketDomainAndOptionAndEnableCmsgReceiveTimeToString);
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsCmsgRequestOnSetupOnlyRecvIPv4Tests,
NetDatagramSocketsCmsgRequestOnSetupOnlyRecvTest,
testing::Combine(testing::Values(SocketDomain::IPv4()),
testing::Values(
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IP, IP_TOS),
.cmsg_size = sizeof(uint8_t),
.optname_to_enable_receive = IP_RECVTOS,
},
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IP, IP_TTL),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IP_RECVTTL,
}),
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup)),
SocketDomainAndOptionAndEnableCmsgReceiveTimeToString);
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsCmsgRequestOnSetupOnlyRecvIPv6Tests,
NetDatagramSocketsCmsgRequestOnSetupOnlyRecvTest,
testing::Combine(testing::Values(SocketDomain::IPv6()),
testing::Values(
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IPV6, IPV6_TCLASS),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IPV6_RECVTCLASS,
},
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IPV6, IPV6_HOPLIMIT),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IPV6_RECVHOPLIMIT,
},
CmsgSocketOption{
.cmsg = STRINGIFIED_CMSG(SOL_IPV6, IPV6_PKTINFO),
.cmsg_size = sizeof(in6_pktinfo),
.optname_to_enable_receive = IPV6_RECVPKTINFO,
}),
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup)),
SocketDomainAndOptionAndEnableCmsgReceiveTimeToString);
class NetDatagramSocketsCmsgSendTest : public NetDatagramSocketsCmsgTestBase,
public testing::TestWithParam<SocketDomain> {
protected:
void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(GetParam())); }
cmsghdr OrdinaryControlMessage() {
return {
// SOL_SOCKET/SCM_RIGHTS is used for general cmsg tests, because SOL_SOCKET messages are
// supported on every socket type, and the SCM_RIGHTS control message is a no-op.
// https://github.com/torvalds/linux/blob/42eb8fdac2f/net/core/sock.c#L2628
.cmsg_len = CMSG_LEN(0),
.cmsg_level = SOL_SOCKET,
.cmsg_type = SCM_RIGHTS,
};
}
};
TEST_P(NetDatagramSocketsCmsgSendTest, NullControlBufferWithNonZeroLength) {
char send_buf[] = "hello";
iovec iovec = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
const msghdr send_msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = nullptr,
.msg_controllen = 1,
};
ASSERT_EQ(sendmsg(connected().get(), &send_msghdr, 0), -1);
ASSERT_EQ(errno, EFAULT) << strerror(errno);
}
TEST_P(NetDatagramSocketsCmsgSendTest, NonNullControlBufferWithZeroLength) {
char send_buf[] = "hello";
iovec iovec = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
uint8_t send_control[1];
const msghdr send_msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = send_control,
.msg_controllen = 0,
};
ASSERT_EQ(sendmsg(connected().get(), &send_msghdr, 0), ssize_t(sizeof(send_buf)))
<< strerror(errno);
ASSERT_NO_FATAL_FAILURE(
ReceiveAndCheckMessage(send_buf, sizeof(send_buf), nullptr, 0, [](msghdr& recv_msghdr) {
EXPECT_EQ(recv_msghdr.msg_controllen, 0u);
ASSERT_EQ(CMSG_FIRSTHDR(&recv_msghdr), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgSendTest, ValidCmsg) {
char send_buf[] = "hello";
iovec iovec = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
cmsghdr cmsg = OrdinaryControlMessage();
const msghdr send_msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = &cmsg,
.msg_controllen = cmsg.cmsg_len,
};
ASSERT_EQ(sendmsg(connected().get(), &send_msghdr, 0), ssize_t(sizeof(send_buf)))
<< strerror(errno);
uint8_t recv_control[CMSG_SPACE(0)];
ASSERT_NO_FATAL_FAILURE(ReceiveAndCheckMessage(send_buf, sizeof(send_buf), recv_control,
sizeof(recv_control), [](msghdr& recv_msghdr) {
EXPECT_EQ(recv_msghdr.msg_controllen, 0u);
ASSERT_EQ(CMSG_FIRSTHDR(&recv_msghdr), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgSendTest, CmsgLengthOutOfBounds) {
char send_buf[] = "hello";
iovec iovec = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
cmsghdr cmsg = OrdinaryControlMessage();
const msghdr send_msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = &cmsg,
.msg_controllen = cmsg.cmsg_len,
};
cmsg.cmsg_len++;
ASSERT_EQ(sendmsg(connected().get(), &send_msghdr, 0), -1);
ASSERT_EQ(errno, EINVAL) << strerror(errno);
}
TEST_P(NetDatagramSocketsCmsgSendTest, ControlBufferSmallerThanCmsgHeader) {
char send_buf[] = "hello";
iovec iovec = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
cmsghdr cmsg = OrdinaryControlMessage();
const msghdr send_msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = &cmsg,
.msg_controllen = sizeof(cmsg) - 1,
};
// The control message header would fail basic validation. But because the control buffer length
// is too small, the control message should be ignored.
cmsg.cmsg_len = 0;
ASSERT_EQ(sendmsg(connected().get(), &send_msghdr, 0), ssize_t(sizeof(send_buf)));
uint8_t recv_control[CMSG_SPACE(0)];
ASSERT_NO_FATAL_FAILURE(ReceiveAndCheckMessage(send_buf, sizeof(send_buf), recv_control,
sizeof(recv_control), [](msghdr& recv_msghdr) {
EXPECT_EQ(recv_msghdr.msg_controllen, 0u);
ASSERT_EQ(CMSG_FIRSTHDR(&recv_msghdr), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgSendTest, CmsgLengthSmallerThanCmsgHeader) {
char send_buf[] = "hello";
iovec iovec = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
cmsghdr cmsg = OrdinaryControlMessage();
const msghdr send_msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = &cmsg,
.msg_controllen = cmsg.cmsg_len,
};
// It is invalid to have a control message header with a size smaller than itself.
cmsg.cmsg_len = sizeof(cmsg) - 1;
ASSERT_EQ(sendmsg(connected().get(), &send_msghdr, 0), -1);
ASSERT_EQ(errno, EINVAL) << strerror(errno);
}
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsCmsgSendTests, NetDatagramSocketsCmsgSendTest,
testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
[](const auto info) {
return std::string(socketDomainToString(info.param));
});
using SocketDomainAndEnableCmsgReceiveTime = std::tuple<SocketDomain, EnableCmsgReceiveTime>;
std::string SocketDomainAndEnableCmsgReceiveTimeToString(
const testing::TestParamInfo<SocketDomainAndEnableCmsgReceiveTime>& info) {
auto const& [domain, enable_cmsg_receive_time] = info.param;
std::ostringstream oss;
oss << socketDomainToString(domain);
oss << '_' << enableCmsgReceiveTimeToString(enable_cmsg_receive_time);
return oss.str();
}
class NetDatagramSocketsCmsgTimestampTest
: public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<SocketDomainAndEnableCmsgReceiveTime> {
protected:
void SetUp() override {
auto [domain, enable_cmsg_receive_time] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(domain, enable_cmsg_receive_time));
}
void EnableReceivingCmsg() const override {
// Enable receiving SO_TIMESTAMP control message.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), SOL_SOCKET, SO_TIMESTAMP, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
}
void TearDown() override { EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets()); }
};
TEST_P(NetDatagramSocketsCmsgTimestampTest, RecvCmsg) {
const std::chrono::duration before = std::chrono::system_clock::now().time_since_epoch();
char control[CMSG_SPACE(sizeof(timeval)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control, sizeof(control), [before](msghdr& msghdr) {
ASSERT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(timeval)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(timeval)));
EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
EXPECT_EQ(cmsg->cmsg_type, SO_TIMESTAMP);
timeval received_tv;
memcpy(&received_tv, CMSG_DATA(cmsg), sizeof(received_tv));
const std::chrono::duration received = std::chrono::seconds(received_tv.tv_sec) +
std::chrono::microseconds(received_tv.tv_usec);
const std::chrono::duration after = std::chrono::system_clock::now().time_since_epoch();
// It is possible for the clock to 'jump'. To avoid flakiness, do not check the received
// timestamp if the clock jumped back in time.
if (before <= after) {
ASSERT_GE(received, before);
ASSERT_LE(received, after);
}
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgTimestampTest, RecvCmsgUnalignedControlBuffer) {
const std::chrono::duration before = std::chrono::system_clock::now().time_since_epoch();
char control[CMSG_SPACE(sizeof(timeval)) + 1];
// Pass an unaligned control buffer.
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control + 1, CMSG_LEN(sizeof(timeval)), [before](msghdr& msghdr) {
ASSERT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(timeval)));
// Fetch back the control buffer and confirm it is unaligned.
cmsghdr* unaligned_cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(unaligned_cmsg, nullptr);
ASSERT_NE(reinterpret_cast<uintptr_t>(unaligned_cmsg) % alignof(cmsghdr), 0u);
// Do not access the unaligned control header directly as that would be an undefined
// behavior. Copy the content to a properly aligned variable first.
cmsghdr header = fbl::UnalignedLoad<cmsghdr>(unaligned_cmsg);
EXPECT_EQ(header.cmsg_len, CMSG_LEN(sizeof(timeval)));
EXPECT_EQ(header.cmsg_level, SOL_SOCKET);
EXPECT_EQ(header.cmsg_type, SO_TIMESTAMP);
timeval received_tv = fbl::UnalignedLoad<timeval>(CMSG_DATA(unaligned_cmsg));
const std::chrono::duration received = std::chrono::seconds(received_tv.tv_sec) +
std::chrono::microseconds(received_tv.tv_usec);
const std::chrono::duration after = std::chrono::system_clock::now().time_since_epoch();
// It is possible for the clock to 'jump'. To avoid flakiness, do not check the received
// timestamp if the clock jumped back in time.
if (before <= after) {
ASSERT_GE(received, before);
ASSERT_LE(received, after);
}
// Note: We can't use CMSG_NXTHDR because:
// * it *must* take the unaligned cmsghdr pointer from the control buffer.
// * and it may access its members (cmsg_len), which would be an undefined behavior.
// So we skip the CMSG_NXTHDR assertion that shows that there is no other control message.
}));
}
INSTANTIATE_TEST_SUITE_P(
NetDatagramSocketsCmsgTimestampTests, NetDatagramSocketsCmsgTimestampTest,
testing::Combine(testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv)),
SocketDomainAndEnableCmsgReceiveTimeToString);
class NetDatagramSocketsCmsgTimestampNsTest
: public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<SocketDomainAndEnableCmsgReceiveTime> {
protected:
void SetUp() override {
auto [domain, enable_cmsg_receive_time] = GetParam();
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(domain, enable_cmsg_receive_time));
}
void EnableReceivingCmsg() const override {
// Enable receiving SO_TIMESTAMPNS control message.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), SOL_SOCKET, SO_TIMESTAMPNS, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
}
void TearDown() override { EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets()); }
// libc++ implementation of chrono' system_clock uses microseconds, so we can't use it to
// retrieve the current time for nanosecond timestamp tests.
// https://github.com/llvm-mirror/libcxx/blob/78d6a7767ed/include/chrono#L1574
// The high_resolution_clock is also not appropriate, because it is an alias on the
// steady_clock.
// https://github.com/llvm-mirror/libcxx/blob/78d6a7767ed/include/chrono#L313
void TimeSinceEpoch(std::chrono::nanoseconds& out) const {
struct timespec ts;
ASSERT_EQ(clock_gettime(CLOCK_REALTIME, &ts), 0) << strerror(errno);
out = std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec);
}
};
TEST_P(NetDatagramSocketsCmsgTimestampNsTest, RecvMsg) {
std::chrono::nanoseconds before;
ASSERT_NO_FATAL_FAILURE(TimeSinceEpoch(before));
char control[CMSG_SPACE(sizeof(timespec)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control, sizeof(control), [&](msghdr& msghdr) {
ASSERT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(timespec)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(timespec)));
EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
EXPECT_EQ(cmsg->cmsg_type, SO_TIMESTAMPNS);
timespec received_ts;
memcpy(&received_ts, CMSG_DATA(cmsg), sizeof(received_ts));
const std::chrono::duration received = std::chrono::seconds(received_ts.tv_sec) +
std::chrono::nanoseconds(received_ts.tv_nsec);
std::chrono::nanoseconds after;
ASSERT_NO_FATAL_FAILURE(TimeSinceEpoch(after));
// It is possible for the clock to 'jump'. To avoid flakiness, do not check the received
// timestamp if the clock jumped back in time.
if (before <= after) {
ASSERT_GE(received, before);
ASSERT_LE(received, after);
}
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgTimestampNsTest, RecvCmsgUnalignedControlBuffer) {
std::chrono::nanoseconds before;
ASSERT_NO_FATAL_FAILURE(TimeSinceEpoch(before));
char control[CMSG_SPACE(sizeof(timespec)) + 1];
// Pass an unaligned control buffer.
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control + 1, CMSG_LEN(sizeof(timespec)), [&](msghdr& msghdr) {
ASSERT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(timespec)));
// Fetch back the control buffer and confirm it is unaligned.
cmsghdr* unaligned_cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(unaligned_cmsg, nullptr);
ASSERT_NE(reinterpret_cast<uintptr_t>(unaligned_cmsg) % alignof(cmsghdr), 0u);
// Do not access the unaligned control header directly as that would be an undefined
// behavior. Copy the content to a properly aligned variable first.
char aligned_cmsg[CMSG_SPACE(sizeof(timespec))];
memcpy(&aligned_cmsg, reinterpret_cast<std::byte*>(unaligned_cmsg), sizeof(aligned_cmsg));
cmsghdr* cmsg = reinterpret_cast<cmsghdr*>(aligned_cmsg);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(timespec)));
EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
EXPECT_EQ(cmsg->cmsg_type, SO_TIMESTAMPNS);
timespec received_tv;
memcpy(&received_tv, CMSG_DATA(cmsg), sizeof(received_tv));
const std::chrono::duration received = std::chrono::seconds(received_tv.tv_sec) +
std::chrono::nanoseconds(received_tv.tv_nsec);
std::chrono::nanoseconds after;
ASSERT_NO_FATAL_FAILURE(TimeSinceEpoch(after));
// It is possible for the clock to 'jump'. To avoid flakiness, do not check the received
// timestamp if the clock jumped back in time.
if (before <= after) {
ASSERT_GE(received, before);
ASSERT_LE(received, after);
}
// Note: We can't use CMSG_NXTHDR because:
// * it *must* take the unaligned cmsghdr pointer from the control buffer.
// * and it may access its members (cmsg_len), which would be an undefined behavior.
// So we skip the CMSG_NXTHDR assertion that shows that there is no other control message.
}));
}
INSTANTIATE_TEST_SUITE_P(
NetDatagramSocketsCmsgTimestampNsTests, NetDatagramSocketsCmsgTimestampNsTest,
testing::Combine(testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv)),
SocketDomainAndEnableCmsgReceiveTimeToString);
class NetDatagramSocketsCmsgIpTosTest : public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<EnableCmsgReceiveTime> {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(SocketDomain::IPv4(), GetParam()));
}
void EnableReceivingCmsg() const override {
// Enable receiving IP_RECVTOS control message.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), SOL_IP, IP_RECVTOS, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(NetDatagramSocketsCmsgIpTosTest, RecvCmsg) {
constexpr uint8_t tos = 42;
ASSERT_EQ(setsockopt(connected().get(), SOL_IP, IP_TOS, &tos, sizeof(tos)), 0) << strerror(errno);
char control[CMSG_SPACE(sizeof(tos)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control, sizeof(control), [tos](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(tos)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(tos)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IP);
EXPECT_EQ(cmsg->cmsg_type, IP_TOS);
uint8_t recv_tos;
memcpy(&recv_tos, CMSG_DATA(cmsg), sizeof(recv_tos));
EXPECT_EQ(recv_tos, tos);
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpTosTest, RecvCmsgBufferTooSmallToBePadded) {
constexpr uint8_t tos = 42;
ASSERT_EQ(setsockopt(connected().get(), SOL_IP, IP_TOS, &tos, sizeof(tos)), 0) << strerror(errno);
// This test is only meaningful if the length of the data is not aligned.
ASSERT_NE(CMSG_ALIGN(sizeof(tos)), sizeof(tos));
// Add an extra byte in the control buffer. It will be reported in the msghdr controllen field.
char control[CMSG_LEN(sizeof(tos)) + 1];
ASSERT_NO_FATAL_FAILURE(SendAndCheckReceivedMessage(control, sizeof(control), [](msghdr& msghdr) {
// There is not enough space in the control buffer for it to be padded by CMSG_SPACE. So we
// expect the size of the input control buffer in controllen instead. It indicates that every
// bytes from the control buffer were used.
EXPECT_EQ(msghdr.msg_controllen, CMSG_LEN(sizeof(tos)) + 1);
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(tos)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IP);
EXPECT_EQ(cmsg->cmsg_type, IP_TOS);
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpTosTest, SendCmsg) {
constexpr uint8_t tos = 42;
char send_buf[] = "hello";
ASSERT_NO_FATAL_FAILURE(
SendWithCmsg(connected().get(), send_buf, sizeof(send_buf), SOL_IP, IP_TOS, tos));
char recv_control[CMSG_SPACE(sizeof(tos)) + 1];
ASSERT_NO_FATAL_FAILURE(ReceiveAndCheckMessage(
send_buf, sizeof(send_buf), recv_control, sizeof(recv_control), [tos](msghdr& recv_msghdr) {
EXPECT_EQ(recv_msghdr.msg_controllen, CMSG_SPACE(sizeof(tos)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(tos)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IP);
EXPECT_EQ(cmsg->cmsg_type, IP_TOS);
uint8_t recv_tos;
memcpy(&recv_tos, CMSG_DATA(cmsg), sizeof(recv_tos));
if (kIsFuchsia) {
// TODO(https://fxbug.dev/42094933): Support sending SOL_IP -> IP_TOS control message.
constexpr uint8_t kDefaultTOS = 0;
EXPECT_EQ(recv_tos, kDefaultTOS);
} else {
EXPECT_EQ(recv_tos, tos);
}
EXPECT_EQ(CMSG_NXTHDR(&recv_msghdr, cmsg), nullptr);
}));
}
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsCmsgIpTosTests, NetDatagramSocketsCmsgIpTosTest,
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv),
[](const auto info) {
return std::string(enableCmsgReceiveTimeToString(info.param));
});
class NetDatagramSocketsCmsgIpTtlTest : public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<EnableCmsgReceiveTime> {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(SocketDomain::IPv4(), GetParam()));
}
void EnableReceivingCmsg() const override {
// Enable receiving IP_TTL control message.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), SOL_IP, IP_RECVTTL, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(NetDatagramSocketsCmsgIpTtlTest, RecvCmsg) {
constexpr int kTtl = 42;
ASSERT_EQ(setsockopt(connected().get(), SOL_IP, IP_TTL, &kTtl, sizeof(kTtl)), 0)
<< strerror(errno);
char control[CMSG_SPACE(sizeof(kTtl)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control, sizeof(control), [kTtl](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(kTtl)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kTtl)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IP);
EXPECT_EQ(cmsg->cmsg_type, IP_TTL);
int recv_ttl;
memcpy(&recv_ttl, CMSG_DATA(cmsg), sizeof(recv_ttl));
EXPECT_EQ(recv_ttl, kTtl);
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpTtlTest, RecvCmsgUnalignedControlBuffer) {
constexpr int kDefaultTTL = 64;
char control[CMSG_SPACE(sizeof(kDefaultTTL)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control + 1, sizeof(control), [kDefaultTTL](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(kDefaultTTL)));
// Fetch back the control buffer and confirm it is unaligned.
cmsghdr* unaligned_cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(unaligned_cmsg, nullptr);
ASSERT_NE(reinterpret_cast<uintptr_t>(unaligned_cmsg) % alignof(cmsghdr), 0u);
// Copy the content to a properly aligned variable.
char aligned_cmsg[CMSG_SPACE(sizeof(kDefaultTTL))];
memcpy(&aligned_cmsg, reinterpret_cast<std::byte*>(unaligned_cmsg), sizeof(aligned_cmsg));
cmsghdr* cmsg = reinterpret_cast<cmsghdr*>(aligned_cmsg);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kDefaultTTL)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IP);
EXPECT_EQ(cmsg->cmsg_type, IP_TTL);
int recv_ttl;
memcpy(&recv_ttl, CMSG_DATA(cmsg), sizeof(recv_ttl));
EXPECT_EQ(recv_ttl, kDefaultTTL);
}));
}
TEST_P(NetDatagramSocketsCmsgIpTtlTest, SendCmsg) {
constexpr int kTtl = 42;
char send_buf[] = "hello";
ASSERT_NO_FATAL_FAILURE(
SendWithCmsg(connected().get(), send_buf, sizeof(send_buf), SOL_IP, IP_TTL, kTtl));
char recv_control[CMSG_SPACE(sizeof(kTtl)) + 1];
ASSERT_NO_FATAL_FAILURE(ReceiveAndCheckMessage(
send_buf, sizeof(send_buf), recv_control, sizeof(recv_control), [kTtl](msghdr& recv_msghdr) {
EXPECT_EQ(recv_msghdr.msg_controllen, CMSG_SPACE(sizeof(kTtl)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kTtl)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IP);
EXPECT_EQ(cmsg->cmsg_type, IP_TTL);
int recv_ttl;
memcpy(&recv_ttl, reinterpret_cast<std::byte*>(CMSG_DATA(cmsg)), sizeof(recv_ttl));
EXPECT_EQ(recv_ttl, kTtl);
EXPECT_EQ(CMSG_NXTHDR(&recv_msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpTtlTest, SendCmsgInvalidValues) {
// A valid IP_TTL must fit in an single byte and must not be zero.
// https://github.com/torvalds/linux/blob/f443e374ae1/net/ipv4/ip_sockglue.c#L304
constexpr std::array<int, 3> kInvalidValues = {-1, 0, 256};
for (int value : kInvalidValues) {
SCOPED_TRACE("ttl=" + std::to_string(value));
char send_buf[] = "hello";
iovec iov = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
std::array<uint8_t, CMSG_SPACE(sizeof(value))> control;
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control.data(),
.msg_controllen = CMSG_LEN(sizeof(value)),
};
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(cmsg, nullptr);
*cmsg = {
.cmsg_len = CMSG_LEN(sizeof(value)),
.cmsg_level = SOL_IP,
.cmsg_type = IP_TTL,
};
memcpy(CMSG_DATA(cmsg), &value, sizeof(value));
ASSERT_EQ(sendmsg(connected().get(), &msg, 0), -1);
ASSERT_EQ(errno, EINVAL) << strerror(errno);
}
}
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsCmsgIpTtlTests, NetDatagramSocketsCmsgIpTtlTest,
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv),
[](const auto info) {
return std::string(enableCmsgReceiveTimeToString(info.param));
});
class NetDatagramSocketsCmsgIpv6TClassTest : public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<EnableCmsgReceiveTime> {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(SocketDomain::IPv6(), GetParam()));
}
void EnableReceivingCmsg() const override {
// Enable receiving IPV6_TCLASS control message.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), SOL_IPV6, IPV6_RECVTCLASS, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(NetDatagramSocketsCmsgIpv6TClassTest, RecvCmsg) {
constexpr int kTClass = 42;
ASSERT_EQ(setsockopt(connected().get(), SOL_IPV6, IPV6_TCLASS, &kTClass, sizeof(kTClass)), 0)
<< strerror(errno);
char control[CMSG_SPACE(sizeof(kTClass)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control, sizeof(control), [kTClass](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(kTClass)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kTClass)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_TCLASS);
uint8_t recv_tclass;
memcpy(&recv_tclass, CMSG_DATA(cmsg), sizeof(recv_tclass));
EXPECT_EQ(recv_tclass, kTClass);
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpv6TClassTest, RecvCmsgUnalignedControlBuffer) {
constexpr int kTClass = 42;
ASSERT_EQ(setsockopt(connected().get(), SOL_IPV6, IPV6_TCLASS, &kTClass, sizeof(kTClass)), 0)
<< strerror(errno);
char control[CMSG_SPACE(sizeof(kTClass)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control + 1, sizeof(control), [kTClass](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(kTClass)));
// Fetch back the control buffer and confirm it is unaligned.
cmsghdr* unaligned_cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(unaligned_cmsg, nullptr);
ASSERT_NE(reinterpret_cast<uintptr_t>(unaligned_cmsg) % alignof(cmsghdr), 0u);
// Copy the content to a properly aligned variable.
char aligned_cmsg[CMSG_SPACE(sizeof(kTClass))];
memcpy(&aligned_cmsg, reinterpret_cast<std::byte*>(unaligned_cmsg), sizeof(aligned_cmsg));
cmsghdr* cmsg = reinterpret_cast<cmsghdr*>(aligned_cmsg);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kTClass)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_TCLASS);
uint8_t recv_tclass;
memcpy(&recv_tclass, CMSG_DATA(cmsg), sizeof(recv_tclass));
EXPECT_EQ(recv_tclass, kTClass);
}));
}
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsCmsgIpv6TClassTests,
NetDatagramSocketsCmsgIpv6TClassTest,
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv),
[](const auto info) {
return std::string(enableCmsgReceiveTimeToString(info.param));
});
class NetDatagramSocketsCmsgIpv6HopLimitTest
: public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<EnableCmsgReceiveTime> {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(SocketDomain::IPv6(), GetParam()));
}
void EnableReceivingCmsg() const override {
// Enable receiving IPV6_HOPLIMIT control message.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), SOL_IPV6, IPV6_RECVHOPLIMIT, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(NetDatagramSocketsCmsgIpv6HopLimitTest, RecvCmsg) {
constexpr int kHopLimit = 42;
ASSERT_EQ(
setsockopt(connected().get(), SOL_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit)), 0)
<< strerror(errno);
char control[CMSG_SPACE(sizeof(kHopLimit)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control, sizeof(control), [kHopLimit](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(kHopLimit)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kHopLimit)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_HOPLIMIT);
int recv_hoplimit;
memcpy(&recv_hoplimit, CMSG_DATA(cmsg), sizeof(recv_hoplimit));
EXPECT_EQ(recv_hoplimit, kHopLimit);
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpv6HopLimitTest, RecvCmsgUnalignedControlBuffer) {
constexpr int kDefaultHopLimit = 64;
char control[CMSG_SPACE(sizeof(kDefaultHopLimit)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control + 1, sizeof(control), [kDefaultHopLimit](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(kDefaultHopLimit)));
// Fetch back the control buffer and confirm it is unaligned.
cmsghdr* unaligned_cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(unaligned_cmsg, nullptr);
ASSERT_NE(reinterpret_cast<uintptr_t>(unaligned_cmsg) % alignof(cmsghdr), 0u);
// Copy the content to a properly aligned variable.
char aligned_cmsg[CMSG_SPACE(sizeof(kDefaultHopLimit))];
memcpy(&aligned_cmsg, reinterpret_cast<std::byte*>(unaligned_cmsg), sizeof(aligned_cmsg));
cmsghdr* cmsg = reinterpret_cast<cmsghdr*>(aligned_cmsg);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kDefaultHopLimit)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_HOPLIMIT);
int recv_hoplimit;
memcpy(&recv_hoplimit, CMSG_DATA(cmsg), sizeof(recv_hoplimit));
EXPECT_EQ(recv_hoplimit, kDefaultHopLimit);
}));
}
TEST_P(NetDatagramSocketsCmsgIpv6HopLimitTest, SendCmsg) {
constexpr int kHopLimit = 42;
char send_buf[] = "hello";
ASSERT_NO_FATAL_FAILURE(SendWithCmsg(connected().get(), send_buf, sizeof(send_buf), SOL_IPV6,
IPV6_HOPLIMIT, kHopLimit));
char recv_control[CMSG_SPACE(sizeof(kHopLimit)) + 1];
ASSERT_NO_FATAL_FAILURE(
ReceiveAndCheckMessage(send_buf, sizeof(send_buf), recv_control, sizeof(recv_control),
[kHopLimit](msghdr& recv_msghdr) {
EXPECT_EQ(recv_msghdr.msg_controllen, CMSG_SPACE(sizeof(kHopLimit)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kHopLimit)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_HOPLIMIT);
int recv_hoplimit;
memcpy(&recv_hoplimit, CMSG_DATA(cmsg), sizeof(recv_hoplimit));
EXPECT_EQ(recv_hoplimit, kHopLimit);
EXPECT_EQ(CMSG_NXTHDR(&recv_msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpv6HopLimitTest, SendCmsgDefaultValue) {
constexpr int kConfiguredHopLimit = 42;
ASSERT_EQ(setsockopt(connected().get(), SOL_IPV6, IPV6_UNICAST_HOPS, &kConfiguredHopLimit,
sizeof(kConfiguredHopLimit)),
0)
<< strerror(errno);
char send_buf[] = "hello";
constexpr int kUseConfiguredHopLimitValue = -1;
ASSERT_NO_FATAL_FAILURE(SendWithCmsg(connected().get(), send_buf, sizeof(send_buf), SOL_IPV6,
IPV6_HOPLIMIT, kUseConfiguredHopLimitValue));
char recv_control[CMSG_SPACE(sizeof(kConfiguredHopLimit)) + 1];
ASSERT_NO_FATAL_FAILURE(ReceiveAndCheckMessage(
send_buf, sizeof(send_buf), recv_control, sizeof(recv_control),
[kConfiguredHopLimit](msghdr& recv_msghdr) {
EXPECT_EQ(recv_msghdr.msg_controllen, CMSG_SPACE(sizeof(kConfiguredHopLimit)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(kConfiguredHopLimit)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_HOPLIMIT);
int recv_hoplimit;
memcpy(&recv_hoplimit, reinterpret_cast<std::byte*>(CMSG_DATA(cmsg)),
sizeof(recv_hoplimit));
EXPECT_EQ(recv_hoplimit, kConfiguredHopLimit);
EXPECT_EQ(CMSG_NXTHDR(&recv_msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpv6HopLimitTest, SendCmsgInvalidValues) {
constexpr std::array<int, 2> kInvalidValues = {-2, 256};
for (int value : kInvalidValues) {
SCOPED_TRACE("hoplimit=" + std::to_string(value));
char send_buf[] = "hello";
iovec iov = {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
};
std::array<uint8_t, CMSG_SPACE(sizeof(value))> control;
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control.data(),
.msg_controllen = CMSG_LEN(sizeof(value)),
};
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(cmsg, nullptr);
*cmsg = {
.cmsg_len = CMSG_LEN(sizeof(value)),
.cmsg_level = SOL_IPV6,
.cmsg_type = IPV6_HOPLIMIT,
};
memcpy(CMSG_DATA(cmsg), &value, sizeof(value));
ASSERT_EQ(sendmsg(connected().get(), &msg, 0), -1);
ASSERT_EQ(errno, EINVAL) << strerror(errno);
}
}
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsCmsgIpv6HopLimitTests,
NetDatagramSocketsCmsgIpv6HopLimitTest,
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv),
[](const auto info) {
return std::string(enableCmsgReceiveTimeToString(info.param));
});
class NetDatagramSocketsCmsgIpv6PktInfoTest : public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<EnableCmsgReceiveTime> {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(SocketDomain::IPv6(), GetParam()));
}
void EnableReceivingCmsg() const override {
// Enable receiving IPV6_PKTINFO control message.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), SOL_IPV6, IPV6_RECVPKTINFO, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(NetDatagramSocketsCmsgIpv6PktInfoTest, RecvCmsg) {
char control[CMSG_SPACE(sizeof(in6_pktinfo)) + 1];
ASSERT_NO_FATAL_FAILURE(SendAndCheckReceivedMessage(control, sizeof(control), [](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(in6_pktinfo)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(in6_pktinfo)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_PKTINFO);
in6_pktinfo recv_pktinfo;
memcpy(&recv_pktinfo, CMSG_DATA(cmsg), sizeof(recv_pktinfo));
const unsigned int lo_ifindex = if_nametoindex("lo");
EXPECT_NE(lo_ifindex, 0u) << strerror(errno);
EXPECT_EQ(recv_pktinfo.ipi6_ifindex, lo_ifindex);
char buf[INET6_ADDRSTRLEN];
ASSERT_TRUE(IN6_IS_ADDR_LOOPBACK(&recv_pktinfo.ipi6_addr))
<< inet_ntop(AF_INET6, &recv_pktinfo.ipi6_addr, buf, sizeof(buf));
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
}));
}
TEST_P(NetDatagramSocketsCmsgIpv6PktInfoTest, RecvCmsgUnalignedControlBuffer) {
char control[CMSG_SPACE(sizeof(in6_pktinfo)) + 1];
ASSERT_NO_FATAL_FAILURE(
SendAndCheckReceivedMessage(control + 1, sizeof(control), [](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(in6_pktinfo)));
// Fetch back the control buffer and confirm it is unaligned.
cmsghdr* unaligned_cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(unaligned_cmsg, nullptr);
ASSERT_NE(reinterpret_cast<uintptr_t>(unaligned_cmsg) % alignof(cmsghdr), 0u);
// Copy the content to a properly aligned variable.
char aligned_cmsg[CMSG_SPACE(sizeof(in6_pktinfo))];
memcpy(&aligned_cmsg, reinterpret_cast<std::byte*>(unaligned_cmsg), sizeof(aligned_cmsg));
cmsghdr* cmsg = reinterpret_cast<cmsghdr*>(aligned_cmsg);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(in6_pktinfo)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
EXPECT_EQ(cmsg->cmsg_type, IPV6_PKTINFO);
in6_pktinfo recv_pktinfo;
memcpy(&recv_pktinfo, CMSG_DATA(cmsg), sizeof(recv_pktinfo));
const unsigned int lo_ifindex = if_nametoindex("lo");
EXPECT_NE(lo_ifindex, 0u) << strerror(errno);
EXPECT_EQ(recv_pktinfo.ipi6_ifindex, lo_ifindex);
char buf[INET6_ADDRSTRLEN];
ASSERT_TRUE(IN6_IS_ADDR_LOOPBACK(&recv_pktinfo.ipi6_addr))
<< inet_ntop(AF_INET6, &recv_pktinfo.ipi6_addr, buf, sizeof(buf));
}));
}
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsCmsgIpv6PktInfoTests,
NetDatagramSocketsCmsgIpv6PktInfoTest,
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv),
[](const auto info) {
return std::string(enableCmsgReceiveTimeToString(info.param));
});
class NetDatagramSocketsMultipleIpv6CmsgsTest
: public NetDatagramSocketsCmsgRecvTestBase,
public testing::TestWithParam<EnableCmsgReceiveTime> {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(SocketDomain::IPv6(), GetParam()));
}
void EnableReceivingCmsg() const override {
// Enable receiving IPV6_HOPLIMIT control message.
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), SOL_IPV6, IPV6_RECVHOPLIMIT, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
// Enable receiving IPV6_PKTINFO control message.
ASSERT_EQ(setsockopt(bound().get(), SOL_IPV6, IPV6_RECVPKTINFO, &kOne, sizeof(kOne)), 0)
<< strerror(errno);
}
void TearDown() override {
if (!IsSkipped()) {
EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets());
}
}
};
TEST_P(NetDatagramSocketsMultipleIpv6CmsgsTest, SendCmsg) {
char send_buf[] = "hello";
size_t buf_len = sizeof(send_buf);
iovec iov = {
.iov_base = send_buf,
.iov_len = buf_len,
};
constexpr int kHopLimit = 42;
const unsigned int lo_ifindex = if_nametoindex("lo");
EXPECT_NE(lo_ifindex, 0u) << strerror(errno);
const in6_pktinfo pktinfo = {
.ipi6_addr = IN6ADDR_LOOPBACK_INIT,
.ipi6_ifindex = lo_ifindex,
};
// This buffer needs to be zero-initialized for CMSG_NXTHDR.
// See CMSG_NXTHDR in https://man7.org/linux/man-pages/man3/cmsg.3.html
char control[CMSG_SPACE(sizeof(kHopLimit)) + CMSG_SPACE(sizeof(pktinfo))] = {};
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
};
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
*cmsg = {
.cmsg_len = CMSG_LEN(sizeof(kHopLimit)),
.cmsg_level = SOL_IPV6,
.cmsg_type = IPV6_HOPLIMIT,
};
memcpy(CMSG_DATA(cmsg), &kHopLimit, sizeof(kHopLimit));
cmsg = CMSG_NXTHDR(&msg, cmsg);
*cmsg = {
.cmsg_len = CMSG_LEN(sizeof(pktinfo)),
.cmsg_level = SOL_IPV6,
.cmsg_type = IPV6_PKTINFO,
};
memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo));
ASSERT_EQ(sendmsg(connected().get(), &msg, 0), ssize_t(buf_len)) << strerror(errno);
// This buffer needs to be zero-initialized for CMSG_NXTHDR.
char recv_control[CMSG_SPACE(sizeof(kHopLimit)) + CMSG_SPACE(sizeof(pktinfo))] = {};
ASSERT_NO_FATAL_FAILURE(ReceiveAndCheckMessage(
send_buf, buf_len, recv_control, sizeof(recv_control), [kHopLimit, pktinfo](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen,
CMSG_SPACE(sizeof(kHopLimit) + CMSG_SPACE(sizeof(pktinfo))));
int hoplimit_count = 0;
int pktinfo_count = 0;
for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr); cmsg != nullptr;
cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
const cmsghdr& cmsg_ref = *cmsg;
EXPECT_EQ(cmsg_ref.cmsg_level, SOL_IPV6);
switch (cmsg_ref.cmsg_type) {
case IPV6_HOPLIMIT: {
EXPECT_EQ(cmsg_ref.cmsg_len, CMSG_LEN(sizeof(kHopLimit)));
int recv_hoplimit;
memcpy(&recv_hoplimit, CMSG_DATA(cmsg), sizeof(recv_hoplimit));
EXPECT_EQ(recv_hoplimit, kHopLimit);
hoplimit_count++;
break;
}
case IPV6_PKTINFO: {
EXPECT_EQ(cmsg_ref.cmsg_len, CMSG_LEN(sizeof(pktinfo)));
in6_pktinfo recv_pktinfo;
memcpy(&recv_pktinfo, CMSG_DATA(cmsg), sizeof(recv_pktinfo));
EXPECT_EQ(recv_pktinfo.ipi6_ifindex, pktinfo.ipi6_ifindex);
char buf[INET6_ADDRSTRLEN];
ASSERT_TRUE(IN6_IS_ADDR_LOOPBACK(&recv_pktinfo.ipi6_addr))
<< "unexpected addr: "
<< inet_ntop(AF_INET6, &recv_pktinfo.ipi6_addr, buf, sizeof(buf));
pktinfo_count++;
break;
}
default:
FAIL() << "unexpected cmsg type: " << cmsg_ref.cmsg_type;
break;
}
}
EXPECT_EQ(hoplimit_count, 1);
EXPECT_EQ(pktinfo_count, 1);
}));
}
INSTANTIATE_TEST_SUITE_P(NetDatagramSocketsMultipleIpv6CmsgsTests,
NetDatagramSocketsMultipleIpv6CmsgsTest,
testing::Values(EnableCmsgReceiveTime::AfterSocketSetup,
EnableCmsgReceiveTime::BetweenSendAndRecv),
[](const auto info) {
return std::string(enableCmsgReceiveTimeToString(info.param));
});
template <typename Instance, typename Arg>
void ValidateLinearizedSendSemantics(const Arg& arg) {
// NOTE: our goal here is to exercise the potential race condition in which a setting
// enabled by a control plane operation is mistakenly applied to a datagram that was
// previously sent. We do so by repeatedly exercising the loop below. We've parallelized
// this loop into multiple shards to reduce the overall latency; empirical testing
// suggested that this sharding produced the highest throughput of [loops / second].
constexpr size_t kIterationsPerThread = 50;
constexpr size_t kNumThreads = 10;
std::vector<std::thread> threads;
for (size_t i = 0; i < kNumThreads; i++) {
Instance instance(arg);
ASSERT_NO_FATAL_FAILURE(instance.SetUpInstance());
threads.emplace_back([instance = std::move(instance)]() mutable {
for (size_t i = 0; i < kIterationsPerThread; i++) {
ASSERT_NO_FATAL_FAILURE(instance.ToggleOn());
ASSERT_NO_FATAL_FAILURE(instance.SendDatagram());
ASSERT_NO_FATAL_FAILURE(instance.ToggleOff());
ASSERT_NO_FATAL_FAILURE(instance.ObserveOn());
}
ASSERT_NO_FATAL_FAILURE(instance.TearDownInstance());
});
}
for (std::thread& t : threads) {
t.join();
}
}
template <typename Instance, typename Arg>
void ValidateCachedSendSemantics(const Arg& arg) {
// NOTE: our goal here is to ensure that control plane operations invalidate client side
// caches, ensuring that subsequent datagrams are processed with the setting enabled by
// the operation. The loop here is just to ensure we cover all of the transitions
// (on -> send -> observe -> off -> send -> observe -> on). The number of iterations is
// basically arbitrary.
constexpr size_t kIterations = 10;
Instance instance(arg);
ASSERT_NO_FATAL_FAILURE(instance.SetUpInstance());
for (size_t i = 0; i < kIterations; i++) {
ASSERT_NO_FATAL_FAILURE(instance.ToggleOn());
ASSERT_NO_FATAL_FAILURE(instance.SendDatagram());
ASSERT_NO_FATAL_FAILURE(instance.ObserveOn());
ASSERT_NO_FATAL_FAILURE(instance.ToggleOff());
ASSERT_NO_FATAL_FAILURE(instance.SendDatagramAndObserveOff());
}
ASSERT_NO_FATAL_FAILURE(instance.TearDownInstance());
}
template <typename T>
struct CmsgValues {
T on;
T off;
};
template <typename T>
std::ostream& operator<<(std::ostream& oss, const CmsgValues<T>& cmsg_values) {
oss << "_ValueOn_" << std::to_string(cmsg_values.on) << "_ValueOff_"
<< std::to_string(cmsg_values.off);
return oss;
}
using cmsgValuesVariant = std::variant<CmsgValues<int>, CmsgValues<uint8_t>>;
struct CmsgLinearizedSendTestCase {
SocketDomain domain;
CmsgSocketOption recv_option;
int send_type;
cmsgValuesVariant send_values;
};
std::string CmsgLinearizedSendTestCaseToString(
const testing::TestParamInfo<CmsgLinearizedSendTestCase>& info) {
auto const& test_case = info.param;
std::ostringstream oss;
oss << socketDomainToString(test_case.domain);
oss << '_' << test_case.recv_option;
std::visit([&](auto arg) { oss << arg; }, test_case.send_values);
return oss.str();
}
class DatagramLinearizedSendSemanticsCmsgTestInstance : public NetDatagramSocketsCmsgTestBase {
public:
DatagramLinearizedSendSemanticsCmsgTestInstance(const CmsgLinearizedSendTestCase& test_case)
: test_case_(test_case) {}
void SetUpInstance() {
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(test_case_.domain));
constexpr int kOne = 1;
ASSERT_EQ(setsockopt(bound().get(), test_case_.recv_option.cmsg.level,
test_case_.recv_option.optname_to_enable_receive, &kOne, sizeof(kOne)),
0)
<< strerror(errno);
}
void TearDownInstance() { EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets()); }
void ToggleOn() {
std::visit(
[&](auto arg) {
ASSERT_EQ(setsockopt(connected().get(), test_case_.recv_option.cmsg.level,
test_case_.send_type, &arg.on, sizeof(arg.on)),
0)
<< strerror(errno);
},
test_case_.send_values);
}
void ToggleOff() {
std::visit(
[&](auto arg) {
ASSERT_EQ(setsockopt(connected().get(), test_case_.recv_option.cmsg.level,
test_case_.send_type, &arg.off, sizeof(arg.off)),
0)
<< strerror(errno);
},
test_case_.send_values);
}
void SendDatagram() {
ASSERT_EQ(send(connected().get(), kBuf.data(), kBuf.size(), 0), ssize_t(kBuf.size()))
<< strerror(errno);
}
void ObserveOn() {
std::visit([&](auto arg) { ASSERT_NO_FATAL_FAILURE(RecvDatagramAndValidateCmsg(arg.on)); },
test_case_.send_values);
}
private:
template <typename CmsgType>
void RecvDatagramAndValidateCmsg(CmsgType expected_value) {
const int cmsg_level = test_case_.recv_option.cmsg.level;
const int cmsg_type = test_case_.recv_option.cmsg.type;
char control[CMSG_SPACE(sizeof(expected_value)) + 1];
ReceiveAndCheckMessage(kBuf.data(), kBuf.size(), control, sizeof(control), [&](msghdr& msghdr) {
EXPECT_EQ(msghdr.msg_controllen, CMSG_SPACE(sizeof(expected_value)));
cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(expected_value)));
EXPECT_EQ(cmsg->cmsg_level, cmsg_level);
EXPECT_EQ(cmsg->cmsg_type, cmsg_type);
CmsgType found_value;
memcpy(&found_value, CMSG_DATA(cmsg), sizeof(found_value));
EXPECT_EQ(found_value, expected_value);
// Verify that message doesn't extend past the data block.
uint8_t* data = CMSG_DATA(cmsg);
uint8_t* data_end = data + sizeof(found_value);
uint8_t* msg_end = reinterpret_cast<uint8_t*>(cmsg) + cmsg->cmsg_len;
EXPECT_EQ(data_end, msg_end);
EXPECT_EQ(CMSG_NXTHDR(&msghdr, cmsg), nullptr);
});
}
static constexpr std::string_view kBuf = "hello";
CmsgLinearizedSendTestCase test_case_;
};
class DatagramLinearizedSendSemanticsCmsgTest
: public testing::TestWithParam<CmsgLinearizedSendTestCase> {};
TEST_P(DatagramLinearizedSendSemanticsCmsgTest, Evaluate) {
ASSERT_NO_FATAL_FAILURE(
ValidateLinearizedSendSemantics<DatagramLinearizedSendSemanticsCmsgTestInstance>(GetParam()));
}
INSTANTIATE_TEST_SUITE_P(DatagramLinearizedSendSemanticsCmsgTests,
DatagramLinearizedSendSemanticsCmsgTest,
testing::Values(
CmsgLinearizedSendTestCase{
.domain = SocketDomain::IPv4(),
.recv_option =
{
.cmsg = STRINGIFIED_CMSG(SOL_IP, IP_TOS),
.cmsg_size = sizeof(uint8_t),
.optname_to_enable_receive = IP_RECVTOS,
},
.send_type = IP_TOS,
.send_values = cmsgValuesVariant(CmsgValues<uint8_t>{
.on = 42,
.off = 0,
}),
},
CmsgLinearizedSendTestCase{
.domain = SocketDomain::IPv4(),
.recv_option =
{
.cmsg = STRINGIFIED_CMSG(SOL_IP, IP_TTL),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IP_RECVTTL,
},
.send_type = IP_TTL,
.send_values = cmsgValuesVariant(CmsgValues<int>{
.on = 42,
.off = 1,
}),
},
CmsgLinearizedSendTestCase{
.domain = SocketDomain::IPv6(),
.recv_option =
{
.cmsg = STRINGIFIED_CMSG(SOL_IPV6, IPV6_TCLASS),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IPV6_RECVTCLASS,
},
.send_type = IPV6_TCLASS,
.send_values = cmsgValuesVariant(CmsgValues<int>{
.on = 42,
.off = 0,
}),
},
CmsgLinearizedSendTestCase{
.domain = SocketDomain::IPv6(),
.recv_option =
{
.cmsg = STRINGIFIED_CMSG(SOL_IPV6, IPV6_HOPLIMIT),
.cmsg_size = sizeof(int),
.optname_to_enable_receive = IPV6_RECVHOPLIMIT,
},
.send_type = IPV6_UNICAST_HOPS,
.send_values = cmsgValuesVariant(CmsgValues<int>{
.on = 42,
.off = 0,
}),
}),
CmsgLinearizedSendTestCaseToString);
class DatagramSendSemanticsTestInstance : public NetDatagramSocketsTestBase {
public:
DatagramSendSemanticsTestInstance(const SocketDomain& domain) : domain_(domain) {}
virtual void SetUpInstance() {
ASSERT_NO_FATAL_FAILURE(SetUpDatagramSockets(domain_));
recvbuf_.resize(kBuf.size() + 1);
}
virtual void TearDownInstance() { EXPECT_NO_FATAL_FAILURE(TearDownDatagramSockets()); }
protected:
void SendDatagramFrom(int fd) {
ASSERT_EQ(send(fd, kBuf.data(), kBuf.size(), 0), ssize_t(kBuf.size())) << strerror(errno);
}
void RecvDatagramOn(int fd) {
pollfd pfd = {
.fd = fd,
.events = POLLIN,
};
const int n = poll(&pfd, 1, std::chrono::milliseconds(kDeprecatedTimeout).count());
EXPECT_GE(n, 0) << strerror(errno);
EXPECT_EQ(n, 1);
ASSERT_EQ(read(fd, recvbuf_.data(), recvbuf_.size()), ssize_t(kBuf.size())) << strerror(errno);
EXPECT_STREQ(kBuf.data(), recvbuf_.data());
}
SocketDomain domain_;
static constexpr std::string_view kBuf = "hello";
private:
std::string recvbuf_;
};
class DatagramCachedSendSemanticsTest : public testing::TestWithParam<SocketDomain> {};
class DatagramLinearizedSendSemanticsTest : public testing::TestWithParam<SocketDomain> {};
class DatagramSendSemanticsConnectInstance : public DatagramSendSemanticsTestInstance {
public:
DatagramSendSemanticsConnectInstance(const SocketDomain& domain)
: DatagramSendSemanticsTestInstance(domain) {}
void SetUpInstance() override {
DatagramSendSemanticsTestInstance::SetUpInstance();
const auto [addr, addrlen] = LoopbackSockaddrAndSocklenForDomain(domain_);
addrlen_ = addrlen;
// Create a third socket on the system with a distinct bound address. We alternate
// between connecting the `connected()` socket to this new socket vs the original `bound()`
// socket. We validate that packets reach the address to which `connected()` was bound
// when `send()` was called -- even when the socket is re-`connect()`ed elsewhere immediately
// afterwards.
ASSERT_TRUE(receiver_fd_ = fbl::unique_fd(socket(domain_.Get(), SOCK_DGRAM, 0)))
<< strerror(errno);
ASSERT_EQ(bind(receiver_fd_.get(), reinterpret_cast<const sockaddr*>(&addr), addrlen_), 0)
<< strerror(errno);
}
void ToggleOn() {
sockaddr_storage addr;
ASSERT_NO_FATAL_FAILURE(LoadSockname(receiver_fd_.get(), addr));
ASSERT_EQ(connect(connected().get(), reinterpret_cast<sockaddr*>(&addr), addrlen_), 0)
<< strerror(errno);
}
void SendDatagram() {
ASSERT_NO_FATAL_FAILURE(DatagramSendSemanticsTestInstance::SendDatagramFrom(connected().get()));
}
void ToggleOff() {
sockaddr_storage addr;
ASSERT_NO_FATAL_FAILURE(LoadSockname(bound().get(), addr));
ASSERT_EQ(connect(connected().get(), reinterpret_cast<sockaddr*>(&addr), addrlen_), 0)
<< strerror(errno);
}
void ObserveOn() { ASSERT_NO_FATAL_FAILURE(RecvDatagramOn(receiver_fd_.get())); }
void SendDatagramAndObserveOff() {
ASSERT_NO_FATAL_FAILURE(SendDatagram());
ASSERT_NO_FATAL_FAILURE(RecvDatagramOn(bound().get()));
}
private:
void LoadSockname(int fd, sockaddr_storage& addr) {
socklen_t found_addrlen = addrlen_;
ASSERT_EQ(getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &found_addrlen), 0)
<< strerror(errno);
ASSERT_EQ(found_addrlen, addrlen_);
}
fbl::unique_fd receiver_fd_;
uint32_t addrlen_;
};
TEST_P(DatagramCachedSendSemanticsTest, Connect) {
ASSERT_NO_FATAL_FAILURE(
ValidateCachedSendSemantics<DatagramSendSemanticsConnectInstance>(GetParam()));
}
TEST_P(DatagramLinearizedSendSemanticsTest, Connect) {
ASSERT_NO_FATAL_FAILURE(
ValidateLinearizedSendSemantics<DatagramSendSemanticsConnectInstance>(GetParam()));
}
class DatagramSendSemanticsCloseInstance : public DatagramSendSemanticsTestInstance {
public:
DatagramSendSemanticsCloseInstance(const SocketDomain& domain)
: DatagramSendSemanticsTestInstance(domain) {}
void SetUpInstance() override {
DatagramSendSemanticsTestInstance::SetUpInstance();
const auto [addr, addrlen] = LoopbackSockaddrAndSocklenForDomain(domain_);
addrlen_ = addrlen;
}
void ToggleOn() {
ASSERT_TRUE(other_sender_fd_ = fbl::unique_fd(socket(domain_.Get(), SOCK_DGRAM, 0)))
<< strerror(errno);
sockaddr_storage addr;
socklen_t found_addrlen = addrlen_;
ASSERT_EQ(getsockname(bound().get(), reinterpret_cast<sockaddr*>(&addr), &found_addrlen), 0)
<< strerror(errno);
ASSERT_EQ(found_addrlen, addrlen_);
ASSERT_EQ(connect(other_sender_fd_.get(), reinterpret_cast<sockaddr*>(&addr), addrlen_), 0)
<< strerror(errno);
}
void SendDatagram() {
ASSERT_NO_FATAL_FAILURE(
DatagramSendSemanticsTestInstance::SendDatagramFrom(other_sender_fd_.get()));
}
void ToggleOff() { EXPECT_EQ(close(other_sender_fd_.release()), 0) << strerror(errno); }
void ObserveOn() { ASSERT_NO_FATAL_FAILURE(RecvDatagramOn(bound().get())); }
void SendDatagramAndObserveOff() {
ASSERT_EQ(send(other_sender_fd_.get(), kBuf.data(), kBuf.size(), 0), -1);
EXPECT_EQ(errno, EBADF);
}
private:
fbl::unique_fd other_sender_fd_;
uint32_t addrlen_;
};
TEST_P(DatagramLinearizedSendSemanticsTest, Close) {
if (!kIsFuchsia) {
GTEST_SKIP() << "Linux does not guarantee linearized send semantics with respect to close().";
}
ASSERT_NO_FATAL_FAILURE(
ValidateLinearizedSendSemantics<DatagramSendSemanticsCloseInstance>(GetParam()));
}
TEST_P(DatagramCachedSendSemanticsTest, Close) {
ASSERT_NO_FATAL_FAILURE(
ValidateCachedSendSemantics<DatagramSendSemanticsCloseInstance>(GetParam()));
}
class DatagramSendSemanticsIpv6OnlyInstance : public DatagramSendSemanticsTestInstance {
public:
DatagramSendSemanticsIpv6OnlyInstance(const SocketDomain& domain)
: DatagramSendSemanticsTestInstance(domain) {}
void SetUpInstance() override {
DatagramSendSemanticsTestInstance::SetUpInstance();
ASSERT_TRUE(recv_fd_ = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
sockaddr_in recv_addr = {
.sin_family = AF_INET,
.sin_addr =
{
.s_addr = htonl(INADDR_LOOPBACK),
},
};
socklen_t addrlen = sizeof(recv_addr);
ASSERT_EQ(bind(recv_fd_.get(), reinterpret_cast<const sockaddr*>(&recv_addr), addrlen), 0)
<< strerror(errno);
ASSERT_EQ(getsockname(recv_fd_.get(), reinterpret_cast<sockaddr*>(&recv_addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(recv_addr));
ASSERT_TRUE(send_fd_ = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
// Construct a IPV4 mapped IPV6 address.
send_addr_ = MapIpv4SockaddrToIpv6Sockaddr(recv_addr);
}
void ToggleOn() {
constexpr int v6_only = 0;
EXPECT_EQ(setsockopt(send_fd_.get(), IPPROTO_IPV6, IPV6_V6ONLY, &v6_only, sizeof(v6_only)), 0)
<< strerror(errno);
}
void SendDatagram() {
ASSERT_EQ(sendto(send_fd_.get(), kBuf.data(), kBuf.size(), 0,
reinterpret_cast<sockaddr*>(&send_addr_), sizeof(sockaddr_in6)),
ssize_t(kBuf.size()))
<< strerror(errno);
}
void ToggleOff() {
constexpr int v6_only = 1;
EXPECT_EQ(setsockopt(send_fd_.get(), IPPROTO_IPV6, IPV6_V6ONLY, &v6_only, sizeof(v6_only)), 0)
<< strerror(errno);
}
void ObserveOn() { ASSERT_NO_FATAL_FAILURE(RecvDatagramOn(recv_fd_.get())); }
void SendDatagramAndObserveOff() {
ASSERT_EQ(sendto(send_fd_.get(), kBuf.data(), kBuf.size(), 0,
reinterpret_cast<sockaddr*>(&send_addr_), sizeof(sockaddr_in6)),
-1);
EXPECT_EQ(errno, EHOSTUNREACH);
}
void TearDownInstance() override {
EXPECT_EQ(close(recv_fd_.release()), 0) << strerror(errno);
EXPECT_EQ(close(send_fd_.release()), 0) << strerror(errno);
ASSERT_NO_FATAL_FAILURE(DatagramSendSemanticsTestInstance::TearDownInstance());
}
private:
fbl::unique_fd recv_fd_;
fbl::unique_fd send_fd_;
sockaddr_in6 send_addr_;
};
TEST_P(DatagramLinearizedSendSemanticsTest, Ipv6Only) {
if (GetParam().Get() != AF_INET6) {
GTEST_SKIP() << "IPV6_V6ONLY can only be used on AF_INET6 sockets.";
}
// TODO(https://fxbug.dev/42178189): Remove this test after setting IPV6_V6ONLY after bind is
// disallowed on Fuchsia.
if (!kIsFuchsia) {
GTEST_SKIP() << "Linux does not support setting IPV6_V6ONLY after a socket has been bound.";
}
ASSERT_NO_FATAL_FAILURE(
ValidateLinearizedSendSemantics<DatagramSendSemanticsIpv6OnlyInstance>(GetParam()));
}
TEST_P(DatagramCachedSendSemanticsTest, Ipv6Only) {
if (GetParam().Get() != AF_INET6) {
GTEST_SKIP() << "IPV6_V6ONLY can only be used on AF_INET6 sockets.";
}
// TODO(https://fxbug.dev/42178189): Remove this test after setting IPV6_V6ONLY after bind is
// disallowed on Fuchsia.
if (!kIsFuchsia) {
GTEST_SKIP() << "Linux does not support setting IPV6_V6ONLY after a socket has been bound.";
}
ASSERT_NO_FATAL_FAILURE(
ValidateCachedSendSemantics<DatagramSendSemanticsIpv6OnlyInstance>(GetParam()));
}
class DatagramSendSemanticsSoBroadcastInstance : public DatagramSendSemanticsTestInstance {
public:
explicit DatagramSendSemanticsSoBroadcastInstance(const SocketDomain& domain)
: DatagramSendSemanticsTestInstance(domain) {}
void SetUpInstance() override {
DatagramSendSemanticsTestInstance::SetUpInstance();
recv_addr_ = {
.sin_family = AF_INET,
.sin_addr =
{
.s_addr = htonl(INADDR_BROADCAST),
},
};
ASSERT_TRUE(recv_fd_ = fbl::unique_fd(socket(domain_.Get(), SOCK_DGRAM, 0))) << strerror(errno);
ASSERT_EQ(
bind(recv_fd_.get(), reinterpret_cast<const sockaddr*>(&recv_addr_), sizeof(recv_addr_)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(recv_addr_);
ASSERT_EQ(getsockname(recv_fd_.get(), reinterpret_cast<sockaddr*>(&recv_addr_), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(recv_addr_));
ASSERT_TRUE(send_fd_ = fbl::unique_fd(socket(domain_.Get(), SOCK_DGRAM, 0))) << strerror(errno);
// Bind the sending socket to a device to ensure that the test does not rely
// on a default route being installed.
const char loopback_name[3] = "lo";
EXPECT_EQ(setsockopt(send_fd_.get(), SOL_SOCKET, SO_BINDTODEVICE, &loopback_name,
sizeof(loopback_name)),
0)
<< strerror(errno);
}
void ToggleOn() {
constexpr int so_broadcast = 1;
EXPECT_EQ(
setsockopt(send_fd_.get(), SOL_SOCKET, SO_BROADCAST, &so_broadcast, sizeof(so_broadcast)),
0)
<< strerror(errno);
}
void SendDatagram() {
ASSERT_EQ(sendto(send_fd_.get(), kBuf.data(), kBuf.size(), 0,
reinterpret_cast<sockaddr*>(&recv_addr_), sizeof(recv_addr_)),
ssize_t(kBuf.size()))
<< strerror(errno);
}
void ToggleOff() {
constexpr int so_broadcast = 0;
EXPECT_EQ(
setsockopt(send_fd_.get(), SOL_SOCKET, SO_BROADCAST, &so_broadcast, sizeof(so_broadcast)),
0)
<< strerror(errno);
}
void ObserveOn() { ASSERT_NO_FATAL_FAILURE(RecvDatagramOn(recv_fd_.get())); }
void SendDatagramAndObserveOff() {
ASSERT_EQ(sendto(send_fd_.get(), kBuf.data(), kBuf.size(), 0,
reinterpret_cast<sockaddr*>(&recv_addr_), sizeof(recv_addr_)),
-1);
EXPECT_EQ(errno, EACCES);
}
void TearDownInstance() override {
EXPECT_EQ(close(recv_fd_.release()), 0) << strerror(errno);
EXPECT_EQ(close(send_fd_.release()), 0) << strerror(errno);
ASSERT_NO_FATAL_FAILURE(DatagramSendSemanticsTestInstance::TearDownInstance());
}
private:
fbl::unique_fd recv_fd_;
fbl::unique_fd send_fd_;
sockaddr_in recv_addr_;
};
TEST_P(DatagramLinearizedSendSemanticsTest, SoBroadcast) {
if (GetParam().Get() != AF_INET) {
GTEST_SKIP() << "SO_BROADCAST can only be used on AF_INET sockets.";
}
ASSERT_NO_FATAL_FAILURE(
ValidateLinearizedSendSemantics<DatagramSendSemanticsSoBroadcastInstance>(GetParam()));
}
TEST_P(DatagramCachedSendSemanticsTest, SoBroadcast) {
if (GetParam().Get() != AF_INET) {
GTEST_SKIP() << "SO_BROADCAST can only be used on AF_INET sockets.";
}
ASSERT_NO_FATAL_FAILURE(
ValidateCachedSendSemantics<DatagramSendSemanticsSoBroadcastInstance>(GetParam()));
}
INSTANTIATE_TEST_SUITE_P(DatagramCachedSendSemanticsTests, DatagramCachedSendSemanticsTest,
testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
[](const auto info) {
return std::string(socketDomainToString(info.param));
});
INSTANTIATE_TEST_SUITE_P(DatagramLinearizedSendSemanticsTests, DatagramLinearizedSendSemanticsTest,
testing::Values(SocketDomain::IPv4(), SocketDomain::IPv6()),
[](const auto info) {
return std::string(socketDomainToString(info.param));
});
} // namespace