| // Copyright 2017 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. |
| |
| // These tests ensure fdio can talk to netstack. |
| // No network connection is required, only a running netstack binary. |
| |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| #include <lib/fit/defer.h> |
| #include <net/if.h> |
| #include <netdb.h> |
| #include <netinet/if_ether.h> |
| #include <netinet/tcp.h> |
| #include <poll.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <sys/uio.h> |
| |
| #include <array> |
| #include <future> |
| #include <latch> |
| #include <thread> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "util.h" |
| |
| namespace { |
| |
| TEST(LocalhostTest, SendToZeroPort) { |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_port = htons(0), |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| 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 struct 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 struct 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); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const struct 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); |
| |
| ASSERT_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); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| }; |
| |
| struct 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); |
| |
| ASSERT_EQ(close(fd.release()), 0) << strerror(errno); |
| } |
| |
| #if !defined(__Fuchsia__) |
| bool IsRoot() { |
| uid_t ruid, euid, suid; |
| EXPECT_EQ(getresuid(&ruid, &euid, &suid), 0) << strerror(errno); |
| gid_t rgid, egid, sgid; |
| EXPECT_EQ(getresgid(&rgid, &egid, &sgid), 0) << strerror(errno); |
| auto uids = {ruid, euid, suid}; |
| auto gids = {rgid, egid, sgid}; |
| return std::all_of(std::begin(uids), std::end(uids), [](uid_t uid) { return uid == 0; }) && |
| std::all_of(std::begin(gids), std::end(gids), [](gid_t gid) { return gid == 0; }); |
| } |
| #endif |
| |
| TEST(LocalhostTest, BindToDevice) { |
| #if !defined(__Fuchsia__) |
| if (!IsRoot()) { |
| GTEST_SKIP() << "This test requires root"; |
| } |
| #endif |
| |
| 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. |
| EXPECT_EQ(setsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, set_dev_unknown, 2), 0) |
| << strerror(errno); |
| |
| // Bind to unknown name should fail. |
| EXPECT_EQ( |
| setsockopt(fd.get(), SOL_SOCKET, SO_BINDTODEVICE, "loblahblahblah", 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); |
| } |
| |
| // Raw sockets are typically used for implementing custom protocols. We intend to support custom |
| // protocols through structured FIDL APIs in the future, so this test ensures that raw sockets are |
| // disabled to prevent them from accidentally becoming load-bearing. |
| TEST(LocalhostTest, RawSocketsNotSupported) { |
| // No raw INET sockets. |
| ASSERT_EQ(socket(AF_INET, SOCK_RAW, 0), -1); |
| ASSERT_EQ(errno, EPROTONOSUPPORT) << strerror(errno); |
| |
| // No packet sockets. |
| ASSERT_EQ(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)), -1); |
| ASSERT_EQ(errno, EPERM) << 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", ¶m.imr_multiaddr.s_addr); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| ASSERT_EQ(setsockopt(s.get(), SOL_IP, IP_ADD_MEMBERSHIP, ¶m, sizeof(param)), 0) |
| << strerror(errno); |
| |
| ASSERT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| struct SockOption { |
| int level; |
| int option; |
| }; |
| |
| constexpr int INET_ECN_MASK = 3; |
| |
| std::string socketTypeToString(const int type) { |
| switch (type) { |
| case SOCK_DGRAM: |
| return "Datagram"; |
| case SOCK_STREAM: |
| return "Stream"; |
| default: |
| return std::to_string(type); |
| } |
| } |
| |
| using SocketKind = std::tuple<int, int>; |
| |
| std::string socketKindToString(const ::testing::TestParamInfo<SocketKind>& info) { |
| auto const& [domain, type] = info.param; |
| |
| std::string domain_str; |
| switch (domain) { |
| case AF_INET: |
| domain_str = "IPv4"; |
| break; |
| case AF_INET6: |
| domain_str = "IPv6"; |
| break; |
| default: |
| domain_str = std::to_string(domain); |
| break; |
| } |
| return domain_str + "_" + socketTypeToString(type); |
| } |
| |
| // Share common functions for SocketKind based tests. |
| class SocketKindTest : public ::testing::TestWithParam<SocketKind> { |
| protected: |
| static fbl::unique_fd NewSocket() { |
| auto const& [domain, type] = GetParam(); |
| return fbl::unique_fd(socket(domain, type, 0)); |
| } |
| }; |
| |
| constexpr int kSockOptOn = 1; |
| constexpr int kSockOptOff = 0; |
| |
| class SocketOptsTest : public SocketKindTest { |
| protected: |
| static bool IsTCP() { return std::get<1>(GetParam()) == SOCK_STREAM; } |
| |
| static bool IsIPv6() { return std::get<0>(GetParam()) == AF_INET6; } |
| |
| static SockOption GetTOSOption() { |
| if (IsIPv6()) { |
| return { |
| .level = IPPROTO_IPV6, |
| .option = IPV6_TCLASS, |
| }; |
| } |
| return { |
| .level = IPPROTO_IP, |
| .option = IP_TOS, |
| }; |
| } |
| |
| static SockOption GetMcastLoopOption() { |
| if (IsIPv6()) { |
| return { |
| .level = IPPROTO_IPV6, |
| .option = IPV6_MULTICAST_LOOP, |
| }; |
| } |
| return { |
| .level = IPPROTO_IP, |
| .option = IP_MULTICAST_LOOP, |
| }; |
| } |
| |
| static SockOption GetMcastTTLOption() { |
| if (IsIPv6()) { |
| return { |
| .level = IPPROTO_IPV6, |
| .option = IPV6_MULTICAST_HOPS, |
| }; |
| } |
| return { |
| .level = IPPROTO_IP, |
| .option = IP_MULTICAST_TTL, |
| }; |
| } |
| |
| static SockOption GetMcastIfOption() { |
| if (IsIPv6()) { |
| return { |
| .level = IPPROTO_IPV6, |
| .option = IPV6_MULTICAST_IF, |
| }; |
| } |
| return { |
| .level = IPPROTO_IP, |
| .option = IP_MULTICAST_IF, |
| }; |
| } |
| |
| static SockOption GetRecvTOSOption() { |
| if (IsIPv6()) { |
| return { |
| .level = IPPROTO_IPV6, |
| .option = IPV6_RECVTCLASS, |
| }; |
| } |
| return { |
| .level = IPPROTO_IP, |
| .option = IP_RECVTOS, |
| }; |
| } |
| |
| static SockOption GetNoChecksum() { |
| return { |
| .level = SOL_SOCKET, |
| .option = SO_NO_CHECK, |
| }; |
| } |
| }; |
| |
| // The SocketOptsTest is adapted from gvisor/tests/syscalls/linux/socket_ip_unbound.cc |
| TEST_P(SocketOptsTest, TtlDefault) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_sz = sizeof(get); |
| constexpr int kDefaultTTL = 64; |
| EXPECT_EQ(getsockopt(s.get(), IPPROTO_IP, IP_TTL, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(get)); |
| EXPECT_EQ(get, kDefaultTTL); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetTtl) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int get1 = -1; |
| socklen_t get1_sz = sizeof(get1); |
| EXPECT_EQ(getsockopt(s.get(), IPPROTO_IP, IP_TTL, &get1, &get1_sz), 0) << strerror(errno); |
| EXPECT_EQ(get1_sz, sizeof(get1)); |
| |
| int set = 100; |
| if (set == get1) { |
| set += 1; |
| } |
| socklen_t set_sz = sizeof(set); |
| EXPECT_EQ(setsockopt(s.get(), IPPROTO_IP, IP_TTL, &set, set_sz), 0) << strerror(errno); |
| |
| int get2 = -1; |
| socklen_t get2_sz = sizeof(get2); |
| EXPECT_EQ(getsockopt(s.get(), IPPROTO_IP, IP_TTL, &get2, &get2_sz), 0) << strerror(errno); |
| EXPECT_EQ(get2_sz, sizeof(get2)); |
| EXPECT_EQ(get2, set); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, ResetTtlToDefault) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int get1 = -1; |
| socklen_t get1_sz = sizeof(get1); |
| EXPECT_EQ(getsockopt(s.get(), IPPROTO_IP, IP_TTL, &get1, &get1_sz), 0) << strerror(errno); |
| EXPECT_EQ(get1_sz, sizeof(get1)); |
| |
| int set1 = 100; |
| if (set1 == get1) { |
| set1 += 1; |
| } |
| socklen_t set1_sz = sizeof(set1); |
| EXPECT_EQ(setsockopt(s.get(), IPPROTO_IP, IP_TTL, &set1, set1_sz), 0) << strerror(errno); |
| |
| int set2 = -1; |
| socklen_t set2_sz = sizeof(set2); |
| EXPECT_EQ(setsockopt(s.get(), IPPROTO_IP, IP_TTL, &set2, set2_sz), 0) << strerror(errno); |
| |
| int get2 = -1; |
| socklen_t get2_sz = sizeof(get2); |
| EXPECT_EQ(getsockopt(s.get(), IPPROTO_IP, IP_TTL, &get2, &get2_sz), 0) << strerror(errno); |
| EXPECT_EQ(get2_sz, sizeof(get2)); |
| EXPECT_EQ(get2, get1); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, ZeroTtl) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = 0; |
| socklen_t set_sz = sizeof(set); |
| EXPECT_EQ(setsockopt(s.get(), IPPROTO_IP, IP_TTL, &set, set_sz), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, InvalidLargeTtl) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = 256; |
| socklen_t set_sz = sizeof(set); |
| EXPECT_EQ(setsockopt(s.get(), IPPROTO_IP, IP_TTL, &set, set_sz), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, InvalidNegativeTtl) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = -2; |
| socklen_t set_sz = sizeof(set); |
| EXPECT_EQ(setsockopt(s.get(), IPPROTO_IP, IP_TTL, &set, set_sz), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, TOSDefault) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| SockOption t = GetTOSOption(); |
| int get = -1; |
| socklen_t get_sz = sizeof(get); |
| constexpr int kDefaultTOS = 0; |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(get)); |
| EXPECT_EQ(get, kDefaultTOS); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetTOS) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = 0xC0; |
| socklen_t set_sz = sizeof(set); |
| SockOption t = GetTOSOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), 0) << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_sz = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(get)); |
| EXPECT_EQ(get, set); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, NullTOS) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| socklen_t set_sz = sizeof(int); |
| SockOption t = GetTOSOption(); |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, nullptr, set_sz), 0) << strerror(errno); |
| } else { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, nullptr, set_sz), -1); |
| EXPECT_EQ(errno, EFAULT) << strerror(errno); |
| } |
| socklen_t get_sz = sizeof(int); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, nullptr, &get_sz), -1); |
| EXPECT_EQ(errno, EFAULT) << strerror(errno); |
| int get = -1; |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, nullptr), -1); |
| EXPECT_EQ(errno, EFAULT) << strerror(errno); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, ZeroTOS) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = 0; |
| socklen_t set_sz = sizeof(set); |
| SockOption t = GetTOSOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), 0) << strerror(errno); |
| int get = -1; |
| socklen_t get_sz = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(get)); |
| EXPECT_EQ(get, set); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, InvalidLargeTOS) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| // Test with exceeding the byte space. |
| int set = 256; |
| constexpr int kDefaultTOS = 0; |
| socklen_t set_sz = sizeof(set); |
| SockOption t = GetTOSOption(); |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| } else { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), 0) << strerror(errno); |
| } |
| int get = -1; |
| socklen_t get_sz = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(get)); |
| EXPECT_EQ(get, kDefaultTOS); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, CheckSkipECN) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = 0xFF; |
| socklen_t set_sz = sizeof(set); |
| SockOption t = GetTOSOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), 0) << strerror(errno); |
| int expect = static_cast<uint8_t>(set); |
| if (IsTCP() |
| #if !defined(__Fuchsia__) |
| // gvisor-netstack`s implemention of setsockopt(..IPV6_TCLASS..) |
| // clears the ECN bits from the TCLASS value. This keeps gvisor |
| // in parity with the Linux test-hosts that run a custom kernel. |
| // But that is not the behavior of vanilla Linux kernels. |
| // This #if can be removed when we migrate away from gvisor-netstack. |
| && !IsIPv6() |
| #endif |
| ) { |
| expect &= ~INET_ECN_MASK; |
| } |
| int get = -1; |
| socklen_t get_sz = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(get)); |
| EXPECT_EQ(get, expect); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, ZeroTOSOptionSize) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = 0xC0; |
| socklen_t set_sz = 0; |
| SockOption t = GetTOSOption(); |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| } else { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), 0) << strerror(errno); |
| } |
| int get = -1; |
| socklen_t get_sz = 0; |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, 0u); |
| EXPECT_EQ(get, -1); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SmallTOSOptionSize) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = 0xC0; |
| constexpr int kDefaultTOS = 0; |
| SockOption t = GetTOSOption(); |
| for (socklen_t i = 1; i < sizeof(int); i++) { |
| int expect_tos; |
| socklen_t expect_sz; |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, i), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| expect_tos = kDefaultTOS; |
| expect_sz = i; |
| } else { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, i), 0) << strerror(errno); |
| expect_tos = set; |
| expect_sz = sizeof(uint8_t); |
| } |
| uint get = -1; |
| socklen_t get_sz = i; |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, expect_sz); |
| // Account for partial copies by getsockopt, retrieve the lower |
| // bits specified by get_sz, while comparing against expect_tos. |
| EXPECT_EQ(get & ~(~0u << (get_sz * 8)), static_cast<uint>(expect_tos)); |
| } |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, LargeTOSOptionSize) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| char buffer[100]; |
| int* set = reinterpret_cast<int*>(buffer); |
| // Point to a larger buffer so that the setsockopt does not overrun. |
| *set = 0xC0; |
| SockOption t = GetTOSOption(); |
| for (socklen_t i = sizeof(int); i < 10; i++) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, set, i), 0) << strerror(errno); |
| int get = -1; |
| socklen_t get_sz = i; |
| // We expect the system call handler to only copy atmost sizeof(int) bytes |
| // as asserted by the check below. Hence, we do not expect the copy to |
| // overflow in getsockopt. |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(int)); |
| EXPECT_EQ(get, *set); |
| } |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, NegativeTOS) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = -1; |
| socklen_t set_sz = sizeof(set); |
| SockOption t = GetTOSOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), 0) << strerror(errno); |
| int expect; |
| if (IsIPv6()) { |
| // On IPv6 TCLASS, setting -1 has the effect of resetting the |
| // TrafficClass. |
| expect = 0; |
| } else { |
| expect = static_cast<uint8_t>(set); |
| if (IsTCP()) { |
| expect &= ~INET_ECN_MASK; |
| } |
| } |
| int get = -1; |
| socklen_t get_sz = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(get)); |
| EXPECT_EQ(get, expect); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, InvalidNegativeTOS) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int set = -2; |
| socklen_t set_sz = sizeof(set); |
| SockOption t = GetTOSOption(); |
| int expect; |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| expect = 0; |
| } else { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &set, set_sz), 0) << strerror(errno); |
| expect = static_cast<uint8_t>(set); |
| if (IsTCP()) { |
| expect &= ~INET_ECN_MASK; |
| } |
| } |
| int get = 0; |
| socklen_t get_sz = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_sz), 0) << strerror(errno); |
| EXPECT_EQ(get_sz, sizeof(get)); |
| EXPECT_EQ(get, expect); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, MulticastLoopDefault) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| SockOption t = GetMcastLoopOption(); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOn); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetMulticastLoop) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| SockOption t = GetMcastLoopOption(); |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOff, sizeof(kSockOptOff)), 0) |
| << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOff); |
| |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOn, sizeof(kSockOptOn)), 0) |
| << strerror(errno); |
| |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOn); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetMulticastLoopChar) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr char kSockOptOnChar = kSockOptOn; |
| constexpr char kSockOptOffChar = kSockOptOff; |
| |
| SockOption t = GetMcastLoopOption(); |
| int want; |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOffChar, sizeof(kSockOptOffChar)), |
| -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| want = kSockOptOnChar; |
| } else { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOffChar, sizeof(kSockOptOffChar)), 0) |
| << strerror(errno); |
| want = kSockOptOffChar; |
| } |
| |
| { |
| char get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } |
| |
| { |
| int16_t get = -1; |
| fprintf(stderr, "%s\n", std::bitset<16>(get).to_string().c_str()); |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| if (IsIPv6()) { |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } else { |
| // This option truncates size < 4 to 1 and only writes the low byte. |
| EXPECT_EQ(get_len, sizeof(char)); |
| EXPECT_EQ(get, static_cast<int16_t>(uint16_t(-1) << 8) | want); |
| } |
| } |
| |
| { |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } |
| |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOnChar, sizeof(kSockOptOnChar)), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| } else { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOnChar, sizeof(kSockOptOnChar)), 0) |
| << strerror(errno); |
| } |
| |
| char get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOn); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, MulticastTTLDefault) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| SockOption t = GetMcastTTLOption(); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, 1); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetUDPMulticastTTLMin) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr int kMin = 0; |
| SockOption t = GetMcastTTLOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kMin, sizeof(kMin)), 0) << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kMin); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetUDPMulticastTTLMax) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr int kMax = 255; |
| SockOption t = GetMcastTTLOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kMax, sizeof(kMax)), 0) << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kMax); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetUDPMulticastTTLNegativeOne) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr int kArbitrary = 6; |
| SockOption t = GetMcastTTLOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kArbitrary, sizeof(kArbitrary)), 0) |
| << strerror(errno); |
| |
| constexpr int kNegOne = -1; |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kNegOne, sizeof(kNegOne)), 0) |
| << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, 1); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetUDPMulticastTTLBelowMin) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr int kBelowMin = -2; |
| SockOption t = GetMcastTTLOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kBelowMin, sizeof(kBelowMin)), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetUDPMulticastTTLAboveMax) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr int kAboveMax = 256; |
| SockOption t = GetMcastTTLOption(); |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kAboveMax, sizeof(kAboveMax)), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetUDPMulticastTTLChar) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr char kArbitrary = 6; |
| SockOption t = GetMcastTTLOption(); |
| int want; |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kArbitrary, sizeof(kArbitrary)), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| want = 1; |
| } else { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kArbitrary, sizeof(kArbitrary)), 0) |
| << strerror(errno); |
| want = kArbitrary; |
| } |
| |
| { |
| char get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } |
| |
| { |
| int16_t get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| if (IsIPv6()) { |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } else { |
| // This option truncates size < 4 to 1 and only writes the low byte. |
| EXPECT_EQ(get_len, sizeof(char)); |
| EXPECT_EQ(get, static_cast<int16_t>(uint16_t(-1) << 8) | want); |
| } |
| } |
| |
| { |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetUDPMulticastIfImrIfindex) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr int kOne = 1; |
| SockOption t = GetMcastIfOption(); |
| if (IsIPv6()) { |
| EXPECT_EQ(setsockopt(s.get(), t.level, t.option, &kOne, sizeof(kOne)), 0) << strerror(errno); |
| |
| int param_out; |
| socklen_t len = sizeof(param_out); |
| ASSERT_EQ(getsockopt(s.get(), t.level, t.option, ¶m_out, &len), 0) << strerror(errno); |
| ASSERT_EQ(len, sizeof(param_out)); |
| |
| ASSERT_EQ(param_out, kOne); |
| } else { |
| ip_mreqn param_in = { |
| .imr_ifindex = kOne, |
| }; |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, ¶m_in, sizeof(param_in)), 0) |
| << strerror(errno); |
| |
| in_addr param_out; |
| socklen_t len = sizeof(param_out); |
| ASSERT_EQ(getsockopt(s.get(), t.level, t.option, ¶m_out, &len), 0) << strerror(errno); |
| ASSERT_EQ(len, sizeof(param_out)); |
| |
| ASSERT_EQ(param_out.s_addr, INADDR_ANY); |
| } |
| |
| ASSERT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetUDPMulticastIfImrAddress) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip multicast tests on TCP socket"; |
| } |
| if (IsIPv6()) { |
| GTEST_SKIP() << "V6 sockets don't support setting IP_MULTICAST_IF by addr"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| SockOption t = GetMcastIfOption(); |
| ip_mreqn param_in = { |
| .imr_address.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, ¶m_in, sizeof(param_in)), 0) |
| << strerror(errno); |
| |
| in_addr param_out; |
| socklen_t len = sizeof(param_out); |
| ASSERT_EQ(getsockopt(s.get(), t.level, t.option, ¶m_out, &len), 0) << strerror(errno); |
| ASSERT_EQ(len, sizeof(param_out)); |
| |
| ASSERT_EQ(param_out.s_addr, param_in.imr_address.s_addr); |
| |
| ASSERT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, ReceiveTOSDefault) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip receive TOS tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| SockOption t = GetRecvTOSOption(); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOff); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetReceiveTOS) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip receive TOS tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| SockOption t = GetRecvTOSOption(); |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOn, sizeof(kSockOptOn)), 0) |
| << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOn); |
| |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOff, sizeof(kSockOptOff)), 0) |
| << strerror(errno); |
| |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOff); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| // Tests that a two byte RECVTOS/RECVTCLASS optval is acceptable. |
| TEST_P(SocketOptsTest, SetReceiveTOSShort) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip receive TOS tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr char kSockOptOn2Byte[] = {kSockOptOn, 0}; |
| constexpr char kSockOptOff2Byte[] = {kSockOptOff, 0}; |
| |
| SockOption t = GetRecvTOSOption(); |
| if (IsIPv6()) { |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOn2Byte, sizeof(kSockOptOn2Byte)), -1) |
| << strerror(errno); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| } else { |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOn2Byte, sizeof(kSockOptOn2Byte)), 0) |
| << strerror(errno); |
| } |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| if (IsIPv6()) { |
| EXPECT_EQ(get, kSockOptOff); |
| } else { |
| EXPECT_EQ(get, kSockOptOn); |
| } |
| |
| if (IsIPv6()) { |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOff2Byte, sizeof(kSockOptOff2Byte)), |
| -1) |
| << strerror(errno); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| } else { |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOff2Byte, sizeof(kSockOptOff2Byte)), |
| 0) |
| << strerror(errno); |
| } |
| |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOff); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| // Tests that a one byte sized optval is acceptable for RECVTOS and not for |
| // RECVTCLASS. |
| TEST_P(SocketOptsTest, SetReceiveTOSChar) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip receive TOS tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| constexpr char kSockOptOnChar = kSockOptOn; |
| constexpr char kSockOptOffChar = kSockOptOff; |
| |
| SockOption t = GetRecvTOSOption(); |
| if (IsIPv6()) { |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOnChar, sizeof(kSockOptOnChar)), -1) |
| << strerror(errno); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| } else { |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOnChar, sizeof(kSockOptOnChar)), 0) |
| << strerror(errno); |
| } |
| |
| int want; |
| if (IsIPv6()) { |
| want = kSockOptOff; |
| } else { |
| want = kSockOptOn; |
| } |
| |
| { |
| char get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } |
| |
| { |
| int16_t get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| if (IsIPv6()) { |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } else { |
| // This option truncates size < 4 to 1 and only writes the low byte. |
| EXPECT_EQ(get_len, sizeof(char)); |
| EXPECT_EQ(get, static_cast<int16_t>(uint16_t(-1) << 8) | want); |
| } |
| } |
| |
| { |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, want); |
| } |
| |
| if (IsIPv6()) { |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOffChar, sizeof(kSockOptOffChar)), -1) |
| << strerror(errno); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| } else { |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOffChar, sizeof(kSockOptOffChar)), 0) |
| << strerror(errno); |
| } |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOff); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, NoChecksumDefault) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip NoChecksum tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| SockOption t = GetNoChecksum(); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOff); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| TEST_P(SocketOptsTest, SetNoChecksum) { |
| if (IsTCP()) { |
| GTEST_SKIP() << "Skip NoChecksum tests on TCP socket"; |
| } |
| |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = NewSocket()) << strerror(errno); |
| |
| SockOption t = GetNoChecksum(); |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOn, sizeof(kSockOptOn)), 0) |
| << strerror(errno); |
| |
| int get = -1; |
| socklen_t get_len = sizeof(get); |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOn); |
| |
| ASSERT_EQ(setsockopt(s.get(), t.level, t.option, &kSockOptOff, sizeof(kSockOptOff)), 0) |
| << strerror(errno); |
| |
| EXPECT_EQ(getsockopt(s.get(), t.level, t.option, &get, &get_len), 0) << strerror(errno); |
| EXPECT_EQ(get_len, sizeof(get)); |
| EXPECT_EQ(get, kSockOptOff); |
| |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(LocalhostTest, SocketOptsTest, |
| ::testing::Combine(::testing::Values(AF_INET, AF_INET6), |
| ::testing::Values(SOCK_DGRAM, SOCK_STREAM)), |
| socketKindToString); |
| |
| using typeMulticast = std::tuple<int, bool>; |
| |
| std::string typeMulticastToString(const ::testing::TestParamInfo<typeMulticast>& info) { |
| auto const& [type, multicast] = info.param; |
| std::string addr; |
| if (multicast) { |
| addr = "Multicast"; |
| } else { |
| addr = "Loopback"; |
| } |
| return socketTypeToString(type) + addr; |
| } |
| |
| class ReuseTest : public ::testing::TestWithParam<typeMulticast> {}; |
| |
| TEST_P(ReuseTest, AllowsAddressReuse) { |
| const int on = true; |
| auto const& [type, multicast] = GetParam(); |
| |
| #if defined(__Fuchsia__) |
| if (multicast && type == SOCK_STREAM) { |
| GTEST_SKIP() << "Cannot bind a TCP socket to a multicast address on Fuchsia"; |
| } |
| #endif |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| if (multicast) { |
| int n = inet_pton(addr.sin_family, "224.0.2.1", &addr.sin_addr); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| } |
| |
| fbl::unique_fd s1; |
| ASSERT_TRUE(s1 = fbl::unique_fd(socket(AF_INET, type, 0))) << strerror(errno); |
| |
| // TODO(gvisor.dev/issue/3839): Remove this. |
| #if defined(__Fuchsia__) |
| // Must outlive the block below. |
| fbl::unique_fd s; |
| if (type != SOCK_DGRAM && multicast) { |
| ASSERT_EQ(bind(s1.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), -1); |
| ASSERT_EQ(errno, EADDRNOTAVAIL) << strerror(errno); |
| ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno); |
| ip_mreqn param = { |
| .imr_multiaddr = addr.sin_addr, |
| .imr_address.s_addr = htonl(INADDR_ANY), |
| .imr_ifindex = 1, |
| }; |
| ASSERT_EQ(setsockopt(s.get(), SOL_IP, IP_ADD_MEMBERSHIP, ¶m, sizeof(param)), 0) |
| << strerror(errno); |
| } |
| #endif |
| |
| ASSERT_EQ(setsockopt(s1.get(), SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)), 0) << strerror(errno); |
| ASSERT_EQ(bind(s1.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(s1.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| fbl::unique_fd s2; |
| ASSERT_TRUE(s2 = fbl::unique_fd(socket(AF_INET, type, 0))) << strerror(errno); |
| ASSERT_EQ(setsockopt(s2.get(), SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)), 0) << strerror(errno); |
| ASSERT_EQ(bind(s2.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(LocalhostTest, ReuseTest, |
| ::testing::Combine(::testing::Values(SOCK_DGRAM, SOCK_STREAM), |
| ::testing::Values(false, true)), |
| typeMulticastToString); |
| |
| TEST(LocalhostTest, Accept) { |
| fbl::unique_fd serverfd; |
| ASSERT_TRUE(serverfd = fbl::unique_fd(socket(AF_INET6, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in6 serveraddr = { |
| .sin6_family = AF_INET6, |
| .sin6_addr = IN6ADDR_LOOPBACK_INIT, |
| }; |
| socklen_t serveraddrlen = sizeof(serveraddr); |
| ASSERT_EQ(bind(serverfd.get(), reinterpret_cast<sockaddr*>(&serveraddr), serveraddrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(getsockname(serverfd.get(), reinterpret_cast<sockaddr*>(&serveraddr), &serveraddrlen), |
| 0) |
| << strerror(errno); |
| ASSERT_EQ(serveraddrlen, sizeof(serveraddr)); |
| ASSERT_EQ(listen(serverfd.get(), 0), 0) << strerror(errno); |
| |
| fbl::unique_fd clientfd; |
| ASSERT_TRUE(clientfd = fbl::unique_fd(socket(AF_INET6, SOCK_STREAM, 0))) << strerror(errno); |
| ASSERT_EQ(connect(clientfd.get(), reinterpret_cast<sockaddr*>(&serveraddr), serveraddrlen), 0) |
| << strerror(errno); |
| |
| struct sockaddr_in connaddr; |
| socklen_t connaddrlen = sizeof(connaddr); |
| fbl::unique_fd connfd; |
| ASSERT_TRUE(connfd = fbl::unique_fd( |
| accept(serverfd.get(), reinterpret_cast<sockaddr*>(&connaddr), &connaddrlen))) |
| << strerror(errno); |
| ASSERT_GT(connaddrlen, sizeof(connaddr)); |
| } |
| |
| TEST(LocalhostTest, AcceptAfterReset) { |
| fbl::unique_fd server; |
| ASSERT_TRUE(server = fbl::unique_fd(socket(AF_INET6, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in6 addr = { |
| .sin6_family = AF_INET6, |
| .sin6_addr = IN6ADDR_LOOPBACK_INIT, |
| }; |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(bind(server.get(), reinterpret_cast<const sockaddr*>(&addr), addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(getsockname(server.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| ASSERT_EQ(listen(server.get(), 0), 0) << strerror(errno); |
| |
| { |
| fbl::unique_fd client; |
| ASSERT_TRUE(client = fbl::unique_fd(socket(AF_INET6, SOCK_STREAM, 0))) << strerror(errno); |
| ASSERT_EQ(connect(client.get(), reinterpret_cast<const sockaddr*>(&addr), addrlen), 0) |
| << strerror(errno); |
| struct linger opt = { |
| .l_onoff = 1, |
| .l_linger = 0, |
| }; |
| ASSERT_EQ(setsockopt(client.get(), SOL_SOCKET, SO_LINGER, &opt, sizeof(opt)), 0) |
| << strerror(errno); |
| |
| // Ensure the accept queue has the passive connection enqueued before attempting to reset it. |
| struct pollfd pfd = { |
| .fd = server.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLIN); |
| |
| // Close the client and trigger a RST. |
| ASSERT_EQ(close(client.release()), 0) << strerror(errno); |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| |
| fbl::unique_fd conn; |
| ASSERT_TRUE( |
| conn = fbl::unique_fd(accept(server.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen))) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| ASSERT_EQ(addr.sin6_family, AF_INET6); |
| char buf[INET6_ADDRSTRLEN]; |
| ASSERT_TRUE(IN6_IS_ADDR_LOOPBACK(&addr.sin6_addr)) |
| << inet_ntop(addr.sin6_family, &addr.sin6_addr, buf, sizeof(buf)); |
| ASSERT_NE(addr.sin6_port, 0); |
| |
| // Wait for the connection to close to avoid flakes when this code is reached before the RST |
| // arrives at |conn|. |
| { |
| struct pollfd pfd = { |
| .fd = conn.get(), |
| }; |
| |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLERR | POLLHUP); |
| } |
| |
| int err; |
| socklen_t optlen = sizeof(err); |
| ASSERT_EQ(getsockopt(conn.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(err)); |
| ASSERT_EQ(err, ECONNRESET) << strerror(err); |
| } |
| |
| TEST(LocalhostTest, ConnectAFMismatchINET) { |
| fbl::unique_fd s; |
| ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))) << strerror(errno); |
| |
| struct sockaddr_in6 addr = { |
| .sin6_family = AF_INET6, |
| .sin6_port = htons(1337), |
| .sin6_addr = IN6ADDR_LOOPBACK_INIT, |
| }; |
| EXPECT_EQ(connect(s.get(), reinterpret_cast<const struct 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); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_port = htons(1337), |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| EXPECT_EQ(connect(s.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| EXPECT_EQ(close(s.release()), 0) << strerror(errno); |
| } |
| |
| // Test the behavior of poll on an unconnected or non-listening stream socket. |
| TEST(NetStreamTest, UnconnectPoll) { |
| fbl::unique_fd init, bound; |
| ASSERT_TRUE(init = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| ASSERT_TRUE(bound = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| ASSERT_EQ(bind(bound.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| for (int16_t events : {0, POLLIN | POLLOUT | POLLPRI | POLLRDHUP}) { |
| struct pollfd pfds[] = {{ |
| .fd = init.get(), |
| .events = events, |
| }, |
| { |
| .fd = bound.get(), |
| .events = events, |
| }}; |
| int n = poll(pfds, std::size(pfds), kTimeout); |
| EXPECT_GE(n, 0) << strerror(errno); |
| EXPECT_EQ(n, static_cast<int>(std::size(pfds))) << " events = " << std::hex << events; |
| |
| for (size_t i = 0; i < std::size(pfds); i++) { |
| EXPECT_EQ(pfds[i].revents, (events & POLLOUT) | POLLHUP) << i; |
| } |
| } |
| |
| // Poll on listening socket does timeout on no incoming connections. |
| ASSERT_EQ(listen(bound.get(), 0), 0) << strerror(errno); |
| struct pollfd pfd = { |
| .fd = bound.get(), |
| }; |
| EXPECT_EQ(poll(&pfd, 1, 0), 0) << strerror(errno); |
| } |
| |
| TEST(NetStreamTest, ConnectTwice) { |
| fbl::unique_fd client, listener; |
| ASSERT_TRUE(client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| ASSERT_EQ(bind(listener.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| ASSERT_EQ(connect(client.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| -1); |
| ASSERT_EQ(errno, ECONNREFUSED) << strerror(errno); |
| |
| ASSERT_EQ(listen(listener.get(), 0), 0) << strerror(errno); |
| |
| // TODO(fxbug.dev/61594): decide if we want to match Linux's behaviour. |
| ASSERT_EQ(connect(client.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| #if defined(__linux__) |
| 0) |
| << strerror(errno); |
| #else |
| -1); |
| ASSERT_EQ(errno, ECONNABORTED) << strerror(errno); |
| #endif |
| |
| ASSERT_EQ(close(listener.release()), 0) << strerror(errno); |
| ASSERT_EQ(close(client.release()), 0) << strerror(errno); |
| } |
| |
| TEST(NetStreamTest, ConnectCloseRace) { |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| // Use the ephemeral port allocated by the stack as destination address for connect. |
| { |
| fbl::unique_fd tmp; |
| ASSERT_TRUE(tmp = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| ASSERT_EQ(bind(tmp.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(tmp.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| } |
| |
| std::array<std::thread, 50> threads; |
| for (auto& t : threads) { |
| t = std::thread([&] { |
| for (int i = 0; i < 5; i++) { |
| fbl::unique_fd client; |
| ASSERT_TRUE(client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| |
| ASSERT_EQ( |
| connect(client.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| -1); |
| ASSERT_TRUE(errno == EINPROGRESS |
| #if !defined(__Fuchsia__) |
| // Linux could return ECONNREFUSED if it processes the incoming RST before |
| // connect system |
| // call returns. |
| || errno == ECONNREFUSED |
| #endif |
| ) |
| << strerror(errno); |
| ASSERT_EQ(close(client.release()), 0) << strerror(errno); |
| } |
| }); |
| } |
| |
| for (auto& t : threads) { |
| t.join(); |
| } |
| } |
| |
| void TestHangupDuringConnect(void (*hangup)(fbl::unique_fd*)) { |
| fbl::unique_fd client, listener; |
| ASSERT_TRUE(client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr_in = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| auto addr = reinterpret_cast<struct sockaddr*>(&addr_in); |
| socklen_t addr_len = sizeof(addr_in); |
| |
| ASSERT_EQ(bind(listener.get(), addr, addr_len), 0) << strerror(errno); |
| { |
| socklen_t addr_len_in = addr_len; |
| ASSERT_EQ(getsockname(listener.get(), addr, &addr_len), 0) << strerror(errno); |
| EXPECT_EQ(addr_len, addr_len_in); |
| } |
| ASSERT_EQ(listen(listener.get(), 0), 0) << strerror(errno); |
| |
| // Connect asynchronously and immediately hang up the listener. |
| int ret = connect(client.get(), addr, addr_len); |
| #if !defined(__Fuchsia__) |
| // Linux connect may succeed if the handshake completes before the system call returns. |
| if (ret != 0) |
| #endif |
| { |
| ASSERT_EQ(ret, -1); |
| ASSERT_EQ(errno, EINPROGRESS) << strerror(errno); |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(hangup(&listener)); |
| |
| // Wait for the connection to close. |
| { |
| struct pollfd pfd = { |
| .fd = client.get(), |
| .events = POLLIN, |
| }; |
| |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| ASSERT_EQ(pfd.revents, POLLIN | POLLERR | POLLHUP); |
| } |
| |
| ASSERT_EQ(close(client.release()), 0) << strerror(errno); |
| } |
| |
| TEST(NetStreamTest, CloseDuringConnect) { |
| TestHangupDuringConnect([](fbl::unique_fd* listener) { |
| ASSERT_EQ(close(listener->release()), 0) << strerror(errno); |
| }); |
| } |
| |
| TEST(NetStreamTest, ShutdownDuringConnect) { |
| TestHangupDuringConnect([](fbl::unique_fd* listener) { |
| ASSERT_EQ(shutdown(listener->get(), SHUT_RD), 0) << strerror(errno); |
| }); |
| } |
| |
| TEST(LocalhostTest, RaceLocalPeerClose) { |
| fbl::unique_fd listener; |
| ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| #if !defined(__Fuchsia__) |
| // Make the listener non-blocking so that we can let accept system call return |
| // below when there are no acceptable connections. |
| int flags = fcntl(listener.get(), F_GETFL, 0); |
| ASSERT_EQ(fcntl(listener.get(), F_SETFL, flags | O_NONBLOCK), 0) << strerror(errno); |
| #endif |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| ASSERT_EQ(bind(listener.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| std::array<std::thread, 50> threads; |
| ASSERT_EQ(listen(listener.get(), threads.size()), 0) << strerror(errno); |
| |
| // Run many iterations in parallel in order to increase load on Netstack and increase the |
| // probability we'll hit the problem. |
| for (auto& t : threads) { |
| t = std::thread([&] { |
| fbl::unique_fd peer; |
| ASSERT_TRUE(peer = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| // Connect and immediately close a peer with linger. This causes the network-initiated |
| // close that will race with the accepted connection close below. Linger is necessary |
| // because we need a TCP RST to force a full teardown, tickling Netstack the right way to |
| // cause a bad race. |
| struct linger opt = { |
| .l_onoff = 1, |
| .l_linger = 0, |
| }; |
| EXPECT_EQ(setsockopt(peer.get(), SOL_SOCKET, SO_LINGER, &opt, sizeof(opt)), 0) |
| << strerror(errno); |
| ASSERT_EQ(connect(peer.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| 0) |
| << strerror(errno); |
| ASSERT_EQ(close(peer.release()), 0) << strerror(errno); |
| |
| // Accept the connection and close it, adding new racing signal (operating on `close`) to |
| // Netstack. |
| int local = accept(listener.get(), nullptr, nullptr); |
| if (local < 0) { |
| #if !defined(__Fuchsia__) |
| // We get EAGAIN when there are no pending acceptable connections. Though the peer |
| // connect was a blocking call, it can return before the final ACK is sent out causing |
| // the RST from linger0+close to be sent out before the final ACK. This would result in |
| // that connection to be not completed and hence not added to the acceptable queue. |
| // |
| // The above race does not currently exist on Fuchsia where the final ACK would always |
| // be sent out over lo before connect() call returns. |
| ASSERT_EQ(errno, EAGAIN) << strerror(errno); |
| } else { |
| ASSERT_EQ(close(local), 0) |
| #else |
| FAIL() |
| #endif |
| << strerror(errno); |
| } |
| }); |
| } |
| |
| for (auto& t : threads) { |
| t.join(); |
| } |
| |
| ASSERT_EQ(close(listener.release()), 0) << strerror(errno); |
| } |
| |
| TEST(LocalhostTest, GetAddrInfo) { |
| struct addrinfo hints = { |
| .ai_family = AF_UNSPEC, |
| .ai_socktype = SOCK_STREAM, |
| }; |
| |
| struct addrinfo* result; |
| ASSERT_EQ(getaddrinfo("localhost", nullptr, &hints, &result), 0) << strerror(errno); |
| |
| int i = 0; |
| for (struct addrinfo* ai = result; ai != nullptr; ai = ai->ai_next) { |
| i++; |
| |
| EXPECT_EQ(ai->ai_socktype, hints.ai_socktype); |
| const struct sockaddr* sa = ai->ai_addr; |
| |
| switch (ai->ai_family) { |
| case AF_INET: { |
| EXPECT_EQ(ai->ai_addrlen, (socklen_t)16); |
| |
| unsigned char expected_addr[4] = {0x7f, 0x00, 0x00, 0x01}; |
| |
| auto sin = reinterpret_cast<const struct sockaddr_in*>(sa); |
| EXPECT_EQ(sin->sin_addr.s_addr, *reinterpret_cast<uint32_t*>(expected_addr)); |
| |
| break; |
| } |
| case AF_INET6: { |
| EXPECT_EQ(ai->ai_addrlen, (socklen_t)28); |
| |
| const char expected_addr[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; |
| |
| auto sin6 = reinterpret_cast<const struct sockaddr_in6*>(sa); |
| EXPECT_STREQ(reinterpret_cast<const char*>(sin6->sin6_addr.s6_addr), expected_addr); |
| |
| break; |
| } |
| } |
| } |
| EXPECT_EQ(i, 2); |
| freeaddrinfo(result); |
| } |
| |
| TEST(LocalhostTest, GetSockName) { |
| fbl::unique_fd sockfd; |
| ASSERT_TRUE(sockfd = fbl::unique_fd(socket(AF_INET6, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr sa; |
| socklen_t len = sizeof(sa); |
| ASSERT_EQ(getsockname(sockfd.get(), &sa, &len), 0) << strerror(errno); |
| ASSERT_GT(len, sizeof(sa)); |
| ASSERT_EQ(sa.sa_family, AF_INET6); |
| } |
| |
| class NetStreamSocketsTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| fbl::unique_fd listener; |
| ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(listener.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| ASSERT_EQ(listen(listener.get(), 0), 0) << strerror(errno); |
| |
| ASSERT_TRUE(client_ = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| ASSERT_EQ(connect(client_.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| 0) |
| << strerror(errno); |
| |
| ASSERT_TRUE(server_ = fbl::unique_fd(accept(listener.get(), nullptr, nullptr))) |
| << strerror(errno); |
| ASSERT_EQ(close(listener.release()), 0) << strerror(errno); |
| } |
| |
| void TearDown() override { |
| if (client_.is_valid()) |
| EXPECT_EQ(close(client_.release()), 0) << strerror(errno); |
| if (server_.is_valid()) |
| EXPECT_EQ(close(server_.release()), 0) << strerror(errno); |
| } |
| |
| fbl::unique_fd& client() { return client_; } |
| |
| fbl::unique_fd& server() { return server_; } |
| |
| private: |
| fbl::unique_fd client_; |
| fbl::unique_fd server_; |
| }; |
| |
| TEST_F(NetStreamSocketsTest, PartialWriteStress) { |
| // Generate a payload large enough to fill the client->server buffers. |
| std::string big_string; |
| { |
| uint32_t sndbuf_opt; |
| socklen_t sndbuf_optlen = sizeof(sndbuf_opt); |
| EXPECT_EQ(getsockopt(client().get(), SOL_SOCKET, SO_SNDBUF, &sndbuf_opt, &sndbuf_optlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(sndbuf_optlen, sizeof(sndbuf_opt)); |
| |
| uint32_t rcvbuf_opt; |
| socklen_t rcvbuf_optlen = sizeof(rcvbuf_opt); |
| EXPECT_EQ(getsockopt(server().get(), SOL_SOCKET, SO_RCVBUF, &rcvbuf_opt, &rcvbuf_optlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(rcvbuf_optlen, sizeof(rcvbuf_opt)); |
| |
| // SO_{SND,RCV}BUF lie and report double the real value. |
| size_t size = (sndbuf_opt + rcvbuf_opt) >> 1; |
| #if defined(__Fuchsia__) |
| // TODO(https://fxbug.dev/60337): We can avoid this additional space once zircon sockets are not |
| // artificially increasing the buffer sizes. |
| size += 2 * (1 << 18); |
| #endif |
| |
| big_string.reserve(size); |
| while (big_string.size() < size) { |
| big_string += "Though this upload be but little, it is fierce."; |
| } |
| } |
| |
| { |
| // Write in small chunks to allow the outbound TCP to coalesce adjacent writes into a single |
| // segment; that is the circumstance in which the data corruption bug that prompted writing this |
| // test was observed. |
| // |
| // Loopback MTU is 64KiB, so use a value smaller than that. |
| constexpr size_t write_size = 1 << 10; // 1 KiB. |
| |
| auto s = big_string; |
| while (!s.empty()) { |
| ssize_t w = write(client().get(), s.data(), std::min(s.size(), write_size)); |
| ASSERT_GE(w, 0) << strerror(errno); |
| s = s.substr(w); |
| } |
| ASSERT_EQ(shutdown(client().get(), SHUT_WR), 0) << strerror(errno); |
| } |
| |
| // Read the data and validate it against our payload. |
| { |
| // Read in small chunks to increase the probability of partial writes from the network endpoint |
| // into the zircon socket; that is the circumstance in which the data corruption bug that |
| // prompted writing this test was observed. |
| // |
| // zircon sockets are 256KiB deep, so use a value smaller than that. |
| // |
| // Note that in spite of the trickery we employ in this test to create the conditions necessary |
| // to trigger the data corruption bug, it is still not guaranteed to happen. This is because a |
| // race is still necessary to trigger the bug; as netstack is copying bytes from the network to |
| // the zircon socket, the application on the other side of this socket (this test) must read |
| // between a partial write and the next write. |
| constexpr size_t read_size = 1 << 13; // 8 KiB. |
| |
| std::string buf; |
| buf.resize(read_size); |
| for (size_t i = 0; i < big_string.size();) { |
| ssize_t r = read(server().get(), buf.data(), buf.size()); |
| ASSERT_GT(r, 0) << strerror(errno); |
| |
| auto actual = buf.substr(0, r); |
| auto expected = big_string.substr(i, r); |
| |
| constexpr size_t kChunkSize = 100; |
| for (size_t j = 0; j < actual.size(); j += kChunkSize) { |
| auto actual_chunk = actual.substr(j, kChunkSize); |
| auto expected_chunk = expected.substr(j, actual_chunk.size()); |
| ASSERT_EQ(actual_chunk, expected_chunk) << "offset " << i + j; |
| } |
| i += r; |
| } |
| } |
| } |
| |
| TEST_F(NetStreamSocketsTest, PeerClosedPOLLOUT) { |
| fill_stream_send_buf(server().get(), client().get()); |
| |
| EXPECT_EQ(close(client().release()), 0) << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = server().get(), |
| .events = POLLOUT, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| EXPECT_GE(n, 0) << strerror(errno); |
| EXPECT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLOUT | POLLERR | POLLHUP); |
| } |
| |
| TEST_F(NetStreamSocketsTest, BlockingAcceptWrite) { |
| const char msg[] = "hello"; |
| ASSERT_EQ(write(server().get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_EQ(close(server().release()), 0) << strerror(errno); |
| |
| char buf[sizeof(msg) + 1] = {}; |
| ASSERT_EQ(read(client().get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_STREQ(buf, msg); |
| } |
| |
| class TimeoutSockoptsTest : public ::testing::TestWithParam<int /* optname */> {}; |
| |
| TEST_P(TimeoutSockoptsTest, TimeoutSockopts) { |
| int optname = GetParam(); |
| ASSERT_TRUE(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO); |
| |
| fbl::unique_fd socket_fd; |
| ASSERT_TRUE(socket_fd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| // Set the timeout. |
| const struct timeval expected_tv = { |
| .tv_sec = 39, |
| // NB: for some reason, Linux's resolution is limited to 4ms. |
| .tv_usec = 504000, |
| }; |
| EXPECT_EQ(setsockopt(socket_fd.get(), SOL_SOCKET, optname, &expected_tv, sizeof(expected_tv)), 0) |
| << strerror(errno); |
| |
| // Reading it back should work. |
| struct timeval actual_tv; |
| socklen_t optlen = sizeof(actual_tv); |
| EXPECT_EQ(getsockopt(socket_fd.get(), SOL_SOCKET, optname, &actual_tv, &optlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(optlen, sizeof(actual_tv)); |
| EXPECT_EQ(actual_tv.tv_sec, expected_tv.tv_sec); |
| EXPECT_EQ(actual_tv.tv_usec, expected_tv.tv_usec); |
| |
| // Reading it back with too much space should work and set optlen. |
| char actual_tv2_buffer[sizeof(struct timeval) * 2]; |
| memset(&actual_tv2_buffer, 44, sizeof(actual_tv2_buffer)); |
| optlen = sizeof(actual_tv2_buffer); |
| auto actual_tv2 = reinterpret_cast<struct timeval*>(actual_tv2_buffer); |
| EXPECT_EQ(getsockopt(socket_fd.get(), SOL_SOCKET, optname, actual_tv2, &optlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(optlen, sizeof(struct timeval)); |
| EXPECT_EQ(actual_tv2->tv_sec, expected_tv.tv_sec); |
| EXPECT_EQ(actual_tv2->tv_usec, expected_tv.tv_usec); |
| for (auto i = sizeof(struct timeval); i < sizeof(struct timeval) * 2; i++) { |
| EXPECT_EQ(actual_tv2_buffer[i], 44); |
| } |
| |
| // Reading it back without enough space should fail gracefully. |
| memset(&actual_tv, 0, sizeof(actual_tv)); |
| optlen = sizeof(actual_tv) - 7; // Not enough space to store the result. |
| // TODO(eyalsoha): Decide if we want to match Linux's behaviour. It writes to |
| // only the first optlen bytes of the timeval. |
| EXPECT_EQ(getsockopt(socket_fd.get(), SOL_SOCKET, optname, &actual_tv, &optlen), |
| #if defined(__Fuchsia__) |
| -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| #else |
| 0) |
| << strerror(errno); |
| EXPECT_EQ(optlen, sizeof(actual_tv) - 7); |
| struct timeval linux_expected_tv = expected_tv; |
| memset(reinterpret_cast<char*>(&linux_expected_tv) + optlen, 0, |
| sizeof(linux_expected_tv) - optlen); |
| EXPECT_EQ(memcmp(&actual_tv, &linux_expected_tv, sizeof(actual_tv)), 0); |
| #endif |
| |
| // Setting it without enough space should fail gracefully. |
| optlen = sizeof(expected_tv) - 1; // Not big enough. |
| EXPECT_EQ(setsockopt(socket_fd.get(), SOL_SOCKET, optname, &expected_tv, optlen), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| |
| // Setting it with too much space should work okay. |
| const struct timeval expected_tv2 = { |
| .tv_sec = 42, |
| .tv_usec = 0, |
| }; |
| optlen = sizeof(expected_tv2) + 1; // Too big. |
| EXPECT_EQ(setsockopt(socket_fd.get(), SOL_SOCKET, optname, &expected_tv2, optlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(getsockopt(socket_fd.get(), SOL_SOCKET, optname, &actual_tv, &optlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(optlen, sizeof(expected_tv2)); |
| EXPECT_EQ(actual_tv.tv_sec, expected_tv2.tv_sec); |
| EXPECT_EQ(actual_tv.tv_usec, expected_tv2.tv_usec); |
| |
| // Disabling rcvtimeo by setting it to zero should work. |
| const struct timeval zero_tv = { |
| .tv_sec = 0, |
| .tv_usec = 0, |
| }; |
| optlen = sizeof(zero_tv); |
| EXPECT_EQ(setsockopt(socket_fd.get(), SOL_SOCKET, optname, &zero_tv, optlen), 0) |
| << strerror(errno); |
| |
| // Reading back the disabled timeout should work. |
| memset(&actual_tv, 55, sizeof(actual_tv)); |
| optlen = sizeof(actual_tv); |
| EXPECT_EQ(getsockopt(socket_fd.get(), SOL_SOCKET, optname, &actual_tv, &optlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(optlen, sizeof(actual_tv)); |
| EXPECT_EQ(actual_tv.tv_sec, zero_tv.tv_sec); |
| EXPECT_EQ(actual_tv.tv_usec, zero_tv.tv_usec); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NetStreamTest, TimeoutSockoptsTest, |
| ::testing::Values(SO_RCVTIMEO, SO_SNDTIMEO)); |
| |
| const int32_t kConnections = 100; |
| |
| TEST(NetStreamTest, BlockingAcceptWriteMultiple) { |
| fbl::unique_fd acptfd; |
| ASSERT_TRUE(acptfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(acptfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(acptfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| |
| ASSERT_EQ(listen(acptfd.get(), kConnections), 0) << strerror(errno); |
| |
| fbl::unique_fd clientfds[kConnections]; |
| for (auto& clientfd : clientfds) { |
| ASSERT_TRUE(clientfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| ASSERT_EQ( |
| connect(clientfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| } |
| |
| const char msg[] = "hello"; |
| for (int i = 0; i < kConnections; i++) { |
| fbl::unique_fd connfd; |
| ASSERT_TRUE(connfd = fbl::unique_fd(accept(acptfd.get(), nullptr, nullptr))) << strerror(errno); |
| |
| ASSERT_EQ(write(connfd.get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_EQ(close(connfd.release()), 0) << strerror(errno); |
| } |
| |
| for (auto& clientfd : clientfds) { |
| char buf[sizeof(msg) + 1] = {}; |
| ASSERT_EQ(read(clientfd.get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_STREQ(buf, msg); |
| ASSERT_EQ(close(clientfd.release()), 0) << strerror(errno); |
| } |
| |
| EXPECT_EQ(close(acptfd.release()), 0) << strerror(errno); |
| } |
| |
| TEST_F(NetStreamSocketsTest, BlockingAcceptDupWrite) { |
| fbl::unique_fd dupfd; |
| ASSERT_TRUE(dupfd = fbl::unique_fd(dup(server().get()))) << strerror(errno); |
| ASSERT_EQ(close(server().release()), 0) << strerror(errno); |
| |
| const char msg[] = "hello"; |
| ASSERT_EQ(write(dupfd.get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_EQ(close(dupfd.release()), 0) << strerror(errno); |
| |
| char buf[sizeof(msg) + 1] = {}; |
| ASSERT_EQ(read(client().get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_STREQ(buf, msg); |
| } |
| |
| TEST(NetStreamTest, NonBlockingAcceptWrite) { |
| fbl::unique_fd acptfd; |
| ASSERT_TRUE(acptfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(acptfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(acptfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| |
| ASSERT_EQ(listen(acptfd.get(), 0), 0) << strerror(errno); |
| |
| fbl::unique_fd clientfd; |
| ASSERT_TRUE(clientfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| ASSERT_EQ(connect(clientfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| 0) |
| << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = acptfd.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| |
| fbl::unique_fd connfd; |
| ASSERT_TRUE(connfd = fbl::unique_fd(accept(acptfd.get(), nullptr, nullptr))) << strerror(errno); |
| |
| const char msg[] = "hello"; |
| ASSERT_EQ(write(connfd.get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_EQ(close(connfd.release()), 0) << strerror(errno); |
| |
| char buf[sizeof(msg) + 1] = {}; |
| ASSERT_EQ(read(clientfd.get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_STREQ(buf, msg); |
| ASSERT_EQ(close(clientfd.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(acptfd.release()), 0) << strerror(errno); |
| } |
| |
| TEST(NetStreamTest, NonBlockingAcceptDupWrite) { |
| fbl::unique_fd acptfd; |
| ASSERT_TRUE(acptfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(acptfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(acptfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| |
| ASSERT_EQ(listen(acptfd.get(), 0), 0) << strerror(errno); |
| |
| fbl::unique_fd clientfd; |
| ASSERT_TRUE(clientfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| ASSERT_EQ(connect(clientfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| 0) |
| << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = acptfd.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| |
| fbl::unique_fd connfd; |
| ASSERT_TRUE(connfd = fbl::unique_fd(accept(acptfd.get(), nullptr, nullptr))) << strerror(errno); |
| |
| fbl::unique_fd dupfd; |
| ASSERT_TRUE(dupfd = fbl::unique_fd(dup(connfd.get()))) << strerror(errno); |
| ASSERT_EQ(close(connfd.release()), 0) << strerror(errno); |
| |
| const char msg[] = "hello"; |
| ASSERT_EQ(write(dupfd.get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_EQ(close(dupfd.release()), 0) << strerror(errno); |
| |
| char buf[sizeof(msg) + 1] = {}; |
| ASSERT_EQ(read(clientfd.get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_STREQ(buf, msg); |
| ASSERT_EQ(close(clientfd.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(acptfd.release()), 0) << strerror(errno); |
| } |
| |
| TEST(NetStreamTest, NonBlockingConnectWrite) { |
| fbl::unique_fd acptfd; |
| ASSERT_TRUE(acptfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(acptfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(acptfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| |
| ASSERT_EQ(listen(acptfd.get(), 0), 0) << strerror(errno); |
| |
| fbl::unique_fd connfd; |
| ASSERT_TRUE(connfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| |
| int ret; |
| EXPECT_EQ( |
| ret = connect(connfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| -1); |
| if (ret == -1) { |
| ASSERT_EQ(EINPROGRESS, errno) << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = connfd.get(), |
| .events = POLLOUT, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| |
| int err; |
| socklen_t optlen = sizeof(err); |
| ASSERT_EQ(getsockopt(connfd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(err)); |
| ASSERT_EQ(err, 0) << strerror(err); |
| } |
| |
| fbl::unique_fd clientfd; |
| ASSERT_TRUE(clientfd = fbl::unique_fd(accept(acptfd.get(), nullptr, nullptr))) << strerror(errno); |
| |
| const char msg[] = "hello"; |
| ASSERT_EQ(write(connfd.get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_EQ(close(connfd.release()), 0) << strerror(errno); |
| |
| char buf[sizeof(msg) + 1] = {}; |
| ASSERT_EQ(read(clientfd.get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_STREQ(buf, msg); |
| ASSERT_EQ(close(clientfd.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(acptfd.release()), 0) << strerror(errno); |
| } |
| |
| TEST(NetStreamTest, NonBlockingConnectRead) { |
| fbl::unique_fd acptfd; |
| ASSERT_TRUE(acptfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(acptfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(acptfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| |
| ASSERT_EQ(listen(acptfd.get(), 0), 0) << strerror(errno); |
| |
| fbl::unique_fd connfd; |
| ASSERT_TRUE(connfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| |
| int ret; |
| EXPECT_EQ( |
| ret = connect(connfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| -1); |
| if (ret == -1) { |
| ASSERT_EQ(EINPROGRESS, errno) << strerror(errno); |
| |
| fbl::unique_fd clientfd; |
| ASSERT_TRUE(clientfd = fbl::unique_fd(accept(acptfd.get(), nullptr, nullptr))) |
| << strerror(errno); |
| |
| const char msg[] = "hello"; |
| ASSERT_EQ(write(clientfd.get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_EQ(close(clientfd.release()), 0) << strerror(errno); |
| |
| // Note: the success of connection can be detected with POLLOUT, but |
| // we use POLLIN here to wait until some data is written by the peer. |
| struct pollfd pfd = { |
| .fd = connfd.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| |
| int err; |
| socklen_t optlen = sizeof(err); |
| ASSERT_EQ(getsockopt(connfd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(err)); |
| ASSERT_EQ(err, 0) << strerror(err); |
| |
| char buf[sizeof(msg) + 1] = {}; |
| ASSERT_EQ(read(connfd.get(), buf, sizeof(buf)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_STREQ(buf, msg); |
| ASSERT_EQ(close(connfd.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(acptfd.release()), 0) << strerror(errno); |
| } |
| } |
| |
| class AddrKind { |
| public: |
| enum class Kind { |
| V4, |
| V6, |
| V4MAPPEDV6, |
| }; |
| |
| explicit AddrKind(enum Kind kind) : kind(kind) {} |
| enum Kind Kind() const { return kind; } |
| |
| constexpr const char* AddrKindToString() const { |
| switch (kind) { |
| case Kind::V4: |
| return "V4"; |
| case Kind::V6: |
| return "V6"; |
| case Kind::V4MAPPEDV6: |
| return "V4MAPPEDV6"; |
| } |
| } |
| |
| private: |
| enum Kind kind; |
| }; |
| |
| template <int socktype> |
| class SocketTest : public ::testing::TestWithParam<AddrKind> { |
| protected: |
| void SetUp() override { |
| ASSERT_TRUE(sock_ = fbl::unique_fd(socket(Domain(), socktype, 0))) << strerror(errno); |
| } |
| |
| void TearDown() override { ASSERT_EQ(close(sock_.release()), 0) << strerror(errno); } |
| |
| const fbl::unique_fd& sock() { return sock_; } |
| |
| sa_family_t Domain() const { |
| switch (GetParam().Kind()) { |
| case AddrKind::Kind::V4: |
| return AF_INET; |
| case AddrKind::Kind::V6: |
| case AddrKind::Kind::V4MAPPEDV6: |
| return AF_INET6; |
| } |
| } |
| |
| socklen_t AddrLen() const { |
| if (Domain() == AF_INET) { |
| return sizeof(sockaddr_in); |
| } |
| return sizeof(sockaddr_in6); |
| } |
| |
| virtual struct sockaddr_storage Address(uint16_t port) const = 0; |
| |
| private: |
| fbl::unique_fd sock_; |
| }; |
| |
| template <int socktype> |
| class AnyAddrSocketTest : public SocketTest<socktype> { |
| protected: |
| struct sockaddr_storage Address(uint16_t port) const override { |
| struct sockaddr_storage addr { |
| .ss_family = this->Domain(), |
| }; |
| |
| switch (this->GetParam().Kind()) { |
| case AddrKind::Kind::V4: { |
| auto sin = reinterpret_cast<struct sockaddr_in*>(&addr); |
| sin->sin_addr.s_addr = htonl(INADDR_ANY); |
| sin->sin_port = port; |
| return addr; |
| } |
| case AddrKind::Kind::V6: { |
| auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); |
| sin6->sin6_addr = IN6ADDR_ANY_INIT; |
| sin6->sin6_port = port; |
| return addr; |
| } |
| case AddrKind::Kind::V4MAPPEDV6: { |
| auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); |
| sin6->sin6_addr = IN6ADDR_ANY_INIT; |
| sin6->sin6_addr.s6_addr[10] = 0xff; |
| sin6->sin6_addr.s6_addr[11] = 0xff; |
| sin6->sin6_port = port; |
| return addr; |
| } |
| } |
| } |
| }; |
| |
| using AnyAddrStreamSocketTest = AnyAddrSocketTest<SOCK_STREAM>; |
| using AnyAddrDatagramSocketTest = AnyAddrSocketTest<SOCK_DGRAM>; |
| |
| TEST_P(AnyAddrStreamSocketTest, Connect) { |
| struct sockaddr_storage any = Address(0); |
| socklen_t addrlen = AddrLen(); |
| ASSERT_EQ(connect(sock().get(), reinterpret_cast<const struct sockaddr*>(&any), addrlen), -1); |
| ASSERT_EQ(errno, ECONNREFUSED) << strerror(errno); |
| |
| // The error should have been consumed. |
| int err; |
| socklen_t optlen = sizeof(err); |
| ASSERT_EQ(getsockopt(sock().get(), SOL_SOCKET, SO_ERROR, &err, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(err)); |
| ASSERT_EQ(err, 0) << strerror(err); |
| } |
| |
| TEST_P(AnyAddrDatagramSocketTest, Connect) { |
| struct sockaddr_storage any = Address(0); |
| socklen_t addrlen = AddrLen(); |
| EXPECT_EQ(connect(sock().get(), reinterpret_cast<const struct sockaddr*>(&any), addrlen), 0) |
| << strerror(errno); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NetStreamTest, AnyAddrStreamSocketTest, |
| ::testing::Values(AddrKind::Kind::V4, AddrKind::Kind::V6, |
| AddrKind::Kind::V4MAPPEDV6), |
| [](const auto info) { return info.param.AddrKindToString(); }); |
| INSTANTIATE_TEST_SUITE_P(NetDatagramTest, AnyAddrDatagramSocketTest, |
| ::testing::Values(AddrKind::Kind::V4, AddrKind::Kind::V6, |
| AddrKind::Kind::V4MAPPEDV6), |
| [](const auto info) { return info.param.AddrKindToString(); }); |
| |
| template <int socktype> |
| class LoopbackAddrSocketTest : public SocketTest<socktype> { |
| protected: |
| struct sockaddr_storage Address(uint16_t port) const override { |
| struct sockaddr_storage addr { |
| .ss_family = this->Domain(), |
| }; |
| |
| switch (this->GetParam().Kind()) { |
| case AddrKind::Kind::V4: { |
| auto sin = reinterpret_cast<struct sockaddr_in*>(&addr); |
| sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| sin->sin_port = htons(port); |
| return addr; |
| } |
| case AddrKind::Kind::V6: { |
| auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); |
| sin6->sin6_addr = IN6ADDR_LOOPBACK_INIT; |
| sin6->sin6_port = htons(port); |
| return addr; |
| } |
| case AddrKind::Kind::V4MAPPEDV6: { |
| auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); |
| sin6->sin6_addr = IN6ADDR_LOOPBACK_INIT; |
| sin6->sin6_addr.s6_addr[10] = 0xff; |
| sin6->sin6_addr.s6_addr[11] = 0xff; |
| sin6->sin6_port = htons(port); |
| return addr; |
| } |
| } |
| } |
| }; |
| |
| using LoopbackDatagramSocketTest = LoopbackAddrSocketTest<SOCK_DGRAM>; |
| |
| template <typename F> |
| void AssertClearError(const fbl::unique_fd& sock, F fn) { |
| char bytes[1]; |
| struct pollfd pfd = { |
| .fd = sock.get(), |
| }; |
| // Send a UDP packet to trigger a port unreachable response. Expect a POLLERR to be signaled on |
| // the socket. |
| ASSERT_EQ(send(sock.get(), bytes, sizeof(bytes), 0), ssize_t(sizeof(bytes))) << strerror(errno); |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| ASSERT_EQ(pfd.revents & POLLERR, POLLERR); |
| fn(); |
| n = poll(&pfd, 1, 0); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 0); |
| } |
| |
| TEST_P(LoopbackDatagramSocketTest, POLLERR) { |
| char bytes[1]; |
| struct iovec iov[] = {{ |
| .iov_base = bytes, |
| .iov_len = sizeof(bytes), |
| }}; |
| struct msghdr msg = { |
| .msg_iov = iov, |
| .msg_iovlen = std::size(iov), |
| }; |
| |
| { |
| // Connect to an existing remote but on a port that is not being used. |
| struct sockaddr_storage loopback = Address(htons(1337)); |
| socklen_t addrlen = AddrLen(); |
| ASSERT_EQ(connect(sock().get(), reinterpret_cast<const struct sockaddr*>(&loopback), addrlen), |
| 0) |
| << strerror(errno); |
| } |
| |
| // Precondition sanity check: no pending events on the socket. |
| struct pollfd pfd = { |
| .fd = sock().get(), |
| }; |
| int n = poll(&pfd, 1, 0); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 0); |
| |
| { |
| SCOPED_TRACE("send"); |
| AssertClearError(sock(), [&]() { |
| EXPECT_EQ(send(sock().get(), bytes, sizeof(bytes), 0), -1); |
| EXPECT_EQ(errno, ECONNREFUSED) << strerror(errno); |
| }); |
| } |
| { |
| SCOPED_TRACE("recv"); |
| AssertClearError(sock(), [&]() { |
| EXPECT_EQ(recv(sock().get(), bytes, sizeof(bytes), 0), -1); |
| EXPECT_EQ(errno, ECONNREFUSED) << strerror(errno); |
| }); |
| } |
| { |
| SCOPED_TRACE("sendmsg"); |
| AssertClearError(sock(), [&]() { |
| EXPECT_EQ(sendmsg(sock().get(), &msg, 0), -1); |
| EXPECT_EQ(errno, ECONNREFUSED) << strerror(errno); |
| }); |
| } |
| { |
| SCOPED_TRACE("recvmsg"); |
| AssertClearError(sock(), [&]() { |
| EXPECT_EQ(recvmsg(sock().get(), &msg, 0), -1); |
| EXPECT_EQ(errno, ECONNREFUSED) << strerror(errno); |
| }); |
| } |
| { |
| SCOPED_TRACE("getsockopt with SO_ERROR"); |
| AssertClearError(sock(), [&]() { |
| int err; |
| socklen_t optlen = sizeof(err); |
| EXPECT_EQ(getsockopt(sock().get(), SOL_SOCKET, SO_ERROR, &err, &optlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(optlen, sizeof(err)); |
| EXPECT_EQ(err, ECONNREFUSED) << strerror(err); |
| }); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NetDatagramTest, LoopbackDatagramSocketTest, |
| ::testing::Values(AddrKind::Kind::V4, AddrKind::Kind::V6), |
| [](const auto info) { return info.param.AddrKindToString(); }); |
| |
| class IOMethod { |
| public: |
| enum class Op { |
| READ, |
| READV, |
| RECV, |
| RECVFROM, |
| RECVMSG, |
| WRITE, |
| WRITEV, |
| SEND, |
| SENDTO, |
| SENDMSG, |
| }; |
| |
| explicit IOMethod(enum Op op) : op(op) {} |
| enum Op Op() const { return op; } |
| |
| ssize_t executeIO(int fd, char* buf, size_t len) const { |
| struct iovec iov[] = {{ |
| .iov_base = buf, |
| .iov_len = len, |
| }}; |
| struct msghdr msg = { |
| .msg_iov = iov, |
| .msg_iovlen = std::size(iov), |
| }; |
| switch (op) { |
| case Op::READ: |
| return read(fd, buf, len); |
| case Op::READV: |
| return readv(fd, iov, std::size(iov)); |
| case Op::RECV: |
| return recv(fd, buf, len, 0); |
| case Op::RECVFROM: |
| return recvfrom(fd, buf, len, 0, nullptr, nullptr); |
| case Op::RECVMSG: |
| return recvmsg(fd, &msg, 0); |
| case Op::WRITE: |
| return write(fd, buf, len); |
| case Op::WRITEV: |
| return writev(fd, iov, std::size(iov)); |
| case Op::SEND: |
| return send(fd, buf, len, 0); |
| case Op::SENDTO: |
| return sendto(fd, buf, len, 0, nullptr, 0); |
| case Op::SENDMSG: |
| return sendmsg(fd, &msg, 0); |
| } |
| } |
| |
| bool isWrite() const { |
| switch (op) { |
| case Op::READ: |
| case Op::READV: |
| case Op::RECV: |
| case Op::RECVFROM: |
| case Op::RECVMSG: |
| return false; |
| case Op::WRITE: |
| case Op::WRITEV: |
| case Op::SEND: |
| case Op::SENDTO: |
| case Op::SENDMSG: |
| default: |
| return true; |
| } |
| } |
| |
| constexpr const char* IOMethodToString() const { |
| switch (op) { |
| case Op::READ: |
| return "Read"; |
| case Op::READV: |
| return "Readv"; |
| case Op::RECV: |
| return "Recv"; |
| case Op::RECVFROM: |
| return "Recvfrom"; |
| case Op::RECVMSG: |
| return "Recvmsg"; |
| case Op::WRITE: |
| return "Write"; |
| case Op::WRITEV: |
| return "Writev"; |
| case Op::SEND: |
| return "Send"; |
| case Op::SENDTO: |
| return "Sendto"; |
| case Op::SENDMSG: |
| return "Sendmsg"; |
| } |
| } |
| |
| private: |
| enum Op op; |
| }; |
| |
| #if !defined(__Fuchsia__) |
| // disableSIGPIPE is typically invoked on Linux, in cases where the caller |
| // expects to perform stream socket writes on an unconnected socket. In such |
| // cases, SIGPIPE is expected on Linux. This returns a fit::deferred_action object |
| // whose destructor would undo the signal masking performed here. |
| // |
| // send{,to,msg} support the MSG_NOSIGNAL flag to suppress this behaviour, but |
| // write and writev do not. |
| auto disableSIGPIPE(bool isWrite) { |
| struct sigaction act = { |
| .sa_handler = SIG_IGN, |
| }; |
| struct sigaction oldact; |
| if (isWrite) { |
| EXPECT_EQ(sigaction(SIGPIPE, &act, &oldact), 0) << strerror(errno); |
| } |
| return fit::defer([=]() { |
| if (isWrite) { |
| EXPECT_EQ(sigaction(SIGPIPE, &oldact, nullptr), 0) << strerror(errno); |
| } |
| }); |
| } |
| #endif |
| |
| class IOMethodTest : public ::testing::TestWithParam<IOMethod> {}; |
| |
| void doNullPtrIO(const fbl::unique_fd& fd, const fbl::unique_fd& other, IOMethod ioMethod, |
| bool datagram) { |
| // A version of ioMethod::executeIO with special handling for vectorized operations: a 1-byte |
| // buffer is prepended to the argument. |
| auto executeIO = [ioMethod](int fd, char* buf, size_t len) { |
| char buffer[1]; |
| struct iovec iov[] = { |
| { |
| .iov_base = buffer, |
| .iov_len = sizeof(buffer), |
| }, |
| { |
| .iov_base = buf, |
| .iov_len = len, |
| }, |
| }; |
| struct msghdr msg = { |
| .msg_iov = iov, |
| .msg_iovlen = std::size(iov), |
| }; |
| |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::READ: |
| case IOMethod::Op::RECV: |
| case IOMethod::Op::RECVFROM: |
| case IOMethod::Op::WRITE: |
| case IOMethod::Op::SEND: |
| case IOMethod::Op::SENDTO: |
| return ioMethod.executeIO(fd, buf, len); |
| case IOMethod::Op::READV: |
| return readv(fd, iov, std::size(iov)); |
| case IOMethod::Op::RECVMSG: |
| return recvmsg(fd, &msg, 0); |
| case IOMethod::Op::WRITEV: |
| return writev(fd, iov, std::size(iov)); |
| case IOMethod::Op::SENDMSG: |
| return sendmsg(fd, &msg, 0); |
| } |
| }; |
| |
| auto prepareForRead = [&](const char* buf, size_t len) { |
| ASSERT_EQ(send(other.get(), buf, len, 0), ssize_t(len)) << strerror(errno); |
| |
| // Wait for the packet to arrive since we are nonblocking. |
| struct pollfd pfd = { |
| .fd = fd.get(), |
| .events = POLLIN, |
| }; |
| |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLIN); |
| }; |
| |
| auto confirmWrite = [&]() { |
| char buffer[1]; |
| #if defined(__Fuchsia__) |
| if (!datagram) { |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::WRITE: |
| case IOMethod::Op::SEND: |
| case IOMethod::Op::SENDTO: |
| break; |
| case IOMethod::Op::WRITEV: |
| case IOMethod::Op::SENDMSG: { |
| // Fuchsia doesn't comply because zircon sockets do not implement atomic vector |
| // operations, so these vector operations end up having sent the byte provided in the |
| // executeIO closure. See https://fxbug.dev/67928 for more details. |
| // |
| // Wait for the packet to arrive since we are nonblocking. |
| struct pollfd pfd = { |
| .fd = other.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLIN); |
| EXPECT_EQ(recv(other.get(), buffer, sizeof(buffer), 0), 1) << strerror(errno); |
| return; |
| } |
| default: |
| FAIL() << "unexpected method " << ioMethod.IOMethodToString(); |
| } |
| } |
| #endif |
| // Nothing was sent. This is not obvious in the vectorized case. |
| EXPECT_EQ(recv(other.get(), buffer, sizeof(buffer), 0), -1); |
| EXPECT_EQ(errno, EAGAIN) << strerror(errno); |
| }; |
| |
| // Receive some data so we can attempt to read it below. |
| if (!ioMethod.isWrite()) { |
| char buffer[] = {0x74, 0x75}; |
| prepareForRead(buffer, sizeof(buffer)); |
| } |
| |
| [&]() { |
| #if defined(__Fuchsia__) |
| if (!datagram) { |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::READ: |
| case IOMethod::Op::RECV: |
| case IOMethod::Op::RECVFROM: |
| case IOMethod::Op::WRITE: |
| case IOMethod::Op::SEND: |
| case IOMethod::Op::SENDTO: |
| break; |
| |
| case IOMethod::Op::READV: |
| case IOMethod::Op::RECVMSG: |
| case IOMethod::Op::WRITEV: |
| case IOMethod::Op::SENDMSG: |
| // Fuchsia doesn't comply because zircon sockets do not implement atomic vector |
| // operations, so these vector operations report success on the byte provided in the |
| // executeIO closure. |
| EXPECT_EQ(executeIO(fd.get(), nullptr, 1), 1) << strerror(errno); |
| return; |
| } |
| } |
| #endif |
| EXPECT_EQ(executeIO(fd.get(), nullptr, 1), -1); |
| EXPECT_EQ(errno, EFAULT) << strerror(errno); |
| }(); |
| |
| if (ioMethod.isWrite()) { |
| confirmWrite(); |
| } else { |
| char buffer[1]; |
| auto result = executeIO(fd.get(), buffer, sizeof(buffer)); |
| if (datagram) { |
| // The datagram was consumed in spite of the buffer being null. |
| EXPECT_EQ(result, -1); |
| EXPECT_EQ(errno, EAGAIN) << strerror(errno); |
| } else { |
| ssize_t space = sizeof(buffer); |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::READV: |
| case IOMethod::Op::RECVMSG: |
| #if defined(__Fuchsia__) |
| // Fuchsia consumed one byte above. |
| #else |
| // An additional byte of space was provided in the executeIO closure. |
| space += 1; |
| #endif |
| case IOMethod::Op::READ: |
| case IOMethod::Op::RECV: |
| case IOMethod::Op::RECVFROM: |
| break; |
| default: |
| FAIL() << "unexpected method " << ioMethod.IOMethodToString(); |
| } |
| EXPECT_EQ(result, space) << strerror(errno); |
| } |
| } |
| |
| // Do it again, but this time write less data so that vector operations can work normally. |
| if (!ioMethod.isWrite()) { |
| char buffer[] = {0x74}; |
| prepareForRead(buffer, sizeof(buffer)); |
| } |
| |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::WRITEV: |
| case IOMethod::Op::SENDMSG: |
| #if defined(__Fuchsia__) |
| if (!datagram) { |
| // Fuchsia doesn't comply because zircon sockets do not implement atomic vector |
| // operations, so these vector operations report success on the byte provided in the |
| // executeIO closure. |
| EXPECT_EQ(executeIO(fd.get(), nullptr, 1), 1) << strerror(errno); |
| break; |
| } |
| #endif |
| case IOMethod::Op::READ: |
| case IOMethod::Op::RECV: |
| case IOMethod::Op::RECVFROM: |
| case IOMethod::Op::WRITE: |
| case IOMethod::Op::SEND: |
| case IOMethod::Op::SENDTO: |
| EXPECT_EQ(executeIO(fd.get(), nullptr, 1), -1); |
| EXPECT_EQ(errno, EFAULT) << strerror(errno); |
| break; |
| case IOMethod::Op::READV: |
| case IOMethod::Op::RECVMSG: |
| // These vectorized operations never reach the faulty buffer, so they work normally. |
| EXPECT_EQ(executeIO(fd.get(), nullptr, 1), 1) << strerror(errno); |
| break; |
| } |
| |
| if (ioMethod.isWrite()) { |
| confirmWrite(); |
| } else { |
| char buffer[1]; |
| auto result = executeIO(fd.get(), buffer, sizeof(buffer)); |
| if (datagram) { |
| // The datagram was consumed in spite of the buffer being null. |
| EXPECT_EQ(result, -1); |
| EXPECT_EQ(errno, EAGAIN) << strerror(errno); |
| } else { |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::READ: |
| case IOMethod::Op::RECV: |
| case IOMethod::Op::RECVFROM: |
| EXPECT_EQ(result, ssize_t(sizeof(buffer))) << strerror(errno); |
| break; |
| case IOMethod::Op::READV: |
| case IOMethod::Op::RECVMSG: |
| // The byte we sent was consumed in the executeIO closure. |
| EXPECT_EQ(result, -1); |
| EXPECT_EQ(errno, EAGAIN) << strerror(errno); |
| break; |
| default: |
| FAIL() << "unexpected method " << ioMethod.IOMethodToString(); |
| } |
| } |
| } |
| } |
| |
| 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 struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_port = 1235, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| ASSERT_EQ(bind(fd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| ASSERT_EQ(connect(fd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| doNullPtrIO(fd, fd, GetParam(), true); |
| } |
| |
| TEST_P(IOMethodTest, NullptrFaultSTREAM) { |
| fbl::unique_fd listener, client, server; |
| ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(listener.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| ASSERT_EQ(listen(listener.get(), 0), 0) << strerror(errno); |
| |
| ASSERT_TRUE(client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| int ret; |
| EXPECT_EQ( |
| ret = connect(client.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| -1); |
| if (ret == -1) { |
| ASSERT_EQ(EINPROGRESS, errno) << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = client.get(), |
| .events = POLLOUT, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| } |
| |
| ASSERT_TRUE(server = fbl::unique_fd(accept4(listener.get(), nullptr, nullptr, SOCK_NONBLOCK))) |
| << strerror(errno); |
| ASSERT_EQ(close(listener.release()), 0) << strerror(errno); |
| |
| doNullPtrIO(client, server, GetParam(), false); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(IOMethodTests, IOMethodTest, |
| ::testing::Values(IOMethod::Op::READ, IOMethod::Op::READV, |
| IOMethod::Op::RECV, IOMethod::Op::RECVFROM, |
| IOMethod::Op::RECVMSG, IOMethod::Op::WRITE, |
| IOMethod::Op::WRITEV, IOMethod::Op::SEND, |
| IOMethod::Op::SENDTO, IOMethod::Op::SENDMSG), |
| [](const auto info) { return info.param.IOMethodToString(); }); |
| |
| using connectingIOParams = std::tuple<IOMethod, bool>; |
| |
| class ConnectingIOTest : public ::testing::TestWithParam<connectingIOParams> {}; |
| |
| // Tests the application behavior when we start to read and write from a stream socket that is not |
| // yet connected. |
| TEST_P(ConnectingIOTest, BlockedIO) { |
| auto const& [ioMethod, closeListener] = GetParam(); |
| fbl::unique_fd listener; |
| ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| ASSERT_EQ(listen(listener.get(), 0), 0) << strerror(errno); |
| |
| // Setup a test client connection over which we test socket reads |
| // when the connection is not yet established. |
| |
| // Linux default behavior is to complete one more connection than what |
| // was passed as listen backlog (zero here). |
| // Hence we initiate 2 client connections in this order: |
| // (1) a precursor client for the sole purpose of filling up the server |
| // accept queue after handshake completion. |
| // (2) a test client that keeps trying to establish connection with |
| // server, but remains in SYN-SENT. |
| fbl::unique_fd precursor_client; |
| ASSERT_TRUE(precursor_client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) |
| << strerror(errno); |
| ASSERT_EQ( |
| connect(precursor_client.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| // Observe the precursor client connection on the server side. This ensures that the TCP stack's |
| // server accept queue is updated with the precursor client connection before any subsequent |
| // client connect requests. The precursor client connect call returns after handshake completion, |
| // but not necessarily after the server side has processed the ACK from the client and updated its |
| // accept queue. |
| struct pollfd pfd = { |
| .fd = listener.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| ASSERT_EQ(pfd.revents, POLLIN); |
| |
| // The test client connection would get established _only_ after both |
| // these conditions are met: |
| // (1) prior client connections are accepted by the server thus |
| // making room for a new connection. |
| // (2) the server-side TCP stack completes handshake in response to |
| // the retransmitted SYN for the test client connection. |
| // |
| // The test would likely perform socket reads before any connection |
| // timeout. |
| fbl::unique_fd test_client; |
| ASSERT_TRUE(test_client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| |
| // Sample data to be written. |
| char sample_data[] = "Sample Data"; |
| // To correctly test reads, keep alteast one byte larger read buffer than what would be written. |
| char recvbuf[sizeof(sample_data) + 1] = {}; |
| bool isWrite = ioMethod.isWrite(); |
| auto executeIO = [&, op = ioMethod]() { |
| if (isWrite) { |
| return op.executeIO(test_client.get(), sample_data, sizeof(sample_data)); |
| } |
| return op.executeIO(test_client.get(), recvbuf, sizeof(recvbuf)); |
| }; |
| #if !defined(__Fuchsia__) |
| auto undo = disableSIGPIPE(isWrite); |
| #endif |
| |
| EXPECT_EQ(executeIO(), -1); |
| if (isWrite) { |
| EXPECT_EQ(errno, EPIPE) << strerror(errno); |
| } else { |
| EXPECT_EQ(errno, ENOTCONN) << strerror(errno); |
| } |
| |
| ASSERT_EQ(connect(test_client.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), |
| -1); |
| ASSERT_EQ(EINPROGRESS, errno) << strerror(errno); |
| |
| // Test socket I/O without waiting for connection to be established. |
| EXPECT_EQ(executeIO(), -1); |
| EXPECT_EQ(errno, EWOULDBLOCK) << strerror(errno); |
| |
| std::latch fut_started(1); |
| // Asynchronously block on I/O from the test client socket. |
| const auto fut = std::async(std::launch::async, [&, err = closeListener]() { |
| // Make the socket blocking. |
| int flags = fcntl(test_client.get(), F_GETFL, 0); |
| EXPECT_EQ(0, fcntl(test_client.get(), F_SETFL, flags ^ O_NONBLOCK)) << strerror(errno); |
| |
| fut_started.count_down(); |
| |
| if (err) { |
| EXPECT_EQ(executeIO(), -1); |
| EXPECT_EQ(errno, ECONNREFUSED) << strerror(errno); |
| } else { |
| EXPECT_EQ(executeIO(), ssize_t(sizeof(sample_data))); |
| } |
| }); |
| fut_started.wait(); |
| EXPECT_EQ(fut.wait_for(std::chrono::milliseconds(10)), std::future_status::timeout); |
| |
| if (closeListener) { |
| ASSERT_EQ(close(listener.release()), 0) << strerror(errno); |
| } else { |
| // Accept the precursor connection to make room for the test client |
| // connection to complete. |
| fbl::unique_fd precursor_accept; |
| ASSERT_TRUE(precursor_accept = fbl::unique_fd(accept(listener.get(), nullptr, nullptr))) |
| << strerror(errno); |
| ASSERT_EQ(close(precursor_accept.release()), 0) << strerror(errno); |
| ASSERT_EQ(close(precursor_client.release()), 0) << strerror(errno); |
| |
| // Accept the test client connection. |
| fbl::unique_fd test_accept; |
| ASSERT_TRUE(test_accept = fbl::unique_fd(accept(listener.get(), nullptr, nullptr))) |
| << strerror(errno); |
| |
| if (isWrite) { |
| // Ensure that we read the data whose send request was enqueued until |
| // the connection was established. |
| ASSERT_EQ(read(test_accept.get(), recvbuf, sizeof(recvbuf)), ssize_t(sizeof(sample_data))) |
| << strerror(errno); |
| ASSERT_STREQ(recvbuf, sample_data); |
| } else { |
| // Write data to unblock the socket read on the test client connection. |
| ASSERT_EQ(write(test_accept.get(), sample_data, sizeof(sample_data)), |
| ssize_t(sizeof(sample_data))) |
| << strerror(errno); |
| } |
| } |
| |
| EXPECT_EQ(fut.wait_for(std::chrono::milliseconds(kTimeout)), std::future_status::ready); |
| } |
| |
| std::string connectingIOParamsToString(const ::testing::TestParamInfo<connectingIOParams> info) { |
| auto const& [ioMethod, closeListener] = info.param; |
| std::stringstream s; |
| if (closeListener) { |
| s << "CloseListener"; |
| } else { |
| s << "Accept"; |
| } |
| s << "During" << ioMethod.IOMethodToString(); |
| |
| return s.str(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| NetStreamTest, ConnectingIOTest, |
| ::testing::Combine(::testing::Values(IOMethod::Op::READ, IOMethod::Op::READV, |
| IOMethod::Op::RECV, IOMethod::Op::RECVFROM, |
| IOMethod::Op::RECVMSG, IOMethod::Op::WRITE, |
| IOMethod::Op::WRITEV, IOMethod::Op::SEND, |
| IOMethod::Op::SENDTO, IOMethod::Op::SENDMSG), |
| ::testing::Values(false, true)), |
| connectingIOParamsToString); |
| |
| namespace { |
| // Test close/shutdown of listening socket with multiple non-blocking connects. |
| // This tests client sockets in connected and connecting states. |
| void TestListenWhileConnect(const IOMethod& ioMethod, void (*stopListen)(fbl::unique_fd&)) { |
| fbl::unique_fd listener; |
| ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| ASSERT_EQ(bind(listener.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| // This test is only interested in deterministically getting a socket in |
| // connecting state. For that, we use a listen backlog of zero which would |
| // mean there is exactly one connection that gets established and is enqueued |
| // to the accept queue. We poll on the listener to ensure that is enqueued. |
| // After that the subsequent client connect will stay in connecting state as |
| // the accept queue is full. |
| ASSERT_EQ(listen(listener.get(), 0), 0) << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| fbl::unique_fd established_client; |
| ASSERT_TRUE(established_client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) |
| << strerror(errno); |
| ASSERT_EQ(connect(established_client.get(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| // Ensure that the accept queue has the completed connection. |
| constexpr int kTimeout = 10000; |
| { |
| pollfd pfd = { |
| .fd = listener.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| ASSERT_EQ(pfd.revents, POLLIN); |
| } |
| |
| fbl::unique_fd connecting_client; |
| ASSERT_TRUE(connecting_client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0))) |
| << strerror(errno); |
| int ret = connect(connecting_client.get(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)); |
| // Linux manpage for connect, for EINPROGRESS error: |
| // "The socket is nonblocking and the connection cannot be completed immediately." |
| // Which means that the non-blocking connect may succeed (ie. ret == 0) in the unlikely case |
| // where the connection does complete immediately before the system call returns. |
| // |
| // On Fuchsia, a non-blocking connect would always fail with EINPROGRESS. |
| #if !defined(__Fuchsia__) |
| if (ret != 0) |
| #endif |
| { |
| EXPECT_EQ(ret, -1); |
| EXPECT_EQ(errno, EINPROGRESS) << strerror(errno); |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(stopListen(listener)); |
| |
| std::array<std::pair<int, int>, 2> sockets = { |
| std::make_pair(established_client.get(), ECONNRESET), |
| std::make_pair(connecting_client.get(), ECONNREFUSED), |
| }; |
| for (size_t i = 0; i < sockets.size(); i++) { |
| SCOPED_TRACE("i=" + std::to_string(i)); |
| auto [fd, expected_errno] = sockets[i]; |
| pollfd pfd = { |
| .fd = fd, |
| .events = POLLIN, |
| }; |
| // When the listening socket is closed, the peer would reset the connection. |
| int n = poll(&pfd, 1, kTimeout); |
| EXPECT_GE(n, 0) << strerror(errno); |
| EXPECT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLIN | POLLHUP | POLLERR); |
| |
| char c; |
| EXPECT_EQ(ioMethod.executeIO(fd, &c, sizeof(c)), -1); |
| EXPECT_EQ(errno, expected_errno) << strerror(errno) << " vs " << strerror(expected_errno); |
| |
| bool isWrite = ioMethod.isWrite(); |
| #if !defined(__Fuchsia__) |
| auto undo = disableSIGPIPE(isWrite); |
| #endif |
| |
| if (isWrite) { |
| ASSERT_EQ(ioMethod.executeIO(fd, &c, sizeof(c)), -1); |
| EXPECT_EQ(errno, EPIPE) << strerror(errno); |
| } else { |
| ASSERT_EQ(ioMethod.executeIO(fd, &c, sizeof(c)), 0) << strerror(errno); |
| } |
| } |
| } |
| |
| class StopListenWhileConnect : public ::testing::TestWithParam<IOMethod> {}; |
| |
| TEST_P(StopListenWhileConnect, Close) { |
| TestListenWhileConnect( |
| GetParam(), [](fbl::unique_fd& f) { ASSERT_EQ(close(f.release()), 0) << strerror(errno); }); |
| } |
| |
| TEST_P(StopListenWhileConnect, Shutdown) { |
| TestListenWhileConnect(GetParam(), [](fbl::unique_fd& f) { |
| ASSERT_EQ(shutdown(f.get(), SHUT_RD), 0) << strerror(errno); |
| }); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NetStreamTest, StopListenWhileConnect, |
| ::testing::Values(IOMethod::Op::READ, IOMethod::Op::READV, |
| IOMethod::Op::RECV, IOMethod::Op::RECVFROM, |
| IOMethod::Op::RECVMSG, IOMethod::Op::WRITE, |
| IOMethod::Op::WRITEV, IOMethod::Op::SEND, |
| IOMethod::Op::SENDTO, IOMethod::Op::SENDMSG), |
| [](const ::testing::TestParamInfo<IOMethod>& info) { |
| return info.param.IOMethodToString(); |
| }); |
| } // namespace |
| |
| TEST(NetStreamTest, NonBlockingConnectRefused) { |
| fbl::unique_fd acptfd; |
| ASSERT_TRUE(acptfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(acptfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(acptfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| |
| // No listen() on acptfd. |
| |
| fbl::unique_fd connfd; |
| ASSERT_TRUE(connfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| int flags = fcntl(connfd.get(), F_GETFL, 0); |
| ASSERT_EQ(0, fcntl(connfd.get(), F_SETFL, flags | O_NONBLOCK)); |
| |
| int ret; |
| EXPECT_EQ( |
| ret = connect(connfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| -1); |
| if (ret == -1) { |
| ASSERT_EQ(EINPROGRESS, errno) << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = connfd.get(), |
| .events = POLLOUT, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| |
| int err; |
| socklen_t optlen = sizeof(err); |
| ASSERT_EQ(getsockopt(connfd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(err)); |
| ASSERT_EQ(err, ECONNREFUSED) << strerror(err); |
| } |
| |
| EXPECT_EQ(close(connfd.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(acptfd.release()), 0) << strerror(errno); |
| } |
| |
| TEST(NetStreamTest, GetTcpInfo) { |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| { |
| tcp_info info; |
| socklen_t info_len = sizeof(tcp_info); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_TCP, TCP_INFO, &info, &info_len), 0) << strerror(errno); |
| ASSERT_EQ(sizeof(tcp_info), info_len); |
| |
| #if defined(__Fuchsia__) |
| // Unsupported fields are intentionally initialized with garbage for explicitness. |
| uint32_t initialization; |
| memset(&initialization, 0xff, sizeof(initialization)); |
| |
| ASSERT_NE(info.tcpi_ca_state, initialization); |
| ASSERT_NE(info.tcpi_rto, initialization); |
| ASSERT_NE(info.tcpi_rtt, initialization); |
| ASSERT_NE(info.tcpi_rttvar, initialization); |
| ASSERT_NE(info.tcpi_snd_ssthresh, initialization); |
| ASSERT_NE(info.tcpi_snd_cwnd, initialization); |
| ASSERT_NE(info.tcpi_reord_seen, initialization); |
| |
| tcp_info expected; |
| memset(&expected, initialization, sizeof(expected)); |
| expected.tcpi_ca_state = info.tcpi_ca_state; |
| expected.tcpi_rto = info.tcpi_rto; |
| expected.tcpi_rtt = info.tcpi_rtt; |
| expected.tcpi_rttvar = info.tcpi_rttvar; |
| expected.tcpi_snd_ssthresh = info.tcpi_snd_ssthresh; |
| expected.tcpi_snd_cwnd = info.tcpi_snd_cwnd; |
| expected.tcpi_reord_seen = info.tcpi_reord_seen; |
| |
| ASSERT_EQ(memcmp(&info, &expected, sizeof(tcp_info)), 0); |
| #endif |
| } |
| |
| // Test that we can partially retrieve TCP_INFO. |
| { |
| uint8_t tcpi_state; |
| socklen_t info_len = sizeof(tcpi_state); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_TCP, TCP_INFO, &tcpi_state, &info_len), 0) |
| << strerror(errno); |
| ASSERT_EQ(info_len, sizeof(tcpi_state)); |
| } |
| |
| ASSERT_EQ(close(fd.release()), 0) << strerror(errno); |
| } |
| |
| TEST(NetStreamTest, GetSocketAcceptConn) { |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| int got = -1; |
| socklen_t got_len = sizeof(got); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &got_len), 0) << strerror(errno); |
| EXPECT_EQ(got_len, sizeof(got)); |
| EXPECT_EQ(got, 0); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_ANY), |
| }; |
| ASSERT_EQ(bind(fd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| got = -1; |
| got_len = sizeof(got); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &got_len), 0) << strerror(errno); |
| EXPECT_EQ(got_len, sizeof(got)); |
| EXPECT_EQ(got, 0); |
| |
| ASSERT_EQ(listen(fd.get(), 0), 0) << strerror(errno); |
| got = -1; |
| got_len = sizeof(got); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &got_len), 0) << strerror(errno); |
| EXPECT_EQ(got_len, sizeof(got)); |
| EXPECT_EQ(got, 1); |
| |
| ASSERT_EQ(shutdown(fd.get(), SHUT_WR), 0) << strerror(errno); |
| // TODO(https://fxbug.dev/61714): Fix the race with shutdown and getsockopt. |
| #if !defined(__Fuchsia__) |
| got = -1; |
| got_len = sizeof(got); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &got_len), 0) << strerror(errno); |
| EXPECT_EQ(got_len, sizeof(got)); |
| EXPECT_EQ(got, 1); |
| #endif |
| |
| ASSERT_EQ(shutdown(fd.get(), SHUT_RD), 0) << strerror(errno); |
| // TODO(https://fxbug.dev/61714): Fix the race with shutdown and getsockopt. |
| #if !defined(__Fuchsia__) |
| got = -1; |
| got_len = sizeof(got); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &got_len), 0) << strerror(errno); |
| EXPECT_EQ(got_len, sizeof(got)); |
| EXPECT_EQ(got, 0); |
| #endif |
| } |
| |
| // Test socket reads on disconnected stream sockets. |
| TEST(NetStreamTest, DisconnectedRead) { |
| fbl::unique_fd socketfd; |
| ASSERT_TRUE(socketfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| struct timeval tv = { |
| // Use minimal non-zero timeout as we expect the blocking recv to return before it actually |
| // starts reading. Without the timeout, the test could deadlock on a blocking recv, when the |
| // underlying code is broken. |
| .tv_usec = 1u, |
| }; |
| EXPECT_EQ(setsockopt(socketfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), 0) |
| << strerror(errno); |
| // Test blocking socket read. |
| EXPECT_EQ(recvfrom(socketfd.get(), nullptr, 0, 0, nullptr, nullptr), -1); |
| EXPECT_EQ(errno, ENOTCONN) << strerror(errno); |
| // Test with MSG_PEEK. |
| EXPECT_EQ(recvfrom(socketfd.get(), nullptr, 0, MSG_PEEK, nullptr, nullptr), -1); |
| EXPECT_EQ(errno, ENOTCONN) << strerror(errno); |
| |
| // Test non blocking socket read. |
| int flags; |
| EXPECT_GE(flags = fcntl(socketfd.get(), F_GETFL, 0), 0) << strerror(errno); |
| EXPECT_EQ(fcntl(socketfd.get(), F_SETFL, flags | O_NONBLOCK), 0) << strerror(errno); |
| EXPECT_EQ(recvfrom(socketfd.get(), nullptr, 0, 0, nullptr, nullptr), -1); |
| EXPECT_EQ(errno, ENOTCONN) << strerror(errno); |
| // Test with MSG_PEEK. |
| EXPECT_EQ(recvfrom(socketfd.get(), nullptr, 0, MSG_PEEK, nullptr, nullptr), -1); |
| EXPECT_EQ(errno, ENOTCONN) << strerror(errno); |
| EXPECT_EQ(close(socketfd.release()), 0) << strerror(errno); |
| } |
| |
| TEST_F(NetStreamSocketsTest, Shutdown) { |
| EXPECT_EQ(shutdown(server().get(), SHUT_WR), 0) << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = client().get(), |
| .events = POLLRDHUP, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| EXPECT_GE(n, 0) << strerror(errno); |
| EXPECT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLRDHUP); |
| } |
| |
| TEST_F(NetStreamSocketsTest, ResetOnFullReceiveBufferShutdown) { |
| // Fill the receive buffer of the client socket. |
| fill_stream_send_buf(server().get(), client().get()); |
| |
| // Setting SO_LINGER to 0 and `close`ing the server socket should |
| // immediately send a TCP RST. |
| struct linger opt = { |
| .l_onoff = 1, |
| .l_linger = 0, |
| }; |
| EXPECT_EQ(setsockopt(server().get(), SOL_SOCKET, SO_LINGER, &opt, sizeof(opt)), 0) |
| << strerror(errno); |
| |
| // Close the server to trigger a TCP RST now that linger is 0. |
| EXPECT_EQ(close(server().release()), 0) << strerror(errno); |
| |
| // Wait for the RST. |
| struct pollfd pfd = { |
| .fd = client().get(), |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLHUP | POLLERR); |
| |
| // The socket is no longer connected. |
| EXPECT_EQ(shutdown(client().get(), SHUT_RD), -1); |
| EXPECT_EQ(errno, ENOTCONN) << strerror(errno); |
| |
| // Create another socket to ensure that the networking stack hasn't panicked. |
| fbl::unique_fd test_sock; |
| ASSERT_TRUE(test_sock = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| } |
| |
| // Tests that a socket which has completed SHUT_RDWR responds to incoming data with RST. |
| TEST_F(NetStreamSocketsTest, ShutdownReset) { |
| // This test is tricky. In Linux we could shutdown(SHUT_RDWR) the server socket, write() some data |
| // on the client socket, and observe the server reply with RST. The SHUT_WR would move the server |
| // socket state out of ESTABLISHED (to FIN-WAIT2 after sending FIN and receiving an ACK) and |
| // SHUT_RD would close the receiver. Only when the server socket has transitioned out of |
| // ESTABLISHED state. At this point, the server socket would respond to incoming data with RST. |
| // |
| // In Fuchsia this is more complicated because each socket is a distributed system (consisting of |
| // netstack and fdio) wherein the socket state is eventually consistent. We must take care to |
| // synchronize our actions with netstack's state as we're testing that netstack correctly sends a |
| // RST in response to data received after shutdown(SHUT_RDWR). |
| // |
| // We can manipulate and inspect state using only shutdown() and poll(), both of which operate on |
| // fdio state rather than netstack state. Combined with the fact that SHUT_RD is not observable by |
| // the peer (i.e. doesn't cause any network traffic), means we are in a pickle. |
| // |
| // On the other hand, SHUT_WR does cause a FIN to be sent, which can be observed by the peer using |
| // poll(POLLRDHUP). Note also that netstack observes SHUT_RD and SHUT_WR on different threads, |
| // meaning that a race condition still exists. At the time of writing, this is the best we can do. |
| |
| // Change internal state to disallow further reads and writes. The state change propagates to |
| // netstack at some future time. We have no way to observe that SHUT_RD has propagated (because it |
| // propagates independently from SHUT_WR). |
| ASSERT_EQ(shutdown(server().get(), SHUT_RDWR), 0) << strerror(errno); |
| |
| // Wait for the FIN to arrive at the client and for the state to propagate to the client's fdio. |
| { |
| struct pollfd pfd = { |
| .fd = client().get(), |
| .events = POLLRDHUP, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLRDHUP); |
| } |
| |
| // Send data from the client(). The server should now very likely be in SHUT_RD and respond with |
| // RST. |
| char c; |
| ASSERT_EQ(write(client().get(), &c, sizeof(c)), ssize_t(sizeof(c))) << strerror(errno); |
| |
| // Wait for the client to receive the RST and for the state to propagate through its fdio. |
| struct pollfd pfd = { |
| .fd = client().get(), |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, 1); |
| EXPECT_EQ(pfd.revents, POLLHUP | POLLERR); |
| } |
| |
| // ShutdownPendingWrite tests for all of the application writes that |
| // occurred before shutdown SHUT_WR, to be received by the remote. |
| TEST_F(NetStreamSocketsTest, ShutdownPendingWrite) { |
| // Fill the send buffer of the server socket so that we have some |
| // pending data waiting to be sent out to the remote. |
| ssize_t wrote = fill_stream_send_buf(server().get(), client().get()); |
| |
| // SHUT_WR should enqueue a FIN after all of the application writes. |
| EXPECT_EQ(shutdown(server().get(), SHUT_WR), 0) << strerror(errno); |
| |
| // All client reads are expected to return here, including the last |
| // read on receiving a FIN. Keeping a timeout for unexpected failures. |
| struct timeval tv = { |
| .tv_sec = kTimeout, |
| }; |
| EXPECT_EQ(setsockopt(client().get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), 0) |
| << strerror(errno); |
| |
| ssize_t rcvd = 0; |
| ssize_t ret; |
| // Keep a large enough buffer to reduce the number of read calls, as |
| // we expect the receive buffer to be filled up at this point. |
| char buf[4096]; |
| // Each read would make room for the server to send out more data |
| // that has been enqueued from successful server socket writes. |
| while ((ret = read(client().get(), &buf, sizeof(buf))) > 0) { |
| rcvd += ret; |
| } |
| // Expect the last read to return 0 after the stack sees a FIN. |
| EXPECT_EQ(ret, 0) << strerror(errno); |
| // Expect no data drops and all written data by server is received |
| // by the client(). |
| EXPECT_EQ(rcvd, wrote); |
| } |
| |
| enum class CloseTarget { |
| CLIENT, |
| SERVER, |
| }; |
| |
| constexpr const char* CloseTargetToString(const CloseTarget s) { |
| switch (s) { |
| case CloseTarget::CLIENT: |
| return "Client"; |
| case CloseTarget::SERVER: |
| return "Server"; |
| } |
| } |
| |
| using blockedIOParams = std::tuple<IOMethod, CloseTarget, bool>; |
| |
| class BlockedIOTest : public NetStreamSocketsTest, |
| public ::testing::WithParamInterface<blockedIOParams> {}; |
| |
| TEST_P(BlockedIOTest, CloseWhileBlocked) { |
| auto const& [ioMethod, closeTarget, lingerEnabled] = GetParam(); |
| |
| bool isWrite = ioMethod.isWrite(); |
| |
| #if defined(__Fuchsia__) |
| if (isWrite) { |
| GTEST_SKIP() << "TODO(https://fxbug.dev/60337): Enable socket write methods after we are able " |
| "to deterministically block on socket writes."; |
| } |
| #endif |
| |
| // If linger is enabled, closing the socket will cause a TCP RST (by definition). |
| bool closeRST = lingerEnabled; |
| if (isWrite) { |
| // Fill the send buffer of the client socket to cause write to block. |
| fill_stream_send_buf(client().get(), server().get()); |
| // Buffes are full. Closing the socket will now cause a TCP RST. |
| closeRST = true; |
| } |
| |
| // While blocked in I/O, close the peer. |
| std::latch fut_started(1); |
| // NB: lambdas are not allowed to capture reference to local binding declared |
| // in enclosing function. |
| const auto fut = std::async(std::launch::async, [&, op = ioMethod]() { |
| fut_started.count_down(); |
| char c; |
| if (closeRST) { |
| ASSERT_EQ(op.executeIO(client().get(), &c, sizeof(c)), -1); |
| EXPECT_EQ(errno, ECONNRESET) << strerror(errno); |
| } else { |
| ASSERT_EQ(op.executeIO(client().get(), &c, sizeof(c)), 0) << strerror(errno); |
| } |
| }); |
| fut_started.wait(); |
| // Give the asynchronous blocking operation some time to reach the blocking state. Clocks |
| // sometimes jump in infrastructure, which may cause a single wait to trip sooner than expected, |
| // without the asynchronous task getting a meaningful shot at running. We protect against that by |
| // splitting the wait into multiple calls as an attempt to guarantee that clock jumps are not what |
| // causes the wait below to continue prematurely. |
| for (int i = 0; i < 50; i++) { |
| EXPECT_EQ(fut.wait_for(std::chrono::milliseconds(1)), std::future_status::timeout); |
| } |
| |
| // When enabled, causes `close` to send a TCP RST. |
| struct linger opt = { |
| .l_onoff = lingerEnabled, |
| .l_linger = 0, |
| }; |
| |
| switch (closeTarget) { |
| case CloseTarget::CLIENT: { |
| ASSERT_EQ(setsockopt(client().get(), SOL_SOCKET, SO_LINGER, &opt, sizeof(opt)), 0) |
| << strerror(errno); |
| |
| int fd = client().release(); |
| |
| ASSERT_EQ(close(fd), 0) << strerror(errno); |
| |
| // Closing the file descriptor does not interrupt the pending I/O. |
| ASSERT_EQ(fut.wait_for(std::chrono::milliseconds(10)), std::future_status::timeout); |
| |
| // The pending I/O is still blocked, but the file descriptor is gone. |
| ASSERT_EQ(fsync(fd), -1) << strerror(errno); |
| ASSERT_EQ(errno, EBADF) << errno; |
| |
| // Fallthrough to unblock the future. |
| } |
| case CloseTarget::SERVER: { |
| ASSERT_EQ(setsockopt(server().get(), SOL_SOCKET, SO_LINGER, &opt, sizeof(opt)), 0) |
| << strerror(errno); |
| ASSERT_EQ(close(server().release()), 0) << strerror(errno); |
| break; |
| } |
| } |
| ASSERT_EQ(fut.wait_for(std::chrono::milliseconds(kTimeout)), std::future_status::ready); |
| |
| #if !defined(__Fuchsia__) |
| auto undo = disableSIGPIPE(isWrite); |
| #endif |
| |
| char c; |
| switch (closeTarget) { |
| case CloseTarget::CLIENT: { |
| ASSERT_EQ(ioMethod.executeIO(client().get(), &c, sizeof(c)), -1); |
| EXPECT_EQ(errno, EBADF) << strerror(errno); |
| break; |
| } |
| case CloseTarget::SERVER: { |
| if (isWrite) { |
| ASSERT_EQ(ioMethod.executeIO(client().get(), &c, sizeof(c)), -1); |
| EXPECT_EQ(errno, EPIPE) << strerror(errno); |
| } else { |
| ASSERT_EQ(ioMethod.executeIO(client().get(), &c, sizeof(c)), 0) << strerror(errno); |
| } |
| break; |
| } |
| } |
| } |
| |
| std::string blockedIOParamsToString(const ::testing::TestParamInfo<blockedIOParams> info) { |
| // NB: this is a freestanding function because structured binding declarations are not allowed in |
| // lambdas. |
| auto const& [ioMethod, closeTarget, lingerEnabled] = info.param; |
| std::stringstream s; |
| s << "close" << CloseTargetToString(closeTarget) << "Linger"; |
| if (lingerEnabled) { |
| s << "Foreground"; |
| } else { |
| s << "Background"; |
| } |
| s << "During" << ioMethod.IOMethodToString(); |
| |
| return s.str(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| NetStreamTest, BlockedIOTest, |
| ::testing::Combine(::testing::Values(IOMethod::Op::READ, IOMethod::Op::READV, |
| IOMethod::Op::RECV, IOMethod::Op::RECVFROM, |
| IOMethod::Op::RECVMSG, IOMethod::Op::WRITE, |
| IOMethod::Op::WRITEV, IOMethod::Op::SEND, |
| IOMethod::Op::SENDTO, IOMethod::Op::SENDMSG), |
| ::testing::Values(CloseTarget::CLIENT, CloseTarget::SERVER), |
| ::testing::Values(false, true)), |
| blockedIOParamsToString); |
| |
| // Use this routine to test blocking socket reads. On failure, this attempts to recover the blocked |
| // thread. |
| // Return value: |
| // (1) actual length of read data on successful recv |
| // (2) 0, when we abort a blocked recv |
| // (3) -1, on failure of both of the above operations. |
| ssize_t asyncSocketRead(int recvfd, int sendfd, char* buf, ssize_t len, int flags, |
| struct sockaddr_in* addr, const socklen_t* addrlen, int socketType, |
| std::chrono::duration<double> timeout) { |
| std::future<ssize_t> recv = std::async(std::launch::async, [recvfd, buf, len, flags]() { |
| memset(buf, 0xde, len); |
| return recvfrom(recvfd, buf, len, flags, nullptr, nullptr); |
| }); |
| |
| if (recv.wait_for(timeout) == std::future_status::ready) { |
| return recv.get(); |
| } |
| |
| // recover the blocked receiver thread |
| switch (socketType) { |
| case SOCK_STREAM: { |
| // shutdown() would unblock the receiver thread with recv returning 0. |
| EXPECT_EQ(shutdown(recvfd, SHUT_RD), 0) << strerror(errno); |
| // We do not use 'timeout' because that maybe short here. We expect to succeed and hence use a |
| // known large timeout to ensure the test does not hang in case underlying code is broken. |
| EXPECT_EQ(recv.wait_for(std::chrono::milliseconds(kTimeout)), std::future_status::ready); |
| EXPECT_EQ(recv.get(), 0); |
| break; |
| } |
| case SOCK_DGRAM: { |
| // Send a 0 length payload to unblock the receiver. |
| // This would ensure that the async-task deterministically exits before call to future`s |
| // destructor. Calling close(.release()) on recvfd when the async task is blocked on recv(), |
| // __does_not__ cause recv to return; this can result in undefined behavior, as the descriptor |
| // can get reused. Instead of sending a valid packet to unblock the recv() task, we could call |
| // shutdown(), but that returns ENOTCONN (unconnected) but still causing recv() to return. |
| // shutdown() becomes unreliable for unconnected UDP sockets because, irrespective of the |
| // effect of calling this call, it returns error. |
| EXPECT_EQ(sendto(sendfd, nullptr, 0, 0, reinterpret_cast<struct sockaddr*>(addr), *addrlen), |
| 0) |
| << strerror(errno); |
| // We use a known large timeout for the same reason as for the above case. |
| EXPECT_EQ(recv.wait_for(std::chrono::milliseconds(kTimeout)), std::future_status::ready); |
| EXPECT_EQ(recv.get(), 0); |
| break; |
| } |
| default: { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| class DatagramSendTest : public ::testing::TestWithParam<IOMethod> {}; |
| |
| TEST_P(DatagramSendTest, SendToIPv4MappedIPv6FromIPv4) { |
| auto ioMethod = GetParam(); |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| ASSERT_EQ(bind(fd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| struct sockaddr_in6 addr6 = { |
| .sin6_family = AF_INET6, |
| .sin6_port = addr.sin_port, |
| }; |
| addr6.sin6_addr.s6_addr[10] = 0xff; |
| addr6.sin6_addr.s6_addr[11] = 0xff; |
| memcpy(&addr6.sin6_addr.s6_addr[12], &addr.sin_addr.s_addr, sizeof(addr.sin_addr.s_addr)); |
| |
| char buf[INET6_ADDRSTRLEN]; |
| ASSERT_TRUE(IN6_IS_ADDR_V4MAPPED(&addr6.sin6_addr)) |
| << inet_ntop(addr6.sin6_family, &addr6.sin6_addr, buf, sizeof(buf)); |
| |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::SENDTO: { |
| ASSERT_EQ(sendto(fd.get(), nullptr, 0, 0, reinterpret_cast<const struct sockaddr*>(&addr6), |
| sizeof(addr6)), |
| -1); |
| ASSERT_EQ(errno, EAFNOSUPPORT) << strerror(errno); |
| break; |
| } |
| case IOMethod::Op::SENDMSG: { |
| struct 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 ioMethod = GetParam(); |
| fbl::unique_fd recvfd; |
| ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| EXPECT_EQ(bind(recvfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| EXPECT_EQ(getsockname(recvfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(addrlen, sizeof(addr)); |
| |
| std::string msg = "hello"; |
| char recvbuf[32] = {}; |
| struct iovec iov = { |
| .iov_base = msg.data(), |
| .iov_len = msg.size(), |
| }; |
| struct 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 (ioMethod.Op()) { |
| case IOMethod::Op::SENDTO: { |
| EXPECT_EQ(sendto(sendfd.get(), msg.data(), msg.size(), 0, |
| reinterpret_cast<struct 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 expect_success_timeout = std::chrono::milliseconds(kTimeout); |
| auto start = std::chrono::steady_clock::now(); |
| EXPECT_EQ(asyncSocketRead(recvfd.get(), sendfd.get(), recvbuf, sizeof(recvbuf), 0, &addr, |
| &addrlen, SOCK_DGRAM, expect_success_timeout), |
| ssize_t(msg.size())); |
| auto success_rcv_duration = std::chrono::steady_clock::now() - start; |
| EXPECT_EQ(std::string(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<struct sockaddr*>(&addr), addrlen), 0) |
| << strerror(errno); |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::SENDTO: { |
| EXPECT_EQ(sendto(sendfd.get(), msg.data(), msg.size(), 0, |
| reinterpret_cast<struct 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, &addr, |
| &addrlen, SOCK_DGRAM, expect_success_timeout), |
| ssize_t(msg.size())); |
| EXPECT_EQ(std::string(recvbuf, msg.size()), msg); |
| |
| // Test sending to an address that is different from what we're connected to. |
| addr.sin_port = htons(ntohs(addr.sin_port) + 1); |
| switch (ioMethod.Op()) { |
| case IOMethod::Op::SENDTO: { |
| EXPECT_EQ(sendto(sendfd.get(), msg.data(), msg.size(), 0, |
| reinterpret_cast<struct 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 = htons(ntohs(addr.sin_port) - 1); |
| // 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, &addr, |
| &addrlen, SOCK_DGRAM, 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); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<struct 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<struct sockaddr*>(&addr), addrlen), 0) |
| << strerror(errno); |
| ASSERT_EQ(write(sendfd.get(), msg, sizeof(msg)), ssize_t(sizeof(msg))) << strerror(errno); |
| ASSERT_EQ(close(sendfd.release()), 0) << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = recvfd.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| 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); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| |
| const char kTestMsg[] = "hello"; |
| const int kTestMsgSize = sizeof(kTestMsg); |
| |
| fbl::unique_fd sendfd; |
| ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno); |
| ASSERT_EQ(kTestMsgSize, sendto(sendfd.get(), kTestMsg, kTestMsgSize, 0, |
| reinterpret_cast<sockaddr*>(&addr), addrlen)); |
| |
| char recv_buf[kTestMsgSize]; |
| |
| // Read only first 2 bytes of the message. recv() is expected to discard the |
| // rest. |
| const int kPartialReadSize = 2; |
| |
| struct iovec iov = { |
| .iov_base = recv_buf, |
| .iov_len = kPartialReadSize, |
| }; |
| struct msghdr msg = { |
| .msg_iov = &iov, |
| .msg_iovlen = 1, |
| }; |
| |
| ASSERT_EQ(recvmsg(recvfd.get(), &msg, 0), kPartialReadSize); |
| ASSERT_EQ(std::string(kTestMsg, kPartialReadSize), std::string(recv_buf, kPartialReadSize)); |
| EXPECT_EQ(MSG_TRUNC, msg.msg_flags); |
| |
| // Send the second packet. |
| ASSERT_EQ(kTestMsgSize, sendto(sendfd.get(), kTestMsg, kTestMsgSize, 0, |
| reinterpret_cast<sockaddr*>(&addr), addrlen)); |
| |
| // Read the whole packet now. |
| recv_buf[0] = 0; |
| iov.iov_len = sizeof(recv_buf); |
| ASSERT_EQ(recvmsg(recvfd.get(), &msg, 0), kTestMsgSize); |
| ASSERT_EQ(std::string(kTestMsg, kTestMsgSize), std::string(recv_buf, kTestMsgSize)); |
| EXPECT_EQ(msg.msg_flags, 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); |
| |
| struct pollfd pfd = { |
| .fd = fd.get(), |
| .events = POLLOUT, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| 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); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| |
| ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<struct 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<struct sockaddr*>(&addr), addrlen), |
| ssize_t(sizeof(msg))) |
| << strerror(errno); |
| |
| char buf[sizeof(msg) + 1] = {}; |
| |
| struct sockaddr_in peer; |
| socklen_t peerlen = sizeof(peer); |
| ASSERT_EQ(recvfrom(recvfd.get(), buf, sizeof(buf), 0, reinterpret_cast<struct 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<struct sockaddr*>(&peer), peerlen), |
| ssize_t(sizeof(msg))) |
| << strerror(errno); |
| |
| ASSERT_EQ(recvfrom(sendfd.get(), buf, sizeof(buf), 0, reinterpret_cast<struct 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); |
| |
| ASSERT_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); |
| |
| struct sockaddr_in6 addr = { |
| .sin6_family = AF_INET6, |
| .sin6_addr = IN6ADDR_LOOPBACK_INIT, |
| }; |
| |
| ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| socklen_t addrlen = sizeof(addr); |
| ASSERT_EQ(getsockname(recvfd.get(), reinterpret_cast<struct 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<struct sockaddr*>(&addr), addrlen), |
| ssize_t(sizeof(msg))) |
| << strerror(errno); |
| |
| char buf[sizeof(msg) + 1] = {}; |
| |
| struct sockaddr_in6 peer; |
| socklen_t peerlen = sizeof(peer); |
| ASSERT_EQ(recvfrom(recvfd.get(), buf, sizeof(buf), 0, reinterpret_cast<struct 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<struct sockaddr*>(&peer), peerlen), |
| ssize_t(sizeof(msg))) |
| << strerror(errno); |
| |
| ASSERT_EQ(recvfrom(sendfd.get(), buf, sizeof(buf), 0, reinterpret_cast<struct 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); |
| |
| ASSERT_EQ(close(sendfd.release()), 0) << strerror(errno); |
| |
| EXPECT_EQ(close(recvfd.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); |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_UNSPEC, |
| }; |
| |
| EXPECT_EQ(connect(fd.get(), reinterpret_cast<const struct sockaddr*>(&addr), |
| offsetof(sockaddr_in, sin_family) + sizeof(addr.sin_family)), |
| 0) |
| << strerror(errno); |
| ASSERT_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); |
| |
| struct sockaddr_in6 addr = { |
| .sin6_family = AF_UNSPEC, |
| }; |
| |
| EXPECT_EQ(connect(fd.get(), reinterpret_cast<const struct sockaddr*>(&addr), |
| offsetof(sockaddr_in6, sin6_family) + sizeof(addr.sin6_family)), |
| 0) |
| << strerror(errno); |
| ASSERT_EQ(close(fd.release()), 0) << strerror(errno); |
| } |
| |
| // Note: we choose 100 because the max number of fds per process is limited to |
| // 256. |
| const int32_t kListeningSockets = 100; |
| |
| TEST(NetStreamTest, MultipleListeningSockets) { |
| fbl::unique_fd listenfds[kListeningSockets]; |
| fbl::unique_fd connfd[kListeningSockets]; |
| |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| socklen_t addrlen = sizeof(addr); |
| |
| for (auto& listenfd : listenfds) { |
| ASSERT_TRUE(listenfd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| ASSERT_EQ(bind(listenfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| 0) |
| << strerror(errno); |
| |
| ASSERT_EQ(listen(listenfd.get(), 0), 0) << strerror(errno); |
| } |
| |
| for (int i = 0; i < kListeningSockets; i++) { |
| ASSERT_EQ(getsockname(listenfds[i].get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), |
| 0) |
| << strerror(errno); |
| ASSERT_EQ(addrlen, sizeof(addr)); |
| |
| ASSERT_TRUE(connfd[i] = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno); |
| |
| ASSERT_EQ( |
| connect(connfd[i].get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| } |
| |
| for (int i = 0; i < kListeningSockets; i++) { |
| ASSERT_EQ(0, close(connfd[i].release())); |
| ASSERT_EQ(0, close(listenfds[i].release())); |
| } |
| } |
| |
| // Socket tests across multiple socket-types, SOCK_DGRAM, SOCK_STREAM. |
| class NetSocketTest : public ::testing::TestWithParam<int> {}; |
| |
| // Test MSG_PEEK |
| // MSG_PEEK : Peek into the socket receive queue without moving the contents from it. |
| // |
| // TODO(fxbug.dev/33100): change this test to use recvmsg instead of recvfrom to exercise MSG_PEEK |
| // with scatter/gather. |
| TEST_P(NetSocketTest, SocketPeekTest) { |
| int socketType = GetParam(); |
| struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr.s_addr = htonl(INADDR_LOOPBACK), |
| }; |
| socklen_t addrlen = sizeof(addr); |
| fbl::unique_fd sendfd; |
| fbl::unique_fd recvfd; |
| ssize_t expectReadLen = 0; |
| char sendbuf[8] = {}; |
| char recvbuf[2 * sizeof(sendbuf)] = {}; |
| ssize_t sendlen = sizeof(sendbuf); |
| |
| ASSERT_TRUE(sendfd = fbl::unique_fd(socket(AF_INET, socketType, 0))) << strerror(errno); |
| // Setup the sender and receiver sockets. |
| switch (socketType) { |
| case SOCK_STREAM: { |
| fbl::unique_fd acptfd; |
| ASSERT_TRUE(acptfd = fbl::unique_fd(socket(AF_INET, socketType, 0))) << strerror(errno); |
| EXPECT_EQ(bind(acptfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| 0) |
| << strerror(errno); |
| EXPECT_EQ(getsockname(acptfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(addrlen, sizeof(addr)); |
| EXPECT_EQ(listen(acptfd.get(), 0), 0) << strerror(errno); |
| EXPECT_EQ( |
| connect(sendfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| ASSERT_TRUE(recvfd = fbl::unique_fd(accept(acptfd.get(), nullptr, nullptr))) |
| << strerror(errno); |
| EXPECT_EQ(close(acptfd.release()), 0) << strerror(errno); |
| // Expect to read both the packets in a single recv() call. |
| expectReadLen = sizeof(recvbuf); |
| break; |
| } |
| case SOCK_DGRAM: { |
| ASSERT_TRUE(recvfd = fbl::unique_fd(socket(AF_INET, socketType, 0))) << strerror(errno); |
| EXPECT_EQ(bind(recvfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| 0) |
| << strerror(errno); |
| EXPECT_EQ(getsockname(recvfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0) |
| << strerror(errno); |
| EXPECT_EQ(addrlen, sizeof(addr)); |
| // Expect to read single packet per recv() call. |
| expectReadLen = sizeof(sendbuf); |
| break; |
| } |
| default: { |
| FAIL() << "unexpected test variant"; |
| } |
| } |
| |
| // This test sends 2 packets with known values and validates MSG_PEEK across the 2 packets. |
| sendbuf[0] = 0x56; |
| sendbuf[6] = 0x78; |
| |
| // send 2 separate packets and test peeking across |
| EXPECT_EQ(sendto(sendfd.get(), sendbuf, sizeof(sendbuf), 0, |
| reinterpret_cast<const struct sockaddr*>(&addr), addrlen), |
| sendlen) |
| << strerror(errno); |
| EXPECT_EQ(sendto(sendfd.get(), sendbuf, sizeof(sendbuf), 0, |
| reinterpret_cast<const struct sockaddr*>(&addr), addrlen), |
| sendlen) |
| << strerror(errno); |
| |
| auto expect_success_timeout = std::chrono::milliseconds(kTimeout); |
| auto start = std::chrono::steady_clock::now(); |
| // First peek on first byte. |
| EXPECT_EQ(asyncSocketRead(recvfd.get(), sendfd.get(), recvbuf, 1, MSG_PEEK, &addr, &addrlen, |
| socketType, expect_success_timeout), |
| 1); |
| auto success_rcv_duration = std::chrono::steady_clock::now() - start; |
| EXPECT_EQ(recvbuf[0], sendbuf[0]); |
| |
| // Second peek across first 2 packets and drain them from the socket receive queue. |
| ssize_t torecv = sizeof(recvbuf); |
| for (int i = 0; torecv > 0; i++) { |
| int flags = i % 2 ? 0 : MSG_PEEK; |
| ssize_t readLen = 0; |
| // Retry socket read with MSG_PEEK to ensure all of the expected data is received. |
| // |
| // TODO(https://fxbug.dev/74639) : Use SO_RCVLOWAT instead of retry. |
| do { |
| readLen = asyncSocketRead(recvfd.get(), sendfd.get(), recvbuf, sizeof(recvbuf), flags, &addr, |
| &addrlen, socketType, expect_success_timeout); |
| if (HasFailure()) { |
| break; |
| } |
| } while (flags == MSG_PEEK && readLen < expectReadLen); |
| EXPECT_EQ(readLen, expectReadLen); |
| |
| EXPECT_EQ(recvbuf[0], sendbuf[0]); |
| EXPECT_EQ(recvbuf[6], sendbuf[6]); |
| // For SOCK_STREAM, we validate peek across 2 packets with a single recv call. |
| if (readLen == sizeof(recvbuf)) { |
| EXPECT_EQ(recvbuf[8], sendbuf[0]); |
| EXPECT_EQ(recvbuf[14], sendbuf[6]); |
| } |
| if (flags != MSG_PEEK) { |
| torecv -= readLen; |
| } |
| } |
| |
| // Third peek on empty socket receive buffer, expect failure. |
| // |
| // 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, 1, MSG_PEEK, &addr, &addrlen, |
| socketType, success_rcv_duration * 10), |
| 0); |
| EXPECT_EQ(close(recvfd.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NetSocket, NetSocketTest, ::testing::Values(SOCK_DGRAM, SOCK_STREAM)); |
| |
| TEST_P(SocketKindTest, IoctlInterfaceLookupRoundTrip) { |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = NewSocket()) << strerror(errno); |
| |
| // This test assumes index 1 is bound to a valid interface. In Fuchsia's test environment (the |
| // component executing this test), 1 is always bound to "lo". |
| struct ifreq ifr_iton = { |
| .ifr_ifindex = 1, |
| }; |
| // Set ifr_name to random chars to test ioctl correctly sets null terminator. |
| memset(ifr_iton.ifr_name, 0xde, IFNAMSIZ); |
| ASSERT_EQ(strnlen(ifr_iton.ifr_name, IFNAMSIZ), (size_t)IFNAMSIZ); |
| ASSERT_EQ(ioctl(fd.get(), SIOCGIFNAME, &ifr_iton), 0) << strerror(errno); |
| ASSERT_LT(strnlen(ifr_iton.ifr_name, IFNAMSIZ), (size_t)IFNAMSIZ); |
| |
| struct ifreq ifr_ntoi; |
| strncpy(ifr_ntoi.ifr_name, ifr_iton.ifr_name, IFNAMSIZ); |
| ASSERT_EQ(ioctl(fd.get(), SIOCGIFINDEX, &ifr_ntoi), 0) << strerror(errno); |
| EXPECT_EQ(ifr_ntoi.ifr_ifindex, 1); |
| |
| struct ifreq ifr_err; |
| memset(ifr_err.ifr_name, 0xde, IFNAMSIZ); |
| // Although the first few bytes of ifr_name contain the correct name, there is no null terminator |
| // and the remaining bytes are gibberish, should match no interfaces. |
| memcpy(ifr_err.ifr_name, ifr_iton.ifr_name, strnlen(ifr_iton.ifr_name, IFNAMSIZ)); |
| |
| struct ioctl_request { |
| std::string name; |
| uint64_t request; |
| }; |
| const ioctl_request requests[] = { |
| { |
| .name = "SIOCGIFINDEX", |
| .request = SIOCGIFINDEX, |
| }, |
| { |
| .name = "SIOCGIFFLAGS", |
| .request = SIOCGIFFLAGS, |
| }, |
| }; |
| for (const auto& request : requests) { |
| ASSERT_EQ(ioctl(fd.get(), request.request, &ifr_err), -1) << request.name; |
| EXPECT_EQ(errno, ENODEV) << request.name << ": " << strerror(errno); |
| } |
| } |
| |
| TEST_P(SocketKindTest, IoctlInterfaceNotFound) { |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = NewSocket()) << strerror(errno); |
| |
| // Invalid ifindex "-1" should match no interfaces. |
| struct ifreq ifr_iton = { |
| .ifr_ifindex = -1, |
| }; |
| ASSERT_EQ(ioctl(fd.get(), SIOCGIFNAME, &ifr_iton), -1); |
| EXPECT_EQ(errno, ENODEV) << strerror(errno); |
| |
| // Empty name should match no interface. |
| struct ifreq ifr = { |
| .ifr_name = 0, |
| }; |
| struct ioctl_request { |
| std::string name; |
| uint64_t request; |
| }; |
| const ioctl_request requests[] = { |
| { |
| .name = "SIOCGIFINDEX", |
| .request = SIOCGIFINDEX, |
| }, |
| { |
| .name = "SIOCGIFFLAGS", |
| .request = SIOCGIFFLAGS, |
| }, |
| }; |
| for (const auto& request : requests) { |
| ASSERT_EQ(ioctl(fd.get(), request.request, &ifr), -1) << request.name; |
| EXPECT_EQ(errno, ENODEV) << request.name << ": " << strerror(errno); |
| } |
| } |
| |
| TEST(SocketKindTest, IoctlLookupForNonSocketFd) { |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(open("/", O_RDONLY | O_DIRECTORY))) << strerror(errno); |
| |
| struct ifreq ifr_iton = { |
| .ifr_ifindex = 1, |
| }; |
| ASSERT_EQ(ioctl(fd.get(), SIOCGIFNAME, &ifr_iton), -1); |
| EXPECT_EQ(errno, ENOTTY) << strerror(errno); |
| |
| struct ifreq ifr; |
| strcpy(ifr.ifr_name, "loblah"); |
| struct ioctl_request { |
| std::string name; |
| uint64_t request; |
| }; |
| const ioctl_request requests[] = { |
| { |
| .name = "SIOCGIFINDEX", |
| .request = SIOCGIFINDEX, |
| }, |
| { |
| .name = "SIOCGIFFLAGS", |
| .request = SIOCGIFFLAGS, |
| }, |
| }; |
| for (const auto& request : requests) { |
| ASSERT_EQ(ioctl(fd.get(), request.request, &ifr), -1) << request.name; |
| EXPECT_EQ(errno, ENOTTY) << request.name << ": " << strerror(errno); |
| } |
| } |
| |
| TEST(IoctlTest, IoctlGetInterfaceFlags) { |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno); |
| |
| struct ifreq ifr_ntof = { |
| .ifr_name = "lo", |
| }; |
| ASSERT_EQ(ioctl(fd.get(), SIOCGIFFLAGS, &ifr_ntof), 0) << strerror(errno); |
| struct expected_flag { |
| std::string name; |
| uint16_t bitmask; |
| bool value; |
| }; |
| const expected_flag 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 defined(__Fuchsia__) |
| 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); |
| #endif |
| } |
| |
| 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. |
| const char FILLER = 0xa; |
| struct ifreq ifr; |
| memset(&ifr, FILLER, sizeof(ifr)); |
| struct ifconf ifc = { |
| .ifc_len = sizeof(ifr) - 1, |
| .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], FILLER) << i; |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NetSocket, SocketKindTest, |
| ::testing::Combine(::testing::Values(AF_INET, AF_INET6), |
| ::testing::Values(SOCK_DGRAM, SOCK_STREAM)), |
| socketKindToString); |
| |
| using DomainProtocol = std::tuple<int, int>; |
| class IcmpSocketTest : public ::testing::TestWithParam<DomainProtocol> {}; |
| |
| TEST_P(IcmpSocketTest, GetSockoptSoProtocol) { |
| #if !defined(__Fuchsia__) |
| if (!IsRoot()) { |
| GTEST_SKIP() << "This test requires root"; |
| } |
| #endif |
| auto const& [domain, protocol] = GetParam(); |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(domain, SOCK_DGRAM, protocol))) << strerror(errno); |
| |
| int opt; |
| socklen_t optlen = sizeof(opt); |
| EXPECT_EQ(getsockopt(fd.get(), SOL_SOCKET, SO_PROTOCOL, &opt, &optlen), 0) << strerror(errno); |
| EXPECT_EQ(optlen, sizeof(opt)); |
| EXPECT_EQ(opt, protocol); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(NetSocket, IcmpSocketTest, |
| ::testing::Values(std::make_pair(AF_INET, IPPROTO_ICMP), |
| std::make_pair(AF_INET6, IPPROTO_ICMPV6))); |
| |
| 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 |
| struct 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); |
| struct sockaddr_in rcv_addr = { |
| .sin_family = AF_INET, |
| .sin_addr = loopback_sin_addr, |
| }; |
| ASSERT_EQ(bind(recvfd.get(), reinterpret_cast<const struct 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<struct 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); |
| struct 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<struct sockaddr*>(&sendto_addr), sizeof(sendto_addr)), |
| ssize_t(sizeof(msg))) |
| << "sendtoaddr=" << loopback_addrstr << ": " << strerror(errno); |
| EXPECT_EQ(close(sendfd.release()), 0) << strerror(errno); |
| |
| struct pollfd pfd = { |
| .fd = recvfd.get(), |
| .events = POLLIN, |
| }; |
| int n = poll(&pfd, 1, kTimeout); |
| 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); |
| } |
| } |
| } |
| } |
| |
| } // namespace |