Skip to content

lkl: add libslirp network backend for user-mode networking - #631

Open
xdqi wants to merge 3 commits into
lkl:masterfrom
xdqi:lkl-slirp
Open

xdqi wants to merge 3 commits into
lkl:masterfrom
xdqi:lkl-slirp

Conversation

@xdqi

@xdqi xdqi commented May 4, 2026 •

Copy link
Copy Markdown

Summary

Add a libslirp-based virtio-net backend to LKL, enabling user-mode TCP/IP networking without root privileges or pre-configured TAP interfaces.

Motivation

The existing LKL network backends (TAP, macvtap, DPDK, VDE, wintap) all require either root privileges, pre-configured host interfaces, or specific hardware. libslirp provides user-mode NAT networking (the same stack as QEMU's -netdev user), so LKL guests can access the network without any host configuration.

Changes

File Description
tools/lkl/lib/virtio_net_slirp.c New slirp backend, POSIX + MinGW
tools/lkl/include/lkl.h Slirp API: lkl_netdev_slirp_create, add_hostfwd, remove_hostfwd
tools/lkl/Makefile.autoconf Auto-detection: header + pkg-config --exists slirp (POSIX), $(CROSS_COMPILE)pkg-config (NT64)
tools/lkl/lib/Build Build integration for virtio_net_slirp.o
tools/lkl/tests/ net.sh -b slirp test (ICMP to the slirp gateway)
.github/workflows/ci.yml Install libslirp-dev and libglib2.0-dev

Architecture

  • Guest network: 10.0.2.0/24, gateway 10.0.2.2, DNS 10.0.2.3
  • Packet ring buffer (256 entries) between slirp's send_packet callback and the LKL RX path
  • A dedicated poll thread drives slirp's socket I/O and has its own wakeup pipe (used by slirp's notify callback and on shutdown)
  • libslirp is not thread safe, so all libslirp calls (virtio TX path, poll thread, hostfwd API) are serialized by a mutex
  • Threads and mutexes come from lkl_host_ops; only the wakeup pipe, the clock and poll() are platform specific (POSIX and Winsock)
  • Works with libslirp 4.x (tested with 4.6.1 and 4.8.0)

API

struct lkl_netdev *lkl_netdev_slirp_create(void);
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);
int lkl_netdev_slirp_remove_hostfwd(struct lkl_netdev *nd, int is_udp,
    const char *host_addr, int host_port);

Build

Requires libslirp-dev (pkg-config: slirp, which also requires glib-2.0). The backend is auto-detected at build time, with no extra flags needed. If slirp is not installed, the backend is silently disabled.

Example usage

// Create slirp netdev — no root required
struct lkl_netdev *nd = lkl_netdev_slirp_create();

// Forward host port 12345 to guest 10.0.2.15:12345
lkl_netdev_slirp_add_hostfwd(nd, 0, "0.0.0.0", 12345, "10.0.2.15", 12345);

// Start kernel, add netdev, configure interface (same as other backends)
lkl_init(&lkl_host_ops);
lkl_start_kernel("mem=32M");
int nd_id = lkl_netdev_add(nd, NULL);
lkl_if_up(lkl_netdev_get_ifindex(nd_id));
lkl_if_set_ipv4(lkl_netdev_get_ifindex(nd_id),
    inet_addr("10.0.2.15"), 24);
lkl_set_ipv4_gateway(inet_addr("10.0.2.2"));

Disclaimer

🤖 Generated with Claude Code but I've audited the code.

@tavip tavip left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, thanks @xdqi ! Overall looks good, I'll take a closer look a bit later.

Please check the windows test failure and the checkpatch errors.

Also, please add tests similar with what we have for the other backends.

Comment thread tools/lkl/include/mingw32/sys/uio.h Outdated
Comment thread tools/lkl/lib/virtio_net_slirp.c
/* ---- slirp callbacks ---- */

static slirp_ssize_t slirp_send_packet_cb(const void *buf, size_t len, void *opaque)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as shown in the build failure, older libslirp versions (4.6.1 on github actions, 4.7.0 in my local environment) don't have this typedef...

we need to have a check for this, or force install newer libslirp, which might be a bit complicated.

lib/virtio_net_slirp.c:218:8: error: unknown type name ‘slirp_ssize_t’
  218 | static slirp_ssize_t slirp_send_packet_cb(const void *buf, size_t len, void *opaque)
      |        ^~~~~~~~~~~~~
lib/virtio_net_slirp.c: In function ‘slirp_send_packet_cb’:
lib/virtio_net_slirp.c:227:25: error: ‘slirp_ssize_t’ undeclared (first use in this function); did you mean ‘lkl_ssize_t’?
  227 |                 return (slirp_ssize_t)len;
      |                         ^~~~~~~~~~~~~

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: slirp_ssize_t falls back to ssize_t when !SLIRP_CHECK_VERSION(4, 8, 0). Tested with 4.6.1 on Ubuntu 22.04.

Comment thread tools/lkl/lib/virtio_net_slirp.c Outdated
Comment on lines +532 to +536
#ifdef __MINGW32__
WaitForSingleObject(nd->poll_thread, INFINITE);
CloseHandle(nd->poll_thread);
#else
pthread_join(nd->poll_thread, NULL);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use lkl_host_ops.thread_join() instead ? (cc: @tavip)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, it uses lkl_host_ops.thread_join() now (and lkl_host_ops.mutex_* for the locks).

Comment thread tools/lkl/lib/virtio_net_slirp.c Outdated
Comment on lines +606 to +618
#ifdef __MINGW32__
nd->poll_thread = CreateThread(NULL, 0, slirp_poll_thread_fn, nd, 0, NULL);
if (!nd->poll_thread) {
fprintf(stderr, "slirp: failed to create poll thread\n");
slirp_cleanup(nd->slirp);
slirp_pipe_close(nd->pipe[0]);
slirp_pipe_close(nd->pipe[1]);
slirp_mutex_destroy(&nd->rx_lock);
free(nd);
return NULL;
}
#else
if (pthread_create(&nd->poll_thread, NULL, slirp_poll_thread_fn, nd) != 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: can we use lkl_host_ops.thread_create() ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, lkl_host_ops.thread_create().

xdqi added 3 commits October 4, 2026 21:55
Add a virtio-net backend using libslirp that provides TCP/IP networking
without root privileges or pre-configured TAP interfaces, the same
user-mode NAT stack used by QEMU's "-netdev user".

- Guest network 10.0.2.0/24, gateway 10.0.2.2, DNS 10.0.2.3
- Port forwarding via lkl_netdev_slirp_add_hostfwd/remove_hostfwd
- Packets from slirp are queued in a ring buffer and handed to the
  virtio rx path; a dedicated poll thread drives slirp's socket I/O
- libslirp is not thread safe, so all libslirp calls (virtio tx path,
  poll thread, hostfwd API) are serialized by a mutex
- Threads and mutexes come from lkl_host_ops; only the wakeup pipe,
  clock and poll primitives are platform specific (POSIX and Win32)
- Works with libslirp 4.x; slirp_ssize_t is only defined since 4.8.0

The backend is enabled automatically when slirp/libslirp.h is found
(POSIX) or when $(CROSS_COMPILE)pkg-config finds slirp (NT64).

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
Run net-test with the slirp backend, similar to the existing pipe/tap/
raw tests. The guest uses the slirp NAT network (10.0.2.0/24) and pings
the slirp gateway 10.0.2.2. The test is skipped when the slirp backend
is not configured.

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
Install libslirp-dev on Ubuntu so the slirp network backend is built
and tested in CI. libglib2.0-dev is needed as well: slirp.pc requires
glib-2.0, but libslirp-dev does not depend on it on Ubuntu 22.04.

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
@xdqi

xdqi commented Oct 4, 2026

Copy link
Copy Markdown
Author

Thanks for the reviews @tavip @thehajime, and sorry for the delay. I rebased on the current master and squashed the series into three commits (backend, test, CI).

Changes since the last version:

  • Older libslirp (@thehajime): slirp_ssize_t only exists since libslirp 4.8.0, so it now falls back to ssize_t for older versions (SLIRP_CHECK_VERSION). The config only uses version 1 fields, which every libslirp 4.x accepts. Tested with 4.6.1 (Ubuntu 22.04) and 4.8.0.
  • Threads (@thehajime): the poll thread now uses lkl_host_ops.thread_create() / thread_join(), and the locks use lkl_host_ops.mutex_*. All the pthread / Win32 thread code is gone. The only platform-specific parts left are the wakeup pipe, the clock and poll().
  • Thread safety: libslirp is not thread safe, but slirp_input() (virtio TX path) and slirp_pollfds_poll() (poll thread) ran concurrently. All libslirp calls are now serialized by a mutex. The poll thread also has its own wakeup pipe (used by slirp's notify callback and on shutdown), and send_packet no longer writes to the RX pipe after poll_hup closes it.
  • Build detection: on Ubuntu 22.04, libslirp-dev doesn't pull in libglib2.0-dev, which slirp.pc requires. The header was found but there were no libs, so the link failed. The backend is now enabled only when pkg-config --exists slirp succeeds. NT64 uses $(CROSS_COMPILE)pkg-config, so a Linux host's libslirp isn't picked up when cross compiling for Windows.
  • CI: installs libslirp-dev libglib2.0-dev. I dropped mingw-w64-x86_64-libslirp from the MSYS2 job: MSYS's pkg-config can't see /mingw64, and MSYS binaries shouldn't link MinGW libraries anyway.
  • checkpatch: the SPDX tag is fixed (@tavip) and the whole series is clean.
  • Windows (@tavip): the empty sys/uio.h stub that broke the MSYS2 build was removed.

Testing (local; CI will confirm):

  • net.sh -b slirp (ICMP to the slirp gateway) passes with libslirp 4.6.1 and 4.8.0.
  • A separate TCP stress test (guest downloads 4 x 32 MiB from a host server through slirp while sending data, with every byte checked) passed 20/20 runs, including netdev remove/free and kernel halt.

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

Test Results

111 files  + 5  111 suites  +5   11m 9s ⏱️ +39s
216 tests +10  205 ✅ +10  11 💤 ±0  0 ❌ ±0 
828 runs  +38  770 ✅ +36  58 💤 +2  0 ❌ ±0 

Results for commit 2e083e4. ± Comparison against base commit d0f76a7.

This pull request removes 3 and adds 13 tests. Note that renamed tests count towards both.
disk-vfio-pci ‑ blkdev
disk-vfio-pci ‑ start_kernel
disk-vfio-pci ‑ stop_kernel
net slirp ‑ cleanup_backend slirp
net slirp ‑ icmp
net slirp ‑ if_up
net slirp ‑ nd_add
net slirp ‑ nd_create
net slirp ‑ nd_ifindex
net slirp ‑ nd_remove
net slirp ‑ set_gateway
net slirp ‑ set_ipv4
net slirp ‑ setup_backend slirp
…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants