From 2097ada2d2f1772cb241a2e13ac19401e8abcf29 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Mon, 8 Jan 2024 13:15:31 -0300 Subject: [PATCH] feat!: add VirtIO net device --- Dockerfile | 2 +- README.md | 9 +- src/Makefile | 31 ++- src/dtb.cpp | 10 + src/machine.cpp | 10 + src/virtio-net-carrier-slirp.cpp | 382 ++++++++++++++++++++++++++++++ src/virtio-net-carrier-slirp.h | 88 +++++++ src/virtio-net-carrier-tuntap.cpp | 215 +++++++++++++++++ src/virtio-net-carrier-tuntap.h | 48 ++++ src/virtio-net.cpp | 140 +++++++++++ src/virtio-net.h | 116 +++++++++ 11 files changed, 1034 insertions(+), 17 deletions(-) create mode 100644 src/virtio-net-carrier-slirp.cpp create mode 100644 src/virtio-net-carrier-slirp.h create mode 100644 src/virtio-net-carrier-tuntap.cpp create mode 100644 src/virtio-net-carrier-tuntap.h create mode 100644 src/virtio-net.cpp create mode 100644 src/virtio-net.h diff --git a/Dockerfile b/Dockerfile index 4fc79eb9e..f3880030f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ARG SANITIZE=no RUN apt-get update && \ DEBIAN_FRONTEND="noninteractive" apt-get install --no-install-recommends -y \ build-essential vim wget git clang-tidy-15 clang-format-15 lcov \ - libboost1.81-dev libssl-dev \ + libboost1.81-dev libssl-dev libslirp-dev \ ca-certificates pkg-config lua5.4 liblua5.4-dev \ libgrpc++-dev libprotobuf-dev protobuf-compiler-grpc \ luarocks xxd && \ diff --git a/README.md b/README.md index 462f5eb4e..8990ac981 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Docker targets: - GNU Make >= 3.81 - GRPC >= 1.45.0 - Lua >= 5.4.4 +- Libslirp >= 4.6.0 - Boost >= 1.81 Obs: Please note that Apple Clang Version number does not follow upstream LLVM/Clang. @@ -31,8 +32,8 @@ Obs: Please note that Apple Clang Version number does not follow upstream LLVM/C ```bash sudo apt-get install build-essential wget git clang-tidy-15 clang-format-15 \ - libboost1.81-dev libssl-dev \ - ca-certificates pkg-config lua5.4 liblua5.4-dev \ + libboost1.81-dev libssl-dev libslirp-dev \ + ca-certificates pkg-config lua5.4 liblua5.4-dev \ libgrpc++-dev libprotobuf-dev protobuf-compiler-grpc \ luarocks @@ -46,7 +47,7 @@ sudo luarocks install --lua-version=5.4 luaposix ##### MacPorts ```bash -sudo port install clang-15 boost libtool wget pkgconfig grpc openssl lua lua-luarocks +sudo port install clang-15 boost libtool wget pkgconfig grpc openssl lua lua-luarocks libslirp sudo luarocks install --lua-version=5.4 lpeg sudo luarocks install --lua-version=5.4 dkjson @@ -57,7 +58,7 @@ sudo luarocks install --lua-version=5.4 luaposix ##### Homebrew ```bash -brew install llvm@15 boost wget pkg-config grpc openssl lua luarocks +brew install llvm@15 boost wget pkg-config grpc openssl lua luarocks libslirp luarocks --lua-dir=$(brew --prefix)/opt/lua install lpeg luarocks --lua-dir=$(brew --prefix)/opt/lua install dkjson luarocks --lua-dir=$(brew --prefix)/opt/lua install luasocket diff --git a/src/Makefile b/src/Makefile index 92b4fc1bf..b57047245 100644 --- a/src/Makefile +++ b/src/Makefile @@ -76,16 +76,18 @@ endif # Homebrew installation ifneq (,$(shell which brew)) BREW_PREFIX = $(shell brew --prefix) -BOOST_LIB_DIR=-L$(BREW_PREFIX)/lib BOOST_INC=-I$(BREW_PREFIX)/include +SLIRP_LIB=-L$(PORT_PREFIX)/libslirp/lib -lslirp +SLIRP_INC=-I$(PORT_PREFIX)/libslirp/include GRPC_PROTOBUF_INC=$(shell pkg-config --cflags-only-I grpc++ protobuf) GRPC_PROTOBUF_LIB=$(shell pkg-config --libs grpc++ protobuf) # Macports installation else ifneq (,$(shell which port)) PORT_PREFIX = /opt/local -BOOST_LIB_DIR=-L$(PORT_PREFIX)/libexec/boost/1.81/lib BOOST_INC=-I$(PORT_PREFIX)/libexec/boost/1.81/include +SLIRP_LIB=-L$(PORT_PREFIX)/libslirp/lib -lslirp +SLIRP_INC=-I$(PORT_PREFIX)/libslirp/include GRPC_PROTOBUF_INC=-I$(PORT_PREFIX)/include GRPC_PROTOBUF_LIB=-L$(PORT_PREFIX)/lib -lgrpc++ -lgrpc -lgpr -lprotobuf -lpthread -labsl_synchronization else @@ -116,6 +118,8 @@ AR=ar rcs INCS= BOOST_INC= +SLIRP_INC= +SLIRP_LIB=-lslirp GRPC_PROTOBUF_INC=$(shell pkg-config --cflags-only-I grpc++ protobuf) GRPC_PROTOBUF_LIB=$(shell pkg-config --libs grpc++ protobuf) LIBCARTESI=libcartesi-$(EMULATOR_VERSION_MAJOR).$(EMULATOR_VERSION_MINOR).so @@ -128,15 +132,15 @@ PROFILE_DATA= endif -LIBCARTESI_LIBS= -LIBCARTESI_GRPC_LIBS=$(GRPC_PROTOBUF_LIB) -LIBCARTESI_JSONRPC_LIBS= -LUACARTESI_LIBS= -LUACARTESI_GRPC_LIBS=$(GRPC_PROTOBUF_LIB) -LUACARTESI_JSONRPC_LIBS= -REMOTE_CARTESI_MACHINE_LIBS=$(GRPC_PROTOBUF_LIB) -JSONRPC_REMOTE_CARTESI_MACHINE_LIBS= -TEST_MACHINE_C_API_LIBS=$(GRPC_PROTOBUF_LIB) +LIBCARTESI_LIBS=$(SLIRP_LIB) +LIBCARTESI_GRPC_LIBS=$(SLIRP_LIB) $(GRPC_PROTOBUF_LIB) +LIBCARTESI_JSONRPC_LIBS=$(SLIRP_LIB) +LUACARTESI_LIBS=$(SLIRP_LIB) +LUACARTESI_GRPC_LIBS=$(SLIRP_LIB) $(GRPC_PROTOBUF_LIB) +LUACARTESI_JSONRPC_LIBS=$(SLIRP_LIB) +REMOTE_CARTESI_MACHINE_LIBS=$(SLIRP_LIB) $(GRPC_PROTOBUF_LIB) +JSONRPC_REMOTE_CARTESI_MACHINE_LIBS=$(SLIRP_LIB) +TEST_MACHINE_C_API_LIBS=$(SLIRP_LIB) $(GRPC_PROTOBUF_LIB) HASH_LIBS= #DEFS+= -DMT_ALL_DIRTY @@ -149,7 +153,7 @@ INCS+= \ -I../third-party/tiny_sha3 \ -I../third-party/downloads \ -I../third-party/mongoose-7.12 \ - $(BOOST_INC) + $(SLIRP_INC) $(BOOST_INC) # Use 64-bit offsets for file operations in POSIX APIs DEFS+=-D_FILE_OFFSET_BITS=64 @@ -323,6 +327,9 @@ LIBCARTESI_OBJS:= \ virtio-device.o \ virtio-console.o \ virtio-p9fs.o \ + virtio-net.o \ + virtio-net-carrier-tuntap.o \ + virtio-net-carrier-slirp.o \ dtb.o \ os.o \ htif.o \ diff --git a/src/dtb.cpp b/src/dtb.cpp index b7f99d33c..178c8cdaf 100644 --- a/src/dtb.cpp +++ b/src/dtb.cpp @@ -161,6 +161,16 @@ void dtb_init(const machine_config &c, unsigned char *dtb_start, uint64_t dtb_le fdt.prop_u32_list<2>("interrupts-extended", {PLIC_PHANDLE, plic_irq_id}); fdt.end_node(); } + if (c.htif.console_getchar) { // virtio net + const uint32_t virtio_idx = 2; + const uint64_t virtio_paddr = PMA_FIRST_VIRTIO_START + virtio_idx * PMA_VIRTIO_LENGTH; + const uint32_t plic_irq_id = virtio_idx + 1; + fdt.begin_node_num("virtio", virtio_paddr); + fdt.prop_string("compatible", "virtio,mmio"); + fdt.prop_u64_list<2>("reg", {virtio_paddr, PMA_VIRTIO_LENGTH}); + fdt.prop_u32_list<2>("interrupts-extended", {PLIC_PHANDLE, plic_irq_id}); + fdt.end_node(); + } fdt.end_node(); } diff --git a/src/machine.cpp b/src/machine.cpp index 1ccf53b22..63b73d068 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -47,6 +47,9 @@ #include "unique-c-ptr.h" #include "virtio-console.h" #include "virtio-factory.h" +#include "virtio-net-carrier-slirp.h" +#include "virtio-net-carrier-tuntap.h" +#include "virtio-net.h" #include "virtio-p9fs.h" /// \file @@ -456,6 +459,13 @@ machine::machine(const machine_config &c, const machine_runtime_config &r) : make_virtio_pma_entry(PMA_FIRST_VIRTIO_START + vdev_p9fs->get_virtio_index() * PMA_VIRTIO_LENGTH, PMA_VIRTIO_LENGTH, "VirtIO p9fs device", &virtio_driver, vdev_p9fs.get())); m_vdevs.push_back(std::move(vdev_p9fs)); + + // Register VirtIO network device + auto vdev_net = std::make_unique(m_vdevs.size(), std::make_unique()); + register_pma_entry( + make_virtio_pma_entry(PMA_FIRST_VIRTIO_START + vdev_net->get_virtio_index() * PMA_VIRTIO_LENGTH, + PMA_VIRTIO_LENGTH, "VirtIO network device", &virtio_driver, vdev_net.get())); + m_vdevs.push_back(std::move(vdev_net)); } // Initialize DTB diff --git a/src/virtio-net-carrier-slirp.cpp b/src/virtio-net-carrier-slirp.cpp new file mode 100644 index 000000000..5013e6b0f --- /dev/null +++ b/src/virtio-net-carrier-slirp.cpp @@ -0,0 +1,382 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +/// \file +/// \brief VirtIO network carrier Slirp implementation. +/// \details \{ +/// +/// This is a user-mode network carrier, so it should work in host's userspace, +/// meaning you don't need root privilege or any configuration in the host to use it, +/// in most case it should work out of the box. +/// +/// While being of use, the slirp network carrier has some limitations: +/// - There is an additional an emulation layer of the TCP/IP stack, so it's slower than TUN network carrier. +/// - Not all IP protocols are emulated, but TCP and UDP should work. +/// - Host cannot access guest TCP services (this can be improved in the future with slirp's hostfwd). +/// +/// The implementation uses libslirp TCP/IP emulator library. +/// +/// To have guest networking using a slirp network carrier, +/// execute the following commands in the guest with root privilege: +/// +/// ip link set dev eth0 up +/// ip addr add 10.0.2.15/24 dev eth0 +/// ip route add default via 10.0.2.2 dev eth0 +/// echo 'nameserver 10.0.2.3' > /etc/resolv.conf +/// +/// To test if everything works, try ping: +/// +/// ping cartesi.io +/// +/// The slirp network settings configuration is fixed to the following: +/// +/// Network: 10.0.2.0 +/// Netmask: 255.255.255.0 +/// Host/Gateway: 10.0.2.2 +/// DHCP Start: 10.0.2.15 +/// Nameserver: 10.0.2.3 +/// +/// \} + +// #define DEBUG_VIRTIO_ERRORS + +#include "virtio-net-carrier-slirp.h" + +#include +#include +#include +#include +#include + +using namespace std::string_literals; + +namespace cartesi { + +static ssize_t slirp_send_packet(const void *buf, size_t len, void *opaque) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + virtio_net_carrier_slirp *carrier = reinterpret_cast(opaque); + if (carrier->send_packets.size() >= SLIRP_MAX_PENDING_PACKETS) { + // Too many send_packets in the write queue, we can just drop it. + // Network re-transmission can recover from this. +#ifdef DEBUG_VIRTIO_ERRORS + (void) fprintf(stderr, "slirp: dropped packet sent by the host because the write queue is full\n"); +#endif + return 0; + } + if (len > VIRTIO_NET_ETHERNET_MAX_LENGTH) { + // This is unexpected, slirp is trying to send an a jumbo Ethernet frames? Drop it. +#ifdef DEBUG_VIRTIO_ERRORS + (void) fprintf(stderr, "slirp: dropped large packet with length %u sent by the host\n", + static_cast(len)); +#endif + return 0; + } + // Add packet to the send packet queue, + // the packet will actually be sent only the next time the device calls read_packet() + slirp_packet packet{len}; + memcpy(packet.buf.data(), buf, len); + try { + carrier->send_packets.emplace_back(std::move(packet)); + return static_cast(len); + } catch (...) { +#ifdef DEBUG_VIRTIO_ERRORS + (void) fprintf(stderr, "slirp: exception thrown while adding a send packet\n"); +#endif + return 0; + } +} + +static void slirp_guest_error(const char *msg, void *opaque) { + (void) msg; + (void) opaque; +#ifdef DEBUG_VIRTIO_ERRORS + (void) fprintf(stderr, "slirp: %s\n", msg); +#endif +} + +static int64_t slirp_clock_get_ns(void *opaque) { + (void) opaque; + const auto now = std::chrono::steady_clock::now(); + const auto ns = std::chrono::duration_cast(now.time_since_epoch()); + return static_cast(ns.count()); +} + +static void *slirp_timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + virtio_net_carrier_slirp *carrier = reinterpret_cast(opaque); + try { + slirp_timer *timer = new slirp_timer; + timer->cb = cb; + timer->cb_opaque = cb_opaque; + timer->expire_timer_msec = -1; + carrier->timers.insert(timer); + return timer; + } catch (...) { + return nullptr; + } +} + +static void slirp_timer_free(void *timer_ptr, void *opaque) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + virtio_net_carrier_slirp *carrier = reinterpret_cast(opaque); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + slirp_timer *timer = reinterpret_cast(timer_ptr); + if (timer) { + auto it = carrier->timers.find(timer); + if (it != carrier->timers.end()) { + carrier->timers.erase(it); + delete timer; + } + } +} + +static void slirp_timer_mod(void *timer_ptr, int64_t expire_timer_msec, void *opaque) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + virtio_net_carrier_slirp *carrier = reinterpret_cast(opaque); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + slirp_timer *timer = reinterpret_cast(timer_ptr); + if (timer && carrier->timers.find(timer) != carrier->timers.end()) { + timer->expire_timer_msec = expire_timer_msec; + } +} + +static void slirp_register_poll_fd(int fd, void *opaque) { + (void) fd; + (void) opaque; + // Nothing to do, this callback is only useful on implementations using poll() instead of select(). +} + +static void slirp_unregister_poll_fd(int fd, void *opaque) { + (void) fd; + (void) opaque; + // Nothing to do, this callback is only useful on implementations using poll() instead of select(). +} + +static void slirp_notify(void *opaque) { + (void) opaque; + // Nothing to do +} + +virtio_net_carrier_slirp::virtio_net_carrier_slirp(const cartesi::virtio_net_user_config &config) { + // Configure slirp + slirp_cfg.version = std::min(SLIRP_CONFIG_VERSION_MAX, SLIRP_VERSION); + slirp_cfg.restricted = false; // Don't isolate the guest from the host + slirp_cfg.in_enabled = true; // IPv4 is enabled + slirp_cfg.vnetwork.s_addr = htonl(SLIRP_DEFAULT_IPV4_VNETWORK); // Network + slirp_cfg.vnetmask.s_addr = htonl(SLIRP_DEFAULT_IPV4_VNETMASK); // Netmask + slirp_cfg.vhost.s_addr = htonl(SLIRP_DEFAULT_IPV4_VHOST); // Host address/gateway + slirp_cfg.vdhcp_start.s_addr = htonl(SLIRP_DEFAULT_IPV4_VDHCP_START); // DHCP start address + slirp_cfg.vnameserver.s_addr = htonl(SLIRP_DEFAULT_IPV4_VNAMESERVER); // DNS server address + slirp_cfg.disable_dhcp = true; + // ??(edubart): Should all the above settings be configurable by the user? + // ??(edubart): Should we add support for IPv6? It is disabled by default. + // Configure required slirp callbacks + slirp_cbs.send_packet = slirp_send_packet; + slirp_cbs.guest_error = slirp_guest_error; + slirp_cbs.clock_get_ns = slirp_clock_get_ns; + slirp_cbs.timer_new = slirp_timer_new; + slirp_cbs.timer_free = slirp_timer_free; + slirp_cbs.timer_mod = slirp_timer_mod; + slirp_cbs.register_poll_fd = slirp_register_poll_fd; + slirp_cbs.unregister_poll_fd = slirp_unregister_poll_fd; + slirp_cbs.notify = slirp_notify; + + // Initialize slirp + slirp = slirp_new(&slirp_cfg, &slirp_cbs, this); + if (!slirp) { + throw std::runtime_error("could not configure slirp network device"); + } + + // Setup host forward ports + for (const auto &hostfwd : config.hostfwd) { + struct in_addr host_addr {}; + struct in_addr guest_addr {}; + // ??(edubart): Should we allow configuring host/guest addrs? + host_addr.s_addr = htonl(INADDR_LOOPBACK); + guest_addr.s_addr = htonl(SLIRP_DEFAULT_IPV4_VDHCP_START); + if (slirp_add_hostfwd(slirp, hostfwd.is_udp, host_addr, hostfwd.host_port, guest_addr, hostfwd.guest_port) < + 0) { + throw std::system_error{errno, std::generic_category(), + "failed to forward "s + (hostfwd.is_udp ? "UDP" : "TCP") + " host port " + + std::to_string(hostfwd.host_port) + " to guest port " + std::to_string(hostfwd.guest_port)}; + } + } +} + +virtio_net_carrier_slirp::~virtio_net_carrier_slirp() { + // Cleanup slirp + if (slirp) { + slirp_cleanup(slirp); + slirp = nullptr; + } + // Delete remaining timers created by slirp + for (slirp_timer *timer : timers) { + delete timer; + } + timers.clear(); +} + +void virtio_net_carrier_slirp::reset() { + // Nothing to do, we don't want to reset slirp to not lose network state. +} + +struct slirp_select_fds { + int *pmaxfd; + fd_set *readfds; + fd_set *writefds; + fd_set *exceptfds; +}; + +static int slirp_add_poll_cb(int fd, int events, void *opaque) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + slirp_select_fds *fds = reinterpret_cast(opaque); + if (events & SLIRP_POLL_IN) { + FD_SET(fd, fds->readfds); + } + if (events & SLIRP_POLL_OUT) { + FD_SET(fd, fds->writefds); + } + if (events & SLIRP_POLL_PRI) { + FD_SET(fd, fds->exceptfds); + } + if (fd > *fds->pmaxfd) { + *fds->pmaxfd = fd; + } + return fd; +} + +static int slirp_get_revents_cb(int fd, void *opaque) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + slirp_select_fds *fds = reinterpret_cast(opaque); + int event = 0; + if (FD_ISSET(fd, fds->readfds)) { + event |= SLIRP_POLL_IN; + } + if (FD_ISSET(fd, fds->writefds)) { + event |= SLIRP_POLL_OUT; + } + if (FD_ISSET(fd, fds->exceptfds)) { + event |= SLIRP_POLL_PRI; + } + return event; +} + +void virtio_net_carrier_slirp::do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { + // Did device reset and slirp failed to reinitialize? + if (!slirp) { + return; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + fd_set *readfds = reinterpret_cast(fds->readfds); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + fd_set *writefds = reinterpret_cast(fds->writefds); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + fd_set *exceptfds = reinterpret_cast(fds->exceptfds); + slirp_select_fds slirp_fds{&fds->maxfd, readfds, writefds, exceptfds}; + const uint32_t initial_timeout_ms = *timeout_us / 1000; + uint32_t timeout_ms = initial_timeout_ms; + slirp_pollfds_fill(slirp, &timeout_ms, slirp_add_poll_cb, &slirp_fds); + if (initial_timeout_ms != timeout_ms) { + *timeout_us = static_cast(timeout_ms) * 1000; + } +} + +bool virtio_net_carrier_slirp::do_poll_selected(int select_ret, select_fd_sets *fds) { + // Did device reset and slirp failed to reinitialize? + if (!slirp) { + return false; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + fd_set *readfds = reinterpret_cast(fds->readfds); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + fd_set *writefds = reinterpret_cast(fds->writefds); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + fd_set *exceptfds = reinterpret_cast(fds->exceptfds); + slirp_select_fds slirp_fds{nullptr, readfds, writefds, exceptfds}; + slirp_pollfds_poll(slirp, select_ret < 0, slirp_get_revents_cb, &slirp_fds); + // Fire expired timers + const int64_t now_ms = slirp_clock_get_ns(nullptr) / 1000000; + for (slirp_timer *timer : timers) { + if (timer->expire_timer_msec != -1 && now_ms >= timer->expire_timer_msec) { + if (timer->cb) { + timer->cb(timer->cb_opaque); + } + // The timer should not fire again until expire_timer_msec is modified by Slirp + timer->expire_timer_msec = -1; + } + } + return !send_packets.empty(); +} + +bool virtio_net_carrier_slirp::write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, + uint32_t read_avail_len, uint32_t *pread_len) { + // Did device reset and slirp failed to reinitialize? + if (!slirp) { + // Just drop it. + *pread_len = 0; + return true; + } + const uint32_t packet_len = read_avail_len - VIRTIO_NET_ETHERNET_FRAME_OFFSET; + if (packet_len > VIRTIO_NET_ETHERNET_MAX_LENGTH) { + // This is unexpected, guest is trying to send jumbo Ethernet frames? Just drop it. + *pread_len = 0; +#ifdef DEBUG_VIRTIO_ERRORS + (void) fprintf(stderr, "slirp: dropped large packet with length %u sent by the guest\n", + static_cast(packet_len)); +#endif + return true; + } + slirp_packet packet{packet_len}; + if (!vq.read_desc_mem(a, desc_idx, VIRTIO_NET_ETHERNET_FRAME_OFFSET, packet.buf.data(), packet.len)) { + // Failure while accessing guest memory, the driver or guest messed up, return false to reset the device. + return false; + } + slirp_input(slirp, packet.buf.data(), static_cast(packet.len)); + // Packet was read and the queue is ready to be consumed. + *pread_len = read_avail_len; + return true; +} + +bool virtio_net_carrier_slirp::read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, + uint32_t write_avail_len, uint32_t *pwritten_len) { + // If no packet was send by slirp, we can just ignore. + if (send_packets.empty()) { + *pwritten_len = 0; + return true; + } + // Retrieve the next packet sent by slirp. + slirp_packet packet = std::move(send_packets.front()); + send_packets.pop_front(); + // Is there enough space in the write buffer to write this packet? + if (VIRTIO_NET_ETHERNET_FRAME_OFFSET + packet.len > write_avail_len) { +#ifdef DEBUG_VIRTIO_ERRORS + (void) fprintf(stderr, "slirp: dropped large packet with length %u sent by the host\n", + static_cast(packet.len)); +#endif + // Despite being a failure, return true to only drop the packet, we don't want to reset the device. + *pwritten_len = 0; + return true; + } + if (!vq.write_desc_mem(a, desc_idx, VIRTIO_NET_ETHERNET_FRAME_OFFSET, packet.buf.data(), packet.len)) { + // Failure while accessing guest memory, the driver or guest messed up, return false to reset the device. + return false; + } + // Packet was written and the queue is ready to be consumed. + *pwritten_len = VIRTIO_NET_ETHERNET_FRAME_OFFSET + packet.len; + return true; +} + +} // namespace cartesi diff --git a/src/virtio-net-carrier-slirp.h b/src/virtio-net-carrier-slirp.h new file mode 100644 index 000000000..f4cce2f27 --- /dev/null +++ b/src/virtio-net-carrier-slirp.h @@ -0,0 +1,88 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef VIRTIO_NET_CARRIER_SLIRP_H +#define VIRTIO_NET_CARRIER_SLIRP_H + +#include "machine-config.h" +#include "virtio-net.h" + +#include +#include + +#include + +#if SLIRP_CONFIG_VERSION_MAX < 3 +#error "slirp version must be 3 or higher" +#endif + +/// \brief Slirp constants +enum slirp_constants { + SLIRP_VERSION = 4, + SLIRP_MAX_PENDING_PACKETS = 1024, +}; + +/// \brief Default IPv4 settings historically used with slirp +enum slirp_ipv4_addresses : uint32_t { + SLIRP_DEFAULT_IPV4_VNETWORK = 0x0a000200, ///< 10.0.2.0 + SLIRP_DEFAULT_IPV4_VNETMASK = 0xffffff00, ///< 255.255.255.0 + SLIRP_DEFAULT_IPV4_VHOST = 0x0a000202, ///< 10.0.2.2 + SLIRP_DEFAULT_IPV4_VDHCP_START = 0x0a00020f, ///< 10.0.2.15 + SLIRP_DEFAULT_IPV4_VNAMESERVER = 0x0a000203, ///< 10.0.2.3 +}; + +namespace cartesi { + +struct slirp_timer { + SlirpTimerCb cb = nullptr; + void *cb_opaque = nullptr; + int64_t expire_timer_msec = -1; +}; + +struct slirp_packet { + size_t len = 0; + std::array buf{}; +}; + +class virtio_net_carrier_slirp final : public virtio_net_carrier { +public: + Slirp *slirp = nullptr; + SlirpConfig slirp_cfg{}; + SlirpCb slirp_cbs{}; + std::list send_packets; + std::unordered_set timers; + + virtio_net_carrier_slirp(const cartesi::virtio_net_user_config &config); + ~virtio_net_carrier_slirp() override; + virtio_net_carrier_slirp(const virtio_net_carrier_slirp &other) = delete; + virtio_net_carrier_slirp(virtio_net_carrier_slirp &&other) = delete; + virtio_net_carrier_slirp &operator=(const virtio_net_carrier_slirp &other) = delete; + virtio_net_carrier_slirp &operator=(virtio_net_carrier_slirp &&other) = delete; + + void reset() override; + + void do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; + bool do_poll_selected(int select_ret, select_fd_sets *fds) override; + + bool write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, + uint32_t *pread_len) override; + bool read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, + uint32_t *pwritten_len) override; +}; + +} // namespace cartesi + +#endif diff --git a/src/virtio-net-carrier-tuntap.cpp b/src/virtio-net-carrier-tuntap.cpp new file mode 100644 index 000000000..a1f3a56af --- /dev/null +++ b/src/virtio-net-carrier-tuntap.cpp @@ -0,0 +1,215 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +/// \file +/// \brief VirtIO network carrier TUN/TAP implementation. +/// \details \{ +/// +/// To have guest networking host's tap0 network interface, +/// execute the following commands in the host with root privilege +/// before starting the machine: +/// +/// modprobe tun +/// ip link add br0 type bridge +/// ip tuntap add dev tap0 mode tap user $USER +/// ip link set dev tap0 master br0 +/// ip link set dev br0 up +/// ip link set dev tap0 up +/// ip addr add 192.168.3.1/24 dev br0 +/// +/// Then to share the host's internet access with the guest: +/// sysctl -w net.ipv4.ip_forward=1 +/// iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +/// +/// In the example above the host public internet interface is eth0, but this depends in the host. +/// +/// Finally start the machine with using tap0 network carrier and +/// execute the following commands in the guest with root privilege: +/// +/// ip link set dev eth0 up +/// ip addr add 192.168.3.2/24 dev eth0 +/// ip route add default via 192.168.3.1 dev eth0 +/// echo "nameserver 8.8.8.8" > /etc/resolv.conf +/// +/// To test if everything works, try ping: +/// +/// ping cartesi.io +/// +/// \} + +// #define DEBUG_VIRTIO_ERRORS + +#include "virtio-net-carrier-tuntap.h" + +#include + +#include +#include +#include +#include +#include + +// Include TUN/TAP headers +#ifdef __linux__ +#include +constexpr const char NET_TUN_DEV[] = "/dev/net/tun"; +#else // Other platform, most likely MacOS or FreeBSD +#define IFF_TAP 0x0002 +#define IFF_NO_PI 0x1000 // Don't provide packet info +#define TUNSETIFF _IOW('T', 202, int) +constexpr const char NET_TUN_DEV[] = "/dev/tun"; +#endif + +namespace cartesi { + +virtio_net_carrier_tuntap::virtio_net_carrier_tuntap(const std::string &tap_name) { + // Open the tun device + const int flags = O_RDWR | // Read/write + O_NONBLOCK | // Read/write should never block + O_DSYNC; // Flush packets right-away upon write + const int fd = open(NET_TUN_DEV, flags); + if (fd < 0) { + throw std::runtime_error( + std::string("could not open tun network device '") + NET_TUN_DEV + "': " + strerror(errno)); + } + // Set the tap network interface + ifreq ifr{}; + ifr.ifr_flags = IFF_TAP | // TAP device + IFF_NO_PI; // Do not provide packet information + strncpy(ifr.ifr_name, tap_name.c_str(), sizeof(ifr.ifr_name)); + if (ioctl(fd, TUNSETIFF, &ifr) != 0) { + close(fd); + throw std::runtime_error( + std::string("could not configure tap network device '") + tap_name + "': " + strerror(errno)); + } + m_tapfd = fd; +} + +virtio_net_carrier_tuntap::~virtio_net_carrier_tuntap() { + if (m_tapfd != -1) { + close(m_tapfd); + } +} + +void virtio_net_carrier_tuntap::reset() { + // Nothing to do. +} + +void virtio_net_carrier_tuntap::do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { + (void) timeout_us; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + fd_set *readfds = reinterpret_cast(fds->readfds); + FD_SET(m_tapfd, readfds); + if (m_tapfd > fds->maxfd) { + fds->maxfd = m_tapfd; + } +} + +bool virtio_net_carrier_tuntap::do_poll_selected(int select_ret, select_fd_sets *fds) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + fd_set *readfds = reinterpret_cast(fds->readfds); + return select_ret > 0 && FD_ISSET(m_tapfd, readfds); +} + +bool virtio_net_carrier_tuntap::write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, + uint32_t read_avail_len, uint32_t *pread_len) { + // Determinate packet size + const uint32_t packet_len = read_avail_len - VIRTIO_NET_ETHERNET_FRAME_OFFSET; + if (packet_len > VIRTIO_NET_ETHERNET_MAX_LENGTH) { + // This is unexpected, guest is trying to send jumbo Ethernet frames? Just drop it. + *pread_len = 0; +#ifdef DEBUG_VIRTIO_ERRORS + (void) fprintf(stderr, "tun: dropped large packet with length %u sent by the guest\n", + static_cast(packet_len)); +#endif + return true; + } + // Read packet from queue buffer + std::array packet_buf{}; + if (!vq.read_desc_mem(a, desc_idx, VIRTIO_NET_ETHERNET_FRAME_OFFSET, packet_buf.data(), packet_len)) { + // Failure while accessing guest memory, the driver or guest messed up, return false to reset the device. + return false; + } + // Keep writing until all packet bytes are written + uint32_t written_packet_len = 0; + while (written_packet_len < packet_len) { + // Set errno to zero because write() may not set it when its return is zero + errno = 0; + // Write to the network interface + const ssize_t written_len = + write(m_tapfd, packet_buf.data() + written_packet_len, packet_len - written_packet_len); + if (written_len <= 0) { + // Retry again when the operation would block or was interrupted + if (errno == EAGAIN || errno == EINTR) { + // sched_yield() lets the host CPU scheduler switch to other processes, + // so we avoid consuming host CPU resources in this infinite loop, + // ??E: We could also use a usleep() here when sched_yield() is not supported. + sched_yield(); + } else { + // Unexpected error, return false to reset the device. + return false; + } + } + written_packet_len += static_cast(written_len); + } + // Packet was read and the queue is ready to be consumed. + *pread_len = read_avail_len; + return true; +} + +bool virtio_net_carrier_tuntap::read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, + uint32_t write_avail_len, uint32_t *pwritten_len) { + // Write network to queue buffer in chunks + std::array packet_buf{}; + // Set errno to zero because read() will not set it when it returns zero (end of file) + errno = 0; + // Read the next packet from the network interface + const ssize_t read_len = read(m_tapfd, packet_buf.data(), VIRTIO_NET_ETHERNET_MAX_LENGTH); + if (read_len <= 0) { + // Stop when the operation would block or was interrupted, + // the next poll will read any pending packet. + if (errno == EAGAIN || errno == EINTR) { + // There is no packet available. + *pwritten_len = 0; + return true; + } else { + // Unexpected error, return false to reset the device. + return false; + } + } + const uint32_t packet_len = static_cast(read_len); + // Is there enough space in the write buffer to write this packet? + if (VIRTIO_NET_ETHERNET_FRAME_OFFSET + packet_len > write_avail_len || + packet_len == VIRTIO_NET_ETHERNET_MAX_LENGTH) { +#ifdef DEBUG_VIRTIO_ERRORS + (void) fprintf(stderr, "tun: dropped large packet with length %u sent by the host\n", + static_cast(packet_len)); +#endif + // Despite being a failure, return true to only drop the packet, we don't want to reset the device. + *pwritten_len = 0; + return true; + } + // Write to queue buffer + if (!vq.write_desc_mem(a, desc_idx, VIRTIO_NET_ETHERNET_FRAME_OFFSET, packet_buf.data(), packet_len)) { + // Failure while accessing guest memory, the driver or guest messed up, return false to reset the device. + return false; + } + // Packet was written and the queue is ready to be consumed. + *pwritten_len = VIRTIO_NET_ETHERNET_FRAME_OFFSET + packet_len; + return true; +} + +} // namespace cartesi diff --git a/src/virtio-net-carrier-tuntap.h b/src/virtio-net-carrier-tuntap.h new file mode 100644 index 000000000..ca1532f62 --- /dev/null +++ b/src/virtio-net-carrier-tuntap.h @@ -0,0 +1,48 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef VIRTIO_NET_CARRIER_TUNTAP_H +#define VIRTIO_NET_CARRIER_TUNTAP_H + +#include "virtio-net.h" + +namespace cartesi { + +class virtio_net_carrier_tuntap final : public virtio_net_carrier { + int m_tapfd = -1; + +public: + virtio_net_carrier_tuntap(const std::string &tap_name); + ~virtio_net_carrier_tuntap() override; + virtio_net_carrier_tuntap(const virtio_net_carrier_tuntap &other) = delete; + virtio_net_carrier_tuntap(virtio_net_carrier_tuntap &&other) = delete; + virtio_net_carrier_tuntap &operator=(const virtio_net_carrier_tuntap &other) = delete; + virtio_net_carrier_tuntap &operator=(virtio_net_carrier_tuntap &&other) = delete; + + void reset() override; + + void do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; + bool do_poll_selected(int select_ret, select_fd_sets *fds) override; + + bool write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, + uint32_t *pread_len) override; + bool read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, + uint32_t *pwritten_len) override; +}; + +} // namespace cartesi + +#endif diff --git a/src/virtio-net.cpp b/src/virtio-net.cpp new file mode 100644 index 000000000..ea23fcc17 --- /dev/null +++ b/src/virtio-net.cpp @@ -0,0 +1,140 @@ +// Copyright 2023 Cartesi Pte. Ltd. +// +// This file is part of the machine-emulator. The machine-emulator is free +// software: you can redistribute it and/or modify it under the terms of the GNU +// Lesser General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// The machine-emulator is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +// for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +// + +#include "virtio-net.h" + +namespace cartesi { + +virtio_net::virtio_net(uint32_t virtio_idx, std::unique_ptr &&carrier) : + virtio_device(virtio_idx, VIRTIO_DEVICE_NETWORK, 0, 0), + m_carrier(std::move(carrier)) {} + +void virtio_net::on_device_reset() { + m_carrier->reset(); +} + +void virtio_net::on_device_ok(i_device_state_access *a) { + (void) a; + // Nothing to do. +} + +bool virtio_net::on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, + uint32_t read_avail_len, uint32_t write_avail_len) { + (void) write_avail_len; + if (queue_idx == VIRTIO_NET_RECEIVEQ) { // Guest has a new slot available in the write queue + // Write any pending packets from host to guest + return poll_nowait(a); + } else if (queue_idx == VIRTIO_NET_TRANSMITQ) { // Guest sent a new packet to the host + if (write_next_packet_to_host(a, queue_idx, desc_idx, read_avail_len)) { + // When a packet is just sent, poll for a response right-away. + // This is necessary to have fast communication between the guest and its host + // with the Slirp carrier. + poll_nowait(a); + return true; + } + return false; + } else { + // Other queues are unexpected + notify_device_needs_reset(a); + return false; + } +} + +void virtio_net::prepare_select(select_fd_sets *fds, uint64_t *timeout_us) { + if (!driver_ok) { + return; + } + m_carrier->do_prepare_select(fds, timeout_us); +} + +bool virtio_net::poll_selected(int select_ret, select_fd_sets *fds, i_device_state_access *da) { + if (!driver_ok) { + return false; + } + // Is there any pending to be read from the host? + if (!m_carrier->do_poll_selected(select_ret, fds)) { + return false; + } + bool interrupt_requested = false; + // Read packets from host and write them the guest, + // until the are no more pending packets to write or the write queue is full. + while (read_next_packet_from_host(da)) { + interrupt_requested = true; + } + return interrupt_requested; +} + +bool virtio_net::write_next_packet_to_host(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, + uint32_t read_avail_len) { + virtq &vq = queue[queue_idx]; + // Write a single packet to the network interface + uint32_t read_len{}; + if (!m_carrier->write_packet_to_host(a, vq, desc_idx, read_avail_len, &read_len)) { + notify_device_needs_reset(a); + return false; + } + // Consume the queue and notify the driver + if (!consume_and_notify_queue(a, queue_idx, desc_idx)) { + notify_device_needs_reset(a); + return false; + } + return true; +} + +bool virtio_net::read_next_packet_from_host(i_device_state_access *a) { + // Bytes from host must be written to queue 0 + constexpr uint32_t queue_idx = VIRTIO_NET_RECEIVEQ; + virtq &vq = queue[queue_idx]; + // Prepare queue buffer for writing + uint16_t desc_idx{}; + uint32_t write_avail_len{}; + if (!prepare_queue_write(a, queue_idx, &desc_idx, &write_avail_len)) { + notify_device_needs_reset(a); + return false; + } + // Write buffer length can be zero in case the queue is not ready or full + if (write_avail_len == 0) { + // This is not a fatal a failure, so no device reset is needed. + return false; + } + // Read a single packet from the network interface + uint32_t written_len{}; + if (!m_carrier->read_packet_from_host(a, vq, desc_idx, write_avail_len, &written_len)) { + notify_device_needs_reset(a); + return false; + } + // The carrier is allowed may have no packets to send or may even drop packets, + // so we consume the buffer and notify the driver only when something was actually written. + if (written_len <= VIRTIO_NET_ETHERNET_FRAME_OFFSET) { + // This is not a fatal a failure, so no device reset is needed. + return false; + } + // Write the net header, we can simply fill the header with zeros + virtio_net_header hdr{}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + if (!vq.write_desc_mem(a, desc_idx, 0, reinterpret_cast(&hdr), VIRTIO_NET_ETHERNET_FRAME_OFFSET)) { + notify_device_needs_reset(a); + return false; + } + // Consume and notify the queue + if (!consume_and_notify_queue(a, queue_idx, desc_idx, written_len)) { + notify_device_needs_reset(a); + return false; + } + return true; +} + +} // namespace cartesi diff --git a/src/virtio-net.h b/src/virtio-net.h new file mode 100644 index 000000000..883ac504b --- /dev/null +++ b/src/virtio-net.h @@ -0,0 +1,116 @@ +// Copyright 2023 Cartesi Pte. Ltd. +// +// This file is part of the machine-emulator. The machine-emulator is free +// software: you can redistribute it and/or modify it under the terms of the GNU +// Lesser General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// The machine-emulator is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +// for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +// + +#ifndef VIRTIO_NET_H +#define VIRTIO_NET_H + +#include "virtio-device.h" + +#include + +namespace cartesi { + +/// \brief VirtIO net packet header +struct virtio_net_header { + uint8_t flags; + uint8_t gso_type; + uint16_t hdr_len; + uint16_t gso_size; + uint16_t csum_start; + uint16_t csum_offset; + uint16_t num_buffers; +}; + +/// \brief VirtIO net constants +enum virtio_net_constants : uint32_t { + VIRTIO_NET_ETHERNET_FRAME_OFFSET = sizeof(virtio_net_header), ///< Offset for writing Ethernet frames + VIRTIO_NET_ETHERNET_MAX_LENGTH = 2048, ///< Large enough to fit Ethernet maximum frame size +}; + +/// \brief VirtIO net Virtqueue indexes +enum virtio_net_virtq : uint32_t { + VIRTIO_NET_RECEIVEQ = 0, ///< Queue of packets from host to guest + VIRTIO_NET_TRANSMITQ = 1, ///< Queue of packets from guest to host +}; + +/// \brief Generic interface for a network carrier on the host. +/// \details The sole purpose of a network carrier +/// is to carry incoming or outgoing packets between the host and the guest. +class virtio_net_carrier { +public: + virtio_net_carrier() = default; + virtual ~virtio_net_carrier() = default; + virtio_net_carrier(const virtio_net_carrier &other) = delete; + virtio_net_carrier(virtio_net_carrier &&other) = delete; + virtio_net_carrier &operator=(const virtio_net_carrier &other) = delete; + virtio_net_carrier &operator=(virtio_net_carrier &&other) = delete; + + /// \brief Reset carrier internal state, discarding all network state. + virtual void reset() = 0; + + /// \brief Fill file descriptors to be polled by select(). + virtual void do_prepare_select(select_fd_sets *fds, uint64_t *timeout_us) = 0; + + /// \brief Poll file descriptors that were marked as ready by select(). + virtual bool do_poll_selected(int select_ret, select_fd_sets *fds) = 0; + + /// \brief Called for carrying outgoing packets from the guest to the host. + /// \param vq Queue reference. + /// \param desc_idx Queue's descriptor index. + /// \param read_avail_len Total readable length in the descriptor buffer. + /// \param pread_len Receives how many bytes were actually read. + /// \returns True on success, false otherwise. + /// \details This function will return true even if when the write queue is full, + /// pread_len will be set to 0 in this case and the packet dropped. + virtual bool write_packet_to_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t read_avail_len, + uint32_t *pread_len) = 0; + + /// \brief Called for carrying incoming packets from the host to the guest. + /// \param vq Queue reference. + /// \param desc_idx Queue's descriptor index. + /// \param write_avail_len Total writable length in the descriptor buffer. + /// \param pwrite_len Receives how many bytes were actually written. + /// \returns True on success, false otherwise. + /// \details This function will true even if when there are no more packets to write, + /// pwritten_len will be set to 0 in this case. + virtual bool read_packet_from_host(i_device_state_access *a, virtq &vq, uint16_t desc_idx, uint32_t write_avail_len, + uint32_t *pwritten_len) = 0; +}; + +/// \brief VirtIO net device +class virtio_net final : public virtio_device { +public: + virtio_net(uint32_t virtio_idx, std::unique_ptr &&carrier); + + void on_device_reset() override; + void on_device_ok(i_device_state_access *a) override; + bool on_device_queue_available(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, + uint32_t read_avail_len, uint32_t write_avail_len) override; + + void prepare_select(select_fd_sets *fds, uint64_t *timeout_us) override; + bool poll_selected(int select_ret, select_fd_sets *fds, i_device_state_access *da) override; + + bool write_next_packet_to_host(i_device_state_access *a, uint32_t queue_idx, uint16_t desc_idx, + uint32_t read_avail_len); + bool read_next_packet_from_host(i_device_state_access *a); + +private: + std::unique_ptr m_carrier; +}; + +} // namespace cartesi + +#endif