public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Stefano Brivio <sbrivio@redhat.com>
To: passt-dev@passt.top
Cc: David Gibson <david@gibson.dropbear.id.au>
Subject: [PATCH v17 7/8] rampstream: Add utility to test for corruption of data streams
Date: Wed, 12 Feb 2025 02:19:44 +0100	[thread overview]
Message-ID: <20250212011945.657249-8-sbrivio@redhat.com> (raw)
In-Reply-To: <20250212011945.657249-1-sbrivio@redhat.com>

From: David Gibson <david@gibson.dropbear.id.au>

---
 test/.gitignore             |   1 +
 test/Makefile               |   5 +-
 test/migrate/rampstream_in  |  60 +++++++++++++++
 test/migrate/rampstream_out |  55 ++++++++++++++
 test/passt.mbuto            |   5 +-
 test/rampstream-check.sh    |   3 +
 test/rampstream.c           | 142 ++++++++++++++++++++++++++++++++++++
 7 files changed, 267 insertions(+), 4 deletions(-)
 create mode 100644 test/migrate/rampstream_in
 create mode 100644 test/migrate/rampstream_out
 create mode 100755 test/rampstream-check.sh
 create mode 100644 test/rampstream.c

diff --git a/test/.gitignore b/test/.gitignore
index 6dd4790..3573444 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -8,5 +8,6 @@ QEMU_EFI.fd
 *.raw.xz
 *.bin
 nstool
+rampstream
 guest-key
 guest-key.pub
diff --git a/test/Makefile b/test/Makefile
index 5e49047..bf63db8 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -52,7 +52,8 @@ UBUNTU_IMGS = $(UBUNTU_OLD_IMGS) $(UBUNTU_NEW_IMGS)
 
 DOWNLOAD_ASSETS = mbuto podman \
 	$(DEBIAN_IMGS) $(FEDORA_IMGS) $(OPENSUSE_IMGS) $(UBUNTU_IMGS)
-TESTDATA_ASSETS = small.bin big.bin medium.bin
+TESTDATA_ASSETS = small.bin big.bin medium.bin \
+	rampstream
 LOCAL_ASSETS = mbuto.img mbuto.mem.img podman/bin/podman QEMU_EFI.fd \
 	$(DEBIAN_IMGS:%=prepared-%) $(FEDORA_IMGS:%=prepared-%) \
 	$(UBUNTU_NEW_IMGS:%=prepared-%) \
@@ -85,7 +86,7 @@ podman/bin/podman: pull-podman
 guest-key guest-key.pub:
 	ssh-keygen -f guest-key -N ''
 
-mbuto.img: passt.mbuto mbuto/mbuto guest-key.pub $(TESTDATA_ASSETS)
+mbuto.img: passt.mbuto mbuto/mbuto guest-key.pub rampstream-check.sh $(TESTDATA_ASSETS)
 	./mbuto/mbuto -p ./$< -c lz4 -f $@
 
 mbuto.mem.img: passt.mem.mbuto mbuto ../passt.avx2
diff --git a/test/migrate/rampstream_in b/test/migrate/rampstream_in
new file mode 100644
index 0000000..392a8a7
--- /dev/null
+++ b/test/migrate/rampstream_in
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/migrate/basic - Check basic migration functionality
+#
+# Copyright (c) 2025 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+g1tools	ip jq dhclient socat cat
+htools	ip jq
+
+set	MAP_HOST4 192.0.2.1
+set	MAP_HOST6 2001:db8:9a55::1
+set	MAP_NS4 192.0.2.2
+set	MAP_NS6 2001:db8:9a55::2
+set	RAMPS 6000000
+
+test	Interface name
+g1out	IFNAME1 ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+hout	HOST_IFNAME ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+check	[ -n "__IFNAME1__" ]
+
+test	DHCP: address
+guest1	ip link set dev __IFNAME1__ up
+guest1	/sbin/dhclient -4 __IFNAME1__
+g1out	ADDR1 ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME1__").addr_info[0].local'
+hout	HOST_ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[0].local'
+check	[ "__ADDR1__" = "__HOST_ADDR__" ]
+
+test	DHCPv6: address
+# Link is up now, wait for DAD to complete
+guest1	while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+guest1	/sbin/dhclient -6 __IFNAME1__
+# Wait for DAD to complete on the DHCP address
+guest1	while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+g1out	ADDR1_6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME1__").addr_info[] | select(.prefixlen == 128).local] | .[0]'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
+check	[ "__ADDR1_6__" = "__HOST_ADDR6__" ]
+
+test	TCP/IPv4: host > guest
+g1out	GW1 ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+guest1b	socat -u TCP4-LISTEN:10001 EXEC:"rampstream-check.sh __RAMPS__"
+sleep	1
+hostb	socat -u EXEC:"test/rampstream send __RAMPS__" TCP4:__ADDR1__:10001
+
+sleep 1
+
+#mon	echo "migrate tcp:0:20005" | socat -u STDIN UNIX:__STATESETUP__/qemu_1_mon.sock
+
+hostw
+
+guest2	cat rampstream.stderr
+guest2	cat rampstream.result
+
diff --git a/test/migrate/rampstream_out b/test/migrate/rampstream_out
new file mode 100644
index 0000000..91b9c63
--- /dev/null
+++ b/test/migrate/rampstream_out
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/migrate/basic - Check basic migration functionality
+#
+# Copyright (c) 2025 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+g1tools	ip jq dhclient socat cat
+htools	ip jq
+
+set	MAP_HOST4 192.0.2.1
+set	MAP_HOST6 2001:db8:9a55::1
+set	MAP_NS4 192.0.2.2
+set	MAP_NS6 2001:db8:9a55::2
+set	RAMPS 6000000
+
+test	Interface name
+g1out	IFNAME1 ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+hout	HOST_IFNAME ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+check	[ -n "__IFNAME1__" ]
+
+test	DHCP: address
+guest1	ip link set dev __IFNAME1__ up
+guest1	/sbin/dhclient -4 __IFNAME1__
+g1out	ADDR1 ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME1__").addr_info[0].local'
+hout	HOST_ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[0].local'
+check	[ "__ADDR1__" = "__HOST_ADDR__" ]
+
+test	DHCPv6: address
+# Link is up now, wait for DAD to complete
+guest1	while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+guest1	/sbin/dhclient -6 __IFNAME1__
+# Wait for DAD to complete on the DHCP address
+guest1	while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+g1out	ADDR1_6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME1__").addr_info[] | select(.prefixlen == 128).local] | .[0]'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
+check	[ "__ADDR1_6__" = "__HOST_ADDR6__" ]
+
+test	TCP/IPv4: guest > host
+g1out	GW1 ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hostb	socat -u TCP4-LISTEN:10006 EXEC:"test/rampstream check __RAMPS__"
+sleep	1
+guest1b	socat -u EXEC:"rampstream send __RAMPS__" TCP4:__MAP_HOST4__:10006
+sleep	1
+
+mon	echo "migrate tcp:0:20005" | socat -u STDIN UNIX:__STATESETUP__/qemu_1_mon.sock
+
+hostw
diff --git a/test/passt.mbuto b/test/passt.mbuto
index e45a284..5e00132 100755
--- a/test/passt.mbuto
+++ b/test/passt.mbuto
@@ -13,7 +13,8 @@
 PROGS="${PROGS:-ash,dash,bash ip mount ls insmod mkdir ln cat chmod lsmod
        modprobe find grep mknod mv rm umount jq iperf3 dhclient hostname
        sed tr chown sipcalc cut socat dd strace ping tail killall sleep sysctl
-       nproc tcp_rr tcp_crr udp_rr which tee seq bc sshd ssh-keygen cmp tcpdump env}"
+       nproc tcp_rr tcp_crr udp_rr which tee seq bc sshd ssh-keygen cmp tcpdump
+       env}"
 
 # OpenSSH 9.8 introduced split binaries, with sshd being the daemon, and
 # sshd-session the per-session program. We need the latter as well, and the path
@@ -31,7 +32,7 @@ LINKS="${LINKS:-
 
 DIRS="${DIRS} /tmp /usr/sbin /usr/share /var/log /var/lib /etc/ssh /run/sshd /root/.ssh"
 
-COPIES="${COPIES} small.bin,/root/small.bin medium.bin,/root/medium.bin big.bin,/root/big.bin"
+COPIES="${COPIES} small.bin,/root/small.bin medium.bin,/root/medium.bin big.bin,/root/big.bin rampstream,/bin/rampstream rampstream-check.sh,/bin/rampstream-check.sh"
 
 FIXUP="${FIXUP}"'
 	mv /sbin/* /usr/sbin || :
diff --git a/test/rampstream-check.sh b/test/rampstream-check.sh
new file mode 100755
index 0000000..c27acdb
--- /dev/null
+++ b/test/rampstream-check.sh
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+(rampstream check "$@" 2>&1; echo $? > rampstream.status) | tee rampstream.err
diff --git a/test/rampstream.c b/test/rampstream.c
new file mode 100644
index 0000000..45adbcf
--- /dev/null
+++ b/test/rampstream.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* rampstream - Generate a check and stream of bytes in a ramp pattern
+ *
+ * Copyright Red Hat
+ * Author: David Gibson <david@gibson.dropbear.id.au>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+/* Length of the repeating ramp.  This is a deliberately not a "round" number so
+ * that we're very likely to misalign with likely block or chunk sizes of the
+ * transport.  That means we'll detect gaps in the stream, even if they occur
+ * neatly on block boundaries.  Specifically this is the largest 8-bit prime. */
+#define RAMPLEN		251
+
+#define INTERVAL	10000
+
+#define	ARRAY_SIZE(a)	((int)(sizeof(a) / sizeof((a)[0])))
+
+#define die(...)						\
+	do {							\
+		fprintf(stderr, "rampstream: " __VA_ARGS__);	\
+		exit(1);					\
+	} while (0)
+
+static void usage(void)
+{
+	die("Usage:\n"
+	    "  rampstream send <number>\n"
+	    "    Generate a ramp pattern of bytes on stdout, repeated <number>\n"
+	    "    times\n"
+	    "  rampstream check <number>\n"
+	    "    Check a ramp pattern of bytes on stdin, repeater <number>\n"
+	    "    times\n");
+}
+
+static void ramp_send(unsigned long long num, const uint8_t *ramp)
+{
+	unsigned long long i;
+
+	for (i = 0; i < num; i++) {
+		int off = 0;
+		ssize_t rc;
+
+		if (i % INTERVAL == 0)
+			fprintf(stderr, "%llu...\r", i);
+
+		while (off < RAMPLEN) {
+			rc = write(1, ramp + off, RAMPLEN - off);
+			if (rc < 0) {
+				if (errno == EINTR ||
+				    errno == EAGAIN ||
+				    errno == EWOULDBLOCK)
+					continue;
+				die("Error writing ramp: %s\n",
+				    strerror(errno));
+			}
+			if (rc == 0)
+				die("Zero length write\n");
+			off += rc;
+		}
+	}
+}
+
+static void ramp_check(unsigned long long num, const uint8_t *ramp)
+{
+	unsigned long long i;
+
+	for (i = 0; i < num; i++) {
+		uint8_t buf[RAMPLEN];
+		int off = 0;
+		ssize_t rc;
+
+		if (i % INTERVAL == 0)
+			fprintf(stderr, "%llu...\r", i);
+		
+		while (off < RAMPLEN) {
+			rc = read(0, buf + off, RAMPLEN - off);
+			if (rc < 0) {
+				if (errno == EINTR ||
+				    errno == EAGAIN ||
+				    errno == EWOULDBLOCK)
+					continue;
+				die("Error reading ramp: %s\n",
+				    strerror(errno));
+			}
+			if (rc == 0)
+				die("Unexpected EOF\n");
+			off += rc;
+		}
+
+		if (memcmp(buf, ramp, sizeof(buf)) != 0) {
+			int j, k;
+
+			for (j = 0; j < RAMPLEN; j++)
+				if (buf[j] != ramp[j])
+					break;
+			for (k = j; k < RAMPLEN && k < j + 16; k++)
+				fprintf(stderr,
+					"Byte %d: expected 0x%02x, got 0x%02x\n",
+					k, ramp[k], buf[k]);
+			die("Data mismatch, ramp %llu, byte %d\n", i, j);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	const char *subcmd = argv[1];
+	unsigned long long num;
+	uint8_t ramp[RAMPLEN];
+	char *e;
+	int i;
+
+	if (argc < 2)
+		usage();
+
+	errno = 0;
+	num = strtoull(argv[2], &e, 0);
+	if (*e || errno)
+		usage();
+
+	/* Initialize the ramp block */
+	for (i = 0; i < RAMPLEN; i++)
+		ramp[i] = i;
+
+	if (strcmp(subcmd, "send") == 0)
+		ramp_send(num, ramp);
+	else if (strcmp(subcmd, "check") == 0)
+		ramp_check(num, ramp);
+	else
+		usage();
+
+	exit(0);
+}
-- 
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* rampstream - Generate a check and stream of bytes in a ramp pattern
+ *
+ * Copyright Red Hat
+ * Author: David Gibson <david@gibson.dropbear.id.au>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+/* Length of the repeating ramp.  This is a deliberately not a "round" number so
+ * that we're very likely to misalign with likely block or chunk sizes of the
+ * transport.  That means we'll detect gaps in the stream, even if they occur
+ * neatly on block boundaries.  Specifically this is the largest 8-bit prime. */
+#define RAMPLEN		251
+
+#define INTERVAL	10000
+
+#define	ARRAY_SIZE(a)	((int)(sizeof(a) / sizeof((a)[0])))
+
+#define die(...)						\
+	do {							\
+		fprintf(stderr, "rampstream: " __VA_ARGS__);	\
+		exit(1);					\
+	} while (0)
+
+static void usage(void)
+{
+	die("Usage:\n"
+	    "  rampstream send <number>\n"
+	    "    Generate a ramp pattern of bytes on stdout, repeated <number>\n"
+	    "    times\n"
+	    "  rampstream check <number>\n"
+	    "    Check a ramp pattern of bytes on stdin, repeater <number>\n"
+	    "    times\n");
+}
+
+static void ramp_send(unsigned long long num, const uint8_t *ramp)
+{
+	unsigned long long i;
+
+	for (i = 0; i < num; i++) {
+		int off = 0;
+		ssize_t rc;
+
+		if (i % INTERVAL == 0)
+			fprintf(stderr, "%llu...\r", i);
+
+		while (off < RAMPLEN) {
+			rc = write(1, ramp + off, RAMPLEN - off);
+			if (rc < 0) {
+				if (errno == EINTR ||
+				    errno == EAGAIN ||
+				    errno == EWOULDBLOCK)
+					continue;
+				die("Error writing ramp: %s\n",
+				    strerror(errno));
+			}
+			if (rc == 0)
+				die("Zero length write\n");
+			off += rc;
+		}
+	}
+}
+
+static void ramp_check(unsigned long long num, const uint8_t *ramp)
+{
+	unsigned long long i;
+
+	for (i = 0; i < num; i++) {
+		uint8_t buf[RAMPLEN];
+		int off = 0;
+		ssize_t rc;
+
+		if (i % INTERVAL == 0)
+			fprintf(stderr, "%llu...\r", i);
+		
+		while (off < RAMPLEN) {
+			rc = read(0, buf + off, RAMPLEN - off);
+			if (rc < 0) {
+				if (errno == EINTR ||
+				    errno == EAGAIN ||
+				    errno == EWOULDBLOCK)
+					continue;
+				die("Error reading ramp: %s\n",
+				    strerror(errno));
+			}
+			if (rc == 0)
+				die("Unexpected EOF\n");
+			off += rc;
+		}
+
+		if (memcmp(buf, ramp, sizeof(buf)) != 0) {
+			int j, k;
+
+			for (j = 0; j < RAMPLEN; j++)
+				if (buf[j] != ramp[j])
+					break;
+			for (k = j; k < RAMPLEN && k < j + 16; k++)
+				fprintf(stderr,
+					"Byte %d: expected 0x%02x, got 0x%02x\n",
+					k, ramp[k], buf[k]);
+			die("Data mismatch, ramp %llu, byte %d\n", i, j);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	const char *subcmd = argv[1];
+	unsigned long long num;
+	uint8_t ramp[RAMPLEN];
+	char *e;
+	int i;
+
+	if (argc < 2)
+		usage();
+
+	errno = 0;
+	num = strtoull(argv[2], &e, 0);
+	if (*e || errno)
+		usage();
+
+	/* Initialize the ramp block */
+	for (i = 0; i < RAMPLEN; i++)
+		ramp[i] = i;
+
+	if (strcmp(subcmd, "send") == 0)
+		ramp_send(num, ramp);
+	else if (strcmp(subcmd, "check") == 0)
+		ramp_check(num, ramp);
+	else
+		usage();
+
+	exit(0);
+}
-- 
2.43.0


  parent reply	other threads:[~2025-02-12  1:19 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-12  1:19 [PATCH v17 0/8] Not-really-draft state migration Stefano Brivio
2025-02-12  1:19 ` [PATCH v17 1/8] migrate: Skeleton of live migration logic Stefano Brivio
2025-02-12  1:19 ` [PATCH v17 2/8] migrate: Migrate guest observed addresses Stefano Brivio
2025-02-12  1:19 ` [PATCH v17 3/8] Add interfaces and configuration bits for passt-repair Stefano Brivio
2025-02-12  1:19 ` [PATCH v17 4/8] vhost_user: Make source quit after reporting migration state Stefano Brivio
2025-02-12  1:19 ` [PATCH v17 5/8] tcp: Get bound address for connected inbound sockets too Stefano Brivio
2025-02-12  1:19 ` [PATCH v17 6/8] migrate: Migrate TCP flows Stefano Brivio
2025-02-12  1:19 ` Stefano Brivio [this message]
2025-02-12  1:19 ` [PATCH v17 8/8] test: Add migration tests Stefano Brivio

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250212011945.657249-8-sbrivio@redhat.com \
    --to=sbrivio@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --cc=passt-dev@passt.top \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).