diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 037c7efab5c996..afa0059eafb61e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: sudo apt update -y sudo apt install -y ccache libjsmn-dev libfuse3-dev clang lld llvm qemu-system-x86 \ qemu-utils sshpass cloud-image-utils binutils cmake ninja-build liblzma-dev libz-dev \ - pkg-config autoconf libtool + pkg-config autoconf libtool libslirp-dev libglib2.0-dev - name: Install patched binutils for Windows if: runner.os == 'Windows' diff --git a/tools/lkl/Makefile.autoconf b/tools/lkl/Makefile.autoconf index 654dcb93f3d0bf..421a090816c2c8 100644 --- a/tools/lkl/Makefile.autoconf +++ b/tools/lkl/Makefile.autoconf @@ -63,6 +63,12 @@ define virtio_net_vde LDLIBS += $(shell pkg-config --libs vdeplug) endef +define virtio_net_slirp + $(call set_autoconf_var,VIRTIO_NET_SLIRP,y) + export SLIRP_CFLAGS := $(shell $(1) --cflags slirp) + LDLIBS += $(shell $(1) --libs slirp) +endef + define zpoline_conf $(eval zpoline_dir=$(abspath $(srctree)/$(1))) $(if $(strip $(foreach f, $(zpoline_dir), $(wildcard $(f)/libzpoline.so))),$(call set_autoconf_var,ZPOLINE_DIR,$(zpoline_dir))) @@ -83,6 +89,9 @@ define posix_host $(if $(filter $(1),elf64-littleriscv),$(call riscv64_host)) $(if $(filter yes,$(dpdk)),$(call virtio_net_dpdk)) $(if $(filter yes,$(vde)),$(call virtio_net_vde)) + $(if $(and $(strip $(call find_include,slirp/libslirp.h)),\ + $(shell pkg-config --exists slirp 2>/dev/null && echo y)),\ + $(call virtio_net_slirp,pkg-config)) $(if $(strip $(call find_include,fuse3/fuse.h)),$(call set_autoconf_var,FUSE,y)) $(if $(strip $(call find_include,archive.h)),$(call set_autoconf_var,ARCHIVE,y)) $(if $(strip $(call find_include,argp.h)),$(call set_autoconf_var,ARGP,y)) @@ -97,6 +106,8 @@ define nt64_host $(call set_autoconf_var,NEEDS_LARGP,y) $(call set_autoconf_var,VIRTIO_NET,y) $(call set_autoconf_var,NT64,y) + $(if $(shell $(CROSS_COMPILE)pkg-config --exists slirp 2>/dev/null && echo y),\ + $(call virtio_net_slirp,$(CROSS_COMPILE)pkg-config)) CFLAGS += -Wl,--enable-auto-image-base -Wl,--image-base -Wl,0x10000000 \ -Wl,--out-implib=$(OUTPUT)liblkl.dll.a -Wl,--export-all-symbols \ -Wl,--enable-auto-import diff --git a/tools/lkl/include/lkl.h b/tools/lkl/include/lkl.h index eaf6c4ceebc929..166bc9fc0d7667 100644 --- a/tools/lkl/include/lkl.h +++ b/tools/lkl/include/lkl.h @@ -844,6 +844,62 @@ lkl_netdev_wintap_create(const char *ifparams) } #endif +/** + * lkl_netdev_slirp_create - create slirp net_device for the virtio net backend + * + * Provides user-mode TCP/IP networking without requiring root privileges. + * Guest network is 10.0.2.0/24 with gateway at 10.0.2.2 and DNS at 10.0.2.3. + * Use lkl_netdev_slirp_add_hostfwd() for port forwarding from host to guest. + */ +#ifdef LKL_HOST_CONFIG_VIRTIO_NET_SLIRP +struct lkl_netdev *lkl_netdev_slirp_create(void); + +/** + * lkl_netdev_slirp_add_hostfwd - add port forwarding from host to guest + * + * @nd - netdev returned by lkl_netdev_slirp_create() + * @is_udp - 1 for UDP, 0 for TCP + * @host_addr - host address to listen on (e.g. "0.0.0.0") + * @host_port - host port number + * @guest_addr - guest address (e.g. "10.0.2.15") + * @guest_port - guest port number + * @returns 0 on success, -1 on failure + */ +int lkl_netdev_slirp_add_hostfwd(struct lkl_netdev *nd, int is_udp, + const char *host_addr, int host_port, + const char *guest_addr, int guest_port); + +/** + * lkl_netdev_slirp_remove_hostfwd - remove a port forwarding rule + * + * @nd - netdev returned by lkl_netdev_slirp_create() + * @is_udp - 1 for UDP, 0 for TCP + * @host_addr - host address + * @host_port - host port number + * @returns 0 on success, -1 on failure + */ +int lkl_netdev_slirp_remove_hostfwd(struct lkl_netdev *nd, int is_udp, + const char *host_addr, int host_port); +#else +static inline struct lkl_netdev *lkl_netdev_slirp_create(void) +{ + return NULL; +} +static inline int +lkl_netdev_slirp_add_hostfwd(struct lkl_netdev *nd, int is_udp, + const char *host_addr, int host_port, + const char *guest_addr, int guest_port) +{ + return -1; +} +static inline int +lkl_netdev_slirp_remove_hostfwd(struct lkl_netdev *nd, int is_udp, + const char *host_addr, int host_port) +{ + return -1; +} +#endif + /** * lkl_add_neighbor - add a permanent arp entry * @ifindex - the ifindex of the interface diff --git a/tools/lkl/lib/Build b/tools/lkl/lib/Build index b1af186a7f968d..afd4eb77e1bbc2 100644 --- a/tools/lkl/lib/Build +++ b/tools/lkl/lib/Build @@ -1,5 +1,6 @@ CFLAGS_posix-host.o += -D_FILE_OFFSET_BITS=64 CFLAGS_virtio_net_vde.o += $(pkg-config --cflags vdeplug 2>/dev/null) +CFLAGS_virtio_net_slirp.o += $(SLIRP_CFLAGS) CFLAGS_nt-host.o += -D_WIN32_WINNT=0x0600 liblkl-y += fs.o @@ -18,6 +19,7 @@ liblkl-$(LKL_HOST_CONFIG_VIRTIO_NET_FD) += virtio_net_raw.o liblkl-$(LKL_HOST_CONFIG_VIRTIO_NET_MACVTAP) += virtio_net_macvtap.o liblkl-$(LKL_HOST_CONFIG_VIRTIO_NET_DPDK) += virtio_net_dpdk.o liblkl-$(LKL_HOST_CONFIG_VIRTIO_NET_VDE) += virtio_net_vde.o +liblkl-$(LKL_HOST_CONFIG_VIRTIO_NET_SLIRP) += virtio_net_slirp.o liblkl-$(LKL_HOST_CONFIG_VIRTIO_NET_FD) += virtio_net_pipe.o liblkl-$(LKL_HOST_CONFIG_NT64) += virtio_net_wintap.o liblkl-$(LKL_HOST_CONFIG_VFIO_PCI) += vfio_pci.o diff --git a/tools/lkl/lib/virtio_net_slirp.c b/tools/lkl/lib/virtio_net_slirp.c new file mode 100644 index 00000000000000..56047b361c5c1f --- /dev/null +++ b/tools/lkl/lib/virtio_net_slirp.c @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * libslirp based virtual network interface for LKL + * + * Provides user-mode networking without requiring root privileges. + * Supports port forwarding from host to LKL guest network. + * Portable: works on both POSIX and Win32 (MinGW). + * + * Copyright (c) 2025 Sheldon Qi + */ + +#include +#include +#include +#include +#include + +#ifdef __MINGW32__ +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include + +#include "virtio.h" +#include + +/* slirp_ssize_t was introduced in libslirp 4.8.0 */ +#if !SLIRP_CHECK_VERSION(4, 8, 0) +#define slirp_ssize_t ssize_t +#endif + +/* ---- Platform abstraction ---- */ + +/* Both ends are non-blocking; used only to wake up poll() */ +struct slirp_pipe { +#ifdef __MINGW32__ + SOCKET rd, wr; +#else + int rd, wr; +#endif +}; + +#ifdef __MINGW32__ +#define SLIRP_POLLPRI 0 + +static int slirp_set_nonblock(SOCKET s) +{ + u_long mode = 1; + + return ioctlsocket(s, FIONBIO, &mode) == SOCKET_ERROR ? -1 : 0; +} + +/* Emulate a pipe with a loopback TCP connection */ +static int slirp_pipe_open(struct slirp_pipe *p) +{ + SOCKET listener, s1, s2; + struct sockaddr_in addr; + int addrlen = sizeof(addr); + + listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener == INVALID_SOCKET) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + if (bind(listener, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR || + listen(listener, 1) == SOCKET_ERROR || + getsockname(listener, (struct sockaddr *)&addr, &addrlen) == SOCKET_ERROR) { + closesocket(listener); + return -1; + } + + s1 = socket(AF_INET, SOCK_STREAM, 0); + if (s1 == INVALID_SOCKET) { + closesocket(listener); + return -1; + } + + if (connect(s1, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) { + closesocket(s1); + closesocket(listener); + return -1; + } + + s2 = accept(listener, NULL, NULL); + closesocket(listener); + if (s2 == INVALID_SOCKET) { + closesocket(s1); + return -1; + } + + if (slirp_set_nonblock(s1) < 0 || slirp_set_nonblock(s2) < 0) { + closesocket(s1); + closesocket(s2); + return -1; + } + + p->rd = s2; + p->wr = s1; + return 0; +} + +static void slirp_pipe_close(struct slirp_pipe *p) +{ + closesocket(p->rd); + closesocket(p->wr); +} + +static void slirp_pipe_kick(struct slirp_pipe *p) +{ + char c = 0; + + /* A full pipe already guarantees a pending wakeup */ + send(p->wr, &c, 1, 0); +} + +static void slirp_pipe_drain(struct slirp_pipe *p) +{ + char buf[256]; + + while (recv(p->rd, buf, sizeof(buf), 0) > 0) + ; +} + +static int64_t slirp_clock_ns(void) +{ + LARGE_INTEGER freq, count; + + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&count); + return (int64_t)((double)count.QuadPart / freq.QuadPart * 1000000000.0); +} + +static int slirp_do_poll(struct pollfd *fds, int nfds, int timeout_ms) +{ + return WSAPoll(fds, nfds, timeout_ms); +} + +static int slirp_parse_addr(const char *str, struct in_addr *addr) +{ + addr->s_addr = inet_addr(str); + return addr->s_addr == INADDR_NONE ? -1 : 0; +} + +#else /* POSIX */ + +#define SLIRP_POLLPRI POLLPRI + +static int slirp_set_nonblock(int fd) +{ + int flags = fcntl(fd, F_GETFL); + + return flags < 0 ? -1 : fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +static int slirp_pipe_open(struct slirp_pipe *p) +{ + int fds[2]; + + if (pipe(fds) < 0) + return -1; + + if (slirp_set_nonblock(fds[0]) < 0 || slirp_set_nonblock(fds[1]) < 0) { + close(fds[0]); + close(fds[1]); + return -1; + } + + p->rd = fds[0]; + p->wr = fds[1]; + return 0; +} + +static void slirp_pipe_close(struct slirp_pipe *p) +{ + close(p->rd); + close(p->wr); +} + +static void slirp_pipe_kick(struct slirp_pipe *p) +{ + char c = 0; + + /* A full pipe already guarantees a pending wakeup */ + if (write(p->wr, &c, 1) < 0) + return; +} + +static void slirp_pipe_drain(struct slirp_pipe *p) +{ + char buf[256]; + + while (read(p->rd, buf, sizeof(buf)) > 0) + ; +} + +static int64_t slirp_clock_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +static int slirp_do_poll(struct pollfd *fds, int nfds, int timeout_ms) +{ + int ret; + + do { + ret = poll(fds, nfds, timeout_ms); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static int slirp_parse_addr(const char *str, struct in_addr *addr) +{ + return inet_aton(str, addr) ? 0 : -1; +} + +#endif /* __MINGW32__ */ + +/* ---- Data structures ---- */ + +#define PKT_RING_SIZE 256 + +struct pkt_entry { + uint8_t *data; + int len; +}; + +struct lkl_netdev_slirp { + struct lkl_netdev dev; + Slirp *slirp; + + /* + * libslirp is not thread safe: serializes all libslirp calls made + * from the virtio TX path, the slirp poll thread and the hostfwd API. + */ + struct lkl_mutex *slirp_lock; + lkl_thread_t poll_tid; + int running; + /* wakes up the slirp poll thread */ + struct slirp_pipe wake; + + /* packets from slirp waiting to be received by the guest */ + struct pkt_entry rx_ring[PKT_RING_SIZE]; + int rx_head; + int rx_tail; + int hup; + struct lkl_mutex *rx_lock; + /* wakes up the virtio net poll thread */ + struct slirp_pipe pipe; +}; + +/* ---- slirp callbacks ---- */ + +static slirp_ssize_t slirp_send_packet_cb(const void *buf, size_t len, + void *opaque) +{ + struct lkl_netdev_slirp *nd = opaque; + struct pkt_entry *pkt; + int next; + + lkl_host_ops.mutex_lock(nd->rx_lock); + next = (nd->rx_head + 1) % PKT_RING_SIZE; + if (next == nd->rx_tail || nd->hup) { + /* Ring full or device gone: drop the packet */ + lkl_host_ops.mutex_unlock(nd->rx_lock); + return (slirp_ssize_t)len; + } + + pkt = &nd->rx_ring[nd->rx_head]; + pkt->data = malloc(len); + if (!pkt->data) { + lkl_host_ops.mutex_unlock(nd->rx_lock); + return -1; + } + memcpy(pkt->data, buf, len); + pkt->len = (int)len; + nd->rx_head = next; + + slirp_pipe_kick(&nd->pipe); + lkl_host_ops.mutex_unlock(nd->rx_lock); + + return (slirp_ssize_t)len; +} + +static void slirp_guest_error_cb(const char *msg, void *opaque) +{ + fprintf(stderr, "slirp guest error: %s\n", msg); +} + +static int64_t slirp_clock_get_ns_cb(void *opaque) +{ + return slirp_clock_ns(); +} + +struct slirp_timer { + SlirpTimerCb cb; + void *cb_opaque; + int64_t expire_ms; +}; + +static void *slirp_timer_new_cb(SlirpTimerCb cb, void *cb_opaque, void *opaque) +{ + struct slirp_timer *t = calloc(1, sizeof(*t)); + + if (t) { + t->cb = cb; + t->cb_opaque = cb_opaque; + t->expire_ms = -1; + } + return t; +} + +static void slirp_timer_free_cb(void *timer, void *opaque) +{ + free(timer); +} + +static void slirp_timer_mod_cb(void *timer, int64_t expire_time, void *opaque) +{ + struct slirp_timer *t = timer; + + t->expire_ms = expire_time; +} + +static void slirp_register_poll_fd_cb(int fd, void *opaque) +{ +} + +static void slirp_unregister_poll_fd_cb(int fd, void *opaque) +{ +} + +static void slirp_notify_cb(void *opaque) +{ + struct lkl_netdev_slirp *nd = opaque; + + slirp_pipe_kick(&nd->wake); +} + +static const SlirpCb slirp_callbacks = { + .send_packet = slirp_send_packet_cb, + .guest_error = slirp_guest_error_cb, + .clock_get_ns = slirp_clock_get_ns_cb, + .timer_new = slirp_timer_new_cb, + .timer_free = slirp_timer_free_cb, + .timer_mod = slirp_timer_mod_cb, + .register_poll_fd = slirp_register_poll_fd_cb, + .unregister_poll_fd = slirp_unregister_poll_fd_cb, + .notify = slirp_notify_cb, +}; + +/* ---- slirp poll thread ---- */ + +struct poll_state { + struct pollfd *fds; + int nfds; + int capacity; +}; + +static int add_poll_cb(int fd, int events, void *opaque) +{ + struct poll_state *ps = opaque; + struct pollfd *pfd; + + if (ps->nfds >= ps->capacity) { + int capacity = ps->capacity ? ps->capacity * 2 : 16; + struct pollfd *fds; + + fds = realloc(ps->fds, sizeof(*fds) * capacity); + if (!fds) + return -1; + ps->fds = fds; + ps->capacity = capacity; + } + + pfd = &ps->fds[ps->nfds]; + pfd->fd = fd; + pfd->events = 0; + pfd->revents = 0; + if (events & SLIRP_POLL_IN) + pfd->events |= POLLIN; + if (events & SLIRP_POLL_OUT) + pfd->events |= POLLOUT; + if (events & SLIRP_POLL_PRI) + pfd->events |= SLIRP_POLLPRI; + + return ps->nfds++; +} + +static int get_revents_cb(int idx, void *opaque) +{ + struct poll_state *ps = opaque; + int revents = 0; + + if (idx < 0 || idx >= ps->nfds) + return 0; + + if (ps->fds[idx].revents & POLLIN) + revents |= SLIRP_POLL_IN; + if (ps->fds[idx].revents & POLLOUT) + revents |= SLIRP_POLL_OUT; + if (ps->fds[idx].revents & SLIRP_POLLPRI) + revents |= SLIRP_POLL_PRI; + if (ps->fds[idx].revents & POLLERR) + revents |= SLIRP_POLL_ERR; + if (ps->fds[idx].revents & POLLHUP) + revents |= SLIRP_POLL_HUP; + + return revents; +} + +static void slirp_poll_thread(void *arg) +{ + struct lkl_netdev_slirp *nd = arg; + struct poll_state ps = {0}; + + for (;;) { + uint32_t timeout = 100; + int ret; + + lkl_host_ops.mutex_lock(nd->slirp_lock); + if (!nd->running) { + lkl_host_ops.mutex_unlock(nd->slirp_lock); + break; + } + ps.nfds = 0; + add_poll_cb((int)nd->wake.rd, SLIRP_POLL_IN, &ps); + slirp_pollfds_fill(nd->slirp, &timeout, add_poll_cb, &ps); + lkl_host_ops.mutex_unlock(nd->slirp_lock); + + ret = slirp_do_poll(ps.fds, ps.nfds, (int)timeout); + if (ret > 0 && (ps.fds[0].revents & POLLIN)) + slirp_pipe_drain(&nd->wake); + + lkl_host_ops.mutex_lock(nd->slirp_lock); + slirp_pollfds_poll(nd->slirp, ret < 0, get_revents_cb, &ps); + lkl_host_ops.mutex_unlock(nd->slirp_lock); + } + + free(ps.fds); +} + +/* ---- LKL netdev ops ---- */ + +static int slirp_net_tx(struct lkl_netdev *dev, struct iovec *iov, int cnt) +{ + struct lkl_netdev_slirp *nd = + container_of(dev, struct lkl_netdev_slirp, dev); + uint8_t *buf; + int i, off, total = 0; + + for (i = 0; i < cnt; i++) + total += iov[i].iov_len; + + buf = malloc(total); + if (!buf) + return -1; + + for (i = 0, off = 0; i < cnt; i++) { + memcpy(buf + off, iov[i].iov_base, iov[i].iov_len); + off += iov[i].iov_len; + } + + lkl_host_ops.mutex_lock(nd->slirp_lock); + slirp_input(nd->slirp, buf, total); + lkl_host_ops.mutex_unlock(nd->slirp_lock); + + free(buf); + return total; +} + +static int slirp_net_rx(struct lkl_netdev *dev, struct iovec *iov, int cnt) +{ + struct lkl_netdev_slirp *nd = + container_of(dev, struct lkl_netdev_slirp, dev); + struct pkt_entry *pkt; + int i, off = 0; + + lkl_host_ops.mutex_lock(nd->rx_lock); + if (nd->rx_tail == nd->rx_head) { + lkl_host_ops.mutex_unlock(nd->rx_lock); + return -1; + } + + pkt = &nd->rx_ring[nd->rx_tail]; + for (i = 0; i < cnt && off < pkt->len; i++) { + int to_copy = pkt->len - off; + + if (to_copy > (int)iov[i].iov_len) + to_copy = (int)iov[i].iov_len; + memcpy(iov[i].iov_base, pkt->data + off, to_copy); + off += to_copy; + } + + free(pkt->data); + pkt->data = NULL; + pkt->len = 0; + nd->rx_tail = (nd->rx_tail + 1) % PKT_RING_SIZE; + lkl_host_ops.mutex_unlock(nd->rx_lock); + + return off; +} + +static int slirp_net_poll(struct lkl_netdev *dev) +{ + struct lkl_netdev_slirp *nd = + container_of(dev, struct lkl_netdev_slirp, dev); + struct pollfd pfd = { + .fd = nd->pipe.rd, + .events = POLLIN, + }; + int ret = LKL_DEV_NET_POLL_TX; + + if (slirp_do_poll(&pfd, 1, -1) < 0) + return -1; + + if (pfd.revents & (POLLHUP | POLLNVAL)) + return LKL_DEV_NET_POLL_HUP; + + if (pfd.revents & POLLIN) + slirp_pipe_drain(&nd->pipe); + + lkl_host_ops.mutex_lock(nd->rx_lock); + if (nd->rx_tail != nd->rx_head) + ret |= LKL_DEV_NET_POLL_RX; + lkl_host_ops.mutex_unlock(nd->rx_lock); + + return ret; +} + +static void slirp_net_poll_hup(struct lkl_netdev *dev) +{ + struct lkl_netdev_slirp *nd = + container_of(dev, struct lkl_netdev_slirp, dev); + + /* Stop slirp_send_packet_cb() from using the pipe once closed */ + lkl_host_ops.mutex_lock(nd->rx_lock); + nd->hup = 1; + lkl_host_ops.mutex_unlock(nd->rx_lock); + + /* this will cause a POLLHUP / POLLNVAL in the poll function */ + slirp_pipe_close(&nd->pipe); +} + +static void slirp_net_free(struct lkl_netdev *dev) +{ + struct lkl_netdev_slirp *nd = + container_of(dev, struct lkl_netdev_slirp, dev); + + lkl_host_ops.mutex_lock(nd->slirp_lock); + nd->running = 0; + lkl_host_ops.mutex_unlock(nd->slirp_lock); + slirp_pipe_kick(&nd->wake); + lkl_host_ops.thread_join(nd->poll_tid); + + slirp_cleanup(nd->slirp); + + while (nd->rx_tail != nd->rx_head) { + free(nd->rx_ring[nd->rx_tail].data); + nd->rx_tail = (nd->rx_tail + 1) % PKT_RING_SIZE; + } + + if (!nd->hup) + slirp_pipe_close(&nd->pipe); + slirp_pipe_close(&nd->wake); + lkl_host_ops.mutex_free(nd->rx_lock); + lkl_host_ops.mutex_free(nd->slirp_lock); + free(nd); +} + +static struct lkl_dev_net_ops slirp_net_ops = { + .tx = slirp_net_tx, + .rx = slirp_net_rx, + .poll = slirp_net_poll, + .poll_hup = slirp_net_poll_hup, + .free = slirp_net_free, +}; + +/* ---- Public API ---- */ + +struct lkl_netdev *lkl_netdev_slirp_create(void) +{ + struct lkl_netdev_slirp *nd; + /* Only version 1 fields are used, accepted by any libslirp 4.x */ + SlirpConfig cfg = { + .version = 1, + .in_enabled = true, + .in6_enabled = false, + .vhostname = "lkl-host", + .disable_host_loopback = false, + }; + +#ifdef __MINGW32__ + WSADATA wsa; + + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) + return NULL; +#endif + + slirp_parse_addr("10.0.2.0", &cfg.vnetwork); + slirp_parse_addr("255.255.255.0", &cfg.vnetmask); + slirp_parse_addr("10.0.2.2", &cfg.vhost); + slirp_parse_addr("10.0.2.15", &cfg.vdhcp_start); + slirp_parse_addr("10.0.2.3", &cfg.vnameserver); + + nd = calloc(1, sizeof(*nd)); + if (!nd) + return NULL; + + nd->slirp_lock = lkl_host_ops.mutex_alloc(0); + nd->rx_lock = lkl_host_ops.mutex_alloc(0); + if (!nd->slirp_lock || !nd->rx_lock) { + fprintf(stderr, "slirp: failed to allocate mutexes\n"); + goto err_free_mutex; + } + + if (slirp_pipe_open(&nd->pipe) < 0) { + fprintf(stderr, "slirp: pipe creation failed\n"); + goto err_free_mutex; + } + + if (slirp_pipe_open(&nd->wake) < 0) { + fprintf(stderr, "slirp: pipe creation failed\n"); + goto err_close_pipe; + } + + nd->slirp = slirp_new(&cfg, &slirp_callbacks, nd); + if (!nd->slirp) { + fprintf(stderr, "slirp: failed to create instance\n"); + goto err_close_wake; + } + + nd->running = 1; + nd->poll_tid = lkl_host_ops.thread_create(slirp_poll_thread, nd); + if (!nd->poll_tid) { + fprintf(stderr, "slirp: failed to create poll thread\n"); + goto err_cleanup; + } + + nd->dev.ops = &slirp_net_ops; + return &nd->dev; + +err_cleanup: + slirp_cleanup(nd->slirp); +err_close_wake: + slirp_pipe_close(&nd->wake); +err_close_pipe: + slirp_pipe_close(&nd->pipe); +err_free_mutex: + if (nd->rx_lock) + lkl_host_ops.mutex_free(nd->rx_lock); + if (nd->slirp_lock) + lkl_host_ops.mutex_free(nd->slirp_lock); + free(nd); + return NULL; +} + +int lkl_netdev_slirp_add_hostfwd(struct lkl_netdev *nd, int is_udp, + const char *host_addr, int host_port, + const char *guest_addr, int guest_port) +{ + struct lkl_netdev_slirp *nds = + container_of(nd, struct lkl_netdev_slirp, dev); + struct in_addr haddr, gaddr; + int ret; + + if (slirp_parse_addr(host_addr, &haddr) < 0 || + slirp_parse_addr(guest_addr, &gaddr) < 0) + return -1; + + lkl_host_ops.mutex_lock(nds->slirp_lock); + ret = slirp_add_hostfwd(nds->slirp, is_udp, haddr, host_port, + gaddr, guest_port); + lkl_host_ops.mutex_unlock(nds->slirp_lock); + /* make the poll thread pick up the new listening socket */ + slirp_pipe_kick(&nds->wake); + + return ret; +} + +int lkl_netdev_slirp_remove_hostfwd(struct lkl_netdev *nd, int is_udp, + const char *host_addr, int host_port) +{ + struct lkl_netdev_slirp *nds = + container_of(nd, struct lkl_netdev_slirp, dev); + struct in_addr haddr; + int ret; + + if (slirp_parse_addr(host_addr, &haddr) < 0) + return -1; + + lkl_host_ops.mutex_lock(nds->slirp_lock); + ret = slirp_remove_hostfwd(nds->slirp, is_udp, haddr, host_port); + lkl_host_ops.mutex_unlock(nds->slirp_lock); + + return ret; +} diff --git a/tools/lkl/tests/net-test.c b/tools/lkl/tests/net-test.c index 4d73a3f15b4c1c..109fd517ebd22b 100644 --- a/tools/lkl/tests/net-test.c +++ b/tools/lkl/tests/net-test.c @@ -30,10 +30,11 @@ enum { BACKEND_PIPE, BACKEND_NONE, BACKEND_WINTAP, + BACKEND_SLIRP, }; const char *backends[] = { "tap", "macvtap", "raw", "dpdk", "pipe", "loopback", - "wintap", NULL }; + "wintap", "slirp", NULL }; static struct { int backend; const char *ifname; @@ -202,6 +203,11 @@ static int lkl_test_nd_create(void) case BACKEND_WINTAP: nd = lkl_netdev_wintap_create(cla.ifname); break; +#ifdef LKL_HOST_CONFIG_VIRTIO_NET_SLIRP + case BACKEND_SLIRP: + nd = lkl_netdev_slirp_create(); + break; +#endif } if (!nd) { diff --git a/tools/lkl/tests/net.sh b/tools/lkl/tests/net.sh index 05bb2843a9d439..db6cf73914e235 100755 --- a/tools/lkl/tests/net.sh +++ b/tools/lkl/tests/net.sh @@ -25,6 +25,8 @@ cleanup_backend() ;; "wintap") ;; + "slirp") + ;; esac } @@ -125,6 +127,12 @@ setup_backend() "OpenVPN TAP-Windows6" static $(ip_host) 255.255.255.0 0.0.0.0 netsh advfirewall set allprofiles state off ;; + "slirp") + if [ -z "$LKL_HOST_CONFIG_VIRTIO_NET_SLIRP" ]; then + echo "slirp not configured" + return $TEST_SKIP + fi + ;; *) echo "don't know how to setup backend $1" return $TEST_FAILURE @@ -181,6 +189,11 @@ run_tests() --ip $(ip_lkl) --netmask-len $TEST_IP_NETMASK \ --dst $(ip_host) --sleep 10 ;; + "slirp") + lkl_test_exec $script_dir/net-test --backend slirp \ + --ip 10.0.2.15 --netmask-len 24 \ + --gateway 10.0.2.2 --dst 10.0.2.2 + ;; esac } diff --git a/tools/lkl/tests/run.py b/tools/lkl/tests/run.py index bdea6225df73fd..bed73923863639 100755 --- a/tools/lkl/tests/run.py +++ b/tools/lkl/tests/run.py @@ -59,6 +59,7 @@ def end(self, obj): 'net.sh -b raw', 'net.sh -b macvtap', 'net.sh -b wintap', + 'net.sh -b slirp', 'lklfuse.sh -t ext4', 'lklfuse.sh -t btrfs', 'lklfuse.sh -t vfat',