blob: b407beb2da21ef37b52db3502ea9dcb9b9896ece [file] [log] [blame]
// Copyright 2018 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.
#include "src/virtualization/bin/vmm/virtio_vsock.h"
#include <iterator>
#include <gtest/gtest.h>
#include "src/lib/testing/loop_fixture/test_loop_fixture.h"
#include "src/virtualization/bin/vmm/phys_mem_fake.h"
#include "src/virtualization/bin/vmm/virtio_device_fake.h"
#include "src/virtualization/bin/vmm/virtio_queue_fake.h"
namespace {
using ::fuchsia::virtualization::HostVsockConnector_Connect_Response;
using ::fuchsia::virtualization::HostVsockConnector_Connect_Result;
static constexpr size_t kDataSize = 4;
struct RxBuffer {
// The number of virtio descriptors to use for this buffer.
static constexpr size_t kNumDescriptors = 3;
// The number of used bytes, as reported by the device when the descriptor
// was returned.
uint32_t used_bytes;
virtio_vsock_hdr_t header __ALIGNED(16);
uint8_t data[kDataSize] __ALIGNED(16);
uint8_t data2[kDataSize] __ALIGNED(16);
};
// Ensure we have padding bytes in our structure so we don't have contiguous
// descriptors.
static_assert(offsetof(RxBuffer, data) > sizeof(RxBuffer::header),
"RxBuffer::data is adjacent to RxBuffer::header");
static_assert(offsetof(RxBuffer, data2) > offsetof(RxBuffer, data) + sizeof(RxBuffer::data),
"RxBuffer::data2 is adjacent to RxBuffer::data");
static constexpr uint32_t kVirtioVsockHostPort = 22;
static constexpr uint32_t kVirtioVsockGuestCid = 3;
static constexpr uint32_t kVirtioVsockGuestPort = 23;
static constexpr uint32_t kVirtioVsockGuestEphemeralPort = 1024;
static constexpr uint16_t kVirtioVsockRxBuffers = 8;
static constexpr uint16_t kVirtioVsockQueueSize = kVirtioVsockRxBuffers * RxBuffer::kNumDescriptors;
static const std::vector<uint8_t> kDefaultData = {1, 9, 8, 5};
struct ConnectionRequest {
uint32_t src_cid;
uint32_t src_port;
uint32_t cid;
uint32_t port;
fuchsia::virtualization::HostVsockConnector::ConnectCallback callback;
};
class TestConnection {
public:
TestConnection() {
FX_CHECK(zx::socket::create(ZX_SOCKET_STREAM, &socket_, &remote_socket_) == ZX_OK);
}
uint32_t count = 0;
zx_status_t status = ZX_ERR_BAD_STATE;
fuchsia::virtualization::GuestVsockAcceptor::AcceptCallback callback() {
return [this](fuchsia::virtualization::GuestVsockAcceptor_Accept_Result result) {
count++;
status = result.is_err() ? result.err() : ZX_OK;
};
}
zx::socket& socket() { return socket_; }
zx::socket take_remote() { return std::move(remote_socket_); }
bool remote_closed() const {
zx_signals_t observed = 0;
zx_status_t status = socket_.wait_one(__ZX_OBJECT_PEER_CLOSED, zx::time(), &observed);
switch (status) {
case ZX_ERR_TIMED_OUT:
return false;
case ZX_OK:
return observed & __ZX_OBJECT_PEER_CLOSED;
default:
FX_CHECK(false) << "Unexpected status " << status;
__UNREACHABLE;
}
}
zx_status_t write(const uint8_t* data, uint32_t size, size_t* actual) {
return socket_.write(0, data, size, actual);
}
zx_status_t read(uint8_t* data, uint32_t size, size_t* actual) {
return socket_.read(0, data, size, actual);
}
private:
zx::socket socket_;
zx::socket remote_socket_;
};
using CreateCallback = fit::function<void(zx::socket*, zx::socket*)>;
static void CreateSocket(zx::socket* socket_handle, zx::socket* remote_socket_handle) {
zx::socket socket, remote_socket;
ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &socket, &remote_socket));
*socket_handle = std::move(socket);
*remote_socket_handle = std::move(remote_socket);
}
class VirtioVsockTest : public ::gtest::TestLoopFixture,
public fuchsia::virtualization::HostVsockConnector {
public:
VirtioVsockTest()
: vsock_(nullptr, phys_mem_, dispatcher()),
rx_queue_(vsock_.rx_queue(), kVirtioVsockQueueSize),
tx_queue_(vsock_.tx_queue(), kVirtioVsockQueueSize) {}
void SetUp() override {
FillRxQueue();
// Set up a GuestVsockEndpoint and GuestVsockAcceptor interface connected
// to the VirtioVsock object.
vsock_.Bind(endpoint_.NewRequest());
endpoint_->SetContextId(kVirtioVsockGuestCid, connector_binding_.NewBinding(),
acceptor_.NewRequest());
endpoint_.events().OnShutdown = [this](uint64_t local_cid, uint32_t local_port,
uint64_t guest_cid, uint32_t remote_port) {
received_shutdown_events_.push_back({local_cid, local_port, guest_cid, remote_port});
};
RunLoopUntilIdle();
}
protected:
struct ShutdownEvent {
uint64_t local_cid;
uint32_t local_port;
uint64_t guest_cid;
uint32_t remote_port;
};
PhysMemFake phys_mem_;
VirtioVsock vsock_;
VirtioQueueFake rx_queue_;
VirtioQueueFake tx_queue_;
fuchsia::virtualization::GuestVsockEndpointPtr endpoint_;
fuchsia::virtualization::GuestVsockAcceptorPtr acceptor_;
fidl::Binding<fuchsia::virtualization::HostVsockConnector> connector_binding_{this};
std::vector<zx::handle> remote_handles_;
std::vector<ConnectionRequest> connection_requests_;
std::vector<ConnectionRequest> connections_established_;
std::vector<ShutdownEvent> received_shutdown_events_;
RxBuffer rx_buffers[kVirtioVsockRxBuffers] = {};
// Set some default credit parameters that should suffice for most tests.
// Tests of the credit system will want to assign a more reasonable buf_alloc
// value.
uint32_t buf_alloc = UINT32_MAX;
uint32_t fwd_cnt = 0;
// |fuchsia::virtualization::HostVsockConnector|
void Connect(uint32_t src_cid, uint32_t src_port, uint32_t cid, uint32_t port,
fuchsia::virtualization::HostVsockConnector::ConnectCallback callback) override {
connection_requests_.emplace_back(
ConnectionRequest{src_cid, src_port, cid, port, std::move(callback)});
}
void VerifyHeader(RxBuffer* buffer, uint32_t host_port, uint32_t guest_port, size_t len,
uint16_t op, uint32_t flags) {
virtio_vsock_hdr_t* header = &buffer->header;
EXPECT_EQ(header->src_cid, fuchsia::virtualization::HOST_CID);
EXPECT_EQ(header->dst_cid, kVirtioVsockGuestCid);
EXPECT_EQ(header->src_port, host_port);
EXPECT_EQ(header->dst_port, guest_port);
EXPECT_EQ(header->len, len);
EXPECT_EQ(header->type, VIRTIO_VSOCK_TYPE_STREAM);
EXPECT_EQ(header->op, op);
EXPECT_EQ(header->flags, flags);
// Verify the bytes reported in the virtio used element matches
// how many bytes specified in our header.
EXPECT_EQ(buffer->used_bytes, header->len + sizeof(*header));
}
// Read a packet sent from the host to the guest.
RxBuffer* DoReceive() {
RunLoopUntilIdle();
if (!rx_queue_.HasUsed()) {
return nullptr;
}
vring_used_elem used_elem = rx_queue_.NextUsed();
RxBuffer* buffer = &rx_buffers[used_elem.id / RxBuffer::kNumDescriptors];
buffer->used_bytes = used_elem.len;
return buffer;
}
// Send a packet from the guest to the host.
void DoSend(uint32_t host_port, uint32_t guest_cid, uint32_t guest_port, uint16_t type,
uint16_t op, uint32_t flags = 0) {
virtio_vsock_hdr_t tx_header = {
.src_cid = guest_cid,
.dst_cid = fuchsia::virtualization::HOST_CID,
.src_port = guest_port,
.dst_port = host_port,
.type = type,
.op = op,
.flags = flags,
.buf_alloc = this->buf_alloc,
.fwd_cnt = this->fwd_cnt,
};
ASSERT_EQ(tx_queue_.BuildDescriptor().AppendReadable(&tx_header, sizeof(tx_header)).Build(),
ZX_OK);
RunLoopUntilIdle();
}
void HostConnectOnPortRequest(uint32_t host_port, TestConnection* connection) {
acceptor_->Accept(fuchsia::virtualization::HOST_CID, host_port, kVirtioVsockGuestPort,
connection->take_remote(), connection->callback());
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, host_port, kVirtioVsockGuestPort, 0, VIRTIO_VSOCK_OP_REQUEST, 0);
}
void HostConnectOnPortResponse(uint32_t host_port) {
DoSend(host_port, kVirtioVsockGuestCid, kVirtioVsockGuestPort, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_RESPONSE);
}
void FillRxQueue() {
for (size_t i = 0; i < std::size(rx_buffers); ++i) {
ASSERT_EQ(rx_queue_.BuildDescriptor()
.AppendWritable(&rx_buffers[i].header, sizeof(rx_buffers[i].header))
.AppendWritable(&rx_buffers[i].data, sizeof(rx_buffers[i].data))
.AppendWritable(&rx_buffers[i].data2, sizeof(rx_buffers[i].data2))
.Build(),
ZX_OK);
}
}
void HostReadOnPort(uint32_t host_port, TestConnection* connection,
std::vector<uint8_t> expected = kDefaultData) {
size_t actual;
ASSERT_EQ(ZX_OK,
connection->write(expected.data(), static_cast<uint32_t>(expected.size()), &actual));
EXPECT_EQ(expected.size(), actual);
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, host_port, kVirtioVsockGuestPort, expected.size(), VIRTIO_VSOCK_OP_RW,
0);
// Verify the data, which may be spread across multiple descriptors.
EXPECT_EQ(memcmp(rx_buffer->data, expected.data(),
expected.size() > kDataSize ? kDataSize : expected.size()),
0);
if (expected.size() > kDataSize) {
EXPECT_EQ(memcmp(rx_buffer->data2, expected.data() + kDataSize, expected.size() - kDataSize),
0);
}
}
void HostQueueWriteOnPort(uint32_t host_port, uint8_t* tx_buffer, size_t len) {
auto tx_header = reinterpret_cast<virtio_vsock_hdr_t*>(tx_buffer);
ASSERT_GE(len, sizeof(*tx_header));
*tx_header = {
.src_cid = kVirtioVsockGuestCid,
.dst_cid = fuchsia::virtualization::HOST_CID,
.src_port = kVirtioVsockGuestPort,
.dst_port = host_port,
.len = static_cast<uint32_t>(len - sizeof(*tx_header)),
.type = VIRTIO_VSOCK_TYPE_STREAM,
.op = VIRTIO_VSOCK_OP_RW,
.buf_alloc = this->buf_alloc,
.fwd_cnt = this->fwd_cnt,
};
ASSERT_EQ(tx_queue_.BuildDescriptor().AppendReadable(tx_buffer, len).Build(), ZX_OK);
}
void HostWriteOnPort(uint32_t host_port, TestConnection* connection) {
uint8_t tx_buffer[sizeof(virtio_vsock_hdr_t) + kDataSize] = {};
uint8_t expected_data[kDataSize] = {2, 3, 0, 1};
memcpy(tx_buffer + sizeof(virtio_vsock_hdr_t), expected_data, kDataSize);
HostQueueWriteOnPort(host_port, tx_buffer, sizeof(tx_buffer));
RunLoopUntilIdle();
uint8_t actual_data[kDataSize];
size_t actual;
ASSERT_EQ(ZX_OK, connection->read(actual_data, sizeof(actual_data), &actual));
EXPECT_EQ(actual, kDataSize);
EXPECT_EQ(memcmp(actual_data, expected_data, kDataSize), 0);
}
void HostConnectOnPort(uint32_t host_port, TestConnection* connection) {
HostConnectOnPortRequest(host_port, connection);
HostConnectOnPortResponse(host_port);
RunLoopUntilIdle();
ASSERT_EQ(1u, connection->count);
ASSERT_EQ(ZX_OK, connection->status);
ASSERT_FALSE(connection->remote_closed());
HostReadOnPort(host_port, connection);
}
void HostShutdownOnPort(uint32_t host_port, uint32_t flags) {
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, host_port, kVirtioVsockGuestPort, 0, VIRTIO_VSOCK_OP_SHUTDOWN, flags);
}
void ExpectHostResetOnPort(uint32_t host_port) {
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, host_port, kVirtioVsockGuestPort, 0, VIRTIO_VSOCK_OP_RST, 0);
}
void GuestConnectOnPortRequest(uint32_t host_port, uint32_t guest_port) {
DoSend(host_port, kVirtioVsockGuestCid, guest_port, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_REQUEST);
RunLoopUntilIdle();
}
void GuestConnectInvokeCallbacks(zx_status_t status, CreateCallback create_callback) {
for (auto it = connection_requests_.begin(); it != connection_requests_.end();
it = connection_requests_.erase(it)) {
zx::socket socket, remote_socket;
if (status == ZX_OK) {
create_callback(&socket, &remote_socket);
remote_handles_.emplace_back(std::move(remote_socket));
}
it->callback(status == ZX_OK ? HostVsockConnector_Connect_Result::WithResponse(
HostVsockConnector_Connect_Response(std::move(socket)))
: HostVsockConnector_Connect_Result::WithErr(std::move(status)));
connections_established_.emplace_back(
ConnectionRequest{it->src_cid, it->src_port, it->cid, it->port, nullptr});
RunLoopUntilIdle();
}
}
void GuestConnectOnPortResponse(uint32_t host_port, uint16_t op, uint32_t guest_port) {
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, host_port, guest_port, 0, op, 0);
if (remote_handles_.empty()) {
EXPECT_EQ(rx_buffer->header.buf_alloc, 0u);
} else {
EXPECT_GT(rx_buffer->header.buf_alloc, 0u);
}
EXPECT_EQ(rx_buffer->header.fwd_cnt, 0u);
}
void GuestConnectOnPort(uint32_t host_port, uint32_t guest_port, CreateCallback create_callback) {
GuestConnectOnPortRequest(host_port, guest_port);
GuestConnectInvokeCallbacks(ZX_OK, std::move(create_callback));
GuestConnectOnPortResponse(host_port, VIRTIO_VSOCK_OP_RESPONSE, guest_port);
}
virtio_vsock_hdr_t* GetCreditUpdate() {
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid, kVirtioVsockGuestPort,
VIRTIO_VSOCK_TYPE_STREAM, VIRTIO_VSOCK_OP_CREDIT_REQUEST);
RxBuffer* rx_buffer = DoReceive();
if (rx_buffer == nullptr) {
return nullptr;
}
VerifyHeader(rx_buffer, kVirtioVsockHostPort, kVirtioVsockGuestPort, 0,
VIRTIO_VSOCK_OP_CREDIT_UPDATE, 0);
return &rx_buffer->header;
}
void SendCreditUpdate(uint32_t host_port, uint32_t guest_port) {
DoSend(host_port, kVirtioVsockGuestCid, guest_port, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_CREDIT_UPDATE);
}
// Return a list of `OnShutdown` events that have been received from the
// virtio-vsock device to the endpoints.
const std::vector<ShutdownEvent>& received_shutdown_events() { return received_shutdown_events_; }
};
TEST_F(VirtioVsockTest, Connect) {
TestConnection connection;
HostConnectOnPort(kVirtioVsockHostPort, &connection);
}
TEST_F(VirtioVsockTest, ConnectMultipleTimes) {
{
TestConnection connection;
HostConnectOnPort(kVirtioVsockHostPort, &connection);
}
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_BOTH);
{
TestConnection connection;
HostConnectOnPort(kVirtioVsockHostPort + 1000, &connection);
}
}
TEST_F(VirtioVsockTest, ConnectMultipleTimesSamePort) {
{
TestConnection connection;
HostConnectOnPort(kVirtioVsockHostPort, &connection);
}
TestConnection connection;
acceptor_->Accept(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort, kVirtioVsockGuestPort,
connection.take_remote(), connection.callback());
RunLoopUntilIdle();
ASSERT_EQ(1u, connection.count);
ASSERT_EQ(ZX_ERR_ALREADY_BOUND, connection.status);
ASSERT_TRUE(connection.remote_closed());
}
TEST_F(VirtioVsockTest, ConnectEarlyData) {
TestConnection connection;
// Put some initial data on the connection
size_t actual;
ASSERT_EQ(
connection.write(kDefaultData.data(), static_cast<uint32_t>(kDefaultData.size()), &actual),
ZX_OK);
HostConnectOnPort(kVirtioVsockHostPort, &connection);
}
TEST_F(VirtioVsockTest, ConnectRefused) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
// Test connection reset.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid, kVirtioVsockGuestPort,
VIRTIO_VSOCK_TYPE_STREAM, VIRTIO_VSOCK_OP_RST);
RunLoopUntilIdle();
ASSERT_EQ(1u, connection.count);
ASSERT_EQ(ZX_ERR_CONNECTION_REFUSED, connection.status);
ASSERT_TRUE(connection.remote_closed());
EXPECT_FALSE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort,
kVirtioVsockGuestPort));
}
TEST_F(VirtioVsockTest, Listen) {
GuestConnectOnPort(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort, CreateSocket);
ASSERT_EQ(1u, connections_established_.size());
ASSERT_TRUE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
}
TEST_F(VirtioVsockTest, ListenMultipleTimes) {
GuestConnectOnPort(kVirtioVsockHostPort + 1, kVirtioVsockGuestEphemeralPort + 1, CreateSocket);
GuestConnectOnPort(kVirtioVsockHostPort + 2, kVirtioVsockGuestEphemeralPort + 2, CreateSocket);
ASSERT_EQ(2u, connections_established_.size());
ASSERT_TRUE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort + 1,
kVirtioVsockGuestEphemeralPort + 1));
ASSERT_TRUE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort + 2,
kVirtioVsockGuestEphemeralPort + 2));
}
TEST_F(VirtioVsockTest, ListenMultipleTimesSameHostPort) {
GuestConnectOnPort(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort, CreateSocket);
GuestConnectOnPort(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort + 1, CreateSocket);
EXPECT_TRUE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
EXPECT_TRUE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort + 1));
}
TEST_F(VirtioVsockTest, ListenMultipleTimesSamePort) {
GuestConnectOnPort(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort, CreateSocket);
EXPECT_TRUE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
GuestConnectOnPortRequest(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort);
GuestConnectOnPortResponse(kVirtioVsockHostPort, VIRTIO_VSOCK_OP_RST,
kVirtioVsockGuestEphemeralPort);
EXPECT_FALSE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
}
TEST_F(VirtioVsockTest, ListenRefused) {
GuestConnectOnPortRequest(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort);
GuestConnectInvokeCallbacks(ZX_ERR_CONNECTION_REFUSED, CreateSocket);
GuestConnectOnPortResponse(kVirtioVsockHostPort, VIRTIO_VSOCK_OP_RST,
kVirtioVsockGuestEphemeralPort);
EXPECT_FALSE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
}
TEST_F(VirtioVsockTest, ListenWrongCid) {
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid + 1000, kVirtioVsockGuestEphemeralPort,
VIRTIO_VSOCK_TYPE_STREAM, VIRTIO_VSOCK_OP_REQUEST);
RunLoopUntilIdle();
EXPECT_FALSE(vsock_.HasConnection(fuchsia::virtualization::HOST_CID, kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
}
TEST_F(VirtioVsockTest, Reset) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
connection.socket().reset();
RunLoopUntilIdle();
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_BOTH);
}
// The device should not send any response to a spurious reset packet.
TEST_F(VirtioVsockTest, NoResponseToSpuriousReset) {
// Send a reset from the guest.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid, kVirtioVsockGuestPort,
VIRTIO_VSOCK_TYPE_STREAM, VIRTIO_VSOCK_OP_RST);
RunLoopUntilIdle();
// Expect no response from the host.
RxBuffer* buffer = DoReceive();
EXPECT_EQ(nullptr, buffer);
}
TEST_F(VirtioVsockTest, ShutdownRead) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
ASSERT_EQ(connection.socket().set_disposition(ZX_SOCKET_DISPOSITION_WRITE_DISABLED, 0), ZX_OK);
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_RECV);
}
TEST_F(VirtioVsockTest, ShutdownWrite) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
ASSERT_EQ(connection.socket().set_disposition(0, ZX_SOCKET_DISPOSITION_WRITE_DISABLED), ZX_OK);
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_SEND);
}
// Ensure endpoints are notified when a socket has been shutdown.
TEST_F(VirtioVsockTest, ShutdownNotifiesEndpoint) {
TestConnection connection;
// Establish a connection between the host and guest.
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
// Have the guest shutdown the stream.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid, kVirtioVsockGuestPort,
VIRTIO_VSOCK_TYPE_STREAM, VIRTIO_VSOCK_OP_SHUTDOWN,
/*flags=*/VIRTIO_VSOCK_FLAG_SHUTDOWN_BOTH);
RunLoopUntilIdle();
// Ensure the host reset the connection.
ExpectHostResetOnPort(kVirtioVsockHostPort);
RunLoopUntilIdle();
// Ensure the endpoint was sent a shutdown event.
ASSERT_EQ(received_shutdown_events().size(), 1u);
const ShutdownEvent& shutdown_event = received_shutdown_events()[0];
EXPECT_EQ(shutdown_event.guest_cid, kVirtioVsockGuestCid);
EXPECT_EQ(shutdown_event.local_cid, fuchsia::virtualization::HOST_CID);
EXPECT_EQ(shutdown_event.local_port, kVirtioVsockHostPort);
EXPECT_EQ(shutdown_event.remote_port, kVirtioVsockGuestPort);
}
// Endpoints should not be sent OnShutdown events when virtio-vsock is
// merely responding to spurious packets.
TEST_F(VirtioVsockTest, SpuriousPacketsDoNotNotifyEndpoints) {
for (uint16_t packet_op : std::vector<uint16_t>{
VIRTIO_VSOCK_OP_SHUTDOWN,
VIRTIO_VSOCK_OP_RESPONSE,
VIRTIO_VSOCK_OP_CREDIT_UPDATE,
VIRTIO_VSOCK_OP_CREDIT_REQUEST,
VIRTIO_VSOCK_OP_INVALID,
VIRTIO_VSOCK_OP_RW,
}) {
SCOPED_TRACE(testing::Message() << "Testing packet operation #" << packet_op);
// Guest sends a spurious shutdown event.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid, kVirtioVsockGuestPort,
VIRTIO_VSOCK_TYPE_STREAM, packet_op);
RunLoopUntilIdle();
// We expect a reset from the host.
ExpectHostResetOnPort(kVirtioVsockHostPort);
RunLoopUntilIdle();
// Ensure that no shutdown events were sent to the endpoints.
EXPECT_TRUE(received_shutdown_events().empty());
}
}
TEST_F(VirtioVsockTest, WriteAfterShutdown) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
ASSERT_EQ(connection.socket().set_disposition(0, ZX_SOCKET_DISPOSITION_WRITE_DISABLED), ZX_OK);
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_SEND);
// Test write after shutdown.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid, kVirtioVsockGuestPort,
VIRTIO_VSOCK_TYPE_STREAM, VIRTIO_VSOCK_OP_RW);
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, kVirtioVsockHostPort, kVirtioVsockGuestPort, 0, VIRTIO_VSOCK_OP_RST, 0);
}
TEST_F(VirtioVsockTest, Read) {
// Fill a single data buffer in the RxBuffer.
std::vector<uint8_t> data = {1, 2, 3, 4};
ASSERT_EQ(data.size(), kDataSize);
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
HostReadOnPort(kVirtioVsockHostPort, &connection, data);
HostReadOnPort(kVirtioVsockHostPort, &connection, data);
}
TEST_F(VirtioVsockTest, ReadChained) {
// Fill both data buffers in the RxBuffer.
std::vector<uint8_t> data = {1, 2, 3, 4, 5, 6, 7, 8};
ASSERT_EQ(data.size(), 2 * kDataSize);
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
HostReadOnPort(kVirtioVsockHostPort, &connection, data);
HostReadOnPort(kVirtioVsockHostPort, &connection, data);
}
TEST_F(VirtioVsockTest, ReadNoBuffer) {
// Set the guest buf_alloc to something smaller than our data transfer.
buf_alloc = 2;
std::vector<uint8_t> expected = {1, 2, 3, 4};
ASSERT_EQ(expected.size(), 2 * buf_alloc);
// Setup connection.
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
// Write data to socket.
size_t actual;
ASSERT_EQ(connection.socket().write(0, expected.data(), expected.size(), &actual), ZX_OK);
EXPECT_EQ(actual, expected.size());
// Expect the guest to pull off |buf_alloc| bytes.
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, kVirtioVsockHostPort, kVirtioVsockGuestPort, buf_alloc,
VIRTIO_VSOCK_OP_RW, 0);
EXPECT_EQ(memcmp(rx_buffer->data, expected.data(), buf_alloc), 0);
// Update credit to indicate the in-flight bytes have been free'd.
fwd_cnt += buf_alloc;
SendCreditUpdate(kVirtioVsockHostPort, kVirtioVsockGuestPort);
// Expect to receive the remaining bytes
rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, kVirtioVsockHostPort, kVirtioVsockGuestPort, buf_alloc,
VIRTIO_VSOCK_OP_RW, 0);
EXPECT_EQ(memcmp(rx_buffer->data, expected.data() + buf_alloc, buf_alloc), 0);
}
TEST_F(VirtioVsockTest, Write) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
HostWriteOnPort(kVirtioVsockHostPort, &connection);
HostWriteOnPort(kVirtioVsockHostPort, &connection);
}
struct SingleBytePacket {
virtio_vsock_hdr_t header;
char c;
SingleBytePacket(char c_) : c(c_) {}
} __PACKED;
TEST_F(VirtioVsockTest, WriteMultiple) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
SingleBytePacket p1('a');
SingleBytePacket p2('b');
HostQueueWriteOnPort(kVirtioVsockHostPort, reinterpret_cast<uint8_t*>(&p1), sizeof(p1));
HostQueueWriteOnPort(kVirtioVsockHostPort, reinterpret_cast<uint8_t*>(&p2), sizeof(p2));
RunLoopUntilIdle();
size_t actual_len = 0;
uint8_t actual_data[3] = {};
ASSERT_EQ(connection.socket().read(0, actual_data, sizeof(actual_data), &actual_len), ZX_OK);
ASSERT_EQ(2u, actual_len);
ASSERT_EQ('a', actual_data[0]);
ASSERT_EQ('b', actual_data[1]);
}
TEST_F(VirtioVsockTest, WriteUpdateCredit) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
SingleBytePacket p1('a');
SingleBytePacket p2('b');
HostQueueWriteOnPort(kVirtioVsockHostPort, reinterpret_cast<uint8_t*>(&p1), sizeof(p1));
HostQueueWriteOnPort(kVirtioVsockHostPort, reinterpret_cast<uint8_t*>(&p2), sizeof(p2));
RunLoopUntilIdle();
// Request credit update, expect 0 fwd_cnt bytes as the data is still in the
// socket.
virtio_vsock_hdr_t* header = GetCreditUpdate();
EXPECT_EQ(header->op, VIRTIO_VSOCK_OP_CREDIT_UPDATE);
EXPECT_GT(header->buf_alloc, 0u);
EXPECT_EQ(header->fwd_cnt, 0u);
// Read from socket.
size_t actual_len = 0;
uint8_t actual_data[3] = {};
ASSERT_EQ(connection.socket().read(0, actual_data, sizeof(actual_data), &actual_len), ZX_OK);
ASSERT_EQ(2u, actual_len);
ASSERT_EQ('a', actual_data[0]);
ASSERT_EQ('b', actual_data[1]);
// Request credit update, expect 2 fwd_cnt bytes as the data has been
// extracted from the socket.
header = GetCreditUpdate();
EXPECT_EQ(header->op, VIRTIO_VSOCK_OP_CREDIT_UPDATE);
EXPECT_GT(header->buf_alloc, 0u);
EXPECT_EQ(header->fwd_cnt, 2u);
}
TEST_F(VirtioVsockTest, WriteSocketFullReset) {
// If the guest writes enough bytes to overflow our socket buffer then we
// must reset the connection as we would lose data.
//
// 5.7.6.3.1: VIRTIO_VSOCK_OP_RW data packets MUST only be transmitted when
// the peer has sufficient free buffer space for the payload.
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
zx_info_socket_t info = {};
ASSERT_EQ(ZX_OK,
connection.socket().get_info(ZX_INFO_SOCKET, &info, sizeof(info), nullptr, nullptr));
size_t buf_size = info.tx_buf_max + sizeof(virtio_vsock_hdr_t) + 1;
auto buf = std::make_unique<uint8_t[]>(buf_size);
memset(buf.get(), 'a', buf_size);
// Queue one descriptor that will completely fill the socket (and then some),
// We'll verify that this resets the connection.
HostQueueWriteOnPort(kVirtioVsockHostPort, buf.get(), buf_size);
RunLoopUntilIdle();
RxBuffer* reset = DoReceive();
ASSERT_NE(nullptr, reset);
VerifyHeader(reset, kVirtioVsockHostPort, kVirtioVsockGuestPort, 0, VIRTIO_VSOCK_OP_RST, 0);
}
TEST_F(VirtioVsockTest, SendCreditUpdateWhenSocketIsDrained) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
// Fill socket buffer completely.
zx_info_socket_t info = {};
ASSERT_EQ(ZX_OK,
connection.socket().get_info(ZX_INFO_SOCKET, &info, sizeof(info), nullptr, nullptr));
size_t buf_size = info.tx_buf_max + sizeof(virtio_vsock_hdr_t);
auto buf = std::make_unique<uint8_t[]>(buf_size);
memset(buf.get(), 'a', buf_size);
HostQueueWriteOnPort(kVirtioVsockHostPort, buf.get(), buf_size);
RunLoopUntilIdle();
// No buffers should be available to read.
ASSERT_EQ(nullptr, DoReceive());
// Read a single byte from socket to free up space in the socket buffer and
// make the socket writable again.
memset(buf.get(), 0, buf_size);
uint8_t byte;
size_t actual_len = 0;
ASSERT_EQ(connection.socket().read(0, &byte, 1, &actual_len), ZX_OK);
ASSERT_EQ(1u, actual_len);
ASSERT_EQ('a', byte);
// Verify we get a credit update now that the socket is wirtable.
RunLoopUntilIdle();
RxBuffer* credit_update = DoReceive();
ASSERT_NE(credit_update, nullptr);
ASSERT_EQ(info.tx_buf_max, credit_update->header.buf_alloc);
ASSERT_EQ(actual_len, credit_update->header.fwd_cnt);
}
TEST_F(VirtioVsockTest, MultipleConnections) {
TestConnection a_connection;
HostConnectOnPortRequest(kVirtioVsockHostPort + 1000, &a_connection);
HostConnectOnPortResponse(kVirtioVsockHostPort + 1000);
TestConnection b_connection;
HostConnectOnPortRequest(kVirtioVsockHostPort + 2000, &b_connection);
HostConnectOnPortResponse(kVirtioVsockHostPort + 2000);
for (auto i = 0; i < (kVirtioVsockRxBuffers / 4); i++) {
HostReadOnPort(kVirtioVsockHostPort + 1000, &a_connection);
HostReadOnPort(kVirtioVsockHostPort + 2000, &b_connection);
HostWriteOnPort(kVirtioVsockHostPort + 1000, &a_connection);
HostWriteOnPort(kVirtioVsockHostPort + 2000, &b_connection);
}
}
TEST_F(VirtioVsockTest, InvalidBuffer) {
zx::socket mock_socket;
ConnectionKey mock_key{0, 0, 0, 0};
std::unique_ptr<VirtioVsock::Connection> conn =
VirtioVsock::Connection::Create(mock_key, std::move(mock_socket), nullptr, nullptr, nullptr);
conn.get()->SetCredit(0, 2);
ASSERT_EQ(conn.get()->op(), VIRTIO_VSOCK_OP_RST);
}
TEST_F(VirtioVsockTest, CreditRequest) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, &connection);
HostConnectOnPortResponse(kVirtioVsockHostPort);
// Test credit request.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid, kVirtioVsockGuestPort,
VIRTIO_VSOCK_TYPE_STREAM, VIRTIO_VSOCK_OP_CREDIT_REQUEST);
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, kVirtioVsockHostPort, kVirtioVsockGuestPort, 0,
VIRTIO_VSOCK_OP_CREDIT_UPDATE, 0);
EXPECT_GT(rx_buffer->header.buf_alloc, 0u);
EXPECT_EQ(rx_buffer->header.fwd_cnt, 0u);
}
TEST_F(VirtioVsockTest, UnsupportedSocketType) {
// Test connection request with invalid type.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestCid, kVirtioVsockGuestPort, UINT16_MAX,
VIRTIO_VSOCK_OP_REQUEST);
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
virtio_vsock_hdr_t* rx_header = &rx_buffer->header;
EXPECT_EQ(rx_header->src_cid, fuchsia::virtualization::HOST_CID);
EXPECT_EQ(rx_header->dst_cid, kVirtioVsockGuestCid);
EXPECT_EQ(rx_header->src_port, kVirtioVsockHostPort);
EXPECT_EQ(rx_header->dst_port, kVirtioVsockGuestPort);
EXPECT_EQ(rx_header->type, VIRTIO_VSOCK_TYPE_STREAM);
EXPECT_EQ(rx_header->op, VIRTIO_VSOCK_OP_RST);
EXPECT_EQ(rx_header->flags, 0u);
}
TEST_F(VirtioVsockTest, InitialCredit) {
GuestConnectOnPort(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort, CreateSocket);
ASSERT_TRUE(remote_handles_.size() == 1);
zx::socket socket(std::move(remote_handles_.back()));
std::vector<uint8_t> expected = {1, 2, 3, 4};
// Write data to socket.
size_t actual;
ASSERT_EQ(socket.write(0, expected.data(), expected.size(), &actual), ZX_OK);
EXPECT_EQ(actual, expected.size());
// Expect that the connection has correct initial credit and so the guest
// will be able to pull out the data.
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(rx_buffer, kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort, expected.size(),
VIRTIO_VSOCK_OP_RW, 0);
EXPECT_EQ(memcmp(rx_buffer->data, expected.data(), expected.size()), 0);
}
TEST(VirtioVsockChain, AllocateAndFree) {
VirtioDeviceFake device;
VirtioQueue* queue = device.queue();
VirtioQueueFake* fake_queue = device.queue_fake();
// Add an item to the queue.
virtio_vsock_hdr_t header = {
.src_port = 1234,
};
uint16_t index;
fake_queue->BuildDescriptor().AppendReadable(&header, sizeof(header)).Build(&index);
// Ensure we can take the item off the queue and read the header value from it.
std::optional<VsockChain> chain = VsockChain::FromQueue(queue, /*writable=*/false);
ASSERT_TRUE(chain.has_value());
EXPECT_EQ(chain->header()->src_port, 1234u);
EXPECT_TRUE(!queue->HasAvail());
// Return the item.
chain->Return(/*used=*/0);
EXPECT_TRUE(fake_queue->HasUsed());
}
TEST(VirtioVsockChain, AllocateEmptyQueue) {
VirtioDeviceFake device;
// Attempt to take an item off the queue. It should fail.
std::optional<VsockChain> chain = VsockChain::FromQueue(device.queue(), /*writable=*/false);
EXPECT_FALSE(chain.has_value());
}
TEST(VirtioVsockChain, AllocateSkipsBadDescriptors) {
VirtioDeviceFake device;
VirtioQueue* queue = device.queue();
VirtioQueueFake* fake_queue = device.queue_fake();
// Add a too-short descriptor.
uint8_t byte;
uint16_t too_small_id;
fake_queue->BuildDescriptor().AppendReadable(&byte, sizeof(byte)).Build(&too_small_id);
// Add a writable descriptor when the caller will ask for a readable descriptor.
virtio_vsock_hdr_t writable_header{};
uint16_t writable_header_id;
fake_queue->BuildDescriptor()
.AppendWritable(&writable_header, sizeof(writable_header))
.Build(&writable_header_id);
// Add a valid descriptor.
virtio_vsock_hdr_t header{
.src_port = 1234,
};
uint16_t valid_id;
fake_queue->BuildDescriptor().AppendReadable(&header, sizeof(header)).Build(&valid_id);
// Ensure the valid descriptor was read.
std::optional<VsockChain> chain = VsockChain::FromQueue(queue, /*writable=*/false);
ASSERT_TRUE(chain.has_value());
EXPECT_EQ(chain->header()->src_port, 1234u);
// Ensure the two invalid descriptors were returned.
EXPECT_EQ(too_small_id, fake_queue->NextUsed().id);
EXPECT_EQ(writable_header_id, fake_queue->NextUsed().id);
chain->Return(/*used=*/0);
}
} // namespace