blob: 42d5e282fe09d5d2e3bc36f48844174cb699451e [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.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async_promise/executor.h>
#include <lib/fit/bridge.h>
#include <lib/fit/defer.h>
#include <lib/fit/promise.h>
#include <lib/fit/scope.h>
#include <lib/trace-provider/provider.h>
#include <lib/zx/fifo.h>
#include <zircon/device/ethernet.h>
#include <memory>
#include <queue>
#include <virtio/net.h>
#include "guest_ethernet.h"
#include "src/virtualization/bin/vmm/device/device_base.h"
#include "src/virtualization/bin/vmm/device/stream_base.h"
static constexpr char kInterfacePath[] = "/dev/class/ethernet/virtio";
static constexpr char kInterfaceName[] = "ethv0";
static constexpr uint8_t kIpv4Address[4] = {10, 0, 0, 1};
static constexpr uint8_t kPrefixLength = 24;
enum class Queue : uint16_t {
RECEIVE = 0,
TRANSMIT = 1,
};
class RxStream : public StreamBase {
public:
void Init(GuestEthernet* guest_ethernet, const PhysMem& phys_mem,
VirtioQueue::InterruptFn interrupt) {
guest_ethernet_ = guest_ethernet;
phys_mem_ = &phys_mem;
StreamBase::Init(phys_mem, std::move(interrupt));
}
void Notify() {
for (; !packet_queue_.empty() && queue_.NextChain(&chain_); chain_.Return()) {
Packet pkt = packet_queue_.front();
chain_.NextDescriptor(&desc_);
if (desc_.len < sizeof(virtio_net_hdr_t)) {
FXL_LOG(ERROR) << "Malformed descriptor";
continue;
}
auto header = static_cast<virtio_net_hdr_t*>(desc_.addr);
// Section 5.1.6.4.1 Device Requirements: Processing of Incoming Packets
// If VIRTIO_NET_F_MRG_RXBUF has not been negotiated, the device MUST
// set num_buffers to 1.
header->num_buffers = 1;
// If none of the VIRTIO_NET_F_GUEST_TSO4, TSO6 or UFO options have been
// negotiated, the device MUST set gso_type to VIRTIO_NET_HDR_GSO_NONE.
header->gso_type = VIRTIO_NET_HDR_GSO_NONE;
// If VIRTIO_NET_F_GUEST_CSUM is not negotiated, the device MUST set
// flags to zero and SHOULD supply a fully checksummed packet to the
// driver.
header->flags = 0;
uintptr_t offset = phys_mem_->offset(header + 1);
uintptr_t length = desc_.len - sizeof(*header);
packet_queue_.pop();
if (length < pkt.length) {
// 5.1.6.3.1 Driver Requirements: Setting Up Receive Buffers: the driver
// SHOULD populate the receive queue(s) with buffers of at least 1526
// bytes.
// If the descriptor is too small for the packet then the driver is
// misbehaving (our MTU is 1500).
FXL_LOG(ERROR) << "Dropping packet that's too large for the descriptor";
continue;
}
memcpy(phys_mem_->as<void>(offset, length), reinterpret_cast<void*>(pkt.addr), pkt.length);
*chain_.Used() = pkt.length + sizeof(*header);
pkt.entry.flags = ETH_FIFO_TX_OK;
guest_ethernet_->Complete(pkt.entry);
}
}
void Receive(uintptr_t addr, size_t length, const eth_fifo_entry_t& entry) {
packet_queue_.push(Packet{addr, length, entry});
Notify();
}
private:
struct Packet {
uintptr_t addr;
size_t length;
eth_fifo_entry_t entry;
};
GuestEthernet* guest_ethernet_ = nullptr;
const PhysMem* phys_mem_ = nullptr;
std::queue<Packet> packet_queue_;
};
class TxStream : public StreamBase {
public:
void Init(GuestEthernet* guest_ethernet, const PhysMem& phys_mem,
VirtioQueue::InterruptFn interrupt) {
guest_ethernet_ = guest_ethernet;
phys_mem_ = &phys_mem;
StreamBase::Init(phys_mem, std::move(interrupt));
}
void Notify() {
for (; queue_.NextChain(&chain_); chain_.Return()) {
chain_.NextDescriptor(&desc_);
if (desc_.has_next) {
// Section 5.1.6.2 Packet Transmission: The header and packet are added
// as one output descriptor to the transmitq.
static bool warned = false;
if (!warned) {
warned = true;
FXL_LOG(ERROR) << "Transmit packet and header must be on a single descriptor";
}
continue;
}
if (desc_.len < sizeof(virtio_net_hdr_t)) {
FXL_LOG(ERROR) << "Failed to read descriptor header";
continue;
}
auto header = static_cast<virtio_net_hdr_t*>(desc_.addr);
uintptr_t offset = phys_mem_->offset(header + 1);
uintptr_t length = desc_.len - sizeof(*header);
guest_ethernet_->Send(phys_mem_->as<void>(offset, length), length);
}
}
private:
GuestEthernet* guest_ethernet_ = nullptr;
const PhysMem* phys_mem_ = nullptr;
};
class VirtioNetImpl : public DeviceBase<VirtioNetImpl>,
public fuchsia::virtualization::hardware::VirtioNet,
public GuestEthernetReceiver {
public:
VirtioNetImpl(sys::ComponentContext* context) : DeviceBase(context), context_(*context) {
netstack_ = context_.svc()->Connect<fuchsia::netstack::Netstack>();
}
// |fuchsia::virtualization::hardware::VirtioDevice|
void NotifyQueue(uint16_t queue) override {
switch (static_cast<Queue>(queue)) {
case Queue::RECEIVE:
rx_stream_.Notify();
break;
case Queue::TRANSMIT:
tx_stream_.Notify();
break;
default:
FXL_CHECK(false) << "Queue index " << queue << " out of range";
__UNREACHABLE;
}
}
// Called by GuestEthernet to notify us when the netstack is trying to send a
// packet to the guest.
void Receive(uintptr_t addr, size_t length, const eth_fifo_entry_t& entry) override {
rx_stream_.Receive(addr, length, entry);
}
private:
// |fuchsia::virtualization::hardware::VirtioNet|
void Start(fuchsia::virtualization::hardware::StartInfo start_info,
StartCallback callback) override {
PrepStart(std::move(start_info));
fuchsia::net::Ipv4Address ipv4;
memcpy(ipv4.addr.data(), kIpv4Address, 4);
fuchsia::net::IpAddress addr;
addr.set_ipv4(ipv4);
fuchsia::net::Subnet subnet;
subnet.addr.set_ipv4(ipv4);
subnet.prefix_len = kPrefixLength;
fuchsia::netstack::InterfaceConfig config;
config.name = kInterfaceName;
config.ip_address_config.set_static_ip(std::move(subnet));
executor_.schedule_task(
AddEthernetDevice(kInterfacePath, std::move(config))
.and_then([this, addr = std::move(addr)](const uint32_t& nic_id) mutable {
return SetInterfaceAddress(nic_id, std::move(addr));
})
.and_then([this](const uint32_t& nic_id) mutable {
netstack_->SetInterfaceStatus(nic_id, true);
})
.or_else([]() mutable { FXL_CHECK(false) << "Failed to set ethernet IP address."; })
.and_then([this]() {
rx_stream_.Init(
&guest_ethernet_, phys_mem_,
fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioNetImpl::Interrupt));
tx_stream_.Init(
&guest_ethernet_, phys_mem_,
fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioNetImpl::Interrupt));
})
.and_then(std::move(callback))
.wrap_with(scope_));
}
fit::promise<uint32_t> AddEthernetDevice(const char* interface_path,
fuchsia::netstack::InterfaceConfig config) {
fit::bridge<uint32_t> bridge;
netstack_->AddEthernetDevice(interface_path, std::move(config), device_binding_.NewBinding(),
[completer = std::move(bridge.completer)](uint32_t id) mutable {
completer.complete_ok(id);
});
return bridge.consumer.promise();
}
fit::promise<uint32_t> SetInterfaceAddress(uint32_t nic_id, fuchsia::net::IpAddress addr) {
fit::bridge<uint32_t> bridge;
netstack_->SetInterfaceAddress(
nic_id, std::move(addr), kPrefixLength,
[nic_id, completer = std::move(bridge.completer)](fuchsia::netstack::NetErr err) mutable {
if (err.status == fuchsia::netstack::Status::OK) {
completer.complete_ok(nic_id);
} else {
FXL_LOG(ERROR) << "Failed to set interface address with "
<< static_cast<uint32_t>(err.status) << " " << err.message;
completer.complete_error();
}
});
return bridge.consumer.promise();
}
// |fuchsia::virtualization::hardware::VirtioDevice|
void ConfigureQueue(uint16_t queue, uint16_t size, zx_gpaddr_t desc, zx_gpaddr_t avail,
zx_gpaddr_t used, ConfigureQueueCallback callback) override {
auto deferred = fit::defer(std::move(callback));
switch (static_cast<Queue>(queue)) {
case Queue::RECEIVE:
rx_stream_.Configure(size, desc, avail, used);
break;
case Queue::TRANSMIT:
tx_stream_.Configure(size, desc, avail, used);
break;
default:
FXL_CHECK(false) << "Queue index " << queue << " out of range";
__UNREACHABLE;
}
}
// |fuchsia::virtualization::hardware::VirtioDevice|
void Ready(uint32_t negotiated_features, ReadyCallback callback) override {
negotiated_features_ = negotiated_features;
callback();
}
sys::ComponentContext& context_;
GuestEthernet guest_ethernet_{this};
fidl::Binding<fuchsia::hardware::ethernet::Device> device_binding_ =
fidl::Binding<fuchsia::hardware::ethernet::Device>(&guest_ethernet_);
fuchsia::netstack::NetstackPtr netstack_;
RxStream rx_stream_;
TxStream tx_stream_;
uint32_t negotiated_features_;
fit::scope scope_;
async::Executor executor_ = async::Executor(async_get_default_dispatcher());
};
int main(int argc, char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
trace::TraceProviderWithFdio trace_provider(loop.dispatcher());
std::unique_ptr<sys::ComponentContext> context = sys::ComponentContext::Create();
VirtioNetImpl virtio_net(context.get());
return loop.Run();
}