diff --git a/.ci/test-netdev.sh b/.ci/test-netdev.sh index 485016cc..85378e5c 100755 --- a/.ci/test-netdev.sh +++ b/.ci/test-netdev.sh @@ -45,10 +45,13 @@ ASSERT expect <stopped) { - ret = semu_step(emu); - if (ret) - return ret; +#if SEMU_HAS(VIRTIONET) + int i = 0; + if (emu->vnet.peer.type == NETDEV_IMPL_user && boot_complete) { + net_user_options_t *usr = (net_user_options_t *) emu->vnet.peer.op; + + uint32_t timeout = -1; + usr->pfd_len = 1; + slirp_pollfds_fill_socket(usr->slirp, &timeout, + semu_slirp_add_poll_socket, usr); + + /* Poll the internal pipe for incoming data. If data is + * available (POLL_IN), process it and forward it to the + * virtio-net device. + */ + int pollout = poll(usr->pfd, usr->pfd_len, 1); + if (usr->pfd[0].revents & POLLIN) { + virtio_net_recv_from_peer(usr->peer); + } + slirp_pollfds_poll(usr->slirp, (pollout <= 0), + semu_slirp_get_revents, usr); + for (i = 0; i < SLIRP_POLL_INTERVAL; i++) { + ret = semu_step(emu); + if (ret) + return ret; + } + } else +#endif + { + ret = semu_step(emu); + if (ret) + return ret; + } } /* unreachable */ diff --git a/minislirp b/minislirp new file mode 160000 index 00000000..0bf4a418 --- /dev/null +++ b/minislirp @@ -0,0 +1 @@ +Subproject commit 0bf4a418590b1d9bdcec06cb9d9c5f6c1ea33891 diff --git a/netdev.c b/netdev.c index 858a21f3..2d93261b 100644 --- a/netdev.c +++ b/netdev.c @@ -8,6 +8,7 @@ #include #include +#include "device.h" #include "netdev.h" static int net_init_tap(); @@ -55,9 +56,19 @@ static int net_init_tap(netdev_t *netdev) return 0; } -static int net_init_user(netdev_t *netdev UNUSED) +static int net_init_user(netdev_t *netdev) { - /* TODO: create slirp dev */ + net_user_options_t *usr = (net_user_options_t *) netdev->op; + memset(usr, 0, sizeof(*usr)); + usr->peer = container_of(netdev, virtio_net_state_t, peer); + if (pipe(usr->channel) < 0) + return false; + assert(fcntl(usr->channel[SLIRP_READ_SIDE], F_SETFL, + fcntl(usr->channel[SLIRP_READ_SIDE], F_GETFL, 0) | + O_NONBLOCK) >= 0); + + net_slirp_init(usr); + return 0; } diff --git a/netdev.h b/netdev.h index 151fa79f..589b1178 100644 --- a/netdev.h +++ b/netdev.h @@ -1,6 +1,11 @@ #pragma once -#include +#include +#include + +#include "minislirp/src/libslirp.h" +#include "utils.h" + /* clang-format off */ #define SUPPORTED_DEVICES \ @@ -18,10 +23,34 @@ typedef struct { int tap_fd; } net_tap_options_t; +/* SLIRP */ +#define SLIRP_POLL_INTERVAL 100000 +#define SLIRP_READ_SIDE 0 +#define SLIRP_WRITE_SIDE 1 typedef struct { - /* TODO: Implement user option */ + semu_timer_t timer; + Slirp *slirp; + SlirpTimerId id; + void *cb_opaque; + void (*cb)(void *opaque); + int64_t expire_timer_msec; +} slirp_timer; + +typedef struct { + Slirp *slirp; + int channel[2]; + int pfd_len; + int pfd_size; + struct pollfd *pfd; + slirp_timer *timer; + void *peer; } net_user_options_t; +Slirp *slirp_create(net_user_options_t *usr, SlirpConfig *cfg); +int net_slirp_init(net_user_options_t *usr); +int semu_slirp_add_poll_socket(slirp_os_socket fd, int events, void *opaque); +int semu_slirp_get_revents(int idx, void *opaque); + typedef struct { char *name; netdev_impl_t type; diff --git a/slirp.c b/slirp.c new file mode 100644 index 00000000..3e996272 --- /dev/null +++ b/slirp.c @@ -0,0 +1,205 @@ +#include +#include + +#include "netdev.h" + +/* Slirp callback: invoked when Slirp wants to send a packet to the backend */ +static ssize_t net_slirp_send_packet(const void *buf, size_t len, void *opaque) +{ + net_user_options_t *usr = (net_user_options_t *) opaque; + + return write(usr->channel[SLIRP_WRITE_SIDE], buf, len); +} + +/* Slirp callback: reports an error from the guest (current unused) */ +static void net_slirp_guest_error(const char *msg UNUSED, void *opaque UNUSED) +{ + // Unused +} + +/* Slirp callback: returns current time in nanoseconds for Slirp timers */ +static int64_t net_slirp_clock_get_ns(void *opaque UNUSED) +{ + net_user_options_t *usr = (net_user_options_t *) opaque; + + return semu_timer_get(&usr->timer->timer); +} + +/* Slirp callback: called when Slirp has finished initialization */ +static void net_slirp_init_completed(Slirp *slirp, void *opaque) +{ + net_user_options_t *s = opaque; + s->slirp = slirp; +} + +static void slirp_timer_init(slirp_timer *t, void (*cb)(void *opaque)) +{ + t->cb = cb; + semu_timer_init(&t->timer, CLOCK_FREQ, 1); +} + +static void net_slirp_timer_cb(void *opaque) +{ + slirp_timer *t = opaque; + slirp_handle_timer(t->slirp, t->id, t->cb_opaque); +} + +/* Slirp callback: allocated and initializes a new timer object */ +static void *net_slirp_timer_new_opaque(SlirpTimerId id, + void *cb_opaque, + void *opaque) +{ + net_user_options_t *usr = (net_user_options_t *) opaque; + slirp_timer *t = malloc(sizeof(slirp_timer)); + usr->timer = t; + t->slirp = usr->slirp; + t->id = id; + t->cb_opaque = cb_opaque; + t->expire_timer_msec = -1; + slirp_timer_init(t, net_slirp_timer_cb); + + return t; +} + +/* Slirp callback: releases resources associated with a timer */ +static void net_slirp_timer_free(void *timer, void *opaque UNUSED) +{ + if (timer) + free(timer); +} + +/* Slirp callback: modifies the expiration time of an existing timer */ +static void net_slirp_timer_mod(void *timer, + int64_t expire_time, + void *opaque UNUSED) +{ + slirp_timer *t = (slirp_timer *) timer; + semu_timer_rebase(&t->timer, expire_time); +} + +/* Slirp callback: registers a pollable socket (unused in this backend) */ +static void net_slirp_register_poll_sock(int fd UNUSED, void *opaque UNUSED) +{ + // Unused +} + +/* Slirp callback: unregisters a pollable socket (unused in this backend) */ +static void net_slirp_unregister_poll_sock(int fd UNUSED, void *opaque UNUSED) +{ + // Unused +} + +/* Slirp callback: notifies backend of pending activity (unused) */ +static void net_slirp_notify(void *opaque UNUSED) +{ + // Unused +} + +static const SlirpCb slirp_cb = { + .send_packet = net_slirp_send_packet, + .guest_error = net_slirp_guest_error, + .clock_get_ns = net_slirp_clock_get_ns, + .init_completed = net_slirp_init_completed, + .timer_new_opaque = net_slirp_timer_new_opaque, + .timer_free = net_slirp_timer_free, + .timer_mod = net_slirp_timer_mod, + .register_poll_socket = net_slirp_register_poll_sock, + .unregister_poll_socket = net_slirp_unregister_poll_sock, + .notify = net_slirp_notify, +}; + +static int poll_to_slirp_poll(int events) +{ + int ret = 0; + if (events & POLLIN) + ret |= SLIRP_POLL_IN; + if (events & POLLOUT) + ret |= SLIRP_POLL_OUT; + if (events & POLLPRI) + ret |= SLIRP_POLL_PRI; + if (events & POLLERR) + ret |= SLIRP_POLL_ERR; + if (events & POLLHUP) + ret |= SLIRP_POLL_HUP; + return ret; +} + +int semu_slirp_get_revents(int idx, void *opaque) +{ + net_user_options_t *usr = opaque; + return poll_to_slirp_poll(usr->pfd[idx].revents); +} + +int semu_slirp_add_poll_socket(slirp_os_socket fd, int events, void *opaque) +{ + net_user_options_t *usr = opaque; + if (usr->pfd_len >= usr->pfd_size) { + int newsize = usr->pfd_size + 16; + struct pollfd *new = realloc(usr->pfd, newsize * sizeof(struct pollfd)); + if (new) { + usr->pfd = new; + usr->pfd_size = newsize; + } + } + if (usr->pfd_len < usr->pfd_size) { + int idx = usr->pfd_len++; + usr->pfd[idx].fd = fd; + + usr->pfd[idx].events = poll_to_slirp_poll(events); + return idx; + } else { + return -1; + } +} + +Slirp *slirp_create(net_user_options_t *usr, SlirpConfig *cfg) +{ + /* Create a Slirp instance with special address. All + * addresses of the form 10.0.2.xxx are special to + * Slirp. + */ + cfg->version = SLIRP_CHECK_VERSION(4, 8, 0) ? 6 + : SLIRP_CHECK_VERSION(4, 7, 0) ? 4 + : 1; + cfg->restricted = 0; + cfg->in_enabled = 1; + inet_pton(AF_INET, "10.0.2.0", &(cfg->vnetwork)); + inet_pton(AF_INET, "255.255.255.0", &(cfg->vnetmask)); + inet_pton(AF_INET, "10.0.2.2", &(cfg->vhost)); + cfg->in6_enabled = 1; + inet_pton(AF_INET6, "fd00::", &cfg->vprefix_addr6); + cfg->vhostname = "slirp"; + cfg->tftp_server_name = NULL; + cfg->tftp_path = NULL; + cfg->bootfile = NULL; + inet_pton(AF_INET, "10.0.2.15", &(cfg->vdhcp_start)); + inet_pton(AF_INET, "10.0.2.3", &(cfg->vnameserver)); + inet_pton(AF_INET6, "fd00::3", &cfg->vnameserver6); + cfg->vdnssearch = NULL; + cfg->vdomainname = NULL; + cfg->if_mtu = 1500; + cfg->if_mru = 1500; + cfg->outbound_addr = NULL; + cfg->disable_host_loopback = 0; + + return slirp_new(cfg, &slirp_cb, usr); +} + +int net_slirp_init(net_user_options_t *usr) +{ + SlirpConfig cfg; + usr->slirp = slirp_create(usr, &cfg); + if (usr->slirp == NULL) { + fprintf(stderr, "create slirp failed\n"); + } + + usr->pfd = malloc(sizeof(struct pollfd)); + + /* Register the read end of the internal pipe (channel[SLIRP_READ_SIDE]) + * with slirp's poll system. This allows slirp to monitor it for incoming + * data (POLL_IN) or hang-up event (POLL_HUP). + */ + semu_slirp_add_poll_socket(usr->channel[SLIRP_READ_SIDE], + SLIRP_POLL_IN | SLIRP_POLL_HUP, usr); + return 0; +} diff --git a/virtio-net.c b/virtio-net.c index 4aa4542c..9fb9baaf 100644 --- a/virtio-net.c +++ b/virtio-net.c @@ -136,7 +136,20 @@ static ssize_t handle_read(netdev_t *netdev, break; } case _(user): - /* TODO: handle read */ + net_user_options_t *usr = (net_user_options_t *) netdev->op; + + plen = readv(usr->channel[SLIRP_READ_SIDE], iovs_cursor, niovs); + if (plen < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { + queue->fd_ready = false; + return -1; + } + + if (plen < 0) { + plen = 0; + fprintf(stderr, "[VNET] could not read packet: %s\n", + strerror(errno)); + } + break; default: break; @@ -168,7 +181,20 @@ static ssize_t handle_write(netdev_t *netdev, break; } case _(user): - /* TODO: handle slirp_input */ + net_user_options_t *usr = (net_user_options_t *) netdev->op; + + uint8_t pkt[1514]; + + /* Aggregate data from scatter-gater I/O vector into a + * contiguous packet buffer + */ + for (size_t i = 0; i < niovs; i++) { + memcpy(pkt + plen, iovs_cursor[i].iov_base, iovs_cursor[i].iov_len); + plen += iovs_cursor[i].iov_len; + } + + slirp_input(usr->slirp, pkt, plen); + break; default: break; @@ -298,7 +324,8 @@ void virtio_net_refresh_queue(virtio_net_state_t *vnet) break; } case _(user): - /* TODO: handle slirp input/output */ + vnet->queues[VNET_QUEUE_TX].fd_ready = true; + virtio_net_try_tx(vnet); break; default: break; @@ -306,6 +333,14 @@ void virtio_net_refresh_queue(virtio_net_state_t *vnet) #undef _ } +void virtio_net_recv_from_peer(void *peer) +{ + virtio_net_state_t *vnet = (virtio_net_state_t *) peer; + vnet->queues[VNET_QUEUE_RX].fd_ready = true; + + virtio_net_try_rx(vnet); +} + static bool virtio_net_reg_read(virtio_net_state_t *vnet, uint32_t addr, uint32_t *value)