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.whitchu...@axis.com>
---
 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

_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization

Reply via email to