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 22, 2024
1 parent a0ad61b commit 15f6f8a
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 46 deletions.
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ ifeq ($(call has, VIRTIOBLK), 1)
endif
endif

NETDEV ?= tap
# virtio-net
ENABLE_VIRTIONET ?= 1
ifneq ($(UNAME_S),Linux)
Expand All @@ -39,6 +40,7 @@ endif
$(call set-feature, VIRTIONET)
ifeq ($(call has, VIRTIONET), 1)
OBJS_EXTRA += virtio-net.o
OBJS_EXTRA += netdev.o
endif

BIN = semu
Expand Down Expand Up @@ -96,7 +98,7 @@ ext4.img:

check: $(BIN) minimal.dtb $(KERNEL_DATA) $(INITRD_DATA) $(DISKIMG_FILE)
@$(call notice, Ready to launch Linux kernel. Please be patient.)
$(Q)./$(BIN) -k $(KERNEL_DATA) -c $(SMP) -b minimal.dtb -i $(INITRD_DATA) $(OPTS)
$(Q)./$(BIN) -k $(KERNEL_DATA) -c $(SMP) -b minimal.dtb -i $(INITRD_DATA) -n $(NETDEV) $(OPTS)

build-image:
scripts/build-image.sh
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 "netdev.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 *name);
#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 netdev.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 "netdev.h"

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

static int (*const net_init_fun[NET_DEV_DRIVER_MAX])(const netdev_t *) = {
[NET_DEV_DRIVER_TAP] = net_init_tap,
[NET_DEV_DRIVER_USER] = net_init_slirp,
};

static const char *netdev_driver_lookup[] = {
[NET_DEV_DRIVER_TAP] = "tap",
[NET_DEV_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_DEV_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 dev
return 0;
}

int netdev_init(netdev_t *netdev, const char *net_type)
{
int dev_idx = find_net_dev_idx(net_type, netdev_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 netdev.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

typedef enum {
NET_DEV_DRIVER_TAP,
NET_DEV_DRIVER_USER,
NET_DEV_DRIVER_MAX,
} net_dev_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_dev_driver_t type;
union {
net_tap_options tap;
net_user_options user;
} op;
} netdev_t;

int netdev_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_DEV_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_DEV_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_DEV_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_DEV_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_dev_driver_t dev_type = vnet->peer.type;
switch (dev_type) {
case NET_DEV_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_DEV_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 *name)
{
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 (!netdev_init(&vnet->peer, name)) {
fprintf(stderr, "Fail to init net device %s\n", name);
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 15f6f8a

Please sign in to comment.