| // 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/bin/guest/vmm/device/test_with_device.h" |
| #include "garnet/bin/guest/vmm/device/virtio_queue_fake.h" |
| |
| #include <fuchsia/netstack/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/component/cpp/startup_context.h> |
| #include <trace-provider/provider.h> |
| |
| #include <virtio/net.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; |
| |
| class VirtioNetTest : public TestWithDevice, |
| public fuchsia::netstack::Netstack { |
| public: |
| void GetPortForService(::std::string service, |
| fuchsia::netstack::Protocol protocol, |
| GetPortForServiceCallback callback) override {} |
| |
| void GetAddress(::std::string address, uint16_t port, |
| GetAddressCallback callback) override {} |
| |
| void GetInterfaces(GetInterfacesCallback callback) override {} |
| |
| void GetRouteTable(GetRouteTableCallback callback) override {} |
| |
| void GetStats(uint32_t nicid, GetStatsCallback callback) override {} |
| |
| void GetAggregateStats(GetAggregateStatsCallback callback) override {} |
| |
| void SetInterfaceStatus(uint32_t nicid, bool enabled) override {} |
| |
| void SetInterfaceAddress(uint32_t nicid, fuchsia::net::IpAddress addr, |
| uint8_t prefixLen, |
| SetInterfaceAddressCallback callback) override {} |
| |
| void RemoveInterfaceAddress( |
| uint32_t nicid, fuchsia::net::IpAddress addr, uint8_t prefixLen, |
| RemoveInterfaceAddressCallback callback) override {} |
| |
| void SetDhcpClientStatus(uint32_t nicid, bool enabled, |
| SetDhcpClientStatusCallback callback) override {} |
| |
| void BridgeInterfaces(::std::vector<uint32_t> nicids, |
| BridgeInterfacesCallback callback) override {} |
| |
| void SetNameServers( |
| ::std::vector<fuchsia::net::IpAddress> servers) override {} |
| |
| void AddEthernetDevice( |
| ::std::string topological_path, |
| fuchsia::netstack::InterfaceConfig interfaceConfig, |
| ::fidl::InterfaceHandle<::fuchsia::hardware::ethernet::Device> device, |
| AddEthernetDeviceCallback callback) override { |
| eth_device_ = device.Bind(); |
| eth_device_added_ = true; |
| } |
| |
| void StartRouteTableTransaction( |
| ::fidl::InterfaceRequest<fuchsia::netstack::RouteTableTransaction> |
| routeTableTransaction, |
| StartRouteTableTransactionCallback callback) override {} |
| |
| 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::guest::device::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.ConnectToService(net_.NewRequest()); |
| status = net_->Start(std::move(start_info)); |
| ASSERT_EQ(ZX_OK, status); |
| |
| // Wait for the device to call AddEthernetDevice on the netstack. |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] { return eth_device_added_; }, |
| zx::sec(5))); |
| |
| // 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, ZX_VMO_NON_RESIZABLE, &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); |
| status = |
| net_->ConfigureQueue(i, q->size(), q->desc(), q->avail(), q->used()); |
| ASSERT_EQ(ZX_OK, status); |
| } |
| |
| eth_device_->Start([](zx_status_t status) { ASSERT_EQ(ZX_OK, status); }); |
| } |
| |
| fuchsia::guest::device::VirtioNetSyncPtr net_; |
| VirtioQueueFake rx_queue_; |
| VirtioQueueFake tx_queue_; |
| ::fidl::BindingSet<fuchsia::netstack::Netstack> bindings_; |
| fuchsia::hardware::ethernet::DevicePtr eth_device_; |
| bool eth_device_added_ = false; |
| |
| 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)); |
| |
| fuchsia::hardware::ethernet::FifoEntry 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); |
| |
| status = net_->NotifyQueue(0); |
| ASSERT_EQ(status, ZX_OK); |
| |
| 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, fuchsia::hardware::ethernet::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); |
| |
| const uint8_t expected_packet[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| memcpy(reinterpret_cast<void*>(&data), expected_packet, packet_size); |
| |
| fuchsia::hardware::ethernet::FifoEntry 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(); |
| |
| status = net_->NotifyQueue(1); |
| 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(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, fuchsia::hardware::ethernet::FIFO_RX_OK); |
| EXPECT_EQ(entry.cookie, 0xdeadbeef); |
| } |