blob: 154aa4de65da5460cabc59bd928b21ed96bf9ca6 [file] [log] [blame] [edit]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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_