Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ccb09b1
tests/run.sh: Add macOS cross-compilation support for guest-agent
mtjhrc Feb 2, 2026
5bb56f5
Makefile: add macOS support to test target
mtjhrc Feb 2, 2026
d84eeaa
tests/README.md: document running tests on macOS
mtjhrc Feb 2, 2026
5e75488
tests: Add support for skipping tests
mtjhrc Feb 2, 2026
8ae32b6
tests: Fix TcpTester::run_server closing TCP stream too early
mtjhrc Feb 11, 2026
03d29c1
tests: Explicitly specify a /tmp directory
mtjhrc Feb 12, 2026
a78aebf
net: add a new flag NET_FLAG_INCLUDE_VNET_HEADER
akerouanton Dec 17, 2025
3b69f88
examples/chroot_vm: Add ability to attach a TAP for networking
mtjhrc Feb 4, 2026
34a23cd
utils: Introduce a set_non_blocking method for file descriptors
mtjhrc Feb 4, 2026
c777eb1
tests: Introduce virtio-net tests (tap, passt, gvproxy)
mtjhrc Feb 4, 2026
b9df69a
fixup: (different branch) do not run unit tests on macos
mtjhrc Feb 11, 2026
126e26d
fixup! tests: Introduce virtio-net tests (tap, passt, gvproxy)
mtjhrc Feb 16, 2026
35fad10
tests: WIP introduce performance tests
mtjhrc Feb 16, 2026
906f470
fixup: vmnet helper test
mtjhrc Feb 24, 2026
cd9a3bc
fixup tests
mtjhrc Feb 24, 2026
3d0210a
CI: run NET tests
mtjhrc Feb 24, 2026
3a8db91
tests: Add vmnet-helper tests
mtjhrc Feb 16, 2026
7107ff0
devices/queue: Add Debug impl for DescriptorChain
mtjhrc Feb 24, 2026
11298b1
devices: Introduce test_utils for creating driving virtio queues
mtjhrc Feb 24, 2026
7c80d97
devices: Introduce batch_queue utilities: TxQueueConsumer and RxQueue…
mtjhrc Feb 24, 2026
4490a24
devices/net: Rewrite virtio-net in terms of new batch_queue utilities
mtjhrc Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,21 @@ clean:

clean-all: clean clean-sysroot

test-prefix/lib64/libkrun.pc: $(LIBRARY_RELEASE_$(OS))
test-prefix/$(LIBDIR_$(OS))/libkrun.pc: $(LIBRARY_RELEASE_$(OS))
mkdir -p test-prefix
PREFIX="$$(realpath test-prefix)" make install

test-prefix: test-prefix/lib64/libkrun.pc
test-prefix: test-prefix/$(LIBDIR_$(OS))/libkrun.pc

TEST ?= all
TEST_FLAGS ?=

# Library path variable differs by OS
LIBPATH_VAR_Linux = LD_LIBRARY_PATH
LIBPATH_VAR_Darwin = DYLD_LIBRARY_PATH
# Extra library paths needed for tests (libkrunfw, llvm)
EXTRA_LIBPATH_Linux =
EXTRA_LIBPATH_Darwin = /opt/homebrew/opt/libkrunfw/lib:/opt/homebrew/opt/llvm/lib

test: test-prefix
cd tests; RUST_LOG=trace LD_LIBRARY_PATH="$$(realpath ../test-prefix/lib64/)" PKG_CONFIG_PATH="$$(realpath ../test-prefix/lib64/pkgconfig/)" ./run.sh test --test-case "$(TEST)" $(TEST_FLAGS)
cd tests; RUST_LOG=trace $(LIBPATH_VAR_$(OS))="$$(realpath ../test-prefix/$(LIBDIR_$(OS))/):$(EXTRA_LIBPATH_$(OS)):$${$(LIBPATH_VAR_$(OS))}" PKG_CONFIG_PATH="$$(realpath ../test-prefix/$(LIBDIR_$(OS))/pkgconfig/)" ./run.sh test --test-case "$(TEST)" $(TEST_FLAGS)
39 changes: 31 additions & 8 deletions examples/chroot_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
enum net_mode {
NET_MODE_PASST = 0,
NET_MODE_TSI,
NET_MODE_TAP,
};

static void print_help(char *const name)
Expand All @@ -38,8 +39,9 @@ static void print_help(char *const name)
" --log=PATH Write libkrun log to file or named pipe at PATH\n"
" --color-log=PATH Write libkrun log to file or named pipe at PATH, use color\n"
" --net=NET_MODE Set network mode\n"
" --passt-socket=PATH Instead of starting passt, connect to passt socket at PATH"
"NET_MODE can be either TSI (default) or PASST\n"
" --passt-socket=PATH Instead of starting passt, connect to passt socket at PATH\n"
" --tap=NAME Use TAP device NAME for networking\n"
"NET_MODE can be TSI (default), PASST, or TAP\n"
"\n"
"NEWROOT: the root directory of the vm\n"
"COMMAND: the command you want to execute in the vm\n"
Expand All @@ -54,6 +56,7 @@ static const struct option long_options[] = {
{ "color-log", required_argument, NULL, 'C' },
{ "net_mode", required_argument, NULL, 'N' },
{ "passt-socket", required_argument, NULL, 'P' },
{ "tap", required_argument, NULL, 'T' },
{ NULL, 0, NULL, 0 }
};

Expand All @@ -63,6 +66,7 @@ struct cmdline {
uint32_t log_style;
enum net_mode net_mode;
char const *passt_socket_path;
char const *tap_name;
char const *new_root;
char *const *guest_argv;
};
Expand All @@ -89,6 +93,7 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
.show_help = false,
.net_mode = NET_MODE_TSI,
.passt_socket_path = NULL,
.tap_name = NULL,
.new_root = NULL,
.guest_argv = NULL,
.log_target = KRUN_LOG_TARGET_DEFAULT,
Expand Down Expand Up @@ -116,6 +121,8 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
cmdline->net_mode = NET_MODE_TSI;
} else if(strcasecmp("PASST", optarg) == 0) {
cmdline->net_mode = NET_MODE_PASST;
} else if(strcasecmp("TAP", optarg) == 0) {
cmdline->net_mode = NET_MODE_TAP;
} else {
fprintf(stderr, "Unknown mode %s\n", optarg);
return false;
Expand All @@ -124,6 +131,10 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
case 'P':
cmdline->passt_socket_path = optarg;
break;
case 'T':
cmdline->tap_name = optarg;
cmdline->net_mode = NET_MODE_TAP;
break;
case '?':
return false;
default:
Expand Down Expand Up @@ -268,15 +279,18 @@ int main(int argc, char *const argv[])
return -1;
}

// Map port 18000 in the host to 8000 in the guest (if networking uses TSI)
if (cmdline.net_mode == NET_MODE_TSI) {
// Configure networking based on mode
uint8_t mac[] = {0x5a, 0x94, 0xef, 0xe4, 0x0c, 0xee};
switch (cmdline.net_mode) {
case NET_MODE_TSI:
// Map port 18000 in the host to 8000 in the guest
if (err = krun_set_port_map(ctx_id, &port_map[0])) {
errno = -err;
perror("Error configuring port map");
return -1;
}
} else {
uint8_t mac[] = {0x5a, 0x94, 0xef, 0xe4, 0x0c, 0xee};
break;
case NET_MODE_PASST:
if (cmdline.passt_socket_path != NULL) {
if (err = krun_add_net_unixstream(ctx_id, cmdline.passt_socket_path, -1, &mac[0], COMPAT_NET_FEATURES, 0)) {
errno = -err;
Expand All @@ -285,17 +299,26 @@ int main(int argc, char *const argv[])
}
} else {
int passt_fd = start_passt();

if (passt_fd < 0) {
return -1;
}

if (err = krun_add_net_unixstream(ctx_id, NULL, passt_fd, &mac[0], COMPAT_NET_FEATURES, 0)) {
errno = -err;
perror("Error configuring net mode");
return -1;
}
}
break;
case NET_MODE_TAP:
if (cmdline.tap_name == NULL) {
return -1;
}
if (err = krun_add_net_tap(ctx_id, (char *)cmdline.tap_name, &mac[0], COMPAT_NET_FEATURES, 0)) {
errno = -err;
perror("Error configuring TAP network");
return -1;
}
break;
}

// Configure the rlimits that will be set in the guest
Expand Down
Binary file added examples/chroot_vm_test
Binary file not shown.
67 changes: 67 additions & 0 deletions examples/test_tap_net.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/bin/bash
# Test script for virtio-net with tap backend
# This script builds libkrun, compiles chroot_vm, and runs a network test

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PREFIX="$SCRIPT_DIR/libkrun-prefix"
CHROOT_ROOT="${CHROOT_ROOT:-/home/mhrica/c/my_rootfs2}"
TAP_DEVICE="${TAP_DEVICE:-tap0}"

echo "=== libkrun tap network test ==="
echo "Project root: $PROJECT_ROOT"
echo "Prefix: $PREFIX"
echo "Chroot root: $CHROOT_ROOT"
echo "TAP device: $TAP_DEVICE"
echo ""

# Build and install libkrun
cd "$PROJECT_ROOT"

if [[ "$1" == "--rebuild" ]] || [[ ! -f "$PREFIX/lib64/libkrun.so" ]]; then
echo "=== Building libkrun ==="
make clean 2>/dev/null || true
make PREFIX="$PREFIX"
make install PREFIX="$PREFIX"
fi

# Compile chroot_vm
echo "=== Compiling chroot_vm_test ==="
cd "$SCRIPT_DIR"
PKG_CONFIG_PATH="$PREFIX/lib64/pkgconfig" \
gcc -g -o chroot_vm_test chroot_vm.c $(pkg-config --cflags --libs libkrun)

# Create a named pipe for log output
LOG_PIPE="$SCRIPT_DIR/krun_log_pipe"
rm -f "$LOG_PIPE"
mkfifo "$LOG_PIPE"

# Start log reader in background
echo "=== Starting log reader ==="
cat "$LOG_PIPE" &
LOG_PID=$!

# Cleanup on exit
cleanup() {
kill $LOG_PID 2>/dev/null || true
rm -f "$LOG_PIPE"
}
trap cleanup EXIT

# Run the test
echo "=== Running chroot_vm with tap backend ==="
echo "Command: LD_LIBRARY_PATH=$PREFIX/lib64 ./chroot_vm_test --color-log=$LOG_PIPE --net=TAP --tap=$TAP_DEVICE $CHROOT_ROOT /usr/bin/ping -c 3 8.8.8.8"
echo ""

LD_LIBRARY_PATH="$PREFIX/lib64" \
./chroot_vm_test \
--color-log="$LOG_PIPE" \
--net=TAP \
--tap="$TAP_DEVICE" \
"$CHROOT_ROOT" \
/guest_net_test.sh

echo ""
echo "=== Test complete ==="
1 change: 1 addition & 0 deletions include/libkrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ int32_t krun_add_virtiofs2(uint32_t ctx_id,
/* Send the VFKIT magic after establishing the connection,
as required by gvproxy in vfkit mode. */
#define NET_FLAG_VFKIT 1 << 0
#define NET_FLAG_INCLUDE_VNET_HEADER 1 << 1

/* TSI (Transparent Socket Impersonation) feature flags for vsock */
#define KRUN_TSI_HIJACK_INET (1 << 0)
Expand Down
4 changes: 4 additions & 0 deletions src/devices/src/virtio/net/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ struct VirtioNetConfig {
mac: [u8; 6],
status: u16,
max_virtqueue_pairs: u16,
include_vnet_header: bool,
}

// Safe because it only has data and has no implicit padding.
Expand Down Expand Up @@ -88,6 +89,7 @@ impl Net {
cfg_backend: VirtioNetBackend,
mac: [u8; 6],
features: u32,
include_vnet_header: bool,
) -> Result<Self> {
let avail_features = features as u64
| (1 << VIRTIO_NET_F_MAC)
Expand All @@ -98,6 +100,7 @@ impl Net {
mac,
status: 0,
max_virtqueue_pairs: 0,
include_vnet_header,
};

Ok(Net {
Expand Down Expand Up @@ -187,6 +190,7 @@ impl VirtioDevice for Net {
interrupt.clone(),
mem.clone(),
self.acked_features,
self.config.include_vnet_header,
self.cfg_backend.clone(),
) {
Ok(worker) => {
Expand Down
7 changes: 6 additions & 1 deletion src/devices/src/virtio/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ use virtio_bindings::virtio_net::virtio_net_hdr_v1;

use super::QueueConfig;

pub const MAX_BUFFER_SIZE: usize = 65562;
/// Each frame forwarded to a unixstream backend is prepended by a 4 byte "header".
/// It is interpreted as a big-endian u32 integer and is the length of the following ethernet frame.
/// In order to avoid unnecessary allocations and copies, the TX buffer is allocated with extra
/// space to accommodate this header.
const FRAME_HEADER_LEN: usize = 4;
pub const MAX_BUFFER_SIZE: usize = 65562 + FRAME_HEADER_LEN;
const QUEUE_SIZE: u16 = 1024;
pub const NUM_QUEUES: usize = 2;
pub static QUEUE_CONFIG: [QueueConfig; NUM_QUEUES] = [QueueConfig::new(QUEUE_SIZE); NUM_QUEUES];
Expand Down
37 changes: 30 additions & 7 deletions src/devices/src/virtio/net/tap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,24 @@ use virtio_bindings::virtio_net::{
};

use super::backend::{ConnectError, NetBackend, ReadError, WriteError};
use super::{write_virtio_net_hdr, FRAME_HEADER_LEN};

ioctl_write_ptr!(tunsetiff, b'T', 202, c_int);
ioctl_write_int!(tunsetoffload, b'T', 208);
ioctl_write_ptr!(tunsetvnethdrsz, b'T', 216, c_int);

pub struct Tap {
fd: OwnedFd,
include_vnet_header: bool,
}

impl Tap {
/// Create an endpoint using the file descriptor of a tap device
pub fn new(tap_name: String, vnet_features: u64) -> Result<Self, ConnectError> {
pub fn new(
tap_name: String,
vnet_features: u64,
include_vnet_header: bool,
) -> Result<Self, ConnectError> {
let fd = match open("/dev/net/tun", OFlag::O_RDWR, Mode::empty()) {
Ok(fd) => fd,
Err(err) => return Err(ConnectError::OpenNetTun(err)),
Expand All @@ -41,7 +47,10 @@ impl Tap {
);
}

req.ifr_ifru.ifru_flags = IFF_TAP as i16 | IFF_NO_PI as i16 | IFF_VNET_HDR as i16;
req.ifr_ifru.ifru_flags = IFF_TAP as i16 | IFF_NO_PI as i16;
if include_vnet_header {
req.ifr_ifru.ifru_flags |= IFF_VNET_HDR as i16;
}

let mut offload_flags: u64 = 0;
if (vnet_features & (1 << VIRTIO_NET_F_GUEST_CSUM)) != 0 {
Expand Down Expand Up @@ -84,15 +93,24 @@ impl Tap {
Err(e) => error!("couldn't obtain fd flags id={fd:?}, err={e}"),
};

Ok(Self { fd })
Ok(Self {
fd,
include_vnet_header,
})
}
}

impl NetBackend for Tap {
/// Try to read a frame from the tap devie. If no bytes are available reports
/// ReadError::NothingRead.
fn read_frame(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
let frame_length = match read(&self.fd, buf) {
let buf_offset = if !self.include_vnet_header {
write_virtio_net_hdr(buf)
} else {
0
};

let frame_length = match read(&self.fd, &mut buf[buf_offset..]) {
Ok(f) => f,
#[allow(unreachable_patterns)]
Err(nix::Error::EAGAIN | nix::Error::EWOULDBLOCK) => {
Expand All @@ -103,12 +121,17 @@ impl NetBackend for Tap {
}
};
debug!("Read eth frame from tap: {frame_length} bytes");
Ok(frame_length)
Ok(buf_offset + frame_length)
}

/// Try to write a frame to the tap device.
fn write_frame(&mut self, _hdr_len: usize, buf: &mut [u8]) -> Result<(), WriteError> {
let ret = write(&self.fd, buf).map_err(WriteError::Internal)?;
fn write_frame(&mut self, hdr_len: usize, buf: &mut [u8]) -> Result<(), WriteError> {
let buf_offset = if !self.include_vnet_header {
hdr_len
} else {
FRAME_HEADER_LEN
};
let ret = write(&self.fd, buf[buf_offset..]).map_err(WriteError::Internal)?;
debug!("Written frame size={}, written={}", buf.len(), ret);
Ok(())
}
Expand Down
Loading