| // Copyright 2018 The gVisor Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "test/util/socket_util.h" |
| |
| #include <arpa/inet.h> |
| #include <netinet/in.h> |
| #include <poll.h> |
| #include <sys/socket.h> |
| |
| #include <cstddef> |
| #include <functional> |
| #include <memory> |
| #include <stack> |
| |
| #include "gtest/gtest.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/time/clock.h" |
| #include "absl/types/optional.h" |
| #include "test/util/file_descriptor.h" |
| #include "test/util/posix_error.h" |
| #include "test/util/temp_path.h" |
| #include "test/util/test_util.h" |
| #include "test/util/thread_util.h" |
| |
| namespace gvisor { |
| namespace testing { |
| |
| Creator<SocketPair> SyscallSocketPairCreator(int domain, int type, |
| int protocol) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { |
| int pair[2]; |
| RETURN_ERROR_IF_SYSCALL_FAIL(socketpair(domain, type, protocol, pair)); |
| MaybeSave(); // Save on successful creation. |
| return std::make_unique<FDSocketPair>(FileDescriptor(pair[0]), |
| FileDescriptor(pair[1])); |
| }; |
| } |
| |
| Creator<FileDescriptor> SyscallSocketCreator(int domain, int type, |
| int protocol) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<FileDescriptor>> { |
| int fd = 0; |
| RETURN_ERROR_IF_SYSCALL_FAIL(fd = socket(domain, type, protocol)); |
| MaybeSave(); // Save on successful creation. |
| return std::make_unique<FileDescriptor>(fd); |
| }; |
| } |
| |
| PosixErrorOr<struct sockaddr_un> UniqueUnixAddr(bool abstract, int domain) { |
| struct sockaddr_un addr = {}; |
| |
| #ifdef ANDROID |
| // Using NewTempAbsPath() can cause the tmp directory path to exceed the max |
| // length (i.e., sizeof(addr.sun_path)). |
| // |
| // However, existing systems that are built with the ANDROID configuration |
| // have their temp directory in a different location, and must respect the |
| // TEST_TMPDIR. |
| std::string path = NewTempAbsPath(); |
| #else |
| std::string path = NewTempAbsPathInDir("/tmp"); |
| #endif // ANDROID |
| |
| if (path.size() >= sizeof(addr.sun_path)) { |
| return PosixError(EINVAL, |
| "Unable to generate a temp path of appropriate length"); |
| } |
| |
| if (abstract) { |
| // Indicate that the path is in the abstract namespace. |
| path[0] = 0; |
| } |
| memcpy(addr.sun_path, path.c_str(), path.length()); |
| addr.sun_family = domain; |
| return addr; |
| } |
| |
| Creator<SocketPair> AcceptBindSocketPairCreator(bool abstract, int domain, |
| int type, int protocol) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { |
| ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un bind_addr, |
| UniqueUnixAddr(abstract, domain)); |
| ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un extra_addr, |
| UniqueUnixAddr(abstract, domain)); |
| |
| ASSIGN_OR_RETURN_ERRNO(auto bound, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| bind(bound.get(), AsSockAddr(&bind_addr), sizeof(bind_addr))); |
| MaybeSave(); // Successful bind. |
| RETURN_ERROR_IF_SYSCALL_FAIL(listen( |
| bound.get(), /* backlog = */ 5)); // NOLINT(bugprone-argument-comment) |
| MaybeSave(); // Successful listen. |
| |
| ASSIGN_OR_RETURN_ERRNO(auto connected, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| connect(connected.get(), AsSockAddr(&bind_addr), sizeof(bind_addr))); |
| MaybeSave(); // Successful connect. |
| |
| ASSIGN_OR_RETURN_ERRNO(auto accepted, |
| Accept4(bound.get(), nullptr, nullptr, |
| type & (SOCK_NONBLOCK | SOCK_CLOEXEC))); |
| MaybeSave(); // Successful connect. |
| |
| // Cleanup no longer needed resources. |
| RETURN_ERROR_IF_SYSCALL_FAIL(close(bound.release())); |
| MaybeSave(); // Dropped original socket. |
| |
| // Only unlink if path is not in abstract namespace. |
| if (bind_addr.sun_path[0] != 0) { |
| RETURN_ERROR_IF_SYSCALL_FAIL(unlink(bind_addr.sun_path)); |
| MaybeSave(); // Unlinked path. |
| } |
| |
| // accepted is before connected to destruct connected before accepted. |
| // Destructors for nonstatic member objects are called in the reverse order |
| // in which they appear in the class declaration. |
| return std::make_unique<AddrFDSocketPair>( |
| std::move(accepted), std::move(connected), bind_addr, extra_addr); |
| }; |
| } |
| |
| Creator<SocketPair> FilesystemAcceptBindSocketPairCreator(int domain, int type, |
| int protocol) { |
| return AcceptBindSocketPairCreator(/* abstract= */ false, domain, type, |
| protocol); |
| } |
| |
| Creator<SocketPair> AbstractAcceptBindSocketPairCreator(int domain, int type, |
| int protocol) { |
| return AcceptBindSocketPairCreator(/* abstract= */ true, domain, type, |
| protocol); |
| } |
| |
| Creator<SocketPair> BidirectionalBindSocketPairCreator(bool abstract, |
| int domain, int type, |
| int protocol) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { |
| ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr1, |
| UniqueUnixAddr(abstract, domain)); |
| ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr2, |
| UniqueUnixAddr(abstract, domain)); |
| |
| ASSIGN_OR_RETURN_ERRNO(auto sock1, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| bind(sock1.get(), AsSockAddr(&addr1), sizeof(addr1))); |
| MaybeSave(); // Successful bind. |
| |
| ASSIGN_OR_RETURN_ERRNO(auto sock2, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| bind(sock2.get(), AsSockAddr(&addr2), sizeof(addr2))); |
| MaybeSave(); // Successful bind. |
| |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| connect(sock1.get(), AsSockAddr(&addr2), sizeof(addr2))); |
| MaybeSave(); // Successful connect. |
| |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| connect(sock2.get(), AsSockAddr(&addr1), sizeof(addr1))); |
| MaybeSave(); // Successful connect. |
| |
| // Cleanup no longer needed resources. |
| |
| // Only unlink if path is not in abstract namespace. |
| if (addr1.sun_path[0] != 0) { |
| RETURN_ERROR_IF_SYSCALL_FAIL(unlink(addr1.sun_path)); |
| MaybeSave(); // Successful unlink. |
| } |
| |
| // Only unlink if path is not in abstract namespace. |
| if (addr2.sun_path[0] != 0) { |
| RETURN_ERROR_IF_SYSCALL_FAIL(unlink(addr2.sun_path)); |
| MaybeSave(); // Successful unlink. |
| } |
| |
| return std::make_unique<FDSocketPair>(std::move(sock1), std::move(sock2)); |
| }; |
| } |
| |
| Creator<SocketPair> FilesystemBidirectionalBindSocketPairCreator(int domain, |
| int type, |
| int protocol) { |
| return BidirectionalBindSocketPairCreator(/* abstract= */ false, domain, type, |
| protocol); |
| } |
| |
| Creator<SocketPair> AbstractBidirectionalBindSocketPairCreator(int domain, |
| int type, |
| int protocol) { |
| return BidirectionalBindSocketPairCreator(/* abstract= */ true, domain, type, |
| protocol); |
| } |
| |
| Creator<SocketPair> SocketpairGoferSocketPairCreator(int domain, int type, |
| int protocol) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { |
| struct sockaddr_un addr = {}; |
| constexpr char kSocketGoferPath[] = "/socket"; |
| memcpy(addr.sun_path, kSocketGoferPath, sizeof(kSocketGoferPath)); |
| addr.sun_family = domain; |
| |
| ASSIGN_OR_RETURN_ERRNO(auto sock1, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| connect(sock1.get(), AsSockAddr(&addr), sizeof(addr))); |
| MaybeSave(); // Successful connect. |
| |
| ASSIGN_OR_RETURN_ERRNO(auto sock2, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| connect(sock2.get(), AsSockAddr(&addr), sizeof(addr))); |
| MaybeSave(); // Successful connect. |
| |
| // Make and close another socketpair to ensure that the duped ends of the |
| // first socketpair get closed. |
| // |
| // The problem is that there is no way to atomically send and close an FD. |
| // The closest that we can do is send and then immediately close the FD, |
| // which is what we do in the gofer. The gofer won't respond to another |
| // request until the reply is sent and the FD is closed, so forcing the |
| // gofer to handle another request will ensure that this has happened. |
| for (int i = 0; i < 2; i++) { |
| int sock; |
| RETURN_ERROR_IF_SYSCALL_FAIL(sock = socket(domain, type, protocol)); |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| connect(sock, AsSockAddr(&addr), sizeof(addr))); |
| RETURN_ERROR_IF_SYSCALL_FAIL(close(sock)); |
| } |
| |
| return std::make_unique<FDSocketPair>(std::move(sock1), std::move(sock2)); |
| }; |
| } |
| |
| Creator<SocketPair> SocketpairGoferFileSocketPairCreator(int flags) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { |
| constexpr char kSocketGoferPath[] = "/socket"; |
| |
| FileDescriptor sock1; |
| { |
| int sock1_fd; |
| RETURN_ERROR_IF_SYSCALL_FAIL(sock1_fd = |
| open(kSocketGoferPath, O_RDWR | flags)); |
| MaybeSave(); // Successful socket creation. |
| sock1.reset(sock1_fd); |
| } |
| |
| FileDescriptor sock2; |
| { |
| int sock2_fd; |
| RETURN_ERROR_IF_SYSCALL_FAIL(sock2_fd = |
| open(kSocketGoferPath, O_RDWR | flags)); |
| MaybeSave(); // Successful socket creation. |
| sock2.reset(sock2_fd); |
| } |
| |
| return std::make_unique<FDSocketPair>(std::move(sock1), std::move(sock2)); |
| }; |
| } |
| |
| Creator<SocketPair> UnboundSocketPairCreator(bool abstract, int domain, |
| int type, int protocol) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { |
| ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr1, |
| UniqueUnixAddr(abstract, domain)); |
| ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr2, |
| UniqueUnixAddr(abstract, domain)); |
| |
| ASSIGN_OR_RETURN_ERRNO(auto sock1, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| ASSIGN_OR_RETURN_ERRNO(auto sock2, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| return std::make_unique<AddrFDSocketPair>(std::move(sock1), |
| std::move(sock2), addr1, addr2); |
| }; |
| } |
| |
| Creator<SocketPair> FilesystemUnboundSocketPairCreator(int domain, int type, |
| int protocol) { |
| return UnboundSocketPairCreator(/* abstract= */ false, domain, type, |
| protocol); |
| } |
| |
| Creator<SocketPair> AbstractUnboundSocketPairCreator(int domain, int type, |
| int protocol) { |
| return UnboundSocketPairCreator(/* abstract= */ true, domain, type, protocol); |
| } |
| |
| void LocalhostAddr(struct sockaddr_in* addr, bool dual_stack) { |
| addr->sin_family = AF_INET; |
| addr->sin_port = htons(0); |
| inet_pton(AF_INET, "127.0.0.1", |
| reinterpret_cast<void*>(&addr->sin_addr.s_addr)); |
| } |
| |
| void LocalhostAddr(struct sockaddr_in6* addr, bool dual_stack) { |
| addr->sin6_family = AF_INET6; |
| addr->sin6_port = htons(0); |
| if (dual_stack) { |
| inet_pton(AF_INET6, "::ffff:127.0.0.1", |
| reinterpret_cast<void*>(&addr->sin6_addr.s6_addr)); |
| } else { |
| inet_pton(AF_INET6, "::1", |
| reinterpret_cast<void*>(&addr->sin6_addr.s6_addr)); |
| } |
| addr->sin6_scope_id = 0; |
| } |
| |
| template <typename T> |
| PosixErrorOr<T> BindIP(int fd, bool dual_stack) { |
| T addr = {}; |
| LocalhostAddr(&addr, dual_stack); |
| RETURN_ERROR_IF_SYSCALL_FAIL(bind(fd, AsSockAddr(&addr), sizeof(addr))); |
| socklen_t addrlen = sizeof(addr); |
| RETURN_ERROR_IF_SYSCALL_FAIL(getsockname(fd, AsSockAddr(&addr), &addrlen)); |
| return addr; |
| } |
| |
| template <typename T> |
| PosixErrorOr<T> TCPBindAndListen(int fd, bool dual_stack) { |
| ASSIGN_OR_RETURN_ERRNO(T addr, BindIP<T>(fd, dual_stack)); |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| listen(fd, /* backlog = */ 5)); // NOLINT(bugprone-argument-comment) |
| return addr; |
| } |
| |
| template <typename T> |
| PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> |
| CreateTCPConnectAcceptSocketPair(int bound, FileDescriptor connected, int type, |
| bool dual_stack, T bind_addr) { |
| int connect_result = 0; |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| (connect_result = RetryEINTR(connect)( |
| connected.get(), AsSockAddr(&bind_addr), sizeof(bind_addr))) == -1 && |
| errno == EINPROGRESS |
| ? 0 |
| : connect_result); |
| MaybeSave(); // Successful connect. |
| |
| if (connect_result == -1) { |
| struct pollfd connect_poll = {connected.get(), POLLOUT | POLLERR | POLLHUP, |
| 0}; |
| int num_fds; |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| (num_fds = RetryEINTR(poll)(&connect_poll, 1, -1))); |
| if (num_fds != 1) { |
| return PosixError(ENOTCONN, "connect failed"); |
| } |
| int error = 0; |
| socklen_t errorlen = sizeof(error); |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| getsockopt(connected.get(), SOL_SOCKET, SO_ERROR, &error, &errorlen)); |
| errno = error; |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| /* connect */ error == 0 ? 0 : -1); |
| } |
| |
| int accepted_fd = -1; |
| struct pollfd accept_poll = {bound, POLLIN, 0}; |
| while (accepted_fd == -1) { |
| RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(poll)(&accept_poll, 1, 0)); |
| |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| (accepted_fd = RetryEINTR(accept4)( |
| bound, nullptr, nullptr, type & (SOCK_NONBLOCK | SOCK_CLOEXEC))) == |
| -1 && |
| errno == EAGAIN |
| ? 0 |
| : accepted_fd); |
| } |
| FileDescriptor accepted(accepted_fd); |
| MaybeSave(); // Successful accept. |
| |
| T extra_addr = {}; |
| LocalhostAddr(&extra_addr, dual_stack); |
| return std::make_unique<AddrFDSocketPair>( |
| std::move(connected), std::move(accepted), bind_addr, extra_addr); |
| } |
| |
| template <typename T> |
| PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> CreateTCPAcceptBindSocketPair( |
| FileDescriptor bound, FileDescriptor connected, int type, bool dual_stack) { |
| ASSIGN_OR_RETURN_ERRNO(T bind_addr, |
| TCPBindAndListen<T>(bound.get(), dual_stack)); |
| |
| auto result = CreateTCPConnectAcceptSocketPair( |
| bound.get(), std::move(connected), type, dual_stack, bind_addr); |
| |
| // Cleanup no longer needed resources. |
| RETURN_ERROR_IF_SYSCALL_FAIL(close(bound.release())); |
| MaybeSave(); // Successful close. |
| |
| return result; |
| } |
| |
| Creator<SocketPair> TCPAcceptBindSocketPairCreator(int domain, int type, |
| int protocol, |
| bool dual_stack) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { |
| ASSIGN_OR_RETURN_ERRNO(auto bound, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| |
| ASSIGN_OR_RETURN_ERRNO(auto connected, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| |
| if (domain == AF_INET) { |
| return CreateTCPAcceptBindSocketPair<sockaddr_in>( |
| std::move(bound), std::move(connected), type, dual_stack); |
| } |
| return CreateTCPAcceptBindSocketPair<sockaddr_in6>( |
| std::move(bound), std::move(connected), type, dual_stack); |
| }; |
| } |
| |
| Creator<SocketPair> TCPAcceptBindPersistentListenerSocketPairCreator( |
| int domain, int type, int protocol, bool dual_stack) { |
| // These are lazily initialized below, on the first call to the returned |
| // lambda. These values are private to each returned lambda, but shared across |
| // invocations of a specific lambda. |
| // |
| // The sharing allows pairs created with the same parameters to share a |
| // listener. This prevents future connects from failing if the connecting |
| // socket selects a port which had previously been used by a listening socket |
| // that still has some connections in TIME-WAIT. |
| // |
| // The lazy initialization is to avoid creating sockets during parameter |
| // enumeration. This is important because parameters are enumerated during the |
| // build process where networking may not be available. |
| auto listener = std::make_shared<absl::optional<int>>(absl::optional<int>()); |
| auto addr4 = std::make_shared<absl::optional<sockaddr_in>>( |
| absl::optional<sockaddr_in>()); |
| auto addr6 = std::make_shared<absl::optional<sockaddr_in6>>( |
| absl::optional<sockaddr_in6>()); |
| |
| return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { |
| ASSIGN_OR_RETURN_ERRNO(auto connected, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| |
| // Share the listener across invocations. |
| if (!listener->has_value()) { |
| int fd = socket(domain, type, protocol); |
| if (fd < 0) { |
| return PosixError(errno, absl::StrCat("socket(", domain, ", ", type, |
| ", ", protocol, ")")); |
| } |
| listener->emplace(fd); |
| MaybeSave(); // Successful socket creation. |
| } |
| |
| // Bind the listener once, but create a new connect/accept pair each |
| // time. |
| if (domain == AF_INET) { |
| if (!addr4->has_value()) { |
| addr4->emplace( |
| TCPBindAndListen<sockaddr_in>(listener->value(), dual_stack) |
| .ValueOrDie()); |
| } |
| return CreateTCPConnectAcceptSocketPair(listener->value(), |
| std::move(connected), type, |
| dual_stack, addr4->value()); |
| } |
| if (!addr6->has_value()) { |
| addr6->emplace( |
| TCPBindAndListen<sockaddr_in6>(listener->value(), dual_stack) |
| .ValueOrDie()); |
| } |
| return CreateTCPConnectAcceptSocketPair(listener->value(), |
| std::move(connected), type, |
| dual_stack, addr6->value()); |
| }; |
| } |
| |
| template <typename T> |
| PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> CreateUDPBoundSocketPair( |
| FileDescriptor sock1, FileDescriptor sock2, int type, bool dual_stack) { |
| ASSIGN_OR_RETURN_ERRNO(T addr1, BindIP<T>(sock1.get(), dual_stack)); |
| ASSIGN_OR_RETURN_ERRNO(T addr2, BindIP<T>(sock2.get(), dual_stack)); |
| |
| return std::make_unique<AddrFDSocketPair>(std::move(sock1), std::move(sock2), |
| addr1, addr2); |
| } |
| |
| template <typename T> |
| PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> |
| CreateUDPBidirectionalBindSocketPair(FileDescriptor sock1, FileDescriptor sock2, |
| int type, bool dual_stack) { |
| ASSIGN_OR_RETURN_ERRNO( |
| auto socks, CreateUDPBoundSocketPair<T>( |
| std::move(sock1), std::move(sock2), type, dual_stack)); |
| |
| // Connect sock1 to sock2. |
| RETURN_ERROR_IF_SYSCALL_FAIL(connect(socks->first_fd(), socks->second_addr(), |
| socks->second_addr_size())); |
| MaybeSave(); // Successful connection. |
| |
| // Connect sock2 to sock1. |
| RETURN_ERROR_IF_SYSCALL_FAIL(connect(socks->second_fd(), socks->first_addr(), |
| socks->first_addr_size())); |
| MaybeSave(); // Successful connection. |
| |
| return socks; |
| } |
| |
| Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type, |
| int protocol, |
| bool dual_stack) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { |
| ASSIGN_OR_RETURN_ERRNO(auto sock1, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| |
| ASSIGN_OR_RETURN_ERRNO(auto sock2, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| |
| if (domain == AF_INET) { |
| return CreateUDPBidirectionalBindSocketPair<sockaddr_in>( |
| std::move(sock1), std::move(sock2), type, dual_stack); |
| } |
| return CreateUDPBidirectionalBindSocketPair<sockaddr_in6>( |
| std::move(sock1), std::move(sock2), type, dual_stack); |
| }; |
| } |
| |
| Creator<SocketPair> UDPUnboundSocketPairCreator(int domain, int type, |
| int protocol, bool dual_stack) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { |
| ASSIGN_OR_RETURN_ERRNO(auto sock1, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| |
| ASSIGN_OR_RETURN_ERRNO(auto sock2, Socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| |
| return std::make_unique<FDSocketPair>(std::move(sock1), std::move(sock2)); |
| }; |
| } |
| |
| SocketPairKind Reversed(SocketPairKind const& base) { |
| auto const& creator = base.creator; |
| return SocketPairKind{ |
| absl::StrCat("reversed ", base.description), base.domain, base.type, |
| base.protocol, |
| [creator]() -> PosixErrorOr<std::unique_ptr<ReversedSocketPair>> { |
| ASSIGN_OR_RETURN_ERRNO(auto creator_value, creator()); |
| return std::make_unique<ReversedSocketPair>(std::move(creator_value)); |
| }}; |
| } |
| |
| Creator<FileDescriptor> UnboundSocketCreator(int domain, int type, |
| int protocol) { |
| return [=]() -> PosixErrorOr<std::unique_ptr<FileDescriptor>> { |
| int sock; |
| RETURN_ERROR_IF_SYSCALL_FAIL(sock = socket(domain, type, protocol)); |
| MaybeSave(); // Successful socket creation. |
| |
| return std::make_unique<FileDescriptor>(sock); |
| }; |
| } |
| |
| std::vector<SocketPairKind> IncludeReversals(std::vector<SocketPairKind> vec) { |
| return ApplyVecToVec<SocketPairKind>(std::vector<Middleware>{NoOp, Reversed}, |
| vec); |
| } |
| |
| SocketPairKind NoOp(SocketPairKind const& base) { return base; } |
| |
| void TransferTest(int fd1, int fd2) { |
| char buf1[20]; |
| RandomizeBuffer(buf1, sizeof(buf1)); |
| ASSERT_THAT(WriteFd(fd1, buf1, sizeof(buf1)), |
| SyscallSucceedsWithValue(sizeof(buf1))); |
| |
| char buf2[20]; |
| ASSERT_THAT(ReadFd(fd2, buf2, sizeof(buf2)), |
| SyscallSucceedsWithValue(sizeof(buf2))); |
| |
| EXPECT_EQ(0, memcmp(buf1, buf2, sizeof(buf1))); |
| |
| RandomizeBuffer(buf1, sizeof(buf1)); |
| ASSERT_THAT(WriteFd(fd2, buf1, sizeof(buf1)), |
| SyscallSucceedsWithValue(sizeof(buf1))); |
| |
| ASSERT_THAT(ReadFd(fd1, buf2, sizeof(buf2)), |
| SyscallSucceedsWithValue(sizeof(buf2))); |
| |
| EXPECT_EQ(0, memcmp(buf1, buf2, sizeof(buf1))); |
| } |
| |
| size_t CalculateUnixSockAddrLen(const char* sun_path) { |
| // Abstract addresses always return the full length. |
| if (sun_path[0] == 0) { |
| return sizeof(sockaddr_un); |
| } |
| // Filesystem addresses use the address length plus the 2 byte sun_family |
| // and null terminator. |
| return strlen(sun_path) + 3; |
| } |
| |
| struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_un& addr) { |
| struct sockaddr_storage addr_storage = {}; |
| memcpy(&addr_storage, &addr, sizeof(addr)); |
| return addr_storage; |
| } |
| |
| struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_in& addr) { |
| struct sockaddr_storage addr_storage = {}; |
| memcpy(&addr_storage, &addr, sizeof(addr)); |
| return addr_storage; |
| } |
| |
| struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_in6& addr) { |
| struct sockaddr_storage addr_storage = {}; |
| memcpy(&addr_storage, &addr, sizeof(addr)); |
| return addr_storage; |
| } |
| |
| SocketKind SimpleSocket(int fam, int type, int proto) { |
| return SocketKind{ |
| absl::StrCat("Family ", fam, ", type ", type, ", proto ", proto), fam, |
| type, proto, SyscallSocketCreator(fam, type, proto)}; |
| } |
| |
| ssize_t SendLargeSendMsg(const std::unique_ptr<SocketPair>& sockets, |
| size_t size, bool reader) { |
| const int rfd = sockets->second_fd(); |
| ScopedThread t([rfd, size, reader] { |
| if (!reader) { |
| return; |
| } |
| |
| // Potentially too many syscalls in the loop. |
| const DisableSave ds; |
| |
| std::vector<char> buf(size); |
| size_t total = 0; |
| |
| while (total < size) { |
| int ret = read(rfd, buf.data(), buf.size()); |
| if (ret == -1 && errno == EAGAIN) { |
| continue; |
| } |
| if (ret > 0) { |
| total += ret; |
| } |
| |
| // Assert to return on first failure. |
| ASSERT_THAT(ret, SyscallSucceeds()); |
| } |
| }); |
| |
| std::vector<char> buf(size); |
| |
| struct iovec iov = {}; |
| iov.iov_base = buf.data(); |
| iov.iov_len = buf.size(); |
| |
| struct msghdr msg = {}; |
| msg.msg_iov = &iov; |
| msg.msg_iovlen = 1; |
| |
| return RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0); |
| } |
| |
| namespace internal { |
| PosixErrorOr<int> TryPortAvailable(int port, AddressFamily family, |
| SocketType type, bool reuse_addr) { |
| if (port < 0) { |
| return PosixError(EINVAL, "Invalid port"); |
| } |
| |
| // Both Ipv6 and Dualstack are AF_INET6. |
| int sock_fam = (family == AddressFamily::kIpv4 ? AF_INET : AF_INET6); |
| int sock_type = (type == SocketType::kTcp ? SOCK_STREAM : SOCK_DGRAM); |
| ASSIGN_OR_RETURN_ERRNO(auto fd, Socket(sock_fam, sock_type, 0)); |
| |
| if (reuse_addr) { |
| int one = 1; |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| setsockopt(fd.get(), SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))); |
| } |
| |
| // Try to bind. |
| sockaddr_storage storage = {}; |
| int storage_size = 0; |
| if (family == AddressFamily::kIpv4) { |
| sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&storage); |
| storage_size = sizeof(*addr); |
| addr->sin_family = AF_INET; |
| addr->sin_port = htons(port); |
| addr->sin_addr.s_addr = htonl(INADDR_ANY); |
| } else { |
| sockaddr_in6* addr = reinterpret_cast<sockaddr_in6*>(&storage); |
| storage_size = sizeof(*addr); |
| addr->sin6_family = AF_INET6; |
| addr->sin6_port = htons(port); |
| if (family == AddressFamily::kDualStack) { |
| inet_pton(AF_INET6, "::ffff:0.0.0.0", |
| reinterpret_cast<void*>(&addr->sin6_addr.s6_addr)); |
| } else { |
| addr->sin6_addr = in6addr_any; |
| } |
| } |
| |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| bind(fd.get(), AsSockAddr(&storage), storage_size)); |
| |
| // If the user specified 0 as the port, we will return the port that the |
| // kernel gave us, otherwise we will validate that this socket bound to the |
| // requested port. |
| sockaddr_storage bound_storage = {}; |
| socklen_t bound_storage_size = sizeof(bound_storage); |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| getsockname(fd.get(), AsSockAddr(&bound_storage), &bound_storage_size)); |
| |
| int available_port = -1; |
| if (bound_storage.ss_family == AF_INET) { |
| sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&bound_storage); |
| available_port = ntohs(addr->sin_port); |
| } else if (bound_storage.ss_family == AF_INET6) { |
| sockaddr_in6* addr = reinterpret_cast<sockaddr_in6*>(&bound_storage); |
| available_port = ntohs(addr->sin6_port); |
| } else { |
| return PosixError(EPROTOTYPE, "Getsockname returned invalid family"); |
| } |
| |
| // If we requested a specific port make sure our bound port is that port. |
| if (port != 0 && available_port != port) { |
| return PosixError(EINVAL, |
| absl::StrCat("Bound port ", available_port, |
| " was not equal to requested port ", port)); |
| } |
| |
| // If we're trying to do a TCP socket, let's also try to listen. |
| if (type == SocketType::kTcp) { |
| RETURN_ERROR_IF_SYSCALL_FAIL(listen(fd.get(), 1)); |
| } |
| |
| return available_port; |
| } |
| } // namespace internal |
| |
| PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size) { |
| struct iovec iov; |
| iov.iov_base = buf; |
| iov.iov_len = buf_size; |
| msg->msg_iov = &iov; |
| msg->msg_iovlen = 1; |
| |
| int ret; |
| RETURN_ERROR_IF_SYSCALL_FAIL(ret = RetryEINTR(sendmsg)(sock, msg, 0)); |
| return ret; |
| } |
| |
| PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout) { |
| fd_set rfd; |
| struct timeval to = {.tv_sec = timeout, .tv_usec = 0}; |
| struct timeval* to_ptr = timeout < 0 ? nullptr : &to; |
| FD_ZERO(&rfd); |
| FD_SET(sock, &rfd); |
| int ret; |
| RETURN_ERROR_IF_SYSCALL_FAIL(ret = |
| select(sock + 1, &rfd, NULL, NULL, to_ptr)); |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| ret = RetryEINTR(recv)(sock, buf, buf_size, MSG_DONTWAIT)); |
| return ret; |
| } |
| |
| PosixErrorOr<int> RecvMsgTimeout(int sock, struct msghdr* msg, int timeout) { |
| fd_set rfd; |
| struct timeval to = {.tv_sec = timeout, .tv_usec = 0}; |
| struct timeval* to_ptr = timeout < 0 ? nullptr : &to; |
| FD_ZERO(&rfd); |
| FD_SET(sock, &rfd); |
| |
| int ret; |
| RETURN_ERROR_IF_SYSCALL_FAIL(ret = |
| select(sock + 1, &rfd, NULL, NULL, to_ptr)); |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| ret = RetryEINTR(recvmsg)(sock, msg, MSG_DONTWAIT)); |
| return ret; |
| } |
| |
| void RecvNoData(int sock) { |
| char data = 0; |
| struct iovec iov; |
| iov.iov_base = &data; |
| iov.iov_len = 1; |
| struct msghdr msg = {}; |
| msg.msg_iov = &iov; |
| msg.msg_iovlen = 1; |
| ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, MSG_DONTWAIT), |
| SyscallFailsWithErrno(EAGAIN)); |
| } |
| |
| TestAddress TestAddress::WithPort(uint16_t port) const { |
| TestAddress addr = *this; |
| switch (addr.family()) { |
| case AF_INET: |
| reinterpret_cast<sockaddr_in*>(&addr.addr)->sin_port = htons(port); |
| break; |
| case AF_INET6: |
| reinterpret_cast<sockaddr_in6*>(&addr.addr)->sin6_port = htons(port); |
| break; |
| } |
| return addr; |
| } |
| |
| namespace { |
| |
| TestAddress V4Addr(std::string description, in_addr_t addr) { |
| TestAddress t(std::move(description)); |
| t.addr.ss_family = AF_INET; |
| t.addr_len = sizeof(sockaddr_in); |
| reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = addr; |
| return t; |
| } |
| |
| TestAddress V6Addr(std::string description, const in6_addr& addr) { |
| TestAddress t(std::move(description)); |
| t.addr.ss_family = AF_INET6; |
| t.addr_len = sizeof(sockaddr_in6); |
| reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = addr; |
| return t; |
| } |
| |
| } // namespace |
| |
| TestAddress V4AddrStr(std::string description, const char* addr) { |
| in_addr_t s_addr; |
| inet_pton(AF_INET, addr, &s_addr); |
| return V4Addr(description, s_addr); |
| } |
| |
| TestAddress V6AddrStr(std::string description, const char* addr) { |
| struct in6_addr s_addr; |
| inet_pton(AF_INET6, addr, &s_addr); |
| return V6Addr(description, s_addr); |
| } |
| |
| TestAddress V4Any() { return V4Addr("V4Any", htonl(INADDR_ANY)); } |
| |
| TestAddress V4Broadcast() { |
| return V4Addr("V4Broadcast", htonl(INADDR_BROADCAST)); |
| } |
| |
| TestAddress V4Loopback() { |
| return V4Addr("V4Loopback", htonl(INADDR_LOOPBACK)); |
| } |
| |
| TestAddress V4LoopbackSubnetBroadcast() { |
| return V4AddrStr("V4LoopbackSubnetBroadcast", "127.255.255.255"); |
| } |
| |
| TestAddress V4MappedAny() { return V6AddrStr("V4MappedAny", "::ffff:0.0.0.0"); } |
| |
| TestAddress V4MappedLoopback() { |
| return V6AddrStr("V4MappedLoopback", "::ffff:127.0.0.1"); |
| } |
| |
| TestAddress V4Multicast() { |
| return V4Addr("V4Multicast", inet_addr(kMulticastAddress)); |
| } |
| |
| TestAddress V4MulticastAllHosts() { |
| return V4Addr("V4MulticastAllHosts", htonl(INADDR_ALLHOSTS_GROUP)); |
| } |
| |
| TestAddress V6Any() { return V6Addr("V6Any", in6addr_any); } |
| |
| TestAddress V6Loopback() { return V6Addr("V6Loopback", in6addr_loopback); } |
| |
| TestAddress V6Multicast() { return V6AddrStr("V6Multicast", "ff05::1234"); } |
| |
| TestAddress V6MulticastInterfaceLocalAllNodes() { |
| return V6AddrStr("V6MulticastInterfaceLocalAllNodes", "ff01::1"); |
| } |
| |
| TestAddress V6MulticastLinkLocalAllNodes() { |
| return V6AddrStr("V6MulticastLinkLocalAllNodes", "ff02::1"); |
| } |
| |
| TestAddress V6MulticastLinkLocalAllRouters() { |
| return V6AddrStr("V6MulticastLinkLocalAllRouters", "ff02::2"); |
| } |
| |
| // Checksum computes the internet checksum of a buffer. |
| uint16_t Checksum(uint16_t* buf, ssize_t buf_size) { |
| // Add up the 16-bit values in the buffer. |
| uint32_t total = 0; |
| for (unsigned int i = 0; i < buf_size; i += sizeof(*buf)) { |
| total += *buf; |
| buf++; |
| } |
| |
| // If buf has an odd size, add the remaining byte. |
| if (buf_size % 2) { |
| total += *(reinterpret_cast<unsigned char*>(buf) - 1); |
| } |
| |
| // This carries any bits past the lower 16 until everything fits in 16 bits. |
| while (total >> 16) { |
| uint16_t lower = total & 0xffff; |
| uint16_t upper = total >> 16; |
| total = lower + upper; |
| } |
| |
| return ~total; |
| } |
| |
| uint16_t IPChecksum(struct iphdr ip) { |
| return Checksum(reinterpret_cast<uint16_t*>(&ip), sizeof(ip)); |
| } |
| |
| // The pseudo-header defined in RFC 768 for calculating the UDP checksum. |
| struct udp_pseudo_hdr { |
| uint32_t srcip; |
| uint32_t destip; |
| char zero; |
| char protocol; |
| uint16_t udplen; |
| }; |
| |
| static_assert(sizeof(udp_pseudo_hdr) == 12); |
| |
| uint16_t UDPChecksum(struct iphdr iphdr, struct udphdr udphdr, |
| const char* payload, ssize_t payload_len) { |
| struct udp_pseudo_hdr phdr = {}; |
| phdr.srcip = iphdr.saddr; |
| phdr.destip = iphdr.daddr; |
| phdr.zero = 0; |
| phdr.protocol = IPPROTO_UDP; |
| phdr.udplen = udphdr.len; |
| |
| ssize_t buf_size = sizeof(phdr) + sizeof(udphdr) + payload_len; |
| char* buf = static_cast<char*>(malloc(buf_size)); |
| memcpy(buf, &phdr, sizeof(phdr)); |
| memcpy(buf + sizeof(phdr), &udphdr, sizeof(udphdr)); |
| memcpy(buf + sizeof(phdr) + sizeof(udphdr), payload, payload_len); |
| |
| uint16_t csum = Checksum(reinterpret_cast<uint16_t*>(buf), buf_size); |
| free(buf); |
| return csum; |
| } |
| |
| // IPv6 pseudo-header for UDP checksum calculation. |
| struct udpv6_pseudo_hdr { |
| in6_addr srcip; |
| in6_addr destip; |
| char zero; |
| char protocol; |
| uint16_t udplen; |
| }; |
| static_assert(sizeof(udpv6_pseudo_hdr) == 36); |
| |
| uint16_t UDPChecksum(struct ip6_hdr iphdr, struct udphdr udphdr, |
| const char* payload, ssize_t payload_len) { |
| struct udpv6_pseudo_hdr phdr = {}; |
| phdr.srcip = iphdr.ip6_src; |
| phdr.destip = iphdr.ip6_dst; |
| phdr.zero = 0; |
| phdr.protocol = IPPROTO_UDP; |
| phdr.udplen = udphdr.len; |
| |
| ssize_t buf_size = sizeof(phdr) + sizeof(udphdr) + payload_len; |
| char* buf = static_cast<char*>(malloc(buf_size)); |
| memcpy(buf, &phdr, sizeof(phdr)); |
| memcpy(buf + sizeof(phdr), &udphdr, sizeof(udphdr)); |
| memcpy(buf + sizeof(phdr) + sizeof(udphdr), payload, payload_len); |
| |
| uint16_t csum = Checksum(reinterpret_cast<uint16_t*>(buf), buf_size); |
| free(buf); |
| return csum; |
| } |
| |
| uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload, |
| ssize_t payload_len) { |
| ssize_t buf_size = sizeof(icmphdr) + payload_len; |
| char* buf = static_cast<char*>(malloc(buf_size)); |
| memcpy(buf, &icmphdr, sizeof(icmphdr)); |
| memcpy(buf + sizeof(icmphdr), payload, payload_len); |
| |
| uint16_t csum = Checksum(reinterpret_cast<uint16_t*>(buf), buf_size); |
| free(buf); |
| return csum; |
| } |
| |
| PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) { |
| switch (family) { |
| case AF_INET: |
| return static_cast<uint16_t>( |
| reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); |
| case AF_INET6: |
| return static_cast<uint16_t>( |
| reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); |
| default: |
| return PosixError(EINVAL, |
| absl::StrCat("unknown socket family: ", family)); |
| } |
| } |
| |
| PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { |
| switch (family) { |
| case AF_INET: |
| reinterpret_cast<sockaddr_in*>(addr)->sin_port = port; |
| return NoError(); |
| case AF_INET6: |
| reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port; |
| return NoError(); |
| default: |
| return PosixError(EINVAL, |
| absl::StrCat("unknown socket family: ", family)); |
| } |
| } |
| |
| sockaddr_storage InetLoopbackAddr(int family) { |
| struct sockaddr_storage addr; |
| memset(&addr, 0, sizeof(addr)); |
| AsSockAddr(&addr)->sa_family = family; |
| |
| if (family == AF_INET) { |
| auto sin = reinterpret_cast<struct sockaddr_in*>(&addr); |
| sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| sin->sin_port = htons(0); |
| return addr; |
| } |
| auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); |
| sin6->sin6_addr = in6addr_loopback; |
| sin6->sin6_port = htons(0); |
| return addr; |
| } |
| |
| void SetupTimeWaitClose(const TestAddress* listener, |
| const TestAddress* connector, bool reuse, |
| bool accept_close, sockaddr_storage* listen_addr, |
| sockaddr_storage* conn_bound_addr) { |
| // Create the listening socket. |
| FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( |
| Socket(listener->family(), SOCK_STREAM, IPPROTO_TCP)); |
| if (reuse) { |
| ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, |
| &kSockOptOn, sizeof(kSockOptOn)), |
| SyscallSucceeds()); |
| } |
| ASSERT_THAT( |
| bind(listen_fd.get(), AsSockAddr(listen_addr), listener->addr_len), |
| SyscallSucceeds()); |
| ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); |
| |
| // Get the port bound by the listening socket. |
| socklen_t addrlen = listener->addr_len; |
| ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(listen_addr), &addrlen), |
| SyscallSucceeds()); |
| |
| uint16_t const port = |
| ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener->family(), *listen_addr)); |
| |
| // Connect to the listening socket. |
| FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( |
| Socket(connector->family(), SOCK_STREAM, IPPROTO_TCP)); |
| |
| // We disable saves after this point as a S/R causes the netstack seed |
| // to be regenerated which changes what ports/ISN is picked for a given |
| // tuple (src ip,src port, dst ip, dst port). This can cause the final |
| // SYN to use a sequence number that looks like one from the current |
| // connection in TIME_WAIT and will not be accepted causing the test |
| // to timeout. |
| // |
| // TODO(gvisor.dev/issue/940): S/R portSeed/portHint |
| DisableSave ds; |
| |
| sockaddr_storage conn_addr = connector->addr; |
| ASSERT_NO_ERRNO(SetAddrPort(connector->family(), &conn_addr, port)); |
| ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), |
| connector->addr_len), |
| SyscallSucceeds()); |
| |
| // Accept the connection. |
| auto accepted = |
| ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); |
| |
| // Get the address/port bound by the connecting socket. |
| socklen_t conn_addrlen = connector->addr_len; |
| ASSERT_THAT( |
| getsockname(conn_fd.get(), AsSockAddr(conn_bound_addr), &conn_addrlen), |
| SyscallSucceeds()); |
| |
| FileDescriptor active_closefd, passive_closefd; |
| if (accept_close) { |
| active_closefd = std::move(accepted); |
| passive_closefd = std::move(conn_fd); |
| } else { |
| active_closefd = std::move(conn_fd); |
| passive_closefd = std::move(accepted); |
| } |
| |
| // shutdown to trigger TIME_WAIT. |
| ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds()); |
| { |
| constexpr int kTimeout = 10000; |
| pollfd pfd = { |
| .fd = passive_closefd.get(), |
| .events = POLLIN, |
| }; |
| ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); |
| ASSERT_EQ(pfd.revents, POLLIN); |
| } |
| // Send/recv some data to be sure that the fin packet has been acked. |
| char c = 0; |
| ASSERT_THAT(send(passive_closefd.get(), &c, 1, 0), |
| SyscallSucceedsWithValue(sizeof(c))); |
| ASSERT_THAT(recv(active_closefd.get(), &c, 1, 0), |
| SyscallSucceedsWithValue(sizeof(c))); |
| ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds()); |
| { |
| constexpr int kTimeout = 10000; |
| constexpr int16_t want_events = POLLHUP; |
| pollfd pfd = { |
| .fd = active_closefd.get(), |
| .events = want_events, |
| }; |
| ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); |
| } |
| |
| // This sleep is needed to reduce flake to ensure that the passive-close |
| // ensures the state transitions to CLOSE from LAST_ACK. |
| absl::SleepFor(absl::Seconds(1)); |
| } |
| |
| constexpr char kRangeFile[] = "/proc/sys/net/ipv4/ip_local_port_range"; |
| |
| PosixErrorOr<int> MaybeLimitEphemeralPorts() { |
| int min = 0; |
| int max = 1 << 16; |
| |
| // Read the ephemeral range from /proc. |
| ASSIGN_OR_RETURN_ERRNO(std::string rangefile, GetContents(kRangeFile)); |
| const std::string err_msg = |
| absl::StrFormat("%s has invalid content: %s", kRangeFile, rangefile); |
| if (rangefile.back() != '\n') { |
| return PosixError(EINVAL, err_msg); |
| } |
| rangefile.pop_back(); |
| std::vector<std::string> range = |
| absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); |
| if (range.size() < 2 || !absl::SimpleAtoi(range.front(), &min) || |
| !absl::SimpleAtoi(range.back(), &max)) { |
| return PosixError(EINVAL, err_msg); |
| } |
| |
| // If we can open as writable, limit the range. |
| if (!access(kRangeFile, W_OK)) { |
| ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, |
| Open(kRangeFile, O_WRONLY | O_TRUNC, 0)); |
| int newMax = min + 50; |
| const std::string small_range = absl::StrFormat("%d %d", min, newMax); |
| int n = write(fd.get(), small_range.c_str(), small_range.size()); |
| if (n < 0) { |
| // Hostinet doesn't allow modifying the host port range. And if we're root |
| // (as we are in some tests), access and open will succeed even if the |
| // file mode is readonly. |
| if (errno != EACCES) { |
| return PosixError( |
| errno, |
| absl::StrFormat("write(%d [%s], \"%s\", %d)", fd.get(), kRangeFile, |
| small_range.c_str(), small_range.size())); |
| } |
| } else { |
| max = newMax; |
| } |
| } |
| return max - min; |
| } |
| |
| PosixErrorOr<std::function<PosixError()>> AllowMartianPacketsOnLoopback() { |
| if (IsRunningOnGvisor()) { |
| return std::function<PosixError()>([]() { return NoError(); }); |
| } |
| |
| constexpr std::array<const char*, 2> files = { |
| "/proc/sys/net/ipv4/conf/lo/accept_local", |
| "/proc/sys/net/ipv4/conf/lo/route_localnet", |
| }; |
| std::stack<std::pair<const char*, char>> initial_configs; |
| |
| // Record and update the initial configurations. |
| PosixError err = [&]() -> PosixError { |
| for (const char* f : files) { |
| ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, Open(f, O_RDWR)); |
| char initial_config; |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| read(fd.get(), &initial_config, sizeof(initial_config))); |
| |
| constexpr char kEnabled = '1'; |
| RETURN_ERROR_IF_SYSCALL_FAIL(lseek(fd.get(), 0, SEEK_SET)); |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| write(fd.get(), &kEnabled, sizeof(kEnabled))); |
| initial_configs.push(std::make_pair(f, initial_config)); |
| } |
| return NoError(); |
| }(); |
| |
| // Only define the restore function after we're done updating the config to |
| // capture the initialized initial_configs std::stack. |
| std::function<PosixError()> restore = [initial_configs]() mutable { |
| while (!initial_configs.empty()) { |
| std::pair<const char*, char> cfg = initial_configs.top(); |
| initial_configs.pop(); |
| ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, Open(cfg.first, O_WRONLY)); |
| RETURN_ERROR_IF_SYSCALL_FAIL( |
| write(fd.get(), &cfg.second, sizeof(cfg.second))); |
| } |
| return NoError(); |
| }; |
| |
| if (!err.ok()) { |
| restore().IgnoreError(); |
| return err; |
| } |
| |
| return restore; |
| } |
| |
| } // namespace testing |
| } // namespace gvisor |