blob: 13a6a0da3e1faec39561ff4610e00195e248fe98 [file] [log] [blame]
// 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.
// These tests ensure the zircon libc can talk to netstack.
// No network connection is required, only a running netstack binary.
#include <fuchsia/posix/socket/llcpp/fidl.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/spawn.h>
#include <lib/sync/completion.h>
#include <lib/zx/process.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <array>
#include <thread>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include "src/lib/testing/predicates/status.h"
#include "util.h"
TEST(NetStreamTest, ReleasePortNoClose) {
fbl::unique_fd first;
ASSERT_TRUE(first = 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(first.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(first.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr));
fbl::unique_fd second;
ASSERT_TRUE(second = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno);
// Confirm bind fails while address is in use.
ASSERT_EQ(bind(second.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), -1);
ASSERT_EQ(errno, EADDRINUSE) << strerror(errno);
// Simulate unexpected process exit by closing the handle without calling close.
zx::handle handle;
zx_status_t status = fdio_fd_transfer(first.release(), handle.reset_and_get_address());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
status = zx_handle_close(handle.release());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
while (true) {
int ret = bind(second.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr));
if (ret == 0) {
break;
}
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, EADDRINUSE) << strerror(errno);
zx::nanosleep(zx::deadline_after(zx::msec(20)));
}
}
TEST(NetStreamTest, RaceClose) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno);
::llcpp::fuchsia::posix::socket::StreamSocket::SyncClient client;
zx_status_t status =
fdio_fd_transfer(fd.release(), client.mutable_channel()->reset_and_get_address());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
sync_completion_t completion;
std::array<std::thread, 10> workers;
for (auto& worker : workers) {
worker = std::thread([&client, &completion]() {
zx_status_t status = sync_completion_wait(&completion, ZX_TIME_INFINITE);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
auto response = client.Close();
if ((status = response.status()) != ZX_OK) {
EXPECT_EQ(status, ZX_ERR_PEER_CLOSED) << zx_status_get_string(status);
} else {
EXPECT_EQ(status = response.Unwrap()->s, ZX_OK) << zx_status_get_string(status);
}
});
}
sync_completion_signal(&completion);
std::for_each(workers.begin(), workers.end(), std::mem_fn(&std::thread::join));
}
TEST(SocketTest, ZXSocketSignalNotPermitted) {
fbl::unique_fd fd;
ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno);
::llcpp::fuchsia::posix::socket::StreamSocket::SyncClient client;
zx_status_t status;
ASSERT_EQ(
status = fdio_fd_transfer(fd.release(), client.mutable_channel()->reset_and_get_address()),
ZX_OK)
<< zx_status_get_string(status);
auto response = client.Describe();
ASSERT_EQ(status = response.status(), ZX_OK) << zx_status_get_string(status);
const ::llcpp::fuchsia::io::NodeInfo& node_info = response.Unwrap()->info;
ASSERT_EQ(node_info.which(), ::llcpp::fuchsia::io::NodeInfo::Tag::kStreamSocket);
const zx::socket& socket = node_info.stream_socket().socket;
EXPECT_EQ(status = socket.signal(ZX_USER_SIGNAL_0, 0), ZX_ERR_ACCESS_DENIED)
<< zx_status_get_string(status);
EXPECT_EQ(status = socket.signal(0, ZX_USER_SIGNAL_0), ZX_ERR_ACCESS_DENIED)
<< zx_status_get_string(status);
EXPECT_EQ(status = socket.signal_peer(ZX_USER_SIGNAL_0, 0), ZX_ERR_ACCESS_DENIED)
<< zx_status_get_string(status);
EXPECT_EQ(status = socket.signal_peer(0, ZX_USER_SIGNAL_0), ZX_ERR_ACCESS_DENIED)
<< zx_status_get_string(status);
}
static const zx::socket& stream_handle(const ::llcpp::fuchsia::io::NodeInfo& node_info) {
return node_info.stream_socket().socket;
}
static const zx::eventpair& datagram_handle(const ::llcpp::fuchsia::io::NodeInfo& node_info) {
return node_info.datagram_socket().event;
}
template <int Type, typename _Client, ::llcpp::fuchsia::io::NodeInfo::Tag Tag, typename _Handle,
const _Handle& (*GetHandle)(const ::llcpp::fuchsia::io::NodeInfo& node_info),
zx_signals_t PeerClosed>
struct SocketImpl {
using Client = _Client;
using Handle = _Handle;
static int type() { return Type; };
static ::llcpp::fuchsia::io::NodeInfo::Tag tag() { return Tag; }
static const Handle& handle(const ::llcpp::fuchsia::io::NodeInfo& node_info) {
return GetHandle(node_info);
}
static zx_signals_t peer_closed() { return PeerClosed; };
};
template <typename Impl>
class SocketTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(fd_ = fbl::unique_fd(socket(AF_INET, Impl::type(), 0))) << strerror(errno);
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);
socklen_t addrlen = sizeof(addr_);
ASSERT_EQ(getsockname(fd_.get(), reinterpret_cast<struct sockaddr*>(&addr_), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr_));
}
fbl::unique_fd fd_;
struct sockaddr_in addr_;
};
using StreamSocketImpl =
SocketImpl<SOCK_STREAM, ::llcpp::fuchsia::posix::socket::StreamSocket::SyncClient,
::llcpp::fuchsia::io::NodeInfo::Tag::kStreamSocket, zx::socket, stream_handle,
ZX_SOCKET_PEER_CLOSED>;
using DatagramSocketImpl =
SocketImpl<SOCK_DGRAM, ::llcpp::fuchsia::posix::socket::DatagramSocket::SyncClient,
::llcpp::fuchsia::io::NodeInfo::Tag::kDatagramSocket, zx::eventpair, datagram_handle,
ZX_EVENTPAIR_PEER_CLOSED>;
class SocketTestNames {
public:
template <typename T>
static std::string GetName(int) {
if (std::is_same<T, StreamSocketImpl>())
return "Stream";
if (std::is_same<T, DatagramSocketImpl>())
return "Datagram";
}
};
using SocketTypes = ::testing::Types<StreamSocketImpl, DatagramSocketImpl>;
TYPED_TEST_SUITE(SocketTest, SocketTypes, SocketTestNames);
TYPED_TEST(SocketTest, CloseResourcesOnClose) {
zx_status_t status;
// Increase the reference count.
zx::channel clone;
ASSERT_EQ(status = fdio_fd_clone(this->fd_.get(), clone.reset_and_get_address()), ZX_OK)
<< zx_status_get_string(status);
typename TypeParam::Client client;
ASSERT_EQ(status = fdio_fd_transfer(this->fd_.release(),
client.mutable_channel()->reset_and_get_address()),
ZX_OK)
<< zx_status_get_string(status);
auto describe_response = client.Describe();
ASSERT_EQ(status = describe_response.status(), ZX_OK) << zx_status_get_string(status);
const ::llcpp::fuchsia::io::NodeInfo& node_info = describe_response.Unwrap()->info;
ASSERT_EQ(node_info.which(), TypeParam::tag());
zx_signals_t observed;
ASSERT_EQ(status = TypeParam::handle(node_info).wait_one(
TypeParam::peer_closed(), zx::deadline_after(zx::msec(100)), &observed),
ZX_ERR_TIMED_OUT)
<< zx_status_get_string(status);
auto close_response = client.Close();
EXPECT_EQ(status = close_response.status(), ZX_OK) << zx_status_get_string(status);
EXPECT_EQ(status = close_response.Unwrap()->s, ZX_OK) << zx_status_get_string(status);
// We still have `clone`, nothing should be closed yet.
ASSERT_EQ(status = TypeParam::handle(node_info).wait_one(
TypeParam::peer_closed(), zx::deadline_after(zx::msec(100)), &observed),
ZX_ERR_TIMED_OUT)
<< zx_status_get_string(status);
clone.reset();
// Give a generous timeout for closures; the channel closing is inherently asynchronous with
// respect to the `Close` FIDL call above (since its return must come over the channel). The
// handle closure is not inherently asynchronous, but happens to be as an implementation detail.
zx::time deadline = zx::deadline_after(zx::sec(5));
ASSERT_EQ(
status = TypeParam::handle(node_info).wait_one(TypeParam::peer_closed(), deadline, &observed),
ZX_OK)
<< zx_status_get_string(status);
ASSERT_EQ(status = client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, deadline, &observed), ZX_OK)
<< zx_status_get_string(status);
}
TEST(SocketTest, AcceptedSocketIsConnected) {
// Create the listening endpoint (server).
fbl::unique_fd serverfd;
ASSERT_TRUE(serverfd = 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(serverfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
ASSERT_EQ(listen(serverfd.get(), 1), 0) << strerror(errno);
// Get the address the server is listening on.
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(serverfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr));
// Connect to the listening endpoint (client).
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);
// Accept the new connection (client) on the listening endpoint (server).
fbl::unique_fd connfd;
ASSERT_TRUE(connfd = fbl::unique_fd(accept(serverfd.get(), nullptr, nullptr))) << strerror(errno);
ASSERT_EQ(close(serverfd.release()), 0) << strerror(errno);
::llcpp::fuchsia::posix::socket::StreamSocket::SyncClient client;
zx_status_t status;
ASSERT_EQ(status = fdio_fd_transfer(connfd.release(),
client.mutable_channel()->reset_and_get_address()),
ZX_OK)
<< zx_status_get_string(status);
auto response = client.Describe();
ASSERT_EQ(status = response.status(), ZX_OK) << zx_status_get_string(status);
const ::llcpp::fuchsia::io::NodeInfo& node_info = response.Unwrap()->info;
ASSERT_EQ(node_info.which(), ::llcpp::fuchsia::io::NodeInfo::Tag::kStreamSocket);
const zx::socket& socket = node_info.stream_socket().socket;
zx_signals_t pending;
ASSERT_EQ(status = socket.wait_one(ZX_USER_SIGNAL_1 | ZX_USER_SIGNAL_3, zx::time::infinite_past(),
&pending),
ZX_OK)
<< zx_status_get_string(status);
EXPECT_TRUE(pending & ZX_USER_SIGNAL_1);
EXPECT_TRUE(pending & ZX_USER_SIGNAL_3);
}
TEST(SocketTest, CloseClonedSocketAfterTcpRst) {
// Create the listening endpoint (server).
fbl::unique_fd serverfd;
ASSERT_TRUE(serverfd = 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(serverfd.get(), reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), 0)
<< strerror(errno);
ASSERT_EQ(listen(serverfd.get(), 1), 0) << strerror(errno);
// Get the address the server is listening on.
socklen_t addrlen = sizeof(addr);
ASSERT_EQ(getsockname(serverfd.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 0)
<< strerror(errno);
ASSERT_EQ(addrlen, sizeof(addr));
// Connect to the listening endpoint (client).
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);
// Accept the new connection (client) on the listening endpoint (server).
fbl::unique_fd connfd;
ASSERT_TRUE(connfd = fbl::unique_fd(accept(serverfd.get(), nullptr, nullptr))) << strerror(errno);
ASSERT_EQ(close(serverfd.release()), 0) << strerror(errno);
// Clone the file descriptor a bunch of times to increase the refcount.
std::array<fbl::unique_fd, 10> connfds;
for (auto& clonefd : connfds) {
{
zx_status_t status;
zx::channel channel;
ASSERT_EQ(status = fdio_fd_clone(connfd.get(), channel.reset_and_get_address()), ZX_OK)
<< zx_status_get_string(status);
ASSERT_EQ(status = fdio_fd_create(channel.release(), clonefd.reset_and_get_address()), ZX_OK)
<< zx_status_get_string(status);
}
}
// Fill up the rcvbuf (client-side).
fill_stream_send_buf(connfd.get(), clientfd.get());
// Closing the client-side connection while it has data that has not been
// read by the client should trigger a TCP RST.
ASSERT_EQ(close(clientfd.release()), 0) << strerror(errno);
std::vector<pollfd> pfd;
for (auto const& fd : connfds) {
pfd.push_back({
.fd = fd.get(),
.events = POLLOUT,
});
}
int n = poll(pfd.data(), pfd.size(), kTimeout);
ASSERT_GE(n, 0) << strerror(errno);
ASSERT_EQ(static_cast<size_t>(n), pfd.size());
for (auto const& pfd : pfd) {
ASSERT_EQ(pfd.revents, POLLOUT | POLLERR | POLLHUP);
}
// Now that the socket's endpoint has been closed, clone the socket again to increase the
// endpoint's reference count, then close all copies of the socket.
std::array<::llcpp::fuchsia::posix::socket::StreamSocket::SyncClient, 10> clients;
for (auto& client : clients) {
zx_status_t status;
ASSERT_EQ(
status = fdio_fd_clone(connfd.get(), client.mutable_channel()->reset_and_get_address()),
ZX_OK)
<< zx_status_get_string(status);
}
for (auto& client : clients) {
auto response = client.Close();
zx_status_t status;
EXPECT_EQ(status = response.status(), ZX_OK) << zx_status_get_string(status);
EXPECT_EQ(status = response.Unwrap()->s, ZX_OK) << zx_status_get_string(status);
}
ASSERT_EQ(close(connfd.release()), 0) << strerror(errno);
for (auto& fd : connfds) {
ASSERT_EQ(close(fd.release()), 0) << strerror(errno);
}
}
TEST(SocketTest, PassFD) {
fbl::unique_fd listener;
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(), 1), 0) << strerror(errno);
zx::process proc;
{
fbl::unique_fd client;
ASSERT_TRUE(client = fbl::unique_fd(socket(AF_INET, SOCK_STREAM, 0))) << strerror(errno);
ASSERT_EQ(connect(client.get(), addr, addr_len), 0) << strerror(errno);
std::array<fdio_spawn_action_t, 2> actions = {
fdio_spawn_action_t{
.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd =
{
.local_fd = client.get(),
.target_fd = STDIN_FILENO,
},
},
fdio_spawn_action_t{
.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd =
{
.local_fd = client.get(),
.target_fd = STDOUT_FILENO,
},
},
};
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH] = {};
constexpr char bin_path[] = "/bin/cat";
const char* argv[] = {bin_path, nullptr};
ASSERT_OK(fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_STDIO,
bin_path, argv, nullptr, actions.size(), actions.data(),
proc.reset_and_get_address(), err_msg))
<< err_msg;
ASSERT_EQ(close(client.release()), 0) << strerror(errno);
}
fbl::unique_fd conn;
ASSERT_TRUE(conn = fbl::unique_fd(accept(listener.get(), nullptr, nullptr))) << strerror(errno);
constexpr char out[] = "hello";
ASSERT_EQ(write(conn.get(), out, sizeof(out)), (ssize_t)sizeof(out)) << strerror(errno);
ASSERT_EQ(shutdown(conn.get(), SHUT_WR), 0) << strerror(errno);
ASSERT_OK(proc.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr));
char in[sizeof(out) + 1];
ASSERT_EQ(read(conn.get(), in, sizeof(in)), (ssize_t)sizeof(out)) << strerror(errno);
ASSERT_STREQ(in, out);
}