blob: 165f8ed48352426530f70f36021a838205312089 [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 "garnet/lib/machina/virtio_vsock.h"
#include "garnet/lib/machina/phys_mem_fake.h"
#include "garnet/lib/machina/virtio_queue_fake.h"
#include "gtest/gtest.h"
namespace machina {
namespace {
static constexpr size_t kDataSize = 4;
struct RxBuffer {
// The number of virtio descriptors to use for this buffer.
static constexpr size_t kNumDescriptors = 3;
virtio_vsock_hdr_t header;
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_port;
uint32_t cid;
uint32_t port;
fuchsia::guest::SocketConnector::ConnectCallback callback;
};
struct TestConnection {
uint32_t count = 0;
zx_status_t status = ZX_ERR_BAD_STATE;
zx::socket socket;
fuchsia::guest::SocketAcceptor::AcceptCallback callback() {
return [this](zx_status_t status, zx::socket socket) {
count++;
this->status = status;
this->socket = std::move(socket);
};
}
};
class VirtioVsockTest : public testing::Test,
public fuchsia::guest::SocketConnector {
public:
VirtioVsockTest()
: vsock_(nullptr, phys_mem_, loop_.async()),
rx_queue_(vsock_.rx_queue()),
tx_queue_(vsock_.tx_queue()) {}
void SetUp() override {
ASSERT_EQ(rx_queue_.Init(kVirtioVsockQueueSize), ZX_OK);
ASSERT_EQ(tx_queue_.Init(kVirtioVsockQueueSize), ZX_OK);
FillRxQueue();
ASSERT_EQ(endpoint_binding_.Bind(endpoint_.NewRequest()), ZX_OK);
endpoint_->SetContextId(kVirtioVsockGuestCid,
connector_binding_.NewBinding(),
acceptor_.NewRequest());
loop_.RunUntilIdle();
}
protected:
PhysMemFake phys_mem_;
async::Loop loop_{&kAsyncLoopConfigMakeDefault};
VirtioVsock vsock_;
VirtioQueueFake rx_queue_;
VirtioQueueFake tx_queue_;
fidl::Binding<fuchsia::guest::SocketEndpoint> endpoint_binding_{&vsock_};
fuchsia::guest::SocketEndpointPtr endpoint_;
fuchsia::guest::SocketAcceptorPtr acceptor_;
fidl::Binding<fuchsia::guest::SocketConnector> connector_binding_{this};
std::vector<zx::socket> remote_sockets_;
std::vector<ConnectionRequest> connection_requests_;
std::vector<ConnectionRequest> connections_established_;
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::guest::SocketConnector|
void Connect(
uint32_t src_port, uint32_t cid, uint32_t port,
fuchsia::guest::SocketConnector::ConnectCallback callback) override {
connection_requests_.emplace_back(
ConnectionRequest{src_port, cid, port, std::move(callback)});
}
void VerifyHeader(virtio_vsock_hdr_t* header, uint32_t host_port,
uint32_t guest_port, uint32_t len, uint16_t op,
uint32_t flags) {
EXPECT_EQ(header->src_cid, fuchsia::guest::kHostCid);
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);
}
RxBuffer* DoReceive() {
loop_.RunUntilIdle();
if (!rx_queue_.HasUsed()) {
return nullptr;
}
vring_used_elem used_elem = rx_queue_.NextUsed();
return &rx_buffers[used_elem.id / RxBuffer::kNumDescriptors];
}
void DoSend(uint32_t host_port, uint32_t guest_port, uint16_t type,
uint16_t op) {
virtio_vsock_hdr_t tx_header = {
.src_cid = kVirtioVsockGuestCid,
.dst_cid = fuchsia::guest::kHostCid,
.src_port = guest_port,
.dst_port = host_port,
.type = type,
.op = op,
.buf_alloc = this->buf_alloc,
.fwd_cnt = this->fwd_cnt,
};
ASSERT_EQ(tx_queue_.BuildDescriptor()
.AppendReadable(&tx_header, sizeof(tx_header))
.Build(),
ZX_OK);
loop_.RunUntilIdle();
}
void HostConnectOnPortRequest(
uint32_t host_port,
fuchsia::guest::SocketAcceptor::AcceptCallback callback) {
acceptor_->Accept(fuchsia::guest::kHostCid, host_port,
kVirtioVsockGuestPort, std::move(callback));
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(&rx_buffer->header, host_port, kVirtioVsockGuestPort, 0,
VIRTIO_VSOCK_OP_REQUEST, 0);
}
void HostConnectOnPortResponse(uint32_t host_port) {
DoSend(host_port, kVirtioVsockGuestPort, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_RESPONSE);
}
void FillRxQueue() {
for (size_t i = 0; i < countof(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, zx::socket* socket,
std::vector<uint8_t> expected = kDefaultData) {
size_t actual;
ASSERT_EQ(socket->write(0, expected.data(), expected.size(), &actual),
ZX_OK);
EXPECT_EQ(actual, expected.size());
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(&rx_buffer->header, 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::guest::kHostCid,
.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,
};
ASSERT_EQ(
tx_queue_.BuildDescriptor().AppendReadable(tx_buffer, len).Build(),
ZX_OK);
}
void HostWriteOnPort(uint32_t host_port, zx::socket* socket) {
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));
loop_.RunUntilIdle();
uint8_t actual_data[kDataSize];
size_t actual;
ASSERT_EQ(socket->read(0, actual_data, sizeof(actual_data), &actual),
ZX_OK);
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.callback());
HostConnectOnPortResponse(host_port);
loop_.RunUntilIdle();
ASSERT_EQ(1u, connection.count);
ASSERT_EQ(ZX_OK, connection.status);
ASSERT_TRUE(connection.socket.is_valid());
HostReadOnPort(host_port, &connection.socket);
}
void HostShutdownOnPort(uint32_t host_port, uint32_t flags) {
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(&rx_buffer->header, host_port, kVirtioVsockGuestPort, 0,
VIRTIO_VSOCK_OP_SHUTDOWN, flags);
}
void GuestConnectOnPortRequest(uint32_t host_port, uint32_t guest_port) {
DoSend(host_port, guest_port, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_REQUEST);
loop_.RunUntilIdle();
}
void GuestConnectInvokeCallbacks(zx_status_t status = ZX_OK) {
for (auto it = connection_requests_.begin();
it != connection_requests_.end();
it = connection_requests_.erase(it)) {
zx::socket h1, h2;
if (status == ZX_OK) {
ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &h1, &h2));
remote_sockets_.emplace_back(std::move(h2));
}
it->callback(status, std::move(h1));
connections_established_.emplace_back(
ConnectionRequest{it->src_port, it->cid, it->port, nullptr});
loop_.RunUntilIdle();
}
}
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->header, host_port, guest_port, 0, op, 0);
if (op == VIRTIO_VSOCK_OP_RST) {
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 = kVirtioVsockGuestEphemeralPort) {
GuestConnectOnPortRequest(host_port, guest_port);
GuestConnectInvokeCallbacks();
GuestConnectOnPortResponse(host_port, VIRTIO_VSOCK_OP_RESPONSE, guest_port);
}
virtio_vsock_hdr_t* GetCreditUpdate() {
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestPort,
VIRTIO_VSOCK_TYPE_STREAM, VIRTIO_VSOCK_OP_CREDIT_REQUEST);
RxBuffer* rx_buffer = DoReceive();
if (rx_buffer == nullptr) {
return nullptr;
}
VerifyHeader(&rx_buffer->header, 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, guest_port, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_CREDIT_UPDATE);
}
};
TEST_F(VirtioVsockTest, Connect) { HostConnectOnPort(kVirtioVsockHostPort); }
TEST_F(VirtioVsockTest, ConnectMultipleTimes) {
HostConnectOnPort(kVirtioVsockHostPort);
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_BOTH);
HostConnectOnPort(kVirtioVsockHostPort + 1000);
}
TEST_F(VirtioVsockTest, ConnectMultipleTimesSamePort) {
HostConnectOnPort(kVirtioVsockHostPort);
TestConnection connection;
acceptor_->Accept(fuchsia::guest::kHostCid, kVirtioVsockHostPort,
kVirtioVsockGuestPort, connection.callback());
loop_.RunUntilIdle();
ASSERT_EQ(1u, connection.count);
ASSERT_EQ(ZX_ERR_ALREADY_BOUND, connection.status);
ASSERT_FALSE(connection.socket.is_valid());
}
TEST_F(VirtioVsockTest, ConnectRefused) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, connection.callback());
// Test connection reset.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestPort, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_RST);
loop_.RunUntilIdle();
ASSERT_EQ(1u, connection.count);
ASSERT_EQ(ZX_ERR_CONNECTION_REFUSED, connection.status);
ASSERT_FALSE(connection.socket.is_valid());
EXPECT_FALSE(vsock_.HasConnection(
fuchsia::guest::kHostCid, kVirtioVsockHostPort, kVirtioVsockGuestPort));
}
TEST_F(VirtioVsockTest, Listen) {
GuestConnectOnPort(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort);
ASSERT_EQ(1u, connections_established_.size());
ASSERT_TRUE(vsock_.HasConnection(fuchsia::guest::kHostCid,
kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
}
TEST_F(VirtioVsockTest, ListenMultipleTimes) {
GuestConnectOnPort(kVirtioVsockHostPort + 1,
kVirtioVsockGuestEphemeralPort + 1);
GuestConnectOnPort(kVirtioVsockHostPort + 2,
kVirtioVsockGuestEphemeralPort + 2);
ASSERT_EQ(2u, connections_established_.size());
ASSERT_TRUE(vsock_.HasConnection(fuchsia::guest::kHostCid,
kVirtioVsockHostPort + 1,
kVirtioVsockGuestEphemeralPort + 1));
ASSERT_TRUE(vsock_.HasConnection(fuchsia::guest::kHostCid,
kVirtioVsockHostPort + 2,
kVirtioVsockGuestEphemeralPort + 2));
}
TEST_F(VirtioVsockTest, ListenMultipleTimesSamePort) {
GuestConnectOnPort(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort);
GuestConnectOnPort(kVirtioVsockHostPort, kVirtioVsockGuestEphemeralPort + 1);
EXPECT_TRUE(vsock_.HasConnection(fuchsia::guest::kHostCid,
kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
EXPECT_TRUE(vsock_.HasConnection(fuchsia::guest::kHostCid,
kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort + 1));
}
TEST_F(VirtioVsockTest, ListenRefused) {
GuestConnectOnPortRequest(kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort);
GuestConnectInvokeCallbacks(ZX_ERR_CONNECTION_REFUSED);
GuestConnectOnPortResponse(kVirtioVsockHostPort, VIRTIO_VSOCK_OP_RST,
kVirtioVsockGuestEphemeralPort);
EXPECT_FALSE(vsock_.HasConnection(fuchsia::guest::kHostCid,
kVirtioVsockHostPort,
kVirtioVsockGuestEphemeralPort));
}
TEST_F(VirtioVsockTest, Reset) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, connection.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
connection.socket.reset();
loop_.RunUntilIdle();
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_BOTH);
}
TEST_F(VirtioVsockTest, ShutdownRead) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, connection.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
ASSERT_EQ(
connection.socket.write(ZX_SOCKET_SHUTDOWN_WRITE, nullptr, 0, nullptr),
ZX_OK);
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_RECV);
}
TEST_F(VirtioVsockTest, ShutdownWrite) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, connection.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
ASSERT_EQ(
connection.socket.write(ZX_SOCKET_SHUTDOWN_READ, nullptr, 0, nullptr),
ZX_OK);
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_SEND);
}
TEST_F(VirtioVsockTest, WriteAfterShutdown) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, connection.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
ASSERT_EQ(
connection.socket.write(ZX_SOCKET_SHUTDOWN_READ, nullptr, 0, nullptr),
ZX_OK);
HostShutdownOnPort(kVirtioVsockHostPort, VIRTIO_VSOCK_FLAG_SHUTDOWN_SEND);
// Test write after shutdown.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestPort, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_RW);
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(&rx_buffer->header, 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.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
HostReadOnPort(kVirtioVsockHostPort, &connection.socket, data);
HostReadOnPort(kVirtioVsockHostPort, &connection.socket, 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.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
HostReadOnPort(kVirtioVsockHostPort, &connection.socket, data);
HostReadOnPort(kVirtioVsockHostPort, &connection.socket, 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.callback());
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->header, 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->header, 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.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
HostWriteOnPort(kVirtioVsockHostPort, &connection.socket);
HostWriteOnPort(kVirtioVsockHostPort, &connection.socket);
}
struct SingleBytePacket {
virtio_vsock_hdr_t header;
char c;
SingleBytePacket(char c_) : c(c_) {}
} __PACKED;
TEST_F(VirtioVsockTest, WriteMultiple) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, connection.callback());
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));
loop_.RunUntilIdle();
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.callback());
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));
loop_.RunUntilIdle();
// 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.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
size_t socket_size = 0;
ASSERT_EQ(ZX_OK,
connection.socket.get_property(ZX_PROP_SOCKET_TX_BUF_MAX,
&socket_size, sizeof(socket_size)));
size_t buf_size = socket_size + 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);
loop_.RunUntilIdle();
RxBuffer* reset = DoReceive();
ASSERT_NE(nullptr, reset);
VerifyHeader(&reset->header, kVirtioVsockHostPort, kVirtioVsockGuestPort, 0,
VIRTIO_VSOCK_OP_RST, 0);
}
TEST_F(VirtioVsockTest, SendCreditUpdateWhenSocketIsDrained) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, connection.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
// Fill socket buffer completely.
size_t socket_size = 0;
ASSERT_EQ(ZX_OK,
connection.socket.get_property(ZX_PROP_SOCKET_TX_BUF_MAX,
&socket_size, sizeof(socket_size)));
size_t buf_size = socket_size + 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);
loop_.RunUntilIdle();
// 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.
loop_.RunUntilIdle();
RxBuffer* credit_update = DoReceive();
ASSERT_NE(credit_update, nullptr);
ASSERT_EQ(socket_size, 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.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort + 1000);
TestConnection b_connection;
HostConnectOnPortRequest(kVirtioVsockHostPort + 2000,
b_connection.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort + 2000);
for (auto i = 0; i < (kVirtioVsockRxBuffers / 4); i++) {
HostReadOnPort(kVirtioVsockHostPort + 1000, &a_connection.socket);
HostReadOnPort(kVirtioVsockHostPort + 2000, &b_connection.socket);
HostWriteOnPort(kVirtioVsockHostPort + 1000, &a_connection.socket);
HostWriteOnPort(kVirtioVsockHostPort + 2000, &b_connection.socket);
}
}
TEST_F(VirtioVsockTest, CreditRequest) {
TestConnection connection;
HostConnectOnPortRequest(kVirtioVsockHostPort, connection.callback());
HostConnectOnPortResponse(kVirtioVsockHostPort);
// Test credit request.
DoSend(kVirtioVsockHostPort, kVirtioVsockGuestPort, VIRTIO_VSOCK_TYPE_STREAM,
VIRTIO_VSOCK_OP_CREDIT_REQUEST);
RxBuffer* rx_buffer = DoReceive();
ASSERT_NE(nullptr, rx_buffer);
VerifyHeader(&rx_buffer->header, 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, 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::guest::kHostCid);
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);
}
} // namespace
} // namespace machina