Skip to content

Commit

Permalink
Refactor network device handling for flexible client support
Browse files Browse the repository at this point in the history
Separate network client handling to support various network backends,
such as Slirp, instead of hard-coding the TAP device. The '-netdev'
flag can now be used to specify the network client type, defaulting
to TAP if unspecified. Additionally, packet transfer functions are
now tailored to each client device, enabling more modular and
extensible network client development.
  • Loading branch information
chiangkd committed Nov 12, 2024
1 parent a0ad61b commit 735cb82
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 45 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ endif
$(call set-feature, VIRTIONET)
ifeq ($(call has, VIRTIONET), 1)
OBJS_EXTRA += virtio-net.o
OBJS_EXTRA += net_client.o
endif

BIN = semu
Expand Down
5 changes: 3 additions & 2 deletions device.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "net_client.h"
#include "riscv.h"
#include "virtio.h"

Expand Down Expand Up @@ -101,7 +102,7 @@ typedef struct {
uint32_t Status;
uint32_t InterruptStatus;
/* supplied by environment */
int tap_fd;
netdev_t peer;
uint32_t *ram;
/* implementation-specific */
void *priv;
Expand All @@ -119,7 +120,7 @@ void virtio_net_write(hart_t *core,
uint32_t value);
void virtio_net_refresh_queue(virtio_net_state_t *vnet);

bool virtio_net_init(virtio_net_state_t *vnet);
bool virtio_net_init(virtio_net_state_t *vnet, const char *client);
#endif /* SEMU_HAS(VIRTIONET) */

/* VirtIO-Block */
Expand Down
20 changes: 15 additions & 5 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -440,19 +440,22 @@ static void handle_options(int argc,
char **dtb_file,
char **initrd_file,
char **disk_file,
char **net_dev,
int *hart_count)
{
*kernel_file = *dtb_file = *initrd_file = *disk_file = NULL;
*kernel_file = *dtb_file = *initrd_file = *disk_file = *net_dev = NULL;

int optidx = 0;
struct option opts[] = {
{"kernel", 1, NULL, 'k'}, {"dtb", 1, NULL, 'b'},
{"initrd", 1, NULL, 'i'}, {"disk", 1, NULL, 'd'},
{"smp", 1, NULL, 'c'}, {"help", 0, NULL, 'h'},
{"netdev", 1, NULL, 'n'}, {"smp", 1, NULL, 'c'},
{"help", 0, NULL, 'h'},
};

int c;
while ((c = getopt_long(argc, argv, "k:b:i:d:c:h", opts, &optidx)) != -1) {
while ((c = getopt_long(argc, argv, "k:b:i:d:n:c:h", opts, &optidx)) !=
-1) {
switch (c) {
case 'k':
*kernel_file = optarg;
Expand All @@ -466,6 +469,9 @@ static void handle_options(int argc,
case 'd':
*disk_file = optarg;
break;
case 'n':
*net_dev = optarg;
break;
case 'c':
*hart_count = atoi(optarg);
break;
Expand All @@ -487,6 +493,9 @@ static void handle_options(int argc,

if (!*dtb_file)
*dtb_file = "minimal.dtb";

if (!*net_dev)
*net_dev = "tap";
}


Expand All @@ -509,9 +518,10 @@ static int semu_start(int argc, char **argv)
char *dtb_file;
char *initrd_file;
char *disk_file;
char *netdev;
int hart_count = 1;
handle_options(argc, argv, &kernel_file, &dtb_file, &initrd_file,
&disk_file, &hart_count);
&disk_file, &netdev, &hart_count);

/* Initialize the emulator */
emu_state_t emu;
Expand Down Expand Up @@ -573,7 +583,7 @@ static int semu_start(int argc, char **argv)
emu.uart.in_fd = 0, emu.uart.out_fd = 1;
capture_keyboard_input(); /* set up uart */
#if SEMU_HAS(VIRTIONET)
if (!virtio_net_init(&(emu.vnet)))
if (!virtio_net_init(&(emu.vnet), netdev))
fprintf(stderr, "No virtio-net functioned\n");
emu.vnet.ram = emu.ram;
#endif
Expand Down
80 changes: 80 additions & 0 deletions net_client.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>

#include "net_client.h"

static int net_init_tap();
static int net_init_slirp();

static int (*const net_init_fun[NET_CLIENT_DRIVER_MAX])(const netdev_t *) = {
[NET_CLIENT_DRIVER_TAP] = net_init_tap,
[NET_CLIENT_DRIVER_USER] = net_init_slirp,
};

static const char *client_driver_lookup[] = {
[NET_CLIENT_DRIVER_TAP] = "tap",
[NET_CLIENT_DRIVER_USER] = "user",
};

static int find_net_dev_idx(const char *net_type, const char **netlookup)
{
int i;
if (!net_type)
return -1;
for (i = 0; i < NET_CLIENT_DRIVER_MAX; i++) {
if (!strcmp(net_type, netlookup[i])) {
return i;
}
}
return -1;
}

static int net_init_tap(netdev_t *netdev)
{
net_tap_options *tap;
tap = &netdev->op.tap;
tap->tap_fd = open("/dev/net/tun", O_RDWR);
if (tap->tap_fd < 0) {
fprintf(stderr, "failed to open TAP device: %s\n", strerror(errno));
return false;
}

/* Specify persistent tap device */
struct ifreq ifreq = {.ifr_flags = IFF_TAP | IFF_NO_PI};
strncpy(ifreq.ifr_name, "tap%d", sizeof(ifreq.ifr_name));
if (ioctl(tap->tap_fd, TUNSETIFF, &ifreq) < 0) {
fprintf(stderr, "failed to allocate TAP device: %s\n", strerror(errno));
return false;
}

fprintf(stderr, "allocated TAP interface: %s\n", ifreq.ifr_name);
assert(fcntl(tap->tap_fd, F_SETFL,
fcntl(tap->tap_fd, F_GETFL, 0) | O_NONBLOCK) >= 0);
return 0;
}

static int net_init_slirp(netdev_t *netdev)
{
// TBD: create slirp client
return 0;
}

int net_client_init(netdev_t *netdev, const char *net_type)
{
int dev_idx = find_net_dev_idx(net_type, client_driver_lookup);
if (dev_idx == -1)
return false;
else
netdev->type = dev_idx;

net_init_fun[netdev->type](netdev);

return true;
}
27 changes: 27 additions & 0 deletions net_client.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

typedef enum {
NET_CLIENT_DRIVER_TAP,
NET_CLIENT_DRIVER_USER,
NET_CLIENT_DRIVER_MAX,
} net_client_driver_t;

typedef struct {
int tap_fd;
} net_tap_options;

typedef struct {
/* TODO: Implement user option */
int temp;
} net_user_options;

typedef struct {
char *name;
net_client_driver_t type;
union {
net_tap_options tap;
net_user_options user;
} op;
} netdev_t;

int net_client_init(netdev_t *nedtev, const char *net_type);
123 changes: 85 additions & 38 deletions virtio-net.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ static void virtio_net_update_status(virtio_net_state_t *vnet, uint32_t status)
return;

/* Reset */
int tap_fd = vnet->tap_fd;
netdev_t peer = vnet->peer;
uint32_t *ram = vnet->ram;
void *priv = vnet->priv;
memset(vnet, 0, sizeof(*vnet));
vnet->tap_fd = tap_fd, vnet->ram = ram;
vnet->peer = peer, vnet->ram = ram;
vnet->priv = priv;
}

Expand Down Expand Up @@ -113,6 +113,64 @@ static bool vnet_iovec_read(struct iovec **vecs,
return n && !*nvecs;
}

static ssize_t handle_read(netdev_t *netdev,
virtio_net_queue_t *queue,
struct iovec *iovs_cursor,
size_t niovs)
{
ssize_t plen = 0;
switch (netdev->type) {
case NET_CLIENT_DRIVER_TAP:
plen = readv(netdev->op.tap.tap_fd, 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;
case NET_CLIENT_DRIVER_USER:
/* TODO: handle read */
break;
default:
break;
}

return plen;
}

static ssize_t handle_write(netdev_t *netdev,
virtio_net_queue_t *queue,
struct iovec *iovs_cursor,
size_t niovs)
{
ssize_t plen = 0;
switch (netdev->type) {
case NET_CLIENT_DRIVER_TAP:
plen = writev(netdev->op.tap.tap_fd, 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 write packet: %s\n",
strerror(errno));
}
break;
case NET_CLIENT_DRIVER_USER:
/* TODO: handle slirp_input */
break;
default:
break;
}

return plen;
}

/* Require existing 'desc_idx' to use as iteration variable, and input
* 'buffer_idx'.
*/
Expand Down Expand Up @@ -186,18 +244,10 @@ static bool vnet_iovec_read(struct iovec **vecs,
virtio_header, sizeof(virtio_header)); \
} \
\
ssize_t plen = \
VERB##v(vnet->tap_fd, buffer_iovs_cursor, buffer_niovs); \
if (plen < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { \
queue->fd_ready = false; \
ssize_t plen = handle_##VERB(&vnet->peer, queue, \
buffer_iovs_cursor, buffer_niovs); \
if (plen < 0) \
break; \
} \
if (plen < 0) { \
plen = 0; \
fprintf(stderr, "[VNET] could not " #VERB " packet: %s\n", \
strerror(errno)); \
} \
\
/* consume from available queue, write to used queue */ \
queue->last_avail++; \
ram[queue->QueueUsed + 1 + (new_used % queue->QueueNum) * 2] = \
Expand All @@ -223,15 +273,25 @@ void virtio_net_refresh_queue(virtio_net_state_t *vnet)
(vnet->Status & VIRTIO_STATUS__DEVICE_NEEDS_RESET))
return;

struct pollfd pfd = {vnet->tap_fd, POLLIN | POLLOUT, 0};
poll(&pfd, 1, 0);
if (pfd.revents & POLLIN) {
vnet->queues[VNET_QUEUE_RX].fd_ready = true;
virtio_net_try_rx(vnet);
}
if (pfd.revents & POLLOUT) {
vnet->queues[VNET_QUEUE_TX].fd_ready = true;
virtio_net_try_tx(vnet);
net_client_driver_t client_type = vnet->peer.type;
switch (client_type) {
case NET_CLIENT_DRIVER_TAP:
struct pollfd pfd = {vnet->peer.op.tap.tap_fd, POLLIN | POLLOUT, 0};
poll(&pfd, 1, 0);
if (pfd.revents & POLLIN) {
vnet->queues[VNET_QUEUE_RX].fd_ready = true;
virtio_net_try_rx(vnet);
}
if (pfd.revents & POLLOUT) {
vnet->queues[VNET_QUEUE_TX].fd_ready = true;
virtio_net_try_tx(vnet);
}
break;
case NET_CLIENT_DRIVER_USER:
/* TODO: handle slirp input/output */
break;
default:
break;
}
}

Expand Down Expand Up @@ -433,7 +493,7 @@ void virtio_net_write(hart_t *vm,
}
}

bool virtio_net_init(virtio_net_state_t *vnet)
bool virtio_net_init(virtio_net_state_t *vnet, const char *client)
{
if (vnet_dev_cnt >= VNET_DEV_CNT_MAX) {
fprintf(stderr,
Expand All @@ -444,23 +504,10 @@ bool virtio_net_init(virtio_net_state_t *vnet)
/* Allocate memory for the private member */
vnet->priv = &vnet_configs[vnet_dev_cnt++];

vnet->tap_fd = open("/dev/net/tun", O_RDWR);
if (vnet->tap_fd < 0) {
fprintf(stderr, "failed to open TAP device: %s\n", strerror(errno));
if (!net_client_init(&vnet->peer, client)) {
fprintf(stderr, "Fail to init client device %s\n", client);
return false;
}

/* Specify persistent tap device */
struct ifreq ifreq = {.ifr_flags = IFF_TAP | IFF_NO_PI};
strncpy(ifreq.ifr_name, TAP_INTERFACE, sizeof(ifreq.ifr_name));
if (ioctl(vnet->tap_fd, TUNSETIFF, &ifreq) < 0) {
fprintf(stderr, "failed to allocate TAP device: %s\n", strerror(errno));
return false;
}

fprintf(stderr, "allocated TAP interface: %s\n", ifreq.ifr_name);
assert(fcntl(vnet->tap_fd, F_SETFL,
fcntl(vnet->tap_fd, F_GETFL, 0) | O_NONBLOCK) >= 0);

return true;
}

0 comments on commit 735cb82

Please sign in to comment.