blob: d99203542e9f5a328605ee6e74e4c8afd29427d5 [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 <fuchsia/netstack/cpp/fidl_test_base.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/trace-provider/provider.h>
#include <zircon/device/ethernet.h>
#include <virtio/net.h>
#include "src/virtualization/bin/vmm/device/test_with_device.h"
#include "src/virtualization/bin/vmm/device/virtio_queue_fake.h"
static constexpr char kVirtioNetUrl[] = "fuchsia-pkg://fuchsia.com/virtio_net#meta/virtio_net.cmx";
static constexpr size_t kNumQueues = 2;
static constexpr uint16_t kQueueSize = 16;
static constexpr size_t kVmoSize = 1024;
static constexpr uint16_t kFakeInterfaceId = 0;
class VirtioNetTest : public TestWithDevice, public fuchsia::netstack::testing::Netstack_TestBase {
protected:
VirtioNetTest()
: rx_queue_(phys_mem_, PAGE_SIZE * kNumQueues, kQueueSize),
tx_queue_(phys_mem_, rx_queue_.end(), kQueueSize) {}
void SetUp() override {
auto env_services = CreateServices();
// Add our fake netstack service.
ASSERT_EQ(ZX_OK, env_services->AddService(bindings_.GetHandler(this)));
// Launch device process.
fuchsia::virtualization::hardware::StartInfo start_info;
zx_status_t status =
LaunchDevice(kVirtioNetUrl, tx_queue_.end(), &start_info, std::move(env_services));
ASSERT_EQ(ZX_OK, status);
// Start device execution.
services_->Connect(net_.NewRequest());
RunLoopUntilIdle();
fuchsia::hardware::ethernet::MacAddress mac_address = {
.octets = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
};
// Start the device, waiting for it to complete before attempting to use it.
bool started = false;
net_->Start(std::move(start_info), mac_address, true /* enable_bridge */,
[&started] { started = true; });
RunLoopUntil([&] { return started; });
// Get fifos.
eth_device_->GetFifos(
[this](zx_status_t status, std::unique_ptr<fuchsia::hardware::ethernet::Fifos> fifos) {
ASSERT_EQ(status, ZX_OK);
rx_ = std::move(fifos->rx);
tx_ = std::move(fifos->tx);
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] { return (rx_ && tx_); }, zx::sec(5)));
size_t vmo_size = kVmoSize;
status = zx::vmo::create(vmo_size, 0, &vmo_);
ASSERT_EQ(status, ZX_OK);
zx::vmo vmo_dup;
status = vmo_.duplicate(ZX_RIGHTS_IO | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER, &vmo_dup);
ASSERT_EQ(status, ZX_OK);
eth_device_->SetIOBuffer(std::move(vmo_dup), [](zx_status_t status) {
ASSERT_EQ(status, ZX_OK) << "Failed to set IO buffer";
});
// Configure device queues.
VirtioQueueFake* queues[kNumQueues] = {&rx_queue_, &tx_queue_};
for (size_t i = 0; i < kNumQueues; i++) {
auto q = queues[i];
q->Configure(PAGE_SIZE * i, PAGE_SIZE);
net_->ConfigureQueue(i, q->size(), q->desc(), q->avail(), q->used(), [] {});
}
bool eth_started = false;
eth_device_->Start([&eth_started](zx_status_t status) {
ASSERT_EQ(ZX_OK, status);
eth_started = true;
});
RunLoopUntil([&] { return eth_started; });
}
// Fake |fuchsia.netstack.Netstack| implementation.
void AddEthernetDevice(std::string topological_path,
fuchsia::netstack::InterfaceConfig interfaceConfig,
fidl::InterfaceHandle<::fuchsia::hardware::ethernet::Device> device,
AddEthernetDeviceCallback callback) override {
eth_device_ = device.Bind();
callback(fuchsia::netstack::Netstack_AddEthernetDevice_Result::WithResponse(
fuchsia::netstack::Netstack_AddEthernetDevice_Response{kFakeInterfaceId}));
}
// Fake |fuchsia.netstack.Netstack| implementation.
void GetInterfaces(GetInterfacesCallback callback) override {
// Return a single fake interface.
std::vector<fuchsia::netstack::NetInterface> interfaces;
interfaces.push_back({
.id = 0,
.flags = fuchsia::netstack::Flags::UP,
.addr = fuchsia::net::IpAddress::WithIpv4(fuchsia::net::Ipv4Address()),
.netmask = fuchsia::net::IpAddress::WithIpv4(fuchsia::net::Ipv4Address()),
.broadaddr = fuchsia::net::IpAddress::WithIpv4(fuchsia::net::Ipv4Address()),
});
callback(std::move(interfaces));
}
// Fake |fuchsia.netstack.Netstack| implementation.
void BridgeInterfaces(std::vector<uint32_t> nicids, BridgeInterfacesCallback callback) override {
callback(fuchsia::netstack::NetErr{.status = fuchsia::netstack::Status::OK}, /*nicid=*/0);
}
void NotImplemented_(const std::string& name) override {
printf("Not implemented: Netstack::%s\n", name.data());
}
// Note: use of sync can be problematic here if the test environment needs to handle
// some incoming FIDL requests.
fuchsia::virtualization::hardware::VirtioNetPtr net_;
VirtioQueueFake rx_queue_;
VirtioQueueFake tx_queue_;
fidl::BindingSet<fuchsia::netstack::Netstack> bindings_;
fuchsia::hardware::ethernet::DevicePtr eth_device_;
zx::fifo rx_;
zx::fifo tx_;
zx::vmo vmo_;
};
TEST_F(VirtioNetTest, SendToGuest) {
const size_t packet_size = 10;
uint8_t* data;
zx_status_t status = DescriptorChainBuilder(rx_queue_)
.AppendWritableDescriptor(&data, sizeof(virtio_net_hdr_t) + packet_size)
.Build();
ASSERT_EQ(status, ZX_OK);
const uint8_t expected_packet[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
ASSERT_EQ(packet_size, sizeof(expected_packet));
vmo_.write(static_cast<const void*>(&expected_packet), 0, sizeof(expected_packet));
eth_fifo_entry_t entry{
.offset = 0,
.length = packet_size,
.flags = 0,
.cookie = 0xdeadbeef,
};
zx_signals_t pending = 0;
status = tx_.wait_one(ZX_FIFO_WRITABLE | ZX_FIFO_PEER_CLOSED, zx::deadline_after(zx::sec(5)),
&pending);
ASSERT_EQ(status, ZX_OK);
status = tx_.write(sizeof(entry), static_cast<void*>(&entry), 1, nullptr);
ASSERT_EQ(status, ZX_OK);
net_->NotifyQueue(0);
RunLoopUntilIdle();
status = tx_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::deadline_after(zx::sec(5)),
&pending);
ASSERT_EQ(status, ZX_OK);
status = tx_.read(sizeof(entry), static_cast<void*>(&entry), 1, nullptr);
ASSERT_EQ(status, ZX_OK);
status = WaitOnInterrupt();
ASSERT_EQ(status, ZX_OK);
virtio_net_hdr_t* hdr = reinterpret_cast<virtio_net_hdr_t*>(data);
EXPECT_EQ(hdr->num_buffers, 1);
EXPECT_EQ(hdr->gso_type, VIRTIO_NET_HDR_GSO_NONE);
EXPECT_EQ(hdr->flags, 0);
data += sizeof(virtio_net_hdr_t);
for (size_t i = 0; i != packet_size; ++i) {
EXPECT_EQ(data[i], expected_packet[i]);
}
EXPECT_EQ(entry.offset, 0u);
EXPECT_EQ(entry.length, packet_size);
EXPECT_EQ(entry.flags, ETH_FIFO_TX_OK);
EXPECT_EQ(entry.cookie, 0xdeadbeef);
}
TEST_F(VirtioNetTest, ReceiveFromGuest) {
const size_t packet_size = 10;
uint8_t data[packet_size + sizeof(virtio_net_hdr_t)];
zx_status_t status = DescriptorChainBuilder(tx_queue_)
.AppendReadableDescriptor(&data, sizeof(virtio_net_hdr_t) + packet_size)
.Build();
ASSERT_EQ(status, ZX_OK);
eth_fifo_entry_t entry{
.offset = 0,
.length = packet_size,
.flags = 0,
.cookie = 0xdeadbeef,
};
zx_signals_t pending = 0;
status = rx_.wait_one(ZX_FIFO_WRITABLE | ZX_FIFO_PEER_CLOSED, zx::deadline_after(zx::sec(5)),
&pending);
ASSERT_EQ(status, ZX_OK);
status = rx_.write(sizeof(entry), static_cast<void*>(&entry), 1, nullptr);
ASSERT_EQ(status, ZX_OK);
RunLoopUntilIdle();
net_->NotifyQueue(1);
RunLoopUntilIdle();
status = rx_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::deadline_after(zx::sec(5)),
&pending);
ASSERT_EQ(status, ZX_OK);
status = rx_.read(sizeof(entry), static_cast<void*>(&entry), 1, nullptr);
ASSERT_EQ(status, ZX_OK);
status = WaitOnInterrupt();
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(entry.offset, 0u);
EXPECT_EQ(entry.length, packet_size);
EXPECT_EQ(entry.flags, ETH_FIFO_RX_OK);
EXPECT_EQ(entry.cookie, 0xdeadbeef);
}
TEST_F(VirtioNetTest, ResumesReceiveFromGuest) {
// Build two descriptors.
const size_t packet_size = 10;
uint8_t data1[packet_size + sizeof(virtio_net_hdr_t)];
uint8_t data2[packet_size + sizeof(virtio_net_hdr_t)];
zx_status_t status = DescriptorChainBuilder(tx_queue_)
.AppendReadableDescriptor(&data1, sizeof(virtio_net_hdr_t) + packet_size)
.Build();
ASSERT_EQ(status, ZX_OK);
status = DescriptorChainBuilder(tx_queue_)
.AppendReadableDescriptor(&data2, sizeof(virtio_net_hdr_t) + packet_size)
.Build();
ASSERT_EQ(status, ZX_OK);
// Notify the device of the two descriptors we built.
net_->NotifyQueue(1);
RunLoopUntilIdle();
// Write one entry into the rx fifo.
eth_fifo_entry_t entry = {
.offset = 0,
.length = packet_size,
.flags = 0,
.cookie = 0xdeadbeef,
};
zx_signals_t pending = 0;
status = rx_.wait_one(ZX_FIFO_WRITABLE | ZX_FIFO_PEER_CLOSED, zx::deadline_after(zx::sec(5)),
&pending);
ASSERT_EQ(status, ZX_OK);
status = rx_.write(sizeof(entry), static_cast<void*>(&entry), 1, nullptr);
ASSERT_EQ(status, ZX_OK);
RunLoopUntilIdle();
// Attempt to ready two entries from the fifo. Since we only gave the device one entry, it should
// only return one to us.
status = rx_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::deadline_after(zx::sec(5)),
&pending);
ASSERT_EQ(status, ZX_OK);
size_t actual;
eth_fifo_entry_t entries[2];
status = rx_.read(sizeof(entries[0]), static_cast<void*>(&entries), 2, &actual);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(actual, 1u);
status = WaitOnInterrupt();
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(entries[0].offset, 0u);
EXPECT_EQ(entries[0].length, packet_size);
EXPECT_EQ(entries[0].flags, ETH_FIFO_RX_OK);
EXPECT_EQ(entries[0].cookie, 0xdeadbeef);
// Now write another entry into the fifo and the device should process the descriptor without
// being notified by the guest (i.e. without a call to NotifyQueue).
entry = {
.offset = 100,
.length = packet_size,
.flags = 0,
.cookie = 0x1337cafe,
};
status = rx_.wait_one(ZX_FIFO_WRITABLE | ZX_FIFO_PEER_CLOSED, zx::deadline_after(zx::sec(5)),
&pending);
ASSERT_EQ(status, ZX_OK);
status = rx_.write(sizeof(entry), static_cast<void*>(&entry), 1, nullptr);
ASSERT_EQ(status, ZX_OK);
RunLoopUntilIdle();
status = rx_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::deadline_after(zx::sec(5)),
&pending);
ASSERT_EQ(status, ZX_OK);
status = rx_.read(sizeof(entries[0]), static_cast<void*>(&entries), 2, &actual);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(actual, 1u);
status = WaitOnInterrupt();
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(entries[0].offset, 100u);
EXPECT_EQ(entries[0].length, packet_size);
EXPECT_EQ(entries[0].flags, ETH_FIFO_RX_OK);
EXPECT_EQ(entries[0].cookie, 0x1337cafeu);
}