| // Copyright 2019 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef SRC_CONNECTIVITY_NETWORK_TESTS_SYSCALL_UTIL_H_ | |
| #define SRC_CONNECTIVITY_NETWORK_TESTS_SYSCALL_UTIL_H_ | |
| #include <lib/fit/defer.h> | |
| #include <netinet/ip.h> | |
| #include <poll.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/uio.h> | |
| #include <chrono> | |
| #include <functional> | |
| #include <future> | |
| #include <random> | |
| #include <fbl/unique_fd.h> | |
| #if defined(__linux__) | |
| #include <sys/syscall.h> | |
| #include <linux/capability.h> | |
| #endif | |
| // Timeout when waiting for something that's expected to occur. | |
| // | |
| // The larger this is, the less likely flakes are to occur. Assuming there | |
| // aren't any bugs, the timeout should never be reached. | |
| inline constexpr std::chrono::duration kPositiveCheckTimeout = std::chrono::seconds(120); | |
| // Timeout when waiting for something that's expected to time out. | |
| // | |
| // Making this small saves time in tests that are expected to fail, but also | |
| // make "false passes" (tests that would have failed, but spuriously passed due | |
| // to hitting the timeout) somewhat more likely. We think this is a reasonable | |
| // trade-off, given that flakes of this kind are rare. | |
| inline constexpr std::chrono::duration kNegativeCheckTimeout = std::chrono::seconds(2); | |
| // TODO(https://fxbug.dev/328778498): Remove and use the positive and negative variants. | |
| inline constexpr std::chrono::duration kDeprecatedTimeout = std::chrono::seconds(2); | |
| constexpr char kFastUdpEnvVar[] = "FAST_UDP"; | |
| struct SocketDomain { | |
| // Should only be used when switching on the return value of which(), because | |
| // enum classes don't guarantee type-safe construction. | |
| enum class Which : sa_family_t { | |
| IPv4 = AF_INET, | |
| IPv6 = AF_INET6, | |
| }; | |
| constexpr static SocketDomain IPv4() { return SocketDomain(Which::IPv4); } | |
| constexpr static SocketDomain IPv6() { return SocketDomain(Which::IPv6); } | |
| sa_family_t Get() const { return static_cast<sa_family_t>(which_); } | |
| Which which() const { return which_; } | |
| private: | |
| explicit constexpr SocketDomain(Which which) : which_(which) {} | |
| Which which_; | |
| }; | |
| struct SocketType { | |
| // Should only be used when switching on the return value of which(), because | |
| // enum classes don't guarantee type-safe construction. | |
| enum class Which : int { | |
| Stream = SOCK_STREAM, | |
| Dgram = SOCK_DGRAM, | |
| }; | |
| constexpr static SocketType Stream() { return SocketType(Which::Stream); } | |
| constexpr static SocketType Dgram() { return SocketType(Which::Dgram); } | |
| int Get() const { return static_cast<int>(which_); } | |
| Which which() const { return which_; } | |
| private: | |
| explicit constexpr SocketType(Which which) : which_(which) {} | |
| Which which_; | |
| }; | |
| struct ShutdownType { | |
| // Should only be used when switching on the return value of which(), because | |
| // enum classes don't guarantee type-safe construction. | |
| enum class Which : int { | |
| Read = SHUT_RD, | |
| Write = SHUT_WR, | |
| }; | |
| constexpr static ShutdownType Read() { return ShutdownType(Which::Read); } | |
| constexpr static ShutdownType Write() { return ShutdownType(Which::Write); } | |
| int Get() const { return static_cast<int>(which_); } | |
| Which which() const { return which_; } | |
| private: | |
| explicit constexpr ShutdownType(Which which) : which_(which) {} | |
| Which which_; | |
| }; | |
| // Returns a `sockaddr_in6` address mapped from the provided `sockaddr_in`. | |
| sockaddr_in6 MapIpv4SockaddrToIpv6Sockaddr(const sockaddr_in& addr4); | |
| #if defined(__Fuchsia__) | |
| #include <lib/zx/socket.h> | |
| #include <zircon/syscalls/object.h> | |
| // Returns the socket info associated with the provided `fd`, backed by a | |
| // `fposix_socket::StreamSocket`. | |
| void ZxSocketInfoStream(int fd, zx_info_socket_t& out_info); | |
| // Returns the socket info associated with the provided `fd`, backed by a | |
| // `fposix_socket::DatagramSocket`. | |
| void ZxSocketInfoDgram(int fd, zx_info_socket_t& out_info); | |
| // Returns the underlying zircon socket associated with the provided `fd`, | |
| // backed by a `fposix_socket::DatagramSocket`. | |
| void ZxSocketDgram(int fd, zx::socket& out_socket); | |
| #endif | |
| // Returns the Tx capacity of the provided `fd`. NOTE: On Fuchsia, this accounts for | |
| // buffer space available within kernel primitives. | |
| void TxCapacity(int fd, size_t& out_capacity); | |
| // Returns the Rx capacity of the provided `fd`. NOTE: On Fuchsia, this accounts for | |
| // buffer space available within kernel primitives. | |
| void RxCapacity(int fd, size_t& out_capacity); | |
| template <typename T> | |
| void AssertBlocked(const std::future<T>& fut) { | |
| // Give an 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 do not | |
| // impact the duration of a wait. | |
| for (int i = 0; i < 50; i++) { | |
| ASSERT_EQ(fut.wait_for(std::chrono::milliseconds(1)), std::future_status::timeout); | |
| } | |
| } | |
| // 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. | |
| fit::deferred_action<std::function<void()>> DisableSigPipe(bool is_write); | |
| // Returns a sockaddr_in holding an IPv4 loopback address with the provided port. | |
| sockaddr_in LoopbackSockaddrV4(in_port_t port); | |
| // Returns a sockaddr_in6 holding an IPv6 loopback address with the provided port. | |
| sockaddr_in6 LoopbackSockaddrV6(in_port_t port); | |
| // Fills `fd`'s send buffer and writes the number of bytes written to `out_bytes_written`. | |
| // | |
| // Assumes that `fd` was previously connected to `peer_fd`. | |
| void fill_stream_send_buf(int fd, int peer_fd, ssize_t* out_bytes_written); | |
| class VectorizedIOMethod { | |
| public: | |
| enum class Op { | |
| READV, | |
| RECVMSG, | |
| WRITEV, | |
| SENDMSG, | |
| }; | |
| explicit constexpr VectorizedIOMethod(Op op) : op_(op) {} | |
| Op Op() const { return op_; } | |
| ssize_t ExecuteIO(int fd, iovec* iovecs, size_t len) const; | |
| constexpr const char* IOMethodToString() const { | |
| switch (op_) { | |
| case Op::READV: | |
| return "Readv"; | |
| case Op::RECVMSG: | |
| return "Recvmsg"; | |
| case Op::WRITEV: | |
| return "Writev"; | |
| case Op::SENDMSG: | |
| return "Sendmsg"; | |
| } | |
| } | |
| private: | |
| const enum Op op_; | |
| }; | |
| class IOMethod { | |
| public: | |
| enum class Op { | |
| READ, | |
| READV, | |
| RECV, | |
| RECVFROM, | |
| RECVMSG, | |
| WRITE, | |
| WRITEV, | |
| SEND, | |
| SENDTO, | |
| SENDMSG, | |
| }; | |
| constexpr IOMethod(Op op) : op_(op) {} | |
| Op Op() const { return op_; } | |
| ssize_t ExecuteIO(int fd, char* buf, size_t len) const; | |
| bool isWrite() const; | |
| constexpr const char* IOMethodToString() const { | |
| switch (op_) { | |
| case Op::READ: | |
| return "Read"; | |
| case Op::READV: | |
| return VectorizedIOMethod(VectorizedIOMethod::Op::READV).IOMethodToString(); | |
| case Op::RECV: | |
| return "Recv"; | |
| case Op::RECVFROM: | |
| return "Recvfrom"; | |
| case Op::RECVMSG: | |
| return VectorizedIOMethod(VectorizedIOMethod::Op::RECVMSG).IOMethodToString(); | |
| case Op::WRITE: | |
| return "Write"; | |
| case Op::WRITEV: | |
| return VectorizedIOMethod(VectorizedIOMethod::Op::WRITEV).IOMethodToString(); | |
| case Op::SEND: | |
| return "Send"; | |
| case Op::SENDTO: | |
| return "Sendto"; | |
| case Op::SENDMSG: | |
| return VectorizedIOMethod(VectorizedIOMethod::Op::SENDMSG).IOMethodToString(); | |
| } | |
| } | |
| private: | |
| const enum Op op_; | |
| }; | |
| constexpr std::initializer_list<IOMethod> kRecvIOMethods = { | |
| IOMethod::Op::READ, IOMethod::Op::READV, IOMethod::Op::RECV, | |
| IOMethod::Op::RECVFROM, IOMethod::Op::RECVMSG, | |
| }; | |
| constexpr std::initializer_list<IOMethod> kSendIOMethods = { | |
| IOMethod::Op::WRITE, IOMethod::Op::WRITEV, IOMethod::Op::SEND, | |
| IOMethod::Op::SENDTO, IOMethod::Op::SENDMSG, | |
| }; | |
| constexpr std::initializer_list<IOMethod> kAllIOMethods = { | |
| 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, | |
| }; | |
| // Performs I/O between `fd` and `other` using `io_method` with a null buffer. | |
| void DoNullPtrIO(const fbl::unique_fd& fd, const fbl::unique_fd& other, IOMethod io_method, | |
| bool datagram); | |
| // 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, | |
| SocketType socket_type, SocketDomain socket_domain, | |
| std::chrono::duration<double> timeout); | |
| // Returns a human-readable string representing the provided domain. | |
| constexpr std::string_view socketDomainToString(const SocketDomain& domain) { | |
| switch (domain.which()) { | |
| case SocketDomain::Which::IPv4: | |
| return "IPv4"; | |
| case SocketDomain::Which::IPv6: | |
| return "IPv6"; | |
| } | |
| } | |
| // Returns a human-readable string representing the provided socket type. | |
| constexpr std::string_view socketTypeToString(const SocketType& socket_type) { | |
| switch (socket_type.which()) { | |
| case SocketType::Which::Dgram: | |
| return "Datagram"; | |
| case SocketType::Which::Stream: | |
| return "Stream"; | |
| } | |
| } | |
| // Returns a sockaddr and its length holding the loopback address for the provided | |
| // socket domain. | |
| std::pair<sockaddr_storage, socklen_t> LoopbackSockaddrAndSocklenForDomain( | |
| const SocketDomain& domain); | |
| // Returns a sockaddr and its length holding the any address for the provided | |
| // socket domain. | |
| std::pair<sockaddr_storage, socklen_t> AnySockaddrAndSocklenForDomain(const SocketDomain& domain); | |
| class SocketAddr { | |
| public: | |
| constexpr static SocketAddr IPv4Any() { | |
| const auto& [addr, addr_len] = AnySockaddrAndSocklenForDomain(SocketDomain::IPv4()); | |
| return SocketAddr{SocketDomain::IPv4(), addr, addr_len, "V4Any"}; | |
| } | |
| constexpr static SocketAddr IPv4Loopback() { | |
| const auto& [addr, addr_len] = LoopbackSockaddrAndSocklenForDomain(SocketDomain::IPv4()); | |
| return SocketAddr{SocketDomain::IPv4(), addr, addr_len, "V4Loopback"}; | |
| } | |
| constexpr static SocketAddr IPv6Any() { | |
| const auto& [addr, addr_len] = AnySockaddrAndSocklenForDomain(SocketDomain::IPv6()); | |
| return SocketAddr{SocketDomain::IPv6(), addr, addr_len, "V6Any"}; | |
| } | |
| constexpr static SocketAddr IPv6Loopback() { | |
| const auto& [addr, addr_len] = LoopbackSockaddrAndSocklenForDomain(SocketDomain::IPv6()); | |
| return SocketAddr{SocketDomain::IPv6(), addr, addr_len, "V6Loopback"}; | |
| } | |
| uint16_t GetPort() const { | |
| switch (domain.which()) { | |
| case SocketDomain::Which::IPv4: | |
| return static_cast<uint16_t>(reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); | |
| case SocketDomain::Which::IPv6: | |
| return static_cast<uint16_t>(reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); | |
| } | |
| } | |
| void SetPort(uint16_t port) { | |
| switch (domain.which()) { | |
| case SocketDomain::Which::IPv4: | |
| reinterpret_cast<sockaddr_in*>(&addr)->sin_port = port; | |
| return; | |
| case SocketDomain::Which::IPv6: | |
| reinterpret_cast<sockaddr_in6*>(&addr)->sin6_port = port; | |
| return; | |
| } | |
| } | |
| SocketDomain domain; | |
| sockaddr_storage addr; | |
| socklen_t addr_len; | |
| const char* description; | |
| }; | |
| #if defined(__linux__) | |
| #define SKIP_IF_CANT_ACCESS_RAW_SOCKETS() \ | |
| do { \ | |
| struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; \ | |
| struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; \ | |
| auto ret = syscall(SYS_capget, &header, &caps); \ | |
| ASSERT_GE(ret, 0) << strerror(errno); \ | |
| if ((caps[CAP_TO_INDEX(CAP_NET_RAW)].effective & CAP_TO_MASK(CAP_NET_RAW)) == 0) { \ | |
| GTEST_SKIP() << "Do not have CAP_NET_RAW capability"; \ | |
| } \ | |
| } while (false) | |
| #else | |
| #define SKIP_IF_CANT_ACCESS_RAW_SOCKETS() ((void*)0) | |
| #endif | |
| #endif // SRC_CONNECTIVITY_NETWORK_TESTS_SYSCALL_UTIL_H_ |