Add a test which uses the vhost_kernel_test driver to test the vhost kernel buffers support. The test uses virtio-net and vhost-net and sets up a loopback network and then tests that ping works between the interface. It also checks that unbinding/rebinding of devices and closing the involved file descriptors in different sequences during active use works correctly. Signed-off-by: Vincent Whitchurch <vincent.whitchurch@xxxxxxxx> --- tools/testing/selftests/Makefile | 1 + .../vhost_kernel/vhost_kernel_test.c | 287 ++++++++++++++++++ .../vhost_kernel/vhost_kernel_test.sh | 125 ++++++++ 3 files changed, 413 insertions(+) create mode 100644 tools/testing/selftests/vhost_kernel/vhost_kernel_test.c create mode 100755 tools/testing/selftests/vhost_kernel/vhost_kernel_test.sh diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index c852eb40c4f7..14a8349e3dc1 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -73,6 +73,7 @@ TARGETS += tmpfs TARGETS += tpm2 TARGETS += user TARGETS += vDSO +TARGETS += vhost_kernel TARGETS += vm TARGETS += x86 TARGETS += zram diff --git a/tools/testing/selftests/vhost_kernel/vhost_kernel_test.c b/tools/testing/selftests/vhost_kernel/vhost_kernel_test.c new file mode 100644 index 000000000000..b0f889bd2f72 --- /dev/null +++ b/tools/testing/selftests/vhost_kernel/vhost_kernel_test.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define _GNU_SOURCE +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <linux/if_tun.h> +#include <linux/virtio_net.h> +#include <linux/vhost.h> +#include <net/if.h> +#include <netdb.h> +#include <netinet/in.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/eventfd.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#ifndef VIRTIO_F_ACCESS_PLATFORM +#define VIRTIO_F_ACCESS_PLATFORM 33 +#endif + +#ifndef VKTEST_ATTACH_VHOST +#define VKTEST_ATTACH_VHOST _IOW(0xbf, 0x31, int) +#endif + +static int vktest; +static const int num_vqs = 2; + +static int tun_alloc(char *dev) +{ + int hdrsize = sizeof(struct virtio_net_hdr_mrg_rxbuf); + struct ifreq ifr = { + .ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR, + }; + int fd, ret; + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) + err(1, "open /dev/net/tun"); + + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + + ret = ioctl(fd, TUNSETIFF, &ifr); + if (ret < 0) + err(1, "TUNSETIFF"); + + ret = ioctl(fd, TUNSETOFFLOAD, + TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6 | TUN_F_TSO_ECN); + if (ret < 0) + err(1, "TUNSETOFFLOAD"); + + ret = ioctl(fd, TUNSETVNETHDRSZ, &hdrsize); + if (ret < 0) + err(1, "TUNSETVNETHDRSZ"); + + strncpy(dev, ifr.ifr_name, IFNAMSIZ); + + return fd; +} + +static void handle_signal(int signum) +{ + if (signum == SIGUSR1) + close(vktest); +} + +static void vhost_net_set_backend(int vhost) +{ + char if_name[IFNAMSIZ]; + int tap_fd; + + snprintf(if_name, IFNAMSIZ, "vhostkernel%d", 0); + + tap_fd = tun_alloc(if_name); + + for (int i = 0; i < num_vqs; i++) { + struct vhost_vring_file txbackend = { + .index = i, + .fd = tap_fd, + }; + int ret; + + ret = ioctl(vhost, VHOST_NET_SET_BACKEND, &txbackend); + if (ret < 0) + err(1, "VHOST_NET_SET_BACKEND"); + } +} + +static void prepare_vhost_vktest(int vhost, int vktest) +{ + uint64_t features = 1llu << VIRTIO_F_ACCESS_PLATFORM | 1llu << VIRTIO_F_VERSION_1; + int ret; + + for (int i = 0; i < num_vqs; i++) { + int kickfd = eventfd(0, EFD_CLOEXEC); + + if (kickfd < 0) + err(1, "eventfd"); + + struct vhost_vring_file kick = { + .index = i, + .fd = kickfd, + }; + + ret = ioctl(vktest, VHOST_SET_VRING_KICK, &kick); + if (ret < 0) + err(1, "VHOST_SET_VRING_KICK"); + + ret = ioctl(vhost, VHOST_SET_VRING_KICK, &kick); + if (ret < 0) + err(1, "VHOST_SET_VRING_KICK"); + } + + for (int i = 0; i < num_vqs; i++) { + int callfd = eventfd(0, EFD_CLOEXEC); + + if (callfd < 0) + err(1, "eventfd"); + + struct vhost_vring_file call = { + .index = i, + .fd = callfd, + }; + + ret = ioctl(vktest, VHOST_SET_VRING_CALL, &call); + if (ret < 0) + err(1, "VHOST_SET_VRING_CALL"); + + ret = ioctl(vhost, VHOST_SET_VRING_CALL, &call); + if (ret < 0) + err(1, "VHOST_SET_VRING_CALL"); + } + + ret = ioctl(vhost, VHOST_SET_FEATURES, &features); + if (ret < 0) + err(1, "VHOST_SET_FEATURES"); +} + +static void test_attach(void) +{ + int vktest, vktest2; + int vhost; + int ret; + + vhost = open("/dev/vhost-net-kernel", O_RDONLY); + if (vhost < 0) + err(1, "vhost"); + + vktest = open("/dev/vktest", O_RDONLY); + if (vktest < 0) + err(1, "vhost"); + + ret = ioctl(vhost, VHOST_SET_OWNER); + if (ret < 0) + err(1, "VHOST_SET_OWNER"); + + prepare_vhost_vktest(vhost, vktest); + + ret = ioctl(vktest, VKTEST_ATTACH_VHOST, vhost); + if (ret < 0) + err(1, "VKTEST_ATTACH_VHOST"); + + vktest2 = open("/dev/vktest", O_RDONLY); + if (vktest2 < 0) + err(1, "vktest"); + + ret = ioctl(vktest2, VKTEST_ATTACH_VHOST, vhost); + if (ret == 0) + errx(1, "Second attach did not fail"); + + close(vktest2); + close(vktest); + close(vhost); +} + +int main(int argc, char *argv[]) +{ + bool serve = false; + uint64_t features; + int vhost; + struct option options[] = { + { "serve", no_argument, NULL, 's' }, + {} + }; + + while (1) { + int c; + + c = getopt_long_only(argc, argv, "", options, NULL); + if (c == -1) + break; + + switch (c) { + case 's': + serve = true; + break; + case '?': + default: + errx(1, "usage %s [--serve]", argv[0]); + } + }; + + if (!serve) { + test_attach(); + return 0; + } + + vhost = open("/dev/vhost-net-kernel", O_RDONLY); + if (vhost < 0) + err(1, "vhost"); + + int ret; + + ret = ioctl(vhost, VHOST_SET_OWNER); + if (ret < 0) + err(1, "VHOST_SET_OWNER"); + + vktest = open("/dev/vktest", O_RDONLY); + if (vktest < 0) + err(1, "vktest"); + + for (int i = 0; i < num_vqs; i++) { + int kickfd; + + kickfd = eventfd(0, EFD_CLOEXEC); + if (kickfd < 0) + err(1, "eventfd"); + + struct vhost_vring_file kick = { + .index = i, + .fd = kickfd, + }; + + ret = ioctl(vktest, VHOST_SET_VRING_KICK, &kick); + if (ret < 0) + err(1, "VHOST_SET_VRING_KICK"); + + ret = ioctl(vhost, VHOST_SET_VRING_KICK, &kick); + if (ret < 0) + err(1, "VHOST_SET_VRING_KICK"); + } + + for (int i = 0; i < num_vqs; i++) { + int callfd; + + callfd = eventfd(0, EFD_CLOEXEC); + if (callfd < 0) + err(1, "eventfd"); + + struct vhost_vring_file call = { + .index = i, + .fd = callfd, + }; + + ret = ioctl(vktest, VHOST_SET_VRING_CALL, &call); + if (ret < 0) + err(1, "VHOST_SET_VRING_CALL"); + + ret = ioctl(vhost, VHOST_SET_VRING_CALL, &call); + if (ret < 0) + err(1, "VHOST_SET_VRING_CALL"); + } + + features = 1llu << VIRTIO_F_ACCESS_PLATFORM | 1llu << VIRTIO_F_VERSION_1; + ret = ioctl(vhost, VHOST_SET_FEATURES, &features); + if (ret < 0) + err(1, "VHOST_SET_FEATURES"); + + vhost_net_set_backend(vhost); + + ret = ioctl(vktest, VKTEST_ATTACH_VHOST, vhost); + if (ret < 0) + err(1, "VKTEST_ATTACH_VHOST"); + + signal(SIGUSR1, handle_signal); + + while (1) + pause(); + + return 0; +} diff --git a/tools/testing/selftests/vhost_kernel/vhost_kernel_test.sh b/tools/testing/selftests/vhost_kernel/vhost_kernel_test.sh new file mode 100755 index 000000000000..82b7896cea68 --- /dev/null +++ b/tools/testing/selftests/vhost_kernel/vhost_kernel_test.sh @@ -0,0 +1,125 @@ +#!/bin/sh -ex +# SPDX-License-Identifier: GPL-2.0-only + +cleanup() { + [ -z "$PID" ] || kill $PID 2>/dev/null || : + [ -z "$PINGPID0" ] || kill $PINGPID0 2>/dev/null || : + [ -z "$PINGPID1" ] || kill $PINGPID1 2>/dev/null || : + ip netns del g2h 2>/dev/null || : + ip netns del h2g 2>/dev/null || : +} + +fail() { + echo "FAIL: $*" + exit 1 +} + +./vhost_kernel_test || fail "Sanity test failed" + +cleanup +trap cleanup EXIT + +test_one() { + ls /sys/class/net/ > before + echo > new + + ./vhost_kernel_test --serve & + PID=$! + + echo 'Waiting for interfaces' + + timeout=5 + while [ $timeout -gt 0 ]; do + timeout=$(($timeout - 1)) + sleep 1 + ls /sys/class/net/ > after + grep -F -x -v -f before after > new || continue + [ $(wc -l < new) -eq 2 ] || continue + break + done + + g2h= + h2g= + + while IFS= read -r iface; do + case $iface in + vhostkernel*) + h2g=$iface + ;; + *) + # Assumed to be virtio-net + g2h=$iface + ;; + esac + + done<new + + [ "$g2h" ] || fail "Did not find guest-to-host interface" + [ "$h2g" ] || fail "Did not find host-to-guest interface" + + # IPv6 link-local addresses prevent short-circuit delivery. + hostip=fe80::0 + guestip=fe80::1 + + # Move the interfaces out of the default namespaces to prevent network manager + # daemons from messing with them. + ip netns add g2h + ip netns add h2g + + ip link set dev $h2g netns h2g + ip netns exec h2g ip addr add dev $h2g scope link $hostip + ip netns exec h2g ip link set dev $h2g up + + ip link set dev $g2h netns g2h + ip netns exec g2h ip addr add dev $g2h scope link $guestip + ip netns exec g2h ip link set dev $g2h up + + # ip netns exec g2h tcpdump -i $g2h -w $g2h.pcap & + # ip netns exec h2g tcpdump -i $h2g -w $h2g.pcap & + + ip netns exec h2g ping6 -c10 -A -s 20000 $guestip%$h2g + ip netns exec g2h ping6 -c10 -A -s 20000 $hostip%$g2h +} + +start_background_flood() { + ip netns exec h2g ping6 -f $guestip%$h2g & + PINGPID0=$! + ip netns exec g2h ping6 -f $hostip%$g2h & + PINGPID1=$! + sleep 1 +} + +echo TEST: Basic test +test_one +# Trigger cleanup races +start_background_flood +cleanup + +echo TEST: Close vhost_test fd before vhost +test_one +start_background_flood +kill -USR1 $PID +PID= +cleanup + +echo TEST: Unbind virtio_net and close +test_one +start_background_flood +echo virtio0 > /sys/bus/virtio/drivers/virtio_net/unbind +cleanup + +echo TEST: Unbind and rebind virtio_net +test_one +start_background_flood +echo virtio0 > /sys/bus/virtio/drivers/virtio_net/unbind +echo virtio0 > /sys/bus/virtio/drivers/virtio_net/bind +# We assume that $g2h is the same after the new probe +ip link set dev $g2h netns g2h +ip netns exec g2h ip addr add dev $g2h scope link $guestip +ip netns exec g2h ip link set dev $g2h up +ip netns exec g2h ping6 -c10 -A -s 20000 $hostip%$g2h +cleanup + +trap - EXIT + +echo OK -- 2.28.0