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
next prev 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).